Merge pull request #53 from paritytech/sprout

Some crypto primitives for PGHR13 proofs verification
This commit is contained in:
Nikolay Volf 2019-03-15 18:43:46 +03:00 committed by GitHub
commit 9ac447fb76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 322 additions and 5 deletions

21
Cargo.lock generated
View File

@ -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"

View File

@ -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" }

View File

@ -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;

View File

@ -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");
}
// Shankss 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(),
)
);
}
}

View File

@ -83,6 +83,7 @@ mod equihash;
mod error;
mod sapling;
mod sigops;
mod sprout;
mod timestamp;
mod work;

100
verification/src/sprout.rs Normal file
View File

@ -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"),
);
}
}