use ark_crypto_primitives::sponge::{ constraints::CryptographicSpongeVar, poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, Absorb, CryptographicSponge, }; use ark_ec::{AffineRepr, CurveGroup, Group}; use ark_ff::{BigInteger, Field, PrimeField}; use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; use crate::transcript::Transcript; /// PoseidonTranscript implements the Transcript trait using the Poseidon hash pub struct PoseidonTranscript where ::ScalarField: Absorb, { sponge: PoseidonSponge, } impl Transcript for PoseidonTranscript where ::ScalarField: Absorb, { type TranscriptConfig = PoseidonConfig; fn new(poseidon_config: &Self::TranscriptConfig) -> Self { let sponge = PoseidonSponge::::new(poseidon_config); Self { sponge } } fn absorb(&mut self, v: &C::ScalarField) { self.sponge.absorb(&v); } fn absorb_vec(&mut self, v: &[C::ScalarField]) { self.sponge.absorb(&v); } fn absorb_point(&mut self, p: &C) { self.sponge.absorb(&prepare_point(p)); } fn get_challenge(&mut self) -> C::ScalarField { let c = self.sponge.squeeze_field_elements(1); self.sponge.absorb(&c[0]); c[0] } fn get_challenge_nbits(&mut self, nbits: usize) -> Vec { self.sponge.squeeze_bits(nbits) } fn get_challenges(&mut self, n: usize) -> Vec { let c = self.sponge.squeeze_field_elements(n); self.sponge.absorb(&c); c } } // Returns the point coordinates in Fr, so it can be absrobed by the transcript. It does not work // over bytes in order to have a logic that can be reproduced in-circuit. fn prepare_point(p: &C) -> Vec { let binding = p.into_affine(); let p_coords = &binding.xy().unwrap(); let x_bi = p_coords .0 .to_base_prime_field_elements() .next() .expect("a") .into_bigint(); let y_bi = p_coords .1 .to_base_prime_field_elements() .next() .expect("a") .into_bigint(); vec![ C::ScalarField::from_le_bytes_mod_order(x_bi.to_bytes_le().as_ref()), C::ScalarField::from_le_bytes_mod_order(y_bi.to_bytes_le().as_ref()), ] } /// PoseidonTranscriptVar implements the gadget compatible with PoseidonTranscript pub struct PoseidonTranscriptVar { sponge: PoseidonSpongeVar, } impl PoseidonTranscriptVar { pub fn new(cs: ConstraintSystemRef, poseidon_config: &PoseidonConfig) -> Self { let sponge = PoseidonSpongeVar::::new(cs, poseidon_config); Self { sponge } } pub fn absorb(&mut self, v: FpVar) -> Result<(), SynthesisError> { self.sponge.absorb(&v) } pub fn absorb_vec(&mut self, v: &[FpVar]) -> Result<(), SynthesisError> { self.sponge.absorb(&v) } pub fn get_challenge(&mut self) -> Result, SynthesisError> { let c = self.sponge.squeeze_field_elements(1)?; self.sponge.absorb(&c[0])?; Ok(c[0].clone()) } /// returns the bit representation of the challenge, we use its output in-circuit for the /// `GC.scalar_mul_le` method. pub fn get_challenge_nbits(&mut self, nbits: usize) -> Result>, SynthesisError> { self.sponge.squeeze_bits(nbits) } pub fn get_challenges(&mut self, n: usize) -> Result>, SynthesisError> { let c = self.sponge.squeeze_field_elements(n)?; self.sponge.absorb(&c)?; Ok(c) } } #[cfg(test)] pub mod tests { use super::*; use ark_crypto_primitives::sponge::poseidon::find_poseidon_ark_and_mds; use ark_pallas::{constraints::GVar, Fq, Fr, Projective}; use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, R1CSVar}; use ark_relations::r1cs::ConstraintSystem; use ark_vesta::Projective as E2Projective; use std::ops::Mul; /// WARNING the method poseidon_test_config is for tests only #[cfg(test)] pub fn poseidon_test_config() -> PoseidonConfig { let full_rounds = 8; let partial_rounds = 31; let alpha = 5; let rate = 2; let (ark, mds) = find_poseidon_ark_and_mds::( F::MODULUS_BIT_SIZE as u64, rate, full_rounds, partial_rounds, 0, ); PoseidonConfig::new( full_rounds as usize, partial_rounds as usize, alpha, mds, ark, rate, 1, ) } #[test] fn test_transcript_and_transcriptvar_get_challenge() { // use 'native' transcript let config = poseidon_test_config::(); let mut tr = PoseidonTranscript::::new(&config); tr.absorb(&Fr::from(42_u32)); let c = tr.get_challenge(); // use 'gadget' transcript let cs = ConstraintSystem::::new_ref(); let mut tr_var = PoseidonTranscriptVar::::new(cs.clone(), &config); let v = FpVar::::new_witness(cs.clone(), || Ok(Fr::from(42_u32))).unwrap(); tr_var.absorb(v).unwrap(); let c_var = tr_var.get_challenge().unwrap(); // assert that native & gadget transcripts return the same challenge assert_eq!(c, c_var.value().unwrap()); } #[test] fn test_transcript_and_transcriptvar_nbits() { let nbits = crate::constants::N_BITS_CHALLENGE; // use 'native' transcript let config = poseidon_test_config::(); let mut tr = PoseidonTranscript::::new(&config); tr.absorb(&Fq::from(42_u32)); // get challenge from native transcript let c_bits = tr.get_challenge_nbits(nbits); // use 'gadget' transcript let cs = ConstraintSystem::::new_ref(); let mut tr_var = PoseidonTranscriptVar::::new(cs.clone(), &config); let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32))).unwrap(); tr_var.absorb(v).unwrap(); // get challenge from circuit transcript let c_var = tr_var.get_challenge_nbits(nbits).unwrap(); let P = Projective::generator(); let PVar = GVar::new_witness(cs.clone(), || Ok(P)).unwrap(); // multiply point P by the challenge in different formats, to ensure that we get the same // result natively and in-circuit // native c*P let c_Fr = Fr::from_bigint(BigInteger::from_bits_le(&c_bits)).unwrap(); let cP_native = P.mul(c_Fr); // native c*P using mul_bits_be (notice the .rev to convert the LE to BE) let cP_native_bits = P.mul_bits_be(c_bits.into_iter().rev()); // in-circuit c*P using scalar_mul_le let cPVar = PVar.scalar_mul_le(c_var.iter()).unwrap(); // check that they are equal assert_eq!( cP_native.into_affine(), cPVar.value().unwrap().into_affine() ); assert_eq!( cP_native_bits.into_affine(), cPVar.value().unwrap().into_affine() ); } }