Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Rebase dynamic lookups #715

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0d14709
halo2_proofs/Cargo.toml: Introduce "unstable-dynamic-lookups" flag
therealyingtong Feb 23, 2023
77b18df
plonk::circuit: DynamicTable, TableTag
therealyingtong Feb 21, 2023
78ea483
circuit::layouter::RegionColumn: Introduce TableTag variant
therealyingtong Feb 21, 2023
be3d62f
plonk::circuit::Expression: Introduce TableTag variant
therealyingtong Feb 21, 2023
fc71bba
plonk::circuit::Assignment: Introduce add_to_lookup() method
therealyingtong Feb 21, 2023
07655b9
circuit::layouter::RegionLayouter: Introduce add_to_lookup() method
therealyingtong Feb 21, 2023
e713366
circuit::Region: Implement add_to_lookup()
therealyingtong Feb 23, 2023
8c2f2b1
plonk::circuit::ConstraintSystem: lookup_dynamic(), create_dynamic_ta…
therealyingtong Feb 23, 2023
1c9424d
circuit::compress_selectors: Extract exclusion_matrix() method
therealyingtong Feb 23, 2023
cc26963
plonk::circuit::ConstraintSystem: compress_dynamic_table_tags()
therealyingtong Feb 23, 2023
f32fd2a
plonk::keygen: Handle dynamic table tags in keygen_vk, keygen_pk
therealyingtong Feb 23, 2023
cfc1468
dev::MockProver::run: Handle dynamic tables
therealyingtong Feb 23, 2023
579741f
dev::failure::VerifyFailure: DynamicTableCellNotAssigned variant
therealyingtong Feb 23, 2023
444b8a6
dev::MockProver::verify: Handle dynamic table errors
therealyingtong Feb 23, 2023
eff6917
cost::CircuitCost::measure: Account for dynamic table tags
therealyingtong Feb 23, 2023
124a1e1
layout::CircuitLayout::render: Handle dynamic table tags
therealyingtong Feb 23, 2023
3cb9ab6
dev::tests: Add tests for dynamic lookups
therealyingtong Feb 23, 2023
5548bdc
halo2_proofs::examples::dynamic_lookup: Dynamic lookup example
therealyingtong Feb 21, 2023
1624127
Docfixes and minor refactors
therealyingtong Feb 24, 2023
a68c7a6
CHANGELOG: <F: Field + From<u64> bound instead of <F: Field>
therealyingtong Mar 12, 2023
edf0c84
book/dev/dynamic-lookups.md: Document dynamic lookups feature
therealyingtong Apr 4, 2023
eb938d7
[WIP] dev::tests: Failing example
therealyingtong Apr 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions book/src/dev/dynamic-lookups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Dynamic lookups
*Note: This is an unstable nightly feature and can be enabled with the flag `#[cfg(feature = unstable-dynamic-lookups)]`.*

The current `halo2` [lookup argument](../design/proving-system/lookup.md) only supports fixed tables, whose values are fixed as part of the constraint system definition. However, some use-cases require looking up witnessed values, which can change with each instance: for example, a set of preimages and hashes of the SHA256 hash function.

## `DynamicTable`
A `DynamicTable` is associated with:
​ | description
--------|--------------------------------------------------------------------------------------------------------------------------------------
`index` | This table's index in the constraint system's list of dynamic tables
columns | A list of `Advice` and `Fixed` columns where this table's values can be assigned.
rows | The rows in this table's columns, where its values are assigned. Every table column must be assigned in the same rows.
`tag` | `index` + 1; the tag is appended to each lookup argument involving this table.

Consider a `DynamicTable` with `index = 1`, `tag = index + 1 = 2`. It is assigned on advice column `A1` and fixed column `F0`, on rows `0`, `2`, `3`. Note that the table need not occupy a contiguous area in the circuit:

| row | A0 | A1 | A2 | F0 | F1 |
|-----|----|----|----|----|----|
| 0 | | ⬤ | | ⬤ | |
| 1 | | `x`| | `x`| |
| 2 | | ⬤ | | ⬤ | |
| 3 | | ⬤ | | ⬤ | |

Now, we specify a lookup `[(q_lookup * A0, A1), (q_lookup * A2,F0)]`: i.e., on the rows where `q_lookup = 1` is enabled, we enforce that the values in `A0` (`A2`) appear somewhere in the table column `A1` (`F0`). If all inputs (◯, ◯) indeed map to a table entry (⬤, ⬤), the lookup argument passes.
| row | q_lookup | A0 | A1 | A2 | F0 | F1 |
|-----|----------|----|----|----|----|----|
| 0 | 1 | ◯ | ⬤ | ◯ | ⬤ | |
| 1 | 1 | ◯ | `x`| ◯ | `x`| |
| 2 | 0 | | ⬤ | | ⬤ | |
| 3 | 0 | | ⬤ | | ⬤ | |

## Table tag
A subtle problem arises in the example above: the cells marked `x` are in the table *columns*, but are not part of the table's *rows*, and therefore must not be considered valid lookup values. To enforce this, we:
- repurpose the fixed column `F1` to serve as the table's "tag column", and
- append the table's **tag** to the lookup argument: `[(q_lookup * A0, A1), (q_lookup * A2,F0), (q_lookup * 2, q_lookup * F1)]`.

In other words, we append a **tag** to get input entries of the form (◯, ◯, 2), in order to match the tagged table entries (⬤, ⬤, 2).
| row | q_lookup | A0 | A1 | A2 | F0 | F1 |
|-----|----------|----|----|----|----|----|
| 0 | 1 | ◯ | ⬤ | ◯ | ⬤ | 2 |
| 1 | 1 | ◯ | `x`| ◯ | `x`| 0 |
| 2 | 0 | | ⬤ | | ⬤ | 2 |
| 3 | 0 | | ⬤ | | ⬤ | 2 |

Notice that if we now attempt to lookup some illegal inputs `x` on row 1, the lookup argument will fail, since the input `(x, x, 2)` does not appear anywhere in the table:
```ignore
[(q_lookup * A0, A1), (q_lookup * A2, F0), (q_lookup * 2, q_lookup * F1)]
= [(1 * x, A1), (1 * x, F0), (1 * 2, 1 * F1)]
= [(x, A1), (x, F0), (2, F1)]
```

| row | q_lookup | A0 | A1 | A2 | F0 | F1 |
|-----|----------|----|----|----|----|----|
| 0 | 1 | ◯ | ⬤ | ◯ | ⬤ | 2 |
| 1 | 1 | `x`| `x`| `x`| `x`| 0 |
| 2 | 0 | | ⬤ | | ⬤ | 2 |
| 3 | 0 | | ⬤ | | ⬤ | 2 |

Table tags also enable an important optimization: it allows unused rows in table columns to be recycled for other purposes, without affecting the lookup argument. By the same reasoning, this also allows dynamic tables to be "stacked" vertically by the layouter. This optimization may easily be applied to fixed lookups too.

Dynamic table tags are assigned to fixed columns in order to enforce their shape in the circuit: even though the values of a table can change across instances, its shape must stay the same. Under the hood, we use the [selector combining](../design/implementation/selector-combining.md) algorithm to combine table tags into fewer fixed columns without overlapping.

## Usage
### Creating dynamic tables
A `DynamicTable` is created using the `ConstraintSystem::create_dynamic_table()` method. The `Fixed` and `Advice` columns involved in the table are passed as arguments. (NB: `Instance` columns are not yet supported.)

```ignore
pub fn create_dynamic_table(
&mut self,
name: impl Into<String>,
fixed_columns: &[Column<Fixed>],
advice_columns: &[Column<Advice>],
) -> DynamicTable;
```

### Configuring dynamic lookups
A `DynamicTable` can be used in a lookup argument configuration using the `ConstraintSystem::lookup_dynamic()` method. This works similarly to `ConstraintSystem::lookup()`; however, it additionally:
- enforces that the table columns involved in the argument are indeed part of the provided `DynamicTable`; and
- adds the table tag to the lookup argument, as described in [[Table tag]](#table-tag).

### Assigning table values
Table values are assigned within regions, using the method `DynamicTable::add_row(&self, region: Region, offset: usize)`. As usual, the absolute rows involved in the dynamic table are only known after the floor planner rearranges the regions. (See [[Chips]](../concepts/chips.md) for an explanation of floor planners and regions.)

### Assigning input values
Input values are assigned within regions, and are usually identified by enabling the appropriate `q_lookup` selector at the desired offset. This is the same behavior as in assigning inputs to fixed tables.
6 changes: 6 additions & 0 deletions halo2_proofs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ and this project adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added

### Changed
- `halo2_proofs::dev`
- `MockProver<F>` now has an additional bound `<F + From<u64>>`.
- `graph::CircuitLayout::render<F>` now has an additional bound `<F + From<u64>>`.

## [0.3.0] - 2023-03-21
### Breaking circuit changes
Expand Down
6 changes: 6 additions & 0 deletions halo2_proofs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ beta = [
]
nightly = [
"beta",
"unstable-dynamic-lookups",
]
unstable-dynamic-lookups = []
# Add flags for in-development features above this line.

[lib]
Expand All @@ -98,3 +100,7 @@ bench = false
[[example]]
name = "circuit-layout"
required-features = ["test-dev-graph"]

[[example]]
name = "dynamic-lookup"
required-features = ["unstable-dynamic-lookups"]
138 changes: 138 additions & 0 deletions halo2_proofs/examples/dynamic-lookup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use halo2_proofs::{
circuit::{Layouter, SimpleFloorPlanner, Value},
dev::MockProver,
pasta::Fp,
plonk::{
create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column,
ConstraintSystem, DynamicTable, Error, Selector, SingleVerifier,
},
poly::{commitment::Params, Rotation},
transcript::{Blake2bRead, Blake2bWrite},
};
use pasta_curves::{vesta, EqAffine};
use rand_core::OsRng;

#[derive(Clone)]
struct EvenOddCircuitConfig {
is_even: Selector,
is_odd: Selector,
a: Column<Advice>,
// starts at zero to use as default
table_vals: Column<Advice>,
even: DynamicTable,
odd: DynamicTable,
}

struct DynLookupCircuit {}
impl Circuit<Fp> for DynLookupCircuit {
type Config = EvenOddCircuitConfig;
type FloorPlanner = SimpleFloorPlanner;

fn configure(meta: &mut ConstraintSystem<Fp>) -> Self::Config {
let a = meta.advice_column();
let table_vals = meta.advice_column();
let is_even = meta.complex_selector();
let is_odd = meta.complex_selector();
let even = meta.create_dynamic_table("even", &[], &[table_vals]);
let odd = meta.create_dynamic_table("odd", &[], &[table_vals]);

meta.lookup_dynamic(&even, |cells| {
let a = cells.query_advice(a, Rotation::cur());

(is_even, vec![(a, table_vals.into())])
});

meta.lookup_dynamic(&odd, |cells| {
let a = cells.query_advice(a, Rotation::cur());

(is_odd, vec![(a, table_vals.into())])
});

EvenOddCircuitConfig {
a,
table_vals,
is_even,
is_odd,
even,
odd,
}
}

fn without_witnesses(&self) -> Self {
Self {}
}

fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<Fp>,
) -> Result<(), Error> {
for i in 0..=5 {
layouter.assign_region(
|| format!("lookup: {}", i),
|mut region| {
// Enable the lookup on rows
if i % 2 == 0 {
config.is_even.enable(&mut region, 0)?;
} else {
config.is_odd.enable(&mut region, 0)?;
};

region.assign_advice(|| "", config.a, 0, || Value::known(Fp::from(i as u64)))
},
)?;
}
layouter.assign_region(
|| "table",
|mut region| {
for i in 0..=5 {
region.assign_advice(
|| "",
config.table_vals,
i,
|| Value::known(Fp::from(i as u64)),
)?;

let table = if i % 2 == 0 {
&config.even
} else {
&config.odd
};
table.add_row(&mut region, i)?;
}
Ok(())
},
)?;
Ok(())
}
}

fn main() {
let k = 5;

MockProver::run(k, &DynLookupCircuit {}, vec![])
.unwrap()
.verify()
.unwrap();

let params: Params<EqAffine> = Params::new(k);
let verifier = SingleVerifier::new(&params);
let vk = keygen_vk(&params, &DynLookupCircuit {}).unwrap();
let pk = keygen_pk(&params, vk, &DynLookupCircuit {}).unwrap();
let mut transcript = Blake2bWrite::<_, vesta::Affine, _>::init(vec![]);
create_proof(
&params,
&pk,
&[DynLookupCircuit {}],
&[&[]],
&mut OsRng,
&mut transcript,
)
.expect("Failed to create proof");

let proof: Vec<u8> = transcript.finalize();

let mut transcript = Blake2bRead::init(&proof[..]);
verify_proof(&params, pk.get_vk(), verifier, &[&[]], &mut transcript)
.expect("could not verify_proof");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 88c88a92a8c1799fc2b2dac1a5b9b434fc9ac5e1218e5104a85c345124d8a937 # shrinks to tables = [TableDescription { index: 0, activations: [false] }, TableDescription { index: 1, activations: [true] }]
8 changes: 8 additions & 0 deletions halo2_proofs/src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::{fmt, marker::PhantomData};

use ff::Field;

#[cfg(feature = "unstable-dynamic-lookups")]
use crate::plonk::TableTag;
use crate::plonk::{Advice, Any, Assigned, Column, Error, Fixed, Instance, Selector, TableColumn};

mod value;
Expand Down Expand Up @@ -204,6 +206,12 @@ impl<'r, F: Field> Region<'r, F> {
.enable_selector(&|| annotation().into(), selector, offset)
}

/// Enables a dynamic table lookup at the given offset.
#[cfg(feature = "unstable-dynamic-lookups")]
pub fn add_to_lookup(&mut self, table: TableTag, offset: usize) -> Result<(), Error> {
self.region.add_to_lookup(table, offset)
}

/// Assign an advice column value (witness).
///
/// Even though `to` has `FnMut` bounds, it is guaranteed to be called at most once.
Expand Down
9 changes: 9 additions & 0 deletions halo2_proofs/src/circuit/floor_planner/single_pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use std::marker::PhantomData;

use ff::Field;

#[cfg(feature = "unstable-dynamic-lookups")]
use crate::plonk::TableTag;
use crate::{
circuit::{
layouter::{RegionColumn, RegionLayouter, RegionShape},
Expand Down Expand Up @@ -259,6 +261,13 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F>
)
}

#[cfg(feature = "unstable-dynamic-lookups")]
fn add_to_lookup(&mut self, table: TableTag, offset: usize) -> Result<(), Error> {
self.layouter
.cs
.add_to_lookup(table, *self.layouter.regions[*self.region_index] + offset)
}

fn assign_advice<'v>(
&'v mut self,
annotation: &'v (dyn Fn() -> String + 'v),
Expand Down
7 changes: 7 additions & 0 deletions halo2_proofs/src/circuit/floor_planner/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,13 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F> for V1Region<'r
)
}

#[cfg(feature = "unstable-dynamic-lookups")]
fn add_to_lookup(&mut self, table: crate::plonk::TableTag, offset: usize) -> Result<(), Error> {
self.plan
.cs
.add_to_lookup(table, *self.plan.regions[*self.region_index] + offset)
}

fn assign_advice<'v>(
&'v mut self,
annotation: &'v (dyn Fn() -> String + 'v),
Expand Down
Loading