mina_poseidon/
sponge.rs

1extern crate alloc;
2use crate::{
3    constants::{PlonkSpongeConstantsKimchi, SpongeConstants},
4    poseidon::{ArithmeticSponge, ArithmeticSpongeParams, Sponge},
5};
6use alloc::{vec, vec::Vec};
7use ark_ec::models::short_weierstrass::{Affine, SWCurveConfig};
8use ark_ff::{BigInteger, Field, One, PrimeField, Zero};
9
10/// Abstracts a sponge operating on a base field `Fq` of the curve
11/// `G`. The parameter `Fr` is modelling the scalar field of the
12/// curve.
13pub trait FqSponge<Fq: Field, G, Fr, const FULL_ROUNDS: usize> {
14    /// Creates a new sponge.
15    fn new(p: &'static ArithmeticSpongeParams<Fq, FULL_ROUNDS>) -> Self;
16
17    /// Absorbs a base field element. This operation is the most
18    /// straightforward and calls the underlying sponge directly.
19    fn absorb_fq(&mut self, x: &[Fq]);
20
21    /// Absorbs a base field point, that is a pair of `Fq` elements.
22    /// In the case of the point to infinity, the values `(0, 0)` are absorbed.
23    fn absorb_g(&mut self, g: &[G]);
24
25    /// Absorbs an element of the scalar field `Fr` --- it is done
26    /// by converting the element to the base field first.
27    fn absorb_fr(&mut self, x: &[Fr]);
28
29    /// Squeeze out a base field challenge. This operation is the most
30    /// direct and calls the underlying sponge.
31    fn challenge_fq(&mut self) -> Fq;
32
33    /// Squeeze out a challenge in the scalar field. Implemented by
34    /// squeezing out base points and then converting them to a scalar
35    /// field element using binary representation.
36    fn challenge(&mut self) -> Fr;
37
38    /// Returns a base field digest by squeezing the underlying sponge directly.
39    fn digest_fq(self) -> Fq;
40
41    /// Returns a scalar field digest using the binary representation technique.
42    fn digest(self) -> Fr;
43}
44
45pub const CHALLENGE_LENGTH_IN_LIMBS: usize = 2;
46
47const HIGH_ENTROPY_LIMBS: usize = 2;
48
49// TODO: move to a different file / module
50/// A challenge which is used as a scalar on a group element in the verifier
51#[derive(Clone, Debug)]
52pub struct ScalarChallenge<F>(pub F);
53
54pub fn endo_coefficient<F: PrimeField>() -> F {
55    let p_minus_1_over_3 = (F::zero() - F::one()) / F::from(3u64);
56
57    F::GENERATOR.pow(p_minus_1_over_3.into_bigint().as_ref())
58}
59
60fn get_bit(limbs_lsb: &[u64], i: u64) -> u64 {
61    let limb = i / 64;
62    let j = i % 64;
63    (limbs_lsb[limb as usize] >> j) & 1
64}
65
66impl<F: PrimeField> ScalarChallenge<F> {
67    pub fn to_field_with_length(&self, length_in_bits: usize, endo_coeff: &F) -> F {
68        let rep = self.0.into_bigint();
69        let r = rep.as_ref();
70
71        let mut a: F = 2_u64.into();
72        let mut b: F = 2_u64.into();
73
74        let one = F::one();
75        let neg_one = -one;
76
77        for i in (0..(length_in_bits as u64 / 2)).rev() {
78            a.double_in_place();
79            b.double_in_place();
80
81            let r_2i = get_bit(r, 2 * i);
82            let s = if r_2i == 0 { &neg_one } else { &one };
83
84            if get_bit(r, 2 * i + 1) == 0 {
85                b += s;
86            } else {
87                a += s;
88            }
89        }
90
91        a * endo_coeff + b
92    }
93
94    pub fn to_field(&self, endo_coeff: &F) -> F {
95        let length_in_bits = 64 * CHALLENGE_LENGTH_IN_LIMBS;
96        self.to_field_with_length(length_in_bits, endo_coeff)
97    }
98}
99
100#[derive(Clone)]
101pub struct DefaultFqSponge<P: SWCurveConfig, SC: SpongeConstants, const FULL_ROUNDS: usize> {
102    pub sponge: ArithmeticSponge<P::BaseField, SC, FULL_ROUNDS>,
103    pub last_squeezed: Vec<u64>,
104}
105
106pub struct DefaultFrSponge<Fr: Field, SC: SpongeConstants, const FULL_ROUNDS: usize> {
107    pub sponge: ArithmeticSponge<Fr, SC, FULL_ROUNDS>,
108    pub last_squeezed: Vec<u64>,
109}
110
111impl<const FULL_ROUNDS: usize, Fr> From<&'static ArithmeticSpongeParams<Fr, FULL_ROUNDS>>
112    for DefaultFrSponge<Fr, PlonkSpongeConstantsKimchi, FULL_ROUNDS>
113where
114    Fr: PrimeField,
115{
116    fn from(p: &'static ArithmeticSpongeParams<Fr, FULL_ROUNDS>) -> Self {
117        DefaultFrSponge {
118            sponge: ArithmeticSponge::new(p),
119            last_squeezed: vec![],
120        }
121    }
122}
123
124fn pack<B: BigInteger>(limbs_lsb: &[u64]) -> B {
125    let mut res: B = 0u64.into();
126    for &x in limbs_lsb.iter().rev() {
127        res <<= 64;
128        res.add_with_carry(&x.into());
129    }
130    res
131}
132
133impl<Fr: PrimeField, SC: SpongeConstants, const FULL_ROUNDS: usize>
134    DefaultFrSponge<Fr, SC, FULL_ROUNDS>
135{
136    pub fn squeeze(&mut self, num_limbs: usize) -> Fr {
137        if self.last_squeezed.len() >= num_limbs {
138            let last_squeezed = self.last_squeezed.clone();
139            let (limbs, remaining) = last_squeezed.split_at(num_limbs);
140            self.last_squeezed = remaining.to_vec();
141            Fr::from(pack::<Fr::BigInt>(limbs))
142        } else {
143            let x = self.sponge.squeeze().into_bigint();
144            self.last_squeezed
145                .extend(&x.as_ref()[0..HIGH_ENTROPY_LIMBS]);
146            self.squeeze(num_limbs)
147        }
148    }
149}
150
151impl<P: SWCurveConfig, SC: SpongeConstants, const FULL_ROUNDS: usize>
152    DefaultFqSponge<P, SC, FULL_ROUNDS>
153where
154    P::BaseField: PrimeField,
155    <P::BaseField as PrimeField>::BigInt: Into<<P::ScalarField as PrimeField>::BigInt>,
156{
157    pub fn squeeze_limbs(&mut self, num_limbs: usize) -> Vec<u64> {
158        if self.last_squeezed.len() >= num_limbs {
159            let last_squeezed = self.last_squeezed.clone();
160            let (limbs, remaining) = last_squeezed.split_at(num_limbs);
161            self.last_squeezed = remaining.to_vec();
162            limbs.to_vec()
163        } else {
164            let x = self.sponge.squeeze().into_bigint();
165            self.last_squeezed
166                .extend(&x.as_ref()[0..HIGH_ENTROPY_LIMBS]);
167            self.squeeze_limbs(num_limbs)
168        }
169    }
170
171    pub fn squeeze_field(&mut self) -> P::BaseField {
172        self.last_squeezed = vec![];
173        self.sponge.squeeze()
174    }
175
176    pub fn squeeze(&mut self, num_limbs: usize) -> P::ScalarField {
177        P::ScalarField::from_bigint(pack(&self.squeeze_limbs(num_limbs)))
178            .expect("internal representation was not a valid field element")
179    }
180}
181
182impl<P: SWCurveConfig, SC: SpongeConstants, const FULL_ROUNDS: usize>
183    FqSponge<P::BaseField, Affine<P>, P::ScalarField, FULL_ROUNDS>
184    for DefaultFqSponge<P, SC, FULL_ROUNDS>
185where
186    P::BaseField: PrimeField,
187    <P::BaseField as PrimeField>::BigInt: Into<<P::ScalarField as PrimeField>::BigInt>,
188{
189    fn new(params: &'static ArithmeticSpongeParams<P::BaseField, FULL_ROUNDS>) -> Self {
190        let sponge = ArithmeticSponge::new(params);
191        DefaultFqSponge {
192            sponge,
193            last_squeezed: vec![],
194        }
195    }
196
197    fn absorb_g(&mut self, g: &[Affine<P>]) {
198        self.last_squeezed = vec![];
199        for g in g.iter() {
200            if g.infinity {
201                // absorb a fake point (0, 0)
202                let zero = P::BaseField::zero();
203                self.sponge.absorb(&[zero]);
204                self.sponge.absorb(&[zero]);
205            } else {
206                self.sponge.absorb(&[g.x]);
207                self.sponge.absorb(&[g.y]);
208            }
209        }
210    }
211
212    fn absorb_fq(&mut self, x: &[P::BaseField]) {
213        self.last_squeezed = vec![];
214
215        for fe in x {
216            self.sponge.absorb(&[*fe])
217        }
218    }
219
220    fn absorb_fr(&mut self, x: &[P::ScalarField]) {
221        self.last_squeezed = vec![];
222
223        x.iter().for_each(|x| {
224            let bits = x.into_bigint().to_bits_le();
225
226            // absorb
227            if <P::ScalarField as PrimeField>::MODULUS
228                < <P::BaseField as PrimeField>::MODULUS.into()
229            {
230                let fe = P::BaseField::from_bigint(
231                    <P::BaseField as PrimeField>::BigInt::from_bits_le(&bits),
232                )
233                .expect("padding code has a bug");
234                self.sponge.absorb(&[fe]);
235            } else {
236                let low_bit = if bits[0] {
237                    P::BaseField::one()
238                } else {
239                    P::BaseField::zero()
240                };
241
242                let high_bits = P::BaseField::from_bigint(
243                    <P::BaseField as PrimeField>::BigInt::from_bits_le(&bits[1..bits.len()]),
244                )
245                .expect("padding code has a bug");
246
247                self.sponge.absorb(&[high_bits]);
248                self.sponge.absorb(&[low_bit]);
249            }
250        });
251    }
252
253    fn digest(mut self) -> P::ScalarField {
254        let x: <P::BaseField as PrimeField>::BigInt = self.squeeze_field().into_bigint();
255        // Returns zero for values that are too large.
256        // This means that there is a bias for the value zero (in one of the curve).
257        // An attacker could try to target that seed, in order to predict the challenges u and v produced by the Fr-Sponge.
258        // This would allow the attacker to mess with the result of the aggregated evaluation proof.
259        // Previously the attacker's odds were 1/q, now it's (q-p)/q.
260        // Since log2(q-p) ~ 86 and log2(q) ~ 254 the odds of a successful attack are negligible.
261        P::ScalarField::from_bigint(x.into()).unwrap_or_else(P::ScalarField::zero)
262    }
263
264    fn digest_fq(mut self) -> P::BaseField {
265        self.squeeze_field()
266    }
267
268    fn challenge(&mut self) -> P::ScalarField {
269        self.squeeze(CHALLENGE_LENGTH_IN_LIMBS)
270    }
271
272    fn challenge_fq(&mut self) -> P::BaseField {
273        self.squeeze_field()
274    }
275}
276
277//
278// OCaml types
279//
280
281#[cfg(feature = "ocaml_types")]
282#[allow(non_local_definitions)]
283pub mod caml {
284    use super::*;
285
286    extern crate alloc;
287    use alloc::{
288        format,
289        string::{String, ToString},
290    };
291
292    //
293    // ScalarChallenge<F> <-> CamlScalarChallenge<CamlF>
294    //
295
296    #[derive(Debug, Clone, ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Struct)]
297    pub struct CamlScalarChallenge<CamlF>(pub CamlF);
298
299    impl<F, CamlF> From<ScalarChallenge<F>> for CamlScalarChallenge<CamlF>
300    where
301        CamlF: From<F>,
302    {
303        fn from(sc: ScalarChallenge<F>) -> Self {
304            Self(sc.0.into())
305        }
306    }
307
308    impl<F, CamlF> From<CamlScalarChallenge<CamlF>> for ScalarChallenge<F>
309    where
310        CamlF: Into<F>,
311    {
312        fn from(caml_sc: CamlScalarChallenge<CamlF>) -> Self {
313            Self(caml_sc.0.into())
314        }
315    }
316}