halo2_axiom/poly/ipa/
commitment.rs

1//! This module contains an implementation of the polynomial commitment scheme
2//! described in the [Halo][halo] paper.
3//!
4//! [halo]: https://eprint.iacr.org/2019/1021
5
6use crate::arithmetic::{best_multiexp, g_to_lagrange, parallelize, CurveAffine, CurveExt};
7use crate::helpers::CurveRead;
8use crate::poly::commitment::{Blind, CommitmentScheme, Params, ParamsProver, ParamsVerifier};
9use crate::poly::ipa::msm::MSMIPA;
10use crate::poly::{Coeff, LagrangeCoeff, Polynomial};
11
12use group::{Curve, Group};
13use std::marker::PhantomData;
14
15mod prover;
16mod verifier;
17
18pub use prover::create_proof;
19pub use verifier::verify_proof;
20
21use std::io;
22
23/// Public parameters for IPA commitment scheme
24#[derive(Debug, Clone)]
25pub struct ParamsIPA<C: CurveAffine> {
26    pub(crate) k: u32,
27    pub(crate) n: u64,
28    pub(crate) g: Vec<C>,
29    pub(crate) g_lagrange: Vec<C>,
30    pub(crate) w: C,
31    pub(crate) u: C,
32}
33
34/// Concrete IPA commitment scheme
35#[derive(Debug)]
36pub struct IPACommitmentScheme<C: CurveAffine> {
37    _marker: PhantomData<C>,
38}
39
40impl<C: CurveAffine> CommitmentScheme for IPACommitmentScheme<C> {
41    type Scalar = C::ScalarExt;
42    type Curve = C;
43
44    type ParamsProver = ParamsIPA<C>;
45    type ParamsVerifier = ParamsVerifierIPA<C>;
46
47    fn new_params(k: u32) -> Self::ParamsProver {
48        ParamsIPA::new(k)
49    }
50
51    fn read_params<R: io::Read>(reader: &mut R) -> io::Result<Self::ParamsProver> {
52        ParamsIPA::read(reader)
53    }
54}
55
56/// Verifier parameters
57pub type ParamsVerifierIPA<C> = ParamsIPA<C>;
58
59impl<'params, C: CurveAffine> ParamsVerifier<'params, C> for ParamsIPA<C> {}
60
61impl<'params, C: CurveAffine> Params<'params, C> for ParamsIPA<C> {
62    type MSM = MSMIPA<'params, C>;
63
64    fn k(&self) -> u32 {
65        self.k
66    }
67
68    fn n(&self) -> u64 {
69        self.n
70    }
71
72    fn downsize(&mut self, k: u32) {
73        assert!(k <= self.k);
74
75        self.k = k;
76        self.n = 1 << k;
77        self.g.truncate(self.n as usize);
78        self.g_lagrange = g_to_lagrange(self.g.iter().map(|g| g.to_curve()).collect(), k);
79    }
80
81    fn empty_msm(&'params self) -> MSMIPA<C> {
82        MSMIPA::new(self)
83    }
84
85    /// This commits to a polynomial using its evaluations over the $2^k$ size
86    /// evaluation domain. The commitment will be blinded by the blinding factor
87    /// `r`.
88    fn commit_lagrange(
89        &self,
90        poly: &Polynomial<C::Scalar, LagrangeCoeff>,
91        r: Blind<C::Scalar>,
92    ) -> C::Curve {
93        let mut tmp_scalars = Vec::with_capacity(poly.len() + 1);
94        let mut tmp_bases = Vec::with_capacity(poly.len() + 1);
95
96        tmp_scalars.extend(poly.iter());
97        tmp_scalars.push(r.0);
98
99        tmp_bases.extend(self.g_lagrange.iter());
100        tmp_bases.push(self.w);
101
102        best_multiexp::<C>(&tmp_scalars, &tmp_bases)
103    }
104
105    /// Writes params to a buffer.
106    fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
107        writer.write_all(&self.k.to_le_bytes())?;
108        for g_element in &self.g {
109            writer.write_all(g_element.to_bytes().as_ref())?;
110        }
111        for g_lagrange_element in &self.g_lagrange {
112            writer.write_all(g_lagrange_element.to_bytes().as_ref())?;
113        }
114        writer.write_all(self.w.to_bytes().as_ref())?;
115        writer.write_all(self.u.to_bytes().as_ref())?;
116
117        Ok(())
118    }
119
120    /// Reads params from a buffer.
121    fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
122        let mut k = [0u8; 4];
123        reader.read_exact(&mut k[..])?;
124        let k = u32::from_le_bytes(k);
125
126        let n: u64 = 1 << k;
127
128        let g: Vec<_> = (0..n).map(|_| C::read(reader)).collect::<Result<_, _>>()?;
129        let g_lagrange: Vec<_> = (0..n).map(|_| C::read(reader)).collect::<Result<_, _>>()?;
130
131        let w = C::read(reader)?;
132        let u = C::read(reader)?;
133
134        Ok(Self {
135            k,
136            n,
137            g,
138            g_lagrange,
139            w,
140            u,
141        })
142    }
143}
144
145impl<'params, C: CurveAffine> ParamsProver<'params, C> for ParamsIPA<C> {
146    type ParamsVerifier = ParamsVerifierIPA<C>;
147
148    fn verifier_params(&'params self) -> &'params Self::ParamsVerifier {
149        self
150    }
151
152    /// Initializes parameters for the curve, given a random oracle to draw
153    /// points from.
154    fn new(k: u32) -> Self {
155        // This is usually a limitation on the curve, but we also want 32-bit
156        // architectures to be supported.
157        assert!(k < 32);
158
159        // In src/arithmetic/fields.rs we ensure that usize is at least 32 bits.
160
161        let n: u64 = 1 << k;
162
163        let g_projective = {
164            let mut g = Vec::with_capacity(n as usize);
165            g.resize(n as usize, C::Curve::identity());
166
167            parallelize(&mut g, move |g, start| {
168                let hasher = C::CurveExt::hash_to_curve("Halo2-Parameters");
169
170                for (i, g) in g.iter_mut().enumerate() {
171                    let i = (i + start) as u32;
172
173                    let mut message = [0u8; 5];
174                    message[1..5].copy_from_slice(&i.to_le_bytes());
175
176                    *g = hasher(&message);
177                }
178            });
179
180            g
181        };
182
183        let g = {
184            let mut g = vec![C::identity(); n as usize];
185            parallelize(&mut g, |g, starts| {
186                C::Curve::batch_normalize(&g_projective[starts..(starts + g.len())], g);
187            });
188            g
189        };
190
191        // Let's evaluate all of the Lagrange basis polynomials
192        // using an inverse FFT.
193        let g_lagrange = g_to_lagrange(g_projective, k);
194
195        let hasher = C::CurveExt::hash_to_curve("Halo2-Parameters");
196        let w = hasher(&[1]).to_affine();
197        let u = hasher(&[2]).to_affine();
198
199        ParamsIPA {
200            k,
201            n,
202            g,
203            g_lagrange,
204            w,
205            u,
206        }
207    }
208
209    /// This computes a commitment to a polynomial described by the provided
210    /// slice of coefficients. The commitment will be blinded by the blinding
211    /// factor `r`.
212    fn commit(&self, poly: &Polynomial<C::Scalar, Coeff>, r: Blind<C::Scalar>) -> C::Curve {
213        let mut tmp_scalars = Vec::with_capacity(poly.len() + 1);
214        let mut tmp_bases = Vec::with_capacity(poly.len() + 1);
215
216        tmp_scalars.extend(poly.iter());
217        tmp_scalars.push(r.0);
218
219        tmp_bases.extend(self.g.iter());
220        tmp_bases.push(self.w);
221
222        best_multiexp::<C>(&tmp_scalars, &tmp_bases)
223    }
224
225    fn get_g(&self) -> &[C] {
226        &self.g
227    }
228}
229
230#[cfg(test)]
231mod test {
232    use crate::poly::commitment::ParamsProver;
233    use crate::poly::commitment::{Blind, Params, MSM};
234    use crate::poly::ipa::commitment::{create_proof, verify_proof, ParamsIPA};
235    use crate::poly::ipa::msm::MSMIPA;
236
237    use ff::Field;
238    use group::Curve;
239
240    #[test]
241    fn test_commit_lagrange_epaffine() {
242        const K: u32 = 6;
243
244        use rand_core::OsRng;
245
246        use crate::poly::EvaluationDomain;
247        use halo2curves::pasta::{EpAffine, Fq};
248
249        let params = ParamsIPA::<EpAffine>::new(K);
250        let domain = EvaluationDomain::new(1, K);
251
252        let mut a = domain.empty_lagrange();
253
254        for (i, a) in a.iter_mut().enumerate() {
255            *a = Fq::from(i as u64);
256        }
257
258        let b = domain.lagrange_to_coeff(a.clone());
259
260        let alpha = Blind(Fq::random(OsRng));
261
262        assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha));
263    }
264
265    #[test]
266    fn test_commit_lagrange_eqaffine() {
267        const K: u32 = 6;
268
269        use rand_core::OsRng;
270
271        use crate::poly::EvaluationDomain;
272        use halo2curves::pasta::{EqAffine, Fp};
273
274        let params: ParamsIPA<EqAffine> = ParamsIPA::<EqAffine>::new(K);
275        let domain = EvaluationDomain::new(1, K);
276
277        let mut a = domain.empty_lagrange();
278
279        for (i, a) in a.iter_mut().enumerate() {
280            *a = Fp::from(i as u64);
281        }
282
283        let b = domain.lagrange_to_coeff(a.clone());
284
285        let alpha = Blind(Fp::random(OsRng));
286
287        assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha));
288    }
289
290    #[test]
291    fn test_opening_proof() {
292        const K: u32 = 6;
293
294        use ff::Field;
295        use rand_core::OsRng;
296
297        use super::super::commitment::{Blind, Params};
298        use crate::arithmetic::eval_polynomial;
299        use crate::halo2curves::pasta::{EpAffine, Fq};
300        use crate::poly::EvaluationDomain;
301        use crate::transcript::{
302            Blake2bRead, Blake2bWrite, Challenge255, Transcript, TranscriptRead, TranscriptWrite,
303        };
304
305        use crate::transcript::TranscriptReadBuffer;
306        use crate::transcript::TranscriptWriterBuffer;
307
308        let rng = OsRng;
309
310        let params = ParamsIPA::<EpAffine>::new(K);
311        let mut params_buffer = vec![];
312        <ParamsIPA<_> as Params<_>>::write(&params, &mut params_buffer).unwrap();
313        let params: ParamsIPA<EpAffine> = Params::read::<_>(&mut &params_buffer[..]).unwrap();
314
315        let domain = EvaluationDomain::new(1, K);
316
317        let mut px = domain.empty_coeff();
318
319        for (i, a) in px.iter_mut().enumerate() {
320            *a = Fq::from(i as u64);
321        }
322
323        let blind = Blind(Fq::random(rng));
324
325        let p = params.commit(&px, blind).to_affine();
326
327        let mut transcript =
328            Blake2bWrite::<Vec<u8>, EpAffine, Challenge255<EpAffine>>::init(vec![]);
329        transcript.write_point(p).unwrap();
330        let x = transcript.squeeze_challenge_scalar::<()>();
331        // Evaluate the polynomial
332        let v = eval_polynomial(&px, *x);
333        transcript.write_scalar(v).unwrap();
334
335        let (proof, ch_prover) = {
336            create_proof(&params, rng, &mut transcript, &px, blind, *x).unwrap();
337            let ch_prover = transcript.squeeze_challenge();
338            (transcript.finalize(), ch_prover)
339        };
340
341        // Verify the opening proof
342        let mut transcript =
343            Blake2bRead::<&[u8], EpAffine, Challenge255<EpAffine>>::init(&proof[..]);
344        let p_prime = transcript.read_point().unwrap();
345        assert_eq!(p, p_prime);
346        let x_prime = transcript.squeeze_challenge_scalar::<()>();
347        assert_eq!(*x, *x_prime);
348        let v_prime = transcript.read_scalar().unwrap();
349        assert_eq!(v, v_prime);
350
351        let mut commitment_msm = MSMIPA::new(&params);
352        commitment_msm.append_term(Fq::one(), p.into());
353
354        let guard = verify_proof(&params, commitment_msm, &mut transcript, *x, v).unwrap();
355        let ch_verifier = transcript.squeeze_challenge();
356        assert_eq!(*ch_prover, *ch_verifier);
357
358        // Test guard behavior prior to checking another proof
359        {
360            // Test use_challenges()
361            let msm_challenges = guard.clone().use_challenges();
362            assert!(msm_challenges.check());
363
364            // Test use_g()
365            let g = guard.compute_g();
366            let (msm_g, _accumulator) = guard.clone().use_g(g);
367            assert!(msm_g.check());
368        }
369    }
370}