Skip to main content

sonobe_primitives/transcripts/poseidon/
sponge.rs

1//! Implementation of transcript traits for arkworks' Poseidon sponge.
2
3use ark_crypto_primitives::sponge::{
4    Absorb, CryptographicSponge, DuplexSpongeMode, FieldBasedCryptographicSponge,
5    constraints::CryptographicSpongeVar,
6    poseidon::{PoseidonConfig, PoseidonSponge, constraints::PoseidonSpongeVar},
7};
8use ark_ff::PrimeField;
9use ark_r1cs_std::fields::{FieldVar, fp::FpVar};
10use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError};
11use ark_std::mem::transmute_copy;
12
13use crate::transcripts::{AbsorbableVar, Transcript, TranscriptGadget};
14
15impl<F: PrimeField> Transcript<F> for PoseidonSponge<F> {
16    type Config = PoseidonConfig<F>;
17    type Gadget = PoseidonSpongeVar<F>;
18
19    fn new(config: Self::Config) -> Self {
20        Self {
21            state: vec![F::zero(); config.rate + config.capacity],
22            parameters: config,
23            mode: DuplexSpongeMode::Absorbing {
24                next_absorb_index: 0,
25            },
26        }
27    }
28
29    fn add_field_elements(&mut self, input: &[F]) -> &mut Self {
30        struct Hack<I>(I);
31        impl<F> Absorb for Hack<&[F]> {
32            fn to_sponge_bytes(&self, _: &mut Vec<u8>) {
33                // Unreachable because `PoseidonSponge::absorb` only calls
34                // `to_sponge_field_elements_as_vec::<F>`
35                unreachable!()
36            }
37
38            fn to_sponge_field_elements<T: PrimeField>(&self, dest: &mut Vec<T>) {
39                // Safe because `F` in `to_sponge_field_elements_as_vec::<F>`,
40                // which is called by `PoseidonSponge::absorb`, is the same as
41                // `T` here.
42                dest.extend(unsafe { transmute_copy::<&[F], &[T]>(&self.0) });
43            }
44        }
45        CryptographicSponge::absorb(self, &Hack(input));
46        self
47    }
48
49    fn get_field_elements(&mut self, num_elements: usize) -> Vec<F> {
50        self.squeeze_native_field_elements(num_elements)
51    }
52}
53
54impl<F: PrimeField> TranscriptGadget<F> for PoseidonSpongeVar<F> {
55    type Config = PoseidonConfig<F>;
56    type Widget = PoseidonSponge<F>;
57
58    fn new(config: PoseidonConfig<F>) -> Self
59    where
60        Self: Sized,
61    {
62        Self {
63            cs: ConstraintSystemRef::None,
64            state: vec![FpVar::<F>::zero(); config.rate + config.capacity],
65            parameters: config,
66            mode: DuplexSpongeMode::Absorbing {
67                next_absorb_index: 0,
68            },
69        }
70    }
71
72    fn add<A: AbsorbableVar<F>>(&mut self, input: &A) -> Result<&mut Self, SynthesisError> {
73        let mut result = Vec::new();
74        input.absorb_into(&mut result)?;
75
76        self.absorb(&result)?;
77        Ok(self)
78    }
79
80    fn get_field_elements(&mut self, num_elements: usize) -> Result<Vec<FpVar<F>>, SynthesisError> {
81        self.squeeze_field_elements(num_elements)
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use ark_bn254::{Fr, G1Projective as G1};
88    use ark_crypto_primitives::sponge::poseidon::{PoseidonSponge, constraints::PoseidonSpongeVar};
89    use ark_ff::UniformRand;
90    use ark_grumpkin::Projective as G2;
91    use ark_r1cs_std::{
92        GR1CSVar, alloc::AllocVar, fields::fp::FpVar,
93        groups::curves::short_weierstrass::ProjectiveVar,
94    };
95    use ark_relations::gr1cs::ConstraintSystem;
96    use ark_std::{error::Error, rand::thread_rng, str::FromStr};
97    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
98    use wasm_bindgen_test::wasm_bindgen_test as test;
99
100    use crate::{
101        algebra::group::emulated::EmulatedAffineVar,
102        transcripts::{Transcript, TranscriptGadget, poseidon::poseidon_circom_config},
103    };
104
105    // Test with value taken from https://github.com/iden3/circomlibjs/blob/43cc582b100fc3459cf78d903a6f538e5d7f38ee/test/poseidon.js#L32
106    #[test]
107    fn check_against_circom_poseidon() -> Result<(), Box<dyn Error>> {
108        let config = poseidon_circom_config();
109        let mut poseidon_sponge = PoseidonSponge::new(config);
110        let v = vec![1, 2, 3, 4]
111            .into_iter()
112            .map(Fr::from)
113            .collect::<Vec<_>>();
114        poseidon_sponge.add(&v);
115        poseidon_sponge.get_field_elements(1);
116        assert_eq!(
117            poseidon_sponge.state[0],
118            Fr::from_str(
119                "18821383157269793795438455681495246036402687001665670618754263018637548127333"
120            )
121            .unwrap()
122        );
123        Ok(())
124    }
125
126    #[test]
127    fn test_challenge_field_element() -> Result<(), Box<dyn Error>> {
128        // Create a transcript outside of the circuit
129        let config = poseidon_circom_config();
130        let mut tr = PoseidonSponge::new(config.clone());
131        tr.add(&Fr::from(42_u32));
132        let c = tr.challenge_field_element();
133
134        // Create a transcript inside of the circuit
135        let cs = ConstraintSystem::new_ref();
136        let mut tr_var = PoseidonSpongeVar::new(config);
137        let v = FpVar::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?;
138        tr_var.add(&v)?;
139        let c_var = tr_var.challenge_field_element()?;
140
141        // Assert that in-circuit and out-of-circuit transcripts return the same
142        // challenge
143        assert_eq!(c, c_var.value()?);
144        Ok(())
145    }
146
147    #[test]
148    fn test_challenge_bits() -> Result<(), Box<dyn Error>> {
149        let nbits = 128;
150
151        // Create a transcript outside of the circuit
152        let config = poseidon_circom_config();
153        let mut tr = PoseidonSponge::new(config.clone());
154        tr.add(&Fr::from(42_u32));
155        let c = tr.challenge_bits(nbits);
156
157        // Create a transcript inside of the circuit
158        let cs = ConstraintSystem::new_ref();
159        let mut tr_var = PoseidonSpongeVar::new(config);
160        let v = FpVar::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?;
161        tr_var.add(&v)?;
162        let c_var = tr_var.challenge_bits(nbits)?;
163
164        // Assert that in-circuit and out-of-circuit transcripts return the same
165        // challenge
166        assert_eq!(c, c_var.value()?);
167        Ok(())
168    }
169
170    #[test]
171    fn test_absorb_canonical_point() -> Result<(), Box<dyn Error>> {
172        // Create a transcript outside of the circuit
173        let config = poseidon_circom_config();
174        let mut tr = PoseidonSponge::new(config.clone());
175        let rng = &mut thread_rng();
176
177        let p = G2::rand(rng);
178        tr.add(&p);
179        let c = tr.challenge_field_element();
180
181        // Create a transcript inside of the circuit
182        let cs = ConstraintSystem::new_ref();
183        let mut tr_var = PoseidonSpongeVar::new(config);
184        let p_var = ProjectiveVar::new_witness(cs, || Ok(p))?;
185        tr_var.add(&p_var)?;
186        let c_var = tr_var.challenge_field_element()?;
187
188        // Assert that in-circuit and out-of-circuit transcripts return the same
189        // challenge
190        assert_eq!(c, c_var.value()?);
191        Ok(())
192    }
193
194    #[test]
195    fn test_absorb_emulated_point() -> Result<(), Box<dyn Error>> {
196        // Create a transcript outside of the circuit
197        let config = poseidon_circom_config();
198        let mut tr = PoseidonSponge::new(config.clone());
199        let rng = &mut thread_rng();
200
201        let p = G1::rand(rng);
202        tr.add(&p);
203        let c = tr.challenge_field_element();
204
205        // Create a transcript inside of the circuit
206        let cs = ConstraintSystem::new_ref();
207        let mut tr_var = PoseidonSpongeVar::new(config);
208        let p_var = EmulatedAffineVar::new_witness(cs, || Ok(p))?;
209        tr_var.add(&p_var)?;
210        let c_var = tr_var.challenge_field_element()?;
211
212        // Assert that in-circuit and out-of-circuit transcripts return the same
213        // challenge
214        assert_eq!(c, c_var.value()?);
215        Ok(())
216    }
217}