halo2_proofs/dev/
cost.rs

1//! Developer tools for investigating the cost of a circuit.
2
3use std::{
4    collections::{HashMap, HashSet},
5    iter,
6    marker::PhantomData,
7    ops::{Add, Mul},
8};
9
10use ff::{Field, PrimeField};
11use group::prime::PrimeGroup;
12
13use crate::{
14    plonk::{
15        Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed,
16        FloorPlanner, Instance, Selector,
17    },
18    poly::Rotation,
19};
20
21/// Measures a circuit to determine its costs, and explain what contributes to them.
22#[derive(Debug)]
23pub struct CircuitCost<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> {
24    /// Power-of-2 bound on the number of rows in the circuit.
25    k: usize,
26    /// Maximum degree of the circuit.
27    max_deg: usize,
28    /// Number of advice columns.
29    advice_columns: usize,
30    /// Number of direct queries for each column type.
31    instance_queries: usize,
32    advice_queries: usize,
33    fixed_queries: usize,
34    /// Number of lookup arguments.
35    lookups: usize,
36    /// Number of columns in the global permutation.
37    permutation_cols: usize,
38    /// Number of distinct sets of points in the multiopening argument.
39    point_sets: usize,
40
41    _marker: PhantomData<(G, ConcreteCircuit)>,
42}
43
44struct Assembly {
45    selectors: Vec<Vec<bool>>,
46}
47
48impl<F: Field> Assignment<F> for Assembly {
49    fn enter_region<NR, N>(&mut self, _: N)
50    where
51        NR: Into<String>,
52        N: FnOnce() -> NR,
53    {
54        // Do nothing; we don't care about regions in this context.
55    }
56
57    fn exit_region(&mut self) {
58        // Do nothing; we don't care about regions in this context.
59    }
60
61    fn enable_selector<A, AR>(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error>
62    where
63        A: FnOnce() -> AR,
64        AR: Into<String>,
65    {
66        self.selectors[selector.0][row] = true;
67
68        Ok(())
69    }
70
71    fn query_instance(&self, _: Column<Instance>, _: usize) -> Result<Option<F>, Error> {
72        Ok(None)
73    }
74
75    fn assign_advice<V, VR, A, AR>(
76        &mut self,
77        _: A,
78        _: Column<Advice>,
79        _: usize,
80        _: V,
81    ) -> Result<(), Error>
82    where
83        V: FnOnce() -> Result<VR, Error>,
84        VR: Into<Assigned<F>>,
85        A: FnOnce() -> AR,
86        AR: Into<String>,
87    {
88        Ok(())
89    }
90
91    fn assign_fixed<V, VR, A, AR>(
92        &mut self,
93        _: A,
94        _: Column<Fixed>,
95        _: usize,
96        _: V,
97    ) -> Result<(), Error>
98    where
99        V: FnOnce() -> Result<VR, Error>,
100        VR: Into<Assigned<F>>,
101        A: FnOnce() -> AR,
102        AR: Into<String>,
103    {
104        Ok(())
105    }
106
107    fn copy(&mut self, _: Column<Any>, _: usize, _: Column<Any>, _: usize) -> Result<(), Error> {
108        Ok(())
109    }
110
111    fn fill_from_row(
112        &mut self,
113        _: Column<Fixed>,
114        _: usize,
115        _: Option<Assigned<F>>,
116    ) -> Result<(), Error> {
117        Ok(())
118    }
119
120    fn push_namespace<NR, N>(&mut self, _: N)
121    where
122        NR: Into<String>,
123        N: FnOnce() -> NR,
124    {
125        // Do nothing; we don't care about namespaces in this context.
126    }
127
128    fn pop_namespace(&mut self, _: Option<String>) {
129        // Do nothing; we don't care about namespaces in this context.
130    }
131}
132
133impl<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> CircuitCost<G, ConcreteCircuit> {
134    /// Measures a circuit with parameter constant `k`.
135    ///
136    /// Panics if `k` is not large enough for the circuit.
137    pub fn measure(k: usize, circuit: &ConcreteCircuit) -> Self {
138        // Collect the layout details.
139        let mut cs = ConstraintSystem::default();
140        let config = ConcreteCircuit::configure(&mut cs);
141        let mut assembly = Assembly {
142            selectors: vec![vec![false; 1 << k]; cs.num_selectors],
143        };
144        ConcreteCircuit::FloorPlanner::synthesize(
145            &mut assembly,
146            circuit,
147            config,
148            cs.constants.clone(),
149        )
150        .unwrap();
151        let (cs, _) = cs.compress_selectors(assembly.selectors);
152
153        assert!((1 << k) >= cs.minimum_rows());
154
155        // Figure out how many point sets we have due to queried cells.
156        let mut column_queries: HashMap<Column<Any>, HashSet<i32>> = HashMap::new();
157        for (c, r) in iter::empty()
158            .chain(
159                cs.advice_queries
160                    .iter()
161                    .map(|(c, r)| (Column::<Any>::from(*c), *r)),
162            )
163            .chain(cs.instance_queries.iter().map(|(c, r)| ((*c).into(), *r)))
164            .chain(cs.fixed_queries.iter().map(|(c, r)| ((*c).into(), *r)))
165            .chain(
166                cs.permutation
167                    .get_columns()
168                    .into_iter()
169                    .map(|c| (c, Rotation::cur())),
170            )
171        {
172            column_queries.entry(c).or_default().insert(r.0);
173        }
174        let mut point_sets: HashSet<Vec<i32>> = HashSet::new();
175        for (_, r) in column_queries {
176            // Sort the query sets so we merge duplicates.
177            let mut query_set: Vec<_> = r.into_iter().collect();
178            query_set.sort_unstable();
179            point_sets.insert(query_set);
180        }
181
182        // Include lookup polynomials in point sets:
183        point_sets.insert(vec![0, 1]); // product_poly
184        point_sets.insert(vec![-1, 0]); // permuted_input_poly
185        point_sets.insert(vec![0]); // permuted_table_poly
186
187        // Include permutation polynomials in point sets.
188        point_sets.insert(vec![0, 1]); // permutation_product_poly
189        let max_deg = cs.degree();
190        let permutation_cols = cs.permutation.get_columns().len();
191        if permutation_cols > max_deg - 2 {
192            // permutation_product_poly for chaining chunks.
193            point_sets.insert(vec![-((cs.blinding_factors() + 1) as i32), 0, 1]);
194        }
195
196        CircuitCost {
197            k,
198            max_deg,
199            advice_columns: cs.num_advice_columns,
200            instance_queries: cs.instance_queries.len(),
201            advice_queries: cs.advice_queries.len(),
202            fixed_queries: cs.fixed_queries.len(),
203            lookups: cs.lookups.len(),
204            permutation_cols,
205            point_sets: point_sets.len(),
206            _marker: PhantomData::default(),
207        }
208    }
209
210    fn permutation_chunks(&self) -> usize {
211        let chunk_size = self.max_deg - 2;
212        (self.permutation_cols + chunk_size - 1) / chunk_size
213    }
214
215    /// Returns the marginal proof size per instance of this circuit.
216    pub fn marginal_proof_size(&self) -> MarginalProofSize<G> {
217        let chunks = self.permutation_chunks();
218
219        MarginalProofSize {
220            // Cells:
221            // - 1 commitment per advice column per instance
222            // - 1 eval per instance column query per instance
223            // - 1 eval per advice column query per instance
224            instance: ProofContribution::new(0, self.instance_queries),
225            advice: ProofContribution::new(self.advice_columns, self.advice_queries),
226
227            // Lookup arguments:
228            // - 3 commitments per lookup argument per instance
229            // - 5 evals per lookup argument per instance
230            lookups: ProofContribution::new(3 * self.lookups, 5 * self.lookups),
231
232            // Global permutation argument:
233            // - chunks commitments per instance
234            // - 2*chunks + (chunks - 1) evals per instance
235            equality: ProofContribution::new(chunks, 3 * chunks - 1),
236
237            _marker: PhantomData::default(),
238        }
239    }
240
241    /// Returns the proof size for the given number of instances of this circuit.
242    pub fn proof_size(&self, instances: usize) -> ProofSize<G> {
243        let marginal = self.marginal_proof_size();
244
245        ProofSize {
246            // Cells:
247            // - marginal cost per instance
248            // - 1 eval per fixed column query
249            instance: marginal.instance * instances,
250            advice: marginal.advice * instances,
251            fixed: ProofContribution::new(0, self.fixed_queries),
252
253            // Lookup arguments:
254            // - marginal cost per instance
255            lookups: marginal.lookups * instances,
256
257            // Global permutation argument:
258            // - marginal cost per instance
259            // - 1 eval per column
260            equality: marginal.equality * instances
261                + ProofContribution::new(0, self.permutation_cols),
262
263            // Vanishing argument:
264            // - 1 + (max_deg - 1) commitments
265            // - 1 random_poly eval
266            vanishing: ProofContribution::new(self.max_deg, 1),
267
268            // Multiopening argument:
269            // - f_commitment
270            // - 1 eval per set of points in multiopen argument
271            multiopen: ProofContribution::new(1, self.point_sets),
272
273            // Polycommit:
274            // - s_poly commitment
275            // - inner product argument (2 * k round commitments)
276            // - a
277            // - xi
278            polycomm: ProofContribution::new(1 + 2 * self.k, 2),
279
280            _marker: PhantomData::default(),
281        }
282    }
283}
284
285/// (commitments, evaluations)
286#[derive(Debug)]
287struct ProofContribution {
288    commitments: usize,
289    evaluations: usize,
290}
291
292impl ProofContribution {
293    fn new(commitments: usize, evaluations: usize) -> Self {
294        ProofContribution {
295            commitments,
296            evaluations,
297        }
298    }
299
300    fn len(&self, point: usize, scalar: usize) -> usize {
301        self.commitments * point + self.evaluations * scalar
302    }
303}
304
305impl Add for ProofContribution {
306    type Output = Self;
307
308    fn add(self, rhs: Self) -> Self::Output {
309        Self {
310            commitments: self.commitments + rhs.commitments,
311            evaluations: self.evaluations + rhs.evaluations,
312        }
313    }
314}
315
316impl Mul<usize> for ProofContribution {
317    type Output = Self;
318
319    fn mul(self, instances: usize) -> Self::Output {
320        Self {
321            commitments: self.commitments * instances,
322            evaluations: self.evaluations * instances,
323        }
324    }
325}
326
327/// The marginal size of a Halo 2 proof, broken down into its contributing factors.
328#[derive(Debug)]
329pub struct MarginalProofSize<G: PrimeGroup> {
330    instance: ProofContribution,
331    advice: ProofContribution,
332    lookups: ProofContribution,
333    equality: ProofContribution,
334    _marker: PhantomData<G>,
335}
336
337impl<G: PrimeGroup> From<MarginalProofSize<G>> for usize {
338    fn from(proof: MarginalProofSize<G>) -> Self {
339        let point = G::Repr::default().as_ref().len();
340        let scalar = <G::Scalar as PrimeField>::Repr::default().as_ref().len();
341
342        proof.instance.len(point, scalar)
343            + proof.advice.len(point, scalar)
344            + proof.lookups.len(point, scalar)
345            + proof.equality.len(point, scalar)
346    }
347}
348
349/// The size of a Halo 2 proof, broken down into its contributing factors.
350#[derive(Debug)]
351pub struct ProofSize<G: PrimeGroup> {
352    instance: ProofContribution,
353    advice: ProofContribution,
354    fixed: ProofContribution,
355    lookups: ProofContribution,
356    equality: ProofContribution,
357    vanishing: ProofContribution,
358    multiopen: ProofContribution,
359    polycomm: ProofContribution,
360    _marker: PhantomData<G>,
361}
362
363impl<G: PrimeGroup> From<ProofSize<G>> for usize {
364    fn from(proof: ProofSize<G>) -> Self {
365        let point = G::Repr::default().as_ref().len();
366        let scalar = <G::Scalar as PrimeField>::Repr::default().as_ref().len();
367
368        proof.instance.len(point, scalar)
369            + proof.advice.len(point, scalar)
370            + proof.fixed.len(point, scalar)
371            + proof.lookups.len(point, scalar)
372            + proof.equality.len(point, scalar)
373            + proof.vanishing.len(point, scalar)
374            + proof.multiopen.len(point, scalar)
375            + proof.polycomm.len(point, scalar)
376    }
377}