From 827e85547e5aaf6bce2b984ee3a0e76da3034af2 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 11:42:22 -0600 Subject: [PATCH 1/4] Public input verification utility for the test framework --- src/circuit/test/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/circuit/test/mod.rs b/src/circuit/test/mod.rs index 9728e04..12fe0ca 100644 --- a/src/circuit/test/mod.rs +++ b/src/circuit/test/mod.rs @@ -294,6 +294,20 @@ impl TestConstraintSystem { } } + pub fn verify(&self, expected: &[E::Fr]) -> bool + { + assert_eq!(expected.len() + 1, self.inputs.len()); + + for (a, b) in self.inputs.iter().skip(1).zip(expected.iter()) + { + if &a.0 != b { + return false + } + } + + return true; + } + pub fn num_inputs(&self) -> usize { self.inputs.len() } From 52eb59766b4ec4205830d4d2619c5988d3a3985a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 12:31:10 -0600 Subject: [PATCH 2/4] Optimize UInt32::addmany/BLAKE2s to combine equality constraints. (Closes #5) --- src/circuit/blake2s.rs | 37 ++++++----- src/circuit/boolean.rs | 7 +++ src/circuit/mod.rs | 5 +- src/circuit/multieq.rs | 137 +++++++++++++++++++++++++++++++++++++++++ src/circuit/uint32.rs | 62 +++++++++---------- 5 files changed, 198 insertions(+), 50 deletions(-) create mode 100644 src/circuit/multieq.rs diff --git a/src/circuit/blake2s.rs b/src/circuit/blake2s.rs index f75f5c7..a78f3cf 100644 --- a/src/circuit/blake2s.rs +++ b/src/circuit/blake2s.rs @@ -15,6 +15,8 @@ use super::uint32::{ UInt32 }; +use super::multieq::MultiEq; + /* 2.1. Parameters The following table summarizes various parameters and their ranges: @@ -88,8 +90,8 @@ const SIGMA: [[usize; 16]; 10] = [ END FUNCTION. */ -fn mixing_g>( - mut cs: CS, +fn mixing_g, M>( + mut cs: M, v: &mut [UInt32], a: usize, b: usize, @@ -98,6 +100,7 @@ fn mixing_g>( x: &UInt32, y: &UInt32 ) -> Result<(), SynthesisError> + where M: ConstraintSystem> { v[a] = UInt32::addmany(cs.namespace(|| "mixing step 1"), &[v[a].clone(), v[b].clone(), x.clone()])?; v[d] = v[d].xor(cs.namespace(|| "mixing step 2"), &v[a])?.rotr(R1); @@ -199,20 +202,24 @@ fn blake2s_compression>( v[14] = v[14].xor(cs.namespace(|| "third xor"), &UInt32::constant(u32::max_value()))?; } - for i in 0..10 { - let mut cs = cs.namespace(|| format!("round {}", i)); + { + let mut cs = MultiEq::new(&mut cs); - let s = SIGMA[i % 10]; + for i in 0..10 { + let mut cs = cs.namespace(|| format!("round {}", i)); - mixing_g(cs.namespace(|| "mixing invocation 1"), &mut v, 0, 4, 8, 12, &m[s[ 0]], &m[s[ 1]])?; - mixing_g(cs.namespace(|| "mixing invocation 2"), &mut v, 1, 5, 9, 13, &m[s[ 2]], &m[s[ 3]])?; - mixing_g(cs.namespace(|| "mixing invocation 3"), &mut v, 2, 6, 10, 14, &m[s[ 4]], &m[s[ 5]])?; - mixing_g(cs.namespace(|| "mixing invocation 4"), &mut v, 3, 7, 11, 15, &m[s[ 6]], &m[s[ 7]])?; + let s = SIGMA[i % 10]; - mixing_g(cs.namespace(|| "mixing invocation 5"), &mut v, 0, 5, 10, 15, &m[s[ 8]], &m[s[ 9]])?; - mixing_g(cs.namespace(|| "mixing invocation 6"), &mut v, 1, 6, 11, 12, &m[s[10]], &m[s[11]])?; - mixing_g(cs.namespace(|| "mixing invocation 7"), &mut v, 2, 7, 8, 13, &m[s[12]], &m[s[13]])?; - mixing_g(cs.namespace(|| "mixing invocation 8"), &mut v, 3, 4, 9, 14, &m[s[14]], &m[s[15]])?; + mixing_g(cs.namespace(|| "mixing invocation 1"), &mut v, 0, 4, 8, 12, &m[s[ 0]], &m[s[ 1]])?; + mixing_g(cs.namespace(|| "mixing invocation 2"), &mut v, 1, 5, 9, 13, &m[s[ 2]], &m[s[ 3]])?; + mixing_g(cs.namespace(|| "mixing invocation 3"), &mut v, 2, 6, 10, 14, &m[s[ 4]], &m[s[ 5]])?; + mixing_g(cs.namespace(|| "mixing invocation 4"), &mut v, 3, 7, 11, 15, &m[s[ 6]], &m[s[ 7]])?; + + mixing_g(cs.namespace(|| "mixing invocation 5"), &mut v, 0, 5, 10, 15, &m[s[ 8]], &m[s[ 9]])?; + mixing_g(cs.namespace(|| "mixing invocation 6"), &mut v, 1, 6, 11, 12, &m[s[10]], &m[s[11]])?; + mixing_g(cs.namespace(|| "mixing invocation 7"), &mut v, 2, 7, 8, 13, &m[s[12]], &m[s[13]])?; + mixing_g(cs.namespace(|| "mixing invocation 8"), &mut v, 3, 4, 9, 14, &m[s[14]], &m[s[15]])?; + } } for i in 0..8 { @@ -350,7 +357,7 @@ mod test { let input_bits: Vec<_> = (0..512).map(|i| AllocatedBit::alloc(cs.namespace(|| format!("input bit {}", i)), Some(true)).unwrap().into()).collect(); blake2s(&mut cs, &input_bits, b"12345678").unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 21792); + assert_eq!(cs.num_constraints(), 21518); } #[test] @@ -367,7 +374,7 @@ mod test { .collect(); blake2s(&mut cs, &input_bits, b"12345678").unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 21792); + assert_eq!(cs.num_constraints(), 21518); } #[test] diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 239d404..e5fa435 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -367,6 +367,13 @@ pub enum Boolean { } impl Boolean { + pub fn is_constant(&self) -> bool { + match *self { + Boolean::Constant(_) => true, + _ => false + } + } + pub fn enforce_equal( mut cs: CS, a: &Self, diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index f12ad9e..f928820 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -2,6 +2,7 @@ pub mod test; pub mod boolean; +pub mod multieq; pub mod uint32; pub mod blake2s; pub mod num; @@ -627,8 +628,8 @@ fn test_input_circuit_with_bls12_381() { instance.synthesize(&mut cs).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 101566); - assert_eq!(cs.hash(), "e3d226975c99e17ef30f5a4b7e87d355ef3dbd80eed0c8de43780f3028946d82"); + assert_eq!(cs.num_constraints(), 101018); + assert_eq!(cs.hash(), "eedcef5fd638e0168ae4d53ac58df66f0acdabea46749cc5f4b39459c8377804"); let expected_value_cm = value_commitment.cm(params).into_xy(); diff --git a/src/circuit/multieq.rs b/src/circuit/multieq.rs new file mode 100644 index 0000000..0f9c755 --- /dev/null +++ b/src/circuit/multieq.rs @@ -0,0 +1,137 @@ +use pairing::{ + Engine, + Field, + PrimeField +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + LinearCombination, + Variable +}; + +pub struct MultiEq>{ + cs: CS, + ops: usize, + bits_used: usize, + lhs: LinearCombination, + rhs: LinearCombination, +} + +impl> MultiEq { + pub fn new(cs: CS) -> Self { + MultiEq { + cs: cs, + ops: 0, + bits_used: 0, + lhs: LinearCombination::zero(), + rhs: LinearCombination::zero() + } + } + + fn accumulate(&mut self) + { + let ops = self.ops; + let lhs = self.lhs.clone(); + let rhs = self.rhs.clone(); + self.cs.enforce( + || format!("multieq {}", ops), + |_| lhs, + |lc| lc + CS::one(), + |_| rhs + ); + self.lhs = LinearCombination::zero(); + self.rhs = LinearCombination::zero(); + self.bits_used = 0; + self.ops += 1; + } + + pub fn enforce_equal( + &mut self, + num_bits: usize, + lhs: &LinearCombination, + rhs: &LinearCombination + ) + { + // Check if we will exceed the capacity + if (E::Fr::CAPACITY as usize) <= (self.bits_used + num_bits) { + self.accumulate(); + } + + assert!((E::Fr::CAPACITY as usize) > (self.bits_used + num_bits)); + + let coeff = E::Fr::from_str("2").unwrap().pow(&[self.bits_used as u64]); + self.lhs = self.lhs.clone() + (coeff, lhs); + self.rhs = self.rhs.clone() + (coeff, rhs); + self.bits_used += num_bits; + } +} + +impl> Drop for MultiEq { + fn drop(&mut self) { + if self.bits_used > 0 { + self.accumulate(); + } + } +} + +impl> ConstraintSystem for MultiEq +{ + type Root = Self; + + fn one() -> Variable { + CS::one() + } + + fn alloc( + &mut self, + annotation: A, + f: F + ) -> Result + where F: FnOnce() -> Result, A: FnOnce() -> AR, AR: Into + { + self.cs.alloc(annotation, f) + } + + fn alloc_input( + &mut self, + annotation: A, + f: F + ) -> Result + where F: FnOnce() -> Result, A: FnOnce() -> AR, AR: Into + { + self.cs.alloc_input(annotation, f) + } + + fn enforce( + &mut self, + annotation: A, + a: LA, + b: LB, + c: LC + ) + where A: FnOnce() -> AR, AR: Into, + LA: FnOnce(LinearCombination) -> LinearCombination, + LB: FnOnce(LinearCombination) -> LinearCombination, + LC: FnOnce(LinearCombination) -> LinearCombination + { + self.cs.enforce(annotation, a, b, c) + } + + fn push_namespace(&mut self, name_fn: N) + where NR: Into, N: FnOnce() -> NR + { + self.cs.get_root().push_namespace(name_fn) + } + + fn pop_namespace(&mut self) + { + self.cs.get_root().pop_namespace() + } + + fn get_root(&mut self) -> &mut Self::Root + { + self + } +} diff --git a/src/circuit/uint32.rs b/src/circuit/uint32.rs index ff39433..4714724 100644 --- a/src/circuit/uint32.rs +++ b/src/circuit/uint32.rs @@ -15,6 +15,8 @@ use super::boolean::{ AllocatedBit }; +use super::multieq::MultiEq; + /// Represents an interpretation of 32 `Boolean` objects as an /// unsigned integer. #[derive(Clone)] @@ -188,12 +190,13 @@ impl UInt32 { } /// Perform modular addition of several `UInt32` objects. - pub fn addmany( - mut cs: CS, + pub fn addmany( + mut cs: M, operands: &[Self] ) -> Result where E: Engine, - CS: ConstraintSystem + CS: ConstraintSystem, + M: ConstraintSystem> { // Make some arbitrary bounds for ourselves to avoid overflows // in the scalar field @@ -208,7 +211,8 @@ impl UInt32 { // Keep track of the resulting value let mut result_value = Some(0u64); - // This is a linear combination that we will enforce to be "zero" + // This is a linear combination that we will enforce to equal the + // output let mut lc = LinearCombination::zero(); let mut all_constants = true; @@ -231,25 +235,9 @@ impl UInt32 { // the linear combination let mut coeff = E::Fr::one(); for bit in &op.bits { - match bit { - &Boolean::Is(ref bit) => { - all_constants = false; + lc = lc + &bit.lc(CS::one(), coeff); - // Add coeff * bit - lc = lc + (coeff, bit.get_variable()); - }, - &Boolean::Not(ref bit) => { - all_constants = false; - - // Add coeff * (1 - bit) = coeff * ONE - coeff * bit - lc = lc + (coeff, CS::one()) - (coeff, bit.get_variable()); - }, - &Boolean::Constant(bit) => { - if bit { - lc = lc + (coeff, CS::one()); - } - } - } + all_constants &= bit.is_constant(); coeff.double(); } @@ -268,6 +256,10 @@ impl UInt32 { // Storage area for the resulting bits let mut result_bits = vec![]; + // Linear combination representing the output, + // for comparison with the sum of the operands + let mut result_lc = LinearCombination::zero(); + // Allocate each bit of the result let mut coeff = E::Fr::one(); let mut i = 0; @@ -278,8 +270,8 @@ impl UInt32 { result_value.map(|v| (v >> i) & 1 == 1) )?; - // Subtract this bit from the linear combination to ensure the sums balance out - lc = lc - (coeff, b.get_variable()); + // Add this bit to the result combination + result_lc = result_lc + (coeff, b.get_variable()); result_bits.push(b.into()); @@ -288,13 +280,8 @@ impl UInt32 { coeff.double(); } - // Enforce that the linear combination equals zero - cs.enforce( - || "modular addition", - |lc| lc, - |lc| lc, - |_| lc - ); + // Enforce equality between the sum and result + cs.get_root().enforce_equal(i, &lc, &result_lc); // Discard carry bits that we don't care about result_bits.truncate(32); @@ -315,6 +302,7 @@ mod test { use pairing::{Field}; use ::circuit::test::*; use bellman::{ConstraintSystem}; + use circuit::multieq::MultiEq; #[test] fn test_uint32_from_bits() { @@ -406,7 +394,11 @@ mod test { let mut expected = a.wrapping_add(b).wrapping_add(c); - let r = UInt32::addmany(cs.namespace(|| "addition"), &[a_bit, b_bit, c_bit]).unwrap(); + let r = { + let mut cs = MultiEq::new(&mut cs); + let r = UInt32::addmany(cs.namespace(|| "addition"), &[a_bit, b_bit, c_bit]).unwrap(); + r + }; assert!(r.value == Some(expected)); @@ -444,7 +436,11 @@ mod test { let d_bit = UInt32::alloc(cs.namespace(|| "d_bit"), Some(d)).unwrap(); let r = a_bit.xor(cs.namespace(|| "xor"), &b_bit).unwrap(); - let r = UInt32::addmany(cs.namespace(|| "addition"), &[r, c_bit, d_bit]).unwrap(); + let r = { + let mut cs = MultiEq::new(&mut cs); + let r = UInt32::addmany(cs.namespace(|| "addition"), &[r, c_bit, d_bit]).unwrap(); + r + }; assert!(cs.is_satisfied()); From ca202ef3045dd85fa1a5fed8384617cb30bd4143 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 12:36:05 -0600 Subject: [PATCH 3/4] Introduce input multipacking abstraction for nullifiers. --- src/circuit/mod.rs | 1 + src/circuit/multipack.rs | 106 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/circuit/multipack.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index f928820..012a37c 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -9,6 +9,7 @@ pub mod num; pub mod lookup; pub mod ecc; pub mod pedersen_hash; +pub mod multipack; use pairing::{ PrimeField, diff --git a/src/circuit/multipack.rs b/src/circuit/multipack.rs new file mode 100644 index 0000000..04f6260 --- /dev/null +++ b/src/circuit/multipack.rs @@ -0,0 +1,106 @@ +use pairing::{Engine, Field, PrimeField}; +use bellman::{ConstraintSystem, SynthesisError}; +use super::boolean::{Boolean}; +use super::num::Num; +use super::Assignment; + +/// Takes a sequence of booleans and exposes them as compact +/// public inputs +pub fn pack_into_inputs( + mut cs: CS, + bits: &[Boolean] +) -> Result<(), SynthesisError> + where E: Engine, CS: ConstraintSystem +{ + for (i, bits) in bits.chunks(E::Fr::CAPACITY as usize).enumerate() + { + let mut num = Num::::zero(); + let mut coeff = E::Fr::one(); + for bit in bits { + num = num.add_bool_with_coeff(CS::one(), bit, coeff); + + coeff.double(); + } + + let input = cs.alloc_input(|| format!("input {}", i), || { + Ok(*num.get_value().get()?) + })?; + + // num * 1 = input + cs.enforce( + || format!("packing constraint {}", i), + |_| num.lc(E::Fr::one()), + |lc| lc + CS::one(), + |lc| lc + input + ); + } + + Ok(()) +} + +pub fn bytes_to_bits(bytes: &[u8]) -> Vec +{ + bytes.iter() + .flat_map(|&v| (0..8).rev().map(move |i| (v >> i) & 1 == 1)) + .collect() +} + +pub fn compute_multipacking( + bits: &[bool] +) -> Vec +{ + let mut result = vec![]; + + for bits in bits.chunks(E::Fr::CAPACITY as usize) + { + let mut cur = E::Fr::zero(); + let mut coeff = E::Fr::one(); + + for bit in bits { + if *bit { + cur.add_assign(&coeff); + } + + coeff.double(); + } + + result.push(cur); + } + + result +} + +#[test] +fn test_multipacking() { + use rand::{SeedableRng, Rng, XorShiftRng}; + use bellman::{ConstraintSystem}; + use pairing::bls12_381::{Bls12}; + use ::circuit::test::*; + use super::boolean::{AllocatedBit, Boolean}; + + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for num_bits in 0..1500 { + let mut cs = TestConstraintSystem::::new(); + + let bits: Vec = (0..num_bits).map(|_| rng.gen()).collect(); + + let circuit_bits = bits.iter().enumerate() + .map(|(i, &b)| { + Boolean::from( + AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + Some(b) + ).unwrap() + ) + }) + .collect::>(); + + let expected_inputs = compute_multipacking::(&bits); + + pack_into_inputs(cs.namespace(|| "pack"), &circuit_bits).unwrap(); + + assert!(cs.is_satisfied()); + assert!(cs.verify(&expected_inputs)); + } +} From 21625d69e0b4c467aaff7f26418d68f4926d407f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 15 Mar 2018 12:44:19 -0600 Subject: [PATCH 4/4] Place Sapling circuit into submodule. --- src/circuit/mod.rs | 740 +----------------------------------- src/circuit/sapling/mod.rs | 748 +++++++++++++++++++++++++++++++++++++ 2 files changed, 750 insertions(+), 738 deletions(-) create mode 100644 src/circuit/sapling/mod.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 012a37c..4cde222 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -11,28 +11,10 @@ pub mod ecc; pub mod pedersen_hash; pub mod multipack; -use pairing::{ - PrimeField, - PrimeFieldRepr, -}; +pub mod sapling; use bellman::{ - SynthesisError, - ConstraintSystem, - Circuit -}; - -use jubjub::{ - JubjubEngine, - FixedGenerators -}; - -use constants; - -use primitives::{ - ValueCommitment, - ProofGenerationKey, - PaymentAddress + SynthesisError }; // TODO: This should probably be removed and we @@ -53,721 +35,3 @@ impl Assignment for Option { } } } - -/// This is an instance of the `Spend` circuit. -pub struct Spend<'a, E: JubjubEngine> { - pub params: &'a E::Params, - - /// Pedersen commitment to the value being spent - pub value_commitment: Option>, - - /// Key required to construct proofs for spending notes - /// for a particular spending key - pub proof_generation_key: Option>, - - /// The payment address associated with the note - pub payment_address: Option>, - - /// The randomness of the note commitment - pub commitment_randomness: Option, - - /// The authentication path of the commitment in the tree - pub auth_path: Vec> -} - -/// This is an output circuit instance. -pub struct Output<'a, E: JubjubEngine> { - pub params: &'a E::Params, - - /// Pedersen commitment to the value being spent - pub value_commitment: Option>, - - /// The payment address of the recipient - pub payment_address: 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 -} - -/// Exposes a Pedersen commitment to the value as an -/// input to the circuit -fn expose_value_commitment( - mut cs: CS, - value_commitment: Option>, - params: &E::Params -) -> Result, SynthesisError> - where E: JubjubEngine, - CS: ConstraintSystem -{ - // Booleanize the value into little-endian bit order - let value_bits = boolean::u64_into_boolean_vec_le( - cs.namespace(|| "value"), - value_commitment.as_ref().map(|c| c.value) - )?; - - // Compute the note value in the exponent - let gv = ecc::fixed_base_multiplication( - cs.namespace(|| "compute the value in the exponent"), - FixedGenerators::ValueCommitmentValue, - &value_bits, - params - )?; - - // Booleanize the randomness. This does not ensure - // the bit representation is "in the field" because - // it doesn't matter for security. - let hr = boolean::field_into_boolean_vec_le( - cs.namespace(|| "hr"), - value_commitment.as_ref().map(|c| c.randomness) - )?; - - // Compute the randomness in the exponent - let hr = ecc::fixed_base_multiplication( - cs.namespace(|| "computation of randomization for value commitment"), - FixedGenerators::ValueCommitmentRandomness, - &hr, - params - )?; - - // Compute the Pedersen commitment to the value - let gvhr = gv.add( - cs.namespace(|| "computation of value commitment"), - &hr, - params - )?; - - // Expose the commitment as an input to the circuit - gvhr.inputize(cs.namespace(|| "commitment point"))?; - - Ok(value_bits) -} - -impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { - fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> - { - let value_bits = expose_value_commitment( - cs.namespace(|| "value commitment"), - self.value_commitment, - self.params - )?; - - // Compute rk = [rsk] ProvingPublicKey - let rk; - { - // Witness rsk as bits - let rsk = boolean::field_into_boolean_vec_le( - cs.namespace(|| "rsk"), - self.proof_generation_key.as_ref().map(|k| k.rsk.clone()) - )?; - - // 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. - - // Compute rk = [rsk] ProvingPublicKey - rk = ecc::fixed_base_multiplication( - cs.namespace(|| "computation of rk"), - FixedGenerators::ProofGenerationKey, - &rsk, - self.params - )?; - } - - // Prover witnesses ak (ensures that it's on the curve) - let ak = ecc::EdwardsPoint::witness( - cs.namespace(|| "ak"), - self.proof_generation_key.as_ref().map(|k| k.ak.clone()), - self.params - )?; - - // There are no sensible attacks on small order points - // of ak (that we're aware of!) but it's a cheap check, - // so we do it. - ak.assert_not_small_order( - cs.namespace(|| "ak not small order"), - self.params - )?; - - // Unpack ak and rk for input to BLAKE2s - - // This is the "viewing key" preimage for CRH^ivk - let mut vk = vec![]; - vk.extend( - ak.repr(cs.namespace(|| "representation of ak"))? - ); - - // This is the nullifier randomness preimage for PRF^nr - let mut nr_preimage = vec![]; - - // Extend vk and nr preimages with the representation of - // rk. - { - let repr_rk = rk.repr( - cs.namespace(|| "representation of rk") - )?; - - vk.extend(repr_rk.iter().cloned()); - nr_preimage.extend(repr_rk); - } - - assert_eq!(vk.len(), 512); - assert_eq!(nr_preimage.len(), 256); - - // Compute the incoming viewing key ivk - let mut ivk = blake2s::blake2s( - cs.namespace(|| "computation of ivk"), - &vk, - constants::CRH_IVK_PERSONALIZATION - )?; - - // Little endian bit order - ivk.reverse(); - - // drop_5 to ensure it's in the field - ivk.truncate(E::Fs::CAPACITY as usize); - - // Witness g_d. Ensures the point is on the - // curve, but not its order. If the prover - // manages to witness a commitment in the - // tree, then the Output circuit would have - // already guaranteed this. - let g_d = { - // This binding is to avoid a weird edge case in Rust's - // ownership/borrowing rules. self is partially moved - // above, but the closure for and_then will have to - // move self (or a reference to self) to reference - // self.params, so we have to copy self.params here. - let params = self.params; - - ecc::EdwardsPoint::witness( - cs.namespace(|| "witness g_d"), - self.payment_address.as_ref().and_then(|a| a.g_d(params)), - self.params - )? - }; - - // Check that g_d is not small order. Technically, this check - // is already done in the Output circuit, and this proof ensures - // g_d is bound to a product of that check, but for defense in - // depth let's check it anyway. It's cheap. - g_d.assert_not_small_order(cs.namespace(|| "g_d not small order"), self.params)?; - - // Compute pk_d = g_d^ivk - let pk_d = g_d.mul( - cs.namespace(|| "compute pk_d"), - &ivk, - self.params - )?; - - // Compute note contents - // value (in big endian) followed by g_d and pk_d - let mut note_contents = vec![]; - note_contents.extend(value_bits.into_iter().rev()); - 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(), - 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 for the note commitment - let cmr = boolean::field_into_boolean_vec_le( - cs.namespace(|| "cmr"), - self.commitment_randomness - )?; - - // Compute the note commitment randomness in the exponent - let cmr = ecc::fixed_base_multiplication( - cs.namespace(|| "computation of commitment randomness"), - FixedGenerators::NoteCommitmentRandomness, - &cmr, - self.params - )?; - - // Randomize the note commitment. Pedersen hashes are not - // themselves hiding commitments. - cm = cm.add( - cs.namespace(|| "randomization of note commitment"), - &cmr, - self.params - )?; - } - - let tree_depth = self.auth_path.len(); - - // This will store (least significant bit first) - // the position of the note in the tree, for use - // in nullifier computation. - let mut position_bits = vec![]; - - // This is an injective encoding, as cur is a - // point in the prime order subgroup. - let mut cur = cm.get_x().clone(); - - for (i, e) in self.auth_path.into_iter().enumerate() { - let cs = &mut cs.namespace(|| format!("merkle tree hash {}", i)); - - // Determines if the current subtree is the "right" leaf at this - // depth of the tree. - let cur_is_right = boolean::Boolean::from(boolean::AllocatedBit::alloc( - cs.namespace(|| "position bit"), - e.map(|e| e.1) - )?); - - // Push this boolean for nullifier computation later - position_bits.push(cur_is_right.clone()); - - // Witness the authentication path element adjacent - // at this depth. - let path_element = num::AllocatedNum::alloc( - cs.namespace(|| "path element"), - || { - Ok(e.get()?.0) - } - )?; - - // Swap the two if the current subtree is on the right - 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. If the prover witnesses a congruency, - // they will be unable to find an authentication path in the - // tree with high probability. - let mut preimage = vec![]; - preimage.extend(xl.into_bits_le(cs.namespace(|| "xl into bits"))?); - preimage.extend(xr.into_bits_le(cs.namespace(|| "xr into bits"))?); - - // Compute the new subtree value - cur = pedersen_hash::pedersen_hash( - cs.namespace(|| "computation of pedersen hash"), - pedersen_hash::Personalization::MerkleTree(i), - &preimage, - self.params - )?.get_x().clone(); // Injective encoding - } - - assert_eq!(position_bits.len(), tree_depth); - - // Expose the anchor - cur.inputize(cs.namespace(|| "anchor"))?; - - // Compute the cm + g^position for preventing - // faerie gold attacks - { - // Compute the position in the exponent - let position = ecc::fixed_base_multiplication( - cs.namespace(|| "g^position"), - FixedGenerators::NullifierPosition, - &position_bits, - self.params - )?; - - // Add the position to the commitment - cm = cm.add( - cs.namespace(|| "faerie gold prevention"), - &position, - self.params - )?; - } - - // Let's compute nr = BLAKE2s(rk || cm + position) - nr_preimage.extend( - cm.repr(cs.namespace(|| "representation of cm"))? - ); - - assert_eq!(nr_preimage.len(), 512); - - // Compute nr - let mut nr = blake2s::blake2s( - cs.namespace(|| "nr computation"), - &nr_preimage, - constants::PRF_NR_PERSONALIZATION - )?; - - // Little endian bit order - nr.reverse(); - - // We want the randomization in the field to - // simplify outside code. - // TODO: This isn't uniformly random. - nr.truncate(E::Fs::CAPACITY as usize); - - // Compute nullifier - let nf = ak.mul( - cs.namespace(|| "computation of nf"), - &nr, - self.params - )?; - - // Expose the nullifier publicly - nf.inputize(cs.namespace(|| "nullifier"))?; - - Ok(()) - } -} - -impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { - fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> - { - let value_bits = expose_value_commitment( - cs.namespace(|| "value commitment"), - self.value_commitment, - self.params - )?; - - // Let's start to construct our note, which contains - // value (big endian) - let mut note_contents = vec![]; - note_contents.extend(value_bits.into_iter().rev()); - - // Let's deal with g_d - { - let params = self.params; - - // Prover witnesses g_d, ensuring it's on the - // curve. - let g_d = ecc::EdwardsPoint::witness( - cs.namespace(|| "witness g_d"), - self.payment_address.as_ref().and_then(|a| a.g_d(params)), - self.params - )?; - - // g_d is ensured to be large order. The relationship - // between g_d and pk_d ultimately binds ivk to the - // note. If this were a small order point, it would - // not do this correctly, and the prover could - // double-spend by finding random ivk's that satisfy - // the relationship. - // - // Further, if it were small order, epk would be - // small order too! - g_d.assert_not_small_order( - cs.namespace(|| "g_d not small order"), - self.params - )?; - - // Extend our note contents with the representation of - // g_d. - note_contents.extend( - g_d.repr(cs.namespace(|| "representation of g_d"))? - ); - - // Booleanize our ephemeral secret key - let esk = boolean::field_into_boolean_vec_le( - cs.namespace(|| "esk"), - self.esk - )?; - - // Create the ephemeral public key from g_d. - let epk = g_d.mul( - cs.namespace(|| "epk computation"), - &esk, - self.params - )?; - - // Expose epk publicly. - epk.inputize(cs.namespace(|| "epk"))?; - } - - // Now let's deal with pk_d. We don't do any checks and - // essentially allow the prover to witness any 256 bits - // they would like. - { - // Just grab pk_d from the witness - let pk_d = self.payment_address.as_ref().map(|e| e.pk_d.into_xy()); - - // Witness the y-coordinate, encoded as little - // endian bits (to match the representation) - let y_contents = boolean::field_into_boolean_vec_le( - cs.namespace(|| "pk_d bits of y"), - pk_d.map(|e| e.1) - )?; - - // Witness the sign bit - let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc( - cs.namespace(|| "pk_d bit of x"), - pk_d.map(|e| e.0.into_repr().is_odd()) - )?); - - // Extend the note with pk_d representation - 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_boolean_vec_le( - cs.namespace(|| "cmr"), - self.commitment_randomness - )?; - - // Compute the note commitment randomness in the exponent - let cmr = ecc::fixed_base_multiplication( - cs.namespace(|| "computation of commitment randomness"), - FixedGenerators::NoteCommitmentRandomness, - &cmr, - self.params - )?; - - // Randomize our note commitment - 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. - cm.get_x().inputize(cs.namespace(|| "commitment"))?; - - Ok(()) - } -} - -#[test] -fn test_input_circuit_with_bls12_381() { - use pairing::{Field, BitIterator}; - use pairing::bls12_381::*; - use rand::{SeedableRng, Rng, XorShiftRng}; - use ::circuit::test::*; - use jubjub::{JubjubBls12, fs, edwards}; - - let params = &JubjubBls12::new(); - let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let tree_depth = 32; - - let value_commitment = ValueCommitment { - value: rng.gen(), - randomness: rng.gen() - }; - - let rsk: fs::Fs = rng.gen(); - let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); - - let proof_generation_key = ::primitives::ProofGenerationKey { - ak: ak.clone(), - rsk: rsk.clone() - }; - - let viewing_key = proof_generation_key.into_viewing_key(params); - - let payment_address; - - loop { - let diversifier = ::primitives::Diversifier(rng.gen()); - - if let Some(p) = viewing_key.into_payment_address( - diversifier, - params - ) - { - payment_address = p; - break; - } - } - - let g_d = payment_address.diversifier.g_d(params).unwrap(); - let commitment_randomness: 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_commitment: Some(value_commitment.clone()), - proof_generation_key: Some(proof_generation_key.clone()), - payment_address: Some(payment_address.clone()), - commitment_randomness: Some(commitment_randomness), - auth_path: auth_path.clone() - }; - - instance.synthesize(&mut cs).unwrap(); - - assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 101018); - assert_eq!(cs.hash(), "eedcef5fd638e0168ae4d53ac58df66f0acdabea46749cc5f4b39459c8377804"); - - let expected_value_cm = value_commitment.cm(params).into_xy(); - - assert_eq!(cs.num_inputs(), 6); - assert_eq!(cs.get_input(0, "ONE"), Fr::one()); - assert_eq!(cs.get_input(1, "value commitment/commitment point/x/input variable"), expected_value_cm.0); - assert_eq!(cs.get_input(2, "value commitment/commitment point/y/input variable"), expected_value_cm.1); - - let note = ::primitives::Note { - value: value_commitment.value, - g_d: g_d.clone(), - pk_d: payment_address.pk_d.clone(), - r: commitment_randomness.clone() - }; - - let mut position = 0u64; - let mut cur = note.cm(params); - - assert_eq!(cs.get("randomization of note commitment/x3/num"), cur); - - for (i, val) in auth_path.into_iter().enumerate() - { - let (uncle, b) = val.unwrap(); - - let mut lhs = cur; - let mut rhs = uncle; - - if b { - ::std::mem::swap(&mut lhs, &mut rhs); - } - - let mut lhs: Vec = BitIterator::new(lhs.into_repr()).collect(); - let mut rhs: Vec = BitIterator::new(rhs.into_repr()).collect(); - - lhs.reverse(); - rhs.reverse(); - - cur = ::pedersen_hash::pedersen_hash::( - ::pedersen_hash::Personalization::MerkleTree(i), - lhs.into_iter() - .take(Fr::NUM_BITS as usize) - .chain(rhs.into_iter().take(Fr::NUM_BITS as usize)), - params - ).into_xy().0; - - if b { - position |= 1 << i; - } - } - - let expected_nf = note.nf(&viewing_key, position, params); - let expected_nf_xy = expected_nf.into_xy(); - - assert_eq!(cs.get_input(3, "anchor/input variable"), cur); - assert_eq!(cs.get_input(4, "nullifier/x/input variable"), expected_nf_xy.0); - assert_eq!(cs.get_input(5, "nullifier/y/input variable"), expected_nf_xy.1); - } -} - -#[test] -fn test_output_circuit_with_bls12_381() { - use pairing::{Field}; - use pairing::bls12_381::*; - use rand::{SeedableRng, Rng, XorShiftRng}; - use ::circuit::test::*; - use jubjub::{JubjubBls12, fs, edwards}; - - let params = &JubjubBls12::new(); - let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let value_commitment = ValueCommitment { - value: rng.gen(), - randomness: rng.gen() - }; - - let rsk: fs::Fs = rng.gen(); - let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); - - let proof_generation_key = ::primitives::ProofGenerationKey { - ak: ak.clone(), - rsk: rsk.clone() - }; - - let viewing_key = proof_generation_key.into_viewing_key(params); - - let payment_address; - - loop { - let diversifier = ::primitives::Diversifier(rng.gen()); - - if let Some(p) = viewing_key.into_payment_address( - diversifier, - params - ) - { - payment_address = p; - break; - } - } - - let commitment_randomness: fs::Fs = rng.gen(); - let esk: fs::Fs = rng.gen(); - - { - let mut cs = TestConstraintSystem::::new(); - - let instance = Output { - params: params, - value_commitment: Some(value_commitment.clone()), - payment_address: Some(payment_address.clone()), - commitment_randomness: Some(commitment_randomness), - esk: Some(esk.clone()) - }; - - instance.synthesize(&mut cs).unwrap(); - - assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "2896f259ad7a50c83604976ee9362358396d547b70f2feaf91d82d287e4ffc1d"); - - let expected_cm = payment_address.create_note( - value_commitment.value, - commitment_randomness, - params - ).expect("should be valid").cm(params); - - let expected_value_cm = value_commitment.cm(params).into_xy(); - - let expected_epk = payment_address.g_d(params).expect("should be valid").mul(esk, params); - let expected_epk_xy = expected_epk.into_xy(); - - assert_eq!(cs.num_inputs(), 6); - assert_eq!(cs.get_input(0, "ONE"), Fr::one()); - assert_eq!(cs.get_input(1, "value commitment/commitment point/x/input variable"), expected_value_cm.0); - assert_eq!(cs.get_input(2, "value commitment/commitment point/y/input variable"), expected_value_cm.1); - assert_eq!(cs.get_input(3, "epk/x/input variable"), expected_epk_xy.0); - assert_eq!(cs.get_input(4, "epk/y/input variable"), expected_epk_xy.1); - assert_eq!(cs.get_input(5, "commitment/input variable"), expected_cm); - } -} diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs new file mode 100644 index 0000000..9b3b6de --- /dev/null +++ b/src/circuit/sapling/mod.rs @@ -0,0 +1,748 @@ +use pairing::{ + PrimeField, + PrimeFieldRepr, +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + Circuit +}; + +use jubjub::{ + JubjubEngine, + FixedGenerators +}; + +use constants; + +use primitives::{ + ValueCommitment, + ProofGenerationKey, + PaymentAddress +}; + +use super::Assignment; +use super::boolean; +use super::ecc; +use super::pedersen_hash; +use super::blake2s; +use super::num; + +/// This is an instance of the `Spend` circuit. +pub struct Spend<'a, E: JubjubEngine> { + pub params: &'a E::Params, + + /// Pedersen commitment to the value being spent + pub value_commitment: Option>, + + /// Key required to construct proofs for spending notes + /// for a particular spending key + pub proof_generation_key: Option>, + + /// The payment address associated with the note + pub payment_address: Option>, + + /// The randomness of the note commitment + pub commitment_randomness: Option, + + /// The authentication path of the commitment in the tree + pub auth_path: Vec> +} + +/// This is an output circuit instance. +pub struct Output<'a, E: JubjubEngine> { + pub params: &'a E::Params, + + /// Pedersen commitment to the value being spent + pub value_commitment: Option>, + + /// The payment address of the recipient + pub payment_address: 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 +} + +/// Exposes a Pedersen commitment to the value as an +/// input to the circuit +fn expose_value_commitment( + mut cs: CS, + value_commitment: Option>, + params: &E::Params +) -> Result, SynthesisError> + where E: JubjubEngine, + CS: ConstraintSystem +{ + // Booleanize the value into little-endian bit order + let value_bits = boolean::u64_into_boolean_vec_le( + cs.namespace(|| "value"), + value_commitment.as_ref().map(|c| c.value) + )?; + + // Compute the note value in the exponent + let gv = ecc::fixed_base_multiplication( + cs.namespace(|| "compute the value in the exponent"), + FixedGenerators::ValueCommitmentValue, + &value_bits, + params + )?; + + // Booleanize the randomness. This does not ensure + // the bit representation is "in the field" because + // it doesn't matter for security. + let hr = boolean::field_into_boolean_vec_le( + cs.namespace(|| "hr"), + value_commitment.as_ref().map(|c| c.randomness) + )?; + + // Compute the randomness in the exponent + let hr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of randomization for value commitment"), + FixedGenerators::ValueCommitmentRandomness, + &hr, + params + )?; + + // Compute the Pedersen commitment to the value + let gvhr = gv.add( + cs.namespace(|| "computation of value commitment"), + &hr, + params + )?; + + // Expose the commitment as an input to the circuit + gvhr.inputize(cs.namespace(|| "commitment point"))?; + + Ok(value_bits) +} + +impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> + { + let value_bits = expose_value_commitment( + cs.namespace(|| "value commitment"), + self.value_commitment, + self.params + )?; + + // Compute rk = [rsk] ProvingPublicKey + let rk; + { + // Witness rsk as bits + let rsk = boolean::field_into_boolean_vec_le( + cs.namespace(|| "rsk"), + self.proof_generation_key.as_ref().map(|k| k.rsk.clone()) + )?; + + // 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. + + // Compute rk = [rsk] ProvingPublicKey + rk = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of rk"), + FixedGenerators::ProofGenerationKey, + &rsk, + self.params + )?; + } + + // Prover witnesses ak (ensures that it's on the curve) + let ak = ecc::EdwardsPoint::witness( + cs.namespace(|| "ak"), + self.proof_generation_key.as_ref().map(|k| k.ak.clone()), + self.params + )?; + + // There are no sensible attacks on small order points + // of ak (that we're aware of!) but it's a cheap check, + // so we do it. + ak.assert_not_small_order( + cs.namespace(|| "ak not small order"), + self.params + )?; + + // Unpack ak and rk for input to BLAKE2s + + // This is the "viewing key" preimage for CRH^ivk + let mut vk = vec![]; + vk.extend( + ak.repr(cs.namespace(|| "representation of ak"))? + ); + + // This is the nullifier randomness preimage for PRF^nr + let mut nr_preimage = vec![]; + + // Extend vk and nr preimages with the representation of + // rk. + { + let repr_rk = rk.repr( + cs.namespace(|| "representation of rk") + )?; + + vk.extend(repr_rk.iter().cloned()); + nr_preimage.extend(repr_rk); + } + + assert_eq!(vk.len(), 512); + assert_eq!(nr_preimage.len(), 256); + + // Compute the incoming viewing key ivk + let mut ivk = blake2s::blake2s( + cs.namespace(|| "computation of ivk"), + &vk, + constants::CRH_IVK_PERSONALIZATION + )?; + + // Little endian bit order + ivk.reverse(); + + // drop_5 to ensure it's in the field + ivk.truncate(E::Fs::CAPACITY as usize); + + // Witness g_d. Ensures the point is on the + // curve, but not its order. If the prover + // manages to witness a commitment in the + // tree, then the Output circuit would have + // already guaranteed this. + let g_d = { + // This binding is to avoid a weird edge case in Rust's + // ownership/borrowing rules. self is partially moved + // above, but the closure for and_then will have to + // move self (or a reference to self) to reference + // self.params, so we have to copy self.params here. + let params = self.params; + + ecc::EdwardsPoint::witness( + cs.namespace(|| "witness g_d"), + self.payment_address.as_ref().and_then(|a| a.g_d(params)), + self.params + )? + }; + + // Check that g_d is not small order. Technically, this check + // is already done in the Output circuit, and this proof ensures + // g_d is bound to a product of that check, but for defense in + // depth let's check it anyway. It's cheap. + g_d.assert_not_small_order(cs.namespace(|| "g_d not small order"), self.params)?; + + // Compute pk_d = g_d^ivk + let pk_d = g_d.mul( + cs.namespace(|| "compute pk_d"), + &ivk, + self.params + )?; + + // Compute note contents + // value (in big endian) followed by g_d and pk_d + let mut note_contents = vec![]; + note_contents.extend(value_bits.into_iter().rev()); + 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(), + 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 for the note commitment + let cmr = boolean::field_into_boolean_vec_le( + cs.namespace(|| "cmr"), + self.commitment_randomness + )?; + + // Compute the note commitment randomness in the exponent + let cmr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of commitment randomness"), + FixedGenerators::NoteCommitmentRandomness, + &cmr, + self.params + )?; + + // Randomize the note commitment. Pedersen hashes are not + // themselves hiding commitments. + cm = cm.add( + cs.namespace(|| "randomization of note commitment"), + &cmr, + self.params + )?; + } + + let tree_depth = self.auth_path.len(); + + // This will store (least significant bit first) + // the position of the note in the tree, for use + // in nullifier computation. + let mut position_bits = vec![]; + + // This is an injective encoding, as cur is a + // point in the prime order subgroup. + let mut cur = cm.get_x().clone(); + + for (i, e) in self.auth_path.into_iter().enumerate() { + let cs = &mut cs.namespace(|| format!("merkle tree hash {}", i)); + + // Determines if the current subtree is the "right" leaf at this + // depth of the tree. + let cur_is_right = boolean::Boolean::from(boolean::AllocatedBit::alloc( + cs.namespace(|| "position bit"), + e.map(|e| e.1) + )?); + + // Push this boolean for nullifier computation later + position_bits.push(cur_is_right.clone()); + + // Witness the authentication path element adjacent + // at this depth. + let path_element = num::AllocatedNum::alloc( + cs.namespace(|| "path element"), + || { + Ok(e.get()?.0) + } + )?; + + // Swap the two if the current subtree is on the right + 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. If the prover witnesses a congruency, + // they will be unable to find an authentication path in the + // tree with high probability. + let mut preimage = vec![]; + preimage.extend(xl.into_bits_le(cs.namespace(|| "xl into bits"))?); + preimage.extend(xr.into_bits_le(cs.namespace(|| "xr into bits"))?); + + // Compute the new subtree value + cur = pedersen_hash::pedersen_hash( + cs.namespace(|| "computation of pedersen hash"), + pedersen_hash::Personalization::MerkleTree(i), + &preimage, + self.params + )?.get_x().clone(); // Injective encoding + } + + assert_eq!(position_bits.len(), tree_depth); + + // Expose the anchor + cur.inputize(cs.namespace(|| "anchor"))?; + + // Compute the cm + g^position for preventing + // faerie gold attacks + { + // Compute the position in the exponent + let position = ecc::fixed_base_multiplication( + cs.namespace(|| "g^position"), + FixedGenerators::NullifierPosition, + &position_bits, + self.params + )?; + + // Add the position to the commitment + cm = cm.add( + cs.namespace(|| "faerie gold prevention"), + &position, + self.params + )?; + } + + // Let's compute nr = BLAKE2s(rk || cm + position) + nr_preimage.extend( + cm.repr(cs.namespace(|| "representation of cm"))? + ); + + assert_eq!(nr_preimage.len(), 512); + + // Compute nr + let mut nr = blake2s::blake2s( + cs.namespace(|| "nr computation"), + &nr_preimage, + constants::PRF_NR_PERSONALIZATION + )?; + + // Little endian bit order + nr.reverse(); + + // We want the randomization in the field to + // simplify outside code. + // TODO: This isn't uniformly random. + nr.truncate(E::Fs::CAPACITY as usize); + + // Compute nullifier + let nf = ak.mul( + cs.namespace(|| "computation of nf"), + &nr, + self.params + )?; + + // Expose the nullifier publicly + nf.inputize(cs.namespace(|| "nullifier"))?; + + Ok(()) + } +} + +impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> + { + let value_bits = expose_value_commitment( + cs.namespace(|| "value commitment"), + self.value_commitment, + self.params + )?; + + // Let's start to construct our note, which contains + // value (big endian) + let mut note_contents = vec![]; + note_contents.extend(value_bits.into_iter().rev()); + + // Let's deal with g_d + { + let params = self.params; + + // Prover witnesses g_d, ensuring it's on the + // curve. + let g_d = ecc::EdwardsPoint::witness( + cs.namespace(|| "witness g_d"), + self.payment_address.as_ref().and_then(|a| a.g_d(params)), + self.params + )?; + + // g_d is ensured to be large order. The relationship + // between g_d and pk_d ultimately binds ivk to the + // note. If this were a small order point, it would + // not do this correctly, and the prover could + // double-spend by finding random ivk's that satisfy + // the relationship. + // + // Further, if it were small order, epk would be + // small order too! + g_d.assert_not_small_order( + cs.namespace(|| "g_d not small order"), + self.params + )?; + + // Extend our note contents with the representation of + // g_d. + note_contents.extend( + g_d.repr(cs.namespace(|| "representation of g_d"))? + ); + + // Booleanize our ephemeral secret key + let esk = boolean::field_into_boolean_vec_le( + cs.namespace(|| "esk"), + self.esk + )?; + + // Create the ephemeral public key from g_d. + let epk = g_d.mul( + cs.namespace(|| "epk computation"), + &esk, + self.params + )?; + + // Expose epk publicly. + epk.inputize(cs.namespace(|| "epk"))?; + } + + // Now let's deal with pk_d. We don't do any checks and + // essentially allow the prover to witness any 256 bits + // they would like. + { + // Just grab pk_d from the witness + let pk_d = self.payment_address.as_ref().map(|e| e.pk_d.into_xy()); + + // Witness the y-coordinate, encoded as little + // endian bits (to match the representation) + let y_contents = boolean::field_into_boolean_vec_le( + cs.namespace(|| "pk_d bits of y"), + pk_d.map(|e| e.1) + )?; + + // Witness the sign bit + let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc( + cs.namespace(|| "pk_d bit of x"), + pk_d.map(|e| e.0.into_repr().is_odd()) + )?); + + // Extend the note with pk_d representation + 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_boolean_vec_le( + cs.namespace(|| "cmr"), + self.commitment_randomness + )?; + + // Compute the note commitment randomness in the exponent + let cmr = ecc::fixed_base_multiplication( + cs.namespace(|| "computation of commitment randomness"), + FixedGenerators::NoteCommitmentRandomness, + &cmr, + self.params + )?; + + // Randomize our note commitment + 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. + cm.get_x().inputize(cs.namespace(|| "commitment"))?; + + Ok(()) + } +} + +#[test] +fn test_input_circuit_with_bls12_381() { + use pairing::{Field, BitIterator}; + use pairing::bls12_381::*; + use rand::{SeedableRng, Rng, XorShiftRng}; + use ::circuit::test::*; + use jubjub::{JubjubBls12, fs, edwards}; + + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let tree_depth = 32; + + let value_commitment = ValueCommitment { + value: rng.gen(), + randomness: rng.gen() + }; + + let rsk: fs::Fs = rng.gen(); + let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); + + let proof_generation_key = ::primitives::ProofGenerationKey { + ak: ak.clone(), + rsk: rsk.clone() + }; + + let viewing_key = proof_generation_key.into_viewing_key(params); + + let payment_address; + + loop { + let diversifier = ::primitives::Diversifier(rng.gen()); + + if let Some(p) = viewing_key.into_payment_address( + diversifier, + params + ) + { + payment_address = p; + break; + } + } + + let g_d = payment_address.diversifier.g_d(params).unwrap(); + let commitment_randomness: 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_commitment: Some(value_commitment.clone()), + proof_generation_key: Some(proof_generation_key.clone()), + payment_address: Some(payment_address.clone()), + commitment_randomness: Some(commitment_randomness), + auth_path: auth_path.clone() + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 101018); + assert_eq!(cs.hash(), "eedcef5fd638e0168ae4d53ac58df66f0acdabea46749cc5f4b39459c8377804"); + + let expected_value_cm = value_commitment.cm(params).into_xy(); + + assert_eq!(cs.num_inputs(), 6); + assert_eq!(cs.get_input(0, "ONE"), Fr::one()); + assert_eq!(cs.get_input(1, "value commitment/commitment point/x/input variable"), expected_value_cm.0); + assert_eq!(cs.get_input(2, "value commitment/commitment point/y/input variable"), expected_value_cm.1); + + let note = ::primitives::Note { + value: value_commitment.value, + g_d: g_d.clone(), + pk_d: payment_address.pk_d.clone(), + r: commitment_randomness.clone() + }; + + let mut position = 0u64; + let mut cur = note.cm(params); + + assert_eq!(cs.get("randomization of note commitment/x3/num"), cur); + + for (i, val) in auth_path.into_iter().enumerate() + { + let (uncle, b) = val.unwrap(); + + let mut lhs = cur; + let mut rhs = uncle; + + if b { + ::std::mem::swap(&mut lhs, &mut rhs); + } + + let mut lhs: Vec = BitIterator::new(lhs.into_repr()).collect(); + let mut rhs: Vec = BitIterator::new(rhs.into_repr()).collect(); + + lhs.reverse(); + rhs.reverse(); + + cur = ::pedersen_hash::pedersen_hash::( + ::pedersen_hash::Personalization::MerkleTree(i), + lhs.into_iter() + .take(Fr::NUM_BITS as usize) + .chain(rhs.into_iter().take(Fr::NUM_BITS as usize)), + params + ).into_xy().0; + + if b { + position |= 1 << i; + } + } + + let expected_nf = note.nf(&viewing_key, position, params); + let expected_nf_xy = expected_nf.into_xy(); + + assert_eq!(cs.get_input(3, "anchor/input variable"), cur); + assert_eq!(cs.get_input(4, "nullifier/x/input variable"), expected_nf_xy.0); + assert_eq!(cs.get_input(5, "nullifier/y/input variable"), expected_nf_xy.1); + } +} + +#[test] +fn test_output_circuit_with_bls12_381() { + use pairing::{Field}; + use pairing::bls12_381::*; + use rand::{SeedableRng, Rng, XorShiftRng}; + use ::circuit::test::*; + use jubjub::{JubjubBls12, fs, edwards}; + + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let value_commitment = ValueCommitment { + value: rng.gen(), + randomness: rng.gen() + }; + + let rsk: fs::Fs = rng.gen(); + let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params); + + let proof_generation_key = ::primitives::ProofGenerationKey { + ak: ak.clone(), + rsk: rsk.clone() + }; + + let viewing_key = proof_generation_key.into_viewing_key(params); + + let payment_address; + + loop { + let diversifier = ::primitives::Diversifier(rng.gen()); + + if let Some(p) = viewing_key.into_payment_address( + diversifier, + params + ) + { + payment_address = p; + break; + } + } + + let commitment_randomness: fs::Fs = rng.gen(); + let esk: fs::Fs = rng.gen(); + + { + let mut cs = TestConstraintSystem::::new(); + + let instance = Output { + params: params, + value_commitment: Some(value_commitment.clone()), + payment_address: Some(payment_address.clone()), + commitment_randomness: Some(commitment_randomness), + esk: Some(esk.clone()) + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 7827); + assert_eq!(cs.hash(), "2896f259ad7a50c83604976ee9362358396d547b70f2feaf91d82d287e4ffc1d"); + + let expected_cm = payment_address.create_note( + value_commitment.value, + commitment_randomness, + params + ).expect("should be valid").cm(params); + + let expected_value_cm = value_commitment.cm(params).into_xy(); + + let expected_epk = payment_address.g_d(params).expect("should be valid").mul(esk, params); + let expected_epk_xy = expected_epk.into_xy(); + + assert_eq!(cs.num_inputs(), 6); + assert_eq!(cs.get_input(0, "ONE"), Fr::one()); + assert_eq!(cs.get_input(1, "value commitment/commitment point/x/input variable"), expected_value_cm.0); + assert_eq!(cs.get_input(2, "value commitment/commitment point/y/input variable"), expected_value_cm.1); + assert_eq!(cs.get_input(3, "epk/x/input variable"), expected_epk_xy.0); + assert_eq!(cs.get_input(4, "epk/y/input variable"), expected_epk_xy.1); + assert_eq!(cs.get_input(5, "commitment/input variable"), expected_cm); + } +}