Skip to main content

nova_snark/provider/
pedersen.rs

1//! This module provides an implementation of a commitment engine
2use crate::provider::msm::batch_add;
3#[cfg(feature = "io")]
4use crate::provider::ptau::{read_points, write_points, PtauFileError};
5use crate::traits::evm_serde::EvmCompatSerde;
6use crate::{
7  errors::NovaError,
8  gadgets::utils::to_bignat_repr,
9  provider::traits::{DlogGroup, DlogGroupExt},
10  traits::{
11    commitment::{CommitmentEngineTrait, CommitmentTrait, Len},
12    AbsorbInRO2Trait, AbsorbInROTrait, Engine, Group, ROTrait, TranscriptReprTrait,
13  },
14};
15use core::{
16  fmt::Debug,
17  marker::PhantomData,
18  ops::{Add, Mul, MulAssign, Range},
19};
20use ff::Field;
21use num_integer::Integer;
22use num_traits::ToPrimitive;
23use rayon::prelude::*;
24use serde::{Deserialize, Serialize};
25use serde_with::serde_as;
26
27#[cfg(feature = "io")]
28const KEY_FILE_HEAD: [u8; 12] = *b"PEDERSEN_KEY";
29
30/// A type that holds commitment generators
31#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
32pub struct CommitmentKey<E: Engine>
33where
34  E::GE: DlogGroup,
35{
36  ck: Vec<<E::GE as DlogGroup>::AffineGroupElement>,
37  h: <E::GE as DlogGroup>::AffineGroupElement,
38}
39
40impl<E: Engine> CommitmentKey<E>
41where
42  E::GE: DlogGroup,
43{
44  /// Returns a reference to the generator points (affine form).
45  pub fn generators(&self) -> &[<E::GE as DlogGroup>::AffineGroupElement] {
46    &self.ck
47  }
48}
49
50impl<E: Engine> Len for CommitmentKey<E>
51where
52  E::GE: DlogGroup,
53{
54  fn length(&self) -> usize {
55    self.ck.len()
56  }
57}
58
59/// A type that holds blinding generator
60#[serde_as]
61#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(bound = "")]
63pub struct DerandKey<E: Engine>
64where
65  E::GE: DlogGroup,
66{
67  #[serde_as(as = "EvmCompatSerde")]
68  h: <E::GE as DlogGroup>::AffineGroupElement,
69}
70
71/// A type that holds a commitment
72#[serde_as]
73#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
74#[serde(bound = "")]
75pub struct Commitment<E: Engine>
76where
77  E::GE: DlogGroup,
78{
79  #[serde_as(as = "EvmCompatSerde")]
80  pub(crate) comm: E::GE,
81}
82
83impl<E: Engine> CommitmentTrait<E> for Commitment<E>
84where
85  E::GE: DlogGroup,
86{
87  fn to_coordinates(&self) -> (E::Base, E::Base, bool) {
88    self.comm.to_coordinates()
89  }
90}
91
92impl<E: Engine> Default for Commitment<E>
93where
94  E::GE: DlogGroup,
95{
96  fn default() -> Self {
97    Commitment {
98      comm: E::GE::zero(),
99    }
100  }
101}
102
103impl<E: Engine> TranscriptReprTrait<E::GE> for Commitment<E>
104where
105  E::GE: DlogGroup,
106{
107  fn to_transcript_bytes(&self) -> Vec<u8> {
108    let (x, y, is_infinity) = self.comm.to_coordinates();
109    let is_infinity_byte = (!is_infinity).into();
110    [
111      x.to_transcript_bytes(),
112      y.to_transcript_bytes(),
113      [is_infinity_byte].to_vec(),
114    ]
115    .concat()
116  }
117}
118
119impl<E: Engine> AbsorbInROTrait<E> for Commitment<E>
120where
121  E::GE: DlogGroup,
122{
123  fn absorb_in_ro(&self, ro: &mut E::RO) {
124    let (x, y, is_infinity) = self.comm.to_coordinates();
125    // When B != 0 (true for BN254, Grumpkin, etc.), (0,0) is not on the curve
126    // so we can use it as a canonical representation for infinity.
127    // This saves one field element absorption per point.
128    let (_, b, _, _) = E::GE::group_params();
129    if b != E::Base::ZERO {
130      let (x, y) = if is_infinity {
131        (E::Base::ZERO, E::Base::ZERO)
132      } else {
133        (x, y)
134      };
135      ro.absorb(x);
136      ro.absorb(y);
137    } else {
138      ro.absorb(x);
139      ro.absorb(y);
140      ro.absorb(if is_infinity {
141        E::Base::ONE
142      } else {
143        E::Base::ZERO
144      });
145    }
146  }
147}
148
149impl<E: Engine> AbsorbInRO2Trait<E> for Commitment<E>
150where
151  E::GE: DlogGroup,
152{
153  fn absorb_in_ro2(&self, ro: &mut E::RO2) {
154    let (x, y, is_infinity) = self.comm.to_coordinates();
155    // When B != 0, use (0,0) for infinity
156    let (_, b, _, _) = E::GE::group_params();
157    let (x, y) = if b != E::Base::ZERO && is_infinity {
158      (E::Base::ZERO, E::Base::ZERO)
159    } else {
160      (x, y)
161    };
162
163    // we have to absorb x and y in big num format
164    let limbs_x = to_bignat_repr(&x);
165    let limbs_y = to_bignat_repr(&y);
166
167    for limb in limbs_x.iter().chain(limbs_y.iter()) {
168      ro.absorb(*limb);
169    }
170    // Only absorb is_infinity when B == 0
171    if b == E::Base::ZERO {
172      ro.absorb(if is_infinity {
173        E::Scalar::ONE
174      } else {
175        E::Scalar::ZERO
176      });
177    }
178  }
179}
180
181impl<E: Engine> MulAssign<E::Scalar> for Commitment<E>
182where
183  E::GE: DlogGroup,
184{
185  fn mul_assign(&mut self, scalar: E::Scalar) {
186    *self = Commitment {
187      comm: self.comm * scalar,
188    };
189  }
190}
191
192impl<'b, E: Engine> Mul<&'b E::Scalar> for &'_ Commitment<E>
193where
194  E::GE: DlogGroup,
195{
196  type Output = Commitment<E>;
197  fn mul(self, scalar: &'b E::Scalar) -> Commitment<E> {
198    Commitment {
199      comm: self.comm * scalar,
200    }
201  }
202}
203
204impl<E: Engine> Mul<E::Scalar> for Commitment<E>
205where
206  E::GE: DlogGroup,
207{
208  type Output = Commitment<E>;
209
210  fn mul(self, scalar: E::Scalar) -> Commitment<E> {
211    Commitment {
212      comm: self.comm * scalar,
213    }
214  }
215}
216
217impl<E: Engine> Add for Commitment<E>
218where
219  E::GE: DlogGroup,
220{
221  type Output = Commitment<E>;
222
223  fn add(self, other: Commitment<E>) -> Commitment<E> {
224    Commitment {
225      comm: self.comm + other.comm,
226    }
227  }
228}
229
230/// Provides a commitment engine
231#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
232pub struct CommitmentEngine<E: Engine> {
233  _p: PhantomData<E>,
234}
235
236impl<E: Engine> CommitmentKey<E>
237where
238  E::GE: DlogGroup,
239{
240  /// Returns the coordinates of the generator points.
241  ///
242  /// This method extracts the (x, y) coordinates of each generator point
243  /// in the commitment key. This is useful for operations that need direct
244  /// access to the underlying elliptic curve points.
245  ///
246  /// # Panics
247  ///
248  /// Panics if any generator point is the point at infinity.
249  pub fn to_coordinates(&self) -> Vec<(E::Base, E::Base)> {
250    self
251      .ck
252      .par_iter()
253      .map(|c| {
254        let (x, y, is_infinity) = <E::GE as DlogGroup>::group(c).to_coordinates();
255        assert!(!is_infinity);
256        (x, y)
257      })
258      .collect()
259  }
260}
261
262impl<E: Engine> CommitmentEngineTrait<E> for CommitmentEngine<E>
263where
264  E::GE: DlogGroupExt,
265{
266  type CommitmentKey = CommitmentKey<E>;
267  type Commitment = Commitment<E>;
268  type DerandKey = DerandKey<E>;
269
270  fn setup(label: &'static [u8], n: usize) -> Result<Self::CommitmentKey, NovaError> {
271    let gens = E::GE::from_label(label, n.next_power_of_two() + 1);
272
273    let (h, ck) = gens.split_first().unwrap();
274
275    Ok(Self::CommitmentKey {
276      ck: ck.to_vec(),
277      h: *h,
278    })
279  }
280
281  fn derand_key(ck: &Self::CommitmentKey) -> Self::DerandKey {
282    Self::DerandKey { h: ck.h }
283  }
284
285  fn commit(ck: &Self::CommitmentKey, v: &[E::Scalar], r: &E::Scalar) -> Self::Commitment {
286    assert!(ck.ck.len() >= v.len());
287
288    Commitment {
289      comm: E::GE::vartime_multiscalar_mul(v, &ck.ck[..v.len()])
290        + <E::GE as DlogGroup>::group(&ck.h) * r,
291    }
292  }
293
294  fn commit_small<T: Integer + Into<u64> + Copy + Sync + ToPrimitive>(
295    ck: &Self::CommitmentKey,
296    v: &[T],
297    r: &E::Scalar,
298  ) -> Self::Commitment {
299    assert!(ck.ck.len() >= v.len());
300
301    Commitment {
302      comm: E::GE::vartime_multiscalar_mul_small(v, &ck.ck[..v.len()])
303        + <E::GE as DlogGroup>::group(&ck.h) * r,
304    }
305  }
306
307  fn commit_small_range<T: Integer + Into<u64> + Copy + Sync + ToPrimitive>(
308    ck: &Self::CommitmentKey,
309    v: &[T],
310    r: &<E as Engine>::Scalar,
311    range: Range<usize>,
312    max_num_bits: usize,
313  ) -> Self::Commitment {
314    let bases = &ck.ck[range.clone()];
315    let scalars = &v[range];
316
317    assert!(bases.len() == scalars.len());
318
319    let mut res =
320      E::GE::vartime_multiscalar_mul_small_with_max_num_bits(scalars, bases, max_num_bits);
321
322    if r != &E::Scalar::ZERO {
323      res += <E::GE as DlogGroup>::group(&ck.h) * r;
324    }
325
326    Commitment { comm: res }
327  }
328
329  fn derandomize(
330    dk: &Self::DerandKey,
331    commit: &Self::Commitment,
332    r: &E::Scalar,
333  ) -> Self::Commitment {
334    Commitment {
335      comm: commit.comm - <E::GE as DlogGroup>::group(&dk.h) * r,
336    }
337  }
338
339  #[cfg(feature = "io")]
340  fn load_setup(
341    reader: &mut (impl std::io::Read + std::io::Seek),
342    _label: &'static [u8],
343    n: usize,
344  ) -> Result<Self::CommitmentKey, PtauFileError> {
345    let num = n.next_power_of_two();
346    {
347      let mut head = [0u8; 12];
348      reader.read_exact(&mut head)?;
349      if head != KEY_FILE_HEAD {
350        return Err(PtauFileError::InvalidHead);
351      }
352    }
353
354    let points = read_points(reader, num + 1)?;
355
356    let (first, second) = points.split_at(1);
357
358    Ok(Self::CommitmentKey {
359      ck: second.to_vec(),
360      h: first[0],
361    })
362  }
363
364  fn ck_to_coordinates(ck: &Self::CommitmentKey) -> Vec<(E::Base, E::Base)> {
365    ck.to_coordinates()
366  }
367
368  fn ck_to_group_elements(ck: &Self::CommitmentKey) -> Vec<E::GE> {
369    ck.ck
370      .par_iter()
371      .map(|g| {
372        let ge = E::GE::group(g);
373        assert!(
374          ge != E::GE::zero(),
375          "CommitmentKey contains a generator at infinity"
376        );
377        ge
378      })
379      .collect()
380  }
381
382  #[cfg(feature = "io")]
383  fn save_setup(
384    ck: &Self::CommitmentKey,
385    writer: &mut impl std::io::Write,
386  ) -> Result<(), PtauFileError> {
387    writer.write_all(&KEY_FILE_HEAD)?;
388    let mut points = Vec::with_capacity(ck.ck.len() + 1);
389    points.push(ck.h);
390    points.extend(ck.ck.iter().cloned());
391    write_points(writer, points)
392  }
393
394  fn commit_sparse_binary(
395    ck: &Self::CommitmentKey,
396    non_zero_indices: &[usize],
397    r: &<E as Engine>::Scalar,
398  ) -> Self::Commitment {
399    let comm = batch_add(&ck.ck, non_zero_indices);
400    let mut comm = <E::GE as DlogGroup>::group(&comm.into());
401
402    if r != &E::Scalar::ZERO {
403      comm += <E::GE as DlogGroup>::group(&ck.h) * r;
404    }
405
406    Commitment { comm }
407  }
408}
409
410/// A trait listing properties of a commitment key that can be managed in a divide-and-conquer fashion
411pub trait CommitmentKeyExtTrait<E: Engine>
412where
413  E::GE: DlogGroup,
414{
415  /// Splits the commitment key into two pieces at a specified point
416  fn split_at(&self, n: usize) -> (Self, Self)
417  where
418    Self: Sized;
419
420  /// Combines two commitment keys into one
421  fn combine(&self, other: &Self) -> Self;
422
423  /// Folds the two commitment keys into one using the provided weights
424  fn fold(&self, w1: &E::Scalar, w2: &E::Scalar) -> Self;
425
426  /// Scales the commitment key using the provided scalar
427  fn scale(&self, r: &E::Scalar) -> Self;
428
429  /// Reinterprets commitments as commitment keys
430  fn reinterpret_commitments_as_ck(
431    c: &[<E::CE as CommitmentEngineTrait<E>>::Commitment],
432  ) -> Result<Self, NovaError>
433  where
434    Self: Sized;
435}
436
437impl<E: Engine<CE = CommitmentEngine<E>>> CommitmentKeyExtTrait<E> for CommitmentKey<E>
438where
439  E::GE: DlogGroupExt,
440{
441  fn split_at(&self, n: usize) -> (CommitmentKey<E>, CommitmentKey<E>) {
442    (
443      CommitmentKey {
444        ck: self.ck[0..n].to_vec(),
445        h: self.h,
446      },
447      CommitmentKey {
448        ck: self.ck[n..].to_vec(),
449        h: self.h,
450      },
451    )
452  }
453
454  fn combine(&self, other: &CommitmentKey<E>) -> CommitmentKey<E> {
455    let ck = {
456      let mut c = self.ck.clone();
457      c.extend(other.ck.clone());
458      c
459    };
460    CommitmentKey { ck, h: self.h }
461  }
462
463  // combines the left and right halves of `self` using `w1` and `w2` as the weights
464  fn fold(&self, w1: &E::Scalar, w2: &E::Scalar) -> CommitmentKey<E> {
465    let w = vec![*w1, *w2];
466    let (L, R) = self.split_at(self.ck.len() / 2);
467
468    let ck = (0..self.ck.len() / 2)
469      .into_par_iter()
470      .map(|i| {
471        let bases = [L.ck[i], R.ck[i]].to_vec();
472        E::GE::vartime_multiscalar_mul(&w, &bases).affine()
473      })
474      .collect();
475
476    CommitmentKey { ck, h: self.h }
477  }
478
479  /// Scales each element in `self` by `r`
480  fn scale(&self, r: &E::Scalar) -> Self {
481    let ck_scaled = self
482      .ck
483      .clone()
484      .into_par_iter()
485      .map(|g| E::GE::vartime_multiscalar_mul(&[*r], &[g]).affine())
486      .collect();
487
488    CommitmentKey {
489      ck: ck_scaled,
490      h: self.h,
491    }
492  }
493
494  /// reinterprets a vector of commitments as a set of generators
495  fn reinterpret_commitments_as_ck(c: &[Commitment<E>]) -> Result<Self, NovaError> {
496    let ck = (0..c.len())
497      .into_par_iter()
498      .map(|i| c[i].comm.affine())
499      .collect();
500
501    // cmt is derandomized by the point that this is called
502    Ok(CommitmentKey {
503      ck,
504      h: E::GE::zero().affine(), // this is okay, since this method is used in IPA only,
505                                 // and we only use non-blinding commits afterwards
506                                 // bc we don't use ZK IPA
507    })
508  }
509}
510
511#[cfg(feature = "io")]
512#[cfg(test)]
513mod tests {
514  use super::*;
515
516  use crate::{provider::GrumpkinEngine, CommitmentKey};
517  use std::{fs::File, io::BufWriter};
518
519  type E = GrumpkinEngine;
520
521  #[test]
522  fn test_key_save_load() {
523    let path = "/tmp/pedersen_test.keys";
524
525    const LABEL: &[u8; 4] = b"test";
526
527    let keys = CommitmentEngine::<E>::setup(LABEL, 100).unwrap();
528
529    CommitmentEngine::save_setup(&keys, &mut BufWriter::new(File::create(path).unwrap())).unwrap();
530
531    let keys_read = CommitmentEngine::load_setup(&mut File::open(path).unwrap(), LABEL, 100);
532
533    assert!(keys_read.is_ok());
534    let keys_read: CommitmentKey<E> = keys_read.unwrap();
535    assert_eq!(keys_read.ck.len(), keys.ck.len());
536    assert_eq!(keys_read.h, keys.h);
537    assert_eq!(keys_read.ck, keys.ck);
538  }
539}