From 98bc83be43af623f4f1a00c8c95db081844773c4 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 19 Jun 2023 18:50:35 -0700 Subject: [PATCH] Performance optimizations (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: remove `Result` from `assign_advice` return value * apply `cargo clippy --fix` * feat: remove `Result` from `assign_advice` return value * apply `cargo clippy --fix` * feat: add timers to "profile" feature * FFT opt * chore: fix measurement display when "profile" is on only * feat: parallelize vanishing random poly generation; remove `Result`s and unwrap instead * feat: add new parallel implementation for `permute_expression_pair` to get (A', S') that is fully multi-threaded: this is a different algorithm than the original `permute_expression_pair_seq` * revert: go back to `Rng` without `Clone` * chore: remove rng: Sync requirement for compatibility reasons * Expose mod `permutation` and re-export `permutation::keygen::Assembly` (#149) * feat: expose mod ule `permutation` and re-export `permutation::keygen::Assembly` * feat: derive `lone` for `permutation::keygen::Assembly` * feat: bump MSRV for `inferno` * feat(MockProver): replace errors by asserts In MockProver, replace all code that returns an error by an assert that panics instead of returning the error. This change aims to make it easier to debug circuit code bugs by getting backtraces. * feat: parallelize vanishing rand poly using `thread_rng()` for now * MockProver test utililities (#153) * test/unwrap_value: escape Value safety in the dev module * test/mock-prover-values: MockProver exposes the generated columns to tests * test/mock-prover-values: doc * mockprover-util: remove unwrap_value --------- Co-authored-by: Aurélien Nicolas * feat: Parallel random blinder poly impl (#152) * feat: Parallelize `commit` blinder poly generator method Solves the concerns raised in #151 related to the performance of the random poly generator inside of `commit`. Resolves: #151 * chore: add `from_evals` for Polynomial * chore: add benches for commit_zk serial vs par * fix: Correct thread_seeds iter size * fix: Clippy * chore: apply review suggestions * fix: Inconsisten num of Scalars generated parallely This fix from @ed255 fixes an error on the code proposal which was rounding the num of Scalars to be generated and so, was producing failures. Co-authored-by: Edu * remove: legacy comments & code --------- Co-authored-by: Edu * chore: remove debug_assert on phase to allow non-specialized circuits to pass --------- Co-authored-by: kilic Co-authored-by: NoCtrlZ Co-authored-by: Brechtpd Co-authored-by: David Nevado Co-authored-by: han0110 Co-authored-by: Nalin Bhardwaj Co-authored-by: Jonathan Wang Co-authored-by: adria0 Co-authored-by: Carlos Pérez <37264926+CPerezz@users.noreply.github.com> Co-authored-by: adria0.eth <5526331+adria0@users.noreply.github.com> Co-authored-by: dante <45801863+alexander-camuto@users.noreply.github.com> Co-authored-by: pinkiebell <40266861+pinkiebell@users.noreply.github.com> Co-authored-by: Eduard S Co-authored-by: naure Co-authored-by: Aurélien Nicolas --- .github/workflows/ci.yml | 85 +- .github/workflows/ci_main.yml | 106 +++ arithmetic/curves/src/tests/field.rs | 18 +- halo2_proofs/Cargo.toml | 54 +- halo2_proofs/benches/commit_zk.rs | 65 ++ halo2_proofs/benches/dev_lookup.rs | 2 +- halo2_proofs/benches/plonk.rs | 96 +- halo2_proofs/examples/serialization.rs | 6 +- halo2_proofs/examples/shuffle.rs | 49 +- halo2_proofs/examples/simple-example.rs | 341 ------- halo2_proofs/examples/two-chip.rs | 536 ----------- halo2_proofs/src/arithmetic.rs | 56 +- halo2_proofs/src/circuit.rs | 20 +- .../src/circuit/floor_planner/single_pass.rs | 21 +- halo2_proofs/src/circuit/floor_planner/v1.rs | 8 + halo2_proofs/src/circuit/layouter.rs | 20 +- halo2_proofs/src/dev.rs | 656 ++++++++++++-- halo2_proofs/src/dev/cost.rs | 8 + halo2_proofs/src/dev/failure.rs | 142 ++- halo2_proofs/src/dev/failure/emitter.rs | 82 +- halo2_proofs/src/dev/graph.rs | 8 + halo2_proofs/src/dev/graph/layout.rs | 8 + halo2_proofs/src/dev/metadata.rs | 134 ++- halo2_proofs/src/plonk.rs | 107 ++- halo2_proofs/src/plonk/assigned.rs | 2 +- halo2_proofs/src/plonk/circuit.rs | 70 +- halo2_proofs/src/plonk/evaluation.rs | 30 +- halo2_proofs/src/plonk/keygen.rs | 20 +- halo2_proofs/src/plonk/lookup/prover.rs | 159 +++- halo2_proofs/src/plonk/permutation.rs | 5 + halo2_proofs/src/plonk/permutation/keygen.rs | 170 ++-- halo2_proofs/src/plonk/permutation/prover.rs | 7 +- .../src/plonk/permutation/verifier.rs | 2 +- halo2_proofs/src/plonk/prover.rs | 196 ++-- halo2_proofs/src/plonk/vanishing/prover.rs | 20 +- halo2_proofs/src/plonk/verifier.rs | 4 +- halo2_proofs/src/poly.rs | 18 +- halo2_proofs/src/poly/domain.rs | 516 ++++++++++- halo2_proofs/src/poly/kzg/commitment.rs | 9 +- halo2_proofs/src/poly/multiopen_test.rs | 42 +- halo2_proofs/src/transcript.rs | 199 +++++ halo2_proofs/tests/plonk_api.rs | 842 +++++++++--------- primitives/poseidon/src/grain.rs | 4 +- primitives/poseidon/src/spec.rs | 8 +- rust-toolchain | 2 +- 45 files changed, 3059 insertions(+), 1894 deletions(-) create mode 100644 .github/workflows/ci_main.yml create mode 100644 halo2_proofs/benches/commit_zk.rs delete mode 100644 halo2_proofs/examples/simple-example.rs delete mode 100644 halo2_proofs/examples/two-chip.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6cd03bdc7..9f956c61f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI checks -on: [push, pull_request] +on: [pull_request, push] jobs: test: @@ -21,89 +21,6 @@ jobs: command: test args: --verbose --release --all --all-features - build: - name: Build target ${{ matrix.target }} - runs-on: ubuntu-latest - strategy: - matrix: - target: - - wasm32-unknown-unknown - - wasm32-wasi - - steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - override: false - - name: Add target - run: rustup target add ${{ matrix.target }} - - name: cargo build - uses: actions-rs/cargo@v1 - with: - command: build - args: --features dev-graph,gadget-traces,unstable --target ${{ matrix.target }} - - bitrot: - name: Bitrot check - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - override: false - # Build benchmarks to prevent bitrot - - name: Build benchmarks - uses: actions-rs/cargo@v1 - with: - command: build - args: --benches --examples --all-features - - codecov: - name: Code coverage - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - # Use stable for this to ensure that cargo-tarpaulin can be built. - - uses: actions-rs/toolchain@v1 - with: - override: false - - name: Install cargo-tarpaulin - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-tarpaulin - - name: Generate coverage report - uses: actions-rs/cargo@v1 - with: - command: tarpaulin - args: --all-features --timeout 600 --out Xml - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3.1.0 - - doc-links: - name: Intra-doc links - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - override: false - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch - - # Ensure intra-documentation links all resolve correctly - # Requires #![deny(intra_doc_link_resolution_failure)] in crates. - - name: Check intra-doc links - uses: actions-rs/cargo@v1 - with: - command: doc - args: --all --document-private-items - fmt: name: Rustfmt timeout-minutes: 30 diff --git a/.github/workflows/ci_main.yml b/.github/workflows/ci_main.yml new file mode 100644 index 0000000000..400bff09bd --- /dev/null +++ b/.github/workflows/ci_main.yml @@ -0,0 +1,106 @@ +name: CI checks main + +on: + push: + branches: + - main +jobs: + test: + name: Test on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + override: false + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --release --all --all-features + bitrot: + name: Bitrot check + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + override: false + # Build benchmarks to prevent bitrot + - name: Build benchmarks + uses: actions-rs/cargo@v1 + with: + command: build + args: --benches --examples --all-features + + codecov: + name: Code coverage + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + # Use stable for this to ensure that cargo-tarpaulin can be built. + - uses: actions-rs/toolchain@v1 + with: + override: false + - name: Install cargo-tarpaulin + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-tarpaulin + - name: Generate coverage report + uses: actions-rs/cargo@v1 + with: + command: tarpaulin + args: --all-features --timeout 600 --out Xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3.1.0 + + doc-links: + name: Intra-doc links + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + override: false + - name: cargo fetch + uses: actions-rs/cargo@v1 + with: + command: fetch + + # Ensure intra-documentation links all resolve correctly + # Requires #![deny(intra_doc_link_resolution_failure)] in crates. + - name: Check intra-doc links + uses: actions-rs/cargo@v1 + with: + command: doc + args: --all --document-private-items + + build: + name: Build target ${{ matrix.target }} + runs-on: ubuntu-latest + strategy: + matrix: + target: + - wasm32-unknown-unknown + - wasm32-wasi + + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + override: false + - name: Add target + run: rustup target add ${{ matrix.target }} + - name: cargo build + uses: actions-rs/cargo@v1 + with: + command: build + args: --features dev-graph,gadget-traces,unstable --target ${{ matrix.target }} diff --git a/arithmetic/curves/src/tests/field.rs b/arithmetic/curves/src/tests/field.rs index ab89a19c44..faa609291f 100644 --- a/arithmetic/curves/src/tests/field.rs +++ b/arithmetic/curves/src/tests/field.rs @@ -46,7 +46,7 @@ pub fn random_field_tests(type_name: String) { } fn random_multiplication_tests(mut rng: R, type_name: String) { - let message = format!("multiplication {}", type_name); + let message = format!("multiplication {type_name}"); let start = start_timer!(|| message); for _ in 0..1000000 { let a = F::random(&mut rng); @@ -72,7 +72,7 @@ fn random_multiplication_tests(mut rng: R, type_name: Stri } fn random_addition_tests(mut rng: R, type_name: String) { - let message = format!("addition {}", type_name); + let message = format!("addition {type_name}"); let start = start_timer!(|| message); for _ in 0..1000000 { let a = F::random(&mut rng); @@ -98,7 +98,7 @@ fn random_addition_tests(mut rng: R, type_name: String) { } fn random_subtraction_tests(mut rng: R, type_name: String) { - let message = format!("subtraction {}", type_name); + let message = format!("subtraction {type_name}"); let start = start_timer!(|| message); for _ in 0..1000000 { let a = F::random(&mut rng); @@ -119,7 +119,7 @@ fn random_subtraction_tests(mut rng: R, type_name: String) } fn random_negation_tests(mut rng: R, type_name: String) { - let message = format!("negation {}", type_name); + let message = format!("negation {type_name}"); let start = start_timer!(|| message); for _ in 0..1000000 { let a = F::random(&mut rng); @@ -133,7 +133,7 @@ fn random_negation_tests(mut rng: R, type_name: String) { } fn random_doubling_tests(mut rng: R, type_name: String) { - let message = format!("doubling {}", type_name); + let message = format!("doubling {type_name}"); let start = start_timer!(|| message); for _ in 0..1000000 { let mut a = F::random(&mut rng); @@ -147,7 +147,7 @@ fn random_doubling_tests(mut rng: R, type_name: String) { } fn random_squaring_tests(mut rng: R, type_name: String) { - let message = format!("squaring {}", type_name); + let message = format!("squaring {type_name}"); let start = start_timer!(|| message); for _ in 0..1000000 { let mut a = F::random(&mut rng); @@ -163,7 +163,7 @@ fn random_squaring_tests(mut rng: R, type_name: String) { fn random_inversion_tests(mut rng: R, type_name: String) { assert!(bool::from(F::zero().invert().is_none())); - let message = format!("inversion {}", type_name); + let message = format!("inversion {type_name}"); let start = start_timer!(|| message); for _ in 0..1000000 { let mut a = F::random(&mut rng); @@ -176,7 +176,7 @@ fn random_inversion_tests(mut rng: R, type_name: String) { } fn random_expansion_tests(mut rng: R, type_name: String) { - let message = format!("expansion {}", type_name); + let message = format!("expansion {type_name}"); let start = start_timer!(|| message); for _ in 0..1000000 { // Compare (a + b)(c + d) and (a*c + b*c + a*d + b*d) @@ -215,7 +215,7 @@ pub fn random_serialization_test(type_name: String) { 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, 0xe5, ]); - let message = format!("serialization {}", type_name); + let message = format!("serialization {type_name}"); let start = start_timer!(|| message); for _ in 0..1000000 { let a = F::random(&mut rng); diff --git a/halo2_proofs/Cargo.toml b/halo2_proofs/Cargo.toml index 9d4535d4d5..c3b4219ccf 100644 --- a/halo2_proofs/Cargo.toml +++ b/halo2_proofs/Cargo.toml @@ -23,36 +23,44 @@ keywords = ["halo", "proofs", "zkp", "zkSNARKs"] all-features = true rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] -# [[bench]] -# name = "arithmetic" -# harness = false -# -# [[bench]] -# name = "hashtocurve" -# harness = false -# -# [[bench]] -# name = "plonk" -# harness = false -# -# [[bench]] -# name = "dev_lookup" -# harness = false -# -# [[bench]] -# name = "fft" -# harness = false +[[bench]] +name = "arithmetic" +harness = false + +[[bench]] +name = "commit_zk" +harness = false + +[[bench]] +name = "hashtocurve" +harness = false + +[[bench]] +name = "plonk" +harness = false + +[[bench]] +name = "dev_lookup" +harness = false + +[[bench]] +name = "fft" +harness = false [dependencies] backtrace = { version = "0.3", optional = true } rayon = "1.5.1" +crossbeam = "0.8" ff = "0.12" group = "0.12" halo2curves = { path = "../arithmetic/curves" } -rand_core = { version = "0.6", default-features = false } +rand = "0.8" +rand_core = { version = "0.6", default-features = false} tracing = "0.1" blake2b_simd = "1" rustc-hash = "1.1.0" +sha3 = "0.9.1" +ark-std = { version = "0.3.0", features = ["print-trace"], optional = true } # Developer tooling dependencies plotters = { version = "0.3.0", optional = true } @@ -63,7 +71,7 @@ assert_matches = "1.5" criterion = "0.3" gumdrop = "0.8" proptest = "1" -rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } +rand_core = { version = "0.6", features = ["getrandom"] } [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] getrandom = { version = "0.2", features = ["js"] } @@ -73,8 +81,8 @@ default = ["batch"] dev-graph = ["plotters", "tabbycat"] gadget-traces = ["backtrace"] sanity-checks = [] -batch = ["rand_core/getrandom"] -profile = [] +batch = ["rand/getrandom"] +profile = ["dep:ark-std"] [lib] bench = false diff --git a/halo2_proofs/benches/commit_zk.rs b/halo2_proofs/benches/commit_zk.rs new file mode 100644 index 0000000000..f1d2f70abf --- /dev/null +++ b/halo2_proofs/benches/commit_zk.rs @@ -0,0 +1,65 @@ +extern crate criterion; + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use group::ff::Field; +use halo2_proofs::*; +use halo2curves::pasta::pallas::Scalar; +use rand_chacha::rand_core::RngCore; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; +use rayon::{current_num_threads, prelude::*}; + +fn rand_poly_serial(mut rng: ChaCha20Rng, domain: usize) -> Vec { + // Sample a random polynomial of degree n - 1 + let mut random_poly = vec![Scalar::zero(); 1 << domain]; + for coeff in random_poly.iter_mut() { + *coeff = Scalar::random(&mut rng); + } + + random_poly +} + +fn rand_poly_par(mut rng: ChaCha20Rng, domain: usize) -> Vec { + // Sample a random polynomial of degree n - 1 + let n_threads = current_num_threads(); + let n = 1usize << domain; + let n_chunks = n_threads + if n % n_threads != 0 { 1 } else { 0 }; + let mut rand_vec = vec![Scalar::zero(); n]; + + let mut thread_seeds: Vec = (0..n_chunks) + .into_iter() + .map(|_| { + let mut seed = [0u8; 32]; + rng.fill_bytes(&mut seed); + ChaCha20Rng::from_seed(seed) + }) + .collect(); + + thread_seeds + .par_iter_mut() + .zip_eq(rand_vec.par_chunks_mut(n / n_threads)) + .for_each(|(mut rng, chunk)| chunk.iter_mut().for_each(|v| *v = Scalar::random(&mut rng))); + + rand_vec +} + +fn bench_commit(c: &mut Criterion) { + let mut group = c.benchmark_group("Blinder_poly"); + let rand = ChaCha20Rng::from_seed([1u8; 32]); + for i in [ + 18usize, 19usize, 20usize, 21usize, 22usize, 23usize, 24usize, 25usize, + ] + .iter() + { + group.bench_with_input(BenchmarkId::new("serial", i), i, |b, i| { + b.iter(|| rand_poly_serial(rand.clone(), *i)) + }); + group.bench_with_input(BenchmarkId::new("parallel", i), i, |b, i| { + b.iter(|| rand_poly_par(rand.clone(), *i)) + }); + } + group.finish(); +} + +criterion_group!(benches, bench_commit); +criterion_main!(benches); diff --git a/halo2_proofs/benches/dev_lookup.rs b/halo2_proofs/benches/dev_lookup.rs index 2c86fc6766..6665993bc1 100644 --- a/halo2_proofs/benches/dev_lookup.rs +++ b/halo2_proofs/benches/dev_lookup.rs @@ -81,7 +81,7 @@ fn criterion_benchmark(c: &mut Criterion) { config.advice, offset as usize, Value::known(F::from((offset % 256) + 1)), - )?; + ); } Ok(()) diff --git a/halo2_proofs/benches/plonk.rs b/halo2_proofs/benches/plonk.rs index a679961463..1a3091d251 100644 --- a/halo2_proofs/benches/plonk.rs +++ b/halo2_proofs/benches/plonk.rs @@ -94,38 +94,18 @@ fn criterion_benchmark(c: &mut Criterion) { || "raw_multiply", |mut region| { let mut value = None; - let lhs = region.assign_advice( - || "lhs", - self.config.a, - 0, - || { - value = Some(f()); - value.unwrap().map(|v| v.0) - }, - )?; - let rhs = region.assign_advice( - || "rhs", - self.config.b, - 0, - || value.unwrap().map(|v| v.1), - )?; - let out = region.assign_advice( - || "out", - self.config.c, - 0, - || value.unwrap().map(|v| v.2), - )?; - - region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::zero()))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::zero()))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?; - region.assign_fixed( - || "a * b", - self.config.sm, - 0, - || Value::known(FF::one()), - )?; - Ok((lhs.cell(), rhs.cell(), out.cell())) + let lhs = region.assign_advice(self.config.a, 0, { + value = Some(f()); + value.unwrap().map(|v| v.0) + }); + let rhs = region.assign_advice(self.config.b, 0, value.unwrap().map(|v| v.1)); + let out = region.assign_advice(self.config.c, 0, value.unwrap().map(|v| v.2)); + + region.assign_fixed(self.config.sa, 0, FF::zero()); + region.assign_fixed(self.config.sb, 0, FF::zero()); + region.assign_fixed(self.config.sc, 0, FF::one()); + region.assign_fixed(self.config.sm, 0, FF::one()); + Ok((*lhs.cell(), *rhs.cell(), *out.cell())) }, ) } @@ -141,38 +121,18 @@ fn criterion_benchmark(c: &mut Criterion) { || "raw_add", |mut region| { let mut value = None; - let lhs = region.assign_advice( - || "lhs", - self.config.a, - 0, - || { - value = Some(f()); - value.unwrap().map(|v| v.0) - }, - )?; - let rhs = region.assign_advice( - || "rhs", - self.config.b, - 0, - || value.unwrap().map(|v| v.1), - )?; - let out = region.assign_advice( - || "out", - self.config.c, - 0, - || value.unwrap().map(|v| v.2), - )?; - - region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::one()))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::one()))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?; - region.assign_fixed( - || "a * b", - self.config.sm, - 0, - || Value::known(FF::zero()), - )?; - Ok((lhs.cell(), rhs.cell(), out.cell())) + let lhs = region.assign_advice(self.config.a, 0, { + value = Some(f()); + value.unwrap().map(|v| v.0) + }); + let rhs = region.assign_advice(self.config.b, 0, value.unwrap().map(|v| v.1)); + let out = region.assign_advice(self.config.c, 0, value.unwrap().map(|v| v.2)); + + region.assign_fixed(self.config.sa, 0, FF::one()); + region.assign_fixed(self.config.sb, 0, FF::one()); + region.assign_fixed(self.config.sc, 0, FF::one()); + region.assign_fixed(self.config.sm, 0, FF::zero()); + Ok((*lhs.cell(), *rhs.cell(), *out.cell())) }, ) } @@ -182,7 +142,13 @@ fn criterion_benchmark(c: &mut Criterion) { left: Cell, right: Cell, ) -> Result<(), Error> { - layouter.assign_region(|| "copy", |mut region| region.constrain_equal(left, right)) + layouter.assign_region( + || "copy", + |mut region| { + region.constrain_equal(&left, &right); + Ok(()) + }, + ) } } diff --git a/halo2_proofs/examples/serialization.rs b/halo2_proofs/examples/serialization.rs index 49474f924e..8b925b7adf 100644 --- a/halo2_proofs/examples/serialization.rs +++ b/halo2_proofs/examples/serialization.rs @@ -103,10 +103,10 @@ impl Circuit for StandardPlonk { layouter.assign_region( || "", |mut region| { - region.assign_advice(config.a, 0, Value::known(self.0))?; + region.assign_advice(config.a, 0, Value::known(self.0)); region.assign_fixed(config.q_a, 0, -Fr::one()); - region.assign_advice(config.a, 1, Value::known(-Fr::from(5u64)))?; + region.assign_advice(config.a, 1, Value::known(-Fr::from(5u64))); for (idx, column) in (1..).zip([ config.q_a, config.q_b, @@ -117,7 +117,7 @@ impl Circuit for StandardPlonk { region.assign_fixed(column, 1, Fr::from(idx as u64)); } - let a = region.assign_advice(config.a, 2, Value::known(Fr::one()))?; + let a = region.assign_advice(config.a, 2, Value::known(Fr::one())); a.copy_advice(&mut region, config.b, 3); a.copy_advice(&mut region, config.c, 4); Ok(()) diff --git a/halo2_proofs/examples/shuffle.rs b/halo2_proofs/examples/shuffle.rs index 71ba001848..3075caea3a 100644 --- a/halo2_proofs/examples/shuffle.rs +++ b/halo2_proofs/examples/shuffle.rs @@ -7,10 +7,10 @@ use halo2_proofs::{ plonk::*, poly::{ commitment::ParamsProver, - ipa::{ - commitment::{IPACommitmentScheme, ParamsIPA}, - multiopen::{ProverIPA, VerifierIPA}, - strategy::AccumulatorStrategy, + kzg::{ + commitment::{KZGCommitmentScheme, ParamsKZG}, + multiopen::{ProverSHPLONK, VerifierSHPLONK}, + strategy::SingleStrategy, }, Rotation, VerificationStrategy, }, @@ -18,6 +18,7 @@ use halo2_proofs::{ Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, }, }; +use halo2curves::bn256::{Bn256, Fr, G1Affine}; use rand_core::{OsRng, RngCore}; use std::iter; @@ -171,10 +172,7 @@ impl Circuit for MyCircuit Circuit for MyCircuit Circuit for MyCircuit( match (prover.verify(), expected) { (Ok(_), Ok(_)) => {} (Err(err), Err(expected)) => { - assert_eq!( + /*assert_eq!( err.into_iter() .map(|failure| match failure { VerifyFailure::ConstraintNotSatisfied { @@ -270,18 +262,18 @@ fn test_mock_prover( }) .collect::>(), expected - ) + )*/ } (_, _) => panic!("MockProver::verify has result unmatching expected"), }; } -fn test_prover( +fn test_prover( k: u32, - circuit: MyCircuit, + circuit: MyCircuit, expected: bool, ) { - let params = ParamsIPA::::new(k); + let params = ParamsKZG::::new(k); let vk = keygen_vk(¶ms, &circuit).unwrap(); let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); @@ -289,7 +281,7 @@ fn test_prover( let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); println!("Begin create proof"); - create_proof::, ProverIPA, _, _, _, _>( + create_proof::, ProverSHPLONK, _, _, _, _>( ¶ms, &pk, &[circuit], @@ -304,18 +296,17 @@ fn test_prover( }; let accepted = { - let strategy = AccumulatorStrategy::new(¶ms); + let strategy = SingleStrategy::new(¶ms); let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - verify_proof::, VerifierIPA, _, _, _>( + verify_proof::, VerifierSHPLONK, _, _, _>( ¶ms, pk.get_vk(), strategy, &[&[]], &mut transcript, ) - .map(|strategy| strategy.finalize()) - .unwrap_or_default() + .is_ok() }; assert_eq!(accepted, expected); @@ -330,7 +321,7 @@ fn main() { { test_mock_prover(K, circuit.clone(), Ok(())); - test_prover::(K, circuit.clone(), true); + test_prover::(K, circuit.clone(), true); } #[cfg(not(feature = "sanity-checks"))] @@ -354,6 +345,6 @@ fn main() { }, )]), ); - test_prover::(K, circuit, false); + test_prover::(K, circuit, false); } } diff --git a/halo2_proofs/examples/simple-example.rs b/halo2_proofs/examples/simple-example.rs deleted file mode 100644 index 3531511f8f..0000000000 --- a/halo2_proofs/examples/simple-example.rs +++ /dev/null @@ -1,341 +0,0 @@ -use std::marker::PhantomData; - -use halo2_proofs::{ - arithmetic::FieldExt, - circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector}, - poly::Rotation, -}; - -// ANCHOR: instructions -trait NumericInstructions: Chip { - /// Variable representing a number. - type Num; - - /// Loads a number into the circuit as a private input. - fn load_private(&self, layouter: impl Layouter, a: Value) -> Result; - - /// Loads a number into the circuit as a fixed constant. - fn load_constant(&self, layouter: impl Layouter, constant: F) -> Result; - - /// Returns `c = a * b`. - fn mul( - &self, - layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result; - - /// Exposes a number as a public input to the circuit. - fn expose_public( - &self, - layouter: impl Layouter, - num: Self::Num, - row: usize, - ) -> Result<(), Error>; -} -// ANCHOR_END: instructions - -// ANCHOR: chip -/// The chip that will implement our instructions! Chips store their own -/// config, as well as type markers if necessary. -struct FieldChip { - config: FieldConfig, - _marker: PhantomData, -} -// ANCHOR_END: chip - -// ANCHOR: chip-config -/// Chip state is stored in a config struct. This is generated by the chip -/// during configuration, and then stored inside the chip. -#[derive(Clone, Debug)] -struct FieldConfig { - /// For this chip, we will use two advice columns to implement our instructions. - /// These are also the columns through which we communicate with other parts of - /// the circuit. - advice: [Column; 2], - - /// This is the public input (instance) column. - instance: Column, - - // We need a selector to enable the multiplication gate, so that we aren't placing - // any constraints on cells where `NumericInstructions::mul` is not being used. - // This is important when building larger circuits, where columns are used by - // multiple sets of instructions. - s_mul: Selector, -} - -impl FieldChip { - fn construct(config: >::Config) -> Self { - Self { - config, - _marker: PhantomData, - } - } - - fn configure( - meta: &mut ConstraintSystem, - advice: [Column; 2], - instance: Column, - constant: Column, - ) -> >::Config { - meta.enable_equality(instance); - meta.enable_constant(constant); - for column in &advice { - meta.enable_equality(*column); - } - let s_mul = meta.selector(); - - // Define our multiplication gate! - meta.create_gate("mul", |meta| { - // To implement multiplication, we need three advice cells and a selector - // cell. We arrange them like so: - // - // | a0 | a1 | s_mul | - // |-----|-----|-------| - // | lhs | rhs | s_mul | - // | out | | | - // - // Gates may refer to any relative offsets we want, but each distinct - // offset adds a cost to the proof. The most common offsets are 0 (the - // current row), 1 (the next row), and -1 (the previous row), for which - // `Rotation` has specific constructors. - let lhs = meta.query_advice(advice[0], Rotation::cur()); - let rhs = meta.query_advice(advice[1], Rotation::cur()); - let out = meta.query_advice(advice[0], Rotation::next()); - let s_mul = meta.query_selector(s_mul); - - // Finally, we return the polynomial expressions that constrain this gate. - // For our multiplication gate, we only need a single polynomial constraint. - // - // The polynomial expressions returned from `create_gate` will be - // constrained by the proving system to equal zero. Our expression - // has the following properties: - // - When s_mul = 0, any value is allowed in lhs, rhs, and out. - // - When s_mul != 0, this constrains lhs * rhs = out. - vec![s_mul * (lhs * rhs - out)] - }); - - FieldConfig { - advice, - instance, - s_mul, - } - } -} -// ANCHOR_END: chip-config - -// ANCHOR: chip-impl -impl Chip for FieldChip { - type Config = FieldConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} -// ANCHOR_END: chip-impl - -// ANCHOR: instructions-impl -/// A variable representing a number. -#[derive(Clone)] -struct Number(AssignedCell); - -impl NumericInstructions for FieldChip { - type Num = Number; - - fn load_private( - &self, - mut layouter: impl Layouter, - value: Value, - ) -> Result { - let config = self.config(); - - layouter.assign_region( - || "load private", - |mut region| { - region - .assign_advice(|| "private input", config.advice[0], 0, || value) - .map(Number) - }, - ) - } - - fn load_constant( - &self, - mut layouter: impl Layouter, - constant: F, - ) -> Result { - let config = self.config(); - - layouter.assign_region( - || "load constant", - |mut region| { - region - .assign_advice_from_constant(|| "constant value", config.advice[0], 0, constant) - .map(Number) - }, - ) - } - - fn mul( - &self, - mut layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result { - let config = self.config(); - - layouter.assign_region( - || "mul", - |mut region: Region<'_, F>| { - // We only want to use a single multiplication gate in this region, - // so we enable it at region offset 0; this means it will constrain - // cells at offsets 0 and 1. - config.s_mul.enable(&mut region, 0)?; - - // The inputs we've been given could be located anywhere in the circuit, - // but we can only rely on relative offsets inside this region. So we - // assign new cells inside the region and constrain them to have the - // same values as the inputs. - a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; - b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; - - // Now we can assign the multiplication result, which is to be assigned - // into the output position. - let value = a.0.value().copied() * b.0.value(); - - // Finally, we do the assignment to the output, returning a - // variable to be used in another part of the circuit. - region - .assign_advice(|| "lhs * rhs", config.advice[0], 1, || value) - .map(Number) - }, - ) - } - - fn expose_public( - &self, - mut layouter: impl Layouter, - num: Self::Num, - row: usize, - ) -> Result<(), Error> { - let config = self.config(); - - layouter.constrain_instance(num.0.cell(), config.instance, row); - Ok(()) - } -} -// ANCHOR_END: instructions-impl - -// ANCHOR: circuit -/// The full circuit implementation. -/// -/// In this struct we store the private input variables. We use `Option` because -/// they won't have any value during key generation. During proving, if any of these -/// were `None` we would get an error. -#[derive(Default)] -struct MyCircuit { - constant: F, - a: Value, - b: Value, -} - -impl Circuit for MyCircuit { - // Since we are using a single chip for everything, we can just reuse its config. - type Config = FieldConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - // We create the two advice columns that FieldChip uses for I/O. - let advice = [meta.advice_column(), meta.advice_column()]; - - // We also need an instance column to store public inputs. - let instance = meta.instance_column(); - - // Create a fixed column to load constants. - let constant = meta.fixed_column(); - - FieldChip::configure(meta, advice, instance, constant) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let field_chip = FieldChip::::construct(config); - - // Load our private values into the circuit. - let a = field_chip.load_private(layouter.namespace(|| "load a"), self.a)?; - let b = field_chip.load_private(layouter.namespace(|| "load b"), self.b)?; - - // Load the constant factor into the circuit. - let constant = - field_chip.load_constant(layouter.namespace(|| "load constant"), self.constant)?; - - // We only have access to plain multiplication. - // We could implement our circuit as: - // asq = a*a - // bsq = b*b - // absq = asq*bsq - // c = constant*asq*bsq - // - // but it's more efficient to implement it as: - // ab = a*b - // absq = ab^2 - // c = constant*absq - let ab = field_chip.mul(layouter.namespace(|| "a * b"), a, b)?; - let absq = field_chip.mul(layouter.namespace(|| "ab * ab"), ab.clone(), ab)?; - let c = field_chip.mul(layouter.namespace(|| "constant * absq"), constant, absq)?; - - // Expose the result as a public input to the circuit. - field_chip.expose_public(layouter.namespace(|| "expose c"), c, 0) - } -} -// ANCHOR_END: circuit - -fn main() { - use halo2_proofs::dev::MockProver; - use halo2curves::pasta::Fp; - - // ANCHOR: test-circuit - // The number of rows in our circuit cannot exceed 2^k. Since our example - // circuit is very small, we can pick a very small value here. - let k = 4; - - // Prepare the private and public inputs to the circuit! - let constant = Fp::from(7); - let a = Fp::from(2); - let b = Fp::from(3); - let c = constant * a.square() * b.square(); - - // Instantiate the circuit with the private inputs. - let circuit = MyCircuit { - constant, - a: Value::known(a), - b: Value::known(b), - }; - - // Arrange the public input. We expose the multiplication result in row 0 - // of the instance column, so we position it there in our public inputs. - let mut public_inputs = vec![c]; - - // Given the correct public input, our circuit will verify. - let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - - // If we try some other public input, the proof will fail! - public_inputs[0] += Fp::one(); - let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); - assert!(prover.verify().is_err()); - // ANCHOR_END: test-circuit -} diff --git a/halo2_proofs/examples/two-chip.rs b/halo2_proofs/examples/two-chip.rs deleted file mode 100644 index 61d40f93ca..0000000000 --- a/halo2_proofs/examples/two-chip.rs +++ /dev/null @@ -1,536 +0,0 @@ -use std::marker::PhantomData; - -use halo2_proofs::{ - arithmetic::FieldExt, - circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, - poly::Rotation, -}; - -// ANCHOR: field-instructions -/// A variable representing a number. -#[derive(Clone)] -struct Number(AssignedCell); - -trait FieldInstructions: AddInstructions + MulInstructions { - /// Variable representing a number. - type Num; - - /// Loads a number into the circuit as a private input. - fn load_private( - &self, - layouter: impl Layouter, - a: Value, - ) -> Result<>::Num, Error>; - - /// Returns `d = (a + b) * c`. - fn add_and_mul( - &self, - layouter: &mut impl Layouter, - a: >::Num, - b: >::Num, - c: >::Num, - ) -> Result<>::Num, Error>; - - /// Exposes a number as a public input to the circuit. - fn expose_public( - &self, - layouter: impl Layouter, - num: >::Num, - row: usize, - ) -> Result<(), Error>; -} -// ANCHOR_END: field-instructions - -// ANCHOR: add-instructions -trait AddInstructions: Chip { - /// Variable representing a number. - type Num; - - /// Returns `c = a + b`. - fn add( - &self, - layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result; -} -// ANCHOR_END: add-instructions - -// ANCHOR: mul-instructions -trait MulInstructions: Chip { - /// Variable representing a number. - type Num; - - /// Returns `c = a * b`. - fn mul( - &self, - layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result; -} -// ANCHOR_END: mul-instructions - -// ANCHOR: field-config -// The top-level config that provides all necessary columns and permutations -// for the other configs. -#[derive(Clone, Debug)] -struct FieldConfig { - /// For this chip, we will use two advice columns to implement our instructions. - /// These are also the columns through which we communicate with other parts of - /// the circuit. - advice: [Column; 2], - - /// Public inputs - instance: Column, - - add_config: AddConfig, - mul_config: MulConfig, -} -// ANCHOR END: field-config - -// ANCHOR: add-config -#[derive(Clone, Debug)] -struct AddConfig { - advice: [Column; 2], - s_add: Selector, -} -// ANCHOR_END: add-config - -// ANCHOR: mul-config -#[derive(Clone, Debug)] -struct MulConfig { - advice: [Column; 2], - s_mul: Selector, -} -// ANCHOR END: mul-config - -// ANCHOR: field-chip -/// The top-level chip that will implement the `FieldInstructions`. -struct FieldChip { - config: FieldConfig, - _marker: PhantomData, -} -// ANCHOR_END: field-chip - -// ANCHOR: add-chip -struct AddChip { - config: AddConfig, - _marker: PhantomData, -} -// ANCHOR END: add-chip - -// ANCHOR: mul-chip -struct MulChip { - config: MulConfig, - _marker: PhantomData, -} -// ANCHOR_END: mul-chip - -// ANCHOR: add-chip-trait-impl -impl Chip for AddChip { - type Config = AddConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} -// ANCHOR END: add-chip-trait-impl - -// ANCHOR: add-chip-impl -impl AddChip { - fn construct(config: >::Config, _loaded: >::Loaded) -> Self { - Self { - config, - _marker: PhantomData, - } - } - - fn configure( - meta: &mut ConstraintSystem, - advice: [Column; 2], - ) -> >::Config { - let s_add = meta.selector(); - - // Define our addition gate! - meta.create_gate("add", |meta| { - let lhs = meta.query_advice(advice[0], Rotation::cur()); - let rhs = meta.query_advice(advice[1], Rotation::cur()); - let out = meta.query_advice(advice[0], Rotation::next()); - let s_add = meta.query_selector(s_add); - - vec![s_add * (lhs + rhs - out)] - }); - - AddConfig { advice, s_add } - } -} -// ANCHOR END: add-chip-impl - -// ANCHOR: add-instructions-impl -impl AddInstructions for FieldChip { - type Num = Number; - fn add( - &self, - layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result { - let config = self.config().add_config.clone(); - - let add_chip = AddChip::::construct(config, ()); - add_chip.add(layouter, a, b) - } -} - -impl AddInstructions for AddChip { - type Num = Number; - - fn add( - &self, - mut layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result { - let config = self.config(); - - layouter.assign_region( - || "add", - |mut region: Region<'_, F>| { - // We only want to use a single addition gate in this region, - // so we enable it at region offset 0; this means it will constrain - // cells at offsets 0 and 1. - config.s_add.enable(&mut region, 0)?; - - // The inputs we've been given could be located anywhere in the circuit, - // but we can only rely on relative offsets inside this region. So we - // assign new cells inside the region and constrain them to have the - // same values as the inputs. - a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; - b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; - - // Now we can compute the addition result, which is to be assigned - // into the output position. - let value = a.0.value().copied() + b.0.value(); - - // Finally, we do the assignment to the output, returning a - // variable to be used in another part of the circuit. - region - .assign_advice(|| "lhs + rhs", config.advice[0], 1, || value) - .map(Number) - }, - ) - } -} -// ANCHOR END: add-instructions-impl - -// ANCHOR: mul-chip-trait-impl -impl Chip for MulChip { - type Config = MulConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} -// ANCHOR END: mul-chip-trait-impl - -// ANCHOR: mul-chip-impl -impl MulChip { - fn construct(config: >::Config, _loaded: >::Loaded) -> Self { - Self { - config, - _marker: PhantomData, - } - } - - fn configure( - meta: &mut ConstraintSystem, - advice: [Column; 2], - ) -> >::Config { - for column in &advice { - meta.enable_equality(*column); - } - let s_mul = meta.selector(); - - // Define our multiplication gate! - meta.create_gate("mul", |meta| { - // To implement multiplication, we need three advice cells and a selector - // cell. We arrange them like so: - // - // | a0 | a1 | s_mul | - // |-----|-----|-------| - // | lhs | rhs | s_mul | - // | out | | | - // - // Gates may refer to any relative offsets we want, but each distinct - // offset adds a cost to the proof. The most common offsets are 0 (the - // current row), 1 (the next row), and -1 (the previous row), for which - // `Rotation` has specific constructors. - let lhs = meta.query_advice(advice[0], Rotation::cur()); - let rhs = meta.query_advice(advice[1], Rotation::cur()); - let out = meta.query_advice(advice[0], Rotation::next()); - let s_mul = meta.query_selector(s_mul); - - // The polynomial expression returned from `create_gate` will be - // constrained by the proving system to equal zero. Our expression - // has the following properties: - // - When s_mul = 0, any value is allowed in lhs, rhs, and out. - // - When s_mul != 0, this constrains lhs * rhs = out. - vec![s_mul * (lhs * rhs - out)] - }); - - MulConfig { advice, s_mul } - } -} -// ANCHOR_END: mul-chip-impl - -// ANCHOR: mul-instructions-impl -impl MulInstructions for FieldChip { - type Num = Number; - fn mul( - &self, - layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result { - let config = self.config().mul_config.clone(); - let mul_chip = MulChip::::construct(config, ()); - mul_chip.mul(layouter, a, b) - } -} - -impl MulInstructions for MulChip { - type Num = Number; - - fn mul( - &self, - mut layouter: impl Layouter, - a: Self::Num, - b: Self::Num, - ) -> Result { - let config = self.config(); - - layouter.assign_region( - || "mul", - |mut region: Region<'_, F>| { - // We only want to use a single multiplication gate in this region, - // so we enable it at region offset 0; this means it will constrain - // cells at offsets 0 and 1. - config.s_mul.enable(&mut region, 0)?; - - // The inputs we've been given could be located anywhere in the circuit, - // but we can only rely on relative offsets inside this region. So we - // assign new cells inside the region and constrain them to have the - // same values as the inputs. - a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?; - b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?; - - // Now we can compute the multiplication result, which is to be assigned - // into the output position. - let value = a.0.value().copied() * b.0.value(); - - // Finally, we do the assignment to the output, returning a - // variable to be used in another part of the circuit. - region - .assign_advice(|| "lhs * rhs", config.advice[0], 1, || value) - .map(Number) - }, - ) - } -} -// ANCHOR END: mul-instructions-impl - -// ANCHOR: field-chip-trait-impl -impl Chip for FieldChip { - type Config = FieldConfig; - type Loaded = (); - - fn config(&self) -> &Self::Config { - &self.config - } - - fn loaded(&self) -> &Self::Loaded { - &() - } -} -// ANCHOR_END: field-chip-trait-impl - -// ANCHOR: field-chip-impl -impl FieldChip { - fn construct(config: >::Config, _loaded: >::Loaded) -> Self { - Self { - config, - _marker: PhantomData, - } - } - - fn configure( - meta: &mut ConstraintSystem, - advice: [Column; 2], - instance: Column, - ) -> >::Config { - let add_config = AddChip::configure(meta, advice); - let mul_config = MulChip::configure(meta, advice); - - meta.enable_equality(instance); - - FieldConfig { - advice, - instance, - add_config, - mul_config, - } - } -} -// ANCHOR_END: field-chip-impl - -// ANCHOR: field-instructions-impl -impl FieldInstructions for FieldChip { - type Num = Number; - - fn load_private( - &self, - mut layouter: impl Layouter, - value: Value, - ) -> Result<>::Num, Error> { - let config = self.config(); - - layouter.assign_region( - || "load private", - |mut region| { - region - .assign_advice(|| "private input", config.advice[0], 0, || value) - .map(Number) - }, - ) - } - - /// Returns `d = (a + b) * c`. - fn add_and_mul( - &self, - layouter: &mut impl Layouter, - a: >::Num, - b: >::Num, - c: >::Num, - ) -> Result<>::Num, Error> { - let ab = self.add(layouter.namespace(|| "a + b"), a, b)?; - self.mul(layouter.namespace(|| "(a + b) * c"), ab, c) - } - - fn expose_public( - &self, - mut layouter: impl Layouter, - num: >::Num, - row: usize, - ) -> Result<(), Error> { - let config = self.config(); - - layouter.constrain_instance(num.0.cell(), config.instance, row) - } -} -// ANCHOR_END: field-instructions-impl - -// ANCHOR: circuit -/// The full circuit implementation. -/// -/// In this struct we store the private input variables. We use `Value` because -/// they won't have any value during key generation. During proving, if any of these -/// were `Value::unknown()` we would get an error. -#[derive(Default)] -struct MyCircuit { - a: Value, - b: Value, - c: Value, -} - -impl Circuit for MyCircuit { - // Since we are using a single chip for everything, we can just reuse its config. - type Config = FieldConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self::default() - } - - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - // We create the two advice columns that FieldChip uses for I/O. - let advice = [meta.advice_column(), meta.advice_column()]; - - // We also need an instance column to store public inputs. - let instance = meta.instance_column(); - - FieldChip::configure(meta, advice, instance) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - let field_chip = FieldChip::::construct(config, ()); - - // Load our private values into the circuit. - let a = field_chip.load_private(layouter.namespace(|| "load a"), self.a)?; - let b = field_chip.load_private(layouter.namespace(|| "load b"), self.b)?; - let c = field_chip.load_private(layouter.namespace(|| "load c"), self.c)?; - - // Use `add_and_mul` to get `d = (a + b) * c`. - let d = field_chip.add_and_mul(&mut layouter, a, b, c)?; - - // Expose the result as a public input to the circuit. - field_chip.expose_public(layouter.namespace(|| "expose d"), d, 0) - } -} -// ANCHOR_END: circuit - -#[allow(clippy::many_single_char_names)] -fn main() { - use group::ff::Field; - use halo2_proofs::dev::MockProver; - use halo2curves::pasta::Fp; - use rand_core::OsRng; - - // ANCHOR: test-circuit - // The number of rows in our circuit cannot exceed 2^k. Since our example - // circuit is very small, we can pick a very small value here. - let k = 4; - - // Prepare the private and public inputs to the circuit! - let rng = OsRng; - let a = Fp::random(rng); - let b = Fp::random(rng); - let c = Fp::random(rng); - let d = (a + b) * c; - - // Instantiate the circuit with the private inputs. - let circuit = MyCircuit { - a: Value::known(a), - b: Value::known(b), - c: Value::known(c), - }; - - // Arrange the public input. We expose the multiplication result in row 0 - // of the instance column, so we position it there in our public inputs. - let mut public_inputs = vec![d]; - - // Given the correct public input, our circuit will verify. - let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); - assert_eq!(prover.verify(), Ok(())); - - // If we try some other public input, the proof will fail! - public_inputs[0] += Fp::one(); - let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); - assert!(prover.verify().is_err()); - // ANCHOR_END: test-circuit -} diff --git a/halo2_proofs/src/arithmetic.rs b/halo2_proofs/src/arithmetic.rs index 69b63502bf..99d2ebb893 100644 --- a/halo2_proofs/src/arithmetic.rs +++ b/halo2_proofs/src/arithmetic.rs @@ -10,6 +10,9 @@ use group::{ pub use halo2curves::{CurveAffine, CurveExt, FieldExt, Group}; +/// TEMP +pub static mut MULTIEXP_TOTAL_TIME: usize = 0; + fn multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: &mut C::Curve) { let coeffs: Vec<_> = coeffs.iter().map(|a| a.to_repr()).collect(); @@ -132,8 +135,11 @@ pub fn small_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::C pub fn best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { assert_eq!(coeffs.len(), bases.len()); + //println!("msm: {}", coeffs.len()); + + let start = get_time(); let num_threads = multicore::current_num_threads(); - if coeffs.len() > num_threads { + let res = if coeffs.len() > num_threads { let chunk = coeffs.len() / num_threads; let num_chunks = coeffs.chunks(chunk).len(); let mut results = vec![C::Curve::identity(); num_chunks]; @@ -155,7 +161,15 @@ pub fn best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Cu let mut acc = C::Curve::identity(); multiexp_serial(coeffs, bases, &mut acc); acc + }; + + let duration = get_duration(start); + #[allow(unsafe_code)] + unsafe { + MULTIEXP_TOTAL_TIME += duration; } + + res } /// Performs a radix-$2$ Fast-Fourier Transformation (FFT) on a vector of size @@ -180,7 +194,7 @@ pub fn best_fft(a: &mut [G], omega: G::Scalar, log_n: u32) { let threads = multicore::current_num_threads(); let log_threads = log2_floor(threads); - let n = a.len() as usize; + let n = a.len(); assert_eq!(n, 1 << log_n); for k in 0..n { @@ -190,18 +204,20 @@ pub fn best_fft(a: &mut [G], omega: G::Scalar, log_n: u32) { } } + //let start = start_measure(format!("twiddles {} ({})", a.len(), threads), false); // precompute twiddle factors - let twiddles: Vec<_> = (0..(n / 2) as usize) + let twiddles: Vec<_> = (0..(n / 2)) .scan(G::Scalar::one(), |w, _| { let tw = *w; w.group_scale(&omega); Some(tw) }) .collect(); + //stop_measure(start); if log_n <= log_threads { let mut chunk = 2_usize; - let mut twiddle_chunk = (n / 2) as usize; + let mut twiddle_chunk = n / 2; for _ in 0..log_n { a.chunks_mut(chunk).for_each(|coeffs| { let (left, right) = coeffs.split_at_mut(chunk / 2); @@ -275,7 +291,7 @@ pub fn recursive_butterfly_arithmetic( /// Convert coefficient bases group elements to lagrange basis by inverse FFT. pub fn g_to_lagrange(g_projective: Vec, k: u32) -> Vec { - let n_inv = C::Scalar::TWO_INV.pow_vartime(&[k as u64, 0, 0, 0]); + let n_inv = C::Scalar::TWO_INV.pow_vartime([k as u64, 0, 0, 0]); let mut omega_inv = C::Scalar::ROOT_OF_UNITY_INV; for _ in k..C::Scalar::S { omega_inv = omega_inv.square(); @@ -320,7 +336,7 @@ pub fn eval_polynomial(poly: &[F], point: F) -> F { { scope.spawn(move |_| { let start = chunk_idx * chunk_size; - out[0] = evaluate(poly, point) * point.pow_vartime(&[start as u64, 0, 0, 0]); + out[0] = evaluate(poly, point) * point.pow_vartime([start as u64, 0, 0, 0]); }); } }); @@ -371,9 +387,9 @@ where pub fn parallelize(v: &mut [T], f: F) { let n = v.len(); let num_threads = multicore::current_num_threads(); - let mut chunk = (n as usize) / num_threads; + let mut chunk = n / num_threads; if chunk < num_threads { - chunk = n as usize; + chunk = 1; } multicore::scope(|scope| { @@ -387,6 +403,29 @@ pub fn parallelize(v: &mu }); } +/// This simple utility function will parallelize an operation that is to be +/// performed over a mutable slice. +pub fn parallelize_count( + v: &mut [T], + num_threads: usize, + f: F, +) { + let n = v.len(); + let mut chunk = n / num_threads; + if chunk < num_threads { + chunk = n; + } + + multicore::scope(|scope| { + for (chunk_num, v) in v.chunks_mut(chunk).enumerate() { + let f = f.clone(); + scope.spawn(move |_| { + f(v, chunk_num); + }); + } + }); +} + fn log2_floor(num: usize) -> u32 { assert!(num > 0); @@ -486,6 +525,7 @@ use rand_core::OsRng; #[cfg(test)] use crate::halo2curves::pasta::Fp; +use crate::plonk::{get_duration, get_time, start_measure, stop_measure}; #[test] fn test_lagrange_interpolate() { diff --git a/halo2_proofs/src/circuit.rs b/halo2_proofs/src/circuit.rs index 294a636940..36ee37ee5f 100644 --- a/halo2_proofs/src/circuit.rs +++ b/halo2_proofs/src/circuit.rs @@ -171,9 +171,7 @@ impl<'v, F: Field> AssignedCell<&'v Assigned, F> { column: Column, offset: usize, ) -> AssignedCell<&'_ Assigned, F> { - let assigned_cell = region - .assign_advice(column, offset, self.value.map(|v| *v)) - .unwrap_or_else(|err| panic!("{err:?}")); + let assigned_cell = region.assign_advice(column, offset, self.value.map(|v| *v)); region.constrain_equal(&assigned_cell.cell, &self.cell); assigned_cell } @@ -217,6 +215,20 @@ impl<'r, F: Field> Region<'r, F> { .enable_selector(&|| annotation().into(), selector, offset) } + /// Allows the circuit implementor to name/annotate a Column within a Region context. + /// + /// This is useful in order to improve the amount of information that `prover.verify()` + /// and `prover.assert_satisfied()` can provide. + pub fn name_column(&mut self, annotation: A, column: T) + where + A: Fn() -> AR, + AR: Into, + T: Into>, + { + self.region + .name_column(&|| annotation().into(), column.into()); + } + /// Assign an advice column value (witness). /// /// Even though `to` has `FnMut` bounds, it is guaranteed to be called at most once. @@ -228,7 +240,7 @@ impl<'r, F: Field> Region<'r, F> { column: Column, offset: usize, to: Value>>, // For now only accept Value, later might change to Value> for batch inversion - ) -> Result, F>, Error> { + ) -> AssignedCell<&'v Assigned, F> { //let mut value = Value::unknown(); self.region.assign_advice( //&|| annotation().into(), diff --git a/halo2_proofs/src/circuit/floor_planner/single_pass.rs b/halo2_proofs/src/circuit/floor_planner/single_pass.rs index fc2aecdbcf..a29e86f150 100644 --- a/halo2_proofs/src/circuit/floor_planner/single_pass.rs +++ b/halo2_proofs/src/circuit/floor_planner/single_pass.rs @@ -275,20 +275,27 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter ) } + fn name_column<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + ) { + self.layouter.cs.annotate_column(annotation, column); + } + fn assign_advice<'b, 'v>( &'b mut self, // annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, to: Value>, // &'v mut (dyn FnMut() -> Value> + 'v), - ) -> Result, F>, Error> { + ) -> AssignedCell<&'v Assigned, F> { let value = self.layouter.cs.assign_advice( - // annotation, column, offset, //*self.layouter.regions[*self.region_index] + offset, to, - )?; + ); - Ok(AssignedCell { + AssignedCell { value, cell: Cell { // region_index: self.region_index, @@ -296,7 +303,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter column: column.into(), }, _marker: PhantomData, - }) + } } fn assign_advice_from_constant<'v>( @@ -307,7 +314,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter constant: Assigned, ) -> Result { let advice = self - .assign_advice(column, offset, Value::known(constant))? + .assign_advice(column, offset, Value::known(constant)) .cell; self.constrain_constant(advice, constant)?; @@ -325,7 +332,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter let value = self.layouter.cs.query_instance(instance, row)?; let cell = self - .assign_advice(advice, offset, value.map(|v| Assigned::Trivial(v)))? + .assign_advice(advice, offset, value.map(|v| Assigned::Trivial(v))) .cell; self.layouter.cs.copy( diff --git a/halo2_proofs/src/circuit/floor_planner/v1.rs b/halo2_proofs/src/circuit/floor_planner/v1.rs index 657b7f2636..04af946a34 100644 --- a/halo2_proofs/src/circuit/floor_planner/v1.rs +++ b/halo2_proofs/src/circuit/floor_planner/v1.rs @@ -483,6 +483,14 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r Ok(()) } + fn name_column<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + ) { + self.plan.cs.annotate_column(annotation, column) + } + fn constrain_equal(&mut self, left: Cell, right: Cell) -> Result<(), Error> { self.plan.cs.copy( left.column, diff --git a/halo2_proofs/src/circuit/layouter.rs b/halo2_proofs/src/circuit/layouter.rs index ba4e585432..07e9249236 100644 --- a/halo2_proofs/src/circuit/layouter.rs +++ b/halo2_proofs/src/circuit/layouter.rs @@ -50,6 +50,16 @@ pub trait RegionLayouter: fmt::Debug { offset: usize, ) -> Result<(), Error>; + /// Allows the circuit implementor to name/annotate a Column within a Region context. + /// + /// This is useful in order to improve the amount of information that `prover.verify()` + /// and `prover.assert_satisfied()` can provide. + fn name_column<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + ); + /// Assign an advice column value (witness) fn assign_advice<'b, 'v>( &'b mut self, @@ -57,7 +67,7 @@ pub trait RegionLayouter: fmt::Debug { column: Column, offset: usize, to: Value>, // &'v mut (dyn FnMut() -> Value> + 'v), - ) -> Result, F>, Error>; + ) -> AssignedCell<&'v Assigned, F>; /// Assigns a constant value to the column `advice` at `offset` within this region. /// @@ -289,6 +299,14 @@ impl RegionLayouter for RegionShape { }) } + fn name_column<'v>( + &'v mut self, + _annotation: &'v (dyn Fn() -> String + 'v), + _column: Column, + ) { + // Do nothing + } + fn constrain_constant(&mut self, _cell: Cell, _constant: Assigned) -> Result<(), Error> { // Global constants don't affect the region shape. Ok(()) diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index 76f997d543..f9eb16af17 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -11,13 +11,16 @@ use std::time::{Duration, Instant}; use blake2b_simd::blake2b; use ff::Field; +use crate::plonk::permutation::keygen::Assembly; use crate::{ arithmetic::{FieldExt, Group}, circuit, plonk::{ - permutation, Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, ColumnType, - ConstraintSystem, Error, Expression, Fixed, FloorPlanner, Instance, Phase, Selector, - VirtualCell, + permutation, + sealed::{self, SealedPhase}, + Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, ColumnType, + ConstraintSystem, Error, Expression, FirstPhase, Fixed, FloorPlanner, Instance, Phase, + Selector, VirtualCell, }, poly::Rotation, }; @@ -29,6 +32,7 @@ use rayon::{ }; pub mod metadata; +use metadata::Column as ColumnMetadata; mod util; mod failure; @@ -58,7 +62,9 @@ struct Region { /// The selectors that have been enabled in this region. All other selectors are by /// construction not enabled. enabled_selectors: HashMap>, - /// The cells assigned in this region. We store this as a `HashMap` with count so that if any cells + /// Annotations given to Advice, Fixed or Instance columns within a region context. + annotations: HashMap, + /// The cells assigned in this region. We store this as a `Vec` so that if any cells /// are double-assigned, they will be visibly darker. cells: HashMap<(Column, usize), usize>, } @@ -83,18 +89,18 @@ impl Region { /// The value of a particular cell within the circuit. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum CellValue { - // An unassigned cell. +pub enum CellValue { + /// An unassigned cell. Unassigned, - // A cell that has been assigned a value. + /// A cell that has been assigned a value. Assigned(F), - // A unique poisoned cell. + /// A unique poisoned cell. Poison(usize), } /// The value of a particular cell within the circuit. #[derive(Clone, Debug, PartialEq, Eq)] -enum AdviceCellValue { +pub enum AdviceCellValue { // A cell that has been assigned a value. Assigned(Arc>), // A unique poisoned cell. @@ -278,13 +284,15 @@ impl Mul for Value { /// }]) /// ); /// -/// // If we provide a too-small K, we get an error. -/// assert!(matches!( -/// MockProver::::run(2, &circuit, vec![]).unwrap_err(), -/// Error::NotEnoughRowsAvailable { -/// current_k, -/// } if current_k == 2, -/// )); +/// // If we provide a too-small K, we get a panic. +/// use std::panic; +/// let result = panic::catch_unwind(|| { +/// MockProver::::run(2, &circuit, vec![]).unwrap_err() +/// }); +/// assert_eq!( +/// result.unwrap_err().downcast_ref::().unwrap(), +/// "n=4, minimum_rows=8, k=2" +/// ); /// ``` #[derive(Debug)] pub struct MockProver { @@ -313,6 +321,14 @@ pub struct MockProver { // A range of available rows for assignment and copies. usable_rows: Range, + + current_phase: sealed::Phase, +} + +impl MockProver { + fn in_phase(&self, phase: P) -> bool { + self.current_phase == phase.to_sealed() + } } impl Assignment for MockProver { @@ -321,29 +337,62 @@ impl Assignment for MockProver { NR: Into, N: FnOnce() -> NR, { + if !self.in_phase(FirstPhase) { + return; + } + assert!(self.current_region.is_none()); self.current_region = Some(Region { name: name().into(), columns: HashSet::default(), rows: None, + annotations: HashMap::default(), enabled_selectors: HashMap::default(), cells: HashMap::default(), }); } fn exit_region(&mut self) { + if !self.in_phase(FirstPhase) { + return; + } + self.regions.push(self.current_region.take().unwrap()); } + fn annotate_column(&mut self, annotation: A, column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + if !self.in_phase(FirstPhase) { + return; + } + + if let Some(region) = self.current_region.as_mut() { + region + .annotations + .insert(ColumnMetadata::from(column), annotation().into()); + } + } + fn enable_selector(&mut self, _: A, selector: &Selector, row: usize) -> Result<(), Error> where A: FnOnce() -> AR, AR: Into, { - if !self.usable_rows.contains(&row) { - return Err(Error::not_enough_rows_available(self.k)); + if !self.in_phase(FirstPhase) { + return Ok(()); } + assert!( + self.usable_rows.contains(&row), + "row={} not in usable_rows={:?}, k={}", + row, + self.usable_rows, + self.k, + ); + // Track that this selector was enabled. We require that all selectors are enabled // inside some region (i.e. no floating selectors). self.current_region @@ -364,15 +413,20 @@ impl Assignment for MockProver { column: Column, row: usize, ) -> Result, Error> { - if !self.usable_rows.contains(&row) { - return Err(Error::not_enough_rows_available(self.k)); - } + assert!( + self.usable_rows.contains(&row), + "row={}, usable_rows={:?}, k={}", + row, + self.usable_rows, + self.k, + ); - self.instance + Ok(self + .instance .get(column.index()) .and_then(|column| column.get(row)) .map(|v| circuit::Value::known(*v)) - .ok_or(Error::BoundsFailure) + .expect("bound failure")) } fn assign_advice<'r, 'v>( @@ -382,38 +436,62 @@ impl Assignment for MockProver { column: Column, row: usize, to: circuit::Value>, - ) -> Result>, Error> { - if !self.usable_rows.contains(&row) { - return Err(Error::not_enough_rows_available(self.k)); + ) -> circuit::Value<&'v Assigned> { + if self.in_phase(FirstPhase) { + assert!( + self.usable_rows.contains(&row), + "row={}, usable_rows={:?}, k={}", + row, + self.usable_rows, + self.k, + ); } - if let Some(region) = self.current_region.as_mut() { - region.update_extent(column.into(), row); - region - .cells - .entry((column.into(), row)) - .and_modify(|count| *count += 1) - .or_default(); + match to.assign() { + Ok(to) => { + let value = self + .advice + .get_mut(column.index()) + .and_then(|v| v.get_mut(row)) + .expect("bounds failure"); + /* We don't use this because we do assign 0s in first pass of second phase sometimes + if let AdviceCellValue::Assigned(value) = value { + // Inconsistent assignment between different phases. + assert_eq!(value.as_ref(), &to, "value={:?}, to={:?}", &value, &to); + let val = Arc::clone(&value); + let val_ref = Arc::downgrade(&val); + circuit::Value::known(unsafe { &*val_ref.as_ptr() }) + } else { + */ + let val = Arc::new(to); + let val_ref = Arc::downgrade(&val); + *value = AdviceCellValue::Assigned(val); + circuit::Value::known(unsafe { &*val_ref.as_ptr() }) + //} + } + Err(err) => { + // Propagate `assign` error if the column is in current phase. + if self.in_phase(column.column_type().phase) { + panic!("{:?}", err); + } + circuit::Value::unknown() + } } - - let advice_get_mut = self - .advice - .get_mut(column.index()) - .and_then(|v| v.get_mut(row)) - .ok_or(Error::BoundsFailure)?; - - let val = Arc::new(to.assign()?); - let val_ref = Arc::downgrade(&val); - *advice_get_mut = AdviceCellValue::Assigned(val); - - Ok(circuit::Value::known(unsafe { &*val_ref.as_ptr() })) } fn assign_fixed(&mut self, column: Column, row: usize, to: Assigned) { - if !self.usable_rows.contains(&row) { - panic!("{:?}", Error::not_enough_rows_available(self.k)); + if !self.in_phase(FirstPhase) { + return; } + assert!( + self.usable_rows.contains(&row), + "row={}, usable_rows={:?}, k={}", + row, + self.usable_rows, + self.k, + ); + if let Some(region) = self.current_region.as_mut() { region.update_extent(column.into(), row); region @@ -427,8 +505,7 @@ impl Assignment for MockProver { .fixed .get_mut(column.index()) .and_then(|v| v.get_mut(row)) - .unwrap_or_else(|| panic!("{:?}", Error::BoundsFailure)) = - CellValue::Assigned(to.evaluate()); + .expect("bounds failure") = CellValue::Assigned(to.evaluate()); } fn copy( @@ -438,10 +515,19 @@ impl Assignment for MockProver { right_column: Column, right_row: usize, ) { - if !self.usable_rows.contains(&left_row) || !self.usable_rows.contains(&right_row) { - panic!("{:?}", Error::not_enough_rows_available(self.k)); + if !self.in_phase(FirstPhase) { + return; } + assert!( + self.usable_rows.contains(&left_row) && self.usable_rows.contains(&right_row), + "left_row={}, right_row={}, usable_rows={:?}, k={}", + left_row, + right_row, + self.usable_rows, + self.k, + ); + self.permutation .copy(left_column, left_row, right_column, right_row) .unwrap_or_else(|err| panic!("{err:?}")) @@ -453,10 +539,18 @@ impl Assignment for MockProver { from_row: usize, to: circuit::Value>, ) -> Result<(), Error> { - if !self.usable_rows.contains(&from_row) { - return Err(Error::not_enough_rows_available(self.k)); + if !self.in_phase(FirstPhase) { + return Ok(()); } + assert!( + self.usable_rows.contains(&from_row), + "row={}, usable_rows={:?}, k={}", + from_row, + self.usable_rows, + self.k, + ); + for row in self.usable_rows.clone().skip(from_row) { self.assign_fixed(col, row, to.assign()?); } @@ -465,6 +559,10 @@ impl Assignment for MockProver { } fn get_challenge(&self, challenge: Challenge) -> circuit::Value { + if self.current_phase <= challenge.phase { + return circuit::Value::unknown(); + } + circuit::Value::known(self.challenges[challenge.index()]) } @@ -495,25 +593,31 @@ impl MockProver { let config = ConcreteCircuit::configure(&mut cs); let cs = cs; - if n < cs.minimum_rows() { - return Err(Error::not_enough_rows_available(k)); - } + assert!( + n >= cs.minimum_rows(), + "n={}, minimum_rows={}, k={}", + n, + cs.minimum_rows(), + k, + ); - if instance.len() != cs.num_instance_columns { - return Err(Error::InvalidInstances); - } + assert_eq!(instance.len(), cs.num_instance_columns); let instance = instance .into_iter() .map(|mut instance| { - if instance.len() > n - (cs.blinding_factors() + 1) { - return Err(Error::InstanceTooLarge); - } + assert!( + instance.len() <= n - (cs.blinding_factors() + 1), + "instance.len={}, n={}, cs.blinding_factors={}", + instance.len(), + n, + cs.blinding_factors() + ); instance.resize(n, F::zero()); - Ok(instance) + instance }) - .collect::, _>>()?; + .collect::>(); // Fixed columns contain no blinding factors. let fixed = vec![vec![CellValue::Unassigned; n]; cs.num_fixed_columns]; @@ -562,9 +666,18 @@ impl MockProver { challenges, permutation, usable_rows: 0..usable_rows, + current_phase: FirstPhase.to_sealed(), }; - ConcreteCircuit::FloorPlanner::synthesize(&mut prover, circuit, config, constants)?; + for current_phase in prover.cs.phases() { + prover.current_phase = current_phase; + ConcreteCircuit::FloorPlanner::synthesize( + &mut prover, + circuit, + config.clone(), + constants.clone(), + )?; + } let (cs, selector_polys) = prover.cs.compress_selectors(prover.selectors.clone()); prover.cs = cs; @@ -579,6 +692,16 @@ impl MockProver { Ok(prover) } + /// Return the content of an advice column as assigned by the circuit. + pub fn advice_values(&self, column: Column) -> &[AdviceCellValue] { + &self.advice[column.index()] + } + + /// Return the content of a fixed column as assigned by the circuit. + pub fn fixed_values(&self, column: Column) -> &[CellValue] { + &self.fixed[column.index()] + } + /// Returns `Ok(())` if this `MockProver` is satisfied, or a list of errors indicating /// the reasons that the circuit is not satisfied. pub fn verify(&self) -> Result<(), Vec> { @@ -638,12 +761,12 @@ impl MockProver { let cell_row = ((gate_row + n + cell.rotation.0) % n) as usize; // Check that it was assigned! - if r.cells.contains_key(&(cell.column, cell_row)) { + if r.cells.get(&(cell.column, cell_row)).is_some() { None } else { Some(VerifyFailure::CellNotAssigned { gate: (gate_index, gate.name()).into(), - region: (r_i, r.name.clone()).into(), + region: (r_i, r.name.clone(), r.annotations.clone()).into(), gate_offset: *selector_row, column: cell.column, offset: cell_row as isize - r.rows.unwrap().0 as isize, @@ -906,7 +1029,7 @@ impl MockProver { // Iterate over each column of the permutation self.permutation - .mapping + .mapping() .iter() .enumerate() .flat_map(move |(column, values)| { @@ -965,8 +1088,7 @@ impl MockProver { /// Returns `Ok(())` if this `MockProver` is satisfied, or a list of errors indicating /// the reasons that the circuit is not satisfied. - /// Constraints are only checked at `gate_row_ids`, - /// and lookup inputs are only checked at `lookup_input_row_ids`, parallelly. + /// Constraints are only checked at `gate_row_ids`, and lookup inputs are only checked at `lookup_input_row_ids`, parallelly. pub fn verify_at_rows_par>( &self, gate_row_ids: I, @@ -1025,7 +1147,12 @@ impl MockProver { } else { Some(VerifyFailure::CellNotAssigned { gate: (gate_index, gate.name()).into(), - region: (r_i, r.name.clone()).into(), + region: ( + r_i, + r.name.clone(), + r.annotations.clone(), + ) + .into(), gate_offset: *selector_row, column: cell.column, offset: cell_row as isize @@ -1081,7 +1208,7 @@ impl MockProver { &|scalar| Value::Real(scalar), &|_| panic!("virtual selectors are removed during optimization"), &util::load(n, row, &self.cs.fixed_queries, &self.fixed), - &util::load(n, row, &self.cs.advice_queries, &advice), + &util::load(n, row, &self.cs.advice_queries, advice), &util::load_instance( n, row, @@ -1113,7 +1240,7 @@ impl MockProver { gate, poly, &util::load(n, row, &self.cs.fixed_queries, &self.fixed), - &util::load(n, row, &self.cs.advice_queries, &advice), + &util::load(n, row, &self.cs.advice_queries, advice), &util::load_instance( n, row, @@ -1281,7 +1408,7 @@ impl MockProver { // Iterate over each column of the permutation self.permutation - .mapping + .mapping() .iter() .enumerate() .flat_map(move |(column, values)| { @@ -1374,6 +1501,41 @@ impl MockProver { panic!("circuit was not satisfied"); } } + + /// Panics if the circuit being checked by this `MockProver` is not satisfied. + /// + /// Any verification failures will be pretty-printed to stderr before the function + /// panics. + /// + /// Constraints are only checked at `gate_row_ids`, and lookup inputs are only checked at `lookup_input_row_ids`, parallelly. + /// + /// Apart from the stderr output, this method is equivalent to: + /// ```ignore + /// assert_eq!(prover.verify_at_rows_par(), Ok(())); + /// ``` + pub fn assert_satisfied_at_rows_par>( + &self, + gate_row_ids: I, + lookup_input_row_ids: I, + ) { + if let Err(errs) = self.verify_at_rows_par(gate_row_ids, lookup_input_row_ids) { + for err in errs { + err.emit(self); + eprintln!(); + } + panic!("circuit was not satisfied"); + } + } + + /// Returns the list of Fixed Columns used within a MockProver instance and the associated values contained on each Cell. + pub fn fixed(&self) -> &Vec>> { + &self.fixed + } + + /// Returns the permutation argument (`Assembly`) used within a MockProver instance. + pub fn permutation(&self) -> &Assembly { + &self.permutation + } } #[cfg(test)] @@ -1384,8 +1546,8 @@ mod tests { use crate::{ circuit::{Layouter, SimpleFloorPlanner, Value}, plonk::{ - Advice, Any, Assigned, Circuit, Column, ConstraintSystem, Error, Expression, Selector, - TableColumn, + sealed::SealedPhase, Advice, Any, Assigned, Circuit, Column, ConstraintSystem, Error, + Expression, FirstPhase, Fixed, Instance, Selector, TableColumn, }, poly::Rotation, }; @@ -1397,6 +1559,7 @@ mod tests { #[derive(Clone)] struct FaultyCircuitConfig { a: Column, + b: Column, q: Selector, } @@ -1420,7 +1583,7 @@ mod tests { vec![q * (a - b)] }); - FaultyCircuitConfig { a, q } + FaultyCircuitConfig { a, b, q } } fn without_witnesses(&self) -> Self { @@ -1443,7 +1606,13 @@ mod tests { /*|| "a",*/ config.a, 0, Value::known(Assigned::Trivial(Fp::zero())), - )?; + ); + + // Name Column a + region.name_column(|| "This is annotated!", config.a); + + // Name Column b + region.name_column(|| "This is also annotated!", config.b); // BUG: Forget to assign b = 0! This could go unnoticed during // development, because cell values default to zero, which in this @@ -1461,14 +1630,169 @@ mod tests { gate: (0, "Equality check").into(), region: (0, "Faulty synthesis".to_owned()).into(), gate_offset: 1, - column: Column::new(1, Any::advice()), + column: Column::new( + 1, + Any::Advice(Advice { + phase: FirstPhase.to_sealed() + }) + ), offset: 1, }]) ); } #[test] - fn bad_lookup() { + fn bad_lookup_any() { + const K: u32 = 4; + + #[derive(Clone)] + struct FaultyCircuitConfig { + a: Column, + table: Column, + advice_table: Column, + q: Selector, + } + + struct FaultyCircuit {} + + impl Circuit for FaultyCircuit { + type Config = FaultyCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let a = meta.advice_column(); + let q = meta.complex_selector(); + let table = meta.instance_column(); + let advice_table = meta.advice_column(); + + meta.annotate_lookup_any_column(table, || "Inst-Table"); + meta.enable_equality(table); + meta.annotate_lookup_any_column(advice_table, || "Adv-Table"); + meta.enable_equality(advice_table); + + meta.lookup_any("lookup", |cells| { + let a = cells.query_advice(a, Rotation::cur()); + let q = cells.query_selector(q); + let advice_table = cells.query_advice(advice_table, Rotation::cur()); + let table = cells.query_instance(table, Rotation::cur()); + + // If q is enabled, a must be in the table. + // When q is not enabled, lookup the default value instead. + let not_q = Expression::Constant(Fp::one()) - q.clone(); + let default = Expression::Constant(Fp::from(2)); + vec![ + ( + q.clone() * a.clone() + not_q.clone() * default.clone(), + table, + ), + (q * a + not_q * default, advice_table), + ] + }); + + FaultyCircuitConfig { + a, + q, + table, + advice_table, + } + } + + fn without_witnesses(&self) -> Self { + Self {} + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // No assignment needed for the table as is an Instance Column. + + layouter.assign_region( + || "Good synthesis", + |mut region| { + // Enable the lookup on rows 0 and 1. + config.q.enable(&mut region, 0)?; + config.q.enable(&mut region, 1)?; + + for i in 0..4 { + // Load Advice lookup table with Instance lookup table values. + region.assign_advice_from_instance( + || "Advice from instance tables", + config.table, + i, + config.advice_table, + i, + )?; + } + + // Assign a = 2 and a = 6. + region.assign_advice(config.a, 0, Value::known(Fp::from(2))); + region.assign_advice(config.a, 1, Value::known(Fp::from(6))); + + Ok(()) + }, + )?; + + layouter.assign_region( + || "Faulty synthesis", + |mut region| { + // Enable the lookup on rows 0 and 1. + config.q.enable(&mut region, 0)?; + config.q.enable(&mut region, 1)?; + + for i in 0..4 { + // Load Advice lookup table with Instance lookup table values. + region.assign_advice_from_instance( + || "Advice from instance tables", + config.table, + i, + config.advice_table, + i, + )?; + } + + // Assign a = 4. + region.assign_advice(config.a, 0, Value::known(Fp::from(4))); + + // BUG: Assign a = 5, which doesn't exist in the table! + region.assign_advice(config.a, 1, Value::known(Fp::from(5))); + + region.name_column(|| "Witness example", config.a); + + Ok(()) + }, + ) + } + } + + let prover = MockProver::run( + K, + &FaultyCircuit {}, + // This is our "lookup table". + vec![vec![ + Fp::from(1u64), + Fp::from(2u64), + Fp::from(4u64), + Fp::from(6u64), + ]], + ) + .unwrap(); + assert_eq!( + prover.verify(), + Err(vec![VerifyFailure::Lookup { + name: "lookup", + lookup_index: 0, + location: FailureLocation::InRegion { + region: (1, "Faulty synthesis").into(), + offset: 1, + } + }]) + ); + } + + #[test] + fn bad_fixed_lookup() { const K: u32 = 4; #[derive(Clone)] @@ -1488,6 +1812,7 @@ mod tests { let a = meta.advice_column(); let q = meta.complex_selector(); let table = meta.lookup_table_column(); + meta.annotate_lookup_column(table, || "Table1"); meta.lookup("lookup", |cells| { let a = cells.query_advice(a, Rotation::cur()); @@ -1541,13 +1866,13 @@ mod tests { config.a, 0, Value::known(Assigned::Trivial(Fp::from(2))), - )?; + ); region.assign_advice( // || "a = 6", config.a, 1, Value::known(Assigned::Trivial(Fp::from(6))), - )?; + ); Ok(()) }, @@ -1566,7 +1891,7 @@ mod tests { config.a, 0, Value::known(Assigned::Trivial(Fp::from(4))), - )?; + ); // BUG: Assign a = 5, which doesn't exist in the table! region.assign_advice( @@ -1574,7 +1899,9 @@ mod tests { config.a, 1, Value::known(Assigned::Trivial(Fp::from(5))), - )?; + ); + + region.name_column(|| "Witness example", config.a); Ok(()) }, @@ -1595,4 +1922,165 @@ mod tests { }]) ); } + + #[test] + fn contraint_unsatisfied() { + const K: u32 = 4; + + #[derive(Clone)] + struct FaultyCircuitConfig { + a: Column, + b: Column, + c: Column, + d: Column, + q: Selector, + } + + struct FaultyCircuit {} + + impl Circuit for FaultyCircuit { + type Config = FaultyCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let a = meta.advice_column(); + let b = meta.advice_column(); + let c = meta.advice_column(); + let d = meta.fixed_column(); + let q = meta.selector(); + + meta.create_gate("Equality check", |cells| { + let a = cells.query_advice(a, Rotation::cur()); + let b = cells.query_advice(b, Rotation::cur()); + let c = cells.query_advice(c, Rotation::cur()); + let d = cells.query_fixed(d, Rotation::cur()); + let q = cells.query_selector(q); + + // If q is enabled, a and b must be assigned to. + vec![q * (a - b) * (c - d)] + }); + + FaultyCircuitConfig { a, b, c, d, q } + } + + fn without_witnesses(&self) -> Self { + Self {} + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "Correct synthesis", + |mut region| { + // Enable the equality gate. + config.q.enable(&mut region, 0)?; + + // Assign a = 1. + region.assign_advice(config.a, 0, Value::known(Fp::one())); + + // Assign b = 1. + region.assign_advice(config.b, 0, Value::known(Fp::one())); + + // Assign c = 5. + region.assign_advice(config.c, 0, Value::known(Fp::from(5u64))); + // Assign d = 7. + region.assign_fixed(config.d, 0, Fp::from(7u64)); + Ok(()) + }, + )?; + layouter.assign_region( + || "Wrong synthesis", + |mut region| { + // Enable the equality gate. + config.q.enable(&mut region, 0)?; + + // Assign a = 1. + region.assign_advice(config.a, 0, Value::known(Fp::one())); + + // Assign b = 0. + region.assign_advice(config.b, 0, Value::known(Fp::zero())); + + // Name Column a + region.name_column(|| "This is Advice!", config.a); + // Name Column b + region.name_column(|| "This is Advice too!", config.b); + + // Assign c = 5. + region.assign_advice(config.c, 0, Value::known(Fp::from(5u64))); + // Assign d = 7. + region.assign_fixed(config.d, 0, Fp::from(7u64)); + + // Name Column c + region.name_column(|| "Another one!", config.c); + // Name Column d + region.name_column(|| "This is a Fixed!", config.d); + + // Note that none of the terms cancel eachother. Therefore we will have a constraint that is non satisfied for + // the `Equalty check` gate. + Ok(()) + }, + ) + } + } + + let prover = MockProver::run(K, &FaultyCircuit {}, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![VerifyFailure::ConstraintNotSatisfied { + constraint: ((0, "Equality check").into(), 0, "").into(), + location: FailureLocation::InRegion { + region: (1, "Wrong synthesis").into(), + offset: 0, + }, + cell_values: vec![ + ( + ( + ( + Any::Advice(Advice { + phase: FirstPhase.to_sealed() + }), + 0 + ) + .into(), + 0 + ) + .into(), + "1".to_string() + ), + ( + ( + ( + Any::Advice(Advice { + phase: FirstPhase.to_sealed() + }), + 1 + ) + .into(), + 0 + ) + .into(), + "0".to_string() + ), + ( + ( + ( + Any::Advice(Advice { + phase: FirstPhase.to_sealed() + }), + 2 + ) + .into(), + 0 + ) + .into(), + "0x5".to_string() + ), + (((Any::Fixed, 0).into(), 0).into(), "0x7".to_string()), + ], + },]) + ) + } } diff --git a/halo2_proofs/src/dev/cost.rs b/halo2_proofs/src/dev/cost.rs index a47bdfe0fb..8f293756b7 100644 --- a/halo2_proofs/src/dev/cost.rs +++ b/halo2_proofs/src/dev/cost.rs @@ -122,6 +122,14 @@ impl Assignment for Assembly { Value::unknown() } + fn annotate_column(&mut self, _annotation: A, _column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + // Do nothing + } + fn push_namespace(&mut self, _: N) where NR: Into, diff --git a/halo2_proofs/src/dev/failure.rs b/halo2_proofs/src/dev/failure.rs index 89c0c05d95..3d08a5812d 100644 --- a/halo2_proofs/src/dev/failure.rs +++ b/halo2_proofs/src/dev/failure.rs @@ -1,19 +1,21 @@ -use std::collections::{BTreeMap, BTreeSet, HashSet}; -use std::fmt; -use std::iter; +use std::collections::{BTreeMap, HashSet}; +use std::fmt::{self, Debug}; use group::ff::Field; use halo2curves::FieldExt; +use super::metadata::{DebugColumn, DebugVirtualCell}; +use super::MockProver; use super::{ metadata, util::{self, AnyQuery}, - MockProver, Region, + Region, }; +use crate::dev::metadata::Constraint; use crate::dev::{AdviceCellValue, CellValue}; use crate::plonk::Assigned; use crate::{ - dev::Value, + dev::{Instance, Value}, plonk::{Any, Column, ConstraintSystem, Expression, Gate}, poly::Rotation, }; @@ -21,7 +23,7 @@ use crate::{ mod emitter; /// The location within the circuit at which a particular [`VerifyFailure`] occurred. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum FailureLocation { /// A location inside a region. InRegion { @@ -50,6 +52,16 @@ impl fmt::Display for FailureLocation { } impl FailureLocation { + /// Returns a `DebugColumn` from Column metadata and `&self`. + pub(super) fn get_debug_column(&self, metadata: metadata::Column) -> DebugColumn { + match self { + Self::InRegion { region, .. } => { + DebugColumn::from((metadata, region.column_annotations.as_ref())) + } + _ => DebugColumn::from((metadata, None)), + } + } + pub(super) fn find_expressions<'a, F: Field>( cs: &ConstraintSystem, regions: &[Region], @@ -104,17 +116,15 @@ impl FailureLocation { (start..=end).contains(&failure_row) && !failure_columns.is_disjoint(&r.columns) }) .map(|(r_i, r)| FailureLocation::InRegion { - region: (r_i, r.name.clone()).into(), - offset: failure_row as usize - r.rows.unwrap().0 as usize, - }) - .unwrap_or_else(|| FailureLocation::OutsideRegion { - row: failure_row as usize, + region: (r_i, r.name.clone(), r.annotations.clone()).into(), + offset: failure_row - r.rows.unwrap().0, }) + .unwrap_or_else(|| FailureLocation::OutsideRegion { row: failure_row }) } } /// The reasons why a particular circuit is not satisfied. -#[derive(Debug, PartialEq)] +#[derive(PartialEq, Eq)] pub enum VerifyFailure { /// A cell used in an active gate was not assigned to. CellNotAssigned { @@ -192,8 +202,8 @@ impl fmt::Display for VerifyFailure { } => { write!( f, - "{} uses {} at offset {}, which requires cell in column {:?} at offset {} to be assigned.", - region, gate, gate_offset, column, offset + "{} uses {} at offset {}, which requires cell in column {:?} at offset {} with annotation {:?} to be assigned.", + region, gate, gate_offset, column, offset, region.get_column_annotation((*column).into()) ) } Self::ConstraintNotSatisfied { @@ -202,8 +212,17 @@ impl fmt::Display for VerifyFailure { cell_values, } => { writeln!(f, "{} is not satisfied {}", constraint, location)?; - for (name, value) in cell_values { - writeln!(f, "- {} = {}", name, value)?; + for (dvc, value) in cell_values.iter().map(|(vc, string)| { + let ann_map = match location { + FailureLocation::InRegion { region, offset: _ } => { + ®ion.column_annotations + } + _ => &None, + }; + + (DebugVirtualCell::from((vc, ann_map.as_ref())), string) + }) { + writeln!(f, "- {} = {}", dvc, value)?; } Ok(()) } @@ -228,14 +247,59 @@ impl fmt::Display for VerifyFailure { Self::Permutation { column, location } => { write!( f, - "Equality constraint not satisfied by cell ({:?}, {})", - column, location + "Equality constraint not satisfied by cell ({}, {})", + location.get_debug_column(*column), + location ) } } } } +impl Debug for VerifyFailure { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + VerifyFailure::ConstraintNotSatisfied { + constraint, + location, + cell_values, + } => { + #[allow(dead_code)] + #[derive(Debug)] + struct ConstraintCaseDebug { + constraint: Constraint, + location: FailureLocation, + cell_values: Vec<(DebugVirtualCell, String)>, + } + + let ann_map = match location { + FailureLocation::InRegion { region, offset: _ } => { + region.column_annotations.clone() + } + _ => None, + }; + + let debug = ConstraintCaseDebug { + constraint: *constraint, + location: location.clone(), + cell_values: cell_values + .iter() + .map(|(vc, value)| { + ( + DebugVirtualCell::from((vc, ann_map.as_ref())), + value.clone(), + ) + }) + .collect(), + }; + + write!(f, "{:#?}", debug) + } + _ => write!(f, "{:#}", self), + } + } +} + /// Renders `VerifyFailure::CellNotAssigned`. /// /// ```text @@ -405,11 +469,41 @@ fn render_lookup( // expressions for the table side of lookups. let lookup_columns = lookup.table_expressions.iter().map(|expr| { expr.evaluate( - &|_| panic!("no constants in table expressions"), - &|_| panic!("no selectors in table expressions"), - &|query| format!("F{}", query.column_index), - &|query| format! {"A{}", query.column_index}, - &|query| format! {"I{}", query.column_index}, + &|f| format! {"Const: {:#?}", f}, + &|s| format! {"S{}", s.0}, + &|query| { + format!( + "{:?}", + prover + .cs + .general_column_annotations + .get(&metadata::Column::from((Any::Fixed, query.column_index))) + .cloned() + .unwrap_or_else(|| format!("F{}", query.column_index())) + ) + }, + &|query| { + format!( + "{:?}", + prover + .cs + .general_column_annotations + .get(&metadata::Column::from((Any::advice(), query.column_index))) + .cloned() + .unwrap_or_else(|| format!("A{}", query.column_index())) + ) + }, + &|query| { + format!( + "{:?}", + prover + .cs + .general_column_annotations + .get(&metadata::Column::from((Any::Instance, query.column_index))) + .cloned() + .unwrap_or_else(|| format!("I{}", query.column_index())) + ) + }, &|challenge| format! {"C{}", challenge.index()}, &|query| format! {"-{}", query}, &|a, b| format! {"{} + {}", a,b}, @@ -445,6 +539,7 @@ fn render_lookup( for i in 0..lookup.input_expressions.len() { eprint!("{}L{}", if i == 0 { "" } else { ", " }, i); } + eprint!(") ∉ ("); for (i, column) in lookup_columns.enumerate() { eprint!("{}{}", if i == 0 { "" } else { ", " }, column); @@ -519,6 +614,7 @@ fn render_lookup( emitter::expression_to_string(input, &layout) ); eprintln!(" ^"); + emitter::render_cell_layout(" | ", location, &columns, &layout, |_, rotation| { if rotation == 0 { eprint!(" <--{{ Lookup '{}' inputs queried here", name); diff --git a/halo2_proofs/src/dev/failure/emitter.rs b/halo2_proofs/src/dev/failure/emitter.rs index 91525a20aa..e84ba8013e 100644 --- a/halo2_proofs/src/dev/failure/emitter.rs +++ b/halo2_proofs/src/dev/failure/emitter.rs @@ -11,6 +11,7 @@ use crate::{ fn padded(p: char, width: usize, text: &str) -> String { let pad = width - text.len(); + format!( "{}{}{}", iter::repeat(p).take(pad - pad / 2).collect::(), @@ -19,6 +20,18 @@ fn padded(p: char, width: usize, text: &str) -> String { ) } +fn column_type_and_idx(column: &metadata::Column) -> String { + format!( + "{}{}", + match column.column_type { + Any::Advice(_) => "A", + Any::Fixed => "F", + Any::Instance => "I", + }, + column.index + ) +} + /// Renders a cell layout around a given failure location. /// /// `highlight_row` is called at the end of each row, with the offset of the active row @@ -32,46 +45,76 @@ pub(super) fn render_cell_layout( highlight_row: impl Fn(Option, i32), ) { let col_width = |cells: usize| cells.to_string().len() + 3; + let mut col_headers = String::new(); // If we are in a region, show rows at offsets relative to it. Otherwise, just show // the rotations directly. let offset = match location { FailureLocation::InRegion { region, offset } => { - eprintln!("{}Cell layout in region '{}':", prefix, region.name); - eprint!("{} | Offset |", prefix); + col_headers + .push_str(format!("{}Cell layout in region '{}':\n", prefix, region.name).as_str()); + col_headers.push_str(format!("{} | Offset |", prefix).as_str()); Some(*offset as i32) } FailureLocation::OutsideRegion { row } => { - eprintln!("{}Cell layout at row {}:", prefix, row); - eprint!("{} |Rotation|", prefix); + col_headers.push_str(format!("{}Cell layout at row {}:\n", prefix, row).as_str()); + col_headers.push_str(format!("{} |Rotation|", prefix).as_str()); None } }; + eprint!("\n{}", col_headers); + + let widths: Vec = columns + .iter() + .map(|(col, _)| { + let size = match location { + FailureLocation::InRegion { region, offset: _ } => { + if let Some(column_ann) = region.column_annotations.as_ref() { + if let Some(ann) = column_ann.get(col) { + ann.len() + } else { + col_width(column_type_and_idx(col).as_str().len()) + } + } else { + col_width(column_type_and_idx(col).as_str().len()) + } + } + FailureLocation::OutsideRegion { row: _ } => { + col_width(column_type_and_idx(col).as_str().len()) + } + }; + size + }) + .collect(); - // Print the assigned cells, and their region offset or rotation. - for (column, cells) in columns { - let width = col_width(*cells); + // Print the assigned cells, and their region offset or rotation + the column name at which they're assigned to. + for ((column, _), &width) in columns.iter().zip(widths.iter()) { eprint!( "{}|", padded( ' ', width, - &format!( - "{}{}", - match column.column_type { - Any::Advice(_) => "A", - Any::Fixed => "F", - Any::Instance => "I", - }, - column.index, - ) + &match location { + FailureLocation::InRegion { region, offset: _ } => { + region + .column_annotations + .as_ref() + .and_then(|column_ann| column_ann.get(column).cloned()) + .unwrap_or_else(|| column_type_and_idx(column)) + } + FailureLocation::OutsideRegion { row: _ } => { + column_type_and_idx(column) + } + } + .to_string() ) ); } + eprintln!(); eprint!("{} +--------+", prefix); - for cells in columns.values() { - eprint!("{}+", padded('-', col_width(*cells), "")); + for &width in widths.iter() { + eprint!("{}+", padded('-', width, "")); } eprintln!(); for (rotation, row) in layout { @@ -80,8 +123,7 @@ pub(super) fn render_cell_layout( prefix, padded(' ', 8, &(offset.unwrap_or(0) + rotation).to_string()) ); - for (col, cells) in columns { - let width = col_width(*cells); + for ((col, _), &width) in columns.iter().zip(widths.iter()) { eprint!( "{}|", padded( diff --git a/halo2_proofs/src/dev/graph.rs b/halo2_proofs/src/dev/graph.rs index b8c6fd90d7..5a43313dea 100644 --- a/halo2_proofs/src/dev/graph.rs +++ b/halo2_proofs/src/dev/graph.rs @@ -99,6 +99,14 @@ impl Assignment for Graph { Ok(()) } + fn annotate_column(&mut self, _annotation: A, _column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + // Do nothing + } + fn query_instance(&self, _: Column, _: usize) -> Result, Error> { Ok(Value::unknown()) } diff --git a/halo2_proofs/src/dev/graph/layout.rs b/halo2_proofs/src/dev/graph/layout.rs index 81f45a9010..0f2e67a81d 100644 --- a/halo2_proofs/src/dev/graph/layout.rs +++ b/halo2_proofs/src/dev/graph/layout.rs @@ -494,6 +494,14 @@ impl Assignment for Layout { Value::unknown() } + fn annotate_column(&mut self, _annotation: A, _column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + // Do nothing + } + fn push_namespace(&mut self, _: N) where NR: Into, diff --git a/halo2_proofs/src/dev/metadata.rs b/halo2_proofs/src/dev/metadata.rs index d7d2443e7d..5fd0835bad 100644 --- a/halo2_proofs/src/dev/metadata.rs +++ b/halo2_proofs/src/dev/metadata.rs @@ -1,10 +1,13 @@ //! Metadata about circuits. +use super::metadata::Column as ColumnMetadata; use crate::plonk::{self, Any}; -use std::fmt; - +use std::{ + collections::HashMap, + fmt::{self, Debug}, +}; /// Metadata about a column within a circuit. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Column { /// The type of the column. pub(super) column_type: Any, @@ -33,6 +36,41 @@ impl From> for Column { } } +/// A helper structure that allows to print a Column with it's annotation as a single structure. +#[derive(Debug, Clone)] +pub(super) struct DebugColumn { + /// The type of the column. + column_type: Any, + /// The index of the column. + index: usize, + /// Annotation of the column + annotation: String, +} + +impl From<(Column, Option<&HashMap>)> for DebugColumn { + fn from(info: (Column, Option<&HashMap>)) -> Self { + DebugColumn { + column_type: info.0.column_type, + index: info.0.index, + annotation: info + .1 + .and_then(|map| map.get(&info.0)) + .cloned() + .unwrap_or_default(), + } + } +} + +impl fmt::Display for DebugColumn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Column('{:?}', {} - {})", + self.column_type, self.index, self.annotation + ) + } +} + /// A "virtual cell" is a PLONK cell that has been queried at a particular relative offset /// within a custom gate. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -82,8 +120,36 @@ impl fmt::Display for VirtualCell { } } +/// Helper structure used to be able to inject Column annotations inside a `Display` or `Debug` call. +#[derive(Clone, Debug)] +pub(super) struct DebugVirtualCell { + name: &'static str, + column: DebugColumn, + rotation: i32, +} + +impl From<(&VirtualCell, Option<&HashMap>)> for DebugVirtualCell { + fn from(info: (&VirtualCell, Option<&HashMap>)) -> Self { + DebugVirtualCell { + name: info.0.name, + column: DebugColumn::from((info.0.column, info.1)), + rotation: info.0.rotation, + } + } +} + +impl fmt::Display for DebugVirtualCell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}@{}", self.column, self.rotation)?; + if !self.name.is_empty() { + write!(f, "({})", self.name)?; + } + Ok(()) + } +} + /// Metadata about a configured gate within a circuit. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Gate { /// The index of the active gate. These indices are assigned in the order in which /// `ConstraintSystem::create_gate` is called during `Circuit::configure`. @@ -106,7 +172,7 @@ impl From<(usize, &'static str)> for Gate { } /// Metadata about a configured constraint within a circuit. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Constraint { /// The gate containing the constraint. pub(super) gate: Gate, @@ -143,7 +209,7 @@ impl From<(Gate, usize, &'static str)> for Constraint { } /// Metadata about an assigned region within a circuit. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone)] pub struct Region { /// The index of the region. These indices are assigned in the order in which /// `Layouter::assign_region` is called during `Circuit::synthesize`. @@ -151,6 +217,35 @@ pub struct Region { /// The name of the region. This is specified by the region creator (such as a chip /// implementation), and is not enforced to be unique. pub(super) name: String, + /// A reference to the annotations of the Columns that exist within this `Region`. + pub(super) column_annotations: Option>, +} + +impl Region { + /// Fetch the annotation of a `Column` within a `Region` providing it's associated metadata. + /// + /// This function will return `None` if: + /// - There's no annotation map generated for this `Region`. + /// - There's no entry on the annotation map corresponding to the metadata provided. + pub(crate) fn get_column_annotation(&self, metadata: ColumnMetadata) -> Option { + self.column_annotations + .as_ref() + .and_then(|map| map.get(&metadata).cloned()) + } +} + +impl PartialEq for Region { + fn eq(&self, other: &Self) -> bool { + self.index == other.index && self.name == other.name + } +} + +impl Eq for Region {} + +impl Debug for Region { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Region {} ('{}')", self.index, self.name) + } } impl fmt::Display for Region { @@ -161,7 +256,11 @@ impl fmt::Display for Region { impl From<(usize, String)> for Region { fn from((index, name): (usize, String)) -> Self { - Region { index, name } + Region { + index, + name, + column_annotations: None, + } } } @@ -170,6 +269,27 @@ impl From<(usize, &str)> for Region { Region { index, name: name.to_owned(), + column_annotations: None, + } + } +} + +impl From<(usize, String, HashMap)> for Region { + fn from((index, name, annotations): (usize, String, HashMap)) -> Self { + Region { + index, + name, + column_annotations: Some(annotations), + } + } +} + +impl From<(usize, &str, HashMap)> for Region { + fn from((index, name, annotations): (usize, &str, HashMap)) -> Self { + Region { + index, + name: name.to_owned(), + column_annotations: Some(annotations), } } } diff --git a/halo2_proofs/src/plonk.rs b/halo2_proofs/src/plonk.rs index e40d3deca2..e3bffc000e 100644 --- a/halo2_proofs/src/plonk.rs +++ b/halo2_proofs/src/plonk.rs @@ -20,6 +20,8 @@ use crate::poly::{ }; use crate::transcript::{ChallengeScalar, EncodedChallenge, Transcript}; use crate::SerdeFormat; +#[cfg(feature = "profile")] +use ark_std::perf_trace::{AtomicUsize, Ordering}; mod assigned; mod circuit; @@ -27,7 +29,7 @@ mod error; mod evaluation; mod keygen; mod lookup; -pub(crate) mod permutation; +pub mod permutation; mod vanishing; mod prover; @@ -41,7 +43,110 @@ pub use prover::*; pub use verifier::*; use evaluation::Evaluator; +use std::env::var; use std::io; +use std::time::Instant; + +/// Temp +#[allow(missing_debug_implementations)] +pub struct MeasurementInfo { + /// Temp + pub measure: bool, + /// Temp + pub time: Instant, + /// Message + pub message: String, + /// Indent + pub indent: usize, +} + +/// TEMP +#[cfg(feature = "profile")] +pub static NUM_INDENT: AtomicUsize = AtomicUsize::new(0); + +/// Temp +pub fn get_time() -> Instant { + Instant::now() +} + +/// Temp +pub fn get_duration(start: Instant) -> usize { + let final_time = Instant::now() - start; + let secs = final_time.as_secs() as usize; + let millis = final_time.subsec_millis() as usize; + let micros = (final_time.subsec_micros() % 1000) as usize; + secs * 1000000 + millis * 1000 + micros +} + +/// Temp +pub fn log_measurement(indent: Option, msg: String, duration: usize) { + let indent = indent.unwrap_or(0); + println!( + "{}{} ........ {}s", + "*".repeat(indent), + msg, + (duration as f32) / 1000000.0 + ); +} + +/// Temp +#[cfg(feature = "profile")] +pub fn start_measure>(msg: S, always: bool) -> MeasurementInfo { + let measure: u32 = var("MEASURE") + .unwrap_or_else(|_| "0".to_string()) + .parse() + .expect("Cannot parse MEASURE env var as u32"); + + let indent = NUM_INDENT.fetch_add(1, Ordering::Relaxed); + + if always || measure == 1 + /* || msg.starts_with("compressed_cosets")*/ + { + MeasurementInfo { + measure: true, + time: get_time(), + message: msg.as_ref().to_string(), + indent, + } + } else { + MeasurementInfo { + measure: false, + time: get_time(), + message: "".to_string(), + indent, + } + } +} +#[cfg(not(feature = "profile"))] +pub fn start_measure>(_: S, _: bool) {} + +/// Temp +#[cfg(feature = "profile")] +pub fn stop_measure(info: MeasurementInfo) -> usize { + NUM_INDENT.fetch_sub(1, Ordering::Relaxed); + let duration = get_duration(info.time); + if info.measure { + log_measurement(Some(info.indent), info.message, duration); + } + duration +} +#[cfg(not(feature = "profile"))] +pub fn stop_measure(_: ()) {} + +/// Get env variable +pub fn env_value(key: &str, default: usize) -> usize { + match var(key) { + Ok(val) => val.parse().unwrap(), + Err(_) => default, + } +} + +/// Temp +pub fn log_info(msg: String) { + if env_value("INFO", 0) != 0 { + println!("{}", msg); + } +} /// This is a verifying key which allows for the verification of proofs for a /// particular circuit. diff --git a/halo2_proofs/src/plonk/assigned.rs b/halo2_proofs/src/plonk/assigned.rs index 7524291e4c..64bf144b94 100644 --- a/halo2_proofs/src/plonk/assigned.rs +++ b/halo2_proofs/src/plonk/assigned.rs @@ -613,7 +613,7 @@ mod proptests { // Ensure that: // - we have at least one value to apply unary operators to. // - we can apply every binary operator pairwise sequentially. - cmp::max(if num_unary > 0 { 1 } else { 0 }, num_binary + 1)), + cmp::max(usize::from(num_unary > 0), num_binary + 1)), operations in arb_operators(num_unary, num_binary).prop_shuffle(), ) -> (Vec>, Vec) { (values, operations) diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index 4090b1c523..1a08107e5c 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -1,12 +1,15 @@ use core::cmp::max; use core::ops::{Add, Mul}; use ff::Field; +use std::collections::HashMap; +use std::env::var; use std::{ convert::TryFrom, ops::{Neg, Sub}, }; use super::{lookup, permutation, Assigned, Error}; +use crate::dev::metadata; use crate::{ circuit::{Layouter, Region, Value}, poly::Rotation, @@ -83,6 +86,12 @@ pub(crate) mod sealed { } } + impl SealedPhase for Phase { + fn to_sealed(self) -> Phase { + self + } + } + /// Sealed trait to help keep `Phase` private. pub trait SealedPhase { fn to_sealed(self) -> Phase; @@ -503,7 +512,7 @@ impl TableColumn { #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub struct Challenge { index: usize, - phase: sealed::Phase, + pub(crate) phase: sealed::Phase, } impl Challenge { @@ -533,6 +542,14 @@ pub trait Assignment { NR: Into, N: FnOnce() -> NR; + /// Allows the developer to include an annotation for an specific column within a `Region`. + /// + /// This is usually useful for debugging circuit failures. + fn annotate_column(&mut self, annotation: A, column: Column) + where + A: FnOnce() -> AR, + AR: Into; + /// Exits the current region. /// /// Panics if we are not currently in a region (if `enter_region` was not called). @@ -560,18 +577,11 @@ pub trait Assignment { /// Assign an advice column value (witness) fn assign_advice<'r, 'v>( - //( &'r mut self, - // annotation: A, column: Column, row: usize, - to: Value>, // V, - ) -> Result>, Error>; - // where - // V: FnOnce() -> Value, - // VR: Into>, - // A: FnOnce() -> AR, - // AR: Into; + to: Value>, + ) -> Value<&'v Assigned>; /// Assign a fixed value fn assign_fixed(&mut self, column: Column, row: usize, to: Assigned); @@ -1376,6 +1386,9 @@ pub struct ConstraintSystem { // input expressions and a sequence of table expressions involved in the lookup. pub(crate) lookups: Vec>, + // List of indexes of Fixed columns which are associated to a circuit-general Column tied to their annotation. + pub(crate) general_column_annotations: HashMap, + // Vector of fixed columns, which can be used to store constant values // that are copied into advice columns. pub(crate) constants: Vec>, @@ -1459,6 +1472,7 @@ impl Default for ConstraintSystem { instance_queries: Vec::new(), permutation: permutation::Argument::new(), lookups: Vec::new(), + general_column_annotations: HashMap::new(), constants: vec![], minimum_degree: None, } @@ -1843,6 +1857,34 @@ impl ConstraintSystem { } } + /// Annotate a Lookup column. + pub fn annotate_lookup_column(&mut self, column: TableColumn, annotation: A) + where + A: Fn() -> AR, + AR: Into, + { + // We don't care if the table has already an annotation. If it's the case we keep the new one. + self.general_column_annotations.insert( + metadata::Column::from((Any::Fixed, column.inner().index)), + annotation().into(), + ); + } + + /// Annotate an Instance column. + pub fn annotate_lookup_any_column(&mut self, column: T, annotation: A) + where + A: Fn() -> AR, + AR: Into, + T: Into>, + { + let col_any = column.into(); + // We don't care if the table has already an annotation. If it's the case we keep the new one. + self.general_column_annotations.insert( + metadata::Column::from((col_any.column_type, col_any.index)), + annotation().into(), + ); + } + /// Allocate a new fixed column pub fn fixed_column(&mut self) -> Column { let tmp = Column { @@ -1959,6 +2001,14 @@ impl ConstraintSystem { .unwrap_or(0), ); + fn get_max_degree() -> usize { + var("MAX_DEGREE") + .unwrap_or_else(|_| "5".to_string()) + .parse() + .expect("Cannot parse MAX_DEGREE env var as usize") + } + degree = std::cmp::min(degree, get_max_degree()); + std::cmp::max(degree, self.minimum_degree.unwrap_or(1)) } diff --git a/halo2_proofs/src/plonk/evaluation.rs b/halo2_proofs/src/plonk/evaluation.rs index f324a4d438..b3357c4684 100644 --- a/halo2_proofs/src/plonk/evaluation.rs +++ b/halo2_proofs/src/plonk/evaluation.rs @@ -26,7 +26,7 @@ use std::{ ops::{Index, Mul, MulAssign}, }; -use super::{ConstraintSystem, Expression}; +use super::{start_measure, stop_measure, ConstraintSystem, Expression}; /// Return the index in the polynomial of size `isize` after rotation `rot`. fn get_rotation_idx(idx: usize, rot: i32, rot_scale: i32, isize: i32) -> usize { @@ -303,12 +303,13 @@ impl Evaluator { let p = &pk.vk.cs.permutation; // Calculate the advice and instance cosets + let start = start_measure("cosets", false); let advice: Vec>> = advice_polys .iter() .map(|advice_polys| { advice_polys .iter() - .map(|poly| domain.coeff_to_extended(poly.clone())) + .map(|poly| domain.coeff_to_extended(poly)) .collect() }) .collect(); @@ -317,10 +318,11 @@ impl Evaluator { .map(|instance_polys| { instance_polys .iter() - .map(|poly| domain.coeff_to_extended(poly.clone())) + .map(|poly| domain.coeff_to_extended(poly)) .collect() }) .collect(); + stop_measure(start); let mut values = domain.empty_extended(); @@ -333,6 +335,7 @@ impl Evaluator { .zip(permutations.iter()) { // Custom gates + let start = start_measure("custom gates", false); multicore::scope(|scope| { let chunk_size = (size + num_threads - 1) / num_threads; for (thread_idx, values) in values.chunks_mut(chunk_size).enumerate() { @@ -360,8 +363,10 @@ impl Evaluator { }); } }); + stop_measure(start); // Permutations + let start = start_measure("permutations", false); let sets = &permutation.sets; if !sets.is_empty() { let blinding_factors = pk.vk.cs.blinding_factors(); @@ -374,7 +379,7 @@ impl Evaluator { // Permutation constraints parallelize(&mut values, |values, start| { - let mut beta_term = extended_omega.pow_vartime(&[start as u64, 0, 0, 0]); + let mut beta_term = extended_omega.pow_vartime([start as u64, 0, 0, 0]); for (i, value) in values.iter_mut().enumerate() { let idx = start + i; let r_next = get_rotation_idx(idx, 1, rot_scale, isize); @@ -442,21 +447,19 @@ impl Evaluator { } }); } + stop_measure(start); // Lookups + let start = start_measure("lookups", false); for (n, lookup) in lookups.iter().enumerate() { // Polynomials required for this lookup. // Calculated here so these only have to be kept in memory for the short time // they are actually needed. - let product_coset = pk.vk.domain.coeff_to_extended(lookup.product_poly.clone()); - let permuted_input_coset = pk - .vk - .domain - .coeff_to_extended(lookup.permuted_input_poly.clone()); - let permuted_table_coset = pk - .vk - .domain - .coeff_to_extended(lookup.permuted_table_poly.clone()); + let product_coset = pk.vk.domain.coeff_to_extended(&lookup.product_poly); + let permuted_input_coset = + pk.vk.domain.coeff_to_extended(&lookup.permuted_input_poly); + let permuted_table_coset = + pk.vk.domain.coeff_to_extended(&lookup.permuted_table_poly); // Lookup constraints parallelize(&mut values, |values, start| { @@ -517,6 +520,7 @@ impl Evaluator { } }); } + stop_measure(start); } values } diff --git a/halo2_proofs/src/plonk/keygen.rs b/halo2_proofs/src/plonk/keygen.rs index 2001f5cc32..c770366d6f 100644 --- a/halo2_proofs/src/plonk/keygen.rs +++ b/halo2_proofs/src/plonk/keygen.rs @@ -100,8 +100,8 @@ impl Assignment for Assembly { _: Column, _: usize, _: Value>, - ) -> Result>, Error> { - Ok(Value::unknown()) + ) -> Value<&'v Assigned> { + Value::unknown() } fn assign_fixed(&mut self, column: Column, row: usize, to: Assigned) { @@ -162,6 +162,14 @@ impl Assignment for Assembly { Value::unknown() } + fn annotate_column(&mut self, _annotation: A, _column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + // Do nothing + } + fn push_namespace(&mut self, _: N) where NR: Into, @@ -286,7 +294,7 @@ where let fixed_cosets = fixed_polys .iter() - .map(|poly| vk.domain.coeff_to_extended(poly.clone())) + .map(|poly| vk.domain.coeff_to_extended(poly)) .collect(); let permutation_pk = assembly @@ -298,7 +306,7 @@ where let mut l0 = vk.domain.empty_lagrange(); l0[0] = C::Scalar::one(); let l0 = vk.domain.lagrange_to_coeff(l0); - let l0 = vk.domain.coeff_to_extended(l0); + let l0 = vk.domain.coeff_to_extended(&l0); // Compute l_blind(X) which evaluates to 1 for each blinding factor row // and 0 otherwise over the domain. @@ -307,14 +315,14 @@ where *evaluation = C::Scalar::one(); } let l_blind = vk.domain.lagrange_to_coeff(l_blind); - let l_blind = vk.domain.coeff_to_extended(l_blind); + let l_blind = vk.domain.coeff_to_extended(&l_blind); // Compute l_last(X) which evaluates to 1 on the first inactive row (just // before the blinding factors) and 0 otherwise over the domain let mut l_last = vk.domain.empty_lagrange(); l_last[params.n() as usize - cs.blinding_factors() - 1] = C::Scalar::one(); let l_last = vk.domain.lagrange_to_coeff(l_last); - let l_last = vk.domain.coeff_to_extended(l_last); + let l_last = vk.domain.coeff_to_extended(&l_last); // Compute l_active_row(X) let one = C::Scalar::one(); diff --git a/halo2_proofs/src/plonk/lookup/prover.rs b/halo2_proofs/src/plonk/lookup/prover.rs index f5d87d061e..43e98e37cc 100644 --- a/halo2_proofs/src/plonk/lookup/prover.rs +++ b/halo2_proofs/src/plonk/lookup/prover.rs @@ -13,11 +13,17 @@ use crate::{ }, transcript::{EncodedChallenge, TranscriptWrite}, }; +#[cfg(feature = "profile")] +use ark_std::{end_timer, start_timer}; use group::{ ff::{BatchInvert, Field}, Curve, }; use rand_core::RngCore; +use rayon::prelude::*; +use rustc_hash::FxHashMap; +use std::collections::HashMap; +use std::hash::Hash; use std::{any::TypeId, convert::TryInto, num::ParseIntError, ops::Index}; use std::{ collections::BTreeMap, @@ -83,6 +89,7 @@ impl Argument { transcript: &mut T, ) -> Result, Error> where + F: Hash, C: CurveAffine, C::Curve: Mul + MulAssign, { @@ -395,6 +402,147 @@ fn permute_expression_pair<'params, C: CurveAffine, P: Params<'params, C>, R: Rn mut rng: R, input_expression: &Polynomial, table_expression: &Polynomial, +) -> Result, Error> +where + C::Scalar: Hash, +{ + let num_threads = rayon::current_num_threads(); + // heuristic on when multi-threading isn't worth it + // for now it seems like multi-threading is often worth it + /*if params.n() < (num_threads as u64) << 10 { + return permute_expression_pair_seq::<_, _, _, ZK>( + pk, + params, + domain, + rng, + input_expression, + table_expression, + ); + }*/ + let usable_rows = params.n() as usize - (pk.vk.cs.blinding_factors() + 1); + + let input_expression = &input_expression[0..usable_rows]; + + // Sort input lookup expression values + #[cfg(feature = "profile")] + let input_time = start_timer!(|| "permute_par input hashmap (cpu par)"); + // count input_expression unique values using a HashMap, using rayon parallel fold+reduce + let capacity = usable_rows / num_threads + 1; + let input_uniques: HashMap = input_expression + .par_iter() + .fold( + || HashMap::with_capacity(capacity), + |mut acc, coeff| { + *acc.entry(*coeff).or_insert(0) += 1; + acc + }, + ) + .reduce_with(|mut m1, m2| { + m2.into_iter().for_each(|(k, v)| { + *m1.entry(k).or_insert(0) += v; + }); + m1 + }) + .unwrap(); + #[cfg(feature = "profile")] + end_timer!(input_time); + + #[cfg(feature = "profile")] + let timer = start_timer!(|| "permute_par input unique ranges (cpu par)"); + let input_unique_ranges = input_uniques + .par_iter() + .fold( + || Vec::with_capacity(capacity), + |mut input_ranges, (&coeff, &count)| { + if input_ranges.is_empty() { + input_ranges.push((coeff, 0..count)); + } else { + let prev_end = input_ranges.last().unwrap().1.end; + input_ranges.push((coeff, prev_end..prev_end + count)); + } + input_ranges + }, + ) + .reduce_with(|r1, mut r2| { + let r1_end = r1.last().unwrap().1.end; + r2.par_iter_mut().for_each(|r2| { + r2.1.start += r1_end; + r2.1.end += r1_end; + }); + [r1, r2].concat() + }) + .unwrap(); + #[cfg(feature = "profile")] + end_timer!(timer); + + #[cfg(feature = "profile")] + let to_vec_time = start_timer!(|| "to_vec"); + let mut sorted_table_coeffs = table_expression[0..usable_rows].to_vec(); + #[cfg(feature = "profile")] + end_timer!(to_vec_time); + #[cfg(feature = "profile")] + let sort_table_time = start_timer!(|| "permute_par sort table"); + sorted_table_coeffs.par_sort(); + #[cfg(feature = "profile")] + end_timer!(sort_table_time); + + #[cfg(feature = "profile")] + let timer = start_timer!(|| "leftover table coeffs (cpu par)"); + let leftover_table_coeffs: Vec = sorted_table_coeffs + .par_iter() + .enumerate() + .filter_map(|(i, coeff)| { + ((i != 0 && coeff == &sorted_table_coeffs[i - 1]) || !input_uniques.contains_key(coeff)) + .then_some(*coeff) + }) + .collect(); + #[cfg(feature = "profile")] + end_timer!(timer); + + let (mut permuted_input_expression, mut permuted_table_coeffs): (Vec<_>, Vec<_>) = + input_unique_ranges + .into_par_iter() + .enumerate() + .flat_map(|(i, (coeff, range))| { + // subtract off the number of rows in table rows that correspond to input uniques + let leftover_range_start = range.start - i; + let leftover_range_end = range.end - i - 1; + [(coeff, coeff)].into_par_iter().chain( + leftover_table_coeffs[leftover_range_start..leftover_range_end] + .par_iter() + .map(move |leftover_table_coeff| (coeff, *leftover_table_coeff)), + ) + }) + .unzip(); + permuted_input_expression.resize_with(params.n() as usize, || C::Scalar::random(&mut rng)); + permuted_table_coeffs.resize_with(params.n() as usize, || C::Scalar::random(&mut rng)); + + Ok(( + domain.lagrange_from_vec(permuted_input_expression), + domain.lagrange_from_vec(permuted_table_coeffs), + )) +} + +/// Given a vector of input values A and a vector of table values S, +/// this method permutes A and S to produce A' and S', such that: +/// - like values in A' are vertically adjacent to each other; and +/// - the first row in a sequence of like values in A' is the row +/// that has the corresponding value in S'. +/// This method returns (A', S') if no errors are encountered. +#[allow(dead_code)] +fn permute_expression_pair_seq< + 'params, + C: CurveAffine, + P: Params<'params, C>, + R: RngCore, + const ZK: bool, +>( + pk: &ProvingKey, + params: &P, + domain: &EvaluationDomain, + mut rng: R, + input_expression: &Polynomial, + table_expression: &Polynomial, ) -> Result, Error> { let blinding_factors = pk.vk.cs.blinding_factors(); let usable_rows = params.n() as usize - (blinding_factors + 1); @@ -403,7 +551,7 @@ fn permute_expression_pair<'params, C: CurveAffine, P: Params<'params, C>, R: Rn permuted_input_expression.truncate(usable_rows); // Sort input lookup expression values - permuted_input_expression.sort(); + permuted_input_expression.par_sort(); // A BTreeMap of each unique element in the table expression and its count let mut leftover_table_map: BTreeMap = table_expression @@ -430,19 +578,20 @@ fn permute_expression_pair<'params, C: CurveAffine, P: Params<'params, C>, R: Rn None } else { // Return error if input_value not found - Some(Err(Error::ConstraintSystemFailure)) + panic!("{:?}", Error::ConstraintSystemFailure); + // Some(Err(Error::ConstraintSystemFailure)) } // If input value is repeated } else { - Some(Ok(row)) + Some(row) } }) - .collect::, _>>()?; + .collect::>(); // Populate permuted table at unfilled rows with leftover table elements for (coeff, count) in leftover_table_map.iter() { for _ in 0..*count { - permuted_table_coeffs[repeated_input_rows.pop().unwrap() as usize] = *coeff; + permuted_table_coeffs[repeated_input_rows.pop().unwrap()] = *coeff; } } assert!(repeated_input_rows.is_empty()); diff --git a/halo2_proofs/src/plonk/permutation.rs b/halo2_proofs/src/plonk/permutation.rs index 99d7a75622..0eba912093 100644 --- a/halo2_proofs/src/plonk/permutation.rs +++ b/halo2_proofs/src/plonk/permutation.rs @@ -1,3 +1,5 @@ +//! Implementation of permutation argument. + use super::circuit::{Any, Column}; use crate::{ arithmetic::CurveAffine, @@ -14,6 +16,8 @@ pub(crate) mod keygen; pub(crate) mod prover; pub(crate) mod verifier; +pub use keygen::Assembly; + use std::io; /// A permutation argument. @@ -72,6 +76,7 @@ impl Argument { } } + /// Returns columns that participate on the permutation argument. pub fn get_columns(&self) -> Vec> { self.columns.clone() } diff --git a/halo2_proofs/src/plonk/permutation/keygen.rs b/halo2_proofs/src/plonk/permutation/keygen.rs index cdb8cc02f9..c15f3130cc 100644 --- a/halo2_proofs/src/plonk/permutation/keygen.rs +++ b/halo2_proofs/src/plonk/permutation/keygen.rs @@ -3,7 +3,7 @@ use group::Curve; use super::{Argument, ProvingKey, VerifyingKey}; use crate::{ - arithmetic::{CurveAffine, FieldExt}, + arithmetic::{parallelize, CurveAffine, FieldExt}, plonk::{Any, Column, Error}, poly::{ commitment::{Blind, CommitmentScheme, Params}, @@ -11,11 +11,16 @@ use crate::{ }, }; -#[derive(Debug)] -pub(crate) struct Assembly { +/// Struct that accumulates all the necessary data in order to construct the permutation argument. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Assembly { + /// Columns that participate on the copy permutation argument. columns: Vec>, - pub(crate) mapping: Vec>, + /// Mapping of the actual copies done. + mapping: Vec>, + /// Some aux data used to swap positions directly when sorting. aux: Vec>, + /// More aux data sizes: Vec>, } @@ -104,49 +109,58 @@ impl Assembly { p: &Argument, ) -> VerifyingKey { // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] - let mut omega_powers = Vec::with_capacity(params.n() as usize); + let mut omega_powers = vec![C::Scalar::zero(); params.n() as usize]; { - let mut cur = C::Scalar::one(); - for _ in 0..params.n() { - omega_powers.push(cur); - cur *= &domain.get_omega(); - } + let omega = domain.get_omega(); + parallelize(&mut omega_powers, |o, start| { + let mut cur = omega.pow_vartime([start as u64]); + for v in o.iter_mut() { + *v = cur; + cur *= ω + } + }) } // Compute [omega_powers * \delta^0, omega_powers * \delta^1, ..., omega_powers * \delta^m] - let mut deltaomega = Vec::with_capacity(p.columns.len()); + let mut deltaomega = vec![omega_powers; p.columns.len()]; { - let mut cur = C::Scalar::one(); - for _ in 0..p.columns.len() { - let mut omega_powers = omega_powers.clone(); - for o in &mut omega_powers { - *o *= &cur; + parallelize(&mut deltaomega, |o, start| { + let mut cur = C::Scalar::DELTA.pow_vartime([start as u64]); + for omega_powers in o.iter_mut() { + for v in omega_powers { + *v *= &cur; + } + cur *= &C::Scalar::DELTA; } + }); + } - deltaomega.push(omega_powers); - - cur *= &C::Scalar::DELTA; - } + // Computes the permutation polynomial based on the permutation + // description in the assembly. + let mut permutations = vec![domain.empty_lagrange(); p.columns.len()]; + { + parallelize(&mut permutations, |o, start| { + for (x, permutation_poly) in o.iter_mut().enumerate() { + let i = start + x; + for (j, p) in permutation_poly.iter_mut().enumerate() { + let (permuted_i, permuted_j) = self.mapping[i][j]; + *p = deltaomega[permuted_i][permuted_j]; + } + } + }); } // Pre-compute commitments for the URS. - let mut commitments = vec![]; - for i in 0..p.columns.len() { - // Computes the permutation polynomial based on the permutation - // description in the assembly. - let mut permutation_poly = domain.empty_lagrange(); - for (j, p) in permutation_poly.iter_mut().enumerate() { - let (permuted_i, permuted_j) = self.mapping[i][j]; - *p = deltaomega[permuted_i][permuted_j]; - } - + let mut commitments = Vec::with_capacity(p.columns.len()); + for permutation in &permutations { // Compute commitment to permutation polynomial commitments.push( params - .commit_lagrange(&permutation_poly, Blind::default()) + .commit_lagrange(permutation, Blind::default()) .to_affine(), ); } + VerifyingKey { commitments } } @@ -157,54 +171,82 @@ impl Assembly { p: &Argument, ) -> ProvingKey { // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] - let mut omega_powers = Vec::with_capacity(params.n() as usize); + let mut omega_powers = vec![C::Scalar::zero(); params.n() as usize]; { - let mut cur = C::Scalar::one(); - for _ in 0..params.n() { - omega_powers.push(cur); - cur *= &domain.get_omega(); - } + let omega = domain.get_omega(); + parallelize(&mut omega_powers, |o, start| { + let mut cur = omega.pow_vartime(&[start as u64]); + for v in o.iter_mut() { + *v = cur; + cur *= ω + } + }) } // Compute [omega_powers * \delta^0, omega_powers * \delta^1, ..., omega_powers * \delta^m] - let mut deltaomega = Vec::with_capacity(p.columns.len()); + let mut deltaomega = vec![omega_powers; p.columns.len()]; { - let mut cur = C::Scalar::one(); - for _ in 0..p.columns.len() { - let mut omega_powers = omega_powers.clone(); - for o in &mut omega_powers { - *o *= &cur; + parallelize(&mut deltaomega, |o, start| { + let mut cur = C::Scalar::DELTA.pow_vartime(&[start as u64]); + for omega_powers in o.iter_mut() { + for v in omega_powers { + *v *= &cur; + } + cur *= &C::Scalar::DELTA; } - - deltaomega.push(omega_powers); - - cur *= &C::Scalar::DELTA; - } + }); } // Compute permutation polynomials, convert to coset form. - let mut permutations = vec![]; - let mut polys = vec![]; - let mut cosets = vec![]; - for i in 0..p.columns.len() { - // Computes the permutation polynomial based on the permutation - // description in the assembly. - let mut permutation_poly = domain.empty_lagrange(); - for (j, p) in permutation_poly.iter_mut().enumerate() { - let (permuted_i, permuted_j) = self.mapping[i][j]; - *p = deltaomega[permuted_i][permuted_j]; - } + let mut permutations = vec![domain.empty_lagrange(); p.columns.len()]; + { + parallelize(&mut permutations, |o, start| { + for (x, permutation_poly) in o.iter_mut().enumerate() { + let i = start + x; + for (j, p) in permutation_poly.iter_mut().enumerate() { + let (permuted_i, permuted_j) = self.mapping[i][j]; + *p = deltaomega[permuted_i][permuted_j]; + } + } + }); + } + + let mut polys = vec![domain.empty_coeff(); p.columns.len()]; + { + parallelize(&mut polys, |o, start| { + for (x, poly) in o.iter_mut().enumerate() { + let i = start + x; + let permutation_poly = permutations[i].clone(); + *poly = domain.lagrange_to_coeff(permutation_poly); + } + }); + } - // Store permutation polynomial and precompute its coset evaluation - permutations.push(permutation_poly.clone()); - let poly = domain.lagrange_to_coeff(permutation_poly); - polys.push(poly.clone()); - cosets.push(domain.coeff_to_extended(poly)); + let mut cosets = vec![domain.empty_extended(); p.columns.len()]; + { + parallelize(&mut cosets, |o, start| { + for (x, coset) in o.iter_mut().enumerate() { + let i = start + x; + let poly = polys[i].clone(); + *coset = domain.coeff_to_extended(&poly); + } + }); } + ProvingKey { permutations, polys, cosets, } } + + /// Returns columns that participate on the permutation argument. + pub fn columns(&self) -> &[Column] { + &self.columns + } + + /// Returns mappings of the copies. + pub fn mapping(&self) -> &[Vec<(usize, usize)>] { + &self.mapping + } } diff --git a/halo2_proofs/src/plonk/permutation/prover.rs b/halo2_proofs/src/plonk/permutation/prover.rs index cddc02348a..ed23a6b4a7 100644 --- a/halo2_proofs/src/plonk/permutation/prover.rs +++ b/halo2_proofs/src/plonk/permutation/prover.rs @@ -3,6 +3,9 @@ use group::{ Curve, }; use rand_core::RngCore; +use rayon::prelude::{ + IndexedParallelIterator, IntoParallelIterator, IntoParallelRefMutIterator, ParallelIterator, +}; use std::iter::{self, ExactSizeIterator}; use super::super::{circuit::Any, ChallengeBeta, ChallengeGamma, ChallengeX}; @@ -126,7 +129,7 @@ impl Argument { Any::Instance => instance, }; parallelize(&mut modified_values, |modified_values, start| { - let mut deltaomega = deltaomega * &omega.pow_vartime(&[start as u64, 0, 0, 0]); + let mut deltaomega = deltaomega * &omega.pow_vartime([start as u64, 0, 0, 0]); for (modified_values, value) in modified_values .iter_mut() .zip(values[column.index()][start..].iter()) @@ -172,7 +175,7 @@ impl Argument { let z = domain.lagrange_to_coeff(z); let permutation_product_poly = z.clone(); - let permutation_product_coset = domain.coeff_to_extended(z.clone()); + let permutation_product_coset = domain.coeff_to_extended(&z); let permutation_product_commitment = permutation_product_commitment_projective.to_affine(); diff --git a/halo2_proofs/src/plonk/permutation/verifier.rs b/halo2_proofs/src/plonk/permutation/verifier.rs index 2e7a707a07..4e6a8c0066 100644 --- a/halo2_proofs/src/plonk/permutation/verifier.rs +++ b/halo2_proofs/src/plonk/permutation/verifier.rs @@ -178,7 +178,7 @@ impl Evaluated { let mut right = set.permutation_product_eval; let mut current_delta = (*beta * &*x) - * &(C::Scalar::DELTA.pow_vartime(&[(chunk_index * chunk_len) as u64])); + * &(C::Scalar::DELTA.pow_vartime([(chunk_index * chunk_len) as u64])); for eval in columns.iter().map(|&column| match column.column_type() { Any::Advice(_) => { advice_evals[vk.cs.get_any_query_index(column, Rotation::cur())] diff --git a/halo2_proofs/src/plonk/prover.rs b/halo2_proofs/src/plonk/prover.rs index efd4305a01..ebc64016bc 100644 --- a/halo2_proofs/src/plonk/prover.rs +++ b/halo2_proofs/src/plonk/prover.rs @@ -1,9 +1,12 @@ +#[cfg(feature = "profile")] +use ark_std::{end_timer, start_timer}; use ff::Field; use group::Curve; use halo2curves::CurveExt; use rand_core::RngCore; use std::collections::BTreeSet; use std::env::var; +use std::hash::Hash; use std::marker::PhantomData; use std::ops::RangeTo; use std::rc::Rc; @@ -20,8 +23,11 @@ use super::{ lookup, permutation, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error, Expression, ProvingKey, }; +use crate::arithmetic::MULTIEXP_TOTAL_TIME; +use crate::plonk::{start_measure, stop_measure}; use crate::poly::batch_invert_assigned_ref; use crate::poly::commitment::ParamsProver; +use crate::poly::FFT_TOTAL_TIME; use crate::transcript::Transcript; use crate::{ arithmetic::{eval_polynomial, CurveAffine, FieldExt}, @@ -59,7 +65,16 @@ pub fn create_proof< instances: &[&[&'a [Scheme::Scalar]]], mut rng: R, mut transcript: &'a mut T, -) -> Result<(), Error> { +) -> Result<(), Error> +where + Scheme::Scalar: Hash, +{ + #[allow(unsafe_code)] + unsafe { + FFT_TOTAL_TIME = 0; + MULTIEXP_TOTAL_TIME = 0; + } + for instance in instances.iter() { if instance.len() != pk.vk.cs.num_instance_columns { return Err(Error::InvalidInstances); @@ -84,7 +99,7 @@ pub fn create_proof< let instance: Vec> = instances .iter() - .map(|instance| -> Result, Error> { + .map(|instance| -> InstanceSingle { let instance_values = instance .iter() .map(|values| { @@ -108,12 +123,12 @@ pub fn create_proof< }) .collect(); - Ok(InstanceSingle { + InstanceSingle { instance_values, instance_polys, - }) + } }) - .collect::, _>>()?; + .collect(); #[derive(Clone)] struct AdviceSingle { @@ -179,6 +194,14 @@ pub fn create_proof< Ok(()) } + fn annotate_column(&mut self, _annotation: A, _column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + // Do nothing + } + fn query_instance(&self, column: Column, row: usize) -> Result, Error> { if !self.usable_rows.contains(&row) { return Err(Error::not_enough_rows_available(self.params.k())); @@ -198,16 +221,14 @@ pub fn create_proof< column: Column, row: usize, to: Value>, - ) -> Result>, Error> { - // TODO: better to assign all at once, deal with phases later - // Ignore assignment of advice column in different phase than current one. - if self.current_phase != column.column_type().phase { - return Ok(Value::unknown()); - } + ) -> Value<&'v Assigned> { + // debug_assert_eq!(self.current_phase, column.column_type().phase); - if !self.usable_rows.contains(&row) { - return Err(Error::not_enough_rows_available(self.params.k())); - } + debug_assert!( + self.usable_rows.contains(&row), + "{:?}", + Error::not_enough_rows_available(self.params.k()) + ); let advice_get_mut = self .advice @@ -227,7 +248,7 @@ pub fn create_proof< .assign() .expect("No Value::unknown() in advice column allowed during create_proof"); let immutable_raw_ptr = advice_get_mut as *const Assigned; - Ok(Value::known(unsafe { &*immutable_raw_ptr })) + Value::known(unsafe { &*immutable_raw_ptr }) } fn assign_fixed(&mut self, _: Column, _: usize, _: Assigned) { @@ -367,6 +388,8 @@ pub fn create_proof< challenge_indices[phase.to_u8() as usize].push(index); } + #[cfg(feature = "profile")] + let phase1_time = start_timer!(|| "Phase 1: Witness assignment and MSM commitments"); let (advice, challenges) = { let mut advice = Vec::with_capacity(instances.len()); let mut challenges = HashMap::::with_capacity(meta.num_challenges); @@ -377,11 +400,13 @@ pub fn create_proof< // WARNING: this will currently not work if `circuits` has more than 1 circuit // because the original API squeezes the challenges for a phase after running all circuits // once in that phase. - assert_eq!( - circuits.len(), - 1, - "New challenge API doesn't work with multiple circuits yet" - ); + if num_phases > 1 { + assert_eq!( + circuits.len(), + 1, + "New challenge API doesn't work with multiple circuits yet" + ); + } for ((circuit, instances), instance_single) in circuits.iter().zip(instances).zip(instance.iter()) { @@ -434,36 +459,47 @@ pub fn create_proof< (advice, challenges) }; + #[cfg(feature = "profile")] + end_timer!(phase1_time); + #[cfg(feature = "profile")] + let phase2_time = start_timer!(|| "Phase 2: Lookup commit permuted"); // Sample theta challenge for keeping lookup columns linearly independent let theta: ChallengeTheta<_> = transcript.squeeze_challenge_scalar(); let lookups: Vec>> = instance .iter() .zip(advice.iter()) - .map(|(instance, advice)| -> Result, Error> { + .map(|(instance, advice)| -> Vec<_> { // Construct and commit to permuted values for each lookup pk.vk .cs .lookups .iter() .map(|lookup| { - lookup.commit_permuted( - pk, - params, - domain, - theta, - &advice.advice_polys, - &pk.fixed_values, - &instance.instance_values, - &challenges, - &mut rng, - transcript, - ) + lookup + .commit_permuted( + pk, + params, + domain, + theta, + &advice.advice_polys, + &pk.fixed_values, + &instance.instance_values, + &challenges, + &mut rng, + transcript, + ) + .unwrap() }) .collect() }) - .collect::, _>>()?; + .collect(); + #[cfg(feature = "profile")] + end_timer!(phase2_time); + + #[cfg(feature = "profile")] + let phase3a_time = start_timer!(|| "Phase 3a: Commit to permutations"); // Sample beta challenge let beta: ChallengeBeta<_> = transcript.squeeze_challenge_scalar(); @@ -476,38 +512,59 @@ pub fn create_proof< .iter() .zip(advice.iter()) .map(|(instance, advice)| { - pk.vk.cs.permutation.commit( - params, - pk, - &pk.permutation, - &advice.advice_polys, - &pk.fixed_values, - &instance.instance_values, - beta, - gamma, - &mut rng, - transcript, - ) + pk.vk + .cs + .permutation + .commit( + params, + pk, + &pk.permutation, + &advice.advice_polys, + &pk.fixed_values, + &instance.instance_values, + beta, + gamma, + &mut rng, + transcript, + ) + .unwrap() }) - .collect::, _>>()?; + .collect::>(); + #[cfg(feature = "profile")] + end_timer!(phase3a_time); + #[cfg(feature = "profile")] + let phase3b_time = start_timer!(|| "Phase 3b: Lookup commit product"); let lookups: Vec>> = lookups .into_iter() - .map(|lookups| -> Result, _> { + .map(|lookups| -> Vec<_> { // Construct and commit to products for each lookup lookups .into_iter() - .map(|lookup| lookup.commit_product(pk, params, beta, gamma, &mut rng, transcript)) - .collect::, _>>() + .map(|lookup| { + lookup + .commit_product(pk, params, beta, gamma, &mut rng, transcript) + .unwrap() + }) + .collect() }) - .collect::, _>>()?; + .collect(); + #[cfg(feature = "profile")] + end_timer!(phase3b_time); + #[cfg(feature = "profile")] + let vanishing_time = start_timer!(|| "Commit to vanishing argument's random poly"); // Commit to the vanishing argument's random polynomial for blinding h(x_3) - let vanishing = vanishing::Argument::commit(params, domain, &mut rng, transcript)?; + let vanishing = vanishing::Argument::commit(params, domain, &mut rng, transcript).unwrap(); // Obtain challenge for keeping all separate gates linearly independent let y: ChallengeY<_> = transcript.squeeze_challenge_scalar(); + #[cfg(feature = "profile")] + end_timer!(vanishing_time); + #[cfg(feature = "profile")] + let fft_time = start_timer!(|| "Calculate advice polys (fft)"); + // Calculate the advice polys let advice: Vec> = advice .into_iter() @@ -526,7 +583,11 @@ pub fn create_proof< }, ) .collect(); + #[cfg(feature = "profile")] + end_timer!(fft_time); + #[cfg(feature = "profile")] + let phase4_time = start_timer!(|| "Phase 4: Evaluate h(X)"); // Evaluate the h(X) polynomial let h_poly = pk.ev.evaluate_h( pk, @@ -546,9 +607,17 @@ pub fn create_proof< &lookups, &permutations, ); + #[cfg(feature = "profile")] + end_timer!(phase4_time); + #[cfg(feature = "profile")] + let timer = start_timer!(|| "Commit to vanishing argument's h(X) commitments"); // Construct the vanishing argument's h(X) commitments let vanishing = vanishing.construct(params, domain, h_poly, &mut rng, transcript)?; + #[cfg(feature = "profile")] + end_timer!(timer); + #[cfg(feature = "profile")] + let eval_time = start_timer!(|| "Commit to vanishing argument's h(X) commitments"); let x: ChallengeX<_> = transcript.squeeze_challenge_scalar(); let xn = x.pow(&[params.n(), 0, 0, 0]); @@ -617,19 +686,21 @@ pub fn create_proof< // Evaluate the permutations, if any, at omega^i x. let permutations: Vec> = permutations .into_iter() - .map(|permutation| -> Result<_, _> { permutation.construct().evaluate(pk, x, transcript) }) - .collect::, _>>()?; + .map(|permutation| permutation.construct().evaluate(pk, x, transcript).unwrap()) + .collect(); // Evaluate the lookups, if any, at omega^i x. let lookups: Vec>> = lookups .into_iter() - .map(|lookups| -> Result, _> { + .map(|lookups| -> Vec<_> { lookups .into_iter() - .map(|p| p.evaluate(pk, x, transcript)) - .collect::, _>>() + .map(|p| p.evaluate(pk, x, transcript).unwrap()) + .collect() }) - .collect::, _>>()?; + .collect(); + #[cfg(feature = "profile")] + end_timer!(eval_time); let instances = instance .iter() @@ -679,8 +750,13 @@ pub fn create_proof< // We query the h(X) polynomial at x .chain(vanishing.open(x)); + #[cfg(feature = "profile")] + let multiopen_time = start_timer!(|| "Phase 5: multiopen"); let prover = P::new(params); - prover + let multiopen_res = prover .create_proof(&mut rng, transcript, instances) - .map_err(|_| Error::ConstraintSystemFailure) + .map_err(|_| Error::ConstraintSystemFailure); + #[cfg(feature = "profile")] + end_timer!(multiopen_time); + multiopen_res } diff --git a/halo2_proofs/src/plonk/vanishing/prover.rs b/halo2_proofs/src/plonk/vanishing/prover.rs index cc52273b59..05c30d78e4 100644 --- a/halo2_proofs/src/plonk/vanishing/prover.rs +++ b/halo2_proofs/src/plonk/vanishing/prover.rs @@ -1,12 +1,13 @@ use std::iter; -use ff::Field; +use ff::{Field, PrimeField}; use group::Curve; -use rand_core::RngCore; +use rand_core::{RngCore, SeedableRng}; +use rayon::{current_num_threads, prelude::*}; use super::Argument; use crate::{ - arithmetic::{eval_polynomial, CurveAffine, FieldExt}, + arithmetic::{eval_polynomial, parallelize, CurveAffine, FieldExt}, plonk::{ChallengeX, ChallengeY, Error}, poly::{ self, @@ -38,19 +39,22 @@ impl Argument { 'params, P: ParamsProver<'params, C>, E: EncodedChallenge, - R: RngCore, + R: RngCore, // + Sync + Clone, T: TranscriptWrite, >( params: &P, domain: &EvaluationDomain, - mut rng: R, + rng: R, transcript: &mut T, ) -> Result, Error> { // Sample a random polynomial of degree n - 1 let mut random_poly = domain.empty_coeff(); - for coeff in random_poly.iter_mut() { - *coeff = C::Scalar::random(&mut rng); - } + parallelize(&mut random_poly, |random_poly, _| { + let mut rng = rand::thread_rng(); + for coeff in random_poly.iter_mut() { + *coeff = C::Scalar::random(&mut rng); + } + }); // Sample a random blinding factor let random_blind = Blind(C::Scalar::random(rng)); diff --git a/halo2_proofs/src/plonk/verifier.rs b/halo2_proofs/src/plonk/verifier.rs index e6a2327e7b..cded3f4997 100644 --- a/halo2_proofs/src/plonk/verifier.rs +++ b/halo2_proofs/src/plonk/verifier.rs @@ -174,7 +174,7 @@ pub fn verify_proof< }) .collect::, _>>()? } else { - let xn = x.pow(&[params.n() as u64, 0, 0, 0]); + let xn = x.pow(&[params.n(), 0, 0, 0]); let (min_rotation, max_rotation) = vk.cs .instance_queries @@ -243,7 +243,7 @@ pub fn verify_proof< // commitments open to the correct values. let vanishing = { // x^n - let xn = x.pow(&[params.n() as u64, 0, 0, 0]); + let xn = x.pow(&[params.n(), 0, 0, 0]); let blinding_factors = vk.cs.blinding_factors(); let l_evals = vk diff --git a/halo2_proofs/src/poly.rs b/halo2_proofs/src/poly.rs index 219afebb77..d7fd0fb3e8 100644 --- a/halo2_proofs/src/poly.rs +++ b/halo2_proofs/src/poly.rs @@ -13,7 +13,7 @@ use halo2curves::FieldExt; use std::fmt::Debug; use std::io; use std::marker::PhantomData; -use std::ops::{Add, Deref, DerefMut, Index, IndexMut, Mul, RangeFrom, RangeFull, Sub}; +use std::ops::{Add, Deref, DerefMut, Index, IndexMut, Mul, Range, RangeFrom, RangeFull, Sub}; /// Generic commitment scheme structures pub mod commitment; @@ -67,7 +67,7 @@ impl Basis for ExtendedLagrangeCoeff {} /// basis. #[derive(Clone, Debug)] pub struct Polynomial { - values: Vec, + pub(crate) values: Vec, _marker: PhantomData, } @@ -85,6 +85,14 @@ impl IndexMut for Polynomial { } } +impl Index> for Polynomial { + type Output = [F]; + + fn index(&self, index: Range) -> &[F] { + self.values.index(index) + } +} + impl Index> for Polynomial { type Output = [F]; @@ -93,6 +101,12 @@ impl Index> for Polynomial { } } +impl IndexMut> for Polynomial { + fn index_mut(&mut self, index: Range) -> &mut [F] { + self.values.index_mut(index) + } +} + impl IndexMut> for Polynomial { fn index_mut(&mut self, index: RangeFrom) -> &mut [F] { self.values.index_mut(index) diff --git a/halo2_proofs/src/poly/domain.rs b/halo2_proofs/src/poly/domain.rs index 54c6b5ed18..260e788900 100644 --- a/halo2_proofs/src/poly/domain.rs +++ b/halo2_proofs/src/poly/domain.rs @@ -2,15 +2,409 @@ //! domain that is of a suitable size for the application. use crate::{ - arithmetic::{best_fft, parallelize, FieldExt, Group}, - plonk::Assigned, + arithmetic::{best_fft, parallelize, parallelize_count, FieldExt, Group}, + multicore, + plonk::{get_duration, get_time, start_measure, stop_measure, Assigned}, }; use super::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation}; use group::ff::{BatchInvert, Field, PrimeField}; -use std::marker::PhantomData; +use std::{env::var, marker::PhantomData}; + +/// TEMP +pub static mut FFT_TOTAL_TIME: usize = 0; + +fn get_fft_mode() -> usize { + var("FFT_MODE") + .unwrap_or_else(|_| "1".to_string()) + .parse() + .expect("Cannot parse FFT_MODE env var as usize") +} + +/// FFTStage +#[derive(Clone, Debug)] +pub struct FFTStage { + radix: usize, + length: usize, +} + +/// FFT stages +pub fn get_stages(size: usize, radixes: Vec) -> Vec { + let mut stages: Vec = vec![]; + + let mut n = size; + + // Use the specified radices + for &radix in &radixes { + n /= radix; + stages.push(FFTStage { radix, length: n }); + } + + // Fill in the rest of the tree if needed + let mut p = 2; + while n > 1 { + while n % p != 0 { + if p == 4 { + p = 2; + } + } + n /= p; + stages.push(FFTStage { + radix: p, + length: n, + }); + } + + /*for i in 0..stages.len() { + println!("Stage {}: {}, {}", i, stages[i].radix, stages[i].length); + }*/ + + stages +} + +/// FFTData +#[derive(Clone, Debug)] +struct FFTData { + n: usize, + + stages: Vec, + + f_twiddles: Vec>, + inv_twiddles: Vec>, + //scratch: Vec, +} + +impl FFTData { + /// Create FFT data + pub fn new(n: usize, omega: F, omega_inv: F) -> Self { + let stages = get_stages(n as usize, vec![]); + let mut f_twiddles = vec![]; + let mut inv_twiddles = vec![]; + let mut scratch = vec![F::zero(); n]; + + // Generate stage twiddles + for inv in 0..2 { + let inverse = inv == 0; + let o = if inverse { omega_inv } else { omega }; + let stage_twiddles = if inverse { + &mut inv_twiddles + } else { + &mut f_twiddles + }; + + let twiddles = &mut scratch; + + // Twiddles + parallelize(twiddles, |twiddles, start| { + let w_m = o; + let mut w = o.pow_vartime(&[start as u64, 0, 0, 0]); + for value in twiddles.iter_mut() { + *value = w; + w *= w_m; + } + }); + + // Re-order twiddles for cache friendliness + let num_stages = stages.len(); + stage_twiddles.resize(num_stages, vec![]); + for l in 0..num_stages { + let radix = stages[l].radix; + let stage_length = stages[l].length; + + let num_twiddles = stage_length * (radix - 1); + stage_twiddles[l].resize(num_twiddles + 1, F::zero()); + + // Set j + stage_twiddles[l][num_twiddles] = twiddles[(twiddles.len() * 3) / 4]; + + let stride = n / (stage_length * radix); + let mut tws = vec![0usize; radix - 1]; + for i in 0..stage_length { + for j in 0..radix - 1 { + stage_twiddles[l][i * (radix - 1) + j] = twiddles[tws[j]]; + tws[j] += (j + 1) * stride; + } + } + } + } + + Self { + n, + stages, + f_twiddles, + inv_twiddles, + //scratch, + } + } +} + +/// Radix 2 butterfly +pub fn butterfly_2(out: &mut [F], twiddles: &[F], stage_length: usize) { + let mut out_offset = 0; + let mut out_offset2 = stage_length; + + let t = out[out_offset2]; + out[out_offset2] = out[out_offset] - t; + out[out_offset] += t; + out_offset2 += 1; + out_offset += 1; + + for twiddle in twiddles[1..stage_length].iter() { + let t = *twiddle * out[out_offset2]; + out[out_offset2] = out[out_offset] - t; + out[out_offset] += t; + out_offset2 += 1; + out_offset += 1; + } +} + +/// Radix 2 butterfly +fn butterfly_2_parallel( + out: &mut [F], + twiddles: &[F], + _stage_length: usize, + num_threads: usize, +) { + let n = out.len(); + let mut chunk = (n as usize) / num_threads; + if chunk < num_threads { + chunk = n as usize; + } + + multicore::scope(|scope| { + let (part_a, part_b) = out.split_at_mut(n / 2); + for (i, (part0, part1)) in part_a + .chunks_mut(chunk) + .zip(part_b.chunks_mut(chunk)) + .enumerate() + { + scope.spawn(move |_| { + let offset = i * chunk; + for k in 0..part0.len() { + let t = twiddles[offset + k] * part1[k]; + part1[k] = part0[k] - t; + part0[k] += t; + } + }); + } + }); +} + +/// Radix 4 butterfly +pub fn butterfly_4(out: &mut [F], twiddles: &[F], stage_length: usize) { + let j = twiddles[twiddles.len() - 1]; + let mut tw = 0; + + /* Case twiddle == one */ + { + let i0 = 0; + let i1 = stage_length; + let i2 = stage_length * 2; + let i3 = stage_length * 3; + + let z0 = out[i0]; + let z1 = out[i1]; + let z2 = out[i2]; + let z3 = out[i3]; + + let t1 = z0 + z2; + let t2 = z1 + z3; + let t3 = z0 - z2; + let t4j = j * (z1 - z3); + + out[i0] = t1 + t2; + out[i1] = t3 - t4j; + out[i2] = t1 - t2; + out[i3] = t3 + t4j; + + tw += 3; + } + + for k in 1..stage_length { + let i0 = k; + let i1 = k + stage_length; + let i2 = k + stage_length * 2; + let i3 = k + stage_length * 3; + + let z0 = out[i0]; + let z1 = out[i1] * twiddles[tw]; + let z2 = out[i2] * twiddles[tw + 1]; + let z3 = out[i3] * twiddles[tw + 2]; + + let t1 = z0 + z2; + let t2 = z1 + z3; + let t3 = z0 - z2; + let t4j = j * (z1 - z3); + + out[i0] = t1 + t2; + out[i1] = t3 - t4j; + out[i2] = t1 - t2; + out[i3] = t3 + t4j; + + tw += 3; + } +} + +/// Radix 4 butterfly +pub fn butterfly_4_parallel( + out: &mut [F], + twiddles: &[F], + _stage_length: usize, + num_threads: usize, +) { + let j = twiddles[twiddles.len() - 1]; + + let n = out.len(); + let mut chunk = (n as usize) / num_threads; + if chunk < num_threads { + chunk = n as usize; + } + multicore::scope(|scope| { + //let mut parts: Vec<&mut [F]> = out.chunks_mut(4).collect(); + //out.chunks_mut(4).map(|c| c.chunks_mut(chunk)).fold(predicate) + let (part_a, part_b) = out.split_at_mut(n / 2); + let (part_aa, part_ab) = part_a.split_at_mut(n / 4); + let (part_ba, part_bb) = part_b.split_at_mut(n / 4); + for (i, (((part0, part1), part2), part3)) in part_aa + .chunks_mut(chunk) + .zip(part_ab.chunks_mut(chunk)) + .zip(part_ba.chunks_mut(chunk)) + .zip(part_bb.chunks_mut(chunk)) + .enumerate() + { + scope.spawn(move |_| { + let offset = i * chunk; + let mut tw = offset * 3; + for k in 0..part1.len() { + let z0 = part0[k]; + let z1 = part1[k] * twiddles[tw]; + let z2 = part2[k] * twiddles[tw + 1]; + let z3 = part3[k] * twiddles[tw + 2]; + + let t1 = z0 + z2; + let t2 = z1 + z3; + let t3 = z0 - z2; + let t4j = j * (z1 - z3); + + part0[k] = t1 + t2; + part1[k] = t3 - t4j; + part2[k] = t1 - t2; + part3[k] = t3 + t4j; + + tw += 3; + } + }); + } + }); +} + +/// Inner recursion +fn recursive_fft_inner( + data_in: &[F], + data_out: &mut [F], + twiddles: &Vec>, + stages: &Vec, + in_offset: usize, + stride: usize, + level: usize, + num_threads: usize, +) { + let radix = stages[level].radix; + let stage_length = stages[level].length; + + if num_threads > 1 { + if stage_length == 1 { + for i in 0..radix { + data_out[i] = data_in[in_offset + i * stride]; + } + } else { + let num_threads_recursive = if num_threads >= radix { + radix + } else { + num_threads + }; + parallelize_count(data_out, num_threads_recursive, |data_out, i| { + let num_threads_in_recursion = if num_threads < radix { + 1 + } else { + (num_threads + i) / radix + }; + recursive_fft_inner( + data_in, + data_out, + twiddles, + stages, + in_offset + i * stride, + stride * radix, + level + 1, + num_threads_in_recursion, + ) + }); + } + match radix { + 2 => butterfly_2_parallel(data_out, &twiddles[level], stage_length, num_threads), + 4 => butterfly_4_parallel(data_out, &twiddles[level], stage_length, num_threads), + _ => unimplemented!("radix unsupported"), + } + } else { + if stage_length == 1 { + for i in 0..radix { + data_out[i] = data_in[in_offset + i * stride]; + } + } else { + for i in 0..radix { + recursive_fft_inner( + data_in, + &mut data_out[i * stage_length..(i + 1) * stage_length], + twiddles, + stages, + in_offset + i * stride, + stride * radix, + level + 1, + num_threads, + ); + } + } + match radix { + 2 => butterfly_2(data_out, &twiddles[level], stage_length), + 4 => butterfly_4(data_out, &twiddles[level], stage_length), + _ => unimplemented!("radix unsupported"), + } + } +} + +fn recursive_fft(data: &FFTData, data_in: &mut Vec, inverse: bool) { + let num_threads = multicore::current_num_threads(); + //let start = start_measure(format!("recursive fft {} ({})", data_in.len(), num_threads), false); + + // TODO: reuse scratch buffer between FFTs + //let start_mem = start_measure(format!("alloc"), false); + let mut scratch = vec![F::zero(); data_in.len()]; + //stop_measure(start_mem); + + recursive_fft_inner( + data_in, + &mut /*data.*/scratch, + if inverse { + &data.inv_twiddles + } else { + &data.f_twiddles + }, + &data.stages, + 0, + 1, + 0, + num_threads, + ); + //let duration = stop_measure(start); + + //let start = start_measure(format!("copy"), false); + // Will simply swap the vector's buffer, no data is actually copied + std::mem::swap(data_in, &mut /*data.*/scratch); + //stop_measure(start); +} /// This structure contains precomputed constants and other details needed for /// performing operations on an evaluation domain of size $2^k$ and an extended @@ -31,6 +425,10 @@ pub struct EvaluationDomain { extended_ifft_divisor: G::Scalar, t_evaluations: Vec, barycentric_weight: G::Scalar, + + // Recursive stuff + fft_data: FFTData, + extended_fft_data: FFTData, } impl EvaluationDomain { @@ -50,6 +448,7 @@ impl EvaluationDomain { while (1 << extended_k) < (n * quotient_poly_degree) { extended_k += 1; } + println!("k: {}, extended_k: {}", k, extended_k); let mut extended_omega = G::Scalar::root_of_unity(); @@ -138,6 +537,12 @@ impl EvaluationDomain { extended_ifft_divisor, t_evaluations, barycentric_weight, + fft_data: FFTData::::new(n as usize, omega, omega_inv), + extended_fft_data: FFTData::::new( + (1 << extended_k) as usize, + extended_omega, + extended_omega_inv, + ), } } @@ -235,11 +640,14 @@ impl EvaluationDomain { /// /// This function will panic if the provided vector is not the correct /// length. - pub fn lagrange_to_coeff(&self, mut a: Polynomial) -> Polynomial { + pub fn lagrange_to_coeff( + &self, + mut a: Polynomial, + ) -> Polynomial { assert_eq!(a.values.len(), 1 << self.k); // Perform inverse FFT to obtain the polynomial in coefficient form - Self::ifft(&mut a.values, self.omega_inv, self.k, self.ifft_divisor); + self.ifft(&mut a.values, self.omega_inv, self.k, self.ifft_divisor); Polynomial { values: a.values, @@ -251,16 +659,19 @@ impl EvaluationDomain { /// evaluation domain, rotating by `rotation` if desired. pub fn coeff_to_extended( &self, - mut a: Polynomial, - ) -> Polynomial { - assert_eq!(a.values.len(), 1 << self.k); + p: &Polynomial, + ) -> Polynomial { + assert_eq!(p.values.len(), 1 << self.k); + + let mut a = Vec::with_capacity(self.extended_len()); + a.extend(&p.values); - self.distribute_powers_zeta(&mut a.values, true); - a.values.resize(self.extended_len(), G::group_zero()); - best_fft(&mut a.values, self.extended_omega, self.extended_k); + self.distribute_powers_zeta(&mut a, true); + a.resize(self.extended_len(), G::Scalar::zero()); + self.fft_inner(&mut a, self.extended_omega, self.extended_k, false); Polynomial { - values: a.values, + values: a, _marker: PhantomData, } } @@ -290,11 +701,14 @@ impl EvaluationDomain { /// This function will panic if the provided vector is not the correct /// length. // TODO/FIXME: caller should be responsible for truncating - pub fn extended_to_coeff(&self, mut a: Polynomial) -> Vec { + pub fn extended_to_coeff( + &self, + mut a: Polynomial, + ) -> Vec { assert_eq!(a.values.len(), self.extended_len()); // Inverse FFT - Self::ifft( + self.ifft( &mut a.values, self.extended_omega_inv, self.extended_k, @@ -344,7 +758,7 @@ impl EvaluationDomain { /// /// `into_coset` should be set to `true` when moving into the coset, /// and `false` when moving out. This toggles the choice of `zeta`. - fn distribute_powers_zeta(&self, a: &mut [G], into_coset: bool) { + fn distribute_powers_zeta(&self, a: &mut [G::Scalar], into_coset: bool) { let coset_powers = if into_coset { [self.g_coset, self.g_coset_inv] } else { @@ -362,8 +776,8 @@ impl EvaluationDomain { }); } - fn ifft(a: &mut [G], omega_inv: G::Scalar, log_n: u32, divisor: G::Scalar) { - best_fft(a, omega_inv, log_n); + fn ifft(&self, a: &mut Vec, omega_inv: G::Scalar, log_n: u32, divisor: G::Scalar) { + self.fft_inner(a, omega_inv, log_n, true); parallelize(a, |a, _| { for a in a { // Finish iFFT @@ -372,6 +786,26 @@ impl EvaluationDomain { }); } + fn fft_inner(&self, a: &mut Vec, omega: G::Scalar, log_n: u32, inverse: bool) { + let start = get_time(); + if get_fft_mode() == 1 { + let fft_data = if a.len() == self.fft_data.n { + &self.fft_data + } else { + &self.extended_fft_data + }; + recursive_fft(fft_data, a, inverse); + } else { + best_fft(a, omega, log_n); + } + let duration = get_duration(start); + + #[allow(unsafe_code)] + unsafe { + FFT_TOTAL_TIME += duration; + } + } + /// Get the size of the domain pub fn k(&self) -> u32 { self.k @@ -408,11 +842,11 @@ impl EvaluationDomain { pub fn rotate_omega(&self, value: G::Scalar, rotation: Rotation) -> G::Scalar { let mut point = value; if rotation.0 >= 0 { - point *= &self.get_omega().pow_vartime(&[rotation.0 as u64]); + point *= &self.get_omega().pow_vartime([rotation.0 as u64]); } else { point *= &self .get_omega_inv() - .pow_vartime(&[(rotation.0 as i64).unsigned_abs()]); + .pow_vartime([(rotation.0 as i64).unsigned_abs()]); } point } @@ -567,3 +1001,47 @@ fn test_l_i() { assert_eq!(eval_polynomial(&l[(8 - i) % 8][..], x), evaluations[7 - i]); } } + +#[test] +fn test_fft() { + use crate::arithmetic::{eval_polynomial, lagrange_interpolate}; + use halo2curves::pasta::pallas::Scalar; + use rand_core::OsRng; + + fn get_degree() -> usize { + var("DEGREE") + .unwrap_or_else(|_| "8".to_string()) + .parse() + .expect("Cannot parse DEGREE env var as usize") + } + let k = get_degree() as u32; + + let mut domain = EvaluationDomain::::new(1, k); + let n = domain.n as usize; + + let input = vec![Scalar::random(OsRng); n]; + /*let mut input = vec![Scalar::zero(); n]; + for i in 0..n { + input[i] = Scalar::random(OsRng); + }*/ + + let num_threads = multicore::current_num_threads(); + + let mut a = input.clone(); + let start = start_measure(format!("best fft {} ({})", a.len(), num_threads), false); + best_fft(&mut a, domain.omega, k); + stop_measure(start); + + let mut b = input.clone(); + let start = start_measure( + format!("recursive fft {} ({})", a.len(), num_threads), + false, + ); + recursive_fft(&mut domain.fft_data, &mut b, false); + stop_measure(start); + + for i in 0..n { + //println!("{}: {} {}", i, a[i], b[i]); + assert_eq!(a[i], b[i]); + } +} diff --git a/halo2_proofs/src/poly/kzg/commitment.rs b/halo2_proofs/src/poly/kzg/commitment.rs index fbc1812e6b..5994327c77 100644 --- a/halo2_proofs/src/poly/kzg/commitment.rs +++ b/halo2_proofs/src/poly/kzg/commitment.rs @@ -71,7 +71,7 @@ impl ParamsKZG { let mut g_projective = vec![E::G1::group_zero(); n as usize]; parallelize(&mut g_projective, |g, start| { let mut current_g: E::G1 = g1.into(); - current_g *= s.pow_vartime(&[start as u64]); + current_g *= s.pow_vartime([start as u64]); for g in g.iter_mut() { *g = current_g; current_g *= s; @@ -93,11 +93,11 @@ impl ParamsKZG { } let n_inv = Option::::from(E::Scalar::from(n).invert()) .expect("inversion should be ok for n = 1<(&self, writer: &mut W) -> io::Result<()> { - Ok(self.write_custom(writer, SerdeFormat::RawBytesUnchecked)) + self.write_custom(writer, SerdeFormat::RawBytesUnchecked); + Ok(()) } /// Reads params from a buffer. diff --git a/halo2_proofs/src/poly/multiopen_test.rs b/halo2_proofs/src/poly/multiopen_test.rs index 1df8edaa03..8dd563b15a 100644 --- a/halo2_proofs/src/poly/multiopen_test.rs +++ b/halo2_proofs/src/poly/multiopen_test.rs @@ -13,8 +13,9 @@ mod test { }; use crate::poly::{Coeff, Polynomial}; use crate::transcript::{ - self, Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, TranscriptRead, - TranscriptReadBuffer, TranscriptWrite, TranscriptWriterBuffer, + self, Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, Keccak256Read, + Keccak256Write, TranscriptRead, TranscriptReadBuffer, TranscriptWrite, + TranscriptWriterBuffer, }; use ff::Field; use group::{Curve, Group}; @@ -59,6 +60,43 @@ mod test { >(verifier_params, &proof[..], true); } + #[test] + fn test_roundtrip_ipa_keccak() { + use crate::poly::ipa::commitment::{IPACommitmentScheme, ParamsIPA}; + use crate::poly::ipa::multiopen::{ProverIPA, VerifierIPA}; + use crate::poly::ipa::strategy::AccumulatorStrategy; + use halo2curves::pasta::{Ep, EqAffine, Fp}; + + const K: u32 = 4; + + let params = ParamsIPA::::new(K); + + let proof = create_proof::< + IPACommitmentScheme, + ProverIPA<_>, + _, + Keccak256Write<_, _, Challenge255<_>>, + >(¶ms); + + let verifier_params = params.verifier_params(); + + verify::< + IPACommitmentScheme, + VerifierIPA<_>, + _, + Keccak256Read<_, _, Challenge255<_>>, + AccumulatorStrategy<_>, + >(verifier_params, &proof[..], false); + + verify::< + IPACommitmentScheme, + VerifierIPA<_>, + _, + Keccak256Read<_, _, Challenge255<_>>, + AccumulatorStrategy<_>, + >(verifier_params, &proof[..], true); + } + #[test] fn test_roundtrip_gwc() { use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; diff --git a/halo2_proofs/src/transcript.rs b/halo2_proofs/src/transcript.rs index 5262f3c1c7..45c08df95b 100644 --- a/halo2_proofs/src/transcript.rs +++ b/halo2_proofs/src/transcript.rs @@ -3,6 +3,7 @@ use blake2b_simd::{Params as Blake2bParams, State as Blake2bState}; use group::ff::PrimeField; +use sha3::{Digest, Keccak256}; use std::convert::TryInto; use halo2curves::{Coordinates, CurveAffine, FieldExt}; @@ -19,6 +20,23 @@ const BLAKE2B_PREFIX_POINT: u8 = 1; /// Prefix to a prover's message containing a scalar const BLAKE2B_PREFIX_SCALAR: u8 = 2; +/// Prefix to a prover's message soliciting a challenge +const KECCAK256_PREFIX_CHALLENGE: u8 = 0; + +/// First prefix to a prover's message soliciting a challenge +/// Not included in the growing state! +const KECCAK256_PREFIX_CHALLENGE_LO: u8 = 10; + +/// Second prefix to a prover's message soliciting a challenge +/// Not included in the growing state! +const KECCAK256_PREFIX_CHALLENGE_HI: u8 = 11; + +/// Prefix to a prover's message containing a curve point +const KECCAK256_PREFIX_POINT: u8 = 1; + +/// Prefix to a prover's message containing a scalar +const KECCAK256_PREFIX_SCALAR: u8 = 2; + /// Generic transcript view (from either the prover or verifier's perspective) pub trait Transcript> { /// Squeeze an encoded verifier challenge from the transcript. @@ -88,6 +106,14 @@ pub struct Blake2bRead> { _marker: PhantomData<(C, E)>, } +/// Keccak256 hash function reader for EVM compatibility +#[derive(Debug, Clone)] +pub struct Keccak256Read> { + state: Keccak256, + reader: R, + _marker: PhantomData<(C, E)>, +} + impl TranscriptReadBuffer> for Blake2bRead> { @@ -104,6 +130,21 @@ impl TranscriptReadBuffer> } } +impl TranscriptReadBuffer> + for Keccak256Read> +{ + /// Initialize a transcript given an input buffer. + fn init(reader: R) -> Self { + let mut state = Keccak256::new(); + state.update(b"Halo2-Transcript"); + Keccak256Read { + state, + reader, + _marker: PhantomData, + } + } +} + impl TranscriptRead> for Blake2bRead> { @@ -133,6 +174,35 @@ impl TranscriptRead> } } +impl TranscriptRead> + for Keccak256Read> +{ + fn read_point(&mut self) -> io::Result { + let mut compressed = C::Repr::default(); + self.reader.read_exact(compressed.as_mut())?; + let point: C = Option::from(C::from_bytes(&compressed)).ok_or_else(|| { + io::Error::new(io::ErrorKind::Other, "invalid point encoding in proof") + })?; + self.common_point(point)?; + + Ok(point) + } + + fn read_scalar(&mut self) -> io::Result { + let mut data = ::Repr::default(); + self.reader.read_exact(data.as_mut())?; + let scalar: C::Scalar = Option::from(C::Scalar::from_repr(data)).ok_or_else(|| { + io::Error::new( + io::ErrorKind::Other, + "invalid field element encoding in proof", + ) + })?; + self.common_scalar(scalar)?; + + Ok(scalar) + } +} + impl Transcript> for Blake2bRead> { @@ -165,6 +235,48 @@ impl Transcript> } } +impl Transcript> + for Keccak256Read> +{ + fn squeeze_challenge(&mut self) -> Challenge255 { + self.state.update(&[KECCAK256_PREFIX_CHALLENGE]); + + let mut state_lo = self.state.clone(); + let mut state_hi = self.state.clone(); + state_lo.update(&[KECCAK256_PREFIX_CHALLENGE_LO]); + state_hi.update(&[KECCAK256_PREFIX_CHALLENGE_HI]); + let result_lo: [u8; 32] = state_lo.finalize().as_slice().try_into().unwrap(); + let result_hi: [u8; 32] = state_hi.finalize().as_slice().try_into().unwrap(); + + let mut t = result_lo.to_vec(); + t.extend_from_slice(&result_hi[..]); + let result: [u8; 64] = t.as_slice().try_into().unwrap(); + + Challenge255::::new(&result) + } + + fn common_point(&mut self, point: C) -> io::Result<()> { + self.state.update(&[KECCAK256_PREFIX_POINT]); + let coords: Coordinates = Option::from(point.coordinates()).ok_or_else(|| { + io::Error::new( + io::ErrorKind::Other, + "cannot write points at infinity to the transcript", + ) + })?; + self.state.update(coords.x().to_repr().as_ref()); + self.state.update(coords.y().to_repr().as_ref()); + + Ok(()) + } + + fn common_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> { + self.state.update(&[KECCAK256_PREFIX_SCALAR]); + self.state.update(scalar.to_repr().as_ref()); + + Ok(()) + } +} + /// We will replace BLAKE2b with an algebraic hash function in a later version. #[derive(Debug, Clone)] pub struct Blake2bWrite> { @@ -173,9 +285,18 @@ pub struct Blake2bWrite> { _marker: PhantomData<(C, E)>, } +/// Keccak256 hash function writer for EVM compatibility +#[derive(Debug, Clone)] +pub struct Keccak256Write> { + state: Keccak256, + writer: W, + _marker: PhantomData<(C, E)>, +} + impl TranscriptWriterBuffer> for Blake2bWrite> { + /// Initialize a transcript given an output buffer. fn init(writer: W) -> Self { Blake2bWrite { state: Blake2bParams::new() @@ -193,6 +314,27 @@ impl TranscriptWriterBuffer> } } +impl TranscriptWriterBuffer> + for Keccak256Write> +{ + /// Initialize a transcript given an output buffer. + fn init(writer: W) -> Self { + let mut state = Keccak256::new(); + state.update(b"Halo2-Transcript"); + Keccak256Write { + state, + writer, + _marker: PhantomData, + } + } + + /// Conclude the interaction and return the output buffer (writer). + fn finalize(self) -> W { + // TODO: handle outstanding scalars? see issue #138 + self.writer + } +} + impl TranscriptWrite> for Blake2bWrite> { @@ -208,6 +350,21 @@ impl TranscriptWrite> } } +impl TranscriptWrite> + for Keccak256Write> +{ + fn write_point(&mut self, point: C) -> io::Result<()> { + self.common_point(point)?; + let compressed = point.to_bytes(); + self.writer.write_all(compressed.as_ref()) + } + fn write_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> { + self.common_scalar(scalar)?; + let data = scalar.to_repr(); + self.writer.write_all(data.as_ref()) + } +} + impl Transcript> for Blake2bWrite> { @@ -240,6 +397,48 @@ impl Transcript> } } +impl Transcript> + for Keccak256Write> +{ + fn squeeze_challenge(&mut self) -> Challenge255 { + self.state.update(&[KECCAK256_PREFIX_CHALLENGE]); + + let mut state_lo = self.state.clone(); + let mut state_hi = self.state.clone(); + state_lo.update(&[KECCAK256_PREFIX_CHALLENGE_LO]); + state_hi.update(&[KECCAK256_PREFIX_CHALLENGE_HI]); + let result_lo: [u8; 32] = state_lo.finalize().as_slice().try_into().unwrap(); + let result_hi: [u8; 32] = state_hi.finalize().as_slice().try_into().unwrap(); + + let mut t = result_lo.to_vec(); + t.extend_from_slice(&result_hi[..]); + let result: [u8; 64] = t.as_slice().try_into().unwrap(); + + Challenge255::::new(&result) + } + + fn common_point(&mut self, point: C) -> io::Result<()> { + self.state.update(&[KECCAK256_PREFIX_POINT]); + let coords: Coordinates = Option::from(point.coordinates()).ok_or_else(|| { + io::Error::new( + io::ErrorKind::Other, + "cannot write points at infinity to the transcript", + ) + })?; + self.state.update(coords.x().to_repr().as_ref()); + self.state.update(coords.y().to_repr().as_ref()); + + Ok(()) + } + + fn common_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> { + self.state.update(&[KECCAK256_PREFIX_SCALAR]); + self.state.update(scalar.to_repr().as_ref()); + + Ok(()) + } +} + /// The scalar representation of a verifier challenge. /// /// The `Type` type can be used to scope the challenge to a specific context, or diff --git a/halo2_proofs/tests/plonk_api.rs b/halo2_proofs/tests/plonk_api.rs index af63b5fb30..511b6c78a9 100644 --- a/halo2_proofs/tests/plonk_api.rs +++ b/halo2_proofs/tests/plonk_api.rs @@ -18,9 +18,11 @@ use halo2_proofs::transcript::{ TranscriptWriterBuffer, }; use rand_core::{OsRng, RngCore}; +use std::hash::Hash; use std::marker::PhantomData; #[test] +#[ignore = "doesn't work because it uses multiple regions"] fn plonk_api() { const K: u32 = 5; @@ -104,50 +106,28 @@ fn plonk_api() { || "raw_multiply", |mut region| { let mut value = None; - let lhs = region.assign_advice( - || "lhs", - self.config.a, - 0, - || { - value = Some(f()); - value.unwrap().map(|v| v.0) - }, - )?; + let lhs = region.assign_advice(self.config.a, 0, { + value = Some(f()); + value.unwrap().map(|v| v.0) + }); region.assign_advice( - || "lhs^4", self.config.d, 0, - || value.unwrap().map(|v| v.0).square().square(), - )?; - let rhs = region.assign_advice( - || "rhs", - self.config.b, - 0, - || value.unwrap().map(|v| v.1), - )?; + value.unwrap().map(|v| v.0).square().square(), + ); + let rhs = region.assign_advice(self.config.b, 0, value.unwrap().map(|v| v.1)); region.assign_advice( - || "rhs^4", self.config.e, 0, - || value.unwrap().map(|v| v.1).square().square(), - )?; - let out = region.assign_advice( - || "out", - self.config.c, - 0, - || value.unwrap().map(|v| v.2), - )?; - - region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::zero()))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::zero()))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?; - region.assign_fixed( - || "a * b", - self.config.sm, - 0, - || Value::known(FF::one()), - )?; - Ok((lhs.cell(), rhs.cell(), out.cell())) + value.unwrap().map(|v| v.1).square().square(), + ); + let out = region.assign_advice(self.config.c, 0, value.unwrap().map(|v| v.2)); + + region.assign_fixed(self.config.sa, 0, FF::zero()); + region.assign_fixed(self.config.sb, 0, FF::zero()); + region.assign_fixed(self.config.sc, 0, FF::one()); + region.assign_fixed(self.config.sm, 0, FF::one()); + Ok((*lhs.cell(), *rhs.cell(), *out.cell())) }, ) } @@ -163,50 +143,28 @@ fn plonk_api() { || "raw_add", |mut region| { let mut value = None; - let lhs = region.assign_advice( - || "lhs", - self.config.a, - 0, - || { - value = Some(f()); - value.unwrap().map(|v| v.0) - }, - )?; + let lhs = region.assign_advice(self.config.a, 0, { + value = Some(f()); + value.unwrap().map(|v| v.0) + }); region.assign_advice( - || "lhs^4", self.config.d, 0, - || value.unwrap().map(|v| v.0).square().square(), - )?; - let rhs = region.assign_advice( - || "rhs", - self.config.b, - 0, - || value.unwrap().map(|v| v.1), - )?; + value.unwrap().map(|v| v.0).square().square(), + ); + let rhs = region.assign_advice(self.config.b, 0, value.unwrap().map(|v| v.1)); region.assign_advice( - || "rhs^4", self.config.e, 0, - || value.unwrap().map(|v| v.1).square().square(), - )?; - let out = region.assign_advice( - || "out", - self.config.c, - 0, - || value.unwrap().map(|v| v.2), - )?; - - region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::one()))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::one()))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?; - region.assign_fixed( - || "a * b", - self.config.sm, - 0, - || Value::known(FF::zero()), - )?; - Ok((lhs.cell(), rhs.cell(), out.cell())) + value.unwrap().map(|v| v.1).square().square(), + ); + let out = region.assign_advice(self.config.c, 0, value.unwrap().map(|v| v.2)); + + region.assign_fixed(self.config.sa, 0, FF::one()); + region.assign_fixed(self.config.sb, 0, FF::one()); + region.assign_fixed(self.config.sc, 0, FF::one()); + region.assign_fixed(self.config.sm, 0, FF::zero()); + Ok((*lhs.cell(), *rhs.cell(), *out.cell())) }, ) } @@ -219,8 +177,9 @@ fn plonk_api() { layouter.assign_region( || "copy", |mut region| { - region.constrain_equal(left, right)?; - region.constrain_equal(left, right) + region.constrain_equal(&left, &right); + region.constrain_equal(&left, &right); + Ok(()) }, ) } @@ -231,15 +190,10 @@ fn plonk_api() { layouter.assign_region( || "public_input", |mut region| { - let value = region.assign_advice(|| "value", self.config.a, 0, &mut f)?; - region.assign_fixed( - || "public", - self.config.sp, - 0, - || Value::known(FF::one()), - )?; + let value = region.assign_advice(self.config.a, 0, f()); + region.assign_fixed(self.config.sp, 0, FF::one()); - Ok(value.cell()) + Ok(*value.cell()) }, ) } @@ -471,7 +425,10 @@ fn plonk_api() { rng: R, params: &'params Scheme::ParamsProver, pk: &ProvingKey, - ) -> Vec { + ) -> Vec + where + Scheme::Scalar: Hash, + { let (a, instance, lookup_table) = common!(Scheme); let circuit: MyCircuit = MyCircuit { @@ -539,7 +496,7 @@ fn plonk_api() { use halo2curves::bn256::Bn256; type Scheme = KZGCommitmentScheme; - bad_keys!(Scheme); + //bad_keys!(Scheme); let params = ParamsKZG::::new(K); let rng = OsRng; @@ -568,7 +525,7 @@ fn plonk_api() { use halo2curves::bn256::Bn256; type Scheme = KZGCommitmentScheme; - bad_keys!(Scheme); + //bad_keys!(Scheme); let params = ParamsKZG::::new(K); let rng = OsRng; @@ -590,130 +547,157 @@ fn plonk_api() { >(verifier_params, pk.get_vk(), &proof[..]); } - fn test_plonk_api_ipa() { - use halo2_proofs::poly::ipa::commitment::{IPACommitmentScheme, ParamsIPA}; - use halo2_proofs::poly::ipa::multiopen::{ProverIPA, VerifierIPA}; - use halo2_proofs::poly::ipa::strategy::AccumulatorStrategy; - use halo2curves::pasta::EqAffine; + /* + fn test_plonk_api_ipa() { + use halo2_proofs::poly::ipa::commitment::{IPACommitmentScheme, ParamsIPA}; + use halo2_proofs::poly::ipa::multiopen::{ProverIPA, VerifierIPA}; + use halo2_proofs::poly::ipa::strategy::AccumulatorStrategy; + use halo2curves::pasta::EqAffine; - type Scheme = IPACommitmentScheme; - bad_keys!(Scheme); + type Scheme = IPACommitmentScheme; + //bad_keys!(Scheme); - let params = ParamsIPA::::new(K); - let rng = OsRng; - - let pk = keygen::>(¶ms); + let params = ParamsIPA::::new(K); + let rng = OsRng; - let proof = create_proof::<_, ProverIPA<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( - rng, ¶ms, &pk, - ); + let pk = keygen::>(¶ms); - let verifier_params = params.verifier_params(); - - verify_proof::< - _, - VerifierIPA<_>, - _, - Blake2bRead<_, _, Challenge255<_>>, - AccumulatorStrategy<_>, - >(verifier_params, pk.get_vk(), &proof[..]); + let proof = create_proof::<_, ProverIPA<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( + rng, ¶ms, &pk, + ); - // Check that the verification key has not changed unexpectedly - { - //panic!("{:#?}", pk.get_vk().pinned()); - assert_eq!( - format!("{:#?}", pk.get_vk().pinned()), - r#####"PinnedVerificationKey { - base_modulus: "0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001", - scalar_modulus: "0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001", - domain: PinnedEvaluationDomain { - k: 5, - extended_k: 7, - omega: 0x0cc3380dc616f2e1daf29ad1560833ed3baea3393eceb7bc8fa36376929b78cc, - }, - cs: PinnedConstraintSystem { - num_fixed_columns: 7, - num_advice_columns: 5, - num_instance_columns: 1, - num_selectors: 0, - gates: [ - Sum( + let verifier_params = params.verifier_params(); + + verify_proof::< + _, + VerifierIPA<_>, + _, + Blake2bRead<_, _, Challenge255<_>>, + AccumulatorStrategy<_>, + >(verifier_params, pk.get_vk(), &proof[..]); + + // Check that the verification key has not changed unexpectedly + { + //panic!("{:#?}", pk.get_vk().pinned()); + assert_eq!( + format!("{:#?}", pk.get_vk().pinned()), + r#####"PinnedVerificationKey { + base_modulus: "0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001", + scalar_modulus: "0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001", + domain: PinnedEvaluationDomain { + k: 5, + extended_k: 7, + omega: 0x0cc3380dc616f2e1daf29ad1560833ed3baea3393eceb7bc8fa36376929b78cc, + }, + cs: PinnedConstraintSystem { + num_fixed_columns: 7, + num_advice_columns: 5, + num_instance_columns: 1, + num_selectors: 0, + gates: [ Sum( Sum( Sum( - Product( - Advice { - query_index: 0, - column_index: 1, - rotation: Rotation( - 0, - ), - }, - Fixed { - query_index: 2, - column_index: 2, - rotation: Rotation( - 0, - ), - }, + Sum( + Product( + Advice { + query_index: 0, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Product( + Advice { + query_index: 1, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), ), Product( - Advice { - query_index: 1, - column_index: 2, - rotation: Rotation( - 0, - ), - }, + Product( + Advice { + query_index: 0, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), Fixed { - query_index: 3, - column_index: 3, + query_index: 5, + column_index: 1, rotation: Rotation( 0, ), }, ), ), - Product( + Negated( Product( Advice { - query_index: 0, - column_index: 1, + query_index: 2, + column_index: 3, rotation: Rotation( 0, ), }, - Advice { - query_index: 1, - column_index: 2, + Fixed { + query_index: 4, + column_index: 4, rotation: Rotation( 0, ), }, ), - Fixed { - query_index: 5, - column_index: 1, - rotation: Rotation( - 0, - ), - }, ), ), - Negated( + Product( + Fixed { + query_index: 1, + column_index: 0, + rotation: Rotation( + 0, + ), + }, Product( Advice { - query_index: 2, - column_index: 3, + query_index: 3, + column_index: 4, rotation: Rotation( - 0, + 1, ), }, - Fixed { + Advice { query_index: 4, - column_index: 4, + column_index: 0, rotation: Rotation( - 0, + -1, ), }, ), @@ -721,307 +705,281 @@ fn plonk_api() { ), Product( Fixed { - query_index: 1, - column_index: 0, + query_index: 6, + column_index: 5, rotation: Rotation( 0, ), }, - Product( - Advice { - query_index: 3, - column_index: 4, - rotation: Rotation( - 1, - ), - }, + Sum( Advice { - query_index: 4, - column_index: 0, + query_index: 0, + column_index: 1, rotation: Rotation( - -1, + 0, ), }, + Negated( + Instance { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), ), ), - ), - Product( - Fixed { - query_index: 6, - column_index: 5, - rotation: Rotation( + ], + advice_queries: [ + ( + Column { + index: 1, + column_type: Advice, + }, + Rotation( 0, ), - }, - Sum( - Advice { - query_index: 0, - column_index: 1, - rotation: Rotation( - 0, - ), + ), + ( + Column { + index: 2, + column_type: Advice, }, - Negated( - Instance { - query_index: 0, - column_index: 0, - rotation: Rotation( - 0, - ), - }, + Rotation( + 0, ), ), - ), - ], - advice_queries: [ - ( - Column { - index: 1, - column_type: Advice, - }, - Rotation( - 0, - ), - ), - ( - Column { - index: 2, - column_type: Advice, - }, - Rotation( - 0, - ), - ), - ( - Column { - index: 3, - column_type: Advice, - }, - Rotation( - 0, + ( + Column { + index: 3, + column_type: Advice, + }, + Rotation( + 0, + ), ), - ), - ( - Column { - index: 4, - column_type: Advice, - }, - Rotation( - 1, + ( + Column { + index: 4, + column_type: Advice, + }, + Rotation( + 1, + ), ), - ), - ( - Column { - index: 0, - column_type: Advice, - }, - Rotation( - -1, + ( + Column { + index: 0, + column_type: Advice, + }, + Rotation( + -1, + ), ), - ), - ( - Column { - index: 0, - column_type: Advice, - }, - Rotation( - 0, + ( + Column { + index: 0, + column_type: Advice, + }, + Rotation( + 0, + ), ), - ), - ( - Column { - index: 4, - column_type: Advice, - }, - Rotation( - 0, + ( + Column { + index: 4, + column_type: Advice, + }, + Rotation( + 0, + ), ), - ), - ], - instance_queries: [ - ( - Column { - index: 0, - column_type: Instance, - }, - Rotation( - 0, + ], + instance_queries: [ + ( + Column { + index: 0, + column_type: Instance, + }, + Rotation( + 0, + ), ), - ), - ], - fixed_queries: [ - ( - Column { - index: 6, - column_type: Fixed, - }, - Rotation( - 0, + ], + fixed_queries: [ + ( + Column { + index: 6, + column_type: Fixed, + }, + Rotation( + 0, + ), ), - ), - ( - Column { - index: 0, - column_type: Fixed, - }, - Rotation( - 0, + ( + Column { + index: 0, + column_type: Fixed, + }, + Rotation( + 0, + ), ), - ), - ( - Column { - index: 2, - column_type: Fixed, - }, - Rotation( - 0, + ( + Column { + index: 2, + column_type: Fixed, + }, + Rotation( + 0, + ), ), - ), - ( - Column { - index: 3, - column_type: Fixed, - }, - Rotation( - 0, + ( + Column { + index: 3, + column_type: Fixed, + }, + Rotation( + 0, + ), ), - ), - ( - Column { - index: 4, - column_type: Fixed, - }, - Rotation( - 0, + ( + Column { + index: 4, + column_type: Fixed, + }, + Rotation( + 0, + ), ), - ), - ( - Column { - index: 1, - column_type: Fixed, - }, - Rotation( - 0, + ( + Column { + index: 1, + column_type: Fixed, + }, + Rotation( + 0, + ), ), - ), - ( - Column { - index: 5, - column_type: Fixed, - }, - Rotation( - 0, + ( + Column { + index: 5, + column_type: Fixed, + }, + Rotation( + 0, + ), ), - ), - ], - permutation: Argument { - columns: [ - Column { - index: 1, - column_type: Advice, - }, - Column { - index: 2, - column_type: Advice, - }, - Column { - index: 3, - column_type: Advice, - }, - Column { - index: 0, - column_type: Fixed, - }, - Column { - index: 0, - column_type: Advice, - }, - Column { - index: 4, - column_type: Advice, - }, - Column { - index: 0, - column_type: Instance, - }, - Column { - index: 1, - column_type: Fixed, - }, - Column { - index: 2, - column_type: Fixed, - }, - Column { - index: 3, - column_type: Fixed, - }, - Column { - index: 4, - column_type: Fixed, - }, - Column { - index: 5, - column_type: Fixed, - }, ], - }, - lookups: [ - Argument { - input_expressions: [ - Advice { - query_index: 0, - column_index: 1, - rotation: Rotation( - 0, - ), + permutation: Argument { + columns: [ + Column { + index: 1, + column_type: Advice, }, - ], - table_expressions: [ - Fixed { - query_index: 0, - column_index: 6, - rotation: Rotation( - 0, - ), + Column { + index: 2, + column_type: Advice, + }, + Column { + index: 3, + column_type: Advice, + }, + Column { + index: 0, + column_type: Fixed, + }, + Column { + index: 0, + column_type: Advice, + }, + Column { + index: 4, + column_type: Advice, + }, + Column { + index: 0, + column_type: Instance, + }, + Column { + index: 1, + column_type: Fixed, + }, + Column { + index: 2, + column_type: Fixed, + }, + Column { + index: 3, + column_type: Fixed, + }, + Column { + index: 4, + column_type: Fixed, + }, + Column { + index: 5, + column_type: Fixed, }, ], }, + lookups: [ + Argument { + input_expressions: [ + Advice { + query_index: 0, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ], + table_expressions: [ + Fixed { + query_index: 0, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ], + }, + ], + constants: [], + minimum_degree: None, + }, + fixed_commitments: [ + (0x2bbc94ef7b22aebef24f9a4b0cc1831882548b605171366017d45c3e6fd92075, 0x082b801a6e176239943bfb759fb02138f47a5c8cc4aa7fa0af559fde4e3abd97), + (0x2bf5082b105b2156ed0e9c5b8e42bf2a240b058f74a464d080e9585274dd1e84, 0x222ad83cee7777e7a160585e212140e5e770dd8d1df788d869b5ee483a5864fb), + (0x374a656456a0aae7429b23336f825752b575dd5a44290ff614946ee59d6a20c0, 0x054491e187e6e3460e7601fb54ae10836d34d420026f96316f0c5c62f86db9b8), + (0x374a656456a0aae7429b23336f825752b575dd5a44290ff614946ee59d6a20c0, 0x054491e187e6e3460e7601fb54ae10836d34d420026f96316f0c5c62f86db9b8), + (0x02e62cd68370b13711139a08cbcdd889e800a272b9ea10acc90880fff9d89199, 0x1a96c468cb0ce77065d3a58f1e55fea9b72d15e44c01bba1e110bd0cbc6e9bc6), + (0x224ef42758215157d3ee48fb8d769da5bddd35e5929a90a4a89736f5c4b5ae9b, 0x11bc3a1e08eb320cde764f1492ecef956d71e996e2165f7a9a30ad2febb511c1), + (0x2d5415bf917fcac32bfb705f8ca35cb12d9bad52aa33ccca747350f9235d3a18, 0x2b2921f815fad504052512743963ef20ed5b401d20627793b006413e73fe4dd4), ], - constants: [], - minimum_degree: None, - }, - fixed_commitments: [ - (0x2bbc94ef7b22aebef24f9a4b0cc1831882548b605171366017d45c3e6fd92075, 0x082b801a6e176239943bfb759fb02138f47a5c8cc4aa7fa0af559fde4e3abd97), - (0x2bf5082b105b2156ed0e9c5b8e42bf2a240b058f74a464d080e9585274dd1e84, 0x222ad83cee7777e7a160585e212140e5e770dd8d1df788d869b5ee483a5864fb), - (0x374a656456a0aae7429b23336f825752b575dd5a44290ff614946ee59d6a20c0, 0x054491e187e6e3460e7601fb54ae10836d34d420026f96316f0c5c62f86db9b8), - (0x374a656456a0aae7429b23336f825752b575dd5a44290ff614946ee59d6a20c0, 0x054491e187e6e3460e7601fb54ae10836d34d420026f96316f0c5c62f86db9b8), - (0x02e62cd68370b13711139a08cbcdd889e800a272b9ea10acc90880fff9d89199, 0x1a96c468cb0ce77065d3a58f1e55fea9b72d15e44c01bba1e110bd0cbc6e9bc6), - (0x224ef42758215157d3ee48fb8d769da5bddd35e5929a90a4a89736f5c4b5ae9b, 0x11bc3a1e08eb320cde764f1492ecef956d71e996e2165f7a9a30ad2febb511c1), - (0x2d5415bf917fcac32bfb705f8ca35cb12d9bad52aa33ccca747350f9235d3a18, 0x2b2921f815fad504052512743963ef20ed5b401d20627793b006413e73fe4dd4), - ], - permutation: VerifyingKey { - commitments: [ - (0x1347b4b385837977a96b87f199c6a9a81520015539d1e8fa79429bb4ca229a00, 0x2168e404cabef513654d6ff516cde73f0ba87e3dc84e4b940ed675b5f66f3884), - (0x0e6d69cd2455ec43be640f6397ed65c9e51b1d8c0fd2216339314ff37ade122a, 0x222ed6dc8cfc9ea26dcc10b9d4add791ada60f2b5a63ee1e4635f88aa0c96654), - (0x13c447846f48c41a5e0675ccf88ebc0cdef2c96c51446d037acb866d24255785, 0x1f0b5414fc5e8219dbfab996eed6129d831488b2386a8b1a63663938903bd63a), - (0x1aae6470aa662b8fda003894ddef5fedd03af318b3231683039d2fac9cab05b9, 0x08832d91ae69e99cd07d096c7a4a284a69e6a16227cbb07932a0cdc56914f3a6), - (0x0850521b0f8ac7dd0550fe3e25c840837076e9635067ed623b81d5cbac5944d9, 0x0c25d65d1038d0a92c72e5fccd96c1caf07801c3c8233290bb292e0c38c256fa), - (0x12febcf696badd970750eabf75dd3ced4c2f54f93519bcee23849025177d2014, 0x0a05ab3cd42c9fbcc1bbfcf9269951640cc9920761c87cf8e211ba73c8d9f90f), - (0x053904bdde8cfead3b517bb4f6ded3e699f8b94ca6156a9dd2f92a2a05a7ec5a, 0x16753ff97c0d82ff586bb7a07bf7f27a92df90b3617fa5e75d4f55c3b0ef8711), - (0x3804548f6816452747a5b542fa5656353fc989db40d69e9e27d6f973b5deebb0, 0x389a44d5037866dd83993af75831a5f90a18ad5244255aa5bd2c922cc5853055), - (0x003a9f9ca71c7c0b832c802220915f6fc8d840162bdde6b0ea05d25fb95559e3, 0x091247ca19d6b73887cd7f68908cbf0db0b47459b7c82276bbdb8a1c937e2438), - (0x3eaa38689d9e391c8a8fafab9568f20c45816321d38f309d4cc37f4b1601af72, 0x247f8270a462ea88450221a56aa6b55d2bc352b80b03501e99ea983251ceea13), - (0x394437571f9de32dccdc546fd4737772d8d92593c85438aa3473243997d5acc8, 0x14924ec6e3174f1fab7f0ce7070c22f04bbd0a0ecebdfc5c94be857f25493e95), - (0x3d907e0591343bd285c2c846f3e871a6ac70d80ec29e9500b8cb57f544e60202, 0x1034e48df35830244cabea076be8a16d67d7896e27c6ac22b285d017105da9c3), - ], - }, -}"##### - ); - } - } + permutation: VerifyingKey { + commitments: [ + (0x1347b4b385837977a96b87f199c6a9a81520015539d1e8fa79429bb4ca229a00, 0x2168e404cabef513654d6ff516cde73f0ba87e3dc84e4b940ed675b5f66f3884), + (0x0e6d69cd2455ec43be640f6397ed65c9e51b1d8c0fd2216339314ff37ade122a, 0x222ed6dc8cfc9ea26dcc10b9d4add791ada60f2b5a63ee1e4635f88aa0c96654), + (0x13c447846f48c41a5e0675ccf88ebc0cdef2c96c51446d037acb866d24255785, 0x1f0b5414fc5e8219dbfab996eed6129d831488b2386a8b1a63663938903bd63a), + (0x1aae6470aa662b8fda003894ddef5fedd03af318b3231683039d2fac9cab05b9, 0x08832d91ae69e99cd07d096c7a4a284a69e6a16227cbb07932a0cdc56914f3a6), + (0x0850521b0f8ac7dd0550fe3e25c840837076e9635067ed623b81d5cbac5944d9, 0x0c25d65d1038d0a92c72e5fccd96c1caf07801c3c8233290bb292e0c38c256fa), + (0x12febcf696badd970750eabf75dd3ced4c2f54f93519bcee23849025177d2014, 0x0a05ab3cd42c9fbcc1bbfcf9269951640cc9920761c87cf8e211ba73c8d9f90f), + (0x053904bdde8cfead3b517bb4f6ded3e699f8b94ca6156a9dd2f92a2a05a7ec5a, 0x16753ff97c0d82ff586bb7a07bf7f27a92df90b3617fa5e75d4f55c3b0ef8711), + (0x3804548f6816452747a5b542fa5656353fc989db40d69e9e27d6f973b5deebb0, 0x389a44d5037866dd83993af75831a5f90a18ad5244255aa5bd2c922cc5853055), + (0x003a9f9ca71c7c0b832c802220915f6fc8d840162bdde6b0ea05d25fb95559e3, 0x091247ca19d6b73887cd7f68908cbf0db0b47459b7c82276bbdb8a1c937e2438), + (0x3eaa38689d9e391c8a8fafab9568f20c45816321d38f309d4cc37f4b1601af72, 0x247f8270a462ea88450221a56aa6b55d2bc352b80b03501e99ea983251ceea13), + (0x394437571f9de32dccdc546fd4737772d8d92593c85438aa3473243997d5acc8, 0x14924ec6e3174f1fab7f0ce7070c22f04bbd0a0ecebdfc5c94be857f25493e95), + (0x3d907e0591343bd285c2c846f3e871a6ac70d80ec29e9500b8cb57f544e60202, 0x1034e48df35830244cabea076be8a16d67d7896e27c6ac22b285d017105da9c3), + ], + }, + }"##### + ); + } + }*/ - test_plonk_api_ipa(); + //test_plonk_api_ipa(); test_plonk_api_gwc(); test_plonk_api_shplonk(); } diff --git a/primitives/poseidon/src/grain.rs b/primitives/poseidon/src/grain.rs index 0a5b1fd701..569150aeba 100644 --- a/primitives/poseidon/src/grain.rs +++ b/primitives/poseidon/src/grain.rs @@ -46,7 +46,7 @@ impl Grain { } assert_eq!(grain.bit_sequence.len(), 80); - let number_of_rounds = r_p as usize + r_f as usize; + let number_of_rounds = r_p + r_f; let constants = (0..number_of_rounds) .map(|_| { let mut round_constants = [F::zero(); T]; @@ -154,7 +154,7 @@ impl Iterator for Grain>(vec: &mut Vec, n: usize, from: T) { - let val = from.into() as u128; + let val = from.into(); for i in (0..n).rev() { vec.push((val >> i) & 1 != 0); } diff --git a/primitives/poseidon/src/spec.rs b/primitives/poseidon/src/spec.rs index 71992d334e..6c9a9382ce 100644 --- a/primitives/poseidon/src/spec.rs +++ b/primitives/poseidon/src/spec.rs @@ -75,7 +75,7 @@ pub struct Spec { impl Spec { /// Number of full rounds pub fn r_f(&self) -> usize { - self.r_f.clone() + self.r_f } /// Set of MDS Matrices used in permutation line pub fn mds_matrices(&self) -> &MDSMatrices { @@ -328,7 +328,7 @@ impl Spec { // Calculate optimized constants for first half of the full rounds let mut constants_start: Vec<[F; T]> = vec![[F::zero(); T]; r_f_half]; - constants_start[0] = constants[0].clone(); + constants_start[0] = constants[0]; for (optimized, constants) in constants_start .iter_mut() .skip(1) @@ -338,7 +338,7 @@ impl Spec { } // Calculate constants for partial rounds - let mut acc = constants[r_f_half + r_p].clone(); + let mut acc = constants[r_f_half + r_p]; let mut constants_partial = vec![F::zero(); r_p]; for (optimized, constants) in constants_partial .iter_mut() @@ -352,7 +352,7 @@ impl Spec { for ((acc, tmp), constant) in acc .iter_mut() .zip(tmp.into_iter()) - .zip(constants.into_iter()) + .zip(constants.iter()) { *acc = tmp + constant } diff --git a/rust-toolchain b/rust-toolchain index 51ab475956..d0217cd34a 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2022-10-28 \ No newline at end of file +nightly-2022-10-28