From def5de3f1607718d2d608abe9d0c959f66ca3e19 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 25 Mar 2018 23:09:11 -0600 Subject: [PATCH] Allow the authentication path to be not enforced for zero-value notes, for indistinguishability. --- src/circuit/sapling/mod.rs | 130 +++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 43 deletions(-) diff --git a/src/circuit/sapling/mod.rs b/src/circuit/sapling/mod.rs index 4e2ea40..ef4ef3f 100644 --- a/src/circuit/sapling/mod.rs +++ b/src/circuit/sapling/mod.rs @@ -1,6 +1,7 @@ use pairing::{ PrimeField, PrimeFieldRepr, + Field, }; use bellman::{ @@ -51,7 +52,11 @@ pub struct Spend<'a, E: JubjubEngine> { pub ar: Option, /// The authentication path of the commitment in the tree - pub auth_path: Vec> + pub auth_path: Vec>, + + /// The anchor; the root of the tree. If the note being + /// spent is zero-value, this can be anything. + pub anchor: Option } /// This is an output circuit instance. @@ -265,13 +270,32 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // value (in big endian) followed by g_d and pk_d let mut note_contents = vec![]; - // Expose the value commitment and place the value - // in the note. - note_contents.extend(expose_value_commitment( - cs.namespace(|| "value commitment"), - self.value_commitment, - self.params - )?); + // Handle the value; we'll need it later for the + // dummy input check. + let mut value_num = num::Num::zero(); + { + // Get the value in little-endian bit order + let value_bits = expose_value_commitment( + cs.namespace(|| "value commitment"), + self.value_commitment, + self.params + )?; + + // Compute the note's value as a linear combination + // of the bits. + let mut coeff = E::Fr::one(); + for bit in &value_bits { + value_num = value_num.add_bool_with_coeff( + CS::one(), + bit, + coeff + ); + coeff.double(); + } + + // Place the value in the note + note_contents.extend(value_bits); + } // Place g_d in the note note_contents.extend( @@ -379,8 +403,30 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?.get_x().clone(); // Injective encoding } - // Expose the anchor - cur.inputize(cs.namespace(|| "anchor"))?; + { + let real_anchor_value = self.anchor; + + // Allocate the "real" anchor that will be exposed. + let rt = num::AllocatedNum::alloc( + cs.namespace(|| "conditional anchor"), + || { + Ok(*real_anchor_value.get()?) + } + )?; + + // (cur - rt) * value = 0 + // if value is zero, cur and rt can be different + // if value is nonzero, they must be equal + cs.enforce( + || "conditionally enforce correct root", + |lc| lc + cur.get_variable() - rt.get_variable(), + |lc| lc + &value_num.lc(E::Fr::one()), + |lc| lc + ); + + // Expose the anchor + rt.inputize(cs.namespace(|| "anchor"))?; + } // Compute the cm + g^position for preventing // faerie gold attacks @@ -607,35 +653,8 @@ fn test_input_circuit_with_bls12_381() { let ar: fs::Fs = rng.gen(); { - 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), - ar: Some(ar), - auth_path: auth_path.clone() - }; - - instance.synthesize(&mut cs).unwrap(); - - assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 98776); - assert_eq!(cs.hash(), "8211d52b5ad2618b2f8106c7c3f9ab213f6206e3ddbbb39e786167de5ea85dc3"); - - assert_eq!(cs.num_inputs(), 8); - assert_eq!(cs.get_input(0, "ONE"), Fr::one()); - let rk = viewing_key.rk(ar, params).into_xy(); - assert_eq!(cs.get_input(1, "rk/x/input variable"), rk.0); - assert_eq!(cs.get_input(2, "rk/y/input variable"), rk.1); - let expected_value_cm = value_commitment.cm(params).into_xy(); - assert_eq!(cs.get_input(3, "value commitment/commitment point/x/input variable"), expected_value_cm.0); - assert_eq!(cs.get_input(4, "value commitment/commitment point/y/input variable"), expected_value_cm.1); - let note = ::primitives::Note { value: value_commitment.value, g_d: g_d.clone(), @@ -644,11 +663,10 @@ fn test_input_circuit_with_bls12_381() { }; let mut position = 0u64; - let mut cur = note.cm(params); + let cm: Fr = note.cm(params); + let mut cur = cm.clone(); - assert_eq!(cs.get("randomization of note commitment/x3/num"), cur); - - for (i, val) in auth_path.into_iter().enumerate() + for (i, val) in auth_path.clone().into_iter().enumerate() { let (uncle, b) = val.unwrap(); @@ -678,13 +696,39 @@ fn test_input_circuit_with_bls12_381() { } } - assert_eq!(cs.get_input(5, "anchor/input variable"), cur); - let expected_nf = note.nf(&viewing_key, position, params); let expected_nf = multipack::bytes_to_bits(&expected_nf); let expected_nf = multipack::compute_multipacking::(&expected_nf); assert_eq!(expected_nf.len(), 2); + 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), + ar: Some(ar), + auth_path: auth_path.clone(), + anchor: Some(cur) + }; + + instance.synthesize(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 98777); + assert_eq!(cs.hash(), "aedc6d7646e8e019db327bf256c322e54bc72aa9ac4e86943899557eb96507f3"); + + assert_eq!(cs.get("randomization of note commitment/x3/num"), cm); + + assert_eq!(cs.num_inputs(), 8); + assert_eq!(cs.get_input(0, "ONE"), Fr::one()); + assert_eq!(cs.get_input(1, "rk/x/input variable"), rk.0); + assert_eq!(cs.get_input(2, "rk/y/input variable"), rk.1); + assert_eq!(cs.get_input(3, "value commitment/commitment point/x/input variable"), expected_value_cm.0); + assert_eq!(cs.get_input(4, "value commitment/commitment point/y/input variable"), expected_value_cm.1); + assert_eq!(cs.get_input(5, "anchor/input variable"), cur); assert_eq!(cs.get_input(6, "pack nullifier/input 0"), expected_nf[0]); assert_eq!(cs.get_input(7, "pack nullifier/input 1"), expected_nf[1]); }