From 4b6623cf44ed8020845f67539bb6cefeb7539e48 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 22 Feb 2018 07:29:55 -0700 Subject: [PATCH 01/13] Add some personalization and a generator. --- src/circuit/pedersen_hash.rs | 5 ++++- src/jubjub/mod.rs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 9dfb460..7eec3bb 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -13,7 +13,8 @@ use super::lookup::*; // TODO: ensure these match the spec pub enum Personalization { NoteCommitment, - AnotherPersonalization + AnotherPersonalization, + MerkleTree(usize) } impl Personalization { @@ -30,6 +31,8 @@ impl Personalization { vec![false, false, false, false, false, false], Personalization::AnotherPersonalization => vec![false, false, false, false, false, true], + Personalization::MerkleTree(_) => + vec![false, false, false, false, true, false], } } } diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 13495bc..4782d90 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -71,7 +71,8 @@ pub enum FixedGenerators { ProvingPublicKey = 1, ValueCommitmentValue = 2, ValueCommitmentRandomness = 3, - Max = 4 + NullifierPosition = 4, + Max = 5 } pub struct JubjubBls12 { From ba7298de3ff629c0326969189fd922f177e558b5 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 22 Feb 2018 11:36:44 -0700 Subject: [PATCH 02/13] Crappy mock-up of the circuit. --- src/circuit/mod.rs | 832 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 831 insertions(+), 1 deletion(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index cf27c34..073926a 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -9,7 +9,23 @@ pub mod lookup; pub mod ecc; pub mod pedersen_hash; -use bellman::SynthesisError; +use pairing::{ + PrimeField, + PrimeFieldRepr, +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + Circuit +}; + +use jubjub::{ + JubjubEngine, + Unknown, + FixedGenerators, + edwards +}; trait Assignment { fn get(&self) -> Result<&T, SynthesisError>; @@ -23,3 +39,817 @@ impl Assignment for Option { } } } + +const MERKLE_TREE_DEPTH: usize = 29; + +pub struct Spend<'a, E: JubjubEngine> { + pub params: &'a E::Params, + /// Value of the note being spent + pub value: Option, + /// Randomness that will hide the value + pub value_randomness: Option, + /// Key which allows the proof to be constructed + /// as defense-in-depth against a flaw in the + /// protocol that would otherwise be exploitable + /// by a holder of a viewing key. + pub rsk: Option, + /// The public key that will be re-randomized for + /// use as a nullifier and signing key for the + /// transaction. + pub ak: Option>, + /// The diversified base used to compute pk_d. + pub g_d: Option>, + /// The randomness used to hide the note commitment data + pub commitment_randomness: Option, + /// The authentication path of the commitment in the tree + pub auth_path: Vec> +} + +impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> + { + // Booleanize the value into little-endian bit order + let value_bits = boolean::u64_into_allocated_bits_be( + cs.namespace(|| "value"), + self.value + )? + .into_iter() + .rev() // Little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + { + let gv = ecc::fixed_base_multiplication( + cs.namespace(|| "compute the value in the exponent"), + FixedGenerators::ValueCommitmentValue, + &value_bits, + self.params + )?; + + // Booleanize the randomness + let hr = boolean::field_into_allocated_bits_be( + cs.namespace(|| "hr"), + self.value_randomness + )? + .into_iter() + .rev() // Little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let hr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of randomization for value commitment"), + FixedGenerators::ValueCommitmentRandomness, + &hr, + self.params + )?; + + let gvhr = gv.add( + cs.namespace(|| "computation of value commitment"), + &hr, + self.params + )?; + + // Expose the value commitment publicly + let value_commitment_x = cs.alloc_input( + || "value commitment x", + || { + Ok(*gvhr.x.get_value().get()?) + } + )?; + + cs.enforce( + || "value commitment x equals input", + |lc| lc + value_commitment_x, + |lc| lc + CS::one(), + |lc| lc + gvhr.x.get_variable() + ); + + let value_commitment_y = cs.alloc_input( + || "value commitment y", + || { + Ok(*gvhr.y.get_value().get()?) + } + )?; + + cs.enforce( + || "value commitment y equals input", + |lc| lc + value_commitment_y, + |lc| lc + CS::one(), + |lc| lc + gvhr.y.get_variable() + ); + } + + // Compute rk = [rsk] ProvingPublicKey + let rk; + { + // Witness rsk as bits + let rsk = boolean::field_into_allocated_bits_be( + cs.namespace(|| "rsk"), + self.rsk + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)).collect::>(); + + // NB: We don't ensure that the bit representation of rsk + // is "in the field" (Fs) because it's not used except to + // demonstrate the prover knows it. If they know a + // congruency then that's equivalent. + + rk = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of rk"), + FixedGenerators::ProvingPublicKey, + &rsk, + self.params + )?; + } + + // Prover witnesses ak (ensures that it's on the curve) + let ak = ecc::EdwardsPoint::witness( + cs.namespace(|| "ak"), + self.ak, + self.params + )?; + + // Unpack ak and rk for input to BLAKE2s + let mut vk = vec![]; + { + let mut ak_x = ak.x.into_bits_strict( + cs.namespace(|| "unpack ak.x") + )?; + let mut ak_y = ak.y.into_bits_strict( + cs.namespace(|| "unpack ak.y") + )?; + + // We want the representation in little endian bit order + ak_x.reverse(); + ak_y.reverse(); + + vk.extend(ak_y); + vk.push(ak_x[0].clone()); + } + let mut rho_preimage = vec![]; + { + let mut rk_x = rk.x.into_bits_strict( + cs.namespace(|| "unpack rk.x") + )?; + let mut rk_y = rk.y.into_bits_strict( + cs.namespace(|| "unpack rk.y") + )?; + + // We want the representation in little endian bit order + rk_x.reverse(); + rk_y.reverse(); + + vk.extend(rk_y.iter().cloned()); + vk.push(rk_x[0].clone()); + + rho_preimage.extend(rk_y.iter().cloned()); + rho_preimage.push(rk_x[0].clone()); + } + + assert_eq!(vk.len(), 512); + + // Compute the incoming viewing key + let mut ivk = blake2s::blake2s( + cs.namespace(|| "computation of ivk"), + &vk + )?; + + // Little endian bit order + ivk.reverse(); + ivk.truncate(251); // drop_5 + + // Witness g_d + let g_d = ecc::EdwardsPoint::witness( + cs.namespace(|| "witness g_d"), + self.g_d, + self.params + )?; + + // Compute pk_d + let pk_d = g_d.mul( + cs.namespace(|| "compute pk_d"), + &ivk, + self.params + )?; + + // Compute note contents + let mut note_contents = vec![]; + note_contents.extend(value_bits); + { + // Unpack g_d for inclusion in the note. + let mut g_d_x = g_d.x.into_bits_strict( + cs.namespace(|| "unpack g_d.x") + )?; + let mut g_d_y = g_d.y.into_bits_strict( + cs.namespace(|| "unpack g_d.y") + )?; + + // We want the representation in little endian bit order + g_d_x.reverse(); + g_d_y.reverse(); + + note_contents.extend(g_d_y); + note_contents.push(g_d_x[0].clone()); + } + { + // Unpack g_d for inclusion in the note. + let mut pk_d_x = pk_d.x.into_bits_strict( + cs.namespace(|| "unpack pk_d.x") + )?; + let mut pk_d_y = pk_d.y.into_bits_strict( + cs.namespace(|| "unpack pk_d.y") + )?; + + // We want the representation in little endian bit order + pk_d_x.reverse(); + pk_d_y.reverse(); + + note_contents.extend(pk_d_y); + note_contents.push(pk_d_x[0].clone()); + } + + assert_eq!( + note_contents.len(), + 64 + // value + 256 + // g_d + 256 // p_d + ); + + // Compute the hash of the note contents + let mut cm = pedersen_hash::pedersen_hash( + cs.namespace(|| "note content hash"), + pedersen_hash::Personalization::NoteCommitment, + ¬e_contents, + self.params + )?; + + { + // Booleanize the randomness + let cmr = boolean::field_into_allocated_bits_be( + cs.namespace(|| "cmr"), + self.commitment_randomness + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let cmr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of commitment randomness"), + FixedGenerators::NoteCommitmentRandomness, + &cmr, + self.params + )?; + + cm = cm.add( + cs.namespace(|| "randomization of note commitment"), + &cmr, + self.params + )?; + } + + assert_eq!(self.auth_path.len(), MERKLE_TREE_DEPTH); + + let mut position_bits = vec![]; + + // Injective encoding. + let mut cur = cm.x.clone(); + + for (i, e) in self.auth_path.into_iter().enumerate() { + let cs = &mut cs.namespace(|| format!("merkle tree hash {}", i)); + + let cur_is_right = boolean::Boolean::from(boolean::AllocatedBit::alloc( + cs.namespace(|| "position bit"), + e.map(|e| e.1) + )?); + + position_bits.push(cur_is_right.clone()); + + let path_element = num::AllocatedNum::alloc( + cs.namespace(|| "path element"), + || { + Ok(e.get()?.0) + } + )?; + + let (xl, xr) = num::AllocatedNum::conditionally_reverse( + cs.namespace(|| "conditional reversal of preimage"), + &cur, + &path_element, + &cur_is_right + )?; + + // We don't need to be strict, because the function is + // collision-resistant. + let mut preimage = vec![]; + preimage.extend(xl.into_bits(cs.namespace(|| "xl into bits"))?); + preimage.extend(xr.into_bits(cs.namespace(|| "xr into bits"))?); + + cur = pedersen_hash::pedersen_hash( + cs.namespace(|| "computation of pedersen hash"), + pedersen_hash::Personalization::MerkleTree(MERKLE_TREE_DEPTH - i), + &preimage, + self.params + )?.x; // Injective encoding + } + + assert_eq!(position_bits.len(), MERKLE_TREE_DEPTH); + + // TODO: cur is now the root of the tree, expose it as public input + + let tmp = ecc::fixed_base_multiplication( + cs.namespace(|| "g^position"), + FixedGenerators::NullifierPosition, + &position_bits, + self.params + )?; + + cm = cm.add( + cs.namespace(|| "faerie gold prevention"), + &tmp, + self.params + )?; + + // Let's compute rho = BLAKE2s(rk || cm + position) + { + // Unpack g_d for inclusion in the note. + let mut cm_x = cm.x.into_bits_strict( + cs.namespace(|| "unpack (cm + position).x") + )?; + let mut cm_y = cm.y.into_bits_strict( + cs.namespace(|| "unpack (cm + position).y") + )?; + + // We want the representation in little endian bit order + cm_x.reverse(); + cm_y.reverse(); + + rho_preimage.extend(cm_y); + rho_preimage.push(cm_x[0].clone()); + } + + let mut rho = blake2s::blake2s( + cs.namespace(|| "rho computation"), + &rho_preimage + )?; + + // Little endian bit order + rho.reverse(); + rho.truncate(251); // drop_5 + + // Compute nullifier + let nf = ak.mul( + cs.namespace(|| "computation of nf"), + &rho, + self.params + )?; + + // TODO: expose nf as public input + + Ok(()) + } +} + +#[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 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 p_d: edwards::Point = edwards::Point::rand(rng, params); + let commitment_randomness: fs::Fs = rng.gen(); + let esk: fs::Fs = rng.gen(); + let rsk: fs::Fs = rng.gen(); + let auth_path = vec![Some((rng.gen(), false)); 29]; + + { + let mut cs = TestConstraintSystem::::new(); + + let instance = Spend { + params: params, + value: Some(1), + 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(), 99816); + } + + // 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, + /// Value of the note being created + pub value: Option, + /// Randomness that will hide the value + pub value_randomness: Option, + /// The diversified base, computed by GH(d) + pub g_d: Option>, + /// The diversified address point, computed by GH(d)^ivk + pub p_d: Option>, + /// The randomness used to hide the note commitment data + pub commitment_randomness: Option, + /// The ephemeral secret key for DH with recipient + pub esk: Option +} + +impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> + { + // Booleanize the value into little-endian bit order + let value_bits = boolean::u64_into_allocated_bits_be( + cs.namespace(|| "value"), + self.value + )? + .into_iter() + .rev() // Little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + { + let gv = ecc::fixed_base_multiplication( + cs.namespace(|| "compute the value in the exponent"), + FixedGenerators::ValueCommitmentValue, + &value_bits, + self.params + )?; + + // Booleanize the randomness + let hr = boolean::field_into_allocated_bits_be( + cs.namespace(|| "hr"), + self.value_randomness + )? + .into_iter() + .rev() // Little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let hr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of randomization for value commitment"), + FixedGenerators::ValueCommitmentRandomness, + &hr, + self.params + )?; + + let gvhr = gv.add( + cs.namespace(|| "computation of value commitment"), + &hr, + self.params + )?; + + // Expose the value commitment publicly + let value_commitment_x = cs.alloc_input( + || "value commitment x", + || { + Ok(*gvhr.x.get_value().get()?) + } + )?; + + cs.enforce( + || "value commitment x equals input", + |lc| lc + value_commitment_x, + |lc| lc + CS::one(), + |lc| lc + gvhr.x.get_variable() + ); + + let value_commitment_y = cs.alloc_input( + || "value commitment y", + || { + Ok(*gvhr.y.get_value().get()?) + } + )?; + + cs.enforce( + || "value commitment y equals input", + |lc| lc + value_commitment_y, + |lc| lc + CS::one(), + |lc| lc + gvhr.y.get_variable() + ); + } + + // Let's start to construct our note + let mut note_contents = vec![]; + note_contents.extend(value_bits); + + // Let's deal with g_d + { + let g_d = ecc::EdwardsPoint::witness( + cs.namespace(|| "witness g_d"), + self.g_d, + self.params + )?; + + // Check that g_d is not of small order + { + let g_d = g_d.double( + cs.namespace(|| "first doubling of g_d"), + self.params + )?; + let g_d = g_d.double( + cs.namespace(|| "second doubling of g_d"), + self.params + )?; + let g_d = g_d.double( + cs.namespace(|| "third doubling of g_d"), + self.params + )?; + + // (0, -1) is a small order point, but won't ever appear here + // because cofactor is 2^3, and we performed three doublings. + // (0, 1) is the neutral element, so checking if x is nonzero + // is sufficient to prevent small order points here. + g_d.x.assert_nonzero(cs.namespace(|| "check not inf"))?; + } + + // Unpack g_d for inclusion in the note. + let mut g_d_x = g_d.x.into_bits_strict( + cs.namespace(|| "unpack g_d.x") + )?; + let mut g_d_y = g_d.y.into_bits_strict( + cs.namespace(|| "unpack g_d.y") + )?; + + // We want the representation in little endian bit order + g_d_x.reverse(); + g_d_y.reverse(); + + note_contents.extend(g_d_y); + note_contents.push(g_d_x[0].clone()); + + // Compute epk from esk + let esk = boolean::field_into_allocated_bits_be( + cs.namespace(|| "esk"), + self.esk + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let epk = g_d.mul( + cs.namespace(|| "epk computation"), + &esk, + self.params + )?; + + // Expose epk publicly + let epk_x = cs.alloc_input( + || "epk x", + || { + Ok(*epk.x.get_value().get()?) + } + )?; + + cs.enforce( + || "epk x equals input", + |lc| lc + epk_x, + |lc| lc + CS::one(), + |lc| lc + epk.x.get_variable() + ); + + let epk_y = cs.alloc_input( + || "epk y", + || { + Ok(*epk.y.get_value().get()?) + } + )?; + + cs.enforce( + || "epk y equals input", + |lc| lc + epk_y, + |lc| lc + CS::one(), + |lc| lc + epk.y.get_variable() + ); + } + + // Now let's deal with p_d. We don't do any checks and + // essentially allow the prover to witness any 256 bits + // they would like. + { + let p_d = self.p_d.map(|e| e.into_xy()); + + let y_contents = boolean::field_into_allocated_bits_be( + cs.namespace(|| "p_d bits of y"), + p_d.map(|e| e.1) + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc( + cs.namespace(|| "p_d bit of x"), + p_d.map(|e| e.0.into_repr().is_odd()) + )?); + + note_contents.extend(y_contents); + note_contents.push(sign_bit); + } + + assert_eq!( + note_contents.len(), + 64 + // value + 256 + // g_d + 256 // p_d + ); + + // Compute the hash of the note contents + let mut cm = pedersen_hash::pedersen_hash( + cs.namespace(|| "note content hash"), + pedersen_hash::Personalization::NoteCommitment, + ¬e_contents, + self.params + )?; + + { + // Booleanize the randomness + let cmr = boolean::field_into_allocated_bits_be( + cs.namespace(|| "cmr"), + self.commitment_randomness + )? + .into_iter() + .rev() // We need it in little endian bit order + .map(|e| boolean::Boolean::from(e)) + .collect::>(); + + let cmr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of commitment randomness"), + FixedGenerators::NoteCommitmentRandomness, + &cmr, + self.params + )?; + + cm = cm.add( + cs.namespace(|| "randomization of note commitment"), + &cmr, + self.params + )?; + } + + // Only the x-coordinate of the output is revealed, + // since we know it is prime order, and we know that + // the x-coordinate is an injective encoding for + // prime-order elements. + let commitment_input = cs.alloc_input( + || "commitment input", + || { + Ok(*cm.x.get_value().get()?) + } + )?; + + cs.enforce( + || "commitment input correct", + |lc| lc + commitment_input, + |lc| lc + CS::one(), + |lc| lc + cm.x.get_variable() + ); + + Ok(()) + } +} + +#[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([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let value: u64 = 1; + let value_randomness: fs::Fs = rng.gen(); + let g_d: edwards::Point = edwards::Point::rand(rng, params); + let p_d: edwards::Point = edwards::Point::rand(rng, params); + let commitment_randomness: fs::Fs = rng.gen(); + let esk: fs::Fs = rng.gen(); + + { + let mut cs = TestConstraintSystem::::new(); + + let instance = 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()) + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(cs.num_constraints(), 8315); + } + + // 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); +} From c812805b3139abfeec36bc5caa02e77397873a28 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 22 Feb 2018 18:43:07 -0700 Subject: [PATCH 03/13] Slight refactor of representations --- src/circuit/ecc.rs | 27 +++++++++ src/circuit/mod.rs | 137 +++++++++++---------------------------------- 2 files changed, 61 insertions(+), 103 deletions(-) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index fbb9b72..79b2813 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -92,6 +92,33 @@ pub fn fixed_base_multiplication( } impl EdwardsPoint { + /// This converts the point into a representation. + pub fn repr( + &self, + mut cs: CS + ) -> Result, SynthesisError> + where CS: ConstraintSystem + { + let mut tmp = vec![]; + + let mut x = self.x.into_bits_strict( + cs.namespace(|| "unpack x") + )?; + + let mut y = self.y.into_bits_strict( + cs.namespace(|| "unpack y") + )?; + + // We want the representation in little endian bit order + x.reverse(); + y.reverse(); + + tmp.extend(y); + tmp.push(x[0].clone()); + + Ok(tmp) + } + /// This 'witnesses' a point inside the constraint system. /// It guarantees the point is on the curve. pub fn witness( diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 073926a..96a9c08 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -173,39 +173,17 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Unpack ak and rk for input to BLAKE2s let mut vk = vec![]; - { - let mut ak_x = ak.x.into_bits_strict( - cs.namespace(|| "unpack ak.x") - )?; - let mut ak_y = ak.y.into_bits_strict( - cs.namespace(|| "unpack ak.y") - )?; - - // We want the representation in little endian bit order - ak_x.reverse(); - ak_y.reverse(); - - vk.extend(ak_y); - vk.push(ak_x[0].clone()); - } let mut rho_preimage = vec![]; + vk.extend( + ak.repr(cs.namespace(|| "representation of ak"))? + ); { - let mut rk_x = rk.x.into_bits_strict( - cs.namespace(|| "unpack rk.x") - )?; - let mut rk_y = rk.y.into_bits_strict( - cs.namespace(|| "unpack rk.y") + let repr_rk = rk.repr( + cs.namespace(|| "representation of rk") )?; - // We want the representation in little endian bit order - rk_x.reverse(); - rk_y.reverse(); - - vk.extend(rk_y.iter().cloned()); - vk.push(rk_x[0].clone()); - - rho_preimage.extend(rk_y.iter().cloned()); - rho_preimage.push(rk_x[0].clone()); + vk.extend(repr_rk.iter().cloned()); + rho_preimage.extend(repr_rk); } assert_eq!(vk.len(), 512); @@ -218,7 +196,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Little endian bit order ivk.reverse(); - ivk.truncate(251); // drop_5 + ivk.truncate(E::Fs::CAPACITY as usize); // drop_5 // Witness g_d let g_d = ecc::EdwardsPoint::witness( @@ -237,38 +215,12 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Compute note contents let mut note_contents = vec![]; note_contents.extend(value_bits); - { - // Unpack g_d for inclusion in the note. - let mut g_d_x = g_d.x.into_bits_strict( - cs.namespace(|| "unpack g_d.x") - )?; - let mut g_d_y = g_d.y.into_bits_strict( - cs.namespace(|| "unpack g_d.y") - )?; - - // We want the representation in little endian bit order - g_d_x.reverse(); - g_d_y.reverse(); - - note_contents.extend(g_d_y); - note_contents.push(g_d_x[0].clone()); - } - { - // Unpack g_d for inclusion in the note. - let mut pk_d_x = pk_d.x.into_bits_strict( - cs.namespace(|| "unpack pk_d.x") - )?; - let mut pk_d_y = pk_d.y.into_bits_strict( - cs.namespace(|| "unpack pk_d.y") - )?; - - // We want the representation in little endian bit order - pk_d_x.reverse(); - pk_d_y.reverse(); - - note_contents.extend(pk_d_y); - note_contents.push(pk_d_x[0].clone()); - } + note_contents.extend( + g_d.repr(cs.namespace(|| "representation of g_d"))? + ); + note_contents.extend( + pk_d.repr(cs.namespace(|| "representation of pk_d"))? + ); assert_eq!( note_contents.len(), @@ -359,36 +311,26 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // TODO: cur is now the root of the tree, expose it as public input - let tmp = ecc::fixed_base_multiplication( - cs.namespace(|| "g^position"), - FixedGenerators::NullifierPosition, - &position_bits, - self.params - )?; + { + let position = ecc::fixed_base_multiplication( + cs.namespace(|| "g^position"), + FixedGenerators::NullifierPosition, + &position_bits, + self.params + )?; - cm = cm.add( - cs.namespace(|| "faerie gold prevention"), - &tmp, - self.params - )?; + cm = cm.add( + cs.namespace(|| "faerie gold prevention"), + &position, + self.params + )?; + } + // Let's compute rho = BLAKE2s(rk || cm + position) - { - // Unpack g_d for inclusion in the note. - let mut cm_x = cm.x.into_bits_strict( - cs.namespace(|| "unpack (cm + position).x") - )?; - let mut cm_y = cm.y.into_bits_strict( - cs.namespace(|| "unpack (cm + position).y") - )?; - - // We want the representation in little endian bit order - cm_x.reverse(); - cm_y.reverse(); - - rho_preimage.extend(cm_y); - rho_preimage.push(cm_x[0].clone()); - } + rho_preimage.extend( + cm.repr(cs.namespace(|| "representation of cm"))? + ); let mut rho = blake2s::blake2s( cs.namespace(|| "rho computation"), @@ -625,20 +567,9 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { g_d.x.assert_nonzero(cs.namespace(|| "check not inf"))?; } - // Unpack g_d for inclusion in the note. - let mut g_d_x = g_d.x.into_bits_strict( - cs.namespace(|| "unpack g_d.x") - )?; - let mut g_d_y = g_d.y.into_bits_strict( - cs.namespace(|| "unpack g_d.y") - )?; - - // We want the representation in little endian bit order - g_d_x.reverse(); - g_d_y.reverse(); - - note_contents.extend(g_d_y); - note_contents.push(g_d_x[0].clone()); + note_contents.extend( + g_d.repr(cs.namespace(|| "representation of g_d"))? + ); // Compute epk from esk let esk = boolean::field_into_allocated_bits_be( From 97585a30ad45987fab41888de6077853d6f1749b Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 09:26:12 -0700 Subject: [PATCH 04/13] Remove from_bits_strict from AllocatedNum. --- src/circuit/num.rs | 120 --------------------------------------------- 1 file changed, 120 deletions(-) diff --git a/src/circuit/num.rs b/src/circuit/num.rs index fe20050..c24fb09 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -101,75 +101,6 @@ impl AllocatedNum { Ok(bits.into_iter().map(|b| Boolean::from(b)).collect()) } - pub fn from_bits_strict( - mut cs: CS, - bits: &[Boolean] - ) -> Result - where CS: ConstraintSystem - { - assert_eq!(bits.len(), E::Fr::NUM_BITS as usize); - - Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, bits)?; - - let one = CS::one(); - let mut lc = LinearCombination::::zero(); - let mut coeff = E::Fr::one(); - let mut value = Some(E::Fr::zero()); - for bit in bits.iter().rev() { - match bit { - &Boolean::Constant(false) => {}, - &Boolean::Constant(true) => { - value.as_mut().map(|value| value.add_assign(&coeff)); - - lc = lc + (coeff, one); - }, - &Boolean::Is(ref bit) => { - match bit.get_value() { - Some(bit) => { - if bit { - value.as_mut().map(|value| value.add_assign(&coeff)); - } - }, - None => { - value = None; - } - } - - lc = lc + (coeff, bit.get_variable()); - }, - &Boolean::Not(ref bit) => { - match bit.get_value() { - Some(bit) => { - if !bit { - value.as_mut().map(|value| value.add_assign(&coeff)); - } - }, - None => { - value = None; - } - } - - lc = lc + (coeff, one) - (coeff, bit.get_variable()); - } - } - - coeff.double(); - } - - let num = Self::alloc(&mut cs, || value.get().map(|v| *v))?; - - lc = lc - num.get_variable(); - - cs.enforce( - || "packing constraint", - |lc| lc, - |lc| lc, - |_| lc - ); - - Ok(num) - } - pub fn mul( &self, mut cs: CS, @@ -544,55 +475,4 @@ mod test { } } } - - #[test] - fn test_from_bits_strict() { - { - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(Fr::char()).skip(1).enumerate() { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } - - let num = AllocatedNum::from_bits_strict(&mut cs, &bits).unwrap(); - assert!(num.value.unwrap().is_zero()); - assert!(!cs.is_satisfied()); - } - - let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - for _ in 0..1000 { - let r = Fr::rand(&mut rng); - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() { - let parity: bool = rng.gen(); - - if parity { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } else { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(!b) - ).unwrap()).not()); - } - } - - let num = AllocatedNum::from_bits_strict(&mut cs, &bits).unwrap(); - assert!(cs.is_satisfied()); - assert_eq!(num.value.unwrap(), r); - assert_eq!(cs.get("num"), r); - - cs.set("num", Fr::rand(&mut rng)); - assert_eq!(cs.which_is_unsatisfied().unwrap(), "packing constraint"); - } - } } From 69abd0391f7faa8f1d95afdc292d2584decc192b Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 11:15:14 -0700 Subject: [PATCH 05/13] Optimize into_bits_strict. --- src/circuit/boolean.rs | 38 +++++++++++ src/circuit/mod.rs | 4 +- src/circuit/num.rs | 148 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 170 insertions(+), 20 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 23ec447..48d243d 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -34,6 +34,44 @@ impl AllocatedBit { self.variable } + /// Allocate a variable in the constraint system which can only be a + /// boolean value. Further, constrain that the boolean is false + /// unless the condition is false. + pub fn alloc_conditionally( + mut cs: CS, + value: Option, + must_be_false: &AllocatedBit + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + let var = cs.alloc(|| "boolean", || { + if *value.get()? { + Ok(E::Fr::one()) + } else { + Ok(E::Fr::zero()) + } + })?; + + // Constrain: (1 - must_be_false - a) * a = 0 + // if must_be_false is true, the equation + // reduces to -a * a = 0, which implies a = 0. + // if must_be_false is false, the equation + // reduces to (1 - a) * a = 0, which is a + // traditional boolean constraint. + cs.enforce( + || "boolean constraint", + |lc| lc + CS::one() - must_be_false.variable - var, + |lc| lc + var, + |lc| lc + ); + + Ok(AllocatedBit { + variable: var, + value: value + }) + } + /// Allocate a variable in the constraint system which can only be a /// boolean value. pub fn alloc( diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 96a9c08..84d768b 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -392,7 +392,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 99816); + assert_eq!(cs.num_constraints(), 97376); } // use bellman::groth16::*; @@ -737,7 +737,7 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 8315); + assert_eq!(cs.num_constraints(), 7827); } // use bellman::groth16::*; diff --git a/src/circuit/num.rs b/src/circuit/num.rs index c24fb09..92e25c7 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -2,6 +2,8 @@ use pairing::{ Engine, Field, PrimeField, + PrimeFieldRepr, + BitIterator }; use bellman::{ @@ -18,6 +20,7 @@ use super::{ use super::boolean::{ self, Boolean, + AllocatedBit }; pub struct AllocatedNum { @@ -63,10 +66,126 @@ impl AllocatedNum { ) -> Result, SynthesisError> where CS: ConstraintSystem { - let bits = self.into_bits(&mut cs)?; - Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, &bits)?; + pub fn kary_and( + mut cs: CS, + v: &[AllocatedBit] + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + assert!(v.len() > 0); - Ok(bits) + // Let's keep this simple for now and just AND them all + // manually + let mut cur = None; + + for (i, v) in v.iter().enumerate() { + if cur.is_none() { + cur = Some(v.clone()); + } else { + cur = Some(AllocatedBit::and( + cs.namespace(|| format!("and {}", i)), + cur.as_ref().unwrap(), + v + )?); + } + } + + Ok(cur.expect("v.len() > 0")) + } + + // We want to ensure that the bit representation of a is + // less than or equal to r - 1. + let mut a = self.value.map(|e| BitIterator::new(e.into_repr())); + let mut b = E::Fr::char(); + b.sub_noborrow(&1.into()); + + let mut result = vec![]; + + // Runs of ones in r + let mut last_run = None; + let mut current_run = vec![]; + + let mut found_one = false; + let mut i = 0; + for b in BitIterator::new(b) { + let a_bit = a.as_mut().map(|e| e.next().unwrap()); + + // Skip over unset bits at the beginning + found_one |= b; + if !found_one { + // a_bit should also be false + a_bit.map(|e| assert!(!e)); + continue; + } + + if b { + // This is part of a run of ones. Let's just + // allocate the boolean with the expected value. + let a_bit = AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + a_bit + )?; + // ... and add it to the current run of ones. + current_run.push(a_bit.clone()); + result.push(a_bit); + } else { + if current_run.len() > 0 { + // This is the start of a run of zeros, but we need + // to k-ary AND against `last_run` first. + + if last_run.is_some() { + current_run.push(last_run.clone().unwrap()); + } + last_run = Some(kary_and( + cs.namespace(|| format!("run ending at {}", i)), + ¤t_run + )?); + current_run.truncate(0); + } + + // If `last_run` is true, `a` must be false, or it would + // not be in the field. + // + // If `last_run` is false, `a` can be true or false. + + let a_bit = AllocatedBit::alloc_conditionally( + cs.namespace(|| format!("bit {}", i)), + a_bit, + &last_run.as_ref().expect("char always starts with a one") + )?; + result.push(a_bit); + } + + i += 1; + } + + // char is prime, so we'll always end on + // a run of zeros. + assert_eq!(current_run.len(), 0); + + // Now, we have `result` in big-endian order. + // However, now we have to unpack self! + + let mut lc = LinearCombination::zero(); + let mut coeff = E::Fr::one(); + + for bit in result.iter().rev() { + lc = lc + (coeff, bit.get_variable()); + + coeff.double(); + } + + lc = lc - self.variable; + + cs.enforce( + || "unpacking constraint", + |lc| lc, + |lc| lc, + |_| lc + ); + + Ok(result.into_iter().map(|b| Boolean::from(b)).collect()) } pub fn into_bits( @@ -315,7 +434,6 @@ mod test { use pairing::{Field, PrimeField, BitIterator}; use ::circuit::test::*; use super::{AllocatedNum, Boolean}; - use super::super::boolean::AllocatedBit; #[test] fn test_allocated_num() { @@ -422,31 +540,25 @@ mod test { // make the bit representation the characteristic cs.set("bit 254/boolean", Fr::one()); - // this makes the unpacking constraint fail - assert_eq!(cs.which_is_unsatisfied().unwrap(), "unpacking constraint"); - - // fix it by making the number zero (congruent to the characteristic) - cs.set("num", Fr::zero()); - - // and constraint is disturbed during enforce in field check - assert_eq!(cs.which_is_unsatisfied().unwrap(), "nand 121/AND 0/and constraint"); - cs.set("nand 121/AND 0/and result", Fr::one()); - - // now the nand should fail (enforce in field is working) - assert_eq!(cs.which_is_unsatisfied().unwrap(), "nand 121/enforce nand"); + // this makes the conditional boolean constraint fail + assert_eq!(cs.which_is_unsatisfied().unwrap(), "bit 254/boolean constraint"); } #[test] fn test_into_bits() { let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - for _ in 0..100 { + for i in 0..200 { let r = Fr::rand(&mut rng); let mut cs = TestConstraintSystem::::new(); let n = AllocatedNum::alloc(&mut cs, || Ok(r)).unwrap(); - let bits = n.into_bits(&mut cs).unwrap(); + let bits = if i % 2 == 0 { + n.into_bits(&mut cs).unwrap() + } else { + n.into_bits_strict(&mut cs).unwrap() + }; assert!(cs.is_satisfied()); From 0d56c7a90251cee396883c0230ea7e332d714c72 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 11:18:39 -0700 Subject: [PATCH 06/13] Remove enforce_in_field. --- src/circuit/boolean.rs | 139 ----------------------------------------- 1 file changed, 139 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 48d243d..c8308ab 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -531,78 +531,6 @@ impl Boolean { }, } } - - /// Asserts that this bit representation is "in - /// the field" when interpreted in big endian. - pub fn enforce_in_field( - mut cs: CS, - bits: &[Self] - ) -> Result<(), SynthesisError> - where E: Engine, - CS: ConstraintSystem - { - assert_eq!(bits.len(), F::NUM_BITS as usize); - - let mut a = bits.iter(); - - // b = char() - 1 - let mut b = F::char(); - b.sub_noborrow(&1.into()); - - // Runs of ones in r - let mut last_run = Boolean::constant(true); - let mut current_run = vec![]; - - let mut found_one = false; - let mut run_i = 0; - let mut nand_i = 0; - for b in BitIterator::new(b) { - // Skip over unset bits at the beginning - found_one |= b; - if !found_one { - continue; - } - - let a = a.next().unwrap(); - - if b { - // This is part of a run of ones. - current_run.push(a.clone()); - } else { - if current_run.len() > 0 { - // This is the start of a run of zeros, but we need - // to k-ary AND against `last_run` first. - - current_run.push(last_run.clone()); - last_run = Self::kary_and( - cs.namespace(|| format!("run {}", run_i)), - ¤t_run - )?; - run_i += 1; - current_run.truncate(0); - } - - // If `last_run` is true, `a` must be false, or it would - // not be in the field. - // - // If `last_run` is false, `a` can be true or false. - // - // Ergo, at least one of `last_run` and `a` must be false. - Self::enforce_nand( - cs.namespace(|| format!("nand {}", nand_i)), - &[last_run.clone(), a.clone()] - )?; - nand_i += 1; - } - } - - // We should always end in a "run" of zeros, because - // the characteristic is an odd prime. So, this should - // be empty. - assert_eq!(current_run.len(), 0); - - Ok(()) - } } impl From for Boolean { @@ -1090,73 +1018,6 @@ mod test { } } - #[test] - fn test_enforce_in_field() { - { - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(Fr::char()).skip(1).enumerate() { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } - - Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); - - assert!(!cs.is_satisfied()); - } - - let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - for _ in 0..1000 { - let r = Fr::rand(&mut rng); - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } - - Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); - - assert!(cs.is_satisfied()); - } - - for _ in 0..1000 { - // Sample a random element not in the field - let r = loop { - let mut a = Fr::rand(&mut rng).into_repr(); - let b = Fr::rand(&mut rng).into_repr(); - - a.add_nocarry(&b); - // we're shaving off the high bit later - a.as_mut()[3] &= 0x7fffffffffffffff; - if Fr::from_repr(a).is_err() { - break a; - } - }; - - let mut cs = TestConstraintSystem::::new(); - - let mut bits = vec![]; - for (i, b) in BitIterator::new(r).skip(1).enumerate() { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - Some(b) - ).unwrap())); - } - - Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); - - assert!(!cs.is_satisfied()); - } - } - #[test] fn test_enforce_nand() { { From a45986ff97a4fd4fde54c290e9913608b732a97a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 11:24:42 -0700 Subject: [PATCH 07/13] Remove enforce_nand. --- src/circuit/boolean.rs | 121 ++++++++++------------------------------- 1 file changed, 28 insertions(+), 93 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index c8308ab..4fa1818 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -366,7 +366,34 @@ impl Boolean { { let c = Self::xor(&mut cs, a, b)?; - Self::enforce_nand(&mut cs, &[c]) + match c { + Boolean::Constant(false) => { + Ok(()) + }, + Boolean::Constant(true) => { + Err(SynthesisError::Unsatisfiable) + }, + Boolean::Is(ref res) => { + cs.enforce( + || "enforce equals zero", + |lc| lc, + |lc| lc, + |lc| lc + res.get_variable() + ); + + Ok(()) + }, + Boolean::Not(ref res) => { + cs.enforce( + || "enforce equals one", + |lc| lc, + |lc| lc, + |lc| lc + CS::one() - res.get_variable() + ); + + Ok(()) + }, + } } pub fn get_value(&self) -> Option { @@ -491,46 +518,6 @@ impl Boolean { Ok(cur) } - - /// Asserts that at least one operand is false. - pub fn enforce_nand( - mut cs: CS, - bits: &[Self] - ) -> Result<(), SynthesisError> - where E: Engine, - CS: ConstraintSystem - { - let res = Self::kary_and(&mut cs, bits)?; - - match res { - Boolean::Constant(false) => { - Ok(()) - }, - Boolean::Constant(true) => { - Err(SynthesisError::Unsatisfiable) - }, - Boolean::Is(ref res) => { - cs.enforce( - || "enforce nand", - |lc| lc, - |lc| lc, - |lc| lc + res.get_variable() - ); - - Ok(()) - }, - Boolean::Not(ref res) => { - cs.enforce( - || "enforce nand", - |lc| lc, - |lc| lc, - |lc| lc + CS::one() - res.get_variable() - ); - - Ok(()) - }, - } - } } impl From for Boolean { @@ -1018,58 +1005,6 @@ mod test { } } - #[test] - fn test_enforce_nand() { - { - let mut cs = TestConstraintSystem::::new(); - - Boolean::enforce_nand(&mut cs, &[Boolean::constant(false)]).is_ok(); - Boolean::enforce_nand(&mut cs, &[Boolean::constant(true)]).is_err(); - } - - for i in 1..5 { - // with every possible assignment for them - for mut b in 0..(1 << i) { - // with every possible negation - for mut n in 0..(1 << i) { - let mut cs = TestConstraintSystem::::new(); - - let mut expected = true; - - let mut bits = vec![]; - for j in 0..i { - expected &= b & 1 == 1; - - if n & 1 == 1 { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", j)), - Some(b & 1 == 1) - ).unwrap())); - } else { - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", j)), - Some(b & 1 == 0) - ).unwrap()).not()); - } - - b >>= 1; - n >>= 1; - } - - let expected = !expected; - - Boolean::enforce_nand(&mut cs, &bits).unwrap(); - - if expected { - assert!(cs.is_satisfied()); - } else { - assert!(!cs.is_satisfied()); - } - } - } - } - } - #[test] fn test_kary_and() { // test different numbers of operands From 8c372126453f3ab4d993314e5d146d265dea4fe7 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 11:25:59 -0700 Subject: [PATCH 08/13] Remove kary_and from Boolean. --- src/circuit/boolean.rs | 57 ------------------------------------------ 1 file changed, 57 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 4fa1818..20342bf 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -496,28 +496,6 @@ impl Boolean { } } } - - pub fn kary_and( - mut cs: CS, - bits: &[Self] - ) -> Result - where E: Engine, - CS: ConstraintSystem - { - assert!(bits.len() > 0); - let mut bits = bits.iter(); - - let mut cur: Self = bits.next().unwrap().clone(); - - let mut i = 0; - while let Some(next) = bits.next() { - cur = Boolean::and(cs.namespace(|| format!("AND {}", i)), &cur, next)?; - - i += 1; - } - - Ok(cur) - } } impl From for Boolean { @@ -1005,41 +983,6 @@ mod test { } } - #[test] - fn test_kary_and() { - // test different numbers of operands - for i in 1..15 { - // with every possible assignment for them - for mut b in 0..(1 << i) { - let mut cs = TestConstraintSystem::::new(); - - let mut expected = true; - - let mut bits = vec![]; - for j in 0..i { - expected &= b & 1 == 1; - - bits.push(Boolean::from(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", j)), - Some(b & 1 == 1) - ).unwrap())); - b >>= 1; - } - - let r = Boolean::kary_and(&mut cs, &bits).unwrap(); - - assert!(cs.is_satisfied()); - - match r { - Boolean::Is(ref r) => { - assert_eq!(r.value.unwrap(), expected); - }, - _ => unreachable!() - } - } - } - } - #[test] fn test_u64_into_allocated_bits_be() { let mut cs = TestConstraintSystem::::new(); From 7a7774d8afd1d80a11e3f5b254f48ddb1171c45d Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 11:28:17 -0700 Subject: [PATCH 09/13] Remove unused imports in Boolean. --- src/circuit/boolean.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 20342bf..18bb4d0 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -2,7 +2,6 @@ use pairing::{ Engine, Field, PrimeField, - PrimeFieldRepr, BitIterator }; @@ -506,10 +505,9 @@ impl From for Boolean { #[cfg(test)] mod test { - use rand::{SeedableRng, Rand, XorShiftRng}; use bellman::{ConstraintSystem}; use pairing::bls12_381::{Bls12, Fr}; - use pairing::{Field, PrimeField, PrimeFieldRepr, BitIterator}; + use pairing::{Field, PrimeField}; use ::circuit::test::*; use super::{ AllocatedBit, From 23d17b904267737ccad54b66abb293170ae539f8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 23 Feb 2018 18:09:26 -0700 Subject: [PATCH 10/13] Minor adjustments. --- src/circuit/mod.rs | 71 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 84d768b..58acf1f 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -40,8 +40,6 @@ impl Assignment for Option { } } -const MERKLE_TREE_DEPTH: usize = 29; - pub struct Spend<'a, E: JubjubEngine> { pub params: &'a E::Params, /// Value of the note being spent @@ -262,7 +260,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; } - assert_eq!(self.auth_path.len(), MERKLE_TREE_DEPTH); + let tree_depth = self.auth_path.len(); let mut position_bits = vec![]; @@ -301,15 +299,30 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { cur = pedersen_hash::pedersen_hash( cs.namespace(|| "computation of pedersen hash"), - pedersen_hash::Personalization::MerkleTree(MERKLE_TREE_DEPTH - i), + pedersen_hash::Personalization::MerkleTree(tree_depth - i), &preimage, self.params )?.x; // Injective encoding } - assert_eq!(position_bits.len(), MERKLE_TREE_DEPTH); + assert_eq!(position_bits.len(), tree_depth); - // TODO: cur is now the root of the tree, expose it as public input + { + // Expose the anchor + let anchor = cs.alloc_input( + || "anchor x", + || { + Ok(*cur.get_value().get()?) + } + )?; + + cs.enforce( + || "anchor x equals anchor", + |lc| lc + anchor, + |lc| lc + CS::one(), + |lc| lc + cur.get_variable() + ); + } { let position = ecc::fixed_base_multiplication( @@ -326,11 +339,12 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; } - // Let's compute rho = BLAKE2s(rk || cm + position) rho_preimage.extend( cm.repr(cs.namespace(|| "representation of cm"))? ); + + assert_eq!(rho_preimage.len(), 512); let mut rho = blake2s::blake2s( cs.namespace(|| "rho computation"), @@ -348,7 +362,36 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; - // TODO: expose nf as public input + { + // Expose the nullifier publicly + let nf_x = cs.alloc_input( + || "nf_x", + || { + Ok(*nf.x.get_value().get()?) + } + )?; + + cs.enforce( + || "nf_x equals input", + |lc| lc + nf_x, + |lc| lc + CS::one(), + |lc| lc + nf.x.get_variable() + ); + + let nf_y = cs.alloc_input( + || "nf_y", + || { + Ok(*nf.y.get_value().get()?) + } + )?; + + cs.enforce( + || "nf_y equals input", + |lc| lc + nf_y, + |lc| lc + CS::one(), + |lc| lc + nf.y.get_variable() + ); + } Ok(()) } @@ -364,22 +407,22 @@ fn test_input_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 p_d: edwards::Point = edwards::Point::rand(rng, params); let commitment_randomness: fs::Fs = rng.gen(); - let esk: fs::Fs = rng.gen(); let rsk: fs::Fs = rng.gen(); - let auth_path = vec![Some((rng.gen(), false)); 29]; + let auth_path = vec![Some((rng.gen(), rng.gen())); tree_depth]; { let mut cs = TestConstraintSystem::::new(); let instance = Spend { params: params, - value: Some(1), + value: Some(value), value_randomness: Some(value_randomness), rsk: Some(rsk), ak: Some(ak), @@ -392,7 +435,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 97376); + assert_eq!(cs.num_constraints(), 97379); } // use bellman::groth16::*; @@ -725,7 +768,7 @@ fn test_output_circuit_with_bls12_381() { let instance = Output { params: params, - value: Some(1), + value: Some(value), value_randomness: Some(value_randomness), g_d: Some(g_d.clone()), p_d: Some(p_d.clone()), From 4441a0da41457955715afe6a2ff7093786c65dd8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 24 Feb 2018 08:01:16 -0700 Subject: [PATCH 11/13] 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; From 3346fba915a99f09c51847bdac7086516ee24f15 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 24 Feb 2018 14:11:01 -0700 Subject: [PATCH 12/13] Implementation of Jubjub point representation. --- Cargo.toml | 3 ++ src/jubjub/edwards.rs | 90 +++++++++++++++++++++++++++++++++++++++++++ src/jubjub/mod.rs | 13 ++++++- src/jubjub/tests.rs | 16 ++++++++ src/lib.rs | 4 ++ 5 files changed, 125 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4999341..0135682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ bellman = "0.0.8" byteorder = "1" +[dev-dependencies] +hex-literal = "0.1" + [features] default = ["u128-support"] u128-support = ["pairing/u128-support"] diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index 130fd59..eeabe9d 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -20,6 +20,12 @@ use rand::{ use std::marker::PhantomData; +use std::io::{ + self, + Write, + Read +}; + // Represents the affine point (X/Z, Y/Z) via the extended // twisted Edwards coordinates. pub struct Point { @@ -80,7 +86,67 @@ impl PartialEq for Point { } } +fn swap_bits_u64(x: u64) -> u64 +{ + let mut tmp = 0; + for i in 0..64 { + tmp |= ((x >> i) & 1) << (63 - i); + } + tmp +} + +#[test] +fn test_swap_bits_u64() { + assert_eq!(swap_bits_u64(17182120934178543809), 0b1000001100011011110000011000111000101111111001001100111001110111); + assert_eq!(swap_bits_u64(15135675916470734665), 0b1001001011110010001101010010001110110000100111010011000001001011); + assert_eq!(swap_bits_u64(6724233301461108393), 0b1001010101100000100011100001010111110001011000101000101010111010); + assert_eq!(swap_bits_u64(206708183275952289), 0b1000010100011010001010100011101011111111111110100111101101000000); + assert_eq!(swap_bits_u64(12712751566144824320), 0b0000000000100110010110111000001110001100001000110011011000001101); + + let mut a = 15863238721320035327u64; + for _ in 0..1000 { + a = a.wrapping_mul(a); + + let swapped = swap_bits_u64(a); + let unswapped = swap_bits_u64(swapped); + + assert_eq!(a, unswapped); + } +} + impl Point { + pub fn read( + reader: R, + params: &E::Params + ) -> io::Result + { + let mut y_repr = ::Repr::default(); + y_repr.read_be(reader)?; + + y_repr.as_mut().reverse(); + + for b in y_repr.as_mut() { + *b = swap_bits_u64(*b); + } + + let x_sign = (y_repr.as_ref()[3] >> 63) == 1; + y_repr.as_mut()[3] &= 0x7fffffffffffffff; + + match E::Fr::from_repr(y_repr) { + Ok(y) => { + match Self::get_for_y(y, x_sign, params) { + Some(p) => Ok(p), + None => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "not on curve")) + } + } + }, + Err(_) => { + Err(io::Error::new(io::ErrorKind::InvalidInput, "y is not in field")) + } + } + } + pub fn get_for_y(y: E::Fr, sign: bool, params: &E::Params) -> Option { // Given a y on the curve, x^2 = (y^2 - 1) / (dy^2 + 1) @@ -151,6 +217,30 @@ impl Point { } impl Point { + pub fn write( + &self, + writer: W + ) -> io::Result<()> + { + let (x, y) = self.into_xy(); + + assert_eq!(E::Fr::NUM_BITS, 255); + + let x_repr = x.into_repr(); + let mut y_repr = y.into_repr(); + if x_repr.is_odd() { + y_repr.as_mut()[3] |= 0x8000000000000000u64; + } + + y_repr.as_mut().reverse(); + + for b in y_repr.as_mut() { + *b = swap_bits_u64(*b); + } + + y_repr.write_be(writer) + } + /// Convert from a Montgomery point pub fn from_montgomery( m: &montgomery::Point, diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 4782d90..189d434 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -72,7 +72,8 @@ pub enum FixedGenerators { ValueCommitmentValue = 2, ValueCommitmentRandomness = 3, NullifierPosition = 4, - Max = 5 + SpendingKeyGenerator = 5, + Max = 6 } pub struct JubjubBls12 { @@ -236,4 +237,14 @@ fn test_jubjub_bls12() { let params = JubjubBls12::new(); tests::test_suite::(¶ms); + + let test_repr = hex!("b9481dd1103b7d1f8578078eb429d3c476472f53e88c0eaefdf51334c7c8b98c"); + let p = edwards::Point::::read(&test_repr[..], ¶ms).unwrap(); + let q = edwards::Point::::get_for_y( + Fr::from_str("22440861827555040311190986994816762244378363690614952020532787748720529117853").unwrap(), + false, + ¶ms + ).unwrap(); + + assert!(p == q); } diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index e034854..dfd44d0 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -26,6 +26,7 @@ pub fn test_suite(params: &E::Params) { test_order::(params); test_mul_associativity::(params); test_loworder::(params); + test_read_write::(params); } fn is_on_mont_curve>( @@ -245,6 +246,21 @@ fn test_get_for(params: &E::Params) { } } +fn test_read_write(params: &E::Params) { + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let e = edwards::Point::::rand(rng, params); + + let mut v = vec![]; + e.write(&mut v).unwrap(); + + let e2 = edwards::Point::read(&v[..], params).unwrap(); + + assert!(e == e2); + } +} + fn test_rand(params: &E::Params) { let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); diff --git a/src/lib.rs b/src/lib.rs index 60e5972..48af45c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,10 @@ extern crate rand; extern crate byteorder; +#[cfg(test)] +#[macro_use] +extern crate hex_literal; + pub mod jubjub; pub mod circuit; pub mod group_hash; From 27674bf8ff59b7c4427af7839029742e3a9333ef Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 24 Feb 2018 17:11:17 -0700 Subject: [PATCH 13/13] Derive Clone for EdwardsPoint. --- src/circuit/ecc.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 79b2813..0828812 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -30,20 +30,12 @@ use super::lookup::{ use super::boolean::Boolean; +#[derive(Clone)] pub struct EdwardsPoint { pub x: AllocatedNum, pub y: AllocatedNum } -impl Clone for EdwardsPoint { - fn clone(&self) -> Self { - EdwardsPoint { - x: self.x.clone(), - y: self.y.clone() - } - } -} - /// Perform a fixed-base scalar multiplication with /// `by` being in little-endian bit order. pub fn fixed_base_multiplication(