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
 15/// PoseidonTranscript implements the Transcript trait using the Poseidon hash
 16pub struct PoseidonTranscript<C: CurveGroup>
 17where
 18    <C as Group>::ScalarField: Absorb,
 19{
 20    sponge: PoseidonSponge<C::ScalarField>,
 21}
 22
 23impl<C: CurveGroup> Transcript<C> for PoseidonTranscript<C>
 24where
 25    <C as Group>::ScalarField: Absorb,
 26{
 27    type TranscriptConfig = PoseidonConfig<C::ScalarField>;
 28
 29    fn new(poseidon_config: &Self::TranscriptConfig) -> Self {
 30        let sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config);
 31        Self { sponge }
 32    }
 33    fn absorb(&mut self, v: &C::ScalarField) {
 34        self.sponge.absorb(&v);
 35    }
 36    fn absorb_vec(&mut self, v: &[C::ScalarField]) {
 37        self.sponge.absorb(&v);
 38    }
 39    fn absorb_point(&mut self, p: &C) -> Result<(), Error> {
 40        self.sponge.absorb(&prepare_point(p)?);
 41        Ok(())
 42    }
 43    fn get_challenge(&mut self) -> C::ScalarField {
 44        let c = self.sponge.squeeze_field_elements(1);
 45        self.sponge.absorb(&c[0]);
 46        c[0]
 47    }
 48    fn get_challenge_nbits(&mut self, nbits: usize) -> Vec<bool> {
 49        self.sponge.squeeze_bits(nbits)
 50    }
 51    fn get_challenges(&mut self, n: usize) -> Vec<C::ScalarField> {
 52        let c = self.sponge.squeeze_field_elements(n);
 53        self.sponge.absorb(&c);
 54        c
 55    }
 56}
 57
 58// Returns the point coordinates in Fr, so it can be absrobed by the transcript. It does not work
 59// over bytes in order to have a logic that can be reproduced in-circuit.
 60fn prepare_point<C: CurveGroup>(p: &C) -> Result<Vec<C::ScalarField>, Error> {
 61    let affine = p.into_affine();
 62    let xy_obj = &affine.xy();
 63    let mut xy = (&C::BaseField::zero(), &C::BaseField::one());
 64    if xy_obj.is_some() {
 65        xy = xy_obj.unwrap();
 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> PoseidonTranscriptVar<F> {
 88    pub fn new(cs: ConstraintSystemRef<F>, poseidon_config: &PoseidonConfig<F>) -> Self {
 89        let sponge = PoseidonSpongeVar::<F>::new(cs, poseidon_config);
 90        Self { sponge }
 91    }
 92    pub fn absorb(&mut self, v: FpVar<F>) -> Result<(), SynthesisError> {
 93        self.sponge.absorb(&v)
 94    }
 95    pub fn absorb_vec(&mut self, v: &[FpVar<F>]) -> Result<(), SynthesisError> {
 96        self.sponge.absorb(&v)
 97    }
 98    pub fn get_challenge(&mut self) -> Result<FpVar<F>, SynthesisError> {
 99        let c = self.sponge.squeeze_field_elements(1)?;
100        self.sponge.absorb(&c[0])?;
101        Ok(c[0].clone())
102    }
103
104    /// returns the bit representation of the challenge, we use its output in-circuit for the
105    /// `GC.scalar_mul_le` method.
106    pub fn get_challenge_nbits(&mut self, nbits: usize) -> Result<Vec<Boolean<F>>, SynthesisError> {
107        self.sponge.squeeze_bits(nbits)
108    }
109    pub fn get_challenges(&mut self, n: usize) -> Result<Vec<FpVar<F>>, SynthesisError> {
110        let c = self.sponge.squeeze_field_elements(n)?;
111        self.sponge.absorb(&c)?;
112        Ok(c)
113    }
114}
115
116#[cfg(test)]
117pub mod tests {
118    use super::*;
119    use ark_crypto_primitives::sponge::poseidon::find_poseidon_ark_and_mds;
120    use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
121    use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, groups::CurveVar, R1CSVar};
122    use ark_relations::r1cs::ConstraintSystem;
123    use ark_vesta::Projective as E2Projective;
124    use std::ops::Mul;
125
126    /// WARNING the method poseidon_test_config is for tests only
127    #[cfg(test)]
128    pub fn poseidon_test_config<F: PrimeField>() -> PoseidonConfig<F> {
129        let full_rounds = 8;
130        let partial_rounds = 31;
131        let alpha = 5;
132        let rate = 2;
133
134        let (ark, mds) = find_poseidon_ark_and_mds::<F>(
135            F::MODULUS_BIT_SIZE as u64,
136            rate,
137            full_rounds,
138            partial_rounds,
139            0,
140        );
141
142        PoseidonConfig::new(
143            full_rounds as usize,
144            partial_rounds as usize,
145            alpha,
146            mds,
147            ark,
148            rate,
149            1,
150        )
151    }
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 = 128;
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}