1use 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
14pub trait TableLayouter<F: Field>: std::fmt::Debug {
20 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
32type DefaultTableValue<F> = Option<Value<Assigned<F>>>;
40
41pub struct SimpleTableLayouter<'r, 'a, F: Field, CS: Assignment<F> + 'a> {
43 cs: &'a mut CS,
44 used_columns: &'r [TableColumn],
45 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 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 column.inner(),
89 offset, {
91 let res = to();
92 value = res;
93 res.assign()?
94 },
95 );
96
97 match (entry.0.is_none(), offset) {
98 (true, 0) => entry.0 = Some(value),
100 (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 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}