Merge pull request #53 from paritytech/sprout
Some crypto primitives for PGHR13 proofs verification
This commit is contained in:
commit
9ac447fb76
|
@ -40,6 +40,11 @@ dependencies = [
|
|||
"xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.4.10"
|
||||
|
@ -126,6 +131,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"bellman 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"blake2-rfc 0.2.18 (git+https://github.com/gtank/blake2-rfc.git?rev=7a5b5fc99ae483a0043db7547fb79a6fa44b88a9)",
|
||||
"blake2b_simd 0.4.1 (git+https://github.com/oconnor663/blake2b_simd.git)",
|
||||
"bn 0.4.4 (git+https://github.com/paritytech/bn)",
|
||||
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pairing 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -154,10 +160,21 @@ dependencies = [
|
|||
"constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2b_simd"
|
||||
version = "0.4.1"
|
||||
source = "git+https://github.com/oconnor663/blake2b_simd.git#b75a0d10e39000fcae18a1f54fab89e2a9a0a1f6"
|
||||
dependencies = [
|
||||
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bn"
|
||||
version = "0.4.4"
|
||||
source = "git+https://github.com/paritytech/bn#2a71dbde5ca93451c8da2135767896a64483759e"
|
||||
source = "git+https://github.com/paritytech/bn#5a4cedc22d2fc556e2f2b30db47b792b572cac97"
|
||||
dependencies = [
|
||||
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crunchy 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1981,6 +1998,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
|
||||
"checksum app_dirs 1.2.1 (git+https://github.com/paritytech/app-dirs-rs)" = "<none>"
|
||||
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
|
||||
"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
|
||||
"checksum assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7deb0a829ca7bcfaf5da70b073a8d128619259a7be8216a355e23f00763059e5"
|
||||
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
|
||||
|
@ -1991,6 +2009,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f"
|
||||
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
|
||||
"checksum blake2-rfc 0.2.18 (git+https://github.com/gtank/blake2-rfc.git?rev=7a5b5fc99ae483a0043db7547fb79a6fa44b88a9)" = "<none>"
|
||||
"checksum blake2b_simd 0.4.1 (git+https://github.com/oconnor663/blake2b_simd.git)" = "<none>"
|
||||
"checksum bn 0.4.4 (git+https://github.com/paritytech/bn)" = "<none>"
|
||||
"checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d"
|
||||
"checksum bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "40ade3d27603c2cb345eb0912aec461a6dec7e06a4ae48589904e808335c7afa"
|
||||
|
|
|
@ -17,3 +17,4 @@ serde = "1.0"
|
|||
serde_derive = "1.0"
|
||||
rustc-hex = "2"
|
||||
lazy_static = "1.2.0"
|
||||
blake2b_simd = { git = "https://github.com/oconnor663/blake2b_simd.git" }
|
||||
|
|
|
@ -10,6 +10,7 @@ extern crate rustc_hex as hex;
|
|||
pub extern crate bellman;
|
||||
pub extern crate pairing;
|
||||
pub extern crate sapling_crypto;
|
||||
pub extern crate blake2b_simd as blake2;
|
||||
|
||||
#[macro_use] extern crate lazy_static;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
pub use bn::{Fr, G1, G2, Group};
|
||||
pub use bn::{Fr, Fq, Fq2, G1, G2, Group, arith::{U256, U512}, AffineG1, AffineG2};
|
||||
use bn::pairing;
|
||||
use std::ops::Neg;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VerifyingKey {
|
||||
|
@ -19,6 +20,17 @@ impl ::std::fmt::Debug for VerifyingKey {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidFieldElement,
|
||||
InvalidCurvePoint,
|
||||
InvalidRawInput,
|
||||
InvalidU256Encoding,
|
||||
InvalidU512Encoding,
|
||||
NotFqMember,
|
||||
NotFq2Member,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Proof {
|
||||
pub a: G1,
|
||||
|
@ -31,6 +43,121 @@ pub struct Proof {
|
|||
pub h: G1,
|
||||
}
|
||||
|
||||
impl Proof {
|
||||
pub fn from_raw(data: &[u8; 296]) -> Result<Self, Error> {
|
||||
Ok(Proof {
|
||||
a: g1_from_compressed(&data[0..33])?,
|
||||
a_prime: g1_from_compressed(&data[33..66])?,
|
||||
b: g2_from_compressed(&data[66..131])?,
|
||||
b_prime: g1_from_compressed(&data[131..164])?,
|
||||
c: g1_from_compressed(&data[164..197])?,
|
||||
c_prime: g1_from_compressed(&data[197..230])?,
|
||||
k: g1_from_compressed(&data[230..263])?,
|
||||
h: g1_from_compressed(&data[263..296])?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
// integer modulus for Fq field
|
||||
pub static ref FQ: U256 = U256::from([
|
||||
0x3c208c16d87cfd47,
|
||||
0x97816a916871ca8d,
|
||||
0xb85045b68181585d,
|
||||
0x30644e72e131a029
|
||||
]);
|
||||
|
||||
pub static ref G1_B: Fq = Fq::from_u256(3.into()).expect("3 is a valid field element and static; qed");
|
||||
|
||||
pub static ref FQ_MINUS3_DIV4: Fq =
|
||||
Fq::from_u256(3.into()).expect("3 is a valid field element and static; qed").neg() *
|
||||
Fq::from_u256(4.into()).expect("4 is a valid field element and static; qed").inverse()
|
||||
.expect("4 has inverse in Fq and is static; qed");
|
||||
|
||||
pub static ref FQ_MINUS1_DIV2: Fq =
|
||||
Fq::from_u256(1.into()).expect("1 is a valid field element and static; qed").neg() *
|
||||
Fq::from_u256(2.into()).expect("2 is a valid field element and static; qed").inverse()
|
||||
.expect("2 has inverse in Fq and is static; qed");
|
||||
|
||||
}
|
||||
|
||||
// Shanks’s algorithm for q ≡ 3 (mod 4)
|
||||
// (FQ mod 4 = 3)
|
||||
fn fq_sqrt(a: Fq) -> Option<Fq> {
|
||||
let a1 = a.pow(*FQ_MINUS3_DIV4);
|
||||
let a1a = a1 * a;
|
||||
let a0 = a1 * (a1a);
|
||||
|
||||
let mut am1 = *FQ;
|
||||
am1.sub(&1.into(), &*FQ);
|
||||
|
||||
if a0 == Fq::from_u256(am1).unwrap() {
|
||||
None
|
||||
} else {
|
||||
Some(a1a)
|
||||
}
|
||||
}
|
||||
|
||||
fn fq2_sqrt(a: Fq2) -> Option<Fq2> {
|
||||
let a1 = a.pow(FQ_MINUS3_DIV4.into_u256());
|
||||
let a1a = a1 * a;
|
||||
let alpha = a1 * a1a;
|
||||
let a0 = alpha.pow(*FQ) * alpha;
|
||||
|
||||
if a0 == Fq2::one().neg() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if alpha == Fq2::one().neg() {
|
||||
Some(Fq2::i() * a1a)
|
||||
} else {
|
||||
let b = (alpha + Fq2::one()).pow(FQ_MINUS1_DIV2.into_u256());
|
||||
Some(b * a1a)
|
||||
}
|
||||
}
|
||||
|
||||
fn g1_from_compressed(data: &[u8]) -> Result<G1, Error> {
|
||||
if data.len() != 33 { return Err(Error::InvalidRawInput); }
|
||||
|
||||
let sign = data[0];
|
||||
let fq = deserialize_fq(&data[1..])?;
|
||||
let x = fq;
|
||||
let y_squared = (fq * fq * fq) + *G1_B;
|
||||
let mut y = fq_sqrt(y_squared).ok_or(Error::InvalidFieldElement)?;
|
||||
|
||||
if sign == 2 { y = y.neg(); }
|
||||
|
||||
AffineG1::new(x, y).map_err(|_| Error::InvalidCurvePoint).map(Into::into)
|
||||
}
|
||||
|
||||
fn g2_from_compressed(data: &[u8]) -> Result<G2, Error> {
|
||||
if data.len() != 65 { return Err(Error::InvalidRawInput); }
|
||||
|
||||
let sign = data[0];
|
||||
let x = deserialize_fq2(&data[1..])?;
|
||||
|
||||
let y_squared = (x * x * x) + G2::b();
|
||||
|
||||
let mut y = fq2_sqrt(y_squared).ok_or(Error::InvalidFieldElement)?;
|
||||
if sign == 10 { y = y.neg(); }
|
||||
|
||||
AffineG2::new(x, y).map_err(|_| Error::InvalidCurvePoint).map(Into::into)
|
||||
}
|
||||
|
||||
fn deserialize_fq(data: &[u8]) -> Result<Fq, Error> {
|
||||
let u256 = U256::from_slice(data).map_err(|_| Error::InvalidU256Encoding)?;
|
||||
Ok(Fq::from_u256(u256).map_err(|_| Error::NotFqMember)?)
|
||||
}
|
||||
|
||||
fn deserialize_fq2(data: &[u8]) -> Result<Fq2, Error> {
|
||||
let u512 = U512::from_slice(data).map_err(|_| Error::InvalidU512Encoding)?;
|
||||
let (res, c0) = u512.divrem(&Fq::modulus());
|
||||
Ok(Fq2::new(
|
||||
Fq::from_u256(c0).map_err(|_| Error::NotFqMember)?,
|
||||
Fq::from_u256(res.ok_or(Error::NotFq2Member)?).map_err(|_| Error::NotFqMember)?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn verify(vk: &VerifyingKey, primary_input: &[Fr], proof: &Proof) -> bool {
|
||||
let p2 = G2::one();
|
||||
|
||||
|
@ -47,8 +174,76 @@ pub fn verify(vk: &VerifyingKey, primary_input: &[Fr], proof: &Proof) -> bool {
|
|||
|
||||
// 3. check same coefficients were used:
|
||||
pairing(proof.k, vk.gamma) ==
|
||||
pairing(acc + proof.a + proof.c, vk.gamma_beta_2) * pairing(vk.gamma_beta_1, proof.b) &&
|
||||
pairing(acc + proof.a + proof.c, vk.gamma_beta_2) * pairing(vk.gamma_beta_1, proof.b) &&
|
||||
// 4. check QAP divisibility
|
||||
pairing(acc + proof.a, proof.b) == pairing(proof.h, vk.z) * pairing(proof.c, p2)
|
||||
}
|
||||
|
||||
// 4. check QAP divisibility
|
||||
pairing(acc + proof.a, proof.b) == pairing(proof.h, vk.z) * pairing(proof.c, p2)
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
fn hex(s: &'static str) -> Vec<u8> {
|
||||
use hex::FromHex;
|
||||
s.from_hex().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqrt_fq() {
|
||||
// from zcash test_proof.cpp
|
||||
let fq1 = Fq::from_str("5204065062716160319596273903996315000119019512886596366359652578430118331601").unwrap();
|
||||
let fq2 = Fq::from_str("348579348568").unwrap();
|
||||
|
||||
assert_eq!(fq1, fq_sqrt(fq2).expect("348579348568 is quadratic residue"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqrt_fq2() {
|
||||
// from zcash test_proof.cpp
|
||||
let x1 = Fq2::new(
|
||||
Fq::from_str("12844195307879678418043983815760255909500142247603239203345049921980497041944").unwrap(),
|
||||
Fq::from_str("7476417578426924565731404322659619974551724117137577781074613937423560117731").unwrap(),
|
||||
);
|
||||
|
||||
let x2 = Fq2::new(
|
||||
Fq::from_str("3345897230485723946872934576923485762803457692345760237495682347502347589474").unwrap(),
|
||||
Fq::from_str("1234912378405347958234756902345768290345762348957605678245967234857634857676").unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(fq2_sqrt(x2).unwrap(), x1);
|
||||
|
||||
// i is sqrt(-1)
|
||||
assert_eq!(
|
||||
fq2_sqrt(Fq2::one().neg()).unwrap(),
|
||||
Fq2::i(),
|
||||
);
|
||||
|
||||
// no sqrt for (1 + 2i)
|
||||
assert!(
|
||||
fq2_sqrt(Fq2::new(Fq::from_str("1").unwrap(), Fq::from_str("2").unwrap())).is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn g1_deserialize() {
|
||||
let g1 = g1_from_compressed(&hex("0230644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46")).expect("Invalid g1 decompress result");
|
||||
assert_eq!(g1.x(), Fq::from_str("21888242871839275222246405745257275088696311157297823662689037894645226208582").unwrap());
|
||||
assert_eq!(g1.y(), Fq::from_str("3969792565221544645472939191694882283483352126195956956354061729942568608776").unwrap());
|
||||
assert_eq!(g1.z(), Fq::one());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn g2_deserialize() {
|
||||
let g2 = g2_from_compressed(
|
||||
&hex("0a023aed31b5a9e486366ea9988b05dba469c6206e58361d9c065bbea7d928204a761efc6e4fa08ed227650134b52c7f7dd0463963e8a4bf21f4899fe5da7f984a")
|
||||
).expect("Valid g2 point hex encoding");
|
||||
|
||||
assert_eq!(g2.x(),
|
||||
Fq2::new(
|
||||
Fq::from_str("5923585509243758863255447226263146374209884951848029582715967108651637186684").unwrap(),
|
||||
Fq::from_str("5336385337059958111259504403491065820971993066694750945459110579338490853570").unwrap(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -83,6 +83,7 @@ mod equihash;
|
|||
mod error;
|
||||
mod sapling;
|
||||
mod sigops;
|
||||
mod sprout;
|
||||
mod timestamp;
|
||||
mod work;
|
||||
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
|
||||
// blake2 hash of (random_seed, nullifier[0], nullifier[1], pub_key_hash) with 'ZcashComputehSig' personal token
|
||||
pub fn compute_hsig(random_seed: [u8; 32], nullifiers: [[u8; 32]; 2], pub_key_hash: [u8; 32]) -> [u8; 32] {
|
||||
use crypto::blake2::Params;
|
||||
|
||||
let res = Params::new()
|
||||
.hash_length(32)
|
||||
.personal(b"ZcashComputehSig")
|
||||
.to_state()
|
||||
.update(&random_seed[..])
|
||||
.update(&nullifiers[0][..])
|
||||
.update(&nullifiers[1][..])
|
||||
.update(&pub_key_hash[..])
|
||||
.finalize();
|
||||
|
||||
let mut result = [0u8; 32];
|
||||
result.copy_from_slice(res.as_bytes());
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::compute_hsig;
|
||||
|
||||
fn hash(s: &'static str) -> [u8; 32] {
|
||||
use hex::FromHex;
|
||||
let mut bytes: Vec<u8> = s.from_hex().expect(&format!("hash '{}' is not actually a hash somehow", s));
|
||||
bytes.reverse();
|
||||
assert_eq!(bytes.len(), 32);
|
||||
let mut result = [0u8; 32];
|
||||
result.copy_from_slice(&bytes[..]);
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vectors() {
|
||||
assert_eq!(
|
||||
compute_hsig(
|
||||
hash("6161616161616161616161616161616161616161616161616161616161616161"),
|
||||
[
|
||||
hash("6262626262626262626262626262626262626262626262626262626262626262"),
|
||||
hash("6363636363636363636363636363636363636363636363636363636363636363"),
|
||||
],
|
||||
hash("6464646464646464646464646464646464646464646464646464646464646464"),
|
||||
),
|
||||
hash("a8cba69f1fa329c055756b4af900f8a00b61e44f4cb8a1824ceb58b90a5b8113"),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
compute_hsig(
|
||||
hash("0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
[
|
||||
hash("0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
hash("0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
],
|
||||
hash("0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
),
|
||||
hash("697322276b5dd93b12fb1fcbd2144b2960f24c73aac6c6a0811447be1e7f1e19"),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
compute_hsig(
|
||||
hash("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"),
|
||||
[
|
||||
hash("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"),
|
||||
hash("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"),
|
||||
],
|
||||
hash("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"),
|
||||
),
|
||||
hash("b61110ec162693bc3d9ca7fb0eec3afd2e278e2f41394b3ff11d7cb761ad4b27"),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
compute_hsig(
|
||||
hash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
[
|
||||
hash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
hash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
],
|
||||
hash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
||||
),
|
||||
hash("4961048919f0ca79d49c9378c36a91a8767060001f4212fe6f7d426f3ccf9f32"),
|
||||
);
|
||||
|
||||
|
||||
assert_eq!(
|
||||
compute_hsig(
|
||||
hash("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"),
|
||||
[
|
||||
hash("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"),
|
||||
hash("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"),
|
||||
],
|
||||
hash("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"),
|
||||
),
|
||||
hash("b61110ec162693bc3d9ca7fb0eec3afd2e278e2f41394b3ff11d7cb761ad4b27"),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue