halo2_axiom/circuit/
table_layouter.rs

1//! Implementations of common table layouters.
2
3use std::{
4    collections::HashMap,
5    fmt::{self, Debug},
6};
7
8use ff::Field;
9
10use crate::plonk::{Assigned, Assignment, Error, TableColumn, TableError};
11
12use super::Value;
13
14/// Helper trait for implementing a custom [`Layouter`].
15///
16/// This trait is used for implementing table assignments.
17///
18/// [`Layouter`]: super::Layouter
19pub trait TableLayouter<F: Field>: std::fmt::Debug {
20    /// Assigns a fixed value to a table cell.
21    ///
22    /// Returns an error if the table cell has already been assigned to.
23    fn assign_cell<'v>(
24        &'v mut self,
25        annotation: &'v (dyn Fn() -> String + 'v),
26        column: TableColumn,
27        offset: usize,
28        to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
29    ) -> Result<(), Error>;
30}
31
32/// The default value to fill a table column with.
33///
34/// - The outer `Option` tracks whether the value in row 0 of the table column has been
35///   assigned yet. This will always be `Some` once a valid table has been completely
36///   assigned.
37/// - The inner `Value` tracks whether the underlying `Assignment` is evaluating
38///   witnesses or not.
39type DefaultTableValue<F> = Option<Value<Assigned<F>>>;
40
41/// A table layouter that can be used to assign values to a table.
42pub struct SimpleTableLayouter<'r, 'a, F: Field, CS: Assignment<F> + 'a> {
43    cs: &'a mut CS,
44    used_columns: &'r [TableColumn],
45    /// maps from a fixed column to a pair (default value, vector saying which rows are assigned)
46    pub default_and_assigned: HashMap<TableColumn, (DefaultTableValue<F>, Vec<bool>)>,
47}
48
49impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> fmt::Debug for SimpleTableLayouter<'r, 'a, F, CS> {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.debug_struct("SimpleTableLayouter")
52            .field("used_columns", &self.used_columns)
53            .field("default_and_assigned", &self.default_and_assigned)
54            .finish()
55    }
56}
57
58impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> SimpleTableLayouter<'r, 'a, F, CS> {
59    /// Returns a new SimpleTableLayouter
60    pub fn new(cs: &'a mut CS, used_columns: &'r [TableColumn]) -> Self {
61        SimpleTableLayouter {
62            cs,
63            used_columns,
64            default_and_assigned: HashMap::default(),
65        }
66    }
67}
68
69impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> TableLayouter<F>
70    for SimpleTableLayouter<'r, 'a, F, CS>
71{
72    fn assign_cell<'v>(
73        &'v mut self,
74        _annotation: &'v (dyn Fn() -> String + 'v),
75        column: TableColumn,
76        offset: usize,
77        to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
78    ) -> Result<(), Error> {
79        if self.used_columns.contains(&column) {
80            return Err(Error::TableError(TableError::UsedColumn(column)));
81        }
82
83        let entry = self.default_and_assigned.entry(column).or_default();
84
85        let value;
86        self.cs.assign_fixed(
87            // annotation,
88            column.inner(),
89            offset, // tables are always assigned starting at row 0
90            {
91                let res = to();
92                value = res;
93                res.assign()?
94            },
95        );
96
97        match (entry.0.is_none(), offset) {
98            // Use the value at offset 0 as the default value for this table column.
99            (true, 0) => entry.0 = Some(value),
100            // Since there is already an existing default value for this table column,
101            // the caller should not be attempting to assign another value at offset 0.
102            (false, 0) => {
103                return Err(Error::TableError(TableError::OverwriteDefault(
104                    column,
105                    format!("{:?}", entry.0.unwrap()),
106                    format!("{:?}", value),
107                )))
108            }
109            _ => (),
110        }
111        if entry.1.len() <= offset {
112            entry.1.resize(offset + 1, false);
113        }
114        entry.1[offset] = true;
115
116        Ok(())
117    }
118}
119
120pub(crate) fn compute_table_lengths<F: Debug>(
121    default_and_assigned: &HashMap<TableColumn, (DefaultTableValue<F>, Vec<bool>)>,
122) -> Result<usize, Error> {
123    let column_lengths: Result<Vec<_>, Error> = default_and_assigned
124        .iter()
125        .map(|(col, (default_value, assigned))| {
126            if default_value.is_none() || assigned.is_empty() {
127                return Err(Error::TableError(TableError::ColumnNotAssigned(*col)));
128            }
129            if assigned.iter().all(|b| *b) {
130                // All values in the column have been assigned
131                Ok((col, assigned.len()))
132            } else {
133                Err(Error::TableError(TableError::ColumnNotAssigned(*col)))
134            }
135        })
136        .collect();
137    let column_lengths = column_lengths?;
138    column_lengths
139        .into_iter()
140        .try_fold((None, 0), |acc, (col, col_len)| {
141            if acc.1 == 0 || acc.1 == col_len {
142                Ok((Some(*col), col_len))
143            } else {
144                let mut cols = [(*col, col_len), (acc.0.unwrap(), acc.1)];
145                cols.sort();
146                Err(Error::TableError(TableError::UnevenColumnLengths(
147                    cols[0], cols[1],
148                )))
149            }
150        })
151        .map(|col_len| col_len.1)
152}
153
154#[cfg(test)]
155mod tests {
156    use halo2curves::pasta::Fp;
157
158    use crate::{
159        circuit::{Layouter, SimpleFloorPlanner},
160        dev::MockProver,
161        plonk::{Circuit, ConstraintSystem},
162        poly::Rotation,
163    };
164
165    use super::*;
166
167    #[test]
168    fn table_no_default() {
169        const K: u32 = 4;
170
171        #[derive(Clone)]
172        struct FaultyCircuitConfig {
173            table: TableColumn,
174        }
175
176        struct FaultyCircuit;
177
178        impl Circuit<Fp> for FaultyCircuit {
179            type Config = FaultyCircuitConfig;
180            type FloorPlanner = SimpleFloorPlanner;
181            #[cfg(feature = "circuit-params")]
182            type Params = ();
183
184            fn without_witnesses(&self) -> Self {
185                Self
186            }
187
188            fn configure(meta: &mut ConstraintSystem<Fp>) -> Self::Config {
189                let a = meta.advice_column();
190                let table = meta.lookup_table_column();
191
192                meta.lookup("", |cells| {
193                    let a = cells.query_advice(a, Rotation::cur());
194                    vec![(a, table)]
195                });
196
197                Self::Config { table }
198            }
199
200            fn synthesize(
201                &self,
202                config: Self::Config,
203                mut layouter: impl Layouter<Fp>,
204            ) -> Result<(), Error> {
205                layouter.assign_table(
206                    || "duplicate assignment",
207                    |mut table| {
208                        table.assign_cell(
209                            || "default",
210                            config.table,
211                            1,
212                            || Value::known(Fp::zero()),
213                        )
214                    },
215                )
216            }
217        }
218
219        let prover = MockProver::run(K, &FaultyCircuit, vec![]);
220        assert_eq!(
221            format!("{}", prover.unwrap_err()),
222            "TableColumn { inner: Column { index: 0, column_type: Fixed } } not fully assigned. Help: assign a value at offset 0."
223        );
224    }
225
226    #[test]
227    fn table_overwrite_default() {
228        const K: u32 = 4;
229
230        #[derive(Clone)]
231        struct FaultyCircuitConfig {
232            table: TableColumn,
233        }
234
235        struct FaultyCircuit;
236
237        impl Circuit<Fp> for FaultyCircuit {
238            type Config = FaultyCircuitConfig;
239            type FloorPlanner = SimpleFloorPlanner;
240            #[cfg(feature = "circuit-params")]
241            type Params = ();
242
243            fn without_witnesses(&self) -> Self {
244                Self
245            }
246
247            fn configure(meta: &mut ConstraintSystem<Fp>) -> Self::Config {
248                let a = meta.advice_column();
249                let table = meta.lookup_table_column();
250
251                meta.lookup("", |cells| {
252                    let a = cells.query_advice(a, Rotation::cur());
253                    vec![(a, table)]
254                });
255
256                Self::Config { table }
257            }
258
259            fn synthesize(
260                &self,
261                config: Self::Config,
262                mut layouter: impl Layouter<Fp>,
263            ) -> Result<(), Error> {
264                layouter.assign_table(
265                    || "duplicate assignment",
266                    |mut table| {
267                        table.assign_cell(
268                            || "default",
269                            config.table,
270                            0,
271                            || Value::known(Fp::zero()),
272                        )?;
273                        table.assign_cell(
274                            || "duplicate",
275                            config.table,
276                            0,
277                            || Value::known(Fp::zero()),
278                        )
279                    },
280                )
281            }
282        }
283
284        let prover = MockProver::run(K, &FaultyCircuit, vec![]);
285        assert_eq!(
286            format!("{}", prover.unwrap_err()),
287            "Attempted to overwrite default value Value { inner: Some(Trivial(0x0000000000000000000000000000000000000000000000000000000000000000)) } with Value { inner: Some(Trivial(0x0000000000000000000000000000000000000000000000000000000000000000)) } in TableColumn { inner: Column { index: 0, column_type: Fixed } }"
288        );
289    }
290
291    #[test]
292    fn table_reuse_column() {
293        const K: u32 = 4;
294
295        #[derive(Clone)]
296        struct FaultyCircuitConfig {
297            table: TableColumn,
298        }
299
300        struct FaultyCircuit;
301
302        impl Circuit<Fp> for FaultyCircuit {
303            type Config = FaultyCircuitConfig;
304            type FloorPlanner = SimpleFloorPlanner;
305            #[cfg(feature = "circuit-params")]
306            type Params = ();
307
308            fn without_witnesses(&self) -> Self {
309                Self
310            }
311
312            fn configure(meta: &mut ConstraintSystem<Fp>) -> Self::Config {
313                let a = meta.advice_column();
314                let table = meta.lookup_table_column();
315
316                meta.lookup("", |cells| {
317                    let a = cells.query_advice(a, Rotation::cur());
318                    vec![(a, table)]
319                });
320
321                Self::Config { table }
322            }
323
324            fn synthesize(
325                &self,
326                config: Self::Config,
327                mut layouter: impl Layouter<Fp>,
328            ) -> Result<(), Error> {
329                layouter.assign_table(
330                    || "first assignment",
331                    |mut table| {
332                        table.assign_cell(
333                            || "default",
334                            config.table,
335                            0,
336                            || Value::known(Fp::zero()),
337                        )
338                    },
339                )?;
340
341                layouter.assign_table(
342                    || "reuse",
343                    |mut table| {
344                        table.assign_cell(|| "reuse", config.table, 1, || Value::known(Fp::zero()))
345                    },
346                )
347            }
348        }
349
350        let prover = MockProver::run(K, &FaultyCircuit, vec![]);
351        assert_eq!(
352            format!("{}", prover.unwrap_err()),
353            "TableColumn { inner: Column { index: 0, column_type: Fixed } } has already been used"
354        );
355    }
356
357    #[test]
358    fn table_uneven_columns() {
359        const K: u32 = 4;
360
361        #[derive(Clone)]
362        struct FaultyCircuitConfig {
363            table: (TableColumn, TableColumn),
364        }
365
366        struct FaultyCircuit;
367
368        impl Circuit<Fp> for FaultyCircuit {
369            type Config = FaultyCircuitConfig;
370            type FloorPlanner = SimpleFloorPlanner;
371            #[cfg(feature = "circuit-params")]
372            type Params = ();
373
374            fn without_witnesses(&self) -> Self {
375                Self
376            }
377
378            fn configure(meta: &mut ConstraintSystem<Fp>) -> Self::Config {
379                let a = meta.advice_column();
380                let table = (meta.lookup_table_column(), meta.lookup_table_column());
381                meta.lookup("", |cells| {
382                    let a = cells.query_advice(a, Rotation::cur());
383
384                    vec![(a.clone(), table.0), (a, table.1)]
385                });
386
387                Self::Config { table }
388            }
389
390            fn synthesize(
391                &self,
392                config: Self::Config,
393                mut layouter: impl Layouter<Fp>,
394            ) -> Result<(), Error> {
395                layouter.assign_table(
396                    || "table with uneven columns",
397                    |mut table| {
398                        table.assign_cell(|| "", config.table.0, 0, || Value::known(Fp::zero()))?;
399                        table.assign_cell(|| "", config.table.0, 1, || Value::known(Fp::zero()))?;
400
401                        table.assign_cell(|| "", config.table.1, 0, || Value::known(Fp::zero()))
402                    },
403                )
404            }
405        }
406
407        let prover = MockProver::run(K, &FaultyCircuit, vec![]);
408        assert_eq!(
409            format!("{}", prover.unwrap_err()),
410            "TableColumn { inner: Column { index: 0, column_type: Fixed } } has length 2 while TableColumn { inner: Column { index: 1, column_type: Fixed } } has length 1"
411        );
412    }
413}