halo2_proofs/poly/
multiopen.rs

1//! This module contains an optimisation of the polynomial commitment opening
2//! scheme described in the [Halo][halo] paper.
3//!
4//! [halo]: https://eprint.iacr.org/2019/1021
5
6use std::collections::{BTreeMap, BTreeSet};
7
8use super::*;
9use crate::{
10    arithmetic::{CurveAffine, FieldExt},
11    transcript::ChallengeScalar,
12};
13
14mod prover;
15mod verifier;
16
17pub use prover::create_proof;
18pub use verifier::verify_proof;
19
20#[derive(Clone, Copy, Debug)]
21struct X1 {}
22/// Challenge for compressing openings at the same point sets together.
23type ChallengeX1<F> = ChallengeScalar<F, X1>;
24
25#[derive(Clone, Copy, Debug)]
26struct X2 {}
27/// Challenge for keeping the multi-point quotient polynomial terms linearly independent.
28type ChallengeX2<F> = ChallengeScalar<F, X2>;
29
30#[derive(Clone, Copy, Debug)]
31struct X3 {}
32/// Challenge point at which the commitments are opened.
33type ChallengeX3<F> = ChallengeScalar<F, X3>;
34
35#[derive(Clone, Copy, Debug)]
36struct X4 {}
37/// Challenge for collapsing the openings of the various remaining polynomials at x_3
38/// together.
39type ChallengeX4<F> = ChallengeScalar<F, X4>;
40
41/// A polynomial query at a point
42#[derive(Debug, Clone)]
43pub struct ProverQuery<'a, C: CurveAffine> {
44    /// point at which polynomial is queried
45    pub point: C::Scalar,
46    /// coefficients of polynomial
47    pub poly: &'a Polynomial<C::Scalar, Coeff>,
48    /// blinding factor of polynomial
49    pub blind: commitment::Blind<C::Scalar>,
50}
51
52/// A polynomial query at a point
53#[derive(Debug, Clone)]
54pub struct VerifierQuery<'r, 'params: 'r, C: CurveAffine> {
55    /// point at which polynomial is queried
56    point: C::Scalar,
57    /// commitment to polynomial
58    commitment: CommitmentReference<'r, 'params, C>,
59    /// evaluation of polynomial at query point
60    eval: C::Scalar,
61}
62
63impl<'r, 'params: 'r, C: CurveAffine> VerifierQuery<'r, 'params, C> {
64    /// Create a new verifier query based on a commitment
65    pub fn new_commitment(commitment: &'r C, point: C::Scalar, eval: C::Scalar) -> Self {
66        VerifierQuery {
67            point,
68            eval,
69            commitment: CommitmentReference::Commitment(commitment),
70        }
71    }
72
73    /// Create a new verifier query based on a linear combination of commitments
74    pub fn new_msm(
75        msm: &'r commitment::MSM<'params, C>,
76        point: C::Scalar,
77        eval: C::Scalar,
78    ) -> Self {
79        VerifierQuery {
80            point,
81            eval,
82            commitment: CommitmentReference::MSM(msm),
83        }
84    }
85}
86
87#[derive(Copy, Clone, Debug)]
88enum CommitmentReference<'r, 'params: 'r, C: CurveAffine> {
89    Commitment(&'r C),
90    MSM(&'r commitment::MSM<'params, C>),
91}
92
93impl<'r, 'params: 'r, C: CurveAffine> PartialEq for CommitmentReference<'r, 'params, C> {
94    fn eq(&self, other: &Self) -> bool {
95        match (self, other) {
96            (&CommitmentReference::Commitment(a), &CommitmentReference::Commitment(b)) => {
97                std::ptr::eq(a, b)
98            }
99            (&CommitmentReference::MSM(a), &CommitmentReference::MSM(b)) => std::ptr::eq(a, b),
100            _ => false,
101        }
102    }
103}
104
105#[derive(Debug)]
106struct CommitmentData<F, T: PartialEq> {
107    commitment: T,
108    set_index: usize,
109    point_indices: Vec<usize>,
110    evals: Vec<F>,
111}
112
113impl<F, T: PartialEq> CommitmentData<F, T> {
114    fn new(commitment: T) -> Self {
115        CommitmentData {
116            commitment,
117            set_index: 0,
118            point_indices: vec![],
119            evals: vec![],
120        }
121    }
122}
123
124trait Query<F>: Sized {
125    type Commitment: PartialEq + Copy;
126    type Eval: Clone + Default;
127
128    fn get_point(&self) -> F;
129    fn get_eval(&self) -> Self::Eval;
130    fn get_commitment(&self) -> Self::Commitment;
131}
132
133type IntermediateSets<F, Q> = (
134    Vec<CommitmentData<<Q as Query<F>>::Eval, <Q as Query<F>>::Commitment>>,
135    Vec<Vec<F>>,
136);
137
138fn construct_intermediate_sets<F: FieldExt, I, Q: Query<F>>(queries: I) -> IntermediateSets<F, Q>
139where
140    I: IntoIterator<Item = Q> + Clone,
141{
142    // Construct sets of unique commitments and corresponding information about
143    // their queries.
144    let mut commitment_map: Vec<CommitmentData<Q::Eval, Q::Commitment>> = vec![];
145
146    // Also construct mapping from a unique point to a point_index. This defines
147    // an ordering on the points.
148    let mut point_index_map = BTreeMap::new();
149
150    // Iterate over all of the queries, computing the ordering of the points
151    // while also creating new commitment data.
152    for query in queries.clone() {
153        let num_points = point_index_map.len();
154        let point_idx = point_index_map
155            .entry(query.get_point())
156            .or_insert(num_points);
157
158        if let Some(pos) = commitment_map
159            .iter()
160            .position(|comm| comm.commitment == query.get_commitment())
161        {
162            commitment_map[pos].point_indices.push(*point_idx);
163        } else {
164            let mut tmp = CommitmentData::new(query.get_commitment());
165            tmp.point_indices.push(*point_idx);
166            commitment_map.push(tmp);
167        }
168    }
169
170    // Also construct inverse mapping from point_index to the point
171    let mut inverse_point_index_map = BTreeMap::new();
172    for (&point, &point_index) in point_index_map.iter() {
173        inverse_point_index_map.insert(point_index, point);
174    }
175
176    // Construct map of unique ordered point_idx_sets to their set_idx
177    let mut point_idx_sets = BTreeMap::new();
178    // Also construct mapping from commitment to point_idx_set
179    let mut commitment_set_map = Vec::new();
180
181    for commitment_data in commitment_map.iter() {
182        let mut point_index_set = BTreeSet::new();
183        // Note that point_index_set is ordered, unlike point_indices
184        for &point_index in commitment_data.point_indices.iter() {
185            point_index_set.insert(point_index);
186        }
187
188        // Push point_index_set to CommitmentData for the relevant commitment
189        commitment_set_map.push((commitment_data.commitment, point_index_set.clone()));
190
191        let num_sets = point_idx_sets.len();
192        point_idx_sets.entry(point_index_set).or_insert(num_sets);
193    }
194
195    // Initialise empty evals vec for each unique commitment
196    for commitment_data in commitment_map.iter_mut() {
197        let len = commitment_data.point_indices.len();
198        commitment_data.evals = vec![Q::Eval::default(); len];
199    }
200
201    // Populate set_index, evals and points for each commitment using point_idx_sets
202    for query in queries {
203        // The index of the point at which the commitment is queried
204        let point_index = point_index_map.get(&query.get_point()).unwrap();
205
206        // The point_index_set at which the commitment was queried
207        let mut point_index_set = BTreeSet::new();
208        for (commitment, point_idx_set) in commitment_set_map.iter() {
209            if query.get_commitment() == *commitment {
210                point_index_set = point_idx_set.clone();
211            }
212        }
213        assert!(!point_index_set.is_empty());
214
215        // The set_index of the point_index_set
216        let set_index = point_idx_sets.get(&point_index_set).unwrap();
217        for commitment_data in commitment_map.iter_mut() {
218            if query.get_commitment() == commitment_data.commitment {
219                commitment_data.set_index = *set_index;
220            }
221        }
222        let point_index_set: Vec<usize> = point_index_set.iter().cloned().collect();
223
224        // The offset of the point_index in the point_index_set
225        let point_index_in_set = point_index_set
226            .iter()
227            .position(|i| i == point_index)
228            .unwrap();
229
230        for commitment_data in commitment_map.iter_mut() {
231            if query.get_commitment() == commitment_data.commitment {
232                // Insert the eval using the ordering of the point_index_set
233                commitment_data.evals[point_index_in_set] = query.get_eval();
234            }
235        }
236    }
237
238    // Get actual points in each point set
239    let mut point_sets: Vec<Vec<F>> = vec![Vec::new(); point_idx_sets.len()];
240    for (point_idx_set, &set_idx) in point_idx_sets.iter() {
241        for &point_idx in point_idx_set.iter() {
242            let point = inverse_point_index_map.get(&point_idx).unwrap();
243            point_sets[set_idx].push(*point);
244        }
245    }
246
247    (commitment_map, point_sets)
248}
249
250#[test]
251fn test_roundtrip() {
252    use group::Curve;
253    use rand_core::OsRng;
254
255    use super::commitment::{Blind, Params};
256    use crate::arithmetic::{eval_polynomial, FieldExt};
257    use crate::pasta::{EqAffine, Fp};
258    use crate::transcript::Challenge255;
259
260    const K: u32 = 4;
261
262    let params: Params<EqAffine> = Params::new(K);
263    let domain = EvaluationDomain::new(1, K);
264    let rng = OsRng;
265
266    let mut ax = domain.empty_coeff();
267    for (i, a) in ax.iter_mut().enumerate() {
268        *a = Fp::from(10 + i as u64);
269    }
270
271    let mut bx = domain.empty_coeff();
272    for (i, a) in bx.iter_mut().enumerate() {
273        *a = Fp::from(100 + i as u64);
274    }
275
276    let mut cx = domain.empty_coeff();
277    for (i, a) in cx.iter_mut().enumerate() {
278        *a = Fp::from(100 + i as u64);
279    }
280
281    let blind = Blind(Fp::random(rng));
282
283    let a = params.commit(&ax, blind).to_affine();
284    let b = params.commit(&bx, blind).to_affine();
285    let c = params.commit(&cx, blind).to_affine();
286
287    let x = Fp::random(rng);
288    let y = Fp::random(rng);
289    let avx = eval_polynomial(&ax, x);
290    let bvx = eval_polynomial(&bx, x);
291    let cvy = eval_polynomial(&cx, y);
292
293    let mut transcript = crate::transcript::Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);
294    create_proof(
295        &params,
296        rng,
297        &mut transcript,
298        std::iter::empty()
299            .chain(Some(ProverQuery {
300                point: x,
301                poly: &ax,
302                blind,
303            }))
304            .chain(Some(ProverQuery {
305                point: x,
306                poly: &bx,
307                blind,
308            }))
309            .chain(Some(ProverQuery {
310                point: y,
311                poly: &cx,
312                blind,
313            })),
314    )
315    .unwrap();
316    let proof = transcript.finalize();
317
318    {
319        let mut proof = &proof[..];
320        let mut transcript =
321            crate::transcript::Blake2bRead::<_, _, Challenge255<_>>::init(&mut proof);
322        let msm = params.empty_msm();
323
324        let guard = verify_proof(
325            &params,
326            &mut transcript,
327            std::iter::empty()
328                .chain(Some(VerifierQuery::new_commitment(&a, x, avx)))
329                .chain(Some(VerifierQuery::new_commitment(&b, x, avx))) // NB: wrong!
330                .chain(Some(VerifierQuery::new_commitment(&c, y, cvy))),
331            msm,
332        )
333        .unwrap();
334
335        // Should fail.
336        assert!(!guard.use_challenges().eval());
337    }
338
339    {
340        let mut proof = &proof[..];
341
342        let mut transcript =
343            crate::transcript::Blake2bRead::<_, _, Challenge255<_>>::init(&mut proof);
344        let msm = params.empty_msm();
345
346        let guard = verify_proof(
347            &params,
348            &mut transcript,
349            std::iter::empty()
350                .chain(Some(VerifierQuery::new_commitment(&a, x, avx)))
351                .chain(Some(VerifierQuery::new_commitment(&b, x, bvx)))
352                .chain(Some(VerifierQuery::new_commitment(&c, y, cvy))),
353            msm,
354        )
355        .unwrap();
356
357        // Should succeed.
358        assert!(guard.use_challenges().eval());
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use super::{construct_intermediate_sets, Query};
365    use crate::arithmetic::FieldExt;
366    use crate::pasta::Fp;
367
368    #[derive(Clone)]
369    struct MyQuery<F> {
370        commitment: usize,
371        point: F,
372        eval: F,
373    }
374
375    impl<F: Clone + Default> Query<F> for MyQuery<F> {
376        type Commitment = usize;
377        type Eval = F;
378
379        fn get_point(&self) -> F {
380            self.point.clone()
381        }
382        fn get_eval(&self) -> Self::Eval {
383            self.eval.clone()
384        }
385        fn get_commitment(&self) -> Self::Commitment {
386            self.commitment
387        }
388    }
389}
390
391#[cfg(test)]
392mod proptests {
393    use proptest::{
394        collection::vec,
395        prelude::*,
396        sample::{select, subsequence},
397        strategy::Strategy,
398    };
399
400    use super::construct_intermediate_sets;
401    use crate::poly::Rotation;
402    use pasta_curves::{arithmetic::FieldExt, Fp};
403
404    use std::collections::BTreeMap;
405    use std::convert::TryFrom;
406
407    #[derive(Debug, Clone)]
408    struct MyQuery<F> {
409        point: F,
410        eval: F,
411        commitment: usize,
412    }
413
414    impl super::Query<Fp> for MyQuery<Fp> {
415        type Commitment = usize;
416        type Eval = Fp;
417
418        fn get_point(&self) -> Fp {
419            self.point
420        }
421
422        fn get_eval(&self) -> Self::Eval {
423            self.eval
424        }
425
426        fn get_commitment(&self) -> Self::Commitment {
427            self.commitment
428        }
429    }
430
431    prop_compose! {
432        fn arb_point()(
433            bytes in vec(any::<u8>(), 64)
434        ) -> Fp {
435            Fp::from_bytes_wide(&<[u8; 64]>::try_from(bytes).unwrap())
436        }
437    }
438
439    prop_compose! {
440        fn arb_query(commitment: usize, point: Fp)(
441            eval in arb_point()
442        ) -> MyQuery<Fp> {
443            MyQuery {
444                point,
445                eval,
446                commitment
447            }
448        }
449    }
450
451    prop_compose! {
452        // Mapping from column index to point index.
453        fn arb_queries_inner(num_points: usize, num_cols: usize, num_queries: usize)(
454            col_indices in vec(select((0..num_cols).collect::<Vec<_>>()), num_queries),
455            point_indices in vec(select((0..num_points).collect::<Vec<_>>()), num_queries)
456        ) -> Vec<(usize, usize)> {
457            col_indices.into_iter().zip(point_indices.into_iter()).collect()
458        }
459    }
460
461    prop_compose! {
462        fn compare_queries(
463            num_points: usize,
464            num_cols: usize,
465            num_queries: usize,
466        )(
467            points_1 in vec(arb_point(), num_points),
468            points_2 in vec(arb_point(), num_points),
469            mapping in arb_queries_inner(num_points, num_cols, num_queries)
470        )(
471            queries_1 in mapping.iter().map(|(commitment, point_idx)| arb_query(*commitment, points_1[*point_idx])).collect::<Vec<_>>(),
472            queries_2 in mapping.iter().map(|(commitment, point_idx)| arb_query(*commitment, points_2[*point_idx])).collect::<Vec<_>>(),
473        ) -> (
474            Vec<MyQuery<Fp>>,
475            Vec<MyQuery<Fp>>
476        ) {
477            (
478                queries_1,
479                queries_2,
480            )
481        }
482    }
483
484    proptest! {
485        #[test]
486        fn test_intermediate_sets(
487            (queries_1, queries_2) in compare_queries(8, 8, 16)
488        ) {
489            let (commitment_data, _point_sets) = construct_intermediate_sets(queries_1);
490            let set_indices = commitment_data.iter().map(|data| data.set_index).collect::<Vec<_>>();
491            let point_indices = commitment_data.iter().map(|data| data.point_indices.clone()).collect::<Vec<_>>();
492
493            // It shouldn't matter what the point or eval values are; we should get
494            // the same exact point set indices and point indices again.
495            let (new_commitment_data, _new_point_sets) = construct_intermediate_sets(queries_2);
496            let new_set_indices = new_commitment_data.iter().map(|data| data.set_index).collect::<Vec<_>>();
497            let new_point_indices = new_commitment_data.iter().map(|data| data.point_indices.clone()).collect::<Vec<_>>();
498
499            assert_eq!(set_indices, new_set_indices);
500            assert_eq!(point_indices, new_point_indices);
501        }
502    }
503}