arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/src/transcript/poseidon.rs
src/transcript/poseidon.rsRaw
  1use ark_crypto_primitives::sponge::{
  2    constraints::CryptographicSpongeVar,
  3    poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
  4    Absorb, CryptographicSponge,
  5};
  6use ark_ec::{AffineRepr, CurveGroup, Group};
  7use ark_ff::{BigInteger, Field, PrimeField};
  8use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar};
  9use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
 10
 11use crate::transcript::Transcript;
 12
 13/// PoseidonTranscript implements the Transcript trait using the Poseidon hash
 14pub struct PoseidonTranscript<C: CurveGroup>
 15where
 16    <C as Group>::ScalarField: Absorb,
 17{
 18    sponge: PoseidonSponge<C::ScalarField>,
 19}
 20
 21impl<C: CurveGroup> Transcript<C> for PoseidonTranscript<C>
 22where
 23    <C as Group>::ScalarField: Absorb,
 24{
 25    type TranscriptConfig = PoseidonConfig<C::ScalarField>;
 26
 27    fn new(poseidon_config: &Self::TranscriptConfig) -> Self {
 28        let sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config);
 29        Self { sponge }
 30    }
 31    fn absorb(&mut self, v: &C::ScalarField) {
 32        self.sponge.absorb(&v);
 33    }
 34    fn absorb_vec(&mut self, v: &[C::ScalarField]) {
 35        self.sponge.absorb(&v);
 36    }
 37    fn absorb_point(&mut self, p: &C) {
 38        self.sponge.absorb(&prepare_point(p));
 39    }
 40    fn get_challenge(&mut self) -> C::ScalarField {
 41        let c = self.sponge.squeeze_field_elements(1);
 42        self.sponge.absorb(&c[0]);
 43        c[0]
 44    }
 45    fn get_challenge_nbits(&mut self, nbits: usize) -> Vec<bool> {
 46        self.sponge.squeeze_bits(nbits)
 47    }
 48    fn get_challenges(&mut self, n: usize) -> Vec<C::ScalarField> {
 49        let c = self.sponge.squeeze_field_elements(n);
 50        self.sponge.absorb(&c);
 51        c
 52    }
 53}
 54
 55// Returns the point coordinates in Fr, so it can be absrobed by the transcript. It does not work
 56// over bytes in order to have a logic that can be reproduced in-circuit.
 57fn prepare_point<C: CurveGroup>(p: &C) -> Vec<C::ScalarField> {
 58    let binding = p.into_affine();
 59    let p_coords = &binding.xy().unwrap();
 60    let x_bi = p_coords
 61        .0
 62        .to_base_prime_field_elements()
 63        .next()
 64        .expect("a")
 65        .into_bigint();
 66    let y_bi = p_coords
 67        .1
 68        .to_base_prime_field_elements()
 69        .next()
 70        .expect("a")
 71        .into_bigint();
 72    vec![
 73        C::ScalarField::from_le_bytes_mod_order(x_bi.to_bytes_le().as_ref()),
 74        C::ScalarField::from_le_bytes_mod_order(y_bi.to_bytes_le().as_ref()),
 75    ]
 76}
 77
 78/// PoseidonTranscriptVar implements the gadget compatible with PoseidonTranscript
 79pub struct PoseidonTranscriptVar<F: PrimeField> {
 80    sponge: PoseidonSpongeVar<F>,
 81}
 82impl<F: PrimeField> PoseidonTranscriptVar<F> {
 83    pub fn new(cs: ConstraintSystemRef<F>, poseidon_config: &PoseidonConfig<F>) -> Self {
 84        let sponge = PoseidonSpongeVar::<F>::new(cs, poseidon_config);
 85        Self { sponge }
 86    }
 87    pub fn absorb(&mut self, v: FpVar<F>) -> Result<(), SynthesisError> {
 88        self.sponge.absorb(&v)
 89    }
 90    pub fn absorb_vec(&mut self, v: &[FpVar<F>]) -> Result<(), SynthesisError> {
 91        self.sponge.absorb(&v)
 92    }
 93    pub fn get_challenge(&mut self) -> Result<FpVar<F>, SynthesisError> {
 94        let c = self.sponge.squeeze_field_elements(1)?;
 95        self.sponge.absorb(&c[0])?;
 96        Ok(c[0].clone())
 97    }
 98
 99    /// returns the bit representation of the challenge, we use its output in-circuit for the
100    /// `GC.scalar_mul_le` method.
101    pub fn get_challenge_nbits(&mut self, nbits: usize) -> Result<Vec<Boolean<F>>, SynthesisError> {
102        self.sponge.squeeze_bits(nbits)
103    }
104    pub fn get_challenges(&mut self, n: usize) -> Result<Vec<FpVar<F>>, SynthesisError> {
105        let c = self.sponge.squeeze_field_elements(n)?;
106        self.sponge.absorb(&c)?;
107        Ok(c)
108    }
109}
110
111#[cfg(test)]
112pub mod tests {
113    use super::*;
114    use ark_crypto_primitives::sponge::poseidon::find_poseidon_ark_and_mds;
115    use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
116    use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, R1CSVar};
117    use ark_relations::r1cs::ConstraintSystem;
118    use ark_vesta::Projective as E2Projective;
119    use std::ops::Mul;
120
121    /// WARNING the method poseidon_test_config is for tests only
122    #[cfg(test)]
123    pub fn poseidon_test_config<F: PrimeField>() -> PoseidonConfig<F> {
124        let full_rounds = 8;
125        let partial_rounds = 31;
126        let alpha = 5;
127        let rate = 2;
128
129        let (ark, mds) = find_poseidon_ark_and_mds::<F>(
130            F::MODULUS_BIT_SIZE as u64,
131            rate,
132            full_rounds,
133            partial_rounds,
134            0,
135        );
136
137        PoseidonConfig::new(
138            full_rounds as usize,
139            partial_rounds as usize,
140            alpha,
141            mds,
142            ark,
143            rate,
144            1,
145        )
146    }
147
148    #[test]
149    fn test_transcript_and_transcriptvar_get_challenge() {
150        // use 'native' transcript
151        let config = poseidon_test_config::<Fr>();
152        let mut tr = PoseidonTranscript::<Projective>::new(&config);
153        tr.absorb(&Fr::from(42_u32));
154        let c = tr.get_challenge();
155
156        // use 'gadget' transcript
157        let cs = ConstraintSystem::<Fr>::new_ref();
158        let mut tr_var = PoseidonTranscriptVar::<Fr>::new(cs.clone(), &config);
159        let v = FpVar::<Fr>::new_witness(cs.clone(), || Ok(Fr::from(42_u32))).unwrap();
160        tr_var.absorb(v).unwrap();
161        let c_var = tr_var.get_challenge().unwrap();
162
163        // assert that native & gadget transcripts return the same challenge
164        assert_eq!(c, c_var.value().unwrap());
165    }
166
167    #[test]
168    fn test_transcript_and_transcriptvar_nbits() {
169        let nbits = crate::constants::N_BITS_CHALLENGE;
170
171        // use 'native' transcript
172        let config = poseidon_test_config::<Fq>();
173        let mut tr = PoseidonTranscript::<E2Projective>::new(&config);
174        tr.absorb(&Fq::from(42_u32));
175
176        // get challenge from native transcript
177        let c_bits = tr.get_challenge_nbits(nbits);
178
179        // use 'gadget' transcript
180        let cs = ConstraintSystem::<Fq>::new_ref();
181        let mut tr_var = PoseidonTranscriptVar::<Fq>::new(cs.clone(), &config);
182        let v = FpVar::<Fq>::new_witness(cs.clone(), || Ok(Fq::from(42_u32))).unwrap();
183        tr_var.absorb(v).unwrap();
184
185        // get challenge from circuit transcript
186        let c_var = tr_var.get_challenge_nbits(nbits).unwrap();
187
188        let P = Projective::generator();
189        let PVar = GVar::new_witness(cs.clone(), || Ok(P)).unwrap();
190
191        // multiply point P by the challenge in different formats, to ensure that we get the same
192        // result natively and in-circuit
193
194        // native c*P
195        let c_Fr = Fr::from_bigint(BigInteger::from_bits_le(&c_bits)).unwrap();
196        let cP_native = P.mul(c_Fr);
197
198        // native c*P using mul_bits_be (notice the .rev to convert the LE to BE)
199        let cP_native_bits = P.mul_bits_be(c_bits.into_iter().rev());
200
201        // in-circuit c*P using scalar_mul_le
202        let cPVar = PVar.scalar_mul_le(c_var.iter()).unwrap();
203
204        // check that they are equal
205        assert_eq!(
206            cP_native.into_affine(),
207            cPVar.value().unwrap().into_affine()
208        );
209        assert_eq!(
210            cP_native_bits.into_affine(),
211            cPVar.value().unwrap().into_affine()
212        );
213    }
214}