make step_circuit mandatory; add support for longer hashes (#30)
Srinath Setty srinath@microsoft.com
Fri, 15 Apr 2022 16:06:26 -0700
2 files changed,
158 insertions(+),
178 deletions(-)
M
src/circuit.rs
→
src/circuit.rs
@@ -104,7 +104,7 @@ SC: StepCircuit<G::Base>,
{ params: NIFSVerifierCircuitParams, inputs: Option<NIFSVerifierCircuitInputs<G>>, - step_circuit: Option<SC>, // The function that is applied for each step. may be None. + step_circuit: SC, // The function that is applied for each step poseidon_constants: NovaPoseidonConstants<G::Base>, }@@ -119,7 +119,7 @@ #[allow(dead_code)]
pub fn new( params: NIFSVerifierCircuitParams, inputs: Option<NIFSVerifierCircuitInputs<G>>, - step_circuit: Option<SC>, + step_circuit: SC, poseidon_constants: NovaPoseidonConstants<G::Base>, ) -> Self where@@ -289,7 +289,7 @@ /***********************************************************************/
// U2' = default if i == 0, otherwise NIFS.V(pp, u_new, U, T) /***********************************************************************/ - //Allocate 0 and 1 + // Allocate 0 and 1 let zero = alloc_zero(cs.namespace(|| "zero"))?; // Hack: We just do this because the number of inputs must be even!! zero.inputize(cs.namespace(|| "allocate zero as input"))?;@@ -477,181 +477,113 @@ |lc| lc,
|lc| lc + next_i.get_variable() - CS::one() - i.get_variable(), ); - if self.step_circuit.is_some() { - /***********************************************************************/ - //Allocate z0 - /***********************************************************************/ + /***********************************************************************/ + // Allocate z0 + /***********************************************************************/ - let z_0 = AllocatedNum::alloc(cs.namespace(|| "z0"), || Ok(self.inputs.get()?.z0))?; + let z_0 = AllocatedNum::alloc(cs.namespace(|| "z0"), || Ok(self.inputs.get()?.z0))?; - /***********************************************************************/ - //Allocate zi - /***********************************************************************/ + /***********************************************************************/ + // Allocate zi + /***********************************************************************/ - let z_i = AllocatedNum::alloc(cs.namespace(|| "zi"), || Ok(self.inputs.get()?.zi))?; + let z_i = AllocatedNum::alloc(cs.namespace(|| "zi"), || Ok(self.inputs.get()?.zi))?; - /***********************************************************************/ - //Check that if i == 0, z0 = zi, that is (i == 0) AND (z0 != zi) = false - /***********************************************************************/ + /***********************************************************************/ + //Check that if i == 0, z0 = zi, that is (i == 0) AND (z0 != zi) = false + /***********************************************************************/ - let z0_is_zi = Boolean::from(alloc_num_equals( - cs.namespace(|| "z0 = zi"), - z_0.clone(), - z_i.clone(), - )?); + let z0_is_zi = Boolean::from(alloc_num_equals( + cs.namespace(|| "z0 = zi"), + z_0.clone(), + z_i.clone(), + )?); - cs.enforce( - || "i == 0 and z0 != zi = false", - |_| base_case.lc(CS::one(), G::Base::one()), - |_| z0_is_zi.not().lc(CS::one(), G::Base::one()), - |lc| lc, - ); + cs.enforce( + || "i == 0 and z0 != zi = false", + |_| base_case.lc(CS::one(), G::Base::one()), + |_| z0_is_zi.not().lc(CS::one(), G::Base::one()), + |lc| lc, + ); - /***********************************************************************/ - // Check that h1 = Hash(params, u2,i,z0,zi) - /***********************************************************************/ + /***********************************************************************/ + // Check that h1 = Hash(params, u2,i,z0,zi) + /***********************************************************************/ - let mut h1_hash: PoseidonROGadget<G::Base> = - PoseidonROGadget::new(self.poseidon_constants.clone()); + let mut h1_hash: PoseidonROGadget<G::Base> = + PoseidonROGadget::new(self.poseidon_constants.clone()); - h1_hash.absorb(params.clone()); - h1_hash.absorb(W_r.x); - h1_hash.absorb(W_r.y); - h1_hash.absorb(W_r.is_infinity); - h1_hash.absorb(E_r.x); - h1_hash.absorb(E_r.y); - h1_hash.absorb(E_r.is_infinity); - h1_hash.absorb(u_r.clone()); + h1_hash.absorb(params.clone()); + h1_hash.absorb(W_r.x); + h1_hash.absorb(W_r.y); + h1_hash.absorb(W_r.is_infinity); + h1_hash.absorb(E_r.x); + h1_hash.absorb(E_r.y); + h1_hash.absorb(E_r.is_infinity); + h1_hash.absorb(u_r.clone()); - // absorb each of the limbs of X_r[0] - for limb in Xr0_bn.into_iter() { - h1_hash.absorb(limb); - } + // absorb each of the limbs of X_r[0] + for limb in Xr0_bn.into_iter() { + h1_hash.absorb(limb); + } - // absorb each of the limbs of X_r[1] - for limb in Xr1_bn.into_iter() { - h1_hash.absorb(limb); - } + // absorb each of the limbs of X_r[1] + for limb in Xr1_bn.into_iter() { + h1_hash.absorb(limb); + } - h1_hash.absorb(i.clone()); - h1_hash.absorb(z_0.clone()); - h1_hash.absorb(z_i.clone()); + h1_hash.absorb(i.clone()); + h1_hash.absorb(z_0.clone()); + h1_hash.absorb(z_i.clone()); - let hash_bits = h1_hash.get_challenge(cs.namespace(|| "Input hash"))?; - let hash = le_bits_to_num(cs.namespace(|| "bits to hash"), hash_bits)?; + let hash_bits = h1_hash.get_challenge(cs.namespace(|| "Input hash"))?; // TODO: use get_hash method + let hash = le_bits_to_num(cs.namespace(|| "bits to hash"), hash_bits)?; - cs.enforce( - || "check h1", - |lc| lc, - |lc| lc, - |lc| lc + h1.get_variable() - hash.get_variable(), - ); + cs.enforce( + || "check h1", + |lc| lc, + |lc| lc, + |lc| lc + h1.get_variable() - hash.get_variable(), + ); - /***********************************************************************/ - // Compute z_{i+1} - /***********************************************************************/ + /***********************************************************************/ + // Compute z_{i+1} + /***********************************************************************/ - let z_next = self - .step_circuit - .unwrap() - .synthesize(&mut cs.namespace(|| "F"), z_i)?; + let z_next = self + .step_circuit + .synthesize(&mut cs.namespace(|| "F"), z_i)?; - /***********************************************************************/ - // Compute the new hash H(params, u2_new, i+1, z0, z_{i+1}) - /***********************************************************************/ + /***********************************************************************/ + // Compute the new hash H(params, u2_new, i+1, z0, z_{i+1}) + /***********************************************************************/ - h1_hash.flush_state(); - h1_hash.absorb(params); - h1_hash.absorb(W_new.x.clone()); - h1_hash.absorb(W_new.y.clone()); - h1_hash.absorb(W_new.is_infinity); - h1_hash.absorb(E_new.x.clone()); - h1_hash.absorb(E_new.y.clone()); - h1_hash.absorb(E_new.is_infinity); - h1_hash.absorb(u_new); + h1_hash.flush_state(); + h1_hash.absorb(params); + h1_hash.absorb(W_new.x.clone()); + h1_hash.absorb(W_new.y.clone()); + h1_hash.absorb(W_new.is_infinity); + h1_hash.absorb(E_new.x.clone()); + h1_hash.absorb(E_new.y.clone()); + h1_hash.absorb(E_new.is_infinity); + h1_hash.absorb(u_new); - // absorb each of the limbs of X_r_new[0] - for limb in Xr0_new_bn.into_iter() { - h1_hash.absorb(limb); - } - - // absorb each of the limbs of X_r_new[1] - for limb in Xr1_new_bn.into_iter() { - h1_hash.absorb(limb); - } - - h1_hash.absorb(next_i.clone()); - h1_hash.absorb(z_0); - h1_hash.absorb(z_next); - let h1_new_bits = h1_hash.get_challenge(cs.namespace(|| "h1_new bits"))?; - let h1_new = le_bits_to_num(cs.namespace(|| "h1_new"), h1_new_bits)?; - let _ = h1_new.inputize(cs.namespace(|| "output h1_new"))?; - } else { - /***********************************************************************/ - // Check that h1 = Hash(params, u2, i) - /***********************************************************************/ + // absorb each of the limbs of X_r_new[0] + for limb in Xr0_new_bn.into_iter() { + h1_hash.absorb(limb); + } - let mut h1_hash: PoseidonROGadget<G::Base> = PoseidonROGadget::new(self.poseidon_constants); + // absorb each of the limbs of X_r_new[1] + for limb in Xr1_new_bn.into_iter() { + h1_hash.absorb(limb); + } - h1_hash.absorb(params.clone()); - h1_hash.absorb(W_r.x); - h1_hash.absorb(W_r.y); - h1_hash.absorb(W_r.is_infinity); - h1_hash.absorb(E_r.x); - h1_hash.absorb(E_r.y); - h1_hash.absorb(E_r.is_infinity); - h1_hash.absorb(u_r); - h1_hash.absorb(i.clone()); - - // absorb each of the limbs of X_r[0] - for limb in Xr0_bn.into_iter() { - h1_hash.absorb(limb); - } - - // absorb each of the limbs of X_r[1] - for limb in Xr1_bn.into_iter() { - h1_hash.absorb(limb); - } - - let hash_bits = h1_hash.get_challenge(cs.namespace(|| "Input hash"))?; - let hash = le_bits_to_num(cs.namespace(|| "bits to hash"), hash_bits)?; - - cs.enforce( - || "check h1", - |lc| lc, - |lc| lc, - |lc| lc + h1.get_variable() - hash.get_variable(), - ); - - /***********************************************************************/ - // Compute the new hash H(params, u2') - /***********************************************************************/ - - h1_hash.flush_state(); - h1_hash.absorb(params); - h1_hash.absorb(W_new.x.clone()); - h1_hash.absorb(W_new.y.clone()); - h1_hash.absorb(W_new.is_infinity); - h1_hash.absorb(E_new.x.clone()); - h1_hash.absorb(E_new.y.clone()); - h1_hash.absorb(E_new.is_infinity); - h1_hash.absorb(u_new); - h1_hash.absorb(next_i.clone()); - - // absorb each of the limbs of X_r_new[0] - for limb in Xr0_new_bn.into_iter() { - h1_hash.absorb(limb); - } - - // absorb each of the limbs of X_r_new[1] - for limb in Xr1_new_bn.into_iter() { - h1_hash.absorb(limb); - } - - let h1_new_bits = h1_hash.get_challenge(cs.namespace(|| "h1_new bits"))?; - let h1_new = le_bits_to_num(cs.namespace(|| "h1_new"), h1_new_bits)?; - let _ = h1_new.inputize(cs.namespace(|| "output h1_new"))?; - } + h1_hash.absorb(next_i.clone()); + h1_hash.absorb(z_0); + h1_hash.absorb(z_next); + let h1_new_bits = h1_hash.get_challenge(cs.namespace(|| "h1_new bits"))?; // TODO: use get_hash method + let h1_new = le_bits_to_num(cs.namespace(|| "h1_new"), h1_new_bits)?; + let _ = h1_new.inputize(cs.namespace(|| "output h1_new"))?; Ok(()) }@@ -700,9 +632,9 @@ let circuit1: NIFSVerifierCircuit<G2, TestCircuit<<G2 as Group>::Base>> =
NIFSVerifierCircuit::new( params.clone(), None, - Some(TestCircuit { + TestCircuit { _p: Default::default(), - }), + }, poseidon_constants1.clone(), ); // First create the shape@@ -718,7 +650,14 @@ // The second circuit that verifies G1
let poseidon_constants2: NovaPoseidonConstants<<G1 as Group>::Base> = NovaPoseidonConstants::new(); let circuit2: NIFSVerifierCircuit<G1, TestCircuit<<G1 as Group>::Base>> = - NIFSVerifierCircuit::new(params.clone(), None, None, poseidon_constants2); + NIFSVerifierCircuit::new( + params.clone(), + None, + TestCircuit { + _p: Default::default(), + }, + poseidon_constants2, + ); // First create the shape let mut cs: ShapeCS<G2> = ShapeCS::new(); let _ = circuit2.synthesize(&mut cs);@@ -755,9 +694,9 @@ let circuit: NIFSVerifierCircuit<G2, TestCircuit<<G2 as Group>::Base>> =
NIFSVerifierCircuit::new( params, Some(inputs), - Some(TestCircuit { + TestCircuit { _p: Default::default(), - }), + }, poseidon_constants1, ); let _ = circuit.synthesize(&mut cs);
M
src/poseidon.rs
→
src/poseidon.rs
@@ -80,10 +80,8 @@ pub fn absorb(&mut self, e: Scalar) {
self.state.push(e); } - /// Compute a challenge by hashing the current state - #[allow(dead_code)] - pub fn get_challenge(&mut self) -> Scalar { - let hash = match self.state.len() { + fn hash_inner(&mut self) -> Scalar { + match self.state.len() { 25 => { Poseidon::<Scalar, U25>::new_with_preimage(&self.state, &self.constants.constants25).hash() }@@ -96,7 +94,13 @@ }
_ => { panic!("Number of elements in the RO state does not match any of the arities used in Nova") } - }; + } + } + + /// Compute a challenge by hashing the current state + #[allow(dead_code)] + pub fn get_challenge(&mut self) -> Scalar { + let hash = self.hash_inner(); // Only keep 128 bits let bits = hash.to_le_bits(); let mut res = Scalar::zero();@@ -109,6 +113,22 @@ coeff += coeff;
} res } + + #[allow(dead_code)] + pub fn get_hash(&mut self) -> Scalar { + let hash = self.hash_inner(); + // Only keep 250 bits + let bits = hash.to_le_bits(); + let mut res = Scalar::zero(); + let mut coeff = Scalar::one(); + for bit in bits[0..250].into_iter() { + if *bit { + res += coeff; + } + coeff += coeff; + } + res + } } /// A Poseidon-based RO gadget to use inside the verifier circuit.@@ -145,9 +165,7 @@ pub fn absorb(&mut self, e: AllocatedNum<Scalar>) {
self.state.push(e); } - /// Compute a challenge by hashing the current state - #[allow(dead_code)] - pub fn get_challenge<CS>(&mut self, mut cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError> + fn hash_inner<CS>(&mut self, mut cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError> where CS: ConstraintSystem<Scalar>, {@@ -171,16 +189,39 @@ _ => {
panic!("Number of elements in the RO state does not match any of the arities used in Nova") } }; + + // return the hash as a vector of bits + Ok( + out + .to_bits_le_strict(cs.namespace(|| "poseidon hash to boolean"))? + .iter() + .map(|boolean| match boolean { + Boolean::Is(ref x) => x.clone(), + _ => panic!("Wrong type of input. We should have never reached there"), + }) + .collect(), + ) + } + + /// Compute a challenge by hashing the current state + #[allow(dead_code)] + pub fn get_challenge<CS>(&mut self, mut cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError> + where + CS: ConstraintSystem<Scalar>, + { + let bits = self.hash_inner(cs.namespace(|| "hash"))?; // Only keep 128 bits - let bits: Vec<AllocatedBit> = out - .to_bits_le_strict(cs.namespace(|| "poseidon hash to boolean"))? - .iter() - .map(|boolean| match boolean { - Boolean::Is(ref x) => x.clone(), - _ => panic!("Wrong type of input. We should have never reached there"), - }) - .collect(); Ok(bits[..128].into()) + } + + #[allow(dead_code)] + pub fn get_hash<CS>(&mut self, mut cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError> + where + CS: ConstraintSystem<Scalar>, + { + let bits = self.hash_inner(cs.namespace(|| "hash"))?; + // Only keep 250 bits + Ok(bits[..250].into()) } }