diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 08c34c40..a1fbae44 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,47 +4,71 @@ on:
push:
branches: ["main"]
pull_request:
- branches: ["main"]
+ branches: ["main", "develop", "community-edition", "release-*"]
env:
CARGO_TERM_COLOR: always
jobs:
build:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-latest-64core-256ram
steps:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run halo2-base tests
+ working-directory: "halo2-base"
run: |
- cd halo2-base
- cargo test -- --test-threads=1
- cd ..
- - name: Run halo2-ecc tests MockProver
+ cargo test
+ - name: Run halo2-ecc tests (mock prover)
+ working-directory: "halo2-ecc"
run: |
- cd halo2-ecc
- cargo test -- --test-threads=1 test_fp
- cargo test -- test_ecc
- cargo test -- test_secp
- cargo test -- test_ecdsa
- cargo test -- test_ec_add
- cargo test -- test_fixed
- cargo test -- test_msm
- cargo test -- test_fb
- cargo test -- test_pairing
- cd ..
- - name: Run halo2-ecc tests real prover
+ cargo test --lib -- --skip bench
+ - name: Run halo2-ecc tests (real prover)
+ working-directory: "halo2-ecc"
run: |
- cd halo2-ecc
- cargo test --release -- test_fp_assert_eq
- cargo test --release -- --nocapture bench_secp256k1_ecdsa
- cargo test --release -- --nocapture bench_ec_add
mv configs/bn254/bench_fixed_msm.t.config configs/bn254/bench_fixed_msm.config
- cargo test --release -- --nocapture bench_fixed_base_msm
mv configs/bn254/bench_msm.t.config configs/bn254/bench_msm.config
- cargo test --release -- --nocapture bench_msm
mv configs/bn254/bench_pairing.t.config configs/bn254/bench_pairing.config
+ mv configs/secp256k1/bench_ecdsa.t.config configs/secp256k1/bench_ecdsa.config
+ cargo test --release -- --nocapture bench_secp256k1_ecdsa
+ cargo test --release -- --nocapture bench_fixed_base_msm
+ cargo test --release -- --nocapture bench_msm
cargo test --release -- --nocapture bench_pairing
- cd ..
+ - name: Run zkevm tests
+ working-directory: "hashes/zkevm"
+ run: |
+ cargo test packed_multi_keccak_prover::k_14
+ cargo t test_vanilla_keccak_kat_vectors
+
+ lint:
+ name: Lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install toolchain
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ override: false
+ components: rustfmt, clippy
+
+ - uses: Swatinem/rust-cache@v1
+ with:
+ cache-on-failure: true
+
+ - name: Run fmt
+ run: cargo fmt --all -- --check
+
+ - name: Run clippy
+ run: cargo clippy --all --all-targets -- -D warnings
+
+ - name: Generate Cargo.lock
+ run: cargo generate-lockfile
+
+ - name: Run cargo audit
+ uses: actions-rs/audit-check@v1
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 65983083..eb915932 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,10 @@ Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
+
+# Local IDE configs
+.idea/
+.vscode/
=======
/target
diff --git a/Cargo.toml b/Cargo.toml
index 9d8d2d5c..b2d3ab72 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,13 +1,10 @@
[workspace]
-members = [
- "halo2-base",
- "halo2-ecc",
- "hashes/zkevm-keccak",
-]
+members = ["halo2-base", "halo2-ecc", "hashes/zkevm"]
+resolver = "2"
[profile.dev]
opt-level = 3
-debug = 1 # change to 0 or 2 for more or less debug info
+debug = 2 # change to 0 or 2 for more or less debug info
overflow-checks = true
incremental = true
@@ -28,7 +25,7 @@ codegen-units = 16
opt-level = 3
debug = false
debug-assertions = false
-lto = "fat"
+lto = "fat"
# `codegen-units = 1` can lead to WORSE performance - always bench to find best profile for your machine!
# codegen-units = 1
panic = "unwind"
@@ -39,7 +36,6 @@ incremental = false
inherits = "release"
debug = true
-# patch so snark-verifier uses this crate's halo2-base
[patch."https://github.com/axiom-crypto/halo2-lib.git"]
-halo2-base = { path = "./halo2-base" }
-halo2-ecc = { path = "./halo2-ecc" }
+halo2-base = { path = "../halo2-lib/halo2-base" }
+halo2-ecc = { path = "../halo2-lib/halo2-ecc" }
diff --git a/halo2-base/Cargo.toml b/halo2-base/Cargo.toml
index 45e1bca7..6816bf95 100644
--- a/halo2-base/Cargo.toml
+++ b/halo2-base/Cargo.toml
@@ -1,29 +1,32 @@
[package]
name = "halo2-base"
-version = "0.3.0"
+version = "0.4.0"
edition = "2021"
[dependencies]
-itertools = "0.10"
+itertools = "0.11"
num-bigint = { version = "0.4", features = ["rand"] }
num-integer = "0.1"
num-traits = "0.2"
rand_chacha = "0.3"
rustc-hash = "1.1"
-ff = "0.12"
-rayon = "1.6.1"
+rayon = "1.7"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
log = "0.4"
+getset = "0.1.2"
+ark-std = { version = "0.3.0", features = ["print-trace"], optional = true }
# Use Axiom's custom halo2 monorepo for faster proving when feature = "halo2-axiom" is on
-halo2_proofs_axiom = { git = "https://github.com/axiom-crypto/halo2.git", rev = "98bc83b", package = "halo2_proofs", optional = true }
+halo2_proofs_axiom = { version = "0.3", package = "halo2-axiom", optional = true }
# Use PSE halo2 and halo2curves for compatibility when feature = "halo2-pse" is on
-halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2023_02_02", optional = true }
+halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", rev = "7a21656", optional = true }
+# This is Scroll's audited poseidon circuit. We only use it for the Native Poseidon spec. We do not use the halo2 circuit at all (and it wouldn't even work because the halo2_proofs tag is not compatbile).
+# We forked it to upgrade to ff v0.13 and removed the circuit module
+poseidon-rs = { package = "poseidon-primitives", version = "=0.1.1" }
# plotting circuit layout
plotters = { version = "0.3.0", optional = true }
-tabbycat = { version = "0.1", features = ["attributes"], optional = true }
# test-utils
rand = { version = "0.8", optional = true }
@@ -31,12 +34,15 @@ rand = { version = "0.8", optional = true }
[dev-dependencies]
ark-std = { version = "0.3.0", features = ["print-trace"] }
rand = "0.8"
-pprof = { version = "0.11", features = ["criterion", "flamegraph"] }
-criterion = "0.4"
+pprof = { version = "0.13", features = ["criterion", "flamegraph"] }
+criterion = "0.5.1"
criterion-macro = "0.4"
-rayon = "1.6.1"
test-case = "3.1.0"
+test-log = "0.2.12"
+env_logger = "0.10.0"
proptest = "1.1.0"
+# native poseidon for testing
+pse-poseidon = { git = "https://github.com/axiom-crypto/pse-poseidon.git" }
# memory allocation
[target.'cfg(not(target_env = "msvc"))'.dependencies]
@@ -45,13 +51,14 @@ jemallocator = { version = "0.5", optional = true }
mimalloc = { version = "0.1", default-features = false, optional = true }
[features]
-default = ["halo2-axiom", "display"]
-dev-graph = ["halo2_proofs?/dev-graph", "halo2_proofs_axiom?/dev-graph", "plotters"]
-halo2-pse = ["halo2_proofs"]
+default = ["halo2-axiom", "display", "test-utils"]
+asm = ["halo2_proofs_axiom?/asm"]
+dev-graph = ["halo2_proofs/dev-graph", "plotters"] # only works with halo2-pse for now
+halo2-pse = ["halo2_proofs/circuit-params"]
halo2-axiom = ["halo2_proofs_axiom"]
display = []
profile = ["halo2_proofs_axiom?/profile"]
-test-utils = ["dep:rand"]
+test-utils = ["dep:rand", "ark-std"]
[[bench]]
name = "mul"
@@ -60,3 +67,7 @@ harness = false
[[bench]]
name = "inner_product"
harness = false
+
+[[example]]
+name = "inner_product"
+required-features = ["test-utils"]
diff --git a/halo2-base/README.md b/halo2-base/README.md
index 6b078ab9..94cbbc58 100644
--- a/halo2-base/README.md
+++ b/halo2-base/README.md
@@ -1,92 +1,92 @@
-# Halo2-base
+# `halo2-base`
-Halo2-base provides a streamlined frontend for interacting with the Halo2 API. It simplifies circuit programming to declaring constraints over a single advice and selector column and provides built-in circuit configuration and parellel proving and witness generation.
+`halo2-base` provides an embedded domain specific language (eDSL) for writing circuits with the [`halo2`](https://github.com/axiom-crypto/halo2) API. It simplifies circuit programming to declaring constraints over a single advice and selector column and provides built-in circuit tuning and support for multi-threaded witness generation.
-Programmed circuit constraints are stored in `GateThreadBuilder` as a `Vec` of `Context`'s. Each `Context` can be interpreted as a "virtual column" which tracks witness values and constraints but does not assign them as cells within the Halo2 backend. Conceptually, one can think that at circuit generation time, the virtual columns are all concatenated into a **single** virtual column. This virtual column is then re-distributed into the minimal number of true `Column`s (aka Plonkish arithmetization columns) to fit within a user-specified number of rows. These true columns are then assigned into the Plonkish arithemization using the vanilla Halo2 backend. This has several benefits:
+For further details, see the [Rust docs](https://axiom-crypto.github.io/halo2-lib/halo2_base/).
-- The user only needs to specify the desired number of rows. The rest of the circuit configuration process is done automatically because the optimal number of columns in the circuit can be calculated from the total number of cells in the `Context`s. This eliminates the need to manually assign circuit parameters at circuit creation time.
-- In addition, this simplifies the process of testing the performance of different circuit configurations (different Plonkish arithmetization shapes) in the Halo2 backend, since the same virtual columns in the `Context` can be re-distributed into different Plonkish arithmetization tables.
+## Virtual Region Managers
-A user can also parallelize witness generation by specifying a function and a `Vec` of inputs to perform in parallel using `parallelize_in()` which creates a separate `Context` for each input that performs the specified function. These "virtual columns" are then computed in parallel during witness generation and combined back into a single column "virtual column" before cell assignment in the Halo2 backend.
+The core framework under which `halo2-base` operates is that of _virtual cell management_. We perform witness generation in a virtual region (outside of the low-level raw halo2 `Circuit::synthesize`) and only at the very end map it to a "raw/physical" region in halo2's Plonkish arithmetization.
-All assigned values in a circuit are assigned in the Halo2 backend by calling `synthesize()` in `GateCircuitBuilder` (or [`RangeCircuitBuilder`](#rangecircuitbuilder)) which in turn invokes `assign_all()` (or `assign_threads_in` if only doing witness generation) in `GateThreadBuilder` to assign the witness values tracked in a `Context` to their respective `Column` in the circuit within the Halo2 backend.
+We formalize this into a new trait `VirtualRegionManager`. Any `VirtualRegionManager` is associated with some subset of columns (more generally, a physical Halo2 region). It can manage its own virtual region however it wants, but it must provide a deterministic way to map the virtual region to the physical region.
-Halo2-base also provides pre-built [Chips](https://zcash.github.io/halo2/concepts/chips.html) for common arithmetic operations in `GateChip` and range check arguments in `RangeChip`. Our `Chip` implementations differ slightly from ZCash's `Chip` implementations. In Zcash, the `Chip` struct stores knowledge about the `Config` and custom gates used. In halo2-base a `Chip` stores only functions while the interaction with the circuit's `Config` is hidden and done in `GateCircuitBuilder`.
+We have the following examples of virtual region managers:
-The structure of halo2-base is outlined as follows:
+- `SinglePhaseCoreManager`: this is associated with our `BasicGateConfig` which is a simple [vertical custom gate](https://docs.axiom.xyz/zero-knowledge-proofs/getting-started-with-halo2#simplified-interface), in a single halo2 challenge phase. It manages a virtual region with a bunch of virtual columns (these are the `Context`s). One can think of all virtual columns as being concatenated into a single big column. Then given the target number of rows in the physical circuit, it will chunk the single virtual column appropriately into multiple physical columns.
+- `CopyConstraintManager`: this is a global manager to allow virtual cells from different regions to be referenced. Virtual cells are referred to as `AssignedValue`. Despite the name (which is from historical reasons), these values are not actually assigned into the physical circuit. `AssignedValue`s are virtual cells. Instead they keep track of a tag for which virtual region they belong to, and some other identifying tag that loosely maps to a CPU thread. When a virtual cell is referenced and used, a copy is performed and the `CopyConstraintManager` keeps track of the equality. After the virtual cells are all physically assigned, this manager will impose the equality constraints on the physical cells.
+ - This manager also keeps track of constants that are used, deduplicates them, and assigns all constants into dedicated fixed columns. It also imposes the equality constraints between advice cells and the fixed cells.
+ - It is **very important** that all virtual region managers reference the same `CopyConstraintManager` to ensure that all copy constraints are managed properly. The `CopyConstraintManager` must also be raw assigned at the end of `Circuit::synthesize` to ensure the copy constraints are actually communicated to the raw halo2 API.
+- `LookupAnyManager`: for any kind of lookup argument (either into a fixed table or dynamic table), we do not want to enable this lookup argument on every column of the circuit since enabling lookup is expensive. Instead, we allocate special advice columns (with no selector) where the lookup argument is always on. When we want to look up certain values, we copy them over to the special advice cells. This also means that the physical location of the cells you want to look up can be unstructured.
-- `builder.rs`: Contains `GateThreadBuilder`, `GateCircuitBuilder`, and `RangeCircuitBuilder` which implement the logic to provide different arithmetization configurations with different performance tradeoffs in the Halo2 backend.
-- `lib.rs`: Defines the `QuantumCell`, `ContextCell`, `AssignedValue`, and `Context` types which track assigned values within a circuit across multiple columns and provide a streamlined interface to assign witness values directly to the advice column.
-- `utils.rs`: Contains `BigPrimeField` and `ScalerField` traits which represent field elements within Halo2 and provides methods to decompose field elements into `u64` limbs and convert between field elements and `BigUint`.
-- `flex_gate.rs`: Contains the implementation of `GateChip` and the `GateInstructions` trait which provide functions for basic arithmetic operations within Halo2.
-- `range.rs:`: Implements `RangeChip` and the `RangeInstructions` trait which provide functions for performing range check and other lookup argument operations.
+The virtual regions are also designed to be able to interact with raw halo2 sub-circuits. The overall architecture of a circuit that may use virtual regions managed by `halo2-lib` alongside raw halo2 sub-circuits looks as follows:
-This readme compliments the in-line documentation of halo2-base, providing an overview of `builder.rs` and `lib.rs`.
+![Virtual regions with raw sub-circuit](https://user-images.githubusercontent.com/31040440/263155207-c5246cb1-f7f5-4214-920c-d4ae34c19e9c.png)
-
+## [`BaseCircuitBuilder`](./src/gates/circuit/mod.rs)
-## [**Context**](src/lib.rs)
-
-`Context` holds all information of an execution trace (circuit and its witness values). `Context` represents a "virtual column" that stores unassigned constraint information in the Halo2 backend. Storing the circuit information in a `Context` rather than assigning it directly to the Halo2 backend allows for the pre-computation of circuit parameters and preserves the underlying circuit information allowing for its rearrangement into multiple columns for parallelization in the Halo2 backend.
-
-During `synthesize()`, the advice values of all `Context`s are concatenated into a single "virtual column" that is split into multiple true `Column`s at `break_points` each representing a different sub-section of the "virtual column". During circuit synthesis, all cells are assigned to Halo2 `AssignedCell`s in a single `Region` within Halo2's backend.
-
-For parallel witness generation, multiple `Context`s are created for each parallel operation. After parallel witness generation, these `Context`'s are combined to form a single "virtual column" as above. Note that while the witness generation can be multi-threaded, the ordering of the contents in each `Context`, and the order of the `Context`s themselves, must be deterministic.
-
-```rust ignore
-pub struct Context {
-
- witness_gen_only: bool,
-
- pub context_id: usize,
-
- pub advice: Vec>,
+A circuit builder in `halo2-lib` is a collection of virtual region managers with an associated raw halo2 configuration of columns and custom gates. The precise configuration of these columns and gates can potentially be tuned after witness generation has been performed. We do not yet codify the notion of a circuit builder into a trait.
- pub cells_to_lookup: Vec>,
+The core circuit builder used throughout `halo2-lib` is the `BaseCircuitBuilder`. It is associated to `BaseConfig`, which consists of instance columns together with either `FlexGateConfig` or `RangeConfig`: `FlexGateConfig` is used when no functionality involving bit range checks (usually necessary for less than comparisons on numbers) is needed, otherwise `RangeConfig` consists of `FlexGateConfig` together with a fixed lookup table for range checks.
- pub zero_cell: Option>,
+The basic construction of `BaseCircuitBuilder` is as follows:
- pub selector: Vec,
+```rust
+let k = 10; // your circuit will have 2^k rows
+let witness_gen_only = false; // constraints are ignored if set to true
+let mut builder = BaseCircuitBuilder::new(witness_gen_only).use_k(k);
+// If you need to use range checks, a good default is to set `lookup_bits` to 1 less than `k`
+let lookup_bits = k - 1;
+builder.set_lookup_bits(lookup_bits); // this can be skipped if you are not using range checks. The program will panic if `lookup_bits` is not set when you need range checks.
- pub advice_equality_constraints: Vec<(ContextCell, ContextCell)>,
-
- pub constant_equality_constraints: Vec<(F, ContextCell)>,
+// this is the struct holding basic our eDSL API functions
+let gate = GateChip::default();
+// if you need RangeChip, construct it with:
+let range = builder.range_chip(); // this will panic if `builder` did not set `lookup_bits`
+{
+ // basic usage:
+ let ctx = builder.main(0); // this is "similar" to spawning a new thread. 0 refers to the halo2 challenge phase
+ // do your computations
}
+// `builder` now contains all information from witness generation and constraints of your circuit
+let unusable_rows = 9; // this is usually enough, set to 20 or higher if program panics
+// This tunes your circuit to find the optimal configuration
+builder.calculate_params(Some(unusable_rows));
+
+// Now you can mock prove or prove your circuit:
+// If you have public instances, you must either provide them yourself or extract from `builder.assigned_instances`.
+MockProver::run(k as u32, &builder, instances).unwrap().assert_satisfied();
```
-`witness_gen_only` is set to `true` if we only care about witness generation and not about circuit constraints, otherwise it is set to false. This should **not** be set to `true` during mock proving or **key generation**. When this flag is `true`, we perform certain optimizations that are only valid when we don't care about constraints or selectors.
+### Proving mode
-A `Context` holds all equality and constant constraints as a `Vec` of `ContextCell` tuples representing the positions of the two cells to constrain. `advice` and`selector` store the respective column values of the `Context`'s which may represent the entire advice and selector column or a sub-section of the advice and selector column during parellel witness generation. `cells_to_lookup` tracks `AssignedValue`'s of cells to be looked up in a global lookup table, specifically for range checks, shared among all `Context`'s'.
+`witness_gen_only` is set to `true` if we only care about witness generation and not about circuit constraints, otherwise it is set to false. This should **not** be set to `true` during mock proving or **key generation**. When this flag is `true`, we perform certain optimizations that are only valid when we don't care about constraints or selectors. This should only be done in the context of real proving, when a proving key has already been created.
-### [**ContextCell**](./src/lib.rs):
+## [**Context**](src/lib.rs)
-`ContextCell` is a pointer to a specific cell within a `Context` identified by the Context's `context_id` and the cell's relative `offset` from the first cell of the advice column of the `Context`.
+`Context` holds all information of an execution trace (circuit and its witness values). `Context` represents a "virtual column" that stores unassigned constraint information in the Halo2 backend. Storing the circuit information in a `Context` rather than assigning it directly to the Halo2 backend allows for the pre-computation of circuit parameters and preserves the underlying circuit information allowing for its rearrangement into multiple columns for parallelization in the Halo2 backend.
-```rust ignore
-#[derive(Clone, Copy, Debug)]
-pub struct ContextCell {
- /// Identifier of the [Context] that this cell belongs to.
- pub context_id: usize,
- /// Relative offset of the cell within this [Context] advice column.
- pub offset: usize,
-}
-```
+During `synthesize()`, the advice values of all `Context`s are concatenated into a single "virtual column" that is split into multiple true `Column`s at `break_points` each representing a different sub-section of the "virtual column". During circuit synthesis, all cells are assigned to Halo2 `AssignedCell`s in a single `Region` within Halo2's backend.
+
+For parallel witness generation, multiple `Context`s are created for each parallel operation. After parallel witness generation, these `Context`'s are combined to form a single "virtual column" as above. Note that while the witness generation can be multi-threaded, the ordering of the contents in each `Context`, and the order of the `Context`s themselves, must be deterministic.
+
+**Warning:** If you create your own `Context` in a new virtual region not provided by our libraries, you must ensure that the `type_id: &str` of the context is a globally unique identifier for the virtual region, distinct from the other `type_id` strings used to identify other virtual regions. We suggest that you either include your crate name as a prefix in the `type_id` or use [`module_path!`](https://doc.rust-lang.org/std/macro.module_path.html) to generate a prefix.
+In the future we will introduce a macro to check this uniqueness at compile time.
### [**AssignedValue**](./src/lib.rs):
-`AssignedValue` represents a specific `Assigned` value assigned to a specific cell within a `Context` of a circuit referenced by a `ContextCell`.
+Despite the name, an `AssignedValue` is a **virtual cell**. It contains the actual witness value as well as a pointer to the location of the virtual cell within a virtual region. The pointer is given by type `ContextCell`. We only store the pointer when not in witness generation only mode as an optimization.
```rust ignore
pub struct AssignedValue {
pub value: Assigned,
-
pub cell: Option,
}
```
### [**Assigned**](./src/plonk/assigned.rs)
-`Assigned` is a wrapper enum for values assigned to a cell within a circuit which stores the value as a fraction and marks it for batched inversion using [Montgomery's trick](https://zcash.github.io/halo2/background/fields.html#montgomerys-trick). Performing batched inversion allows for the computation of the inverse of all marked values with a single inversion operation.
+`Assigned` is not a ZK or circuit-related type.
+`Assigned` is a wrapper enum for a field element which stores the value as a fraction and marks it for batched inversion using [Montgomery's trick](https://zcash.github.io/halo2/background/fields.html#montgomerys-trick). Performing batched inversion allows for the computation of the inverse of all marked values with a single inversion operation.
```rust ignore
pub enum Assigned {
@@ -99,21 +99,15 @@ pub enum Assigned {
}
```
-
-
## [**QuantumCell**](./src/lib.rs)
-`QuantumCell` is a helper enum that abstracts the scenarios in which a value is assigned to the advice column in Halo2-base. Without `QuantumCell` assigning existing or constant values to the advice column requires manually specifying the enforced constraints on top of assigning the value leading to bloated code. `QuantumCell` handles these technical operations, all a developer needs to do is specify which enum option in `QuantumCell` the value they are adding corresponds to.
+`QuantumCell` is a helper enum that abstracts the scenarios in which a value is assigned to the advice column in `halo2-base`. Without `QuantumCell`, assigning existing or constant values to the advice column requires manually specifying the enforced constraints on top of assigning the value leading to bloated code. `QuantumCell` handles these technical operations, all a developer needs to do is specify which enum option in `QuantumCell` the value they are adding corresponds to.
```rust ignore
pub enum QuantumCell {
-
Existing(AssignedValue),
-
Witness(F),
-
WitnessFraction(Assigned),
-
Constant(F),
}
```
@@ -123,468 +117,11 @@ QuantumCell contains the following enum variants.
- **Existing**:
Assigns a value to the advice column that exists within the advice column. The value is an existing value from some previous part of your computation already in the advice column in the form of an `AssignedValue`. When you add an existing cell into the table a new cell will be assigned into the advice column with value equal to the existing value. An equality constraint will then be added between the new cell and the "existing" cell so the Verifier has a guarantee that these two cells are always equal.
- ```rust ignore
- QuantumCell::Existing(acell) => {
- self.advice.push(acell.value);
-
- if !self.witness_gen_only {
- let new_cell =
- ContextCell { context_id: self.context_id, offset: self.advice.len() - 1 };
- self.advice_equality_constraints.push((new_cell, acell.cell.unwrap()));
- }
- }
- ```
-
- **Witness**:
Assigns an entirely new witness value into the advice column, such as a private input. When `assign_cell()` is called the value is wrapped in as an `Assigned::Trivial()` which marks it for exclusion from batch inversion.
- ```rust ignore
- QuantumCell::Witness(val) => {
- self.advice.push(Assigned::Trivial(val));
- }
- ```
-- **WitnessFraction**:
- Assigns an entirely new witness value to the advice column. `WitnessFraction` exists for optimization purposes and accepts Assigned values wrapped in `Assigned::Rational()` marked for batch inverion.
- ```rust ignore
- QuantumCell::WitnessFraction(val) => {
- self.advice.push(val);
- }
- ```
-- **Constant**:
- A value that is a "known" constant. A "known" refers to known at circuit creation time to both the Prover and Verifier. When you assign a constant value there exists another secret "Fixed" column in the circuit constraint table whose values are fixed at circuit creation time. When you assign a Constant value, you are adding this value to the Fixed column, adding the value as a witness to the Advice column, and then imposing an equality constraint between the two corresponding cells in the Fixed and Advice columns.
-
-```rust ignore
-QuantumCell::Constant(c) => {
- self.advice.push(Assigned::Trivial(c));
- // If witness generation is not performed, enforce equality constraints between the existing cell and the new cell
- if !self.witness_gen_only {
- let new_cell =
- ContextCell { context_id: self.context_id, offset: self.advice.len() - 1 };
- self.constant_equality_constraints.push((c, new_cell));
- }
-}
-```
-
-
-## [**GateThreadBuilder**](./src/gates/builder.rs) & [**GateCircuitBuilder**](./src/gates/builder.rs)
-
-`GateThreadBuilder` tracks the cell assignments of a circuit as an array of `Vec` of `Context`' where `threads[i]` contains all `Context`'s for phase `i`. Each array element corresponds to a distinct challenge phase of Halo2's proving system, each of which has its own unique set of rows and columns.
-
-```rust ignore
-#[derive(Clone, Debug, Default)]
-pub struct GateThreadBuilder {
- /// Threads for each challenge phase
- pub threads: [Vec>; MAX_PHASE],
- /// Max number of threads
- thread_count: usize,
- /// Flag for witness generation. If true, the gate thread builder is used for witness generation only.
- witness_gen_only: bool,
- /// The `unknown` flag is used during key generation. If true, during key generation witness [Value]s are replaced with Value::unknown() for safety.
- use_unknown: bool,
-}
-```
-
-Once a `GateThreadBuilder` is created, gates may be assigned to a `Context` (or in the case of parallel witness generation multiple `Context`'s) within `threads`. Once the circuit is written `config()` is called to pre-compute the circuits size and set the circuit's environment variables.
-
-[**config()**](./src/gates/builder.rs)
-
-```rust ignore
-pub fn config(&self, k: usize, minimum_rows: Option) -> FlexGateConfigParams {
- let max_rows = (1 << k) - minimum_rows.unwrap_or(0);
- let total_advice_per_phase = self
- .threads
- .iter()
- .map(|threads| threads.iter().map(|ctx| ctx.advice.len()).sum::())
- .collect::>();
- // we do a rough estimate by taking ceil(advice_cells_per_phase / 2^k )
- // if this is too small, manual configuration will be needed
- let num_advice_per_phase = total_advice_per_phase
- .iter()
- .map(|count| (count + max_rows - 1) / max_rows)
- .collect::>();
-
- let total_lookup_advice_per_phase = self
- .threads
- .iter()
- .map(|threads| threads.iter().map(|ctx| ctx.cells_to_lookup.len()).sum::())
- .collect::>();
- let num_lookup_advice_per_phase = total_lookup_advice_per_phase
- .iter()
- .map(|count| (count + max_rows - 1) / max_rows)
- .collect::>();
-
- let total_fixed: usize = HashSet::::from_iter(self.threads.iter().flat_map(|threads| {
- threads.iter().flat_map(|ctx| ctx.constant_equality_constraints.iter().map(|(c, _)| *c))
- }))
- .len();
- let num_fixed = (total_fixed + (1 << k) - 1) >> k;
-
- let params = FlexGateConfigParams {
- strategy: GateStrategy::Vertical,
- num_advice_per_phase,
- num_lookup_advice_per_phase,
- num_fixed,
- k,
- };
- #[cfg(feature = "display")]
- {
- for phase in 0..MAX_PHASE {
- if total_advice_per_phase[phase] != 0 || total_lookup_advice_per_phase[phase] != 0 {
- println!(
- "Gate Chip | Phase {}: {} advice cells , {} lookup advice cells",
- phase, total_advice_per_phase[phase], total_lookup_advice_per_phase[phase],
- );
- }
- }
- println!("Total {total_fixed} fixed cells");
- println!("Auto-calculated config params:\n {params:#?}");
- }
- std::env::set_var("FLEX_GATE_CONFIG_PARAMS", serde_json::to_string(¶ms).unwrap());
- params
-}
-```
-
-For circuit creation a `GateCircuitBuilder` is created by passing the `GateThreadBuilder` as an argument to `GateCircuitBuilder`'s `keygen`,`mock`, or `prover` functions. `GateCircuitBuilder` acts as a middleman between `GateThreadBuilder` and the Halo2 backend by implementing Halo2's`Circuit` Trait and calling into `GateThreadBuilder` `assign_all()` and `assign_threads_in()` functions to perform circuit assignment.
-
-**Note for developers:** We encourage you to always use [`RangeCircuitBuilder`](#rangecircuitbuilder) instead of `GateCircuitBuilder`: the former is smart enough to know to not create a lookup table if no cells are marked for lookup, so `RangeCircuitBuilder` is a strict generalization of `GateCircuitBuilder`.
-
-```rust ignore
-/// Vector of vectors tracking the thread break points across different halo2 phases
-pub type MultiPhaseThreadBreakPoints = Vec;
-
-#[derive(Clone, Debug)]
-pub struct GateCircuitBuilder {
- /// The Thread Builder for the circuit
- pub builder: RefCell>,
- /// Break points for threads within the circuit
- pub break_points: RefCell,
-}
-
-impl Circuit for GateCircuitBuilder {
- type Config = FlexGateConfig;
- type FloorPlanner = SimpleFloorPlanner;
-
- /// Creates a new instance of the circuit without withnesses filled in.
- fn without_witnesses(&self) -> Self {
- unimplemented!()
- }
-
- /// Configures a new circuit using the the parameters specified [Config].
- fn configure(meta: &mut ConstraintSystem) -> FlexGateConfig {
- let FlexGateConfigParams {
- strategy,
- num_advice_per_phase,
- num_lookup_advice_per_phase: _,
- num_fixed,
- k,
- } = serde_json::from_str(&std::env::var("FLEX_GATE_CONFIG_PARAMS").unwrap()).unwrap();
- FlexGateConfig::configure(meta, strategy, &num_advice_per_phase, num_fixed, k)
- }
-
- /// Performs the actual computation on the circuit (e.g., witness generation), filling in all the advice values for a particular proof.
- fn synthesize(
- &self,
- config: Self::Config,
- mut layouter: impl Layouter,
- ) -> Result<(), Error> {
- self.sub_synthesize(&config, &[], &[], &mut layouter);
- Ok(())
- }
-}
-```
-
-During circuit creation `synthesize()` is invoked which passes into `sub_synthesize()` a `FlexGateConfig` containing the actual circuits columns and a mutable reference to a `Layouter` from the Halo2 API which facilitates the final assignment of cells within a `Region` of a circuit in Halo2's backend.
-
-`GateCircuitBuilder` contains a list of breakpoints for each thread across all phases in and `GateThreadBuilder` itself. Both are wrapped in a `RefCell` allowing them to be borrowed mutably so the function performing circuit creation can take ownership of the `builder` and `break_points` can be recorded during circuit creation for later use.
-
-[**sub_synthesize()**](./src/gates/builder.rs)
-
-```rust ignore
- pub fn sub_synthesize(
- &self,
- gate: &FlexGateConfig,
- lookup_advice: &[Vec>],
- q_lookup: &[Option],
- layouter: &mut impl Layouter,
- ) -> HashMap<(usize, usize), (circuit::Cell, usize)> {
- let mut first_pass = SKIP_FIRST_PASS;
- let mut assigned_advices = HashMap::new();
- layouter
- .assign_region(
- || "GateCircuitBuilder generated circuit",
- |mut region| {
- if first_pass {
- first_pass = false;
- return Ok(());
- }
- // only support FirstPhase in this Builder because getting challenge value requires more specialized witness generation during synthesize
- // If we are not performing witness generation only, we can skip the first pass and assign threads directly
- if !self.builder.borrow().witness_gen_only {
- // clone the builder so we can re-use the circuit for both vk and pk gen
- let builder = self.builder.borrow().clone();
- for threads in builder.threads.iter().skip(1) {
- assert!(
- threads.is_empty(),
- "GateCircuitBuilder only supports FirstPhase for now"
- );
- }
- let assignments = builder.assign_all(
- gate,
- lookup_advice,
- q_lookup,
- &mut region,
- Default::default(),
- );
- *self.break_points.borrow_mut() = assignments.break_points;
- assigned_advices = assignments.assigned_advices;
- } else {
- // If we are only generating witness, we can skip the first pass and assign threads directly
- let builder = self.builder.take();
- let break_points = self.break_points.take();
- for (phase, (threads, break_points)) in builder
- .threads
- .into_iter()
- .zip(break_points.into_iter())
- .enumerate()
- .take(1)
- {
- assign_threads_in(
- phase,
- threads,
- gate,
- lookup_advice.get(phase).unwrap_or(&vec![]),
- &mut region,
- break_points,
- );
- }
- }
- Ok(())
- },
- )
- .unwrap();
- assigned_advices
- }
-```
-
-Within `sub_synthesize()` `layouter`'s `assign_region()` function is invoked which yields a mutable reference to `Region`. `region` is used to assign cells within a contiguous region of the circuit represented in Halo2's proving system.
-
-If `witness_gen_only` is not set within the `builder` (for keygen, and mock proving) `sub_synthesize` takes ownership of the `builder`, and calls `assign_all()` to assign all cells within this context to a circuit in Halo2's backend. The resulting column breakpoints are recorded in `GateCircuitBuilder`'s `break_points` field.
-
-`assign_all()` iterates over each `Context` within a `phase` and assigns the values and constraints of the advice, selector, fixed, and lookup columns to the circuit using `region`.
-
-Breakpoints for the advice column are assigned sequentially. If, the `row_offset` of the cell value being currently assigned exceeds the maximum amount of rows allowed in a column a new column is created.
-
-It should be noted this process is only compatible with the first phase of Halo2's proving system as retrieving witness challenges in later phases requires more specialized witness generation during synthesis. Therefore, `assign_all()` must assert all elements in `threads` are unassigned excluding the first phase.
-
-[**assign_all()**](./src/gates/builder.rs)
-
-```rust ignore
-pub fn assign_all(
- &self,
- config: &FlexGateConfig,
- lookup_advice: &[Vec>],
- q_lookup: &[Option],
- region: &mut Region,
- KeygenAssignments {
- mut assigned_advices,
- mut assigned_constants,
- mut break_points
- }: KeygenAssignments,
- ) -> KeygenAssignments {
- ...
- for (phase, threads) in self.threads.iter().enumerate() {
- let mut break_point = vec![];
- let mut gate_index = 0;
- let mut row_offset = 0;
- for ctx in threads {
- let mut basic_gate = config.basic_gates[phase]
- .get(gate_index)
- .unwrap_or_else(|| panic!("NOT ENOUGH ADVICE COLUMNS IN PHASE {phase}. Perhaps blinding factors were not taken into account. The max non-poisoned rows is {max_rows}"));
- assert_eq!(ctx.selector.len(), ctx.advice.len());
-
- for (i, (advice, &q)) in ctx.advice.iter().zip(ctx.selector.iter()).enumerate() {
- let column = basic_gate.value;
- let value = if use_unknown { Value::unknown() } else { Value::known(advice) };
- #[cfg(feature = "halo2-axiom")]
- let cell = *region.assign_advice(column, row_offset, value).cell();
- #[cfg(not(feature = "halo2-axiom"))]
- let cell = region
- .assign_advice(|| "", column, row_offset, || value.map(|v| *v))
- .unwrap()
- .cell();
- assigned_advices.insert((ctx.context_id, i), (cell, row_offset));
- ...
-
-```
-
-In the case a breakpoint falls on the overlap between two gates (such as chained addition of two cells) the cells the breakpoint falls on must be copied to the next column and a new equality constraint enforced between the value of the cell in the old column and the copied cell in the new column. This prevents the circuit from being undersconstratined and preserves the equality constraint from the overlapping gates.
-
-```rust ignore
-if (q && row_offset + 4 > max_rows) || row_offset >= max_rows - 1 {
- break_point.push(row_offset);
- row_offset = 0;
- gate_index += 1;
-
-// when there is a break point, because we may have two gates that overlap at the current cell, we must copy the current cell to the next column for safety
- basic_gate = config.basic_gates[phase]
- .get(gate_index)
- .unwrap_or_else(|| panic!("NOT ENOUGH ADVICE COLUMNS IN PHASE {phase}. Perhaps blinding factors were not taken into account. The max non-poisoned rows is {max_rows}"));
- let column = basic_gate.value;
-
- #[cfg(feature = "halo2-axiom")]
- {
- let ncell = region.assign_advice(column, row_offset, value);
- region.constrain_equal(ncell.cell(), &cell);
- }
- #[cfg(not(feature = "halo2-axiom"))]
- {
- let ncell = region
- .assign_advice(|| "", column, row_offset, || value.map(|v| *v))
- .unwrap()
- .cell();
- region.constrain_equal(ncell, cell).unwrap();
- }
-}
-
-```
-
-If `witness_gen_only` is set, only witness generation is performed, and no copy constraints or selector values are considered.
-
-Witness generation can be parallelized by a user by calling `parallelize_in()` and specifying a function and a `Vec` of inputs to perform in parallel. `parallelize_in()` creates a separate `Context` for each input that performs the specified function and appends them to the `Vec` of `Context`'s of a particular phase.
-
-[**assign_threads_in()**](./src/gates/builder.rs)
-
-```rust ignore
-pub fn assign_threads_in(
- phase: usize,
- threads: Vec>,
- config: &FlexGateConfig,
- lookup_advice: &[Column],
- region: &mut Region,
- break_points: ThreadBreakPoints,
-) {
- if config.basic_gates[phase].is_empty() {
- assert!(threads.is_empty(), "Trying to assign threads in a phase with no columns");
- return;
- }
-
- let mut break_points = break_points.into_iter();
- let mut break_point = break_points.next();
-
- let mut gate_index = 0;
- let mut column = config.basic_gates[phase][gate_index].value;
- let mut row_offset = 0;
-
- let mut lookup_offset = 0;
- let mut lookup_advice = lookup_advice.iter();
- let mut lookup_column = lookup_advice.next();
- for ctx in threads {
- // if lookup_column is [None], that means there should be a single advice column and it has lookup enabled, so we don't need to copy to special lookup advice columns
- if lookup_column.is_some() {
- for advice in ctx.cells_to_lookup {
- if lookup_offset >= config.max_rows {
- lookup_offset = 0;
- lookup_column = lookup_advice.next();
- }
- // Assign the lookup advice values to the lookup_column
- let value = advice.value;
- let lookup_column = *lookup_column.unwrap();
- #[cfg(feature = "halo2-axiom")]
- region.assign_advice(lookup_column, lookup_offset, Value::known(value));
- #[cfg(not(feature = "halo2-axiom"))]
- region
- .assign_advice(|| "", lookup_column, lookup_offset, || Value::known(value))
- .unwrap();
-
- lookup_offset += 1;
- }
- }
- // Assign advice values to the advice columns in each [Context]
- for advice in ctx.advice {
- #[cfg(feature = "halo2-axiom")]
- region.assign_advice(column, row_offset, Value::known(advice));
- #[cfg(not(feature = "halo2-axiom"))]
- region.assign_advice(|| "", column, row_offset, || Value::known(advice)).unwrap();
-
- if break_point == Some(row_offset) {
- break_point = break_points.next();
- row_offset = 0;
- gate_index += 1;
- column = config.basic_gates[phase][gate_index].value;
-
- #[cfg(feature = "halo2-axiom")]
- region.assign_advice(column, row_offset, Value::known(advice));
- #[cfg(not(feature = "halo2-axiom"))]
- region.assign_advice(|| "", column, row_offset, || Value::known(advice)).unwrap();
- }
-
- row_offset += 1;
- }
- }
-
-```
-
-`sub_synthesize` iterates over all phases and calls `assign_threads_in()` for that phase. `assign_threads_in()` iterates over all `Context`s within that phase and assigns all lookup and advice values in the `Context`, creating a new advice column at every pre-computed "breakpoint" by incrementing `gate_index` and assigning `column` to a new `Column` found at `config.basic_gates[phase][gate_index].value`.
-
-## [**RangeCircuitBuilder**](./src/gates/builder.rs)
-
-`RangeCircuitBuilder` is a wrapper struct around `GateCircuitBuilder`. Like `GateCircuitBuilder` it acts as a middleman between `GateThreadBuilder` and the Halo2 backend by implementing Halo2's `Circuit` Trait.
-
-```rust ignore
-#[derive(Clone, Debug)]
-pub struct RangeCircuitBuilder(pub GateCircuitBuilder);
-
-impl Circuit for RangeCircuitBuilder {
- type Config = RangeConfig;
- type FloorPlanner = SimpleFloorPlanner;
-
- /// Creates a new instance of the [RangeCircuitBuilder] without witnesses by setting the witness_gen_only flag to false
- fn without_witnesses(&self) -> Self {
- unimplemented!()
- }
-
- /// Configures a new circuit using the the parameters specified [Config] and environment variable `LOOKUP_BITS`.
- fn configure(meta: &mut ConstraintSystem) -> Self::Config {
- let FlexGateConfigParams {
- strategy,
- num_advice_per_phase,
- num_lookup_advice_per_phase,
- num_fixed,
- k,
- } = serde_json::from_str(&var("FLEX_GATE_CONFIG_PARAMS").unwrap()).unwrap();
- let strategy = match strategy {
- GateStrategy::Vertical => RangeStrategy::Vertical,
- };
- let lookup_bits = var("LOOKUP_BITS").unwrap_or_else(|_| "0".to_string()).parse().unwrap();
- RangeConfig::configure(
- meta,
- strategy,
- &num_advice_per_phase,
- &num_lookup_advice_per_phase,
- num_fixed,
- lookup_bits,
- k,
- )
- }
-
- /// Performs the actual computation on the circuit (e.g., witness generation), populating the lookup table and filling in all the advice values for a particular proof.
- fn synthesize(
- &self,
- config: Self::Config,
- mut layouter: impl Layouter,
- ) -> Result<(), Error> {
- // only load lookup table if we are actually doing lookups
- if config.lookup_advice.iter().map(|a| a.len()).sum::() != 0
- || !config.q_lookup.iter().all(|q| q.is_none())
- {
- config.load_lookup_table(&mut layouter).expect("load lookup table should not fail");
- }
- self.0.sub_synthesize(&config.gate, &config.lookup_advice, &config.q_lookup, &mut layouter);
- Ok(())
- }
-}
-```
-
-`RangeCircuitBuilder` differs from `GateCircuitBuilder` in that it contains a `RangeConfig` instead of a `FlexGateConfig` as its `Config`. `RangeConfig` contains a `lookup` table needed to declare lookup arguments within Halo2's backend. When creating a circuit that uses lookup tables `GateThreadBuilder` must be wrapped with `RangeCircuitBuilder` instead of `GateCircuitBuilder` otherwise circuit synthesis will fail as a lookup table is not present within the Halo2 backend.
+- **WitnessFraction**:
+ Assigns an entirely new witness value to the advice column. `WitnessFraction` exists for optimization purposes and accepts Assigned values wrapped in `Assigned::Rational()` marked for batch inverion (see [Assigned](#assigned)).
-**Note:** We encourage you to always use `RangeCircuitBuilder` instead of `GateCircuitBuilder`: the former is smart enough to know to not create a lookup table if no cells are marked for lookup, so `RangeCircuitBuilder` is a strict generalization of `GateCircuitBuilder`.
+- **Constant**:
+ A value that is a "known" constant. A "known" refers to known at circuit creation time to both the Prover and Verifier. When you assign a constant value there exists another secret Fixed column in the circuit constraint table whose values are fixed at circuit creation time. When you assign a Constant value, you are adding this value to the Fixed column, adding the value as a witness to the Advice column, and then imposing an equality constraint between the two corresponding cells in the Fixed and Advice columns.
diff --git a/halo2-base/benches/inner_product.rs b/halo2-base/benches/inner_product.rs
index 9454faa3..45f503b9 100644
--- a/halo2-base/benches/inner_product.rs
+++ b/halo2-base/benches/inner_product.rs
@@ -1,28 +1,17 @@
-#![allow(unused_imports)]
-#![allow(unused_variables)]
-use halo2_base::gates::builder::{GateCircuitBuilder, GateThreadBuilder};
-use halo2_base::gates::flex_gate::{FlexGateConfig, GateChip, GateInstructions, GateStrategy};
+use halo2_base::gates::circuit::{builder::RangeCircuitBuilder, CircuitBuilderStage};
+use halo2_base::gates::flex_gate::{GateChip, GateInstructions};
use halo2_base::halo2_proofs::{
arithmetic::Field,
- circuit::*,
dev::MockProver,
- halo2curves::bn256::{Bn256, Fr, G1Affine},
+ halo2curves::bn256::{Bn256, Fr},
plonk::*,
- poly::kzg::{
- commitment::{KZGCommitmentScheme, ParamsKZG},
- multiopen::ProverSHPLONK,
- },
- transcript::{Blake2bWrite, Challenge255, TranscriptWriterBuffer},
+ poly::kzg::commitment::ParamsKZG,
};
+use halo2_base::utils::testing::gen_proof;
use halo2_base::utils::ScalarField;
-use halo2_base::{
- Context,
- QuantumCell::{Existing, Witness},
- SKIP_FIRST_PASS,
-};
+use halo2_base::{Context, QuantumCell::Existing};
use itertools::Itertools;
use rand::rngs::OsRng;
-use std::marker::PhantomData;
use criterion::{criterion_group, criterion_main};
use criterion::{BenchmarkId, Criterion};
@@ -47,20 +36,20 @@ fn inner_prod_bench(ctx: &mut Context, a: Vec, b: Vec)
fn bench(c: &mut Criterion) {
let k = 19u32;
// create circuit for keygen
- let mut builder = GateThreadBuilder::new(false);
+ let mut builder =
+ RangeCircuitBuilder::from_stage(CircuitBuilderStage::Keygen).use_k(k as usize);
inner_prod_bench(builder.main(0), vec![Fr::zero(); 5], vec![Fr::zero(); 5]);
- builder.config(k as usize, Some(20));
- let circuit = GateCircuitBuilder::mock(builder);
+ let config_params = builder.calculate_params(Some(20));
// check the circuit is correct just in case
- MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied();
+ MockProver::run(k, &builder, vec![]).unwrap().assert_satisfied();
let params = ParamsKZG::::setup(k, OsRng);
- let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail");
- let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail");
+ let vk = keygen_vk(¶ms, &builder).expect("vk should not fail");
+ let pk = keygen_pk(¶ms, vk, &builder).expect("pk should not fail");
- let break_points = circuit.break_points.take();
- drop(circuit);
+ let break_points = builder.break_points();
+ drop(builder);
let mut group = c.benchmark_group("plonk-prover");
group.sample_size(10);
@@ -69,22 +58,12 @@ fn bench(c: &mut Criterion) {
&(¶ms, &pk),
|bencher, &(params, pk)| {
bencher.iter(|| {
- let mut builder = GateThreadBuilder::new(true);
+ let mut builder =
+ RangeCircuitBuilder::prover(config_params.clone(), break_points.clone());
let a = (0..5).map(|_| Fr::random(OsRng)).collect_vec();
let b = (0..5).map(|_| Fr::random(OsRng)).collect_vec();
inner_prod_bench(builder.main(0), a, b);
- let circuit = GateCircuitBuilder::prover(builder, break_points.clone());
-
- let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);
- create_proof::<
- KZGCommitmentScheme,
- ProverSHPLONK<'_, Bn256>,
- Challenge255,
- _,
- Blake2bWrite, G1Affine, Challenge255<_>>,
- _,
- >(params, pk, &[circuit], &[&[]], OsRng, &mut transcript)
- .expect("prover should not fail");
+ gen_proof(params, pk, builder);
})
},
);
diff --git a/halo2-base/benches/mul.rs b/halo2-base/benches/mul.rs
index 16687e08..ee239abd 100644
--- a/halo2-base/benches/mul.rs
+++ b/halo2-base/benches/mul.rs
@@ -1,15 +1,12 @@
-use ff::Field;
-use halo2_base::gates::builder::{GateCircuitBuilder, GateThreadBuilder};
+use halo2_base::gates::circuit::{builder::RangeCircuitBuilder, CircuitBuilderStage};
use halo2_base::gates::flex_gate::{GateChip, GateInstructions};
use halo2_base::halo2_proofs::{
- halo2curves::bn256::{Bn256, Fr, G1Affine},
+ halo2curves::bn256::{Bn256, Fr},
+ halo2curves::ff::Field,
plonk::*,
- poly::kzg::{
- commitment::{KZGCommitmentScheme, ParamsKZG},
- multiopen::ProverGWC,
- },
- transcript::{Blake2bWrite, Challenge255, TranscriptWriterBuffer},
+ poly::kzg::commitment::ParamsKZG,
};
+use halo2_base::utils::testing::gen_proof;
use halo2_base::utils::ScalarField;
use halo2_base::Context;
use rand::rngs::OsRng;
@@ -34,16 +31,16 @@ fn mul_bench(ctx: &mut Context, inputs: [F; 2]) {
fn bench(c: &mut Criterion) {
// create circuit for keygen
- let mut builder = GateThreadBuilder::new(false);
+ let mut builder =
+ RangeCircuitBuilder::from_stage(CircuitBuilderStage::Keygen).use_k(K as usize);
mul_bench(builder.main(0), [Fr::zero(); 2]);
- builder.config(K as usize, Some(9));
- let circuit = GateCircuitBuilder::keygen(builder);
+ let config_params = builder.calculate_params(Some(9));
let params = ParamsKZG::::setup(K, OsRng);
- let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail");
- let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail");
+ let vk = keygen_vk(¶ms, &builder).expect("vk should not fail");
+ let pk = keygen_pk(¶ms, vk, &builder).expect("pk should not fail");
- let break_points = circuit.break_points.take();
+ let break_points = builder.break_points();
let a = Fr::random(OsRng);
let b = Fr::random(OsRng);
@@ -53,21 +50,12 @@ fn bench(c: &mut Criterion) {
&(¶ms, &pk, [a, b]),
|bencher, &(params, pk, inputs)| {
bencher.iter(|| {
- let mut builder = GateThreadBuilder::new(true);
+ let mut builder =
+ RangeCircuitBuilder::prover(config_params.clone(), break_points.clone());
// do the computation
mul_bench(builder.main(0), inputs);
- let circuit = GateCircuitBuilder::prover(builder, break_points.clone());
- let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);
- create_proof::<
- KZGCommitmentScheme,
- ProverGWC<'_, Bn256>,
- Challenge255,
- _,
- Blake2bWrite, G1Affine, Challenge255<_>>,
- _,
- >(params, pk, &[circuit], &[&[]], OsRng, &mut transcript)
- .unwrap();
+ gen_proof(params, pk, builder);
})
},
);
diff --git a/halo2-base/examples/inner_product.rs b/halo2-base/examples/inner_product.rs
index 8572817e..c1413211 100644
--- a/halo2-base/examples/inner_product.rs
+++ b/halo2-base/examples/inner_product.rs
@@ -1,95 +1,39 @@
-#![allow(unused_imports)]
-#![allow(unused_variables)]
-use halo2_base::gates::builder::{GateCircuitBuilder, GateThreadBuilder};
-use halo2_base::gates::flex_gate::{FlexGateConfig, GateChip, GateInstructions, GateStrategy};
-use halo2_base::halo2_proofs::{
- arithmetic::Field,
- circuit::*,
- dev::MockProver,
- halo2curves::bn256::{Bn256, Fr, G1Affine},
- plonk::*,
- poly::kzg::multiopen::VerifierSHPLONK,
- poly::kzg::strategy::SingleStrategy,
- poly::kzg::{
- commitment::{KZGCommitmentScheme, ParamsKZG},
- multiopen::ProverSHPLONK,
- },
- transcript::{Blake2bRead, TranscriptReadBuffer},
- transcript::{Blake2bWrite, Challenge255, TranscriptWriterBuffer},
-};
+#![cfg(feature = "test-utils")]
+use halo2_base::gates::flex_gate::{GateChip, GateInstructions};
+use halo2_base::gates::RangeInstructions;
+use halo2_base::halo2_proofs::{arithmetic::Field, halo2curves::bn256::Fr};
+use halo2_base::utils::testing::base_test;
use halo2_base::utils::ScalarField;
-use halo2_base::{
- Context,
- QuantumCell::{Existing, Witness},
- SKIP_FIRST_PASS,
-};
+use halo2_base::{Context, QuantumCell::Existing};
use itertools::Itertools;
use rand::rngs::OsRng;
-use std::marker::PhantomData;
-
-use criterion::{criterion_group, criterion_main};
-use criterion::{BenchmarkId, Criterion};
-
-use pprof::criterion::{Output, PProfProfiler};
-// Thanks to the example provided by @jebbow in his article
-// https://www.jibbow.com/posts/criterion-flamegraphs/
const K: u32 = 19;
-fn inner_prod_bench(ctx: &mut Context, a: Vec, b: Vec) {
+fn inner_prod_bench(
+ ctx: &mut Context,
+ gate: &GateChip,
+ a: Vec,
+ b: Vec,
+) {
assert_eq!(a.len(), b.len());
let a = ctx.assign_witnesses(a);
let b = ctx.assign_witnesses(b);
- let chip = GateChip::default();
for _ in 0..(1 << K) / 16 - 10 {
- chip.inner_product(ctx, a.clone(), b.clone().into_iter().map(Existing));
+ gate.inner_product(ctx, a.clone(), b.clone().into_iter().map(Existing));
}
}
fn main() {
- let k = 10u32;
- // create circuit for keygen
- let mut builder = GateThreadBuilder::new(false);
- inner_prod_bench(builder.main(0), vec![Fr::zero(); 5], vec![Fr::zero(); 5]);
- builder.config(k as usize, Some(20));
- let circuit = GateCircuitBuilder::mock(builder);
-
- // check the circuit is correct just in case
- MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied();
-
- let params = ParamsKZG::::setup(k, OsRng);
- let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail");
- let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail");
-
- let break_points = circuit.break_points.take();
-
- let mut builder = GateThreadBuilder::new(true);
- let a = (0..5).map(|_| Fr::random(OsRng)).collect_vec();
- let b = (0..5).map(|_| Fr::random(OsRng)).collect_vec();
- inner_prod_bench(builder.main(0), a, b);
- let circuit = GateCircuitBuilder::prover(builder, break_points);
-
- let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);
- create_proof::<
- KZGCommitmentScheme,
- ProverSHPLONK<'_, Bn256>,
- Challenge255,
- _,
- Blake2bWrite, G1Affine, Challenge255<_>>,
- _,
- >(¶ms, &pk, &[circuit], &[&[]], OsRng, &mut transcript)
- .expect("prover should not fail");
-
- let strategy = SingleStrategy::new(¶ms);
- let proof = transcript.finalize();
- let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]);
- verify_proof::<
- KZGCommitmentScheme,
- VerifierSHPLONK<'_, Bn256>,
- Challenge255,
- Blake2bRead<&[u8], G1Affine, Challenge255>,
- _,
- >(¶ms, pk.get_vk(), strategy, &[&[]], &mut transcript)
- .unwrap();
+ base_test().k(12).bench_builder(
+ (vec![Fr::ZERO; 5], vec![Fr::ZERO; 5]),
+ (
+ (0..5).map(|_| Fr::random(OsRng)).collect_vec(),
+ (0..5).map(|_| Fr::random(OsRng)).collect_vec(),
+ ),
+ |pool, range, (a, b)| {
+ inner_prod_bench(pool.main(), range.gate(), a, b);
+ },
+ );
}
diff --git a/halo2-base/src/gates/builder.rs b/halo2-base/src/gates/builder.rs
deleted file mode 100644
index 22c2ce93..00000000
--- a/halo2-base/src/gates/builder.rs
+++ /dev/null
@@ -1,796 +0,0 @@
-use super::{
- flex_gate::{FlexGateConfig, GateStrategy, MAX_PHASE},
- range::{RangeConfig, RangeStrategy},
-};
-use crate::{
- halo2_proofs::{
- circuit::{self, Layouter, Region, SimpleFloorPlanner, Value},
- plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector},
- },
- utils::ScalarField,
- AssignedValue, Context, SKIP_FIRST_PASS,
-};
-use serde::{Deserialize, Serialize};
-use std::{
- cell::RefCell,
- collections::{HashMap, HashSet},
- env::{set_var, var},
-};
-
-mod parallelize;
-pub use parallelize::*;
-
-/// Vector of thread advice column break points
-pub type ThreadBreakPoints = Vec;
-/// Vector of vectors tracking the thread break points across different halo2 phases
-pub type MultiPhaseThreadBreakPoints = Vec;
-
-/// Stores the cell values loaded during the Keygen phase of a halo2 proof and breakpoints for multi-threading
-#[derive(Clone, Debug, Default)]
-pub struct KeygenAssignments {
- /// Advice assignments
- pub assigned_advices: HashMap<(usize, usize), (circuit::Cell, usize)>, // (key = ContextCell, value = (circuit::Cell, row offset))
- /// Constant assignments in Fixes Assignments
- pub assigned_constants: HashMap, // (key = constant, value = circuit::Cell)
- /// Advice column break points for threads in each phase.
- pub break_points: MultiPhaseThreadBreakPoints,
-}
-
-/// Builds the process for gate threading
-#[derive(Clone, Debug, Default)]
-pub struct GateThreadBuilder {
- /// Threads for each challenge phase
- pub threads: [Vec>; MAX_PHASE],
- /// Max number of threads
- thread_count: usize,
- /// Flag for witness generation. If true, the gate thread builder is used for witness generation only.
- pub witness_gen_only: bool,
- /// The `unknown` flag is used during key generation. If true, during key generation witness [Value]s are replaced with Value::unknown() for safety.
- use_unknown: bool,
-}
-
-impl GateThreadBuilder {
- /// Creates a new [GateThreadBuilder] and spawns a main thread in phase 0.
- /// * `witness_gen_only`: If true, the [GateThreadBuilder] is used for witness generation only.
- /// * If true, the gate thread builder only does witness asignments and does not store constraint information -- this should only be used for the real prover.
- /// * If false, the gate thread builder is used for keygen and mock prover (it can also be used for real prover) and the builder stores circuit information (e.g. copy constraints, fixed columns, enabled selectors).
- /// * These values are fixed for the circuit at key generation time, and they do not need to be re-computed by the prover in the actual proving phase.
- pub fn new(witness_gen_only: bool) -> Self {
- let mut threads = [(); MAX_PHASE].map(|_| vec![]);
- // start with a main thread in phase 0
- threads[0].push(Context::new(witness_gen_only, 0));
- Self { threads, thread_count: 1, witness_gen_only, use_unknown: false }
- }
-
- /// Creates a new [GateThreadBuilder] with `witness_gen_only` set to false.
- ///
- /// Performs the witness assignment computations and then checks using normal programming logic whether the gate constraints are all satisfied.
- pub fn mock() -> Self {
- Self::new(false)
- }
-
- /// Creates a new [GateThreadBuilder] with `witness_gen_only` set to false.
- ///
- /// Performs the witness assignment computations and generates prover and verifier keys.
- pub fn keygen() -> Self {
- Self::new(false)
- }
-
- /// Creates a new [GateThreadBuilder] with `witness_gen_only` set to true.
- ///
- /// Performs the witness assignment computations and then runs the proving system.
- pub fn prover() -> Self {
- Self::new(true)
- }
-
- /// Creates a new [GateThreadBuilder] with `use_unknown` flag set.
- /// * `use_unknown`: If true, during key generation witness [Value]s are replaced with Value::unknown() for safety.
- pub fn unknown(self, use_unknown: bool) -> Self {
- Self { use_unknown, ..self }
- }
-
- /// Returns a mutable reference to the [Context] of a gate thread. Spawns a new thread for the given phase, if none exists.
- /// * `phase`: The challenge phase (as an index) of the gate thread.
- pub fn main(&mut self, phase: usize) -> &mut Context {
- if self.threads[phase].is_empty() {
- self.new_thread(phase)
- } else {
- self.threads[phase].last_mut().unwrap()
- }
- }
-
- /// Returns the `witness_gen_only` flag.
- pub fn witness_gen_only(&self) -> bool {
- self.witness_gen_only
- }
-
- /// Returns the `use_unknown` flag.
- pub fn use_unknown(&self) -> bool {
- self.use_unknown
- }
-
- /// Returns the current number of threads in the [GateThreadBuilder].
- pub fn thread_count(&self) -> usize {
- self.thread_count
- }
-
- /// Creates a new thread id by incrementing the `thread count`
- pub fn get_new_thread_id(&mut self) -> usize {
- let thread_id = self.thread_count;
- self.thread_count += 1;
- thread_id
- }
-
- /// Spawns a new thread for a new given `phase`. Returns a mutable reference to the [Context] of the new thread.
- /// * `phase`: The phase (index) of the gate thread.
- pub fn new_thread(&mut self, phase: usize) -> &mut Context {
- let thread_id = self.thread_count;
- self.thread_count += 1;
- self.threads[phase].push(Context::new(self.witness_gen_only, thread_id));
- self.threads[phase].last_mut().unwrap()
- }
-
- /// Auto-calculates configuration parameters for the circuit
- ///
- /// * `k`: The number of in the circuit (i.e. numeber of rows = 2k)
- /// * `minimum_rows`: The minimum number of rows in the circuit that cannot be used for witness assignments and contain random `blinding factors` to ensure zk property, defaults to 0.
- pub fn config(&self, k: usize, minimum_rows: Option) -> FlexGateConfigParams {
- let max_rows = (1 << k) - minimum_rows.unwrap_or(0);
- let total_advice_per_phase = self
- .threads
- .iter()
- .map(|threads| threads.iter().map(|ctx| ctx.advice.len()).sum::())
- .collect::>();
- // we do a rough estimate by taking ceil(advice_cells_per_phase / 2^k )
- // if this is too small, manual configuration will be needed
- let num_advice_per_phase = total_advice_per_phase
- .iter()
- .map(|count| (count + max_rows - 1) / max_rows)
- .collect::>();
-
- let total_lookup_advice_per_phase = self
- .threads
- .iter()
- .map(|threads| threads.iter().map(|ctx| ctx.cells_to_lookup.len()).sum::())
- .collect::>();
- let num_lookup_advice_per_phase = total_lookup_advice_per_phase
- .iter()
- .map(|count| (count + max_rows - 1) / max_rows)
- .collect::>();
-
- let total_fixed: usize = HashSet::::from_iter(self.threads.iter().flat_map(|threads| {
- threads.iter().flat_map(|ctx| ctx.constant_equality_constraints.iter().map(|(c, _)| *c))
- }))
- .len();
- let num_fixed = (total_fixed + (1 << k) - 1) >> k;
-
- let params = FlexGateConfigParams {
- strategy: GateStrategy::Vertical,
- num_advice_per_phase,
- num_lookup_advice_per_phase,
- num_fixed,
- k,
- };
- #[cfg(feature = "display")]
- {
- for phase in 0..MAX_PHASE {
- if total_advice_per_phase[phase] != 0 || total_lookup_advice_per_phase[phase] != 0 {
- println!(
- "Gate Chip | Phase {}: {} advice cells , {} lookup advice cells",
- phase, total_advice_per_phase[phase], total_lookup_advice_per_phase[phase],
- );
- }
- }
- println!("Total {total_fixed} fixed cells");
- log::info!("Auto-calculated config params:\n {params:#?}");
- }
- set_var("FLEX_GATE_CONFIG_PARAMS", serde_json::to_string(¶ms).unwrap());
- params
- }
-
- /// Assigns all advice and fixed cells, turns on selectors, and imposes equality constraints.
- ///
- /// Returns the assigned advices, and constants in the form of [KeygenAssignments].
- ///
- /// Assumes selector and advice columns are already allocated and of the same length.
- ///
- /// Note: `assign_all()` **should** be called during keygen or if using mock prover. It also works for the real prover, but there it is more optimal to use [`assign_threads_in`] instead.
- /// * `config`: The [FlexGateConfig] of the circuit.
- /// * `lookup_advice`: The lookup advice columns.
- /// * `q_lookup`: The lookup advice selectors.
- /// * `region`: The [Region] of the circuit.
- /// * `assigned_advices`: The assigned advice cells.
- /// * `assigned_constants`: The assigned fixed cells.
- /// * `break_points`: The break points of the circuit.
- pub fn assign_all(
- &self,
- config: &FlexGateConfig,
- lookup_advice: &[Vec>],
- q_lookup: &[Option],
- region: &mut Region,
- KeygenAssignments {
- mut assigned_advices,
- mut assigned_constants,
- mut break_points
- }: KeygenAssignments,
- ) -> KeygenAssignments {
- let use_unknown = self.use_unknown;
- let max_rows = config.max_rows;
- let mut fixed_col = 0;
- let mut fixed_offset = 0;
- for (phase, threads) in self.threads.iter().enumerate() {
- let mut break_point = vec![];
- let mut gate_index = 0;
- let mut row_offset = 0;
- for ctx in threads {
- let mut basic_gate = config.basic_gates[phase]
- .get(gate_index)
- .unwrap_or_else(|| panic!("NOT ENOUGH ADVICE COLUMNS IN PHASE {phase}. Perhaps blinding factors were not taken into account. The max non-poisoned rows is {max_rows}"));
- assert_eq!(ctx.selector.len(), ctx.advice.len());
-
- for (i, (advice, &q)) in ctx.advice.iter().zip(ctx.selector.iter()).enumerate() {
- let column = basic_gate.value;
- let value = if use_unknown { Value::unknown() } else { Value::known(advice) };
- #[cfg(feature = "halo2-axiom")]
- let cell = *region.assign_advice(column, row_offset, value).cell();
- #[cfg(not(feature = "halo2-axiom"))]
- let cell = region
- .assign_advice(|| "", column, row_offset, || value.map(|v| *v))
- .unwrap()
- .cell();
- assigned_advices.insert((ctx.context_id, i), (cell, row_offset));
-
- // If selector enabled and row_offset is valid add break point to Keygen Assignments, account for break point overlap, and enforce equality constraint for gate outputs.
- if (q && row_offset + 4 > max_rows) || row_offset >= max_rows - 1 {
- break_point.push(row_offset);
- row_offset = 0;
- gate_index += 1;
-
- // when there is a break point, because we may have two gates that overlap at the current cell, we must copy the current cell to the next column for safety
- basic_gate = config.basic_gates[phase]
- .get(gate_index)
- .unwrap_or_else(|| panic!("NOT ENOUGH ADVICE COLUMNS IN PHASE {phase}. Perhaps blinding factors were not taken into account. The max non-poisoned rows is {max_rows}"));
- let column = basic_gate.value;
-
- #[cfg(feature = "halo2-axiom")]
- {
- let ncell = region.assign_advice(column, row_offset, value);
- region.constrain_equal(ncell.cell(), &cell);
- }
- #[cfg(not(feature = "halo2-axiom"))]
- {
- let ncell = region
- .assign_advice(|| "", column, row_offset, || value.map(|v| *v))
- .unwrap()
- .cell();
- region.constrain_equal(ncell, cell).unwrap();
- }
- }
-
- if q {
- basic_gate
- .q_enable
- .enable(region, row_offset)
- .expect("enable selector should not fail");
- }
-
- row_offset += 1;
- }
- // Assign fixed cells
- for (c, _) in ctx.constant_equality_constraints.iter() {
- if assigned_constants.get(c).is_none() {
- #[cfg(feature = "halo2-axiom")]
- let cell =
- region.assign_fixed(config.constants[fixed_col], fixed_offset, c);
- #[cfg(not(feature = "halo2-axiom"))]
- let cell = region
- .assign_fixed(
- || "",
- config.constants[fixed_col],
- fixed_offset,
- || Value::known(*c),
- )
- .unwrap()
- .cell();
- assigned_constants.insert(*c, cell);
- fixed_col += 1;
- if fixed_col >= config.constants.len() {
- fixed_col = 0;
- fixed_offset += 1;
- }
- }
- }
- }
- break_points.push(break_point);
- }
- // we constrain equality constraints in a separate loop in case context `i` contains references to context `j` for `j > i`
- for (phase, threads) in self.threads.iter().enumerate() {
- let mut lookup_offset = 0;
- let mut lookup_col = 0;
- for ctx in threads {
- for (left, right) in &ctx.advice_equality_constraints {
- let (left, _) = assigned_advices[&(left.context_id, left.offset)];
- let (right, _) = assigned_advices[&(right.context_id, right.offset)];
- #[cfg(feature = "halo2-axiom")]
- region.constrain_equal(&left, &right);
- #[cfg(not(feature = "halo2-axiom"))]
- region.constrain_equal(left, right).unwrap();
- }
- for (left, right) in &ctx.constant_equality_constraints {
- let left = assigned_constants[left];
- let (right, _) = assigned_advices[&(right.context_id, right.offset)];
- #[cfg(feature = "halo2-axiom")]
- region.constrain_equal(&left, &right);
- #[cfg(not(feature = "halo2-axiom"))]
- region.constrain_equal(left, right).unwrap();
- }
-
- for advice in &ctx.cells_to_lookup {
- // if q_lookup is Some, that means there should be a single advice column and it has lookup enabled
- let cell = advice.cell.unwrap();
- let (acell, row_offset) = assigned_advices[&(cell.context_id, cell.offset)];
- if let Some(q_lookup) = q_lookup[phase] {
- assert_eq!(config.basic_gates[phase].len(), 1);
- q_lookup.enable(region, row_offset).unwrap();
- continue;
- }
- // otherwise, we copy the advice value to the special lookup_advice columns
- if lookup_offset >= max_rows {
- lookup_offset = 0;
- lookup_col += 1;
- }
- let value = advice.value;
- let value = if use_unknown { Value::unknown() } else { Value::known(value) };
- let column = lookup_advice[phase][lookup_col];
-
- #[cfg(feature = "halo2-axiom")]
- {
- let bcell = region.assign_advice(column, lookup_offset, value);
- region.constrain_equal(&acell, bcell.cell());
- }
- #[cfg(not(feature = "halo2-axiom"))]
- {
- let bcell = region
- .assign_advice(|| "", column, lookup_offset, || value)
- .expect("assign_advice should not fail")
- .cell();
- region.constrain_equal(acell, bcell).unwrap();
- }
- lookup_offset += 1;
- }
- }
- }
- KeygenAssignments { assigned_advices, assigned_constants, break_points }
- }
-}
-
-/// Assigns threads to regions of advice column.
-///
-/// Uses preprocessed `break_points` to assign where to divide the advice column into a new column for each thread.
-///
-/// Performs only witness generation, so should only be evoked during proving not keygen.
-///
-/// Assumes that the advice columns are already assigned.
-/// * `phase` - the phase of the circuit
-/// * `threads` - [Vec] threads to assign
-/// * `config` - immutable reference to the configuration of the circuit
-/// * `lookup_advice` - Slice of lookup advice columns
-/// * `region` - mutable reference to the region to assign threads to
-/// * `break_points` - the preprocessed break points for the threads
-pub fn assign_threads_in(
- phase: usize,
- threads: Vec>,
- config: &FlexGateConfig,
- lookup_advice: &[Column],
- region: &mut Region,
- break_points: ThreadBreakPoints,
-) {
- if config.basic_gates[phase].is_empty() {
- assert!(threads.is_empty(), "Trying to assign threads in a phase with no columns");
- return;
- }
-
- let mut break_points = break_points.into_iter();
- let mut break_point = break_points.next();
-
- let mut gate_index = 0;
- let mut column = config.basic_gates[phase][gate_index].value;
- let mut row_offset = 0;
-
- let mut lookup_offset = 0;
- let mut lookup_advice = lookup_advice.iter();
- let mut lookup_column = lookup_advice.next();
- for ctx in threads {
- // if lookup_column is [None], that means there should be a single advice column and it has lookup enabled, so we don't need to copy to special lookup advice columns
- if lookup_column.is_some() {
- for advice in ctx.cells_to_lookup {
- if lookup_offset >= config.max_rows {
- lookup_offset = 0;
- lookup_column = lookup_advice.next();
- }
- // Assign the lookup advice values to the lookup_column
- let value = advice.value;
- let lookup_column = *lookup_column.unwrap();
- #[cfg(feature = "halo2-axiom")]
- region.assign_advice(lookup_column, lookup_offset, Value::known(value));
- #[cfg(not(feature = "halo2-axiom"))]
- region
- .assign_advice(|| "", lookup_column, lookup_offset, || Value::known(value))
- .unwrap();
-
- lookup_offset += 1;
- }
- }
- // Assign advice values to the advice columns in each [Context]
- for advice in ctx.advice {
- #[cfg(feature = "halo2-axiom")]
- region.assign_advice(column, row_offset, Value::known(advice));
- #[cfg(not(feature = "halo2-axiom"))]
- region.assign_advice(|| "", column, row_offset, || Value::known(advice)).unwrap();
-
- if break_point == Some(row_offset) {
- break_point = break_points.next();
- row_offset = 0;
- gate_index += 1;
- column = config.basic_gates[phase][gate_index].value;
-
- #[cfg(feature = "halo2-axiom")]
- region.assign_advice(column, row_offset, Value::known(advice));
- #[cfg(not(feature = "halo2-axiom"))]
- region.assign_advice(|| "", column, row_offset, || Value::known(advice)).unwrap();
- }
-
- row_offset += 1;
- }
- }
-}
-
-/// A Config struct defining the parameters for a FlexGate circuit.
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct FlexGateConfigParams {
- /// The gate strategy used for the advice column of the circuit and applied at every row.
- pub strategy: GateStrategy,
- /// Security parameter `k` used for the keygen.
- pub k: usize,
- /// The number of advice columns per phase
- pub num_advice_per_phase: Vec,
- /// The number of advice columns that do not have lookup enabled per phase
- pub num_lookup_advice_per_phase: Vec,
- /// The number of fixed columns per phase
- pub num_fixed: usize,
-}
-
-/// A wrapper struct to auto-build a circuit from a `GateThreadBuilder`.
-#[derive(Clone, Debug)]
-pub struct GateCircuitBuilder {
- /// The Thread Builder for the circuit
- pub builder: RefCell>, // `RefCell` is just to trick circuit `synthesize` to take ownership of the inner builder
- /// Break points for threads within the circuit
- pub break_points: RefCell, // `RefCell` allows the circuit to record break points in a keygen call of `synthesize` for use in later witness gen
-}
-
-impl GateCircuitBuilder {
- /// Creates a new [GateCircuitBuilder] with `use_unknown` of [GateThreadBuilder] set to true.
- pub fn keygen(builder: GateThreadBuilder) -> Self {
- Self { builder: RefCell::new(builder.unknown(true)), break_points: RefCell::new(vec![]) }
- }
-
- /// Creates a new [GateCircuitBuilder] with `use_unknown` of [GateThreadBuilder] set to false.
- pub fn mock(builder: GateThreadBuilder) -> Self {
- Self { builder: RefCell::new(builder.unknown(false)), break_points: RefCell::new(vec![]) }
- }
-
- /// Creates a new [GateCircuitBuilder].
- pub fn prover(
- builder: GateThreadBuilder,
- break_points: MultiPhaseThreadBreakPoints,
- ) -> Self {
- Self { builder: RefCell::new(builder), break_points: RefCell::new(break_points) }
- }
-
- /// Synthesizes from the [GateCircuitBuilder] by populating the advice column and assigning new threads if witness generation is performed.
- pub fn sub_synthesize(
- &self,
- gate: &FlexGateConfig,
- lookup_advice: &[Vec>],
- q_lookup: &[Option],
- layouter: &mut impl Layouter,
- ) -> HashMap<(usize, usize), (circuit::Cell, usize)> {
- let mut first_pass = SKIP_FIRST_PASS;
- let mut assigned_advices = HashMap::new();
- layouter
- .assign_region(
- || "GateCircuitBuilder generated circuit",
- |mut region| {
- if first_pass {
- first_pass = false;
- return Ok(());
- }
- // only support FirstPhase in this Builder because getting challenge value requires more specialized witness generation during synthesize
- // If we are not performing witness generation only, we can skip the first pass and assign threads directly
- if !self.builder.borrow().witness_gen_only {
- // clone the builder so we can re-use the circuit for both vk and pk gen
- let builder = self.builder.borrow().clone();
- for threads in builder.threads.iter().skip(1) {
- assert!(
- threads.is_empty(),
- "GateCircuitBuilder only supports FirstPhase for now"
- );
- }
- let assignments = builder.assign_all(
- gate,
- lookup_advice,
- q_lookup,
- &mut region,
- Default::default(),
- );
- *self.break_points.borrow_mut() = assignments.break_points;
- assigned_advices = assignments.assigned_advices;
- } else {
- // If we are only generating witness, we can skip the first pass and assign threads directly
- let builder = self.builder.take();
- let break_points = self.break_points.take();
- for (phase, (threads, break_points)) in builder
- .threads
- .into_iter()
- .zip(break_points.into_iter())
- .enumerate()
- .take(1)
- {
- assign_threads_in(
- phase,
- threads,
- gate,
- lookup_advice.get(phase).unwrap_or(&vec![]),
- &mut region,
- break_points,
- );
- }
- }
- Ok(())
- },
- )
- .unwrap();
- assigned_advices
- }
-}
-
-impl Circuit for GateCircuitBuilder {
- type Config = FlexGateConfig;
- type FloorPlanner = SimpleFloorPlanner;
-
- /// Creates a new instance of the circuit without withnesses filled in.
- fn without_witnesses(&self) -> Self {
- unimplemented!()
- }
-
- /// Configures a new circuit using the the parameters specified [Config].
- fn configure(meta: &mut ConstraintSystem) -> FlexGateConfig {
- let FlexGateConfigParams {
- strategy,
- num_advice_per_phase,
- num_lookup_advice_per_phase: _,
- num_fixed,
- k,
- } = serde_json::from_str(&var("FLEX_GATE_CONFIG_PARAMS").unwrap()).unwrap();
- FlexGateConfig::configure(meta, strategy, &num_advice_per_phase, num_fixed, k)
- }
-
- /// Performs the actual computation on the circuit (e.g., witness generation), filling in all the advice values for a particular proof.
- fn synthesize(
- &self,
- config: Self::Config,
- mut layouter: impl Layouter,
- ) -> Result<(), Error> {
- self.sub_synthesize(&config, &[], &[], &mut layouter);
- Ok(())
- }
-}
-
-/// A wrapper struct to auto-build a circuit from a `GateThreadBuilder`.
-#[derive(Clone, Debug)]
-pub struct RangeCircuitBuilder(pub GateCircuitBuilder);
-
-impl RangeCircuitBuilder {
- /// Creates an instance of the [RangeCircuitBuilder] and executes in keygen mode.
- pub fn keygen(builder: GateThreadBuilder) -> Self {
- Self(GateCircuitBuilder::keygen(builder))
- }
-
- /// Creates a mock instance of the [RangeCircuitBuilder].
- pub fn mock(builder: GateThreadBuilder) -> Self {
- Self(GateCircuitBuilder::mock(builder))
- }
-
- /// Creates an instance of the [RangeCircuitBuilder] and executes in prover mode.
- pub fn prover(
- builder: GateThreadBuilder,
- break_points: MultiPhaseThreadBreakPoints,
- ) -> Self {
- Self(GateCircuitBuilder::prover(builder, break_points))
- }
-}
-
-impl Circuit for RangeCircuitBuilder {
- type Config = RangeConfig;
- type FloorPlanner = SimpleFloorPlanner;
-
- /// Creates a new instance of the [RangeCircuitBuilder] without witnesses by setting the witness_gen_only flag to false
- fn without_witnesses(&self) -> Self {
- unimplemented!()
- }
-
- /// Configures a new circuit using the the parameters specified [Config] and environment variable `LOOKUP_BITS`.
- fn configure(meta: &mut ConstraintSystem) -> Self::Config {
- let FlexGateConfigParams {
- strategy,
- num_advice_per_phase,
- num_lookup_advice_per_phase,
- num_fixed,
- k,
- } = serde_json::from_str(&var("FLEX_GATE_CONFIG_PARAMS").unwrap()).unwrap();
- let strategy = match strategy {
- GateStrategy::Vertical => RangeStrategy::Vertical,
- };
- let lookup_bits = var("LOOKUP_BITS").unwrap_or_else(|_| "0".to_string()).parse().unwrap();
- RangeConfig::configure(
- meta,
- strategy,
- &num_advice_per_phase,
- &num_lookup_advice_per_phase,
- num_fixed,
- lookup_bits,
- k,
- )
- }
-
- /// Performs the actual computation on the circuit (e.g., witness generation), populating the lookup table and filling in all the advice values for a particular proof.
- fn synthesize(
- &self,
- config: Self::Config,
- mut layouter: impl Layouter,
- ) -> Result<(), Error> {
- // only load lookup table if we are actually doing lookups
- if config.lookup_advice.iter().map(|a| a.len()).sum::() != 0
- || !config.q_lookup.iter().all(|q| q.is_none())
- {
- config.load_lookup_table(&mut layouter).expect("load lookup table should not fail");
- }
- self.0.sub_synthesize(&config.gate, &config.lookup_advice, &config.q_lookup, &mut layouter);
- Ok(())
- }
-}
-
-/// Configuration with [`RangeConfig`] and a single public instance column.
-#[derive(Clone, Debug)]
-pub struct RangeWithInstanceConfig {
- /// The underlying range configuration
- pub range: RangeConfig,
- /// The public instance column
- pub instance: Column,
-}
-
-/// This is an extension of [`RangeCircuitBuilder`] that adds support for public instances (aka public inputs+outputs)
-///
-/// The intended design is that a [`GateThreadBuilder`] is populated and then produces some assigned instances, which are supplied as `assigned_instances` to this struct.
-/// The [`Circuit`] implementation for this struct will then expose these instances and constrain them using the Halo2 API.
-#[derive(Clone, Debug)]
-pub struct RangeWithInstanceCircuitBuilder {
- /// The underlying circuit builder
- pub circuit: RangeCircuitBuilder,
- /// The assigned instances to expose publicly at the end of circuit synthesis
- pub assigned_instances: Vec>,
-}
-
-impl RangeWithInstanceCircuitBuilder {
- /// See [`RangeCircuitBuilder::keygen`]
- pub fn keygen(
- builder: GateThreadBuilder,
- assigned_instances: Vec>,
- ) -> Self {
- Self { circuit: RangeCircuitBuilder::keygen(builder), assigned_instances }
- }
-
- /// See [`RangeCircuitBuilder::mock`]
- pub fn mock(builder: GateThreadBuilder, assigned_instances: Vec>) -> Self {
- Self { circuit: RangeCircuitBuilder::mock(builder), assigned_instances }
- }
-
- /// See [`RangeCircuitBuilder::prover`]
- pub fn prover(
- builder: GateThreadBuilder,
- assigned_instances: Vec>,
- break_points: MultiPhaseThreadBreakPoints,
- ) -> Self {
- Self { circuit: RangeCircuitBuilder::prover(builder, break_points), assigned_instances }
- }
-
- /// Creates a new instance of the [RangeWithInstanceCircuitBuilder].
- pub fn new(circuit: RangeCircuitBuilder, assigned_instances: Vec>) -> Self {
- Self { circuit, assigned_instances }
- }
-
- /// Calls [`GateThreadBuilder::config`]
- pub fn config(&self, k: u32, minimum_rows: Option) -> FlexGateConfigParams {
- self.circuit.0.builder.borrow().config(k as usize, minimum_rows)
- }
-
- /// Gets the break points of the circuit.
- pub fn break_points(&self) -> MultiPhaseThreadBreakPoints {
- self.circuit.0.break_points.borrow().clone()
- }
-
- /// Gets the number of instances.
- pub fn instance_count(&self) -> usize {
- self.assigned_instances.len()
- }
-
- /// Gets the instances.
- pub fn instance(&self) -> Vec {
- self.assigned_instances.iter().map(|v| *v.value()).collect()
- }
-}
-
-impl Circuit for RangeWithInstanceCircuitBuilder {
- type Config = RangeWithInstanceConfig;
- type FloorPlanner = SimpleFloorPlanner;
-
- fn without_witnesses(&self) -> Self {
- unimplemented!()
- }
-
- fn configure(meta: &mut ConstraintSystem) -> Self::Config {
- let range = RangeCircuitBuilder::configure(meta);
- let instance = meta.instance_column();
- meta.enable_equality(instance);
- RangeWithInstanceConfig { range, instance }
- }
-
- fn synthesize(
- &self,
- config: Self::Config,
- mut layouter: impl Layouter,
- ) -> Result<(), Error> {
- // copied from RangeCircuitBuilder::synthesize but with extra logic to expose public instances
- let range = config.range;
- let circuit = &self.circuit.0;
- // only load lookup table if we are actually doing lookups
- if range.lookup_advice.iter().map(|a| a.len()).sum::() != 0
- || !range.q_lookup.iter().all(|q| q.is_none())
- {
- range.load_lookup_table(&mut layouter).expect("load lookup table should not fail");
- }
- // we later `take` the builder, so we need to save this value
- let witness_gen_only = circuit.builder.borrow().witness_gen_only();
- let assigned_advices = circuit.sub_synthesize(
- &range.gate,
- &range.lookup_advice,
- &range.q_lookup,
- &mut layouter,
- );
-
- if !witness_gen_only {
- // expose public instances
- let mut layouter = layouter.namespace(|| "expose");
- for (i, instance) in self.assigned_instances.iter().enumerate() {
- let cell = instance.cell.unwrap();
- let (cell, _) = assigned_advices
- .get(&(cell.context_id, cell.offset))
- .expect("instance not assigned");
- layouter.constrain_instance(*cell, config.instance, i);
- }
- }
- Ok(())
- }
-}
-
-/// Defines stage of the circuit builder.
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum CircuitBuilderStage {
- /// Keygen phase
- Keygen,
- /// Prover Circuit
- Prover,
- /// Mock Circuit
- Mock,
-}
diff --git a/halo2-base/src/gates/builder/parallelize.rs b/halo2-base/src/gates/builder/parallelize.rs
deleted file mode 100644
index ab9171d5..00000000
--- a/halo2-base/src/gates/builder/parallelize.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use itertools::Itertools;
-use rayon::prelude::*;
-
-use crate::{utils::ScalarField, Context};
-
-use super::GateThreadBuilder;
-
-/// Utility function to parallelize an operation involving [`Context`]s in phase `phase`.
-pub fn parallelize_in(
- phase: usize,
- builder: &mut GateThreadBuilder,
- input: Vec,
- f: FR,
-) -> Vec
-where
- F: ScalarField,
- T: Send,
- R: Send,
- FR: Fn(&mut Context, T) -> R + Send + Sync,
-{
- let witness_gen_only = builder.witness_gen_only();
- // to prevent concurrency issues with context id, we generate all the ids first
- let ctx_ids = input.iter().map(|_| builder.get_new_thread_id()).collect_vec();
- let (outputs, mut ctxs): (Vec<_>, Vec<_>) = input
- .into_par_iter()
- .zip(ctx_ids.into_par_iter())
- .map(|(input, ctx_id)| {
- // create new context
- let mut ctx = Context::new(witness_gen_only, ctx_id);
- let output = f(&mut ctx, input);
- (output, ctx)
- })
- .unzip();
- // we collect the new threads to ensure they are a FIXED order, otherwise later `assign_threads_in` will get confused
- builder.threads[phase].append(&mut ctxs);
-
- outputs
-}
diff --git a/halo2-base/src/gates/circuit/builder.rs b/halo2-base/src/gates/circuit/builder.rs
new file mode 100644
index 00000000..03dd5f92
--- /dev/null
+++ b/halo2-base/src/gates/circuit/builder.rs
@@ -0,0 +1,374 @@
+use std::sync::{Arc, Mutex};
+
+use getset::{Getters, MutGetters, Setters};
+use itertools::Itertools;
+
+use crate::{
+ gates::{
+ circuit::CircuitBuilderStage,
+ flex_gate::{
+ threads::{GateStatistics, MultiPhaseCoreManager, SinglePhaseCoreManager},
+ MultiPhaseThreadBreakPoints, MAX_PHASE,
+ },
+ range::RangeConfig,
+ RangeChip,
+ },
+ halo2_proofs::{
+ circuit::{Layouter, Region},
+ plonk::{Column, Instance},
+ },
+ utils::ScalarField,
+ virtual_region::{
+ copy_constraints::{CopyConstraintManager, SharedCopyConstraintManager},
+ lookups::LookupAnyManager,
+ manager::VirtualRegionManager,
+ },
+ AssignedValue, Context,
+};
+
+use super::BaseCircuitParams;
+
+/// Keeping the naming `RangeCircuitBuilder` for backwards compatibility.
+pub type RangeCircuitBuilder = BaseCircuitBuilder;
+
+/// A circuit builder is a collection of virtual region managers that together assign virtual
+/// regions into a single physical circuit.
+///
+/// [BaseCircuitBuilder] is a circuit builder to create a circuit where the columns correspond to [super::BaseConfig].
+/// This builder can hold multiple threads, but the `Circuit` implementation only evaluates the first phase.
+/// The user will have to implement a separate `Circuit` with multi-phase witness generation logic.
+///
+/// This is used to manage the virtual region corresponding to [super::FlexGateConfig] and (optionally) [RangeConfig].
+/// This can be used even if only using [`GateChip`](crate::gates::flex_gate::GateChip) without [RangeChip].
+///
+/// The circuit will have `NI` public instance (aka public inputs+outputs) columns.
+#[derive(Clone, Debug, Getters, MutGetters, Setters)]
+pub struct BaseCircuitBuilder {
+ /// Virtual region for each challenge phase. These cannot be shared across threads while keeping circuit deterministic.
+ #[getset(get = "pub", get_mut = "pub", set = "pub")]
+ pub(super) core: MultiPhaseCoreManager,
+ /// The range lookup manager
+ #[getset(get = "pub", get_mut = "pub", set = "pub")]
+ pub(super) lookup_manager: [LookupAnyManager; MAX_PHASE],
+ /// Configuration parameters for the circuit shape
+ pub config_params: BaseCircuitParams,
+ /// The assigned instances to expose publicly at the end of circuit synthesis
+ pub assigned_instances: Vec>>,
+}
+
+impl Default for BaseCircuitBuilder {
+ /// Quick start default circuit builder which can be used for MockProver, Keygen, and real prover.
+ /// For best performance during real proof generation, we recommend using [BaseCircuitBuilder::prover] instead.
+ fn default() -> Self {
+ Self::new(false)
+ }
+}
+
+impl BaseCircuitBuilder {
+ /// Creates a new [BaseCircuitBuilder] with all default managers.
+ /// * `witness_gen_only`:
+ /// * If true, the builder only does witness asignments and does not store constraint information -- this should only be used for the real prover.
+ /// * If false, the builder also imposes constraints (selectors, fixed columns, copy constraints). Primarily used for keygen and mock prover (but can also be used for real prover).
+ ///
+ /// By default, **no** circuit configuration parameters have been set.
+ /// These should be set separately using `use_params`, or `use_k`, `use_lookup_bits`, and `calculate_params`.
+ ///
+ /// Upon construction, there are no public instances (aka all witnesses are private).
+ /// The intended usage is that _before_ calling `synthesize`, witness generation can be done to populate
+ /// assigned instances, which are supplied as `assigned_instances` to this struct.
+ /// The `Circuit` implementation for this struct will then expose these instances and constrain
+ /// them using the Halo2 API.
+ pub fn new(witness_gen_only: bool) -> Self {
+ let core = MultiPhaseCoreManager::new(witness_gen_only);
+ let lookup_manager = [(); MAX_PHASE]
+ .map(|_| LookupAnyManager::new(witness_gen_only, core.copy_manager.clone()));
+ Self { core, lookup_manager, config_params: Default::default(), assigned_instances: vec![] }
+ }
+
+ /// Creates a new [MultiPhaseCoreManager] depending on the stage of circuit building. If the stage is [CircuitBuilderStage::Prover], the [MultiPhaseCoreManager] is used for witness generation only.
+ pub fn from_stage(stage: CircuitBuilderStage) -> Self {
+ Self::new(stage.witness_gen_only()).unknown(stage == CircuitBuilderStage::Keygen)
+ }
+
+ /// Creates a new [BaseCircuitBuilder] with a pinned circuit configuration given by `config_params` and `break_points`.
+ pub fn prover(
+ config_params: BaseCircuitParams,
+ break_points: MultiPhaseThreadBreakPoints,
+ ) -> Self {
+ Self::new(true).use_params(config_params).use_break_points(break_points)
+ }
+
+ /// Sets the copy manager to the given one in all shared references.
+ pub fn set_copy_manager(&mut self, copy_manager: SharedCopyConstraintManager) {
+ for lm in &mut self.lookup_manager {
+ lm.set_copy_manager(copy_manager.clone());
+ }
+ self.core.set_copy_manager(copy_manager);
+ }
+
+ /// Returns `self` with a given copy manager
+ pub fn use_copy_manager(mut self, copy_manager: SharedCopyConstraintManager) -> Self {
+ self.set_copy_manager(copy_manager);
+ self
+ }
+
+ /// Deep clone of `self`, where the underlying object of shared references in [SharedCopyConstraintManager] and [LookupAnyManager] are cloned.
+ pub fn deep_clone(&self) -> Self {
+ let cm: CopyConstraintManager = self.core.copy_manager.lock().unwrap().clone();
+ let cm_ref = Arc::new(Mutex::new(cm));
+ let mut clone = self.clone().use_copy_manager(cm_ref.clone());
+ for lm in &mut clone.lookup_manager {
+ *lm = lm.deep_clone(cm_ref.clone());
+ }
+ clone
+ }
+
+ /// The log_2 size of the lookup table, if using.
+ pub fn lookup_bits(&self) -> Option {
+ self.config_params.lookup_bits
+ }
+
+ /// Set lookup bits
+ pub fn set_lookup_bits(&mut self, lookup_bits: usize) {
+ self.config_params.lookup_bits = Some(lookup_bits);
+ }
+
+ /// Returns new with lookup bits
+ pub fn use_lookup_bits(mut self, lookup_bits: usize) -> Self {
+ self.set_lookup_bits(lookup_bits);
+ self
+ }
+
+ /// Sets new `k` = log2 of domain
+ pub fn set_k(&mut self, k: usize) {
+ self.config_params.k = k;
+ }
+
+ /// Returns new with `k` set
+ pub fn use_k(mut self, k: usize) -> Self {
+ self.set_k(k);
+ self
+ }
+
+ /// Set the number of instance columns. This resizes `self.assigned_instances`.
+ pub fn set_instance_columns(&mut self, num_instance_columns: usize) {
+ self.config_params.num_instance_columns = num_instance_columns;
+ while self.assigned_instances.len() < num_instance_columns {
+ self.assigned_instances.push(vec![]);
+ }
+ assert_eq!(self.assigned_instances.len(), num_instance_columns);
+ }
+
+ /// Returns new with `self.assigned_instances` resized to specified number of instance columns.
+ pub fn use_instance_columns(mut self, num_instance_columns: usize) -> Self {
+ self.set_instance_columns(num_instance_columns);
+ self
+ }
+
+ /// Set config params
+ pub fn set_params(&mut self, params: BaseCircuitParams) {
+ self.set_instance_columns(params.num_instance_columns);
+ self.config_params = params;
+ }
+
+ /// Returns new with config params
+ pub fn use_params(mut self, params: BaseCircuitParams) -> Self {
+ self.set_params(params);
+ self
+ }
+
+ /// The break points of the circuit.
+ pub fn break_points(&self) -> MultiPhaseThreadBreakPoints {
+ self.core
+ .phase_manager
+ .iter()
+ .map(|pm| pm.break_points.borrow().as_ref().expect("break points not set").clone())
+ .collect()
+ }
+
+ /// Sets the break points of the circuit.
+ pub fn set_break_points(&mut self, break_points: MultiPhaseThreadBreakPoints) {
+ if break_points.is_empty() {
+ return;
+ }
+ self.core.touch(break_points.len() - 1);
+ for (pm, bp) in self.core.phase_manager.iter().zip_eq(break_points) {
+ *pm.break_points.borrow_mut() = Some(bp);
+ }
+ }
+
+ /// Returns new with break points
+ pub fn use_break_points(mut self, break_points: MultiPhaseThreadBreakPoints) -> Self {
+ self.set_break_points(break_points);
+ self
+ }
+
+ /// Returns if the circuit is only used for witness generation.
+ pub fn witness_gen_only(&self) -> bool {
+ self.core.witness_gen_only()
+ }
+
+ /// Creates a new [MultiPhaseCoreManager] with `use_unknown` flag set.
+ /// * `use_unknown`: If true, during key generation witness `Value`s are replaced with `Value::unknown()` for safety.
+ pub fn unknown(mut self, use_unknown: bool) -> Self {
+ self.core = self.core.unknown(use_unknown);
+ self
+ }
+
+ /// Clears state and copies, effectively resetting the circuit builder.
+ pub fn clear(&mut self) {
+ self.core.clear();
+ for lm in &mut self.lookup_manager {
+ lm.clear();
+ }
+ self.assigned_instances.iter_mut().for_each(|c| c.clear());
+ }
+
+ /// Returns a mutable reference to the [Context] of a gate thread. Spawns a new thread for the given phase, if none exists.
+ /// * `phase`: The challenge phase (as an index) of the gate thread.
+ pub fn main(&mut self, phase: usize) -> &mut Context {
+ self.core.main(phase)
+ }
+
+ /// Returns [SinglePhaseCoreManager] with the virtual region with all core threads in the given phase.
+ pub fn pool(&mut self, phase: usize) -> &mut SinglePhaseCoreManager {
+ self.core.phase_manager.get_mut(phase).unwrap()
+ }
+
+ /// Spawns a new thread for a new given `phase`. Returns a mutable reference to the [Context] of the new thread.
+ /// * `phase`: The phase (index) of the gate thread.
+ pub fn new_thread(&mut self, phase: usize) -> &mut Context {
+ self.core.new_thread(phase)
+ }
+
+ /// Returns some statistics about the virtual region.
+ pub fn statistics(&self) -> RangeStatistics {
+ let gate = self.core.statistics();
+ let total_lookup_advice_per_phase = self.total_lookup_advice_per_phase();
+ RangeStatistics { gate, total_lookup_advice_per_phase }
+ }
+
+ fn total_lookup_advice_per_phase(&self) -> Vec {
+ self.lookup_manager.iter().map(|lm| lm.total_rows()).collect()
+ }
+
+ /// Auto-calculates configuration parameters for the circuit and sets them.
+ ///
+ /// * `k`: The number of in the circuit (i.e. numeber of rows = 2k)
+ /// * `minimum_rows`: The minimum number of rows in the circuit that cannot be used for witness assignments and contain random `blinding factors` to ensure zk property, defaults to 0.
+ /// * `lookup_bits`: The fixed lookup table will consist of [0, 2lookup_bits)
+ pub fn calculate_params(&mut self, minimum_rows: Option) -> BaseCircuitParams {
+ let k = self.config_params.k;
+ let ni = self.config_params.num_instance_columns;
+ assert_ne!(k, 0, "k must be set");
+ let max_rows = (1 << k) - minimum_rows.unwrap_or(0);
+ let gate_params = self.core.calculate_params(k, minimum_rows);
+ let total_lookup_advice_per_phase = self.total_lookup_advice_per_phase();
+ let num_lookup_advice_per_phase = total_lookup_advice_per_phase
+ .iter()
+ .map(|count| (count + max_rows - 1) / max_rows)
+ .collect::>();
+
+ let params = BaseCircuitParams {
+ k: gate_params.k,
+ num_advice_per_phase: gate_params.num_advice_per_phase,
+ num_fixed: gate_params.num_fixed,
+ num_lookup_advice_per_phase,
+ lookup_bits: self.lookup_bits(),
+ num_instance_columns: ni,
+ };
+ self.config_params = params.clone();
+ #[cfg(feature = "display")]
+ {
+ println!("Total range check advice cells to lookup per phase: {total_lookup_advice_per_phase:?}");
+ log::info!("Auto-calculated config params:\n {params:#?}");
+ }
+ params
+ }
+
+ /// Copies `assigned_instances` to the instance columns. Should only be called at the very end of
+ /// `synthesize` after virtual `assigned_instances` have been assigned to physical circuit.
+ pub fn assign_instances(
+ &self,
+ instance_columns: &[Column],
+ mut layouter: impl Layouter,
+ ) {
+ if !self.core.witness_gen_only() {
+ // expose public instances
+ for (instances, instance_col) in self.assigned_instances.iter().zip_eq(instance_columns)
+ {
+ for (i, instance) in instances.iter().enumerate() {
+ let cell = instance.cell.unwrap();
+ let copy_manager = self.core.copy_manager.lock().unwrap();
+ let cell =
+ copy_manager.assigned_advices.get(&cell).expect("instance not assigned");
+ layouter.constrain_instance(*cell, *instance_col, i);
+ }
+ }
+ }
+ }
+
+ /// Creates a new [RangeChip] sharing the same [LookupAnyManager]s as `self`.
+ pub fn range_chip(&self) -> RangeChip {
+ RangeChip::new(
+ self.config_params.lookup_bits.expect("lookup bits not set"),
+ self.lookup_manager.clone(),
+ )
+ }
+
+ /// Copies the queued cells to be range looked up in phase `phase` to special advice lookup columns
+ /// using [LookupAnyManager].
+ ///
+ /// ## Special case
+ /// Just for [RangeConfig], we have special handling for the case where there is a single (physical)
+ /// advice column in [super::FlexGateConfig]. In this case, `RangeConfig` does not create extra lookup advice columns,
+ /// the single advice column has lookup enabled, and there is a selector to toggle when lookup should
+ /// be turned on.
+ pub fn assign_lookups_in_phase(
+ &self,
+ config: &RangeConfig,
+ region: &mut Region,
+ phase: usize,
+ ) {
+ let lookup_manager = self.lookup_manager.get(phase).expect("too many phases");
+ if lookup_manager.total_rows() == 0 {
+ return;
+ }
+ if let Some(q_lookup) = config.q_lookup.get(phase).and_then(|q| *q) {
+ // if q_lookup is Some, that means there should be a single advice column and it has lookup enabled
+ assert_eq!(config.gate.basic_gates[phase].len(), 1);
+ if !self.witness_gen_only() {
+ let cells_to_lookup = lookup_manager.cells_to_lookup.lock().unwrap();
+ for advice in cells_to_lookup.iter().flat_map(|(_, advices)| advices) {
+ let cell = advice[0].cell.as_ref().unwrap();
+ let copy_manager = self.core.copy_manager.lock().unwrap();
+ let acell = copy_manager.assigned_advices[cell];
+ assert_eq!(
+ acell.column,
+ config.gate.basic_gates[phase][0].value.into(),
+ "lookup column does not match"
+ );
+ q_lookup.enable(region, acell.row_offset).unwrap();
+ }
+ }
+ } else {
+ let lookup_cols = config
+ .lookup_advice
+ .get(phase)
+ .expect("No special lookup advice columns")
+ .iter()
+ .map(|c| [*c])
+ .collect_vec();
+ lookup_manager.assign_raw(&lookup_cols, region);
+ }
+ let _ = lookup_manager.assigned.set(());
+ }
+}
+
+/// Basic statistics
+pub struct RangeStatistics {
+ /// Number of advice cells for the basic gate and total constants used
+ pub gate: GateStatistics,
+ /// Total special advice cells that need to be looked up, per phase
+ pub total_lookup_advice_per_phase: Vec,
+}
diff --git a/halo2-base/src/gates/circuit/mod.rs b/halo2-base/src/gates/circuit/mod.rs
new file mode 100644
index 00000000..dc0ece12
--- /dev/null
+++ b/halo2-base/src/gates/circuit/mod.rs
@@ -0,0 +1,217 @@
+use serde::{Deserialize, Serialize};
+
+use crate::utils::ScalarField;
+use crate::{
+ halo2_proofs::{
+ circuit::{Layouter, SimpleFloorPlanner},
+ plonk::{Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector},
+ },
+ virtual_region::manager::VirtualRegionManager,
+};
+
+use self::builder::BaseCircuitBuilder;
+
+use super::flex_gate::{FlexGateConfig, FlexGateConfigParams};
+use super::range::RangeConfig;
+
+/// Module that helps auto-build circuits
+pub mod builder;
+
+/// A struct defining the configuration parameters for a halo2-base circuit
+/// - this is used to configure [BaseConfig].
+#[derive(Clone, Default, Debug, Serialize, Deserialize)]
+pub struct BaseCircuitParams {
+ // Keeping FlexGateConfigParams expanded for backwards compatibility
+ /// Specifies the number of rows in the circuit to be 2k
+ pub k: usize,
+ /// The number of advice columns per phase
+ pub num_advice_per_phase: Vec,
+ /// The number of fixed columns
+ pub num_fixed: usize,
+ /// The number of bits that can be ranged checked using a special lookup table with values [0, 2lookup_bits), if using.
+ /// The number of special advice columns that have range lookup enabled per phase
+ pub num_lookup_advice_per_phase: Vec,
+ /// This is `None` if no lookup table is used.
+ pub lookup_bits: Option,
+ /// Number of public instance columns
+ #[serde(default)]
+ pub num_instance_columns: usize,
+}
+
+impl BaseCircuitParams {
+ fn gate_params(&self) -> FlexGateConfigParams {
+ FlexGateConfigParams {
+ k: self.k,
+ num_advice_per_phase: self.num_advice_per_phase.clone(),
+ num_fixed: self.num_fixed,
+ }
+ }
+}
+
+/// Configuration with [`BaseConfig`] with `NI` public instance columns.
+#[derive(Clone, Debug)]
+pub struct BaseConfig {
+ /// The underlying private gate/range configuration
+ pub base: MaybeRangeConfig,
+ /// The public instance column
+ pub instance: Vec>,
+}
+
+/// Smart Halo2 circuit config that has different variants depending on whether you need range checks or not.
+/// The difference is that to enable range checks, the Halo2 config needs to add a lookup table.
+#[derive(Clone, Debug)]
+pub enum MaybeRangeConfig {
+ /// Config for a circuit that does not use range checks
+ WithoutRange(FlexGateConfig),
+ /// Config for a circuit that does use range checks
+ WithRange(RangeConfig),
+}
+
+impl BaseConfig {
+ /// Generates a new `BaseConfig` depending on `params`.
+ /// - It will generate a `RangeConfig` is `params` has `lookup_bits` not None **and** `num_lookup_advice_per_phase` are not all empty or zero (i.e., if `params` indicates that the circuit actually requires a lookup table).
+ /// - Otherwise it will generate a `FlexGateConfig`.
+ pub fn configure(meta: &mut ConstraintSystem, params: BaseCircuitParams) -> Self {
+ let total_lookup_advice_cols = params.num_lookup_advice_per_phase.iter().sum::();
+ let base = if params.lookup_bits.is_some() && total_lookup_advice_cols != 0 {
+ // We only add a lookup table if lookup bits is not None
+ MaybeRangeConfig::WithRange(RangeConfig::configure(
+ meta,
+ params.gate_params(),
+ ¶ms.num_lookup_advice_per_phase,
+ params.lookup_bits.unwrap(),
+ ))
+ } else {
+ MaybeRangeConfig::WithoutRange(FlexGateConfig::configure(meta, params.gate_params()))
+ };
+ let instance = (0..params.num_instance_columns)
+ .map(|_| {
+ let inst = meta.instance_column();
+ meta.enable_equality(inst);
+ inst
+ })
+ .collect();
+ Self { base, instance }
+ }
+
+ /// Returns the inner [`FlexGateConfig`]
+ pub fn gate(&self) -> &FlexGateConfig {
+ match &self.base {
+ MaybeRangeConfig::WithoutRange(config) => config,
+ MaybeRangeConfig::WithRange(config) => &config.gate,
+ }
+ }
+
+ /// Returns the fixed columns for constants
+ pub fn constants(&self) -> &Vec> {
+ match &self.base {
+ MaybeRangeConfig::WithoutRange(config) => &config.constants,
+ MaybeRangeConfig::WithRange(config) => &config.gate.constants,
+ }
+ }
+
+ /// Returns a slice of the selector column to enable lookup -- this is only in the situation where there is a single advice column of any kind -- per phase
+ /// Returns empty slice if there are no lookups enabled.
+ pub fn q_lookup(&self) -> &[Option] {
+ match &self.base {
+ MaybeRangeConfig::WithoutRange(_) => &[],
+ MaybeRangeConfig::WithRange(config) => &config.q_lookup,
+ }
+ }
+
+ /// Updates the number of usable rows in the circuit. Used if you mutate [ConstraintSystem] after `BaseConfig::configure` is called.
+ pub fn set_usable_rows(&mut self, usable_rows: usize) {
+ match &mut self.base {
+ MaybeRangeConfig::WithoutRange(config) => config.max_rows = usable_rows,
+ MaybeRangeConfig::WithRange(config) => config.gate.max_rows = usable_rows,
+ }
+ }
+
+ /// Initialization of config at very beginning of `synthesize`.
+ /// Loads fixed lookup table, if using.
+ pub fn initialize(&self, layouter: &mut impl Layouter) {
+ // only load lookup table if we are actually doing lookups
+ if let MaybeRangeConfig::WithRange(config) = &self.base {
+ config.load_lookup_table(layouter).expect("load lookup table should not fail");
+ }
+ }
+}
+
+impl Circuit for BaseCircuitBuilder {
+ type Config = BaseConfig;
+ type FloorPlanner = SimpleFloorPlanner;
+ type Params = BaseCircuitParams;
+
+ fn params(&self) -> Self::Params {
+ self.config_params.clone()
+ }
+
+ /// Creates a new instance of the [BaseCircuitBuilder] without witnesses by setting the witness_gen_only flag to false
+ fn without_witnesses(&self) -> Self {
+ unimplemented!()
+ }
+
+ /// Configures a new circuit using [`BaseCircuitParams`]
+ fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config {
+ BaseConfig::configure(meta, params)
+ }
+
+ fn configure(_: &mut ConstraintSystem) -> Self::Config {
+ unreachable!("You must use configure_with_params");
+ }
+
+ /// Performs the actual computation on the circuit (e.g., witness generation), populating the lookup table and filling in all the advice values for a particular proof.
+ fn synthesize(
+ &self,
+ config: Self::Config,
+ mut layouter: impl Layouter,
+ ) -> Result<(), Error> {
+ // only load lookup table if we are actually doing lookups
+ if let MaybeRangeConfig::WithRange(config) = &config.base {
+ config.load_lookup_table(&mut layouter).expect("load lookup table should not fail");
+ }
+ // Only FirstPhase (phase 0)
+ layouter
+ .assign_region(
+ || "BaseCircuitBuilder generated circuit",
+ |mut region| {
+ let usable_rows = config.gate().max_rows;
+ self.core.phase_manager[0].assign_raw(
+ &(config.gate().basic_gates[0].clone(), usable_rows),
+ &mut region,
+ );
+ // Only assign cells to lookup if we're sure we're doing range lookups
+ if let MaybeRangeConfig::WithRange(config) = &config.base {
+ self.assign_lookups_in_phase(config, &mut region, 0);
+ }
+ // Impose equality constraints
+ if !self.core.witness_gen_only() {
+ self.core.copy_manager.assign_raw(config.constants(), &mut region);
+ }
+ Ok(())
+ },
+ )
+ .unwrap();
+
+ self.assign_instances(&config.instance, layouter.namespace(|| "expose"));
+ Ok(())
+ }
+}
+
+/// Defines stage of circuit building.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum CircuitBuilderStage {
+ /// Keygen phase
+ Keygen,
+ /// Prover Circuit
+ Prover,
+ /// Mock Circuit
+ Mock,
+}
+
+impl CircuitBuilderStage {
+ /// Returns true if the circuit is used for witness generation only.
+ pub fn witness_gen_only(&self) -> bool {
+ matches!(self, CircuitBuilderStage::Prover)
+ }
+}
diff --git a/halo2-base/src/gates/flex_gate.rs b/halo2-base/src/gates/flex_gate/mod.rs
similarity index 73%
rename from halo2-base/src/gates/flex_gate.rs
rename to halo2-base/src/gates/flex_gate/mod.rs
index 1907521e..92e59338 100644
--- a/halo2-base/src/gates/flex_gate.rs
+++ b/halo2-base/src/gates/flex_gate/mod.rs
@@ -10,28 +10,31 @@ use crate::{
AssignedValue, Context,
QuantumCell::{self, Constant, Existing, Witness, WitnessFraction},
};
+use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::{
iter::{self},
marker::PhantomData,
};
-/// The maximum number of phases in halo2.
-pub const MAX_PHASE: usize = 3;
-
-/// Specifies the gate strategy for the gate chip
-#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
-pub enum GateStrategy {
- /// # Vertical Gate Strategy:
- /// `q_0 * (a + b * c - d) = 0`
- /// where
- /// * a = value[0], b = value[1], c = value[2], d = value[3]
- /// * q = q_enable[0]
- /// * q is either 0 or 1 so this is just a simple selector
- /// We chose `a + b * c` instead of `a * b + c` to allow "chaining" of gates, i.e., the output of one gate because `a` in the next gate.
- Vertical,
-}
+pub mod threads;
+/// Vector of thread advice column break points
+pub type ThreadBreakPoints = Vec;
+/// Vector of vectors tracking the thread break points across different halo2 phases
+pub type MultiPhaseThreadBreakPoints = Vec;
+
+/// The maximum number of phases in halo2.
+pub(super) const MAX_PHASE: usize = 3;
+
+/// # Vertical Gate Strategy:
+/// `q_0 * (a + b * c - d) = 0`
+/// where
+/// * `a = value[0], b = value[1], c = value[2], d = value[3]`
+/// * `q = q_enable[0]`
+/// * `q` is either 0 or 1 so this is just a simple selector
+/// We chose `a + b * c` instead of `a * b + c` to allow "chaining" of gates, i.e., the output of one gate because `a` in the next gate.
+///
/// A configuration for a basic gate chip describing the selector, and advice column values.
#[derive(Clone, Debug)]
pub struct BasicGateConfig {
@@ -45,13 +48,17 @@ pub struct BasicGateConfig {
}
impl BasicGateConfig {
+ /// Constructor
+ pub fn new(q_enable: Selector, value: Column) -> Self {
+ Self { q_enable, value, _marker: PhantomData }
+ }
+
/// Instantiates a new [BasicGateConfig].
///
/// Assumes `phase` is in the range [0, MAX_PHASE).
/// * `meta`: [ConstraintSystem] used for the gate
- /// * `strategy`: The [GateStrategy] to use for the gate
/// * `phase`: The phase to add the gate to
- pub fn configure(meta: &mut ConstraintSystem, strategy: GateStrategy, phase: u8) -> Self {
+ pub fn configure(meta: &mut ConstraintSystem, phase: u8) -> Self {
let value = match phase {
0 => meta.advice_column_in(FirstPhase),
1 => meta.advice_column_in(SecondPhase),
@@ -62,13 +69,9 @@ impl BasicGateConfig {
let q_enable = meta.selector();
- match strategy {
- GateStrategy::Vertical => {
- let config = Self { q_enable, value, _marker: PhantomData };
- config.create_gate(meta);
- config
- }
- }
+ let config = Self { q_enable, value, _marker: PhantomData };
+ config.create_gate(meta);
+ config
}
/// Wrapper for [ConstraintSystem].create_gate(name, meta) creates a gate form [q * (a + b * c - out)].
@@ -87,83 +90,64 @@ impl BasicGateConfig {
}
}
+/// A Config struct defining the parameters for [FlexGateConfig]
+#[derive(Clone, Default, Debug, Serialize, Deserialize)]
+pub struct FlexGateConfigParams {
+ /// Specifies the number of rows in the circuit to be 2k
+ pub k: usize,
+ /// The number of advice columns per phase
+ pub num_advice_per_phase: Vec,
+ /// The number of fixed columns
+ pub num_fixed: usize,
+}
+
/// Defines a configuration for a flex gate chip describing the selector, and advice column values for the chip.
#[derive(Clone, Debug)]
pub struct FlexGateConfig {
/// A [Vec] of [BasicGateConfig] that define gates for each halo2 phase.
- pub basic_gates: [Vec>; MAX_PHASE],
+ pub basic_gates: Vec>>,
/// A [Vec] of [Fixed] [Column]s for allocating constant values.
pub constants: Vec>,
- /// Number of advice columns for each halo2 phase.
- pub num_advice: [usize; MAX_PHASE],
- /// [GateStrategy] for the flex gate.
- _strategy: GateStrategy,
- /// Max number of rows in flex gate.
+ /// Max number of usable rows in the circuit.
pub max_rows: usize,
}
impl FlexGateConfig {
/// Generates a new [FlexGateConfig]
///
- /// Assumes `num_advice` is a [Vec] of length [MAX_PHASE]
/// * `meta`: [ConstraintSystem] of the circuit
- /// * `strategy`: [GateStrategy] of the flex gate
- /// * `num_advice`: Number of [Advice] [Column]s in each phase
- /// * `num_fixed`: Number of [Fixed] [Column]s in each phase
- /// * `circuit_degree`: Degree that expresses the size of circuit (i.e., 2^circuit_degree is the number of rows in the circuit)
- pub fn configure(
- meta: &mut ConstraintSystem,
- strategy: GateStrategy,
- num_advice: &[usize],
- num_fixed: usize,
- // log2_ceil(# rows in circuit)
- circuit_degree: usize,
- ) -> Self {
+ /// * `params`: see [FlexGateConfigParams]
+ pub fn configure(meta: &mut ConstraintSystem