Skip to main content

sonobe_primitives/commitments/
pedersen.rs

1//! Implementation of the Pedersen commitment scheme, including out-of-circuit
2//! widgets and in-circuit gadgets.
3//!
4//! The Pedersen commitment to a vector `v` is computed as `<g, v> + h ยท r`,
5//! where `g` and `h` are generators, `r` is a random scalar, and `<g, v>` is
6//! the multi-scalar multiplication of `g` and `v`.
7
8use ark_ec::AffineRepr;
9use ark_r1cs_std::{
10    alloc::{AllocVar, AllocationMode},
11    boolean::Boolean,
12    convert::ToBitsGadget,
13    eq::EqGadget,
14    fields::fp::FpVar,
15    groups::CurveVar,
16};
17use ark_relations::gr1cs::{Namespace, SynthesisError};
18use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
19use ark_std::{UniformRand, borrow::Borrow, iter::repeat_with, marker::PhantomData, rand::RngCore};
20
21use super::{CommitmentDef, CommitmentDefGadget, CommitmentKey, CommitmentOps, Error};
22use crate::{
23    algebra::{field::emulated::EmulatedFieldVar, group::emulated::EmulatedAffineVar},
24    commitments::{CommitmentOpsGadget, GroupBasedCommitment},
25    traits::{CF1, CF2, SonobeCurve},
26    utils::null::Null,
27};
28
29/// [`PedersenKey`] stores the public parameters for the Pedersen commitment
30/// scheme, where `H` controls whether the scheme is hiding or not.
31#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)]
32pub struct PedersenKey<C: SonobeCurve, const H: bool> {
33    g: Vec<C::Affine>,
34    h: C,
35}
36
37impl<C: SonobeCurve, const H: bool> CommitmentKey for PedersenKey<C, H> {
38    fn max_scalars_len(&self) -> usize {
39        self.g.len()
40    }
41}
42
43impl<C: SonobeCurve, const H: bool> PedersenKey<C, H> {
44    fn new(len: usize, mut rng: impl RngCore) -> Self {
45        let generators = repeat_with(|| C::rand(&mut rng))
46            .take(len.next_power_of_two())
47            .collect::<Vec<_>>();
48        Self {
49            g: C::normalize_batch(&generators),
50            h: if H { C::rand(&mut rng) } else { C::zero() },
51        }
52    }
53}
54
55impl<C: SonobeCurve> PedersenKey<C, true> {
56    fn commit(&self, v: &[C::ScalarField], r: &C::ScalarField) -> Result<C, Error> {
57        if self.g.len() < v.len() {
58            return Err(Error::MessageTooLong(self.g.len(), v.len()));
59        }
60        // <g, v> + h * r
61        // use msm_unchecked because we already ensured at the if that generators are long enough
62        Ok(C::msm_unchecked(&self.g, v) + self.h.mul(r))
63    }
64}
65
66impl<C: SonobeCurve> PedersenKey<C, false> {
67    fn commit(&self, v: &[C::ScalarField]) -> Result<C, Error> {
68        if self.g.len() < v.len() {
69            return Err(Error::MessageTooLong(self.g.len(), v.len()));
70        }
71        // <g, v>
72        // use msm_unchecked because we already ensured at the if that generators are long enough
73        Ok(C::msm_unchecked(&self.g, v))
74    }
75}
76
77/// [`PedersenKeyVar`] is the in-circuit variable for [`PedersenKey`], whose
78/// generators are encoded in the canonical form.
79pub struct PedersenKeyVar<C: SonobeCurve, const H: bool> {
80    g: Vec<C::Var>,
81    h: C::Var,
82}
83
84/// [`PedersenEmulatedKeyVar`] is the in-circuit variable for [`PedersenKey`],
85/// whose generators are encoded in the emulated form.
86pub struct PedersenEmulatedKeyVar<C: SonobeCurve, const H: bool> {
87    #[allow(dead_code)]
88    g: Vec<EmulatedAffineVar<CF1<C>, C>>,
89    #[allow(dead_code)]
90    h: EmulatedAffineVar<CF1<C>, C>,
91}
92
93impl<C: SonobeCurve, const H: bool> AllocVar<PedersenKey<C, H>, C::BaseField>
94    for PedersenKeyVar<C, H>
95{
96    fn new_variable<T: Borrow<PedersenKey<C, H>>>(
97        cs: impl Into<Namespace<C::BaseField>>,
98        f: impl FnOnce() -> Result<T, SynthesisError>,
99        mode: AllocationMode,
100    ) -> Result<Self, SynthesisError> {
101        let cs = cs.into().cs();
102        let v = f()?;
103        let PedersenKey { g, h } = v.borrow();
104
105        Ok(Self {
106            g: AllocVar::new_variable(cs.clone(), || Ok(&g[..]), mode)?,
107            h: AllocVar::new_variable(cs.clone(), || Ok(*h), mode)?,
108        })
109    }
110}
111
112impl<C: SonobeCurve, const H: bool> AllocVar<PedersenKey<C, H>, CF1<C>>
113    for PedersenEmulatedKeyVar<C, H>
114{
115    fn new_variable<T: Borrow<PedersenKey<C, H>>>(
116        cs: impl Into<Namespace<CF1<C>>>,
117        f: impl FnOnce() -> Result<T, SynthesisError>,
118        mode: AllocationMode,
119    ) -> Result<Self, SynthesisError> {
120        let cs = cs.into().cs();
121        let v = f()?;
122        let PedersenKey { g, h } = v.borrow();
123
124        Ok(Self {
125            g: AllocVar::new_variable(
126                cs.clone(),
127                || Ok(g.iter().map(|i| i.into_group()).collect::<Vec<_>>()),
128                mode,
129            )?,
130            h: AllocVar::new_variable(cs.clone(), || Ok(*h), mode)?,
131        })
132    }
133}
134
135/// [`Pedersen`] defines the out-of-circuit Pedersen widget, where `H` controls
136/// whether the scheme is hiding or not.
137#[derive(Clone, Debug, PartialEq, Eq)]
138pub struct Pedersen<C: SonobeCurve, const H: bool> {
139    _c: PhantomData<C>,
140}
141
142impl<C: SonobeCurve> CommitmentDef for Pedersen<C, false> {
143    const IS_HIDING: bool = false;
144
145    type Key = PedersenKey<C, false>;
146    type Scalar = C::ScalarField;
147    type Commitment = C;
148    type Randomness = Null;
149}
150
151impl<C: SonobeCurve> CommitmentDef for Pedersen<C, true> {
152    const IS_HIDING: bool = true;
153
154    type Key = PedersenKey<C, true>;
155    type Scalar = C::ScalarField;
156    type Commitment = C;
157    type Randomness = C::ScalarField;
158}
159
160impl<C: SonobeCurve> GroupBasedCommitment for Pedersen<C, false> {
161    type Gadget1 = PedersenGadget<C, false>;
162    type Gadget2 = PedersenEmulatedGadget<C, false>;
163}
164
165impl<C: SonobeCurve> GroupBasedCommitment for Pedersen<C, true> {
166    type Gadget1 = PedersenGadget<C, true>;
167    type Gadget2 = PedersenEmulatedGadget<C, true>;
168}
169
170impl<C: SonobeCurve> CommitmentOps for Pedersen<C, false> {
171    fn generate_key(len: usize, rng: impl RngCore) -> Result<PedersenKey<C, false>, Error> {
172        Ok(PedersenKey::new(len, rng))
173    }
174
175    fn commit(
176        ck: &PedersenKey<C, false>,
177        v: &[CF1<C>],
178        _rng: impl RngCore,
179    ) -> Result<(C, Null), Error> {
180        Ok((ck.commit(v)?, Null))
181    }
182
183    fn open(ck: &PedersenKey<C, false>, v: &[CF1<C>], _r: &Null, cm: &C) -> Result<(), Error> {
184        (&ck.commit(v)? == cm)
185            .then_some(())
186            .ok_or(Error::CommitmentVerificationFail)
187    }
188}
189
190impl<C: SonobeCurve> CommitmentOps for Pedersen<C, true> {
191    fn generate_key(len: usize, rng: impl RngCore) -> Result<PedersenKey<C, true>, Error> {
192        Ok(PedersenKey::new(len, rng))
193    }
194
195    fn commit(
196        ck: &PedersenKey<C, true>,
197        v: &[CF1<C>],
198        mut rng: impl RngCore,
199    ) -> Result<(C, CF1<C>), Error> {
200        let r = C::ScalarField::rand(&mut rng);
201        Ok((ck.commit(v, &r)?, r))
202    }
203
204    fn open(ck: &PedersenKey<C, true>, v: &[CF1<C>], r: &CF1<C>, cm: &C) -> Result<(), Error> {
205        (&(ck.commit(v, r)?) == cm)
206            .then_some(())
207            .ok_or(Error::CommitmentVerificationFail)
208    }
209}
210
211/// [`PedersenGadget`] defines the in-circuit Pedersen gadget that operates over
212/// the base field of the curve and supports canonical elliptic curve point
213/// variables as commitments, where `H` controls whether the scheme is hiding or
214/// not.
215#[derive(Clone)]
216pub struct PedersenGadget<C: SonobeCurve, const H: bool> {
217    _c: PhantomData<C>,
218}
219
220impl<C: SonobeCurve, const H: bool> PedersenGadget<C, H> {
221    /// [`PedersenGadget::msm`] performs multi-scalar multiplication in-circuit
222    /// with the given generators `g` and scalar bits `v`.
223    fn msm(g: &[C::Var], v: &[Vec<Boolean<CF2<C>>>]) -> Result<C::Var, SynthesisError> {
224        let mut res = C::Var::zero();
225        for (g_i, v_i) in g.iter().zip(v) {
226            res += g_i.scalar_mul_le(v_i.to_bits_le()?.iter())?;
227        }
228        Ok(res)
229    }
230}
231
232impl<C: SonobeCurve> CommitmentOpsGadget for PedersenGadget<C, false> {
233    fn open(
234        ck: &PedersenKeyVar<C, false>,
235        v: &[EmulatedFieldVar<CF2<C>, CF1<C>>],
236        _r: &Null,
237        cm: &C::Var,
238    ) -> Result<(), SynthesisError> {
239        Self::msm(
240            &ck.g,
241            &v.iter()
242                .map(|i| i.to_bits_le())
243                .collect::<Result<Vec<_>, _>>()?,
244        )?
245        .enforce_equal(cm)
246    }
247}
248
249impl<C: SonobeCurve> CommitmentOpsGadget for PedersenGadget<C, true> {
250    fn open(
251        ck: &PedersenKeyVar<C, true>,
252        v: &[EmulatedFieldVar<CF2<C>, CF1<C>>],
253        r: &EmulatedFieldVar<CF2<C>, CF1<C>>,
254        cm: &C::Var,
255    ) -> Result<(), SynthesisError> {
256        let gv = Self::msm(
257            &ck.g,
258            &v.iter()
259                .map(|i| i.to_bits_le())
260                .collect::<Result<Vec<_>, _>>()?,
261        )?;
262        let hr = ck.h.scalar_mul_le(r.to_bits_le()?.iter())?;
263        (gv + hr).enforce_equal(cm)
264    }
265}
266
267/// [`PedersenEmulatedGadget`] defines the in-circuit Pedersen gadget that
268/// operates over the scalar field of the curve and supports emulated elliptic
269/// curve point variables as commitments, where `H` controls whether the scheme
270/// is hiding or not.
271#[derive(Clone, Debug, PartialEq, Eq)]
272pub struct PedersenEmulatedGadget<C: SonobeCurve, const H: bool> {
273    _c: PhantomData<C>,
274}
275
276impl<C: SonobeCurve> CommitmentDefGadget for PedersenGadget<C, false> {
277    type ConstraintField = CF2<C>;
278
279    type KeyVar = PedersenKeyVar<C, false>;
280
281    type ScalarVar = EmulatedFieldVar<CF2<C>, CF1<C>>;
282
283    type CommitmentVar = C::Var;
284
285    type RandomnessVar = Null;
286
287    type Widget = Pedersen<C, false>;
288}
289
290impl<C: SonobeCurve> CommitmentDefGadget for PedersenGadget<C, true> {
291    type ConstraintField = CF2<C>;
292
293    type KeyVar = PedersenKeyVar<C, true>;
294
295    type ScalarVar = EmulatedFieldVar<CF2<C>, CF1<C>>;
296
297    type CommitmentVar = C::Var;
298
299    type RandomnessVar = EmulatedFieldVar<CF2<C>, CF1<C>>;
300
301    type Widget = Pedersen<C, true>;
302}
303
304impl<C: SonobeCurve> CommitmentDefGadget for PedersenEmulatedGadget<C, false> {
305    type ConstraintField = CF1<C>;
306
307    type KeyVar = PedersenEmulatedKeyVar<C, false>;
308
309    type ScalarVar = FpVar<CF1<C>>;
310
311    type CommitmentVar = EmulatedAffineVar<CF1<C>, C>;
312
313    type RandomnessVar = Null;
314
315    type Widget = Pedersen<C, false>;
316}
317
318impl<C: SonobeCurve> CommitmentDefGadget for PedersenEmulatedGadget<C, true> {
319    type ConstraintField = CF1<C>;
320
321    type KeyVar = PedersenEmulatedKeyVar<C, true>;
322
323    type ScalarVar = FpVar<CF1<C>>;
324
325    type CommitmentVar = EmulatedAffineVar<CF1<C>, C>;
326
327    type RandomnessVar = FpVar<CF1<C>>;
328
329    type Widget = Pedersen<C, true>;
330}
331
332#[cfg(test)]
333mod tests {
334    use ark_bn254::G1Projective;
335    use ark_std::{
336        error::Error,
337        rand::{Rng, thread_rng},
338    };
339    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
340    use wasm_bindgen_test::wasm_bindgen_test as test;
341
342    use super::*;
343    use crate::commitments::tests::{
344        test_commitment_correctness, test_commitment_gadget_correctness,
345    };
346
347    #[test]
348    fn test_pedersen_commitment() -> Result<(), Box<dyn Error>> {
349        let mut rng = thread_rng();
350        for i in 0..10 {
351            let len = rng.gen_range((1 << i)..(1 << (i + 1)));
352            test_commitment_correctness::<Pedersen<G1Projective, false>>(&mut rng, len)?;
353            test_commitment_correctness::<Pedersen<G1Projective, true>>(&mut rng, len)?;
354        }
355        Ok(())
356    }
357
358    #[test]
359    fn test_pedersen_commitment_circuit() -> Result<(), Box<dyn Error>> {
360        let mut rng = thread_rng();
361        for i in 0..5 {
362            let len = rng.gen_range((1 << i)..(1 << (i + 1)));
363            test_commitment_gadget_correctness::<PedersenGadget<G1Projective, false>>(
364                &mut rng, len,
365            )?;
366            test_commitment_gadget_correctness::<PedersenGadget<G1Projective, true>>(
367                &mut rng, len,
368            )?;
369        }
370        Ok(())
371    }
372}