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/// WARNING the method poseidon_test_config is for tests only
119pub fn poseidon_test_config<F: PrimeField>() -> PoseidonConfig<F> {
120    let full_rounds = 8;
121    let partial_rounds = 31;
122    let alpha = 5;
123    let rate = 2;
124
125    let (ark, mds) = ark_crypto_primitives::sponge::poseidon::find_poseidon_ark_and_mds::<F>(
126        F::MODULUS_BIT_SIZE as u64,
127        rate,
128        full_rounds,
129        partial_rounds,
130        0,
131    );
132
133    PoseidonConfig::new(
134        full_rounds as usize,
135        partial_rounds as usize,
136        alpha,
137        mds,
138        ark,
139        rate,
140        1,
141    )
142}
143
144#[cfg(test)]
145pub mod tests {
146    use super::*;
147    use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
148    use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, R1CSVar};
149    use ark_relations::r1cs::ConstraintSystem;
150    use ark_vesta::Projective as E2Projective;
151    use std::ops::Mul;
152
153    #[test]
154    fn test_transcript_and_transcriptvar_get_challenge() {
155        // use 'native' transcript
156        let config = poseidon_test_config::<Fr>();
157        let mut tr = PoseidonTranscript::<Projective>::new(&config);
158        tr.absorb(&Fr::from(42_u32));
159        let c = tr.get_challenge();
160
161        // use 'gadget' transcript
162        let cs = ConstraintSystem::<Fr>::new_ref();
163        let mut tr_var = PoseidonTranscriptVar::<Fr>::new(cs.clone(), &config);
164        let v = FpVar::<Fr>::new_witness(cs.clone(), || Ok(Fr::from(42_u32))).unwrap();
165        tr_var.absorb(v).unwrap();
166        let c_var = tr_var.get_challenge().unwrap();
167
168        // assert that native & gadget transcripts return the same challenge
169        assert_eq!(c, c_var.value().unwrap());
170    }
171
172    #[test]
173    fn test_transcript_and_transcriptvar_nbits() {
174        let nbits = crate::constants::N_BITS_RO;
175
176        // use 'native' transcript
177        let config = poseidon_test_config::<Fq>();
178        let mut tr = PoseidonTranscript::<E2Projective>::new(&config);
179        tr.absorb(&Fq::from(42_u32));
180
181        // get challenge from native transcript
182        let c_bits = tr.get_challenge_nbits(nbits);
183
184        // use 'gadget' transcript
185        let cs = ConstraintSystem::<Fq>::new_ref();
186        let mut tr_var = PoseidonTranscriptVar::<Fq>::new(cs.clone(), &config);
187        let v = FpVar::<Fq>::new_witness(cs.clone(), || Ok(Fq::from(42_u32))).unwrap();
188        tr_var.absorb(v).unwrap();
189
190        // get challenge from circuit transcript
191        let c_var = tr_var.get_challenge_nbits(nbits).unwrap();
192
193        let P = Projective::generator();
194        let PVar = GVar::new_witness(cs.clone(), || Ok(P)).unwrap();
195
196        // multiply point P by the challenge in different formats, to ensure that we get the same
197        // result natively and in-circuit
198
199        // native c*P
200        let c_Fr = Fr::from_bigint(BigInteger::from_bits_le(&c_bits)).unwrap();
201        let cP_native = P.mul(c_Fr);
202
203        // native c*P using mul_bits_be (notice the .rev to convert the LE to BE)
204        let cP_native_bits = P.mul_bits_be(c_bits.into_iter().rev());
205
206        // in-circuit c*P using scalar_mul_le
207        let cPVar = PVar.scalar_mul_le(c_var.iter()).unwrap();
208
209        // check that they are equal
210        assert_eq!(
211            cP_native.into_affine(),
212            cPVar.value().unwrap().into_affine()
213        );
214        assert_eq!(
215            cP_native_bits.into_affine(),
216            cPVar.value().unwrap().into_affine()
217        );
218    }
219}