//! Poseidon Constants and Poseidon-based RO used in Nova use super::traits::{ROCircuitTrait, ROConstantsTrait, ROTrait}; use bellperson::{ gadgets::{ boolean::{AllocatedBit, Boolean}, num::AllocatedNum, }, ConstraintSystem, SynthesisError, }; use core::marker::PhantomData; use ff::{PrimeField, PrimeFieldBits}; use generic_array::typenum::U24; use neptune::{ circuit2::Elt, poseidon::PoseidonConstants, sponge::{ api::{IOPattern, SpongeAPI, SpongeOp}, circuit::SpongeCircuit, vanilla::{Mode::Simplex, Sponge, SpongeTrait}, }, Strength, }; /// All Poseidon Constants that are used in Nova #[derive(Clone)] pub struct PoseidonConstantsCircuit(PoseidonConstants); impl ROConstantsTrait for PoseidonConstantsCircuit where Scalar: PrimeField + PrimeFieldBits, { /// Generate Poseidon constants #[allow(clippy::new_without_default)] fn new() -> Self { Self(Sponge::::api_constants(Strength::Standard)) } } /// A Poseidon-based RO to use outside circuits pub struct PoseidonRO where Base: PrimeField + PrimeFieldBits, Scalar: PrimeField + PrimeFieldBits, { // Internal State state: Vec, constants: PoseidonConstantsCircuit, num_absorbs: usize, squeezed: bool, _p: PhantomData, } impl ROTrait for PoseidonRO where Base: PrimeField + PrimeFieldBits, Scalar: PrimeField + PrimeFieldBits, { type Constants = PoseidonConstantsCircuit; fn new(constants: PoseidonConstantsCircuit, num_absorbs: usize) -> Self { Self { state: Vec::new(), constants, num_absorbs, squeezed: false, _p: PhantomData::default(), } } /// Absorb a new number into the state of the oracle fn absorb(&mut self, e: Base) { assert!(!self.squeezed, "Cannot absorb after squeezing"); self.state.push(e); } /// Compute a challenge by hashing the current state fn squeeze(&mut self, num_bits: usize) -> Scalar { // check if we have squeezed already assert!(!self.squeezed, "Cannot squeeze again after squeezing"); self.squeezed = true; let mut sponge = Sponge::new_with_constants(&self.constants.0, Simplex); let acc = &mut (); let parameter = IOPattern(vec![ SpongeOp::Absorb(self.num_absorbs as u32), SpongeOp::Squeeze(1u32), ]); sponge.start(parameter, Some(1u32), acc); assert_eq!(self.num_absorbs, self.state.len()); SpongeAPI::absorb(&mut sponge, self.num_absorbs as u32, &self.state, acc); let hash = SpongeAPI::squeeze(&mut sponge, 1, acc); sponge.finish(acc).unwrap(); // Only return `num_bits` let bits = hash[0].to_le_bits(); let mut res = Scalar::zero(); let mut coeff = Scalar::one(); for bit in bits[0..num_bits].into_iter() { if *bit { res += coeff; } coeff += coeff; } res } } /// A Poseidon-based RO gadget to use inside the verifier circuit. pub struct PoseidonROCircuit where Scalar: PrimeField + PrimeFieldBits, { // Internal state state: Vec>, constants: PoseidonConstantsCircuit, num_absorbs: usize, squeezed: bool, } impl ROCircuitTrait for PoseidonROCircuit where Scalar: PrimeField + PrimeFieldBits, { type Constants = PoseidonConstantsCircuit; /// Initialize the internal state and set the poseidon constants fn new(constants: PoseidonConstantsCircuit, num_absorbs: usize) -> Self { Self { state: Vec::new(), constants, num_absorbs, squeezed: false, } } /// Absorb a new number into the state of the oracle fn absorb(&mut self, e: AllocatedNum) { assert!(!self.squeezed, "Cannot absorb after squeezing"); self.state.push(e); } /// Compute a challenge by hashing the current state fn squeeze( &mut self, mut cs: CS, num_bits: usize, ) -> Result, SynthesisError> where CS: ConstraintSystem, { // check if we have squeezed already assert!(!self.squeezed, "Cannot squeeze again after squeezing"); self.squeezed = true; let parameter = IOPattern(vec![ SpongeOp::Absorb(self.num_absorbs as u32), SpongeOp::Squeeze(1u32), ]); let mut ns = cs.namespace(|| "ns"); let hash = { let mut sponge = SpongeCircuit::new_with_constants(&self.constants.0, Simplex); let acc = &mut ns; assert_eq!(self.num_absorbs, self.state.len()); sponge.start(parameter, Some(1u32), acc); neptune::sponge::api::SpongeAPI::absorb( &mut sponge, self.num_absorbs as u32, &(0..self.state.len()) .map(|i| Elt::Allocated(self.state[i].clone())) .collect::>>(), acc, ); let output = neptune::sponge::api::SpongeAPI::squeeze(&mut sponge, 1, acc); sponge.finish(acc).unwrap(); output }; let hash = Elt::ensure_allocated(&hash[0], &mut ns.namespace(|| "ensure allocated"), true)?; // return the hash as a vector of bits, truncated Ok( hash .to_bits_le_strict(ns.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::>()[..num_bits] .into(), ) } } #[cfg(test)] mod tests { use super::*; type S = pasta_curves::pallas::Scalar; type B = pasta_curves::vesta::Scalar; type G = pasta_curves::pallas::Point; use crate::{ bellperson::solver::SatisfyingAssignment, constants::NUM_CHALLENGE_BITS, gadgets::utils::le_bits_to_num, }; use ff::Field; use rand::rngs::OsRng; #[test] fn test_poseidon_ro() { // Check that the number computed inside the circuit is equal to the number computed outside the circuit let mut csprng: OsRng = OsRng; let constants = PoseidonConstantsCircuit::new(); let num_absorbs = 32; let mut ro: PoseidonRO = PoseidonRO::new(constants.clone(), num_absorbs); let mut ro_gadget: PoseidonROCircuit = PoseidonROCircuit::new(constants, num_absorbs); let mut cs: SatisfyingAssignment = SatisfyingAssignment::new(); for i in 0..num_absorbs { let num = S::random(&mut csprng); ro.absorb(num); let num_gadget = AllocatedNum::alloc(cs.namespace(|| format!("data {}", i)), || Ok(num)).unwrap(); num_gadget .inputize(&mut cs.namespace(|| format!("input {}", i))) .unwrap(); ro_gadget.absorb(num_gadget); } let num = ro.squeeze(NUM_CHALLENGE_BITS); let num2_bits = ro_gadget.squeeze(&mut cs, NUM_CHALLENGE_BITS).unwrap(); let num2 = le_bits_to_num(&mut cs, num2_bits).unwrap(); assert_eq!(num.to_repr(), num2.get_value().unwrap().to_repr()); } }