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