lambdaworks_crypto/commitments/
kzg.rs

1use super::traits::IsCommitmentScheme;
2use alloc::{borrow::ToOwned, vec::Vec};
3use core::{marker::PhantomData, mem};
4use lambdaworks_math::{
5    cyclic_group::IsGroup,
6    elliptic_curve::traits::IsPairing,
7    errors::DeserializationError,
8    field::{element::FieldElement, traits::IsPrimeField},
9    msm::pippenger::msm,
10    polynomial::Polynomial,
11    traits::{AsBytes, Deserializable},
12    unsigned_integer::element::UnsignedInteger,
13};
14
15#[derive(PartialEq, Clone, Debug)]
16pub struct StructuredReferenceString<G1Point, G2Point> {
17    /// Vector of points in G1 encoding g1, s g1, s^2 g1, s^3 g1, ... s^n g1
18    pub powers_main_group: Vec<G1Point>,
19    /// Slice of points in G2 encoding g2, s g2
20    /// We could relax this to include more powers, but for most applications
21    /// this suffices
22    pub powers_secondary_group: [G2Point; 2],
23}
24
25impl<G1Point, G2Point> StructuredReferenceString<G1Point, G2Point>
26where
27    G1Point: IsGroup,
28    G2Point: IsGroup,
29{
30    /// Creates a new SRS from slices of G1points and a slice of length 2 of G2 points
31    pub fn new(powers_main_group: &[G1Point], powers_secondary_group: &[G2Point; 2]) -> Self {
32        Self {
33            powers_main_group: powers_main_group.into(),
34            powers_secondary_group: powers_secondary_group.clone(),
35        }
36    }
37}
38
39#[cfg(feature = "std")]
40impl<G1Point, G2Point> StructuredReferenceString<G1Point, G2Point>
41where
42    G1Point: IsGroup + Deserializable,
43    G2Point: IsGroup + Deserializable,
44{
45    /// Read SRS from file
46    pub fn from_file(file_path: &str) -> Result<Self, crate::errors::SrsFromFileError> {
47        let bytes = std::fs::read(file_path)?;
48        Ok(Self::deserialize(&bytes)?)
49    }
50}
51
52impl<G1Point, G2Point> AsBytes for StructuredReferenceString<G1Point, G2Point>
53where
54    G1Point: IsGroup + AsBytes,
55    G2Point: IsGroup + AsBytes,
56{
57    /// Serialize SRS
58    fn as_bytes(&self) -> Vec<u8> {
59        let mut serialized_data: Vec<u8> = Vec::new();
60        // First 4 bytes encodes protocol version
61        let protocol_version: [u8; 4] = [0; 4];
62
63        serialized_data.extend(&protocol_version);
64
65        // Second 8 bytes store the amount of G1 elements to be stored, this is more than can be indexed with a 64-bit architecture, and some millions of terabytes of data if the points were compressed
66        let mut main_group_len_bytes: Vec<u8> = self.powers_main_group.len().to_le_bytes().to_vec();
67
68        // For architectures with less than 64 bits for pointers
69        // We add extra zeros at the end
70        while main_group_len_bytes.len() < 8 {
71            main_group_len_bytes.push(0)
72        }
73
74        serialized_data.extend(&main_group_len_bytes);
75
76        // G1 elements
77        for point in &self.powers_main_group {
78            serialized_data.extend(point.as_bytes());
79        }
80
81        // G2 elements
82        for point in &self.powers_secondary_group {
83            serialized_data.extend(point.as_bytes());
84        }
85
86        serialized_data
87    }
88}
89
90impl<G1Point, G2Point> Deserializable for StructuredReferenceString<G1Point, G2Point>
91where
92    G1Point: IsGroup + Deserializable,
93    G2Point: IsGroup + Deserializable,
94{
95    fn deserialize(bytes: &[u8]) -> Result<Self, DeserializationError> {
96        const MAIN_GROUP_LEN_OFFSET: usize = 4;
97        const MAIN_GROUP_OFFSET: usize = 12;
98
99        let main_group_len_u64 = u64::from_le_bytes(
100            // This unwrap can't fail since we are fixing the size of the slice
101            bytes[MAIN_GROUP_LEN_OFFSET..MAIN_GROUP_OFFSET]
102                .try_into()
103                .unwrap(),
104        );
105
106        let main_group_len = usize::try_from(main_group_len_u64)
107            .map_err(|_| DeserializationError::PointerSizeError)?;
108
109        let mut main_group: Vec<G1Point> = Vec::new();
110        let mut secondary_group: Vec<G2Point> = Vec::new();
111
112        let size_g1_point = mem::size_of::<G1Point>();
113        let size_g2_point = mem::size_of::<G2Point>();
114
115        for i in 0..main_group_len {
116            // The second unwrap shouldn't fail since the amount of bytes is fixed
117            let point = G1Point::deserialize(
118                bytes[i * size_g1_point + MAIN_GROUP_OFFSET
119                    ..i * size_g1_point + size_g1_point + MAIN_GROUP_OFFSET]
120                    .try_into()
121                    .unwrap(),
122            )?;
123            main_group.push(point);
124        }
125
126        let g2s_offset = size_g1_point * main_group_len + 12;
127        for i in 0..2 {
128            // The second unwrap shouldn't fail since the amount of bytes is fixed
129            let point = G2Point::deserialize(
130                bytes[i * size_g2_point + g2s_offset
131                    ..i * size_g2_point + g2s_offset + size_g2_point]
132                    .try_into()
133                    .unwrap(),
134            )?;
135            secondary_group.push(point);
136        }
137
138        let secondary_group_slice = [secondary_group[0].clone(), secondary_group[1].clone()];
139
140        let srs = StructuredReferenceString::new(&main_group, &secondary_group_slice);
141        Ok(srs)
142    }
143}
144
145#[derive(Clone)]
146pub struct KateZaveruchaGoldberg<F: IsPrimeField, P: IsPairing> {
147    srs: StructuredReferenceString<P::G1Point, P::G2Point>,
148    phantom: PhantomData<F>,
149}
150
151impl<F: IsPrimeField, P: IsPairing> KateZaveruchaGoldberg<F, P> {
152    pub fn new(srs: StructuredReferenceString<P::G1Point, P::G2Point>) -> Self {
153        Self {
154            srs,
155            phantom: PhantomData,
156        }
157    }
158}
159
160impl<const N: usize, F: IsPrimeField<RepresentativeType = UnsignedInteger<N>>, P: IsPairing>
161    IsCommitmentScheme<F> for KateZaveruchaGoldberg<F, P>
162{
163    type Commitment = P::G1Point;
164
165    /// Given a polynomial and an SRS, creates a commitment to p(x), which corresponds to a G1 point
166    /// The commitment is p(s) g1, evaluated as \sum_i c_i srs.powers_main_group[i], where c_i are the coefficients
167    /// of the polynomial.
168    fn commit(&self, p: &Polynomial<FieldElement<F>>) -> Self::Commitment {
169        let coefficients: Vec<_> = p
170            .coefficients
171            .iter()
172            .map(|coefficient| coefficient.representative())
173            .collect();
174        msm(
175            &coefficients,
176            &self.srs.powers_main_group[..coefficients.len()],
177        )
178        .expect("`points` is sliced by `cs`'s length")
179    }
180
181    /// Creates an evaluation proof for the polynomial p at x equal to y.
182    /// This is a commitment to the quotient polynomial q(t) = (p(t) - y)/(t - x)
183    /// The commitment is simply q(s) g1, corresponding to a G1 point
184    fn open(
185        &self,
186        x: &FieldElement<F>,
187        y: &FieldElement<F>,
188        p: &Polynomial<FieldElement<F>>,
189    ) -> Self::Commitment {
190        let mut poly_to_commit = p - y;
191        poly_to_commit.ruffini_division_inplace(x);
192        self.commit(&poly_to_commit)
193    }
194
195    /// Verifies the correct evaluation of a polynomial p by providing a commitment to p,
196    /// the point x, the evaluation y (p(x) = y) and an evaluation proof (commitment to the quotient polynomial)
197    /// Basically, we want to show that, at secret point s, p(s) - y = (s - x) q(s)
198    /// It uses pairings to verify the above condition, e(cm(p) - yg1,g2)*(cm(q), sg2 - xg2)^-1
199    /// Returns true for valid evaluation
200    fn verify(
201        &self,
202        x: &FieldElement<F>,
203        y: &FieldElement<F>,
204        p_commitment: &Self::Commitment,
205        proof: &Self::Commitment,
206    ) -> bool {
207        let g1 = &self.srs.powers_main_group[0];
208        let g2 = &self.srs.powers_secondary_group[0];
209        let alpha_g2 = &self.srs.powers_secondary_group[1];
210
211        let e = P::compute_batch(&[
212            (
213                &p_commitment.operate_with(&(g1.operate_with_self(y.representative())).neg()),
214                g2,
215            ),
216            (
217                &proof.neg(),
218                &(alpha_g2.operate_with(&(g2.operate_with_self(x.representative())).neg())),
219            ),
220        ]);
221        e == Ok(FieldElement::one())
222    }
223
224    /// Creates an evaluation proof for several polynomials at a single point x. upsilon is used to
225    /// perform the random linear combination, using Horner's evaluation form
226    fn open_batch(
227        &self,
228        x: &FieldElement<F>,
229        ys: &[FieldElement<F>],
230        polynomials: &[Polynomial<FieldElement<F>>],
231        upsilon: &FieldElement<F>,
232    ) -> Self::Commitment {
233        let acc_polynomial = polynomials
234            .iter()
235            .rev()
236            .fold(Polynomial::zero(), |acc, polynomial| {
237                acc * upsilon.to_owned() + polynomial
238            });
239
240        let acc_y = ys
241            .iter()
242            .rev()
243            .fold(FieldElement::zero(), |acc, y| acc * upsilon.to_owned() + y);
244
245        self.open(x, &acc_y, &acc_polynomial)
246    }
247
248    /// Verifies an evaluation proof for the evaluation of a batch of polynomials at x, using upsilon to perform the random
249    /// linear combination
250    /// Outputs true if the evaluation is correct
251    fn verify_batch(
252        &self,
253        x: &FieldElement<F>,
254        ys: &[FieldElement<F>],
255        p_commitments: &[Self::Commitment],
256        proof: &Self::Commitment,
257        upsilon: &FieldElement<F>,
258    ) -> bool {
259        let acc_commitment =
260            p_commitments
261                .iter()
262                .rev()
263                .fold(P::G1Point::neutral_element(), |acc, point| {
264                    acc.operate_with_self(upsilon.to_owned().representative())
265                        .operate_with(point)
266                });
267
268        let acc_y = ys
269            .iter()
270            .rev()
271            .fold(FieldElement::zero(), |acc, y| acc * upsilon.to_owned() + y);
272        self.verify(x, &acc_y, &acc_commitment, proof)
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use alloc::vec::Vec;
279    use core::slice;
280    use lambdaworks_math::{
281        cyclic_group::IsGroup,
282        elliptic_curve::{
283            short_weierstrass::{
284                curves::bls12_381::{
285                    curve::BLS12381Curve,
286                    default_types::{FrElement, FrField},
287                    pairing::BLS12381AtePairing,
288                    twist::BLS12381TwistCurve,
289                },
290                point::ShortWeierstrassProjectivePoint,
291            },
292            traits::{IsEllipticCurve, IsPairing},
293        },
294        field::element::FieldElement,
295        polynomial::Polynomial,
296        traits::{AsBytes, Deserializable},
297        unsigned_integer::element::U256,
298    };
299
300    use crate::commitments::traits::IsCommitmentScheme;
301
302    use super::{KateZaveruchaGoldberg, StructuredReferenceString};
303    use rand::Rng;
304
305    type G1 = ShortWeierstrassProjectivePoint<BLS12381Curve>;
306
307    #[allow(clippy::upper_case_acronyms)]
308    type KZG = KateZaveruchaGoldberg<FrField, BLS12381AtePairing>;
309
310    fn create_srs() -> StructuredReferenceString<
311        <BLS12381AtePairing as IsPairing>::G1Point,
312        <BLS12381AtePairing as IsPairing>::G2Point,
313    > {
314        let mut rng = rand::thread_rng();
315        let toxic_waste = FrElement::new(U256 {
316            limbs: [
317                rng.gen::<u64>(),
318                rng.gen::<u64>(),
319                rng.gen::<u64>(),
320                rng.gen::<u64>(),
321            ],
322        });
323        let g1 = BLS12381Curve::generator();
324        let g2 = BLS12381TwistCurve::generator();
325        let powers_main_group: Vec<G1> = (0..100)
326            .map(|exponent| {
327                g1.operate_with_self(toxic_waste.pow(exponent as u128).representative())
328            })
329            .collect();
330        let powers_secondary_group = [
331            g2.clone(),
332            g2.operate_with_self(toxic_waste.representative()),
333        ];
334        StructuredReferenceString::new(&powers_main_group, &powers_secondary_group)
335    }
336
337    #[test]
338    fn kzg_1() {
339        let kzg = KZG::new(create_srs());
340        let p = Polynomial::<FrElement>::new(&[FieldElement::one(), FieldElement::one()]);
341        let p_commitment: <BLS12381AtePairing as IsPairing>::G1Point = kzg.commit(&p);
342        let x = -FieldElement::one();
343        let y = p.evaluate(&x);
344        let proof = kzg.open(&x, &y, &p);
345        assert_eq!(y, FieldElement::zero());
346        assert_eq!(proof, BLS12381Curve::generator());
347        assert!(kzg.verify(&x, &y, &p_commitment, &proof));
348    }
349
350    #[test]
351    fn poly_9000_constant_should_verify_proof() {
352        let kzg = KZG::new(create_srs());
353        let p = Polynomial::new(&[FieldElement::from(9000)]);
354        let p_commitment: <BLS12381AtePairing as IsPairing>::G1Point = kzg.commit(&p);
355        let x = FieldElement::one();
356        let y = FieldElement::from(9000);
357        let proof = kzg.open(&x, &y, &p);
358        assert!(kzg.verify(&x, &y, &p_commitment, &proof));
359    }
360
361    #[test]
362    fn poly_9000_batched_should_verify() {
363        let kzg = KZG::new(create_srs());
364        let p0 = Polynomial::<FrElement>::new(&[FieldElement::from(9000)]);
365        let p0_commitment: <BLS12381AtePairing as IsPairing>::G1Point = kzg.commit(&p0);
366
367        let x = FieldElement::one();
368        let y0 = FieldElement::from(9000);
369        let upsilon = &FieldElement::from(1);
370
371        let proof = kzg.open_batch(&x, slice::from_ref(&y0), &[p0], upsilon);
372
373        assert!(kzg.verify_batch(&x, &[y0], &[p0_commitment], &proof, upsilon));
374    }
375
376    #[test]
377    fn two_poly_9000_batched_should_verify() {
378        let kzg = KZG::new(create_srs());
379        let p0 = Polynomial::<FrElement>::new(&[FieldElement::from(9000)]);
380        let p0_commitment: <BLS12381AtePairing as IsPairing>::G1Point = kzg.commit(&p0);
381
382        let x = FieldElement::one();
383        let y0 = FieldElement::from(9000);
384        let upsilon = &FieldElement::from(1);
385
386        let proof = kzg.open_batch(&x, &[y0.clone(), y0.clone()], &[p0.clone(), p0], upsilon);
387
388        assert!(kzg.verify_batch(
389            &x,
390            &[y0.clone(), y0],
391            &[p0_commitment.clone(), p0_commitment],
392            &proof,
393            upsilon
394        ));
395    }
396
397    #[test]
398    fn two_poly_batched_should_verify() {
399        let kzg = KZG::new(create_srs());
400
401        let x = FieldElement::from(3);
402
403        let p0 = Polynomial::<FrElement>::new(&[FieldElement::from(9000)]); // Constant polynomial
404        let p0_commitment: <BLS12381AtePairing as IsPairing>::G1Point = kzg.commit(&p0);
405        let y0 = FieldElement::from(9000);
406
407        let p1 = Polynomial::<FrElement>::new(&[
408            FieldElement::from(1),
409            FieldElement::from(2),
410            -FieldElement::from(1),
411        ]); // p(x) = 1 + 2x - x²
412        let p1_commitment: <BLS12381AtePairing as IsPairing>::G1Point = kzg.commit(&p1);
413        let y1 = p1.evaluate(&x);
414
415        let upsilon = &FieldElement::from(1);
416
417        let proof = kzg.open_batch(&x, &[y0.clone(), y1.clone()], &[p0, p1], upsilon);
418
419        assert!(kzg.verify_batch(
420            &x,
421            &[y0, y1],
422            &[p0_commitment, p1_commitment],
423            &proof,
424            upsilon
425        ));
426    }
427
428    #[test]
429    fn serialize_deserialize_srs() {
430        let srs = create_srs();
431        let bytes = srs.as_bytes();
432        let deserialized: StructuredReferenceString<
433            ShortWeierstrassProjectivePoint<BLS12381Curve>,
434            ShortWeierstrassProjectivePoint<BLS12381TwistCurve>,
435        > = StructuredReferenceString::deserialize(&bytes).unwrap();
436
437        assert_eq!(srs, deserialized);
438    }
439
440    #[test]
441    #[cfg(feature = "std")]
442    fn load_srs_from_file() {
443        type TestSrsType = StructuredReferenceString<
444            ShortWeierstrassProjectivePoint<BLS12381Curve>,
445            ShortWeierstrassProjectivePoint<BLS12381TwistCurve>,
446        >;
447
448        let base_dir = env!("CARGO_MANIFEST_DIR");
449        let srs_file = base_dir.to_owned() + "/src/commitments/test_srs/srs_3_g1_elements.bin";
450
451        let srs = TestSrsType::from_file(&srs_file).unwrap();
452
453        assert_eq!(srs.powers_main_group.len(), 3);
454    }
455}