From 4441a0da41457955715afe6a2ff7093786c65dd8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 24 Feb 2018 08:01:16 -0700 Subject: [PATCH] Hash the constraint systems to check integrity. --- Cargo.toml | 2 + src/circuit/mod.rs | 178 ++++++++++---------------------------- src/circuit/test/mod.rs | 187 +++++++++++++++++++++++++++++++++++++++- src/lib.rs | 2 + 4 files changed, 234 insertions(+), 135 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a60e138..4999341 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ blake2 = "0.7" digest = "0.7" bellman = "0.0.8" +byteorder = "1" + [features] default = ["u128-support"] u128-support = ["pairing/u128-support"] diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 58acf1f..e1256ef 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -397,94 +397,6 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { } } -#[test] -fn test_input_circuit_with_bls12_381() { - use pairing::bls12_381::*; - use rand::{SeedableRng, Rng, XorShiftRng}; - use ::circuit::test::*; - use jubjub::{JubjubBls12, fs}; - - let params = &JubjubBls12::new(); - let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let tree_depth = 29; - - let value: u64 = 1; - let value_randomness: fs::Fs = rng.gen(); - let ak: edwards::Point = edwards::Point::rand(rng, params); - let g_d: edwards::Point = edwards::Point::rand(rng, params); - let commitment_randomness: fs::Fs = rng.gen(); - let rsk: fs::Fs = rng.gen(); - let auth_path = vec![Some((rng.gen(), rng.gen())); tree_depth]; - - { - let mut cs = TestConstraintSystem::::new(); - - let instance = Spend { - params: params, - value: Some(value), - value_randomness: Some(value_randomness), - rsk: Some(rsk), - ak: Some(ak), - g_d: Some(g_d), - commitment_randomness: Some(commitment_randomness), - auth_path: auth_path - }; - - instance.synthesize(&mut cs).unwrap(); - - assert!(cs.is_satisfied()); - - assert_eq!(cs.num_constraints(), 97379); - } - - // use bellman::groth16::*; - - // let groth_params = generate_random_parameters::(Spend { - // params: params, - // value: None, - // value_randomness: None, - // rsk: None, - // ak: None, - // g_d: None, - // commitment_randomness: None, - // auth_path: vec![None; 29] - // }, rng).unwrap(); - - // let pvk = prepare_verifying_key(&groth_params.vk); - - // use std::time::{Duration, Instant}; - - // // Let's benchmark stuff! - // const SAMPLES: u32 = 50; - // let mut total_proving = Duration::new(0, 0); - - // for _ in 0..SAMPLES { - // let start = Instant::now(); - // { - // let c = Spend { - // params: params, - // value: Some(1), - // value_randomness: Some(value_randomness.clone()), - // rsk: Some(rsk.clone()), - // ak: Some(ak.clone()), - // g_d: Some(g_d.clone()), - // commitment_randomness: Some(commitment_randomness.clone()), - // auth_path: auth_path.clone() - // }; - - // create_random_proof(c, &groth_params, rng).unwrap(); - // } - // total_proving += start.elapsed(); - // } - - // let proving_avg = total_proving / SAMPLES; - // let proving_avg = proving_avg.subsec_nanos() as f64 / 1_000_000_000f64 - // + (proving_avg.as_secs() as f64); - - // panic!("Average proving time: {:?} seconds", proving_avg); -} - /// This is an output circuit instance. pub struct Output<'a, E: JubjubEngine> { pub params: &'a E::Params, @@ -747,7 +659,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { } #[test] -fn test_output_circuit_with_bls12_381() { +fn test_input_circuit_with_bls12_381() { use pairing::bls12_381::*; use rand::{SeedableRng, Rng, XorShiftRng}; use ::circuit::test::*; @@ -756,6 +668,48 @@ fn test_output_circuit_with_bls12_381() { let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let tree_depth = 29; + + let value: u64 = 1; + let value_randomness: fs::Fs = rng.gen(); + let ak: edwards::Point = edwards::Point::rand(rng, params); + let g_d: edwards::Point = edwards::Point::rand(rng, params); + let commitment_randomness: fs::Fs = rng.gen(); + let rsk: fs::Fs = rng.gen(); + let auth_path = vec![Some((rng.gen(), rng.gen())); tree_depth]; + + { + let mut cs = TestConstraintSystem::::new(); + + let instance = Spend { + params: params, + value: Some(value), + value_randomness: Some(value_randomness), + rsk: Some(rsk), + ak: Some(ak), + g_d: Some(g_d), + commitment_randomness: Some(commitment_randomness), + auth_path: auth_path + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 97379); + assert_eq!(cs.hash(), "4d8e71c91a621e41599ea488ee89f035c892a260a595d3c85a20a82daa2d1654"); + } +} + +#[test] +fn test_output_circuit_with_bls12_381() { + use pairing::bls12_381::*; + use rand::{SeedableRng, Rng, XorShiftRng}; + use ::circuit::test::*; + use jubjub::{JubjubBls12, fs}; + + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let value: u64 = 1; let value_randomness: fs::Fs = rng.gen(); let g_d: edwards::Point = edwards::Point::rand(rng, params); @@ -779,51 +733,7 @@ fn test_output_circuit_with_bls12_381() { instance.synthesize(&mut cs).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 7827); + assert_eq!(cs.hash(), "225a2df7e21b9af8b436ffb9dadd645e4df843a5151c7481b0553422d5eaa793"); } - - // use bellman::groth16::*; - - // let groth_params = generate_random_parameters::(Output { - // params: params, - // value: None, - // value_randomness: None, - // g_d: None, - // p_d: None, - // commitment_randomness: None, - // esk: None - // }, rng).unwrap(); - - // let pvk = prepare_verifying_key(&groth_params.vk); - - // use std::time::{Duration, Instant}; - - // // Let's benchmark stuff! - // const SAMPLES: u32 = 50; - // let mut total_proving = Duration::new(0, 0); - - // for _ in 0..SAMPLES { - // let start = Instant::now(); - // { - // let c = Output { - // params: params, - // value: Some(1), - // value_randomness: Some(value_randomness), - // g_d: Some(g_d.clone()), - // p_d: Some(p_d.clone()), - // commitment_randomness: Some(commitment_randomness), - // esk: Some(esk.clone()) - // }; - - // create_random_proof(c, &groth_params, rng).unwrap(); - // } - // total_proving += start.elapsed(); - // } - - // let proving_avg = total_proving / SAMPLES; - // let proving_avg = proving_avg.subsec_nanos() as f64 / 1_000_000_000f64 - // + (proving_avg.as_secs() as f64); - - // panic!("Average proving time: {:?} seconds", proving_avg); } diff --git a/src/circuit/test/mod.rs b/src/circuit/test/mod.rs index ef042fe..e7f7515 100644 --- a/src/circuit/test/mod.rs +++ b/src/circuit/test/mod.rs @@ -1,6 +1,8 @@ use pairing::{ Engine, - Field + Field, + PrimeField, + PrimeFieldRepr }; use bellman::{ @@ -12,6 +14,13 @@ use bellman::{ }; use std::collections::HashMap; +use std::fmt::Write; + +use blake2::{Blake2s}; +use digest::{FixedOutput, Input}; +use byteorder::{BigEndian, ByteOrder}; +use std::cmp::Ordering; +use std::collections::BTreeMap; #[derive(Debug)] enum NamedObject { @@ -34,6 +43,90 @@ pub struct TestConstraintSystem { aux: Vec<(E::Fr, String)> } +#[derive(Clone, Copy)] +struct OrderedVariable(Variable); + +impl Eq for OrderedVariable {} +impl PartialEq for OrderedVariable { + fn eq(&self, other: &OrderedVariable) -> bool { + match (self.0.get_unchecked(), other.0.get_unchecked()) { + (Index::Input(ref a), Index::Input(ref b)) => a == b, + (Index::Aux(ref a), Index::Aux(ref b)) => a == b, + _ => false + } + } +} +impl PartialOrd for OrderedVariable { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for OrderedVariable { + fn cmp(&self, other: &Self) -> Ordering { + match (self.0.get_unchecked(), other.0.get_unchecked()) { + (Index::Input(ref a), Index::Input(ref b)) => a.cmp(b), + (Index::Aux(ref a), Index::Aux(ref b)) => a.cmp(b), + (Index::Input(_), Index::Aux(_)) => Ordering::Less, + (Index::Aux(_), Index::Input(_)) => Ordering::Greater + } + } +} + +fn proc_lc( + terms: &[(Variable, E::Fr)], +) -> BTreeMap +{ + let mut map = BTreeMap::new(); + for &(var, coeff) in terms { + map.entry(OrderedVariable(var)) + .or_insert(E::Fr::zero()) + .add_assign(&coeff); + } + + // Remove terms that have a zero coefficient to normalize + let mut to_remove = vec![]; + for (var, coeff) in map.iter() { + if coeff.is_zero() { + to_remove.push(var.clone()) + } + } + + for var in to_remove { + map.remove(&var); + } + + map +} + +fn hash_lc( + terms: &[(Variable, E::Fr)], + h: &mut Blake2s +) +{ + let map = proc_lc::(terms); + + let mut buf = [0u8; 9 + 32]; + BigEndian::write_u64(&mut buf[0..8], map.len() as u64); + h.process(&buf[0..8]); + + for (var, coeff) in map { + match var.0.get_unchecked() { + Index::Input(i) => { + buf[0] = b'I'; + BigEndian::write_u64(&mut buf[1..9], i as u64); + }, + Index::Aux(i) => { + buf[0] = b'A'; + BigEndian::write_u64(&mut buf[1..9], i as u64); + } + } + + coeff.into_repr().write_be(&mut buf[9..]).unwrap(); + + h.process(&buf); + } +} + fn eval_lc( terms: &[(Variable, E::Fr)], inputs: &[(E::Fr, String)], @@ -69,6 +162,98 @@ impl TestConstraintSystem { } } + pub fn pretty_print(&self) -> String { + let mut s = String::new(); + + let negone = { + let mut tmp = E::Fr::one(); + tmp.negate(); + tmp + }; + + let powers_of_two = (0..E::Fr::NUM_BITS).map(|i| { + E::Fr::from_str("2").unwrap().pow(&[i as u64]) + }).collect::>(); + + let pp = |s: &mut String, lc: &LinearCombination| { + write!(s, "(").unwrap(); + let mut is_first = true; + for (var, coeff) in proc_lc::(lc.as_ref()) { + if coeff == negone { + write!(s, " - ").unwrap(); + } else if !is_first { + write!(s, " + ").unwrap(); + } + is_first = false; + + if coeff != E::Fr::one() && coeff != negone { + for (i, x) in powers_of_two.iter().enumerate() { + if x == &coeff { + write!(s, "2^{} . ", i).unwrap(); + break; + } + } + + write!(s, "{} . ", coeff).unwrap(); + } + + match var.0.get_unchecked() { + Index::Input(i) => { + write!(s, "`{}`", &self.inputs[i].1).unwrap(); + }, + Index::Aux(i) => { + write!(s, "`{}`", &self.aux[i].1).unwrap(); + } + } + } + if is_first { + // Nothing was visited, print 0. + write!(s, "0").unwrap(); + } + write!(s, ")").unwrap(); + }; + + for &(ref a, ref b, ref c, ref name) in &self.constraints { + write!(&mut s, "\n").unwrap(); + + write!(&mut s, "{}: ", name).unwrap(); + pp(&mut s, a); + write!(&mut s, " * ").unwrap(); + pp(&mut s, b); + write!(&mut s, " = ").unwrap(); + pp(&mut s, c); + } + + write!(&mut s, "\n").unwrap(); + + s + } + + pub fn hash(&self) -> String { + let mut h = Blake2s::new_keyed(&[], 32); + { + let mut buf = [0u8; 24]; + + BigEndian::write_u64(&mut buf[0..8], self.inputs.len() as u64); + BigEndian::write_u64(&mut buf[8..16], self.aux.len() as u64); + BigEndian::write_u64(&mut buf[16..24], self.constraints.len() as u64); + h.process(&buf); + } + + for constraint in &self.constraints { + hash_lc::(constraint.0.as_ref(), &mut h); + hash_lc::(constraint.1.as_ref(), &mut h); + hash_lc::(constraint.2.as_ref(), &mut h); + } + + let mut s = String::new(); + for b in h.fixed_result().as_ref() { + s += &format!("{:02x}", b); + } + + s + } + pub fn which_is_unsatisfied(&self) -> Option<&str> { for &(ref a, ref b, ref c, ref path) in &self.constraints { let mut a = eval_lc::(a.as_ref(), &self.inputs, &self.aux); diff --git a/src/lib.rs b/src/lib.rs index 6e45309..60e5972 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ extern crate blake2; extern crate digest; extern crate rand; +extern crate byteorder; + pub mod jubjub; pub mod circuit; pub mod group_hash;