diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6cd03bd..9f956c61 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 00000000..df387bc3 --- /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.4 + + 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/halo2_gadgets/Cargo.toml b/halo2_gadgets/Cargo.toml index 35a24328..c4ebf6fa 100644 --- a/halo2_gadgets/Cargo.toml +++ b/halo2_gadgets/Cargo.toml @@ -24,11 +24,11 @@ rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] [dependencies] arrayvec = "0.7.0" bitvec = "1" -ff = "0.12" -group = "0.12" +ff = { version = "0.13", features = ["bits"] } +group = "0.13" halo2_proofs = { version = "0.2", path = "../halo2_proofs" } lazy_static = "1" -halo2curves = { git = 'https://github.com/privacy-scaling-explorations/halo2curves', tag = 'v0.2.0' } +halo2curves = { git = 'https://github.com/privacy-scaling-explorations/halo2curves', tag = "0.3.2" } proptest = { version = "1.0.0", optional = true } rand = "0.8" subtle = "2.3" @@ -49,6 +49,7 @@ bench = false [features] dev-graph = ["halo2_proofs/dev-graph", "plotters"] +circuit-params = ["halo2_proofs/circuit-params"] test-dependencies = ["proptest"] unstable = [] diff --git a/halo2_gadgets/benches/poseidon.rs b/halo2_gadgets/benches/poseidon.rs index 74454612..03e595fe 100644 --- a/halo2_gadgets/benches/poseidon.rs +++ b/halo2_gadgets/benches/poseidon.rs @@ -3,14 +3,25 @@ use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, plonk::{ create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, - ConstraintSystem, Error, + ConstraintSystem, Error, Instance, + }, + poly::{ + commitment::ParamsProver, + ipa::{ + commitment::{IPACommitmentScheme, ParamsIPA}, + multiopen::ProverIPA, + strategy::SingleStrategy, + }, + VerificationStrategy, + }, + transcript::{ + Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, }, - transcript::{Blake2bRead, Blake2bWrite, Challenge255}, }; use halo2curves::pasta::{pallas, vesta, EqAffine, Fp}; use halo2_gadgets::poseidon::{ - primitives::{self as poseidon, ConstantLength, Spec}, + primitives::{self as poseidon, generate_constants, ConstantLength, Mds, Spec}, Hash, Pow5Chip, Pow5Config, }; use std::convert::TryInto; @@ -19,34 +30,19 @@ use std::marker::PhantomData; use criterion::{criterion_group, criterion_main, Criterion}; use rand::rngs::OsRng; -use halo2_proofs::{ - poly::{ - commitment::ParamsProver, - ipa::{ - commitment::{IPACommitmentScheme, ParamsIPA}, - multiopen::ProverIPA, - strategy::SingleStrategy, - }, - VerificationStrategy, - }, - transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}, -}; - #[derive(Clone, Copy)] struct HashCircuit where S: Spec + Clone + Copy, { message: Value<[Fp; L]>, - // For the purpose of this test, witness the result. - // TODO: Move this into an instance column. - output: Value, _spec: PhantomData, } #[derive(Debug, Clone)] struct MyConfig { input: [Column; L], + expected: Column, poseidon_config: Pow5Config, } @@ -57,17 +53,20 @@ where { type Config = MyConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self { message: Value::unknown(), - output: Value::unknown(), _spec: PhantomData, } } fn configure(meta: &mut ConstraintSystem) -> Self::Config { let state = (0..WIDTH).map(|_| meta.advice_column()).collect::>(); + let expected = meta.instance_column(); + meta.enable_equality(expected); let partial_sbox = meta.advice_column(); let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); @@ -77,6 +76,7 @@ where Self::Config { input: state[..RATE].try_into().unwrap(), + expected, poseidon_config: Pow5Chip::configure::( meta, state.try_into().unwrap(), @@ -118,39 +118,14 @@ where )?; let output = hasher.hash(layouter.namespace(|| "hash"), message)?; - layouter.assign_region( - || "constrain output", - |mut region| { - let expected_var = - region.assign_advice(|| "load output", config.input[0], 0, || self.output)?; - region.constrain_equal(output.cell(), expected_var.cell()) - }, - ) + layouter.constrain_instance(output.cell(), config.expected, 0) } } #[derive(Debug, Clone, Copy)] struct MySpec; -impl Spec for MySpec<3, 2> { - fn full_rounds() -> usize { - 8 - } - - fn partial_rounds() -> usize { - 56 - } - - fn sbox(val: Fp) -> Fp { - val.pow_vartime(&[5]) - } - - fn secure_mds() -> usize { - 0 - } -} - -impl Spec for MySpec<9, 8> { +impl Spec for MySpec { fn full_rounds() -> usize { 8 } @@ -166,27 +141,13 @@ impl Spec for MySpec<9, 8> { fn secure_mds() -> usize { 0 } -} -impl Spec for MySpec<12, 11> { - fn full_rounds() -> usize { - 8 - } - - fn partial_rounds() -> usize { - 56 - } - - fn sbox(val: Fp) -> Fp { - val.pow_vartime(&[5]) - } - - fn secure_mds() -> usize { - 0 + fn constants() -> (Vec<[Fp; WIDTH]>, Mds, Mds) { + generate_constants::<_, Self, WIDTH, RATE>() } } -const K: u32 = 6; +const K: u32 = 7; fn bench_poseidon( name: &str, @@ -199,7 +160,6 @@ fn bench_poseidon( let empty_circuit = HashCircuit:: { message: Value::unknown(), - output: Value::unknown(), _spec: PhantomData, }; @@ -211,7 +171,7 @@ fn bench_poseidon( let verifier_name = name.to_string() + "-verifier"; let mut rng = OsRng; - let message = (0..L) + let message: [Fp; L] = (0..L) .map(|_| pallas::Base::random(rng)) .collect::>() .try_into() @@ -220,19 +180,18 @@ fn bench_poseidon( let circuit = HashCircuit:: { message: Value::known(message), - output: Value::known(output), _spec: PhantomData, }; c.bench_function(&prover_name, |b| { + // Create a proof + let mut transcript = Blake2bWrite::<_, EqAffine, Challenge255<_>>::init(vec![]); b.iter(|| { - // Create a proof - let mut transcript = Blake2bWrite::<_, EqAffine, Challenge255<_>>::init(vec![]); create_proof::, ProverIPA<_>, _, _, _, _>( ¶ms, &pk, &[circuit], - &[&[]], + &[&[&[output]]], &mut rng, &mut transcript, ) @@ -246,7 +205,7 @@ fn bench_poseidon( ¶ms, &pk, &[circuit], - &[&[]], + &[&[&[output]]], &mut rng, &mut transcript, ) @@ -257,7 +216,14 @@ fn bench_poseidon( b.iter(|| { let strategy = SingleStrategy::new(¶ms); let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - assert!(verify_proof(¶ms, pk.get_vk(), strategy, &[&[]], &mut transcript).is_ok()); + assert!(verify_proof( + ¶ms, + pk.get_vk(), + strategy, + &[&[&[output]]], + &mut transcript + ) + .is_ok()); }); }); } diff --git a/halo2_gadgets/benches/sha256.rs b/halo2_gadgets/benches/sha256.rs index 670956cb..0e14d619 100644 --- a/halo2_gadgets/benches/sha256.rs +++ b/halo2_gadgets/benches/sha256.rs @@ -37,6 +37,8 @@ fn bench(name: &str, k: u32, c: &mut Criterion) { impl Circuit for MyCircuit { type Config = Table16Config; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self::default() diff --git a/halo2_gadgets/src/ecc.rs b/halo2_gadgets/src/ecc.rs index 08f34b15..05ea9990 100644 --- a/halo2_gadgets/src/ecc.rs +++ b/halo2_gadgets/src/ecc.rs @@ -731,6 +731,8 @@ pub(crate) mod tests { impl Circuit for MyCircuit { type Config = EccConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { MyCircuit { test_errors: false } diff --git a/halo2_gadgets/src/ecc/chip/add.rs b/halo2_gadgets/src/ecc/chip/add.rs index 9f24d0d7..11661d51 100644 --- a/halo2_gadgets/src/ecc/chip/add.rs +++ b/halo2_gadgets/src/ecc/chip/add.rs @@ -1,10 +1,11 @@ use super::EccPoint; +use ff::PrimeField; use halo2_proofs::{ circuit::Region, plonk::{Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, poly::Rotation, }; -use halo2curves::{pasta::pallas, FieldExt}; +use halo2curves::pasta::pallas; use std::collections::HashSet; #[derive(Clone, Copy, Debug, Eq, PartialEq)] diff --git a/halo2_gadgets/src/ecc/chip/constants.rs b/halo2_gadgets/src/ecc/chip/constants.rs index cecbbae9..62961654 100644 --- a/halo2_gadgets/src/ecc/chip/constants.rs +++ b/halo2_gadgets/src/ecc/chip/constants.rs @@ -6,7 +6,7 @@ use group::{ Curve, }; use halo2_proofs::arithmetic::lagrange_interpolate; -use halo2curves::{pasta::pallas, CurveAffine, FieldExt}; +use halo2curves::{pasta::pallas, CurveAffine}; /// Window size for fixed-base scalar multiplication pub const FIXED_BASE_WINDOW_SIZE: usize = 3; @@ -61,7 +61,7 @@ fn compute_window_table(base: C, num_windows: usize) -> Vec<[C; // Generate window table entries for the last window, w = `num_windows - 1`. // For the last window, we compute [k * (2^3)^w - sum]B, where sum is defined // as sum = \sum_{j = 0}^{`num_windows - 2`} 2^{3j+1} - let sum = (0..(num_windows - 1)).fold(C::Scalar::zero(), |acc, j| { + let sum = (0..(num_windows - 1)).fold(C::Scalar::ZERO, |acc, j| { acc + C::Scalar::from(2).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * j as u64 + 1, 0, 0, 0]) }); window_table.push( @@ -181,7 +181,7 @@ pub fn test_lagrange_coeffs(base: C, num_windows: usize) { .rev() .cloned() .reduce(|acc, coeff| acc * x + coeff) - .unwrap_or_else(C::Base::zero) + .unwrap_or(C::Base::ZERO) } let lagrange_coeffs = compute_lagrange_coeffs(base, num_windows); @@ -213,7 +213,7 @@ pub fn test_lagrange_coeffs(base: C, num_windows: usize) { // Compute the actual x-coordinate of the multiple [k * (8^84) - offset]B, // where offset = \sum_{j = 0}^{83} 2^{3j+1} - let offset = (0..(num_windows - 1)).fold(C::Scalar::zero(), |acc, w| { + let offset = (0..(num_windows - 1)).fold(C::Scalar::ZERO, |acc, w| { acc + C::Scalar::from(2).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, 0, 0, 0]) }); let scalar = C::Scalar::from(bits as u64) @@ -229,8 +229,9 @@ pub fn test_lagrange_coeffs(base: C, num_windows: usize) { #[cfg(test)] mod tests { + use ff::FromUniformBytes; use group::{ff::Field, Curve, Group}; - use halo2curves::{pasta::pallas, CurveAffine, FieldExt}; + use halo2curves::{pasta::pallas, CurveAffine}; use proptest::prelude::*; use super::{compute_window_table, find_zs_and_us, test_lagrange_coeffs, H, NUM_WINDOWS}; @@ -241,7 +242,7 @@ mod tests { // Instead of rejecting out-of-range bytes, let's reduce them. let mut buf = [0; 64]; buf[..32].copy_from_slice(&bytes); - let scalar = pallas::Scalar::from_bytes_wide(&buf); + let scalar = pallas::Scalar::from_uniform_bytes(&buf); pallas::Point::generator() * scalar } } diff --git a/halo2_gadgets/src/ecc/chip/mul.rs b/halo2_gadgets/src/ecc/chip/mul.rs index 26c705a1..7896bf58 100644 --- a/halo2_gadgets/src/ecc/chip/mul.rs +++ b/halo2_gadgets/src/ecc/chip/mul.rs @@ -8,16 +8,15 @@ use std::{ ops::{Deref, Range}, }; -use ff::PrimeField; use halo2_proofs::{ - arithmetic::FieldExt, + arithmetic::Field, circuit::{AssignedCell, Layouter, Region, Value}, plonk::{Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Selector}, poly::Rotation, }; -use uint::construct_uint; - +use halo2curves::group::ff::PrimeField; use halo2curves::pasta::pallas; +use uint::construct_uint; mod complete; pub(super) mod incomplete; @@ -389,8 +388,8 @@ impl Config { #[derive(Clone, Debug)] // `x`-coordinate of the accumulator. -struct X(AssignedCell, F>); -impl Deref for X { +struct X(AssignedCell, F>); +impl Deref for X { type Target = AssignedCell, F>; fn deref(&self) -> &Self::Target { @@ -400,8 +399,8 @@ impl Deref for X { #[derive(Clone, Debug)] // `y`-coordinate of the accumulator. -struct Y(AssignedCell, F>); -impl Deref for Y { +struct Y(AssignedCell, F>); +impl Deref for Y { type Target = AssignedCell, F>; fn deref(&self) -> &Self::Target { @@ -411,8 +410,8 @@ impl Deref for Y { #[derive(Clone, Debug)] // Cumulative sum `z` used to decompose the scalar. -struct Z(AssignedCell); -impl Deref for Z { +struct Z(AssignedCell); +impl Deref for Z { type Target = AssignedCell; fn deref(&self) -> &Self::Target { diff --git a/halo2_gadgets/src/ecc/chip/mul/incomplete.rs b/halo2_gadgets/src/ecc/chip/mul/incomplete.rs index bfe51c7e..9b1d7494 100644 --- a/halo2_gadgets/src/ecc/chip/mul/incomplete.rs +++ b/halo2_gadgets/src/ecc/chip/mul/incomplete.rs @@ -1,6 +1,7 @@ use super::super::NonIdentityEccPoint; use super::{X, Y, Z}; use crate::utilities::bool_check; +use ff::PrimeField; use halo2_proofs::{ circuit::{Region, Value}, plonk::{ @@ -8,7 +9,7 @@ use halo2_proofs::{ }, poly::Rotation, }; -use halo2curves::{pasta::pallas, FieldExt}; +use halo2curves::pasta::pallas; /// A helper struct for implementing single-row double-and-add using incomplete addition. #[derive(Copy, Clone, Debug, Eq, PartialEq)] diff --git a/halo2_gadgets/src/ecc/chip/mul/overflow.rs b/halo2_gadgets/src/ecc/chip/mul/overflow.rs index 0d7bd696..3625ff73 100644 --- a/halo2_gadgets/src/ecc/chip/mul/overflow.rs +++ b/halo2_gadgets/src/ecc/chip/mul/overflow.rs @@ -9,8 +9,8 @@ use halo2_proofs::{ plonk::{Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, poly::Rotation, }; - -use halo2curves::{pasta::pallas, FieldExt}; +use halo2curves::group::ff::PrimeField; +use halo2curves::pasta::pallas; use std::iter; diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed.rs b/halo2_gadgets/src/ecc/chip/mul_fixed.rs index 909dd171..0005a108 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed.rs @@ -7,7 +7,7 @@ use crate::utilities::decompose_running_sum::RunningSumConfig; use std::marker::PhantomData; use group::{ - ff::{PrimeField, PrimeFieldBits}, + ff::{Field, PrimeField, PrimeFieldBits}, Curve, }; use halo2_proofs::{ @@ -18,7 +18,7 @@ use halo2_proofs::{ }, poly::Rotation, }; -use halo2curves::{pasta::pallas, CurveAffine, FieldExt}; +use halo2curves::{pasta::pallas, CurveAffine}; use lazy_static::lazy_static; pub mod base_field_elem; diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed/base_field_elem.rs b/halo2_gadgets/src/ecc/chip/mul_fixed/base_field_elem.rs index 08fd34e3..91671847 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/base_field_elem.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/base_field_elem.rs @@ -13,7 +13,7 @@ use halo2_proofs::{ plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, poly::Rotation, }; -use halo2curves::{pasta::pallas, FieldExt}; +use halo2curves::pasta::pallas; use std::convert::TryInto; diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs b/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs index b82620c2..607c51e5 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs @@ -295,7 +295,7 @@ pub mod tests { // [-1]B is the largest scalar field element. { - let scalar_fixed = -pallas::Scalar::one(); + let scalar_fixed = -pallas::Scalar::ONE; let neg_1 = ScalarFixed::new( chip.clone(), layouter.namespace(|| "-1"), diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs index 47b2aa14..42363baa 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs @@ -209,6 +209,7 @@ impl> Config { // tested at the circuit-level. { use super::super::FixedPoint; + use ff::Field; use group::{ff::PrimeField, Curve}; scalar @@ -228,9 +229,9 @@ impl> Config { let magnitude = pallas::Scalar::from_repr(magnitude.to_repr()).unwrap(); let sign = if sign == &&pallas::Base::one() { - pallas::Scalar::one() + pallas::Scalar::ONE } else { - -pallas::Scalar::one() + -pallas::Scalar::ONE }; magnitude * sign @@ -248,13 +249,16 @@ impl> Config { #[cfg(test)] pub mod tests { - use group::{ff::PrimeField, Curve}; + use group::{ + ff::{Field, PrimeField}, + Curve, + }; use halo2_proofs::{ arithmetic::CurveAffine, circuit::{AssignedCell, Chip, Layouter, Value}, plonk::{Any, Error}, }; - use halo2curves::{pasta::pallas, FieldExt}; + use halo2curves::pasta::pallas; use crate::{ ecc::{ @@ -359,9 +363,9 @@ pub mod tests { let scalar = { let magnitude = pallas::Scalar::from_repr(magnitude.to_repr()).unwrap(); let sign = if *sign == pallas::Base::one() { - pallas::Scalar::one() + pallas::Scalar::ONE } else { - -pallas::Scalar::one() + -pallas::Scalar::ONE }; magnitude * sign }; @@ -430,6 +434,8 @@ pub mod tests { impl Circuit for MyCircuit { type Config = EccConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self::default() @@ -580,7 +586,7 @@ pub mod tests { offset: 1, }, cell_values: vec![( - ((Any::Advice, 5).into(), 0).into(), + ((Any::advice(), 5).into(), 0).into(), format_value(*magnitude_error), )], }, @@ -589,7 +595,7 @@ pub mod tests { location: FailureLocation::OutsideRegion { row: 0 }, }, VerifyFailure::Permutation { - column: (Any::Advice, 4).into(), + column: (Any::advice(), 4).into(), location: FailureLocation::InRegion { region: (2, "Short fixed-base mul (incomplete addition)") .into(), @@ -631,7 +637,7 @@ pub mod tests { region: (3, "Short fixed-base mul (most significant word)").into(), offset: 1, }, - cell_values: vec![(((Any::Advice, 4).into(), 0).into(), "0".to_string())], + cell_values: vec![(((Any::advice(), 4).into(), 0).into(), "0".to_string())], }, VerifyFailure::ConstraintNotSatisfied { constraint: ( @@ -646,14 +652,14 @@ pub mod tests { }, cell_values: vec![ ( - ((Any::Advice, 1).into(), 0).into(), + ((Any::advice(), 1).into(), 0).into(), format_value(negation_check_y), ), ( - ((Any::Advice, 3).into(), 0).into(), + ((Any::advice(), 3).into(), 0).into(), format_value(negation_check_y), ), - (((Any::Advice, 4).into(), 0).into(), "0".to_string()), + (((Any::advice(), 4).into(), 0).into(), "0".to_string()), ], } ]) diff --git a/halo2_gadgets/src/poseidon.rs b/halo2_gadgets/src/poseidon.rs index 13500751..ffd745ad 100644 --- a/halo2_gadgets/src/poseidon.rs +++ b/halo2_gadgets/src/poseidon.rs @@ -4,9 +4,9 @@ use std::convert::TryInto; use std::fmt; use std::marker::PhantomData; +use ff::PrimeField; use group::ff::Field; use halo2_proofs::{ - arithmetic::FieldExt, circuit::{AssignedCell, Chip, Layouter}, plonk::Error, }; @@ -27,7 +27,7 @@ pub enum PaddedWord { } /// The set of circuit instructions required to use the Poseidon permutation. -pub trait PoseidonInstructions, const T: usize, const RATE: usize>: +pub trait PoseidonInstructions, const T: usize, const RATE: usize>: Chip { /// Variable representing the word over which the Poseidon permutation operates. @@ -45,7 +45,7 @@ pub trait PoseidonInstructions, const T: usize, /// /// [`Hash`]: self::Hash pub trait PoseidonSpongeInstructions< - F: FieldExt, + F: Field, S: Spec, D: Domain, const T: usize, @@ -71,7 +71,7 @@ pub trait PoseidonSpongeInstructions< /// A word over which the Poseidon permutation operates. #[derive(Debug)] pub struct Word< - F: FieldExt, + F: Field, PoseidonChip: PoseidonInstructions, S: Spec, const T: usize, @@ -81,7 +81,7 @@ pub struct Word< } impl< - F: FieldExt, + F: Field, PoseidonChip: PoseidonInstructions, S: Spec, const T: usize, @@ -100,7 +100,7 @@ impl< } fn poseidon_sponge< - F: FieldExt, + F: Field, PoseidonChip: PoseidonSpongeInstructions, S: Spec, D: Domain, @@ -122,7 +122,7 @@ fn poseidon_sponge< /// A Poseidon sponge. #[derive(Debug)] pub struct Sponge< - F: FieldExt, + F: Field, PoseidonChip: PoseidonSpongeInstructions, S: Spec, M: SpongeMode, @@ -137,7 +137,7 @@ pub struct Sponge< } impl< - F: FieldExt, + F: Field, PoseidonChip: PoseidonSpongeInstructions, S: Spec, D: Domain, @@ -210,7 +210,7 @@ impl< } impl< - F: FieldExt, + F: Field, PoseidonChip: PoseidonSpongeInstructions, S: Spec, D: Domain, @@ -241,7 +241,7 @@ impl< /// A Poseidon hash function, built around a sponge. #[derive(Debug)] pub struct Hash< - F: FieldExt, + F: Field, PoseidonChip: PoseidonSpongeInstructions, S: Spec, D: Domain, @@ -252,7 +252,7 @@ pub struct Hash< } impl< - F: FieldExt, + F: Field, PoseidonChip: PoseidonSpongeInstructions, S: Spec, D: Domain, @@ -267,7 +267,7 @@ impl< } impl< - F: FieldExt, + F: PrimeField, PoseidonChip: PoseidonSpongeInstructions, T, RATE>, S: Spec, const T: usize, diff --git a/halo2_gadgets/src/poseidon/pow5.rs b/halo2_gadgets/src/poseidon/pow5.rs index 7b9862e5..7abd8632 100644 --- a/halo2_gadgets/src/poseidon/pow5.rs +++ b/halo2_gadgets/src/poseidon/pow5.rs @@ -2,7 +2,7 @@ use std::convert::TryInto; use std::iter; use halo2_proofs::{ - arithmetic::FieldExt, + arithmetic::Field, circuit::{AssignedCell, Cell, Chip, Layouter, Region, Value}, plonk::{ Advice, Any, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector, @@ -18,7 +18,7 @@ use crate::utilities::Var; /// Configuration for a [`Pow5Chip`]. #[derive(Clone, Debug)] -pub struct Pow5Config { +pub struct Pow5Config { pub(crate) state: [Column; WIDTH], partial_sbox: Column, rc_a: [Column; WIDTH], @@ -40,11 +40,11 @@ pub struct Pow5Config { /// The chip is implemented using a single round per row for full rounds, and two rounds /// per row for partial rounds. #[derive(Debug)] -pub struct Pow5Chip { +pub struct Pow5Chip { config: Pow5Config, } -impl Pow5Chip { +impl Pow5Chip { /// Configures this chip for use in a circuit. /// /// # Side-effects @@ -209,7 +209,7 @@ impl Pow5Chip Chip for Pow5Chip { +impl Chip for Pow5Chip { type Config = Pow5Config; type Loaded = (); @@ -222,7 +222,7 @@ impl Chip for Pow5Chip, const WIDTH: usize, const RATE: usize> +impl, const WIDTH: usize, const RATE: usize> PoseidonInstructions for Pow5Chip { type Word = StateWord; @@ -273,7 +273,7 @@ impl, const WIDTH: usize, const RATE: usize } impl< - F: FieldExt, + F: Field, S: Spec, D: Domain, const WIDTH: usize, @@ -302,7 +302,7 @@ impl< }; for i in 0..RATE { - load_state_word(i, F::zero())?; + load_state_word(i, F::ZERO)?; } load_state_word(RATE, D::initial_capacity_element())?; @@ -372,7 +372,7 @@ impl< .get(i) .map(|word| word.0.value().cloned()) // The capacity element is never altered by the input. - .unwrap_or_else(|| Value::known(F::zero())); + .unwrap_or_else(|| Value::known(F::ZERO)); region .assign_advice( || format!("load output_{}", i), @@ -403,21 +403,21 @@ impl< /// A word in the Poseidon state. #[derive(Clone, Debug)] -pub struct StateWord(AssignedCell); +pub struct StateWord(AssignedCell); -impl From> for AssignedCell { +impl From> for AssignedCell { fn from(state_word: StateWord) -> AssignedCell { state_word.0 } } -impl From> for StateWord { +impl From> for StateWord { fn from(cell_value: AssignedCell) -> StateWord { StateWord(cell_value) } } -impl Var for StateWord { +impl Var for StateWord { fn cell(&self) -> Cell { self.0.cell() } @@ -428,9 +428,9 @@ impl Var for StateWord { } #[derive(Debug)] -struct Pow5State([StateWord; WIDTH]); +struct Pow5State([StateWord; WIDTH]); -impl Pow5State { +impl Pow5State { fn full_round( self, region: &mut Region, @@ -450,7 +450,7 @@ impl Pow5State { r.as_ref().map(|r| { r.iter() .enumerate() - .fold(F::zero(), |acc, (j, r_j)| acc + m_i[j] * r_j) + .fold(F::ZERO, |acc, (j, r_j)| acc + m_i[j] * r_j) }) }); @@ -491,7 +491,7 @@ impl Pow5State { r.as_ref().map(|r| { m_i.iter() .zip(r.iter()) - .fold(F::zero(), |acc, (m_ij, r_j)| acc + *m_ij * r_j) + .fold(F::ZERO, |acc, (m_ij, r_j)| acc + *m_ij * r_j) }) }) .collect(); @@ -524,7 +524,7 @@ impl Pow5State { r_mid.as_ref().map(|r| { m_i.iter() .zip(r.iter()) - .fold(F::zero(), |acc, (m_ij, r_j)| acc + *m_ij * r_j) + .fold(F::ZERO, |acc, (m_ij, r_j)| acc + *m_ij * r_j) }) }) .collect(); @@ -620,6 +620,8 @@ mod tests { { type Config = Pow5Config; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { PermuteCircuit::(PhantomData) @@ -735,6 +737,8 @@ mod tests { { type Config = Pow5Config; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self { diff --git a/halo2_gadgets/src/poseidon/primitives.rs b/halo2_gadgets/src/poseidon/primitives.rs index 4d7d5b03..06f199f1 100644 --- a/halo2_gadgets/src/poseidon/primitives.rs +++ b/halo2_gadgets/src/poseidon/primitives.rs @@ -5,7 +5,9 @@ use std::fmt; use std::iter; use std::marker::PhantomData; -use halo2_proofs::arithmetic::FieldExt; +use ff::FromUniformBytes; +use ff::PrimeField; +use halo2_proofs::arithmetic::Field; pub(crate) mod fp; pub(crate) mod fq; @@ -27,10 +29,10 @@ pub(crate) type State = [F; T]; pub(crate) type SpongeRate = [Option; RATE]; /// The type used to hold the MDS matrix and its inverse. -pub(crate) type Mds = [[F; T]; T]; +pub type Mds = [[F; T]; T]; /// A specification for a Poseidon permutation. -pub trait Spec: fmt::Debug { +pub trait Spec: fmt::Debug { /// The number of full rounds for this specification. /// /// This must be an even number. @@ -50,33 +52,41 @@ pub trait Spec: fmt::Debug { fn secure_mds() -> usize; /// Generates `(round_constants, mds, mds^-1)` corresponding to this specification. - fn constants() -> (Vec<[F; T]>, Mds, Mds) { - let r_f = Self::full_rounds(); - let r_p = Self::partial_rounds(); - - let mut grain = grain::Grain::new(SboxType::Pow, T as u16, r_f as u16, r_p as u16); - - let round_constants = (0..(r_f + r_p)) - .map(|_| { - let mut rc_row = [F::zero(); T]; - for (rc, value) in rc_row - .iter_mut() - .zip((0..T).map(|_| grain.next_field_element())) - { - *rc = value; - } - rc_row - }) - .collect(); + fn constants() -> (Vec<[F; T]>, Mds, Mds); +} - let (mds, mds_inv) = mds::generate_mds::(&mut grain, Self::secure_mds()); +/// Generates `(round_constants, mds, mds^-1)` corresponding to this specification. +pub fn generate_constants< + F: FromUniformBytes<64> + Ord, + S: Spec, + const T: usize, + const RATE: usize, +>() -> (Vec<[F; T]>, Mds, Mds) { + let r_f = S::full_rounds(); + let r_p = S::partial_rounds(); - (round_constants, mds, mds_inv) - } + let mut grain = grain::Grain::new(SboxType::Pow, T as u16, r_f as u16, r_p as u16); + + let round_constants = (0..(r_f + r_p)) + .map(|_| { + let mut rc_row = [F::ZERO; T]; + for (rc, value) in rc_row + .iter_mut() + .zip((0..T).map(|_| grain.next_field_element())) + { + *rc = value; + } + rc_row + }) + .collect(); + + let (mds, mds_inv) = mds::generate_mds::(&mut grain, S::secure_mds()); + + (round_constants, mds, mds_inv) } /// Runs the Poseidon permutation on the given state. -pub(crate) fn permute, const T: usize, const RATE: usize>( +pub(crate) fn permute, const T: usize, const RATE: usize>( state: &mut State, mds: &Mds, round_constants: &[[F; T]], @@ -85,7 +95,7 @@ pub(crate) fn permute, const T: usize, const RA let r_p = S::partial_rounds(); let apply_mds = |state: &mut State| { - let mut new_state = [F::zero(); T]; + let mut new_state = [F::ZERO; T]; // Matrix multiplication #[allow(clippy::needless_range_loop)] for i in 0..T { @@ -123,7 +133,7 @@ pub(crate) fn permute, const T: usize, const RA }); } -fn poseidon_sponge, const T: usize, const RATE: usize>( +fn poseidon_sponge, const T: usize, const RATE: usize>( state: &mut State, input: Option<&Absorbing>, mds_matrix: &Mds, @@ -156,7 +166,7 @@ mod private { pub trait SpongeMode: private::SealedSpongeMode {} /// The absorbing state of the `Sponge`. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Absorbing(pub(crate) SpongeRate); /// The squeezing state of the `Sponge`. @@ -178,9 +188,10 @@ impl Absorbing { } } +#[derive(Clone)] /// A Poseidon sponge. pub(crate) struct Sponge< - F: FieldExt, + F: Field, S: Spec, M: SpongeMode, const T: usize, @@ -193,7 +204,7 @@ pub(crate) struct Sponge< _marker: PhantomData, } -impl, const T: usize, const RATE: usize> +impl, const T: usize, const RATE: usize> Sponge, T, RATE> { /// Constructs a new sponge for the given Poseidon specification. @@ -201,7 +212,7 @@ impl, const T: usize, const RATE: usize> let (round_constants, mds_matrix, _) = S::constants(); let mode = Absorbing([None; RATE]); - let mut state = [F::zero(); T]; + let mut state = [F::ZERO; T]; state[RATE] = initial_capacity_element; Sponge { @@ -251,7 +262,7 @@ impl, const T: usize, const RATE: usize> } } -impl, const T: usize, const RATE: usize> +impl, const T: usize, const RATE: usize> Sponge, T, RATE> { /// Squeezes an element from the sponge. @@ -275,7 +286,7 @@ impl, const T: usize, const RATE: usize> } /// A domain in which a Poseidon hash function is being used. -pub trait Domain { +pub trait Domain { /// Iterator that outputs padding field elements. type Padding: IntoIterator; @@ -295,7 +306,7 @@ pub trait Domain { #[derive(Clone, Copy, Debug)] pub struct ConstantLength; -impl Domain for ConstantLength { +impl Domain for ConstantLength { type Padding = iter::Take>; fn name() -> String { @@ -315,13 +326,14 @@ impl Domain for Constan // Poseidon authors encode the constant length into the capacity element, ensuring // that inputs of different lengths do not share the same permutation. let k = (L + RATE - 1) / RATE; - iter::repeat(F::zero()).take(k * RATE - L) + iter::repeat(F::ZERO).take(k * RATE - L) } } +#[derive(Clone)] /// A Poseidon hash function, built around a sponge. pub struct Hash< - F: FieldExt, + F: Field, S: Spec, D: Domain, const T: usize, @@ -331,7 +343,7 @@ pub struct Hash< _domain: PhantomData, } -impl, D: Domain, const T: usize, const RATE: usize> +impl, D: Domain, const T: usize, const RATE: usize> fmt::Debug for Hash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -345,7 +357,7 @@ impl, D: Domain, const T: usize, const } } -impl, D: Domain, const T: usize, const RATE: usize> +impl, D: Domain, const T: usize, const RATE: usize> Hash { /// Initializes a new hasher. @@ -357,7 +369,7 @@ impl, D: Domain, const T: usize, const } } -impl, const T: usize, const RATE: usize, const L: usize> +impl, const T: usize, const RATE: usize, const L: usize> Hash, T, RATE> { /// Hashes the given input. @@ -374,9 +386,9 @@ impl, const T: usize, const RATE: usize, const #[cfg(test)] mod tests { - use halo2curves::{pasta::pallas, FieldExt}; - use super::{permute, ConstantLength, Hash, P128Pow5T3 as OrchardNullifier, Spec}; + use ff::PrimeField; + use halo2curves::pasta::pallas; #[test] fn orchard_spec_equivalence() { diff --git a/halo2_gadgets/src/poseidon/primitives/grain.rs b/halo2_gadgets/src/poseidon/primitives/grain.rs index f3cf94f7..99711f97 100644 --- a/halo2_gadgets/src/poseidon/primitives/grain.rs +++ b/halo2_gadgets/src/poseidon/primitives/grain.rs @@ -3,7 +3,8 @@ use std::marker::PhantomData; use bitvec::prelude::*; -use halo2_proofs::arithmetic::FieldExt; +use ff::{FromUniformBytes, PrimeField}; +use halo2_proofs::arithmetic::Field; const STATE: usize = 80; @@ -43,13 +44,13 @@ impl SboxType { } } -pub(super) struct Grain { +pub(super) struct Grain { state: BitArr!(for 80, in u8, Msb0), next_bit: usize, _field: PhantomData, } -impl Grain { +impl Grain { pub(super) fn new(sbox: SboxType, t: u16, r_f: u16, r_p: u16) -> Self { // Initialize the LFSR state. let mut state = bitarr![u8, Msb0; 1; STATE]; @@ -135,7 +136,9 @@ impl Grain { } } } +} +impl> Grain { /// Returns the next field element from this Grain instantiation, without using /// rejection sampling. pub(super) fn next_field_element_without_rejection(&mut self) -> F { @@ -161,11 +164,11 @@ impl Grain { view[i / 8] |= if bit { 1 << (i % 8) } else { 0 }; } - F::from_bytes_wide(&bytes) + F::from_uniform_bytes(&bytes) } } -impl Iterator for Grain { +impl Iterator for Grain { type Item = bool; fn next(&mut self) -> Option { diff --git a/halo2_gadgets/src/poseidon/primitives/mds.rs b/halo2_gadgets/src/poseidon/primitives/mds.rs index fb809e3a..892ee11f 100644 --- a/halo2_gadgets/src/poseidon/primitives/mds.rs +++ b/halo2_gadgets/src/poseidon/primitives/mds.rs @@ -1,8 +1,8 @@ -use halo2_proofs::arithmetic::FieldExt; +use ff::FromUniformBytes; use super::{grain::Grain, Mds}; -pub(super) fn generate_mds( +pub(super) fn generate_mds + Ord, const T: usize>( grain: &mut Grain, mut select: usize, ) -> (Mds, Mds) { @@ -48,7 +48,7 @@ pub(super) fn generate_mds( // However, the Poseidon paper and reference impl use the positive formulation, // and we want to rely on the reference impl for MDS security, so we use the same // formulation. - let mut mds = [[F::zero(); T]; T]; + let mut mds = [[F::ZERO; T]; T]; #[allow(clippy::needless_range_loop)] for i in 0..T { for j in 0..T { @@ -74,10 +74,10 @@ pub(super) fn generate_mds( // where A_i(x) and B_i(x) are the Lagrange polynomials for xs and ys respectively. // // We adapt this to the positive Cauchy formulation by negating ys. - let mut mds_inv = [[F::zero(); T]; T]; + let mut mds_inv = [[F::ZERO; T]; T]; let l = |xs: &[F], j, x: F| { let x_j = xs[j]; - xs.iter().enumerate().fold(F::one(), |acc, (m, x_m)| { + xs.iter().enumerate().fold(F::ONE, |acc, (m, x_m)| { if m == j { acc } else { diff --git a/halo2_gadgets/src/poseidon/primitives/p128pow5t3.rs b/halo2_gadgets/src/poseidon/primitives/p128pow5t3.rs index 379c399b..c8d54d9a 100644 --- a/halo2_gadgets/src/poseidon/primitives/p128pow5t3.rs +++ b/halo2_gadgets/src/poseidon/primitives/p128pow5t3.rs @@ -66,29 +66,31 @@ impl Spec for P128Pow5T3 { #[cfg(test)] mod tests { - use ff::PrimeField; - use std::marker::PhantomData; - - use halo2curves::FieldExt; - use super::{ super::{fp, fq}, Fp, Fq, }; - use crate::poseidon::primitives::{permute, ConstantLength, Hash, Spec}; + use crate::poseidon::primitives::{ + generate_constants, permute, ConstantLength, Hash, Mds, Spec, + }; + use ff::PrimeField; + use ff::{Field, FromUniformBytes}; + use std::marker::PhantomData; /// The same Poseidon specification as poseidon::P128Pow5T3, but constructed /// such that its constants will be generated at runtime. #[derive(Debug)] - pub struct P128Pow5T3Gen(PhantomData); + pub struct P128Pow5T3Gen(PhantomData); - impl P128Pow5T3Gen { + impl P128Pow5T3Gen { pub fn new() -> Self { P128Pow5T3Gen(PhantomData::default()) } } - impl Spec for P128Pow5T3Gen { + impl + Ord, const SECURE_MDS: usize> Spec + for P128Pow5T3Gen + { fn full_rounds() -> usize { 8 } @@ -98,17 +100,21 @@ mod tests { } fn sbox(val: F) -> F { - val.pow_vartime(&[5]) + val.pow_vartime([5]) } fn secure_mds() -> usize { SECURE_MDS } + + fn constants() -> (Vec<[F; 3]>, Mds, Mds) { + generate_constants::<_, Self, 3, 2>() + } } #[test] fn verify_constants() { - fn verify_constants_helper( + fn verify_constants_helper + Ord>( expected_round_constants: [[F; 3]; 64], expected_mds: [[F; 3]; 3], expected_mds_inv: [[F; 3]; 3], diff --git a/halo2_gadgets/src/sha256.rs b/halo2_gadgets/src/sha256.rs index 19a658df..391020a3 100644 --- a/halo2_gadgets/src/sha256.rs +++ b/halo2_gadgets/src/sha256.rs @@ -7,7 +7,7 @@ use std::convert::TryInto; use std::fmt; use halo2_proofs::{ - arithmetic::FieldExt, + arithmetic::Field, circuit::{Chip, Layouter}, plonk::Error, }; @@ -22,7 +22,7 @@ pub const BLOCK_SIZE: usize = 16; const DIGEST_SIZE: usize = 8; /// The set of circuit instructions required to use the [`Sha256`] gadget. -pub trait Sha256Instructions: Chip { +pub trait Sha256Instructions: Chip { /// Variable representing the SHA-256 internal state. type State: Clone + fmt::Debug; /// Variable representing a 32-bit word of the input block to the SHA-256 compression @@ -63,14 +63,14 @@ pub struct Sha256Digest([BlockWord; DIGEST_SIZE]); /// A gadget that constrains a SHA-256 invocation. It supports input at a granularity of /// 32 bits. #[derive(Debug)] -pub struct Sha256> { +pub struct Sha256> { chip: CS, state: CS::State, cur_block: Vec, length: usize, } -impl> Sha256 { +impl> Sha256 { /// Create a new hasher instance. pub fn new(chip: Sha256Chip, mut layouter: impl Layouter) -> Result { let state = chip.initialization_vector(&mut layouter)?; diff --git a/halo2_gadgets/src/sha256/table16.rs b/halo2_gadgets/src/sha256/table16.rs index bf9b35aa..76b8e347 100644 --- a/halo2_gadgets/src/sha256/table16.rs +++ b/halo2_gadgets/src/sha256/table16.rs @@ -141,7 +141,7 @@ impl AssignedBits { let column: Column = column.into(); match column.column_type() { - Any::Advice => { + Any::Advice(_) => { region.assign_advice(annotation, column.try_into().unwrap(), offset, || { value.clone() }) @@ -176,7 +176,7 @@ impl AssignedBits<16> { let column: Column = column.into(); let value: Value> = value.map(|v| v.into()); match column.column_type() { - Any::Advice => { + Any::Advice(_) => { region.assign_advice(annotation, column.try_into().unwrap(), offset, || { value.clone() }) @@ -211,7 +211,7 @@ impl AssignedBits<32> { let column: Column = column.into(); let value: Value> = value.map(|v| v.into()); match column.column_type() { - Any::Advice => { + Any::Advice(_) => { region.assign_advice(annotation, column.try_into().unwrap(), offset, || { value.clone() }) @@ -468,6 +468,8 @@ mod tests { impl Circuit for MyCircuit { type Config = Table16Config; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { MyCircuit {} diff --git a/halo2_gadgets/src/sha256/table16/compression.rs b/halo2_gadgets/src/sha256/table16/compression.rs index 62deb429..874452a4 100644 --- a/halo2_gadgets/src/sha256/table16/compression.rs +++ b/halo2_gadgets/src/sha256/table16/compression.rs @@ -954,6 +954,8 @@ mod tests { impl Circuit for MyCircuit { type Config = Table16Config; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { MyCircuit {} diff --git a/halo2_gadgets/src/sha256/table16/compression/compression_gates.rs b/halo2_gadgets/src/sha256/table16/compression/compression_gates.rs index e22a1021..1cf6a605 100644 --- a/halo2_gadgets/src/sha256/table16/compression/compression_gates.rs +++ b/halo2_gadgets/src/sha256/table16/compression/compression_gates.rs @@ -1,15 +1,13 @@ use super::super::{util::*, Gate}; -use halo2_proofs::{ - arithmetic::FieldExt, - plonk::{Constraint, Constraints, Expression}, -}; +use ff::PrimeField; +use halo2_proofs::plonk::{Constraint, Constraints, Expression}; use std::marker::PhantomData; -pub struct CompressionGate(PhantomData); +pub struct CompressionGate(PhantomData); -impl CompressionGate { +impl CompressionGate { fn ones() -> Expression { - Expression::Constant(F::one()) + Expression::Constant(F::ONE) } // Decompose `A,B,C,D` words @@ -59,16 +57,16 @@ impl CompressionGate { + c_mid * F::from(1 << 16) + c_hi * F::from(1 << 19) + d * F::from(1 << 22) - + word_lo * (-F::one()) - + word_hi * F::from(1 << 16) * (-F::one()); + + word_lo * (-F::ONE) + + word_hi * F::from(1 << 16) * (-F::ONE); let spread_check = spread_a + spread_b * F::from(1 << 4) + spread_c_lo * F::from(1 << 26) + spread_c_mid * F::from(1 << 32) + spread_c_hi * F::from(1 << 38) + spread_d * F::from(1 << 44) - + spread_word_lo * (-F::one()) - + spread_word_hi * F::from(1 << 32) * (-F::one()); + + spread_word_lo * (-F::ONE) + + spread_word_hi * F::from(1 << 32) * (-F::ONE); Constraints::with_selector( s_decompose_abcd, @@ -130,16 +128,16 @@ impl CompressionGate { + b_hi * F::from(1 << 8) + c * F::from(1 << 11) + d * F::from(1 << 25) - + word_lo * (-F::one()) - + word_hi * F::from(1 << 16) * (-F::one()); + + word_lo * (-F::ONE) + + word_hi * F::from(1 << 16) * (-F::ONE); let spread_check = spread_a_lo + spread_a_hi * F::from(1 << 6) + spread_b_lo * F::from(1 << 12) + spread_b_hi * F::from(1 << 16) + spread_c * F::from(1 << 22) + spread_d * F::from(1 << 50) - + spread_word_lo * (-F::one()) - + spread_word_hi * F::from(1 << 32) * (-F::one()); + + spread_word_lo * (-F::ONE) + + spread_word_hi * F::from(1 << 32) * (-F::ONE); Constraints::with_selector( s_decompose_efgh, @@ -189,7 +187,7 @@ impl CompressionGate { + spread_c_mid * F::from(1 << 52) + spread_c_hi * F::from(1 << 58); let xor = xor_0 + xor_1 + xor_2; - let check = spread_witness + (xor * -F::one()); + let check = spread_witness + (xor * -F::ONE); Some(("s_upper_sigma_0", s_upper_sigma_0 * check)) } @@ -233,7 +231,7 @@ impl CompressionGate { + spread_b_hi * F::from(1 << 30) + spread_c * F::from(1 << 36); let xor = xor_0 + xor_1 + xor_2; - let check = spread_witness + (xor * -F::one()); + let check = spread_witness + (xor * -F::ONE); Some(("s_upper_sigma_1", s_upper_sigma_1 * check)) } @@ -259,7 +257,7 @@ impl CompressionGate { let rhs_odd = spread_p0_odd + spread_p1_odd * F::from(1 << 32); let rhs = rhs_even + rhs_odd * F::from(2); - let check = lhs + rhs * -F::one(); + let check = lhs + rhs * -F::ONE; Some(("s_ch", s_ch * check)) } @@ -286,9 +284,9 @@ impl CompressionGate { let neg_check = { let evens = Self::ones() * F::from(MASK_EVEN_32 as u64); // evens - spread_e_lo = spread_e_neg_lo - let lo_check = spread_e_neg_lo.clone() + spread_e_lo + (evens.clone() * (-F::one())); + let lo_check = spread_e_neg_lo.clone() + spread_e_lo + (evens.clone() * (-F::ONE)); // evens - spread_e_hi = spread_e_neg_hi - let hi_check = spread_e_neg_hi.clone() + spread_e_hi + (evens * (-F::one())); + let hi_check = spread_e_neg_hi.clone() + spread_e_hi + (evens * (-F::ONE)); std::iter::empty() .chain(Some(("lo_check", lo_check))) diff --git a/halo2_gadgets/src/sha256/table16/gates.rs b/halo2_gadgets/src/sha256/table16/gates.rs index 4f268092..d5f3840a 100644 --- a/halo2_gadgets/src/sha256/table16/gates.rs +++ b/halo2_gadgets/src/sha256/table16/gates.rs @@ -1,10 +1,11 @@ -use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; +use ff::PrimeField; +use halo2_proofs::{arithmetic::Field, plonk::Expression}; -pub struct Gate(pub Expression); +pub struct Gate(pub Expression); -impl Gate { +impl Gate { fn ones() -> Expression { - Expression::Constant(F::one()) + Expression::Constant(F::ONE) } // Helper gates @@ -32,7 +33,7 @@ impl Gate { for i in 0..deg { let i = i as u64; if i != idx { - expr = expr * (Self::ones() * (-F::one()) * F::from(i) + var.clone()); + expr = expr * (Self::ones() * (-F::ONE) * F::from(i) + var.clone()); } } expr * F::from(u64::from(eval)) @@ -46,13 +47,13 @@ impl Gate { } } if denom < 0 { - -F::one() * F::from(factor / (-denom as u64)) + -F::ONE * F::from(factor / (-denom as u64)) } else { F::from(factor / (denom as u64)) } }; - let mut expr = Self::ones() * F::zero(); + let mut expr = Self::ones() * F::ZERO; for ((idx, _), eval) in points.iter().enumerate().zip(evals.iter()) { expr = expr + numerator(var.clone(), *eval, idx as u64) * denominator(idx as i32) } @@ -63,7 +64,7 @@ impl Gate { pub fn range_check(value: Expression, lower_range: u64, upper_range: u64) -> Expression { let mut expr = Self::ones(); for i in lower_range..(upper_range + 1) { - expr = expr * (Self::ones() * (-F::one()) * F::from(i) + value.clone()) + expr = expr * (Self::ones() * (-F::ONE) * F::from(i) + value.clone()) } expr } diff --git a/halo2_gadgets/src/sha256/table16/message_schedule.rs b/halo2_gadgets/src/sha256/table16/message_schedule.rs index 690e086c..3da70c0a 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule.rs @@ -411,6 +411,8 @@ mod tests { impl Circuit for MyCircuit { type Config = Table16Config; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { MyCircuit {} diff --git a/halo2_gadgets/src/sha256/table16/message_schedule/schedule_gates.rs b/halo2_gadgets/src/sha256/table16/message_schedule/schedule_gates.rs index fab51bd3..cf6a9172 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule/schedule_gates.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule/schedule_gates.rs @@ -1,10 +1,11 @@ use super::super::Gate; -use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; +use ff::PrimeField; +use halo2_proofs::plonk::Expression; use std::marker::PhantomData; -pub struct ScheduleGate(PhantomData); +pub struct ScheduleGate(PhantomData); -impl ScheduleGate { +impl ScheduleGate { /// s_word for W_16 to W_63 #[allow(clippy::too_many_arguments)] pub fn s_word( @@ -25,8 +26,8 @@ impl ScheduleGate { let word_check = lo + hi * F::from(1 << 16) - + (carry.clone() * F::from(1 << 32) * (-F::one())) - + (word * (-F::one())); + + (carry.clone() * F::from(1 << 32) * (-F::ONE)) + + (word * (-F::ONE)); let carry_check = Gate::range_check(carry, 0, 3); [("word_check", word_check), ("carry_check", carry_check)] @@ -58,11 +59,8 @@ impl ScheduleGate { tag_d: Expression, word: Expression, ) -> impl Iterator)> { - let decompose_check = a - + b * F::from(1 << 3) - + c * F::from(1 << 7) - + d * F::from(1 << 18) - + word * (-F::one()); + let decompose_check = + a + b * F::from(1 << 3) + c * F::from(1 << 7) + d * F::from(1 << 18) + word * (-F::ONE); let range_check_tag_c = Gate::range_check(tag_c, 0, 2); let range_check_tag_d = Gate::range_check(tag_d, 0, 4); @@ -99,7 +97,7 @@ impl ScheduleGate { + e * F::from(1 << 17) + f * F::from(1 << 18) + g * F::from(1 << 19) - + word * (-F::one()); + + word * (-F::ONE); let range_check_tag_d = Gate::range_check(tag_d, 0, 0); let range_check_tag_g = Gate::range_check(tag_g, 0, 3); @@ -129,7 +127,7 @@ impl ScheduleGate { + b * F::from(1 << 10) + c * F::from(1 << 17) + d * F::from(1 << 19) - + word * (-F::one()); + + word * (-F::ONE); let range_check_tag_a = Gate::range_check(tag_a, 0, 1); let range_check_tag_d = Gate::range_check(tag_d, 0, 3); diff --git a/halo2_gadgets/src/sha256/table16/spread_table.rs b/halo2_gadgets/src/sha256/table16/spread_table.rs index 3e1488e9..9b8a313c 100644 --- a/halo2_gadgets/src/sha256/table16/spread_table.rs +++ b/halo2_gadgets/src/sha256/table16/spread_table.rs @@ -1,6 +1,7 @@ use super::{util::*, AssignedBits}; +use ff::PrimeField; use halo2_proofs::{ - arithmetic::FieldExt, + arithmetic::Field, circuit::{Chip, Layouter, Region, Value}, plonk::{Advice, Column, ConstraintSystem, Error, TableColumn}, poly::Rotation, @@ -153,12 +154,12 @@ pub(super) struct SpreadTableConfig { } #[derive(Clone, Debug)] -pub(super) struct SpreadTableChip { +pub(super) struct SpreadTableChip { config: SpreadTableConfig, _marker: PhantomData, } -impl Chip for SpreadTableChip { +impl Chip for SpreadTableChip { type Config = SpreadTableConfig; type Loaded = (); @@ -171,7 +172,7 @@ impl Chip for SpreadTableChip { } } -impl SpreadTableChip { +impl SpreadTableChip { pub fn configure( meta: &mut ConstraintSystem, input_tag: Column, @@ -250,45 +251,42 @@ impl SpreadTableChip { } impl SpreadTableConfig { - fn generate() -> impl Iterator { - (1..=(1 << 16)).scan( - (F::zero(), F::zero(), F::zero()), - |(tag, dense, spread), i| { - // We computed this table row in the previous iteration. - let res = (*tag, *dense, *spread); - - // i holds the zero-indexed row number for the next table row. - match i { - BITS_7 | BITS_10 | BITS_11 | BITS_13 | BITS_14 => *tag += F::one(), - _ => (), - } - *dense += F::one(); - if i & 1 == 0 { - // On even-numbered rows we recompute the spread. - *spread = F::zero(); - for b in 0..16 { - if (i >> b) & 1 != 0 { - *spread += F::from(1 << (2 * b)); - } + fn generate() -> impl Iterator { + (1..=(1 << 16)).scan((F::ZERO, F::ZERO, F::ZERO), |(tag, dense, spread), i| { + // We computed this table row in the previous iteration. + let res = (*tag, *dense, *spread); + + // i holds the zero-indexed row number for the next table row. + match i { + BITS_7 | BITS_10 | BITS_11 | BITS_13 | BITS_14 => *tag += F::ONE, + _ => (), + } + *dense += F::ONE; + if i & 1 == 0 { + // On even-numbered rows we recompute the spread. + *spread = F::ZERO; + for b in 0..16 { + if (i >> b) & 1 != 0 { + *spread += F::from(1 << (2 * b)); } - } else { - // On odd-numbered rows we add one. - *spread += F::one(); } + } else { + // On odd-numbered rows we add one. + *spread += F::ONE; + } - Some(res) - }, - ) + Some(res) + }) } } #[cfg(test)] mod tests { use super::{get_tag, SpreadTableChip, SpreadTableConfig}; + use ff::PrimeField; use rand::Rng; use halo2_proofs::{ - arithmetic::FieldExt, circuit::{Layouter, SimpleFloorPlanner, Value}, dev::MockProver, plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, @@ -303,9 +301,11 @@ mod tests { struct MyCircuit {} - impl Circuit for MyCircuit { + impl Circuit for MyCircuit { type Config = SpreadTableConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { MyCircuit {} @@ -354,20 +354,20 @@ mod tests { }; // Test the first few small values. - add_row(F::zero(), F::from(0b000), F::from(0b000000))?; - add_row(F::zero(), F::from(0b001), F::from(0b000001))?; - add_row(F::zero(), F::from(0b010), F::from(0b000100))?; - add_row(F::zero(), F::from(0b011), F::from(0b000101))?; - add_row(F::zero(), F::from(0b100), F::from(0b010000))?; - add_row(F::zero(), F::from(0b101), F::from(0b010001))?; + add_row(F::ZERO, F::from(0b000), F::from(0b000000))?; + add_row(F::ZERO, F::from(0b001), F::from(0b000001))?; + add_row(F::ZERO, F::from(0b010), F::from(0b000100))?; + add_row(F::ZERO, F::from(0b011), F::from(0b000101))?; + add_row(F::ZERO, F::from(0b100), F::from(0b010000))?; + add_row(F::ZERO, F::from(0b101), F::from(0b010001))?; // Test the tag boundaries: // 7-bit - add_row(F::zero(), F::from(0b1111111), F::from(0b01010101010101))?; - add_row(F::one(), F::from(0b10000000), F::from(0b0100000000000000))?; + add_row(F::ZERO, F::from(0b1111111), F::from(0b01010101010101))?; + add_row(F::ONE, F::from(0b10000000), F::from(0b0100000000000000))?; // - 10-bit add_row( - F::one(), + F::ONE, F::from(0b1111111111), F::from(0b01010101010101010101), )?; diff --git a/halo2_gadgets/src/sinsemilla.rs b/halo2_gadgets/src/sinsemilla.rs index 3cec450e..543d49f5 100644 --- a/halo2_gadgets/src/sinsemilla.rs +++ b/halo2_gadgets/src/sinsemilla.rs @@ -203,9 +203,9 @@ where let to_base_field = |bits: &[Value]| -> Value { let bits: Value> = bits.iter().cloned().collect(); bits.map(|bits| { - bits.into_iter().rev().fold(C::Base::zero(), |acc, bit| { + bits.into_iter().rev().fold(C::Base::ZERO, |acc, bit| { if bit { - acc.double() + C::Base::one() + acc.double() + C::Base::ONE } else { acc.double() } @@ -243,7 +243,7 @@ where subpieces: impl IntoIterator>>, ) -> Result { let (field_elem, total_bits) = subpieces.into_iter().fold( - (Value::known(C::Base::zero()), 0), + (Value::known(C::Base::ZERO), 0), |(acc, bits), subpiece| { assert!(bits < 64); let subpiece_shifted = subpiece @@ -525,6 +525,8 @@ pub(crate) mod tests { SinsemillaConfig, ); type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { MyCircuit {} diff --git a/halo2_gadgets/src/sinsemilla/chip/generator_table.rs b/halo2_gadgets/src/sinsemilla/chip/generator_table.rs index a653c13b..a50a687e 100644 --- a/halo2_gadgets/src/sinsemilla/chip/generator_table.rs +++ b/halo2_gadgets/src/sinsemilla/chip/generator_table.rs @@ -6,7 +6,8 @@ use halo2_proofs::{ use super::{CommitDomains, FixedPoints, HashDomains}; use crate::sinsemilla::primitives::{self as sinsemilla, SINSEMILLA_S}; -use halo2curves::{pasta::pallas, FieldExt}; +use ff::PrimeField; +use halo2curves::pasta::pallas; /// Table containing independent generators S[0..2^k] #[derive(Eq, PartialEq, Copy, Clone, Debug)] diff --git a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs index 70eab6b8..9c15dd11 100644 --- a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs @@ -10,8 +10,8 @@ use halo2_proofs::{ plonk::{Assigned, Error}, }; -use group::ff::{PrimeField, PrimeFieldBits}; -use halo2curves::{pasta::pallas, CurveAffine, FieldExt}; +use group::ff::{Field, PrimeField, PrimeFieldBits}; +use halo2curves::{pasta::pallas, CurveAffine}; use std::ops::Deref; @@ -376,15 +376,15 @@ where } /// The x-coordinate of the accumulator in a Sinsemilla hash instance. -struct X(AssignedCell, F>); +struct X(AssignedCell, F>); -impl From, F>> for X { +impl From, F>> for X { fn from(cell_value: AssignedCell, F>) -> Self { X(cell_value) } } -impl Deref for X { +impl Deref for X { type Target = AssignedCell, F>; fn deref(&self) -> &AssignedCell, F> { @@ -397,15 +397,15 @@ impl Deref for X { /// This is never actually witnessed until the last round, since it /// can be derived from other variables. Thus it only exists as a field /// element, not a `CellValue`. -struct Y(Value>); +struct Y(Value>); -impl From>> for Y { +impl From>> for Y { fn from(value: Value>) -> Self { Y(value) } } -impl Deref for Y { +impl Deref for Y { type Target = Value>; fn deref(&self) -> &Value> { diff --git a/halo2_gadgets/src/sinsemilla/merkle.rs b/halo2_gadgets/src/sinsemilla/merkle.rs index a9ae781d..219a9025 100644 --- a/halo2_gadgets/src/sinsemilla/merkle.rs +++ b/halo2_gadgets/src/sinsemilla/merkle.rs @@ -213,6 +213,8 @@ pub mod tests { MerkleConfig, ); type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self::default() diff --git a/halo2_gadgets/src/sinsemilla/merkle/chip.rs b/halo2_gadgets/src/sinsemilla/merkle/chip.rs index 97da766d..bb042a5a 100644 --- a/halo2_gadgets/src/sinsemilla/merkle/chip.rs +++ b/halo2_gadgets/src/sinsemilla/merkle/chip.rs @@ -5,7 +5,7 @@ use halo2_proofs::{ plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, poly::Rotation, }; -use halo2curves::{pasta::pallas, FieldExt}; +use halo2curves::pasta::pallas; use super::MerkleInstructions; diff --git a/halo2_gadgets/src/sinsemilla/message.rs b/halo2_gadgets/src/sinsemilla/message.rs index 6bb72e2f..62696834 100644 --- a/halo2_gadgets/src/sinsemilla/message.rs +++ b/halo2_gadgets/src/sinsemilla/message.rs @@ -1,17 +1,17 @@ //! Gadget and chips for the Sinsemilla hash function. use ff::PrimeFieldBits; use halo2_proofs::{ - arithmetic::FieldExt, + arithmetic::Field, circuit::{AssignedCell, Cell, Value}, }; use std::fmt::Debug; /// A [`Message`] composed of several [`MessagePiece`]s. #[derive(Clone, Debug)] -pub struct Message(Vec>); +pub struct Message(Vec>); -impl - From>> for Message +impl From>> + for Message { fn from(pieces: Vec>) -> Self { // A message cannot contain more than `MAX_WORDS` words. @@ -20,7 +20,7 @@ impl } } -impl std::ops::Deref +impl std::ops::Deref for Message { type Target = [MessagePiece]; @@ -35,13 +35,13 @@ impl std:: /// The piece must fit within a base field element, which means its length /// cannot exceed the base field's `NUM_BITS`. #[derive(Clone, Debug)] -pub struct MessagePiece { +pub struct MessagePiece { cell_value: AssignedCell, /// The number of K-bit words in this message piece. num_words: usize, } -impl MessagePiece { +impl MessagePiece { pub fn new(cell_value: AssignedCell, num_words: usize) -> Self { assert!(num_words * K < F::NUM_BITS as usize); Self { diff --git a/halo2_gadgets/src/utilities.rs b/halo2_gadgets/src/utilities.rs index c6868784..887ee924 100644 --- a/halo2_gadgets/src/utilities.rs +++ b/halo2_gadgets/src/utilities.rs @@ -1,11 +1,10 @@ //! Utility gadgets. -use ff::{Field, PrimeFieldBits}; +use ff::{Field, PrimeField, PrimeFieldBits}; use halo2_proofs::{ circuit::{AssignedCell, Cell, Layouter, Value}, plonk::{Advice, Column, Error, Expression}, }; -use halo2curves::FieldExt; use std::marker::PhantomData; use std::ops::Range; @@ -32,7 +31,7 @@ impl FieldValue for AssignedCell { } /// Trait for a variable in the circuit. -pub trait Var: Clone + std::fmt::Debug + From> { +pub trait Var: Clone + std::fmt::Debug + From> { /// The cell at which this variable was allocated. fn cell(&self) -> Cell; @@ -40,7 +39,7 @@ pub trait Var: Clone + std::fmt::Debug + From> { fn value(&self) -> Value; } -impl Var for AssignedCell { +impl Var for AssignedCell { fn cell(&self) -> Cell { self.cell() } @@ -51,7 +50,7 @@ impl Var for AssignedCell { } /// Trait for utilities used across circuits. -pub trait UtilitiesInstructions { +pub trait UtilitiesInstructions { /// Variable in the circuit. type Var: Var; @@ -130,15 +129,15 @@ impl RangeConstrained> { } /// Checks that an expression is either 1 or 0. -pub fn bool_check(value: Expression) -> Expression { +pub fn bool_check(value: Expression) -> Expression { range_check(value, 2) } /// If `a` then `b`, else `c`. Returns (a * b) + (1 - a) * c. /// /// `a` must be a boolean-constrained expression. -pub fn ternary(a: Expression, b: Expression, c: Expression) -> Expression { - let one_minus_a = Expression::Constant(F::one()) - a.clone(); +pub fn ternary(a: Expression, b: Expression, c: Expression) -> Expression { + let one_minus_a = Expression::Constant(F::ONE) - a.clone(); a * b + one_minus_a * c } @@ -156,9 +155,9 @@ pub fn bitrange_subset(field_elem: &F, bitrange: Range .skip(bitrange.start) .take(bitrange.end - bitrange.start) .rev() - .fold(F::zero(), |acc, bit| { + .fold(F::ZERO, |acc, bit| { if bit { - acc.double() + F::one() + acc.double() + F::ONE } else { acc.double() } @@ -167,7 +166,7 @@ pub fn bitrange_subset(field_elem: &F, bitrange: Range /// Check that an expression is in the small range [0..range), /// i.e. 0 ≤ word < range. -pub fn range_check(word: Expression, range: usize) -> Expression { +pub fn range_check(word: Expression, range: usize) -> Expression { (1..range).fold(word.clone(), |acc, i| { acc * (Expression::Constant(F::from(i as u64)) - word.clone()) }) @@ -240,6 +239,7 @@ pub fn i2lebsp(int: u64) -> [bool; NUM_BITS] { #[cfg(test)] mod tests { use super::*; + use ff::FromUniformBytes; use group::ff::{Field, PrimeField}; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, @@ -247,7 +247,7 @@ mod tests { plonk::{Any, Circuit, ConstraintSystem, Constraints, Error, Selector}, poly::Rotation, }; - use halo2curves::{pasta::pallas, FieldExt}; + use halo2curves::pasta::pallas; use proptest::prelude::*; use rand::rngs::OsRng; use std::convert::TryInto; @@ -271,6 +271,8 @@ mod tests { impl Circuit for MyCircuit { type Config = Config; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { MyCircuit(self.0) @@ -329,7 +331,7 @@ mod tests { region: (0, "range constrain").into(), offset: 0, }, - cell_values: vec![(((Any::Advice, 0).into(), 0).into(), "0x8".to_string())], + cell_values: vec![(((Any::advice(), 0).into(), 0).into(), "0x8".to_string())], }]) ); } @@ -420,7 +422,7 @@ mod tests { // Instead of rejecting out-of-range bytes, let's reduce them. let mut buf = [0; 64]; buf[..32].copy_from_slice(&bytes); - pallas::Scalar::from_bytes_wide(&buf) + pallas::Scalar::from_uniform_bytes(&buf) } } diff --git a/halo2_gadgets/src/utilities/cond_swap.rs b/halo2_gadgets/src/utilities/cond_swap.rs index 9dc1afa3..d71b2599 100644 --- a/halo2_gadgets/src/utilities/cond_swap.rs +++ b/halo2_gadgets/src/utilities/cond_swap.rs @@ -1,16 +1,16 @@ //! Gadget and chip for a conditional swap utility. use super::{bool_check, ternary, UtilitiesInstructions}; +use ff::{Field, PrimeField}; use halo2_proofs::{ circuit::{AssignedCell, Chip, Layouter, Value}, plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, poly::Rotation, }; -use halo2curves::FieldExt; use std::marker::PhantomData; /// Instructions for a conditional swap gadget. -pub trait CondSwapInstructions: UtilitiesInstructions { +pub trait CondSwapInstructions: UtilitiesInstructions { #[allow(clippy::type_complexity)] /// Given an input pair (a,b) and a `swap` boolean flag, returns /// (b,a) if `swap` is set, else (a,b) if `swap` is not set. @@ -32,7 +32,7 @@ pub struct CondSwapChip { _marker: PhantomData, } -impl Chip for CondSwapChip { +impl Chip for CondSwapChip { type Config = CondSwapConfig; type Loaded = (); @@ -63,11 +63,11 @@ impl CondSwapConfig { } } -impl UtilitiesInstructions for CondSwapChip { +impl UtilitiesInstructions for CondSwapChip { type Var = AssignedCell; } -impl CondSwapInstructions for CondSwapChip { +impl CondSwapInstructions for CondSwapChip { #[allow(clippy::type_complexity)] fn swap( &self, @@ -122,7 +122,7 @@ impl CondSwapInstructions for CondSwapChip { } } -impl CondSwapChip { +impl CondSwapChip { /// Configures this chip for use in a circuit. /// /// # Side-effects @@ -195,27 +195,30 @@ impl CondSwapChip { mod tests { use super::super::UtilitiesInstructions; use super::{CondSwapChip, CondSwapConfig, CondSwapInstructions}; + use ff::PrimeField; use group::ff::Field; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, dev::MockProver, plonk::{Circuit, ConstraintSystem, Error}, }; - use halo2curves::{pasta::pallas::Base, FieldExt}; + use halo2curves::pasta::pallas::Base; use rand::rngs::OsRng; #[test] fn cond_swap() { #[derive(Default)] - struct MyCircuit { + struct MyCircuit { a: Value, b: Value, swap: Value, } - impl Circuit for MyCircuit { + impl Circuit for MyCircuit { type Config = CondSwapConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self::default() diff --git a/halo2_gadgets/src/utilities/decompose_running_sum.rs b/halo2_gadgets/src/utilities/decompose_running_sum.rs index ba98a047..041044ff 100644 --- a/halo2_gadgets/src/utilities/decompose_running_sum.rs +++ b/halo2_gadgets/src/utilities/decompose_running_sum.rs @@ -30,13 +30,12 @@ use halo2_proofs::{ }; use super::range_check; -use halo2curves::FieldExt; use std::marker::PhantomData; /// The running sum $[z_0, ..., z_W]$. If created in strict mode, $z_W = 0$. #[derive(Debug)] -pub struct RunningSum(Vec>); -impl std::ops::Deref for RunningSum { +pub struct RunningSum(Vec>); +impl std::ops::Deref for RunningSum { type Target = Vec>; fn deref(&self) -> &Vec> { @@ -46,15 +45,13 @@ impl std::ops::Deref for RunningSum { /// Configuration that provides methods for running sum decomposition. #[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct RunningSumConfig { +pub struct RunningSumConfig { q_range_check: Selector, z: Column, _marker: PhantomData, } -impl - RunningSumConfig -{ +impl RunningSumConfig { /// Returns the q_range_check selector of this [`RunningSumConfig`]. pub(crate) fn q_range_check(&self) -> Selector { self.q_range_check @@ -200,7 +197,7 @@ impl if strict { // Constrain the final running sum output to be zero. - region.constrain_constant(zs.last().unwrap().cell(), F::zero())?; + region.constrain_constant(zs.last().unwrap().cell(), F::ZERO)?; } Ok(RunningSum(zs)) @@ -216,7 +213,7 @@ mod tests { dev::{FailureLocation, MockProver, VerifyFailure}, plonk::{Any, Circuit, ConstraintSystem, Error}, }; - use halo2curves::{pasta::pallas, FieldExt}; + use halo2curves::pasta::pallas; use rand::rngs::OsRng; use crate::ecc::chip::{ @@ -228,7 +225,7 @@ mod tests { #[test] fn test_running_sum() { struct MyCircuit< - F: FieldExt + PrimeFieldBits, + F: PrimeFieldBits, const WORD_NUM_BITS: usize, const WINDOW_NUM_BITS: usize, const NUM_WINDOWS: usize, @@ -238,7 +235,7 @@ mod tests { } impl< - F: FieldExt + PrimeFieldBits, + F: PrimeFieldBits, const WORD_NUM_BITS: usize, const WINDOW_NUM_BITS: usize, const NUM_WINDOWS: usize, @@ -246,6 +243,8 @@ mod tests { { type Config = RunningSumConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self { @@ -358,14 +357,14 @@ mod tests { location: FailureLocation::OutsideRegion { row: 1 }, }, VerifyFailure::Permutation { - column: (Any::Advice, 0).into(), + column: (Any::advice(), 0).into(), location: FailureLocation::InRegion { region: (0, "decompose").into(), offset: 22, }, }, VerifyFailure::Permutation { - column: (Any::Advice, 0).into(), + column: (Any::advice(), 0).into(), location: FailureLocation::InRegion { region: (0, "decompose").into(), offset: 45, diff --git a/halo2_gadgets/src/utilities/lookup_range_check.rs b/halo2_gadgets/src/utilities/lookup_range_check.rs index f97654c3..22a8bf2b 100644 --- a/halo2_gadgets/src/utilities/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities/lookup_range_check.rs @@ -14,8 +14,8 @@ use super::*; /// The running sum $[z_0, ..., z_W]$. If created in strict mode, $z_W = 0$. #[derive(Debug)] -pub struct RunningSum(Vec>); -impl std::ops::Deref for RunningSum { +pub struct RunningSum(Vec>); +impl std::ops::Deref for RunningSum { type Target = Vec>; fn deref(&self) -> &Vec> { @@ -23,7 +23,7 @@ impl std::ops::Deref for RunningSum { } } -impl RangeConstrained> { +impl RangeConstrained> { /// Witnesses a subset of the bits in `value` and constrains them to be the correct /// number of bits. /// @@ -56,7 +56,7 @@ impl RangeConstrained> { /// Configuration that provides methods for a lookup range check. #[derive(Eq, PartialEq, Debug, Clone, Copy)] -pub struct LookupRangeCheckConfig { +pub struct LookupRangeCheckConfig { q_lookup: Selector, q_running: Selector, q_bitshift: Selector, @@ -65,7 +65,7 @@ pub struct LookupRangeCheckConfig _marker: PhantomData, } -impl LookupRangeCheckConfig { +impl LookupRangeCheckConfig { /// The `running_sum` advice column breaks the field element into `K`-bit /// words. It is used to construct the input expression to the lookup /// argument. @@ -118,7 +118,7 @@ impl LookupRangeCheckConfig // In the short range check, the word is directly witnessed. let short_lookup = { let short_word = z_cur; - let q_short = Expression::Constant(F::one()) - q_running; + let q_short = Expression::Constant(F::ONE) - q_running; q_short * short_word }; @@ -285,7 +285,7 @@ impl LookupRangeCheckConfig if strict { // Constrain the final `z` to be zero. - region.constrain_constant(zs.last().unwrap().cell(), F::zero())?; + region.constrain_constant(zs.last().unwrap().cell(), F::ZERO)?; } Ok(RunningSum(zs)) @@ -395,21 +395,23 @@ mod tests { dev::{FailureLocation, MockProver, VerifyFailure}, plonk::{Circuit, ConstraintSystem, Error}, }; - use halo2curves::{pasta::pallas, FieldExt}; + use halo2curves::pasta::pallas; use std::{convert::TryInto, marker::PhantomData}; #[test] fn lookup_range_check() { #[derive(Clone, Copy)] - struct MyCircuit { + struct MyCircuit { num_words: usize, _marker: PhantomData, } - impl Circuit for MyCircuit { + impl Circuit for MyCircuit { type Config = LookupRangeCheckConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { *self @@ -434,11 +436,11 @@ mod tests { // Lookup constraining element to be no longer than num_words * K bits. let elements_and_expected_final_zs = [ - (F::from((1 << (self.num_words * K)) - 1), F::zero(), true), // a word that is within self.num_words * K bits long - (F::from(1 << (self.num_words * K)), F::one(), false), // a word that is just over self.num_words * K bits long + (F::from((1 << (self.num_words * K)) - 1), F::ZERO, true), // a word that is within self.num_words * K bits long + (F::from(1 << (self.num_words * K)), F::ONE, false), // a word that is just over self.num_words * K bits long ]; - fn expected_zs( + fn expected_zs( element: F, num_words: usize, ) -> Vec { @@ -498,14 +500,16 @@ mod tests { #[test] fn short_range_check() { - struct MyCircuit { + struct MyCircuit { element: Value, num_bits: usize, } - impl Circuit for MyCircuit { + impl Circuit for MyCircuit { type Config = LookupRangeCheckConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { MyCircuit { @@ -582,13 +586,13 @@ mod tests { assert_eq!( prover.verify(), Err(vec![VerifyFailure::Lookup { - name: "lookup", + name: "lookup".to_string(), lookup_index: 0, location: FailureLocation::InRegion { region: (1, "Range check 6 bits").into(), offset: 1, }, - }]) + }]), ); } @@ -603,7 +607,7 @@ mod tests { prover.verify(), Err(vec![ VerifyFailure::Lookup { - name: "lookup", + name: "lookup".to_string(), lookup_index: 0, location: FailureLocation::InRegion { region: (1, "Range check 6 bits").into(), @@ -611,7 +615,7 @@ mod tests { }, }, VerifyFailure::Lookup { - name: "lookup", + name: "lookup".to_string(), lookup_index: 0, location: FailureLocation::InRegion { region: (1, "Range check 6 bits").into(), @@ -641,7 +645,7 @@ mod tests { assert_eq!( prover.verify(), Err(vec![VerifyFailure::Lookup { - name: "lookup", + name: "lookup".to_string(), lookup_index: 0, location: FailureLocation::InRegion { region: (1, "Range check 6 bits").into(), diff --git a/halo2_proofs/Cargo.toml b/halo2_proofs/Cargo.toml index 8b8dee7f..1d744357 100644 --- a/halo2_proofs/Cargo.toml +++ b/halo2_proofs/Cargo.toml @@ -27,6 +27,10 @@ rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] name = "arithmetic" harness = false +[[bench]] +name = "commit_zk" +harness = false + [[bench]] name = "hashtocurve" harness = false @@ -46,12 +50,14 @@ harness = false [dependencies] backtrace = { version = "0.3", optional = true } rayon = "1.5.1" -ff = "0.12" -group = "0.12" -halo2curves = { git = 'https://github.com/privacy-scaling-explorations/halo2curves', tag = "0.2.1" } +ff = "0.13" +group = "0.13" +halo2curves = { git = 'https://github.com/privacy-scaling-explorations/halo2curves', tag = "0.3.2" } rand_core = { version = "0.6", default-features = false } tracing = "0.1" blake2b_simd = "1" +sha3 = "0.9.1" +rand_chacha = "0.3" # Developer tooling dependencies plotters = { version = "0.3.0", optional = true } @@ -71,8 +77,10 @@ getrandom = { version = "0.2", features = ["js"] } default = ["batch"] dev-graph = ["plotters", "tabbycat"] gadget-traces = ["backtrace"] +thread-safe-region = [] sanity-checks = [] batch = ["rand_core/getrandom"] +circuit-params = [] [lib] bench = false @@ -80,3 +88,5 @@ bench = false [[example]] name = "circuit-layout" required-features = ["dev-graph"] + + diff --git a/halo2_proofs/benches/commit_zk.rs b/halo2_proofs/benches/commit_zk.rs new file mode 100644 index 00000000..f1d2f70a --- /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 bb6cfdad..62ed5a7f 100644 --- a/halo2_proofs/benches/dev_lookup.rs +++ b/halo2_proofs/benches/dev_lookup.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate criterion; -use halo2_proofs::arithmetic::FieldExt; +use ff::{Field, PrimeField}; use halo2_proofs::circuit::{Layouter, SimpleFloorPlanner, Value}; use halo2_proofs::dev::MockProver; use halo2_proofs::plonk::*; @@ -14,7 +14,7 @@ use criterion::{BenchmarkId, Criterion}; fn criterion_benchmark(c: &mut Criterion) { #[derive(Clone, Default)] - struct MyCircuit { + struct MyCircuit { _marker: PhantomData, } @@ -25,9 +25,11 @@ fn criterion_benchmark(c: &mut Criterion) { advice: Column, } - impl Circuit for MyCircuit { + impl Circuit for MyCircuit { type Config = MyConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self::default() @@ -42,7 +44,7 @@ fn criterion_benchmark(c: &mut Criterion) { meta.lookup("lookup", |meta| { let selector = meta.query_selector(config.selector); - let not_selector = Expression::Constant(F::one()) - selector.clone(); + let not_selector = Expression::Constant(F::ONE) - selector.clone(); let advice = meta.query_advice(config.advice, Rotation::cur()); vec![(selector * advice + not_selector, config.table)] }); diff --git a/halo2_proofs/benches/plonk.rs b/halo2_proofs/benches/plonk.rs index a6799614..9c9bd261 100644 --- a/halo2_proofs/benches/plonk.rs +++ b/halo2_proofs/benches/plonk.rs @@ -2,7 +2,6 @@ extern crate criterion; use group::ff::Field; -use halo2_proofs::arithmetic::FieldExt; use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; use halo2_proofs::plonk::*; use halo2_proofs::poly::{commitment::ParamsProver, Rotation}; @@ -43,7 +42,7 @@ fn criterion_benchmark(c: &mut Criterion) { sm: Column, } - trait StandardCs { + trait StandardCs { fn raw_multiply( &self, layouter: &mut impl Layouter, @@ -62,17 +61,17 @@ fn criterion_benchmark(c: &mut Criterion) { } #[derive(Clone)] - struct MyCircuit { + struct MyCircuit { a: Value, k: u32, } - struct StandardPlonk { + struct StandardPlonk { config: PlonkConfig, _marker: PhantomData, } - impl StandardPlonk { + impl StandardPlonk { fn new(config: PlonkConfig) -> Self { StandardPlonk { config, @@ -81,7 +80,7 @@ fn criterion_benchmark(c: &mut Criterion) { } } - impl StandardCs for StandardPlonk { + impl StandardCs for StandardPlonk { fn raw_multiply( &self, layouter: &mut impl Layouter, @@ -116,15 +115,10 @@ fn criterion_benchmark(c: &mut Criterion) { || 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()), - )?; + 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())) }, ) @@ -163,14 +157,14 @@ fn criterion_benchmark(c: &mut Criterion) { || 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", 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()), + || Value::known(FF::ZERO), )?; Ok((lhs.cell(), rhs.cell(), out.cell())) }, @@ -186,9 +180,11 @@ fn criterion_benchmark(c: &mut Criterion) { } } - impl Circuit for MyCircuit { + impl Circuit for MyCircuit { type Config = PlonkConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self { diff --git a/halo2_proofs/examples/circuit-layout.rs b/halo2_proofs/examples/circuit-layout.rs index beb99502..18de27a7 100644 --- a/halo2_proofs/examples/circuit-layout.rs +++ b/halo2_proofs/examples/circuit-layout.rs @@ -1,6 +1,5 @@ use ff::Field; use halo2_proofs::{ - arithmetic::FieldExt, circuit::{Cell, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, TableColumn}, poly::Rotation, @@ -28,7 +27,7 @@ struct PlonkConfig { sl: TableColumn, } -trait StandardCs { +trait StandardCs { fn raw_multiply(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> where F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; @@ -39,17 +38,17 @@ trait StandardCs { fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error>; } -struct MyCircuit { +struct MyCircuit { a: Value, lookup_table: Vec, } -struct StandardPlonk { +struct StandardPlonk { config: PlonkConfig, _marker: PhantomData, } -impl StandardPlonk { +impl StandardPlonk { fn new(config: PlonkConfig) -> Self { StandardPlonk { config, @@ -58,7 +57,7 @@ impl StandardPlonk { } } -impl StandardCs for StandardPlonk { +impl StandardCs for StandardPlonk { fn raw_multiply( &self, region: &mut Region, @@ -94,10 +93,10 @@ impl StandardCs for StandardPlonk { 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()))?; + 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())) } fn raw_add(&self, region: &mut Region, mut f: F) -> Result<(Cell, Cell, Cell), Error> @@ -131,10 +130,10 @@ impl StandardCs for StandardPlonk { 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()))?; + 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())) } fn copy(&self, region: &mut Region, left: Cell, right: Cell) -> Result<(), Error> { @@ -159,9 +158,11 @@ impl StandardCs for StandardPlonk { } } -impl Circuit for MyCircuit { +impl Circuit for MyCircuit { type Config = PlonkConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self { diff --git a/halo2_proofs/examples/serialization.rs b/halo2_proofs/examples/serialization.rs new file mode 100644 index 00000000..39b6b119 --- /dev/null +++ b/halo2_proofs/examples/serialization.rs @@ -0,0 +1,192 @@ +use std::{ + fs::File, + io::{BufReader, BufWriter, Write}, +}; + +use ff::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{ + create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, + ConstraintSystem, Error, Fixed, Instance, ProvingKey, + }, + poly::{ + kzg::{ + commitment::{KZGCommitmentScheme, ParamsKZG}, + multiopen::{ProverGWC, VerifierGWC}, + strategy::SingleStrategy, + }, + Rotation, + }, + transcript::{ + Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, + }, + SerdeFormat, +}; +use halo2curves::bn256::{Bn256, Fr, G1Affine}; +use rand_core::OsRng; + +#[derive(Clone, Copy)] +struct StandardPlonkConfig { + a: Column, + b: Column, + c: Column, + q_a: Column, + q_b: Column, + q_c: Column, + q_ab: Column, + constant: Column, + #[allow(dead_code)] + instance: Column, +} + +impl StandardPlonkConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [a, b, c] = [(); 3].map(|_| meta.advice_column()); + let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); + let instance = meta.instance_column(); + + [a, b, c].map(|column| meta.enable_equality(column)); + + meta.create_gate( + "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", + |meta| { + let [a, b, c] = [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); + let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] + .map(|column| meta.query_fixed(column, Rotation::cur())); + let instance = meta.query_instance(instance, Rotation::cur()); + Some( + q_a * a.clone() + + q_b * b.clone() + + q_c * c + + q_ab * a * b + + constant + + instance, + ) + }, + ); + + StandardPlonkConfig { + a, + b, + c, + q_a, + q_b, + q_c, + q_ab, + constant, + instance, + } + } +} + +#[derive(Clone, Default)] +struct StandardPlonk(Fr); + +impl Circuit for StandardPlonk { + type Config = StandardPlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + StandardPlonkConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "", + |mut region| { + region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; + region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fr::one()))?; + + region.assign_advice(|| "", config.a, 1, || Value::known(-Fr::from(5u64)))?; + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed(|| "", column, 1, || Value::known(Fr::from(idx as u64)))?; + } + + 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(()) + }, + ) + } +} + +fn main() { + let k = 4; + let circuit = StandardPlonk(Fr::random(OsRng)); + let params = ParamsKZG::::setup(k, OsRng); + let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail"); + let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail"); + + let f = File::create("serialization-test.pk").unwrap(); + let mut writer = BufWriter::new(f); + pk.write(&mut writer, SerdeFormat::RawBytes).unwrap(); + writer.flush().unwrap(); + + let f = File::open("serialization-test.pk").unwrap(); + let mut reader = BufReader::new(f); + #[allow(clippy::unit_arg)] + let pk = ProvingKey::::read::<_, StandardPlonk>( + &mut reader, + SerdeFormat::RawBytes, + #[cfg(feature = "circuit-params")] + circuit.params(), + ) + .unwrap(); + + std::fs::remove_file("serialization-test.pk").unwrap(); + + let instances: &[&[Fr]] = &[&[circuit.0]]; + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof::< + KZGCommitmentScheme, + ProverGWC<'_, Bn256>, + Challenge255, + _, + Blake2bWrite, G1Affine, Challenge255<_>>, + _, + >( + ¶ms, + &pk, + &[circuit], + &[instances], + OsRng, + &mut transcript, + ) + .expect("prover should not fail"); + let proof = transcript.finalize(); + + let strategy = SingleStrategy::new(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + assert!(verify_proof::< + KZGCommitmentScheme, + VerifierGWC<'_, Bn256>, + Challenge255, + Blake2bRead<&[u8], G1Affine, Challenge255>, + SingleStrategy<'_, Bn256>, + >( + ¶ms, + pk.get_vk(), + strategy, + &[instances], + &mut transcript + ) + .is_ok()); +} diff --git a/halo2_proofs/examples/shuffle.rs b/halo2_proofs/examples/shuffle.rs new file mode 100644 index 00000000..17bbb333 --- /dev/null +++ b/halo2_proofs/examples/shuffle.rs @@ -0,0 +1,357 @@ +use ff::{BatchInvert, FromUniformBytes}; +use halo2_proofs::{ + arithmetic::{CurveAffine, Field}, + circuit::{floor_planner::V1, Layouter, Value}, + dev::{metadata, FailureLocation, MockProver, VerifyFailure}, + halo2curves::pasta::EqAffine, + plonk::*, + poly::{ + commitment::ParamsProver, + ipa::{ + commitment::{IPACommitmentScheme, ParamsIPA}, + multiopen::{ProverIPA, VerifierIPA}, + strategy::AccumulatorStrategy, + }, + VerificationStrategy, + }, + transcript::{ + Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, + }, +}; +use rand_core::{OsRng, RngCore}; +use std::iter; + +fn rand_2d_array(rng: &mut R) -> [[F; H]; W] { + [(); W].map(|_| [(); H].map(|_| F::random(&mut *rng))) +} + +fn shuffled( + original: [[F; H]; W], + rng: &mut R, +) -> [[F; H]; W] { + let mut shuffled = original; + + for row in (1..H).rev() { + let rand_row = (rng.next_u32() as usize) % row; + for column in shuffled.iter_mut() { + column.swap(row, rand_row); + } + } + + shuffled +} + +#[derive(Clone)] +struct MyConfig { + q_shuffle: Selector, + q_first: Selector, + q_last: Selector, + original: [Column; W], + shuffled: [Column; W], + theta: Challenge, + gamma: Challenge, + z: Column, +} + +impl MyConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [q_shuffle, q_first, q_last] = [(); 3].map(|_| meta.selector()); + // First phase + let original = [(); W].map(|_| meta.advice_column_in(FirstPhase)); + let shuffled = [(); W].map(|_| meta.advice_column_in(FirstPhase)); + let [theta, gamma] = [(); 2].map(|_| meta.challenge_usable_after(FirstPhase)); + // Second phase + let z = meta.advice_column_in(SecondPhase); + + meta.create_gate("z should start with 1", |_| { + let one = Expression::Constant(F::ONE); + + vec![q_first.expr() * (one - z.cur())] + }); + + meta.create_gate("z should end with 1", |_| { + let one = Expression::Constant(F::ONE); + + vec![q_last.expr() * (one - z.cur())] + }); + + meta.create_gate("z should have valid transition", |_| { + let q_shuffle = q_shuffle.expr(); + let original = original.map(|advice| advice.cur()); + let shuffled = shuffled.map(|advice| advice.cur()); + let [theta, gamma] = [theta, gamma].map(|challenge| challenge.expr()); + + // Compress + let original = original + .iter() + .cloned() + .reduce(|acc, a| acc * theta.clone() + a) + .unwrap(); + let shuffled = shuffled + .iter() + .cloned() + .reduce(|acc, a| acc * theta.clone() + a) + .unwrap(); + + vec![q_shuffle * (z.cur() * (original + gamma.clone()) - z.next() * (shuffled + gamma))] + }); + + Self { + q_shuffle, + q_first, + q_last, + original, + shuffled, + theta, + gamma, + z, + } + } +} + +#[derive(Clone, Default)] +struct MyCircuit { + original: Value<[[F; H]; W]>, + shuffled: Value<[[F; H]; W]>, +} + +impl MyCircuit { + fn rand(rng: &mut R) -> Self { + let original = rand_2d_array::(rng); + let shuffled = shuffled(original, rng); + + Self { + original: Value::known(original), + shuffled: Value::known(shuffled), + } + } +} + +impl Circuit for MyCircuit { + type Config = MyConfig; + type FloorPlanner = V1; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + MyConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let theta = layouter.get_challenge(config.theta); + let gamma = layouter.get_challenge(config.gamma); + + layouter.assign_region( + || "Shuffle original into shuffled", + |mut region| { + // Keygen + config.q_first.enable(&mut region, 0)?; + config.q_last.enable(&mut region, H)?; + for offset in 0..H { + config.q_shuffle.enable(&mut region, offset)?; + } + + // First phase + for (idx, (&column, values)) in config + .original + .iter() + .zip(self.original.transpose_array().iter()) + .enumerate() + { + for (offset, &value) in values.transpose_array().iter().enumerate() { + region.assign_advice( + || format!("original[{}][{}]", idx, offset), + column, + offset, + || value, + )?; + } + } + for (idx, (&column, values)) in config + .shuffled + .iter() + .zip(self.shuffled.transpose_array().iter()) + .enumerate() + { + for (offset, &value) in values.transpose_array().iter().enumerate() { + region.assign_advice( + || format!("shuffled[{}][{}]", idx, offset), + column, + offset, + || value, + )?; + } + } + + // Second phase + let z = self.original.zip(self.shuffled).zip(theta).zip(gamma).map( + |(((original, shuffled), theta), gamma)| { + let mut product = vec![F::ZERO; H]; + for (idx, product) in product.iter_mut().enumerate() { + let mut compressed = F::ZERO; + for value in shuffled.iter() { + compressed *= theta; + compressed += value[idx]; + } + + *product = compressed + gamma + } + + product.iter_mut().batch_invert(); + + for (idx, product) in product.iter_mut().enumerate() { + let mut compressed = F::ZERO; + for value in original.iter() { + compressed *= theta; + compressed += value[idx]; + } + + *product *= compressed + gamma + } + + #[allow(clippy::let_and_return)] + let z = iter::once(F::ONE) + .chain(product) + .scan(F::ONE, |state, cur| { + *state *= &cur; + Some(*state) + }) + .collect::>(); + + #[cfg(feature = "sanity-checks")] + assert_eq!(F::ONE, *z.last().unwrap()); + + z + }, + ); + for (offset, value) in z.transpose_vec(H + 1).into_iter().enumerate() { + region.assign_advice( + || format!("z[{}]", offset), + config.z, + offset, + || value, + )?; + } + + Ok(()) + }, + ) + } +} + +fn test_mock_prover, const W: usize, const H: usize>( + k: u32, + circuit: MyCircuit, + expected: Result<(), Vec<(metadata::Constraint, FailureLocation)>>, +) { + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + match (prover.verify(), expected) { + (Ok(_), Ok(_)) => {} + (Err(err), Err(expected)) => { + assert_eq!( + err.into_iter() + .map(|failure| match failure { + VerifyFailure::ConstraintNotSatisfied { + constraint, + location, + .. + } => (constraint, location), + _ => panic!("MockProver::verify has result unmatching expected"), + }) + .collect::>(), + expected + ) + } + (_, _) => panic!("MockProver::verify has result unmatching expected"), + }; +} + +fn test_prover( + k: u32, + circuit: MyCircuit, + expected: bool, +) where + C::Scalar: FromUniformBytes<64>, +{ + let params = ParamsIPA::::new(k); + let vk = keygen_vk(¶ms, &circuit).unwrap(); + let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); + + let proof = { + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + + create_proof::, ProverIPA, _, _, _, _>( + ¶ms, + &pk, + &[circuit], + &[&[]], + OsRng, + &mut transcript, + ) + .expect("proof generation should not fail"); + + transcript.finalize() + }; + + let accepted = { + let strategy = AccumulatorStrategy::new(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + + verify_proof::, VerifierIPA, _, _, _>( + ¶ms, + pk.get_vk(), + strategy, + &[&[]], + &mut transcript, + ) + .map(|strategy| strategy.finalize()) + .unwrap_or_default() + }; + + assert_eq!(accepted, expected); +} + +fn main() { + const W: usize = 4; + const H: usize = 32; + const K: u32 = 8; + + let circuit = &MyCircuit::<_, W, H>::rand(&mut OsRng); + + { + test_mock_prover(K, circuit.clone(), Ok(())); + test_prover::(K, circuit.clone(), true); + } + + #[cfg(not(feature = "sanity-checks"))] + { + use std::ops::IndexMut; + + let mut circuit = circuit.clone(); + circuit.shuffled = circuit.shuffled.map(|mut shuffled| { + shuffled.index_mut(0).swap(0, 1); + shuffled + }); + + test_mock_prover( + K, + circuit.clone(), + Err(vec![( + ((1, "z should end with 1").into(), 0, "").into(), + FailureLocation::InRegion { + region: (0, "Shuffle original into shuffled").into(), + offset: 32, + }, + )]), + ); + test_prover::(K, circuit, false); + } +} diff --git a/halo2_proofs/examples/simple-example.rs b/halo2_proofs/examples/simple-example.rs index c5e7a9f2..242257a6 100644 --- a/halo2_proofs/examples/simple-example.rs +++ b/halo2_proofs/examples/simple-example.rs @@ -1,14 +1,14 @@ use std::marker::PhantomData; use halo2_proofs::{ - arithmetic::FieldExt, + arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector}, poly::Rotation, }; // ANCHOR: instructions -trait NumericInstructions: Chip { +trait NumericInstructions: Chip { /// Variable representing a number. type Num; @@ -39,7 +39,7 @@ trait NumericInstructions: Chip { // ANCHOR: chip /// The chip that will implement our instructions! Chips store their own /// config, as well as type markers if necessary. -struct FieldChip { +struct FieldChip { config: FieldConfig, _marker: PhantomData, } @@ -65,7 +65,7 @@ struct FieldConfig { s_mul: Selector, } -impl FieldChip { +impl FieldChip { fn construct(config: >::Config) -> Self { Self { config, @@ -126,7 +126,7 @@ impl FieldChip { // ANCHOR_END: chip-config // ANCHOR: chip-impl -impl Chip for FieldChip { +impl Chip for FieldChip { type Config = FieldConfig; type Loaded = (); @@ -143,9 +143,9 @@ impl Chip for FieldChip { // ANCHOR: instructions-impl /// A variable representing a number. #[derive(Clone)] -struct Number(AssignedCell); +struct Number(AssignedCell); -impl NumericInstructions for FieldChip { +impl NumericInstructions for FieldChip { type Num = Number; fn load_private( @@ -238,16 +238,18 @@ impl NumericInstructions for FieldChip { /// 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 { +struct MyCircuit { constant: F, a: Value, b: Value, } -impl Circuit for MyCircuit { +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; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self::default() diff --git a/halo2_proofs/examples/two-chip.rs b/halo2_proofs/examples/two-chip.rs index 61d40f93..336f9c49 100644 --- a/halo2_proofs/examples/two-chip.rs +++ b/halo2_proofs/examples/two-chip.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use halo2_proofs::{ - arithmetic::FieldExt, + arithmetic::Field, circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, poly::Rotation, @@ -10,9 +10,9 @@ use halo2_proofs::{ // ANCHOR: field-instructions /// A variable representing a number. #[derive(Clone)] -struct Number(AssignedCell); +struct Number(AssignedCell); -trait FieldInstructions: AddInstructions + MulInstructions { +trait FieldInstructions: AddInstructions + MulInstructions { /// Variable representing a number. type Num; @@ -43,7 +43,7 @@ trait FieldInstructions: AddInstructions + MulInstructions { // ANCHOR_END: field-instructions // ANCHOR: add-instructions -trait AddInstructions: Chip { +trait AddInstructions: Chip { /// Variable representing a number. type Num; @@ -58,7 +58,7 @@ trait AddInstructions: Chip { // ANCHOR_END: add-instructions // ANCHOR: mul-instructions -trait MulInstructions: Chip { +trait MulInstructions: Chip { /// Variable representing a number. type Num; @@ -108,28 +108,28 @@ struct MulConfig { // ANCHOR: field-chip /// The top-level chip that will implement the `FieldInstructions`. -struct FieldChip { +struct FieldChip { config: FieldConfig, _marker: PhantomData, } // ANCHOR_END: field-chip // ANCHOR: add-chip -struct AddChip { +struct AddChip { config: AddConfig, _marker: PhantomData, } // ANCHOR END: add-chip // ANCHOR: mul-chip -struct MulChip { +struct MulChip { config: MulConfig, _marker: PhantomData, } // ANCHOR_END: mul-chip // ANCHOR: add-chip-trait-impl -impl Chip for AddChip { +impl Chip for AddChip { type Config = AddConfig; type Loaded = (); @@ -144,7 +144,7 @@ impl Chip for AddChip { // ANCHOR END: add-chip-trait-impl // ANCHOR: add-chip-impl -impl AddChip { +impl AddChip { fn construct(config: >::Config, _loaded: >::Loaded) -> Self { Self { config, @@ -174,7 +174,7 @@ impl AddChip { // ANCHOR END: add-chip-impl // ANCHOR: add-instructions-impl -impl AddInstructions for FieldChip { +impl AddInstructions for FieldChip { type Num = Number; fn add( &self, @@ -189,7 +189,7 @@ impl AddInstructions for FieldChip { } } -impl AddInstructions for AddChip { +impl AddInstructions for AddChip { type Num = Number; fn add( @@ -231,7 +231,7 @@ impl AddInstructions for AddChip { // ANCHOR END: add-instructions-impl // ANCHOR: mul-chip-trait-impl -impl Chip for MulChip { +impl Chip for MulChip { type Config = MulConfig; type Loaded = (); @@ -246,7 +246,7 @@ impl Chip for MulChip { // ANCHOR END: mul-chip-trait-impl // ANCHOR: mul-chip-impl -impl MulChip { +impl MulChip { fn construct(config: >::Config, _loaded: >::Loaded) -> Self { Self { config, @@ -296,7 +296,7 @@ impl MulChip { // ANCHOR_END: mul-chip-impl // ANCHOR: mul-instructions-impl -impl MulInstructions for FieldChip { +impl MulInstructions for FieldChip { type Num = Number; fn mul( &self, @@ -310,7 +310,7 @@ impl MulInstructions for FieldChip { } } -impl MulInstructions for MulChip { +impl MulInstructions for MulChip { type Num = Number; fn mul( @@ -352,7 +352,7 @@ impl MulInstructions for MulChip { // ANCHOR END: mul-instructions-impl // ANCHOR: field-chip-trait-impl -impl Chip for FieldChip { +impl Chip for FieldChip { type Config = FieldConfig; type Loaded = (); @@ -367,7 +367,7 @@ impl Chip for FieldChip { // ANCHOR_END: field-chip-trait-impl // ANCHOR: field-chip-impl -impl FieldChip { +impl FieldChip { fn construct(config: >::Config, _loaded: >::Loaded) -> Self { Self { config, @@ -396,7 +396,7 @@ impl FieldChip { // ANCHOR_END: field-chip-impl // ANCHOR: field-instructions-impl -impl FieldInstructions for FieldChip { +impl FieldInstructions for FieldChip { type Num = Number; fn load_private( @@ -448,16 +448,18 @@ impl FieldInstructions for FieldChip { /// 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 { +struct MyCircuit { a: Value, b: Value, c: Value, } -impl Circuit for MyCircuit { +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; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self::default() @@ -496,7 +498,6 @@ impl Circuit for MyCircuit { #[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; diff --git a/halo2_proofs/examples/vector-mul.rs b/halo2_proofs/examples/vector-mul.rs new file mode 100644 index 00000000..23dc6d27 --- /dev/null +++ b/halo2_proofs/examples/vector-mul.rs @@ -0,0 +1,350 @@ +use std::marker::PhantomData; + +use halo2_proofs::{ + arithmetic::Field, + circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, 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, Error>; + + /// Returns `c = a * b`. The caller is responsible for ensuring that `a.len() == b.len()`. + fn mul( + &self, + layouter: impl Layouter, + a: &[Self::Num], + b: &[Self::Num], + ) -> Result, Error>; + + /// 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; 3], + + /// 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; 3], + instance: Column, + ) -> >::Config { + meta.enable_equality(instance); + 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 | a2 | s_mul | + // |-----|-----|-----|-------| + // | lhs | rhs | out | s_mul | + // + // 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[2], Rotation::cur()); + 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, Debug)] +struct Number(AssignedCell); + +impl NumericInstructions for FieldChip { + type Num = Number; + + fn load_private( + &self, + mut layouter: impl Layouter, + values: &[Value], + ) -> Result, Error> { + let config = self.config(); + + layouter.assign_region( + || "load private", + |mut region| { + values + .iter() + .enumerate() + .map(|(i, value)| { + region + .assign_advice(|| "private input", config.advice[0], i, || *value) + .map(Number) + }) + .collect() + }, + ) + } + + fn mul( + &self, + mut layouter: impl Layouter, + a: &[Self::Num], + b: &[Self::Num], + ) -> Result, Error> { + let config = self.config(); + assert_eq!(a.len(), b.len()); + + #[cfg(feature = "thread-safe-region")] + { + use rayon::prelude::{ + IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator, + }; + layouter.assign_region( + || "mul", + |region: Region<'_, F>| { + let thread_safe_region = std::sync::Mutex::new(region); + a.par_iter() + .zip(b.par_iter()) + .enumerate() + .map(|(i, (a, b))| { + let mut region = thread_safe_region.lock().unwrap(); + + config.s_mul.enable(&mut region, i)?; + + a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; + b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; + + 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[2], i, || value) + .map(Number) + }) + .collect() + }, + ) + } + + #[cfg(not(feature = "thread-safe-region"))] + layouter.assign_region( + || "mul", + |mut region: Region<'_, F>| { + a.iter() + .zip(b.iter()) + .enumerate() + .map(|(i, (a, b))| { + config.s_mul.enable(&mut region, i)?; + + a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?; + b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?; + + 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[2], i, || value) + .map(Number) + }) + .collect() + }, + ) + } + + 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) + } +} +// 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 { + a: Vec>, + b: Vec>, +} + +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; + #[cfg(feature = "circuit-params")] + type Params = (); + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + // We create the three advice columns that FieldChip uses for I/O. + let advice = [ + meta.advice_column(), + 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 ab = field_chip.mul(layouter.namespace(|| "a * b"), &a, &b)?; + + for (i, c) in ab.iter().enumerate() { + // Expose the result as a public input to the circuit. + field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?; + } + Ok(()) + } +} +// ANCHOR_END: circuit + +fn main() { + use halo2_proofs::dev::MockProver; + use halo2curves::pasta::Fp; + + const N: usize = 20000; + // 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 = 16; + + // Prepare the private and public inputs to the circuit! + let a = [Fp::from(2); N]; + let b = [Fp::from(3); N]; + let c: Vec = a.iter().zip(b).map(|(&a, b)| a * b).collect(); + + // Instantiate the circuit with the private inputs. + let circuit = MyCircuit { + a: a.iter().map(|&x| Value::known(x)).collect(), + b: b.iter().map(|&x| Value::known(x)).collect(), + }; + + // 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 = c; + + let start = std::time::Instant::now(); + // 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(())); + println!("positive test took {:?}", start.elapsed()); + + // If we try some other public input, the proof will fail! + let start = std::time::Instant::now(); + public_inputs[0] += Fp::one(); + let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); + assert!(prover.verify().is_err()); + println!("negative test took {:?}", start.elapsed()); + // ANCHOR_END: test-circuit +} diff --git a/halo2_proofs/src/arithmetic.rs b/halo2_proofs/src/arithmetic.rs index 69b63502..a53b541b 100644 --- a/halo2_proofs/src/arithmetic.rs +++ b/halo2_proofs/src/arithmetic.rs @@ -5,10 +5,25 @@ use super::multicore; pub use ff::Field; use group::{ ff::{BatchInvert, PrimeField}, - Curve, Group as _, + Curve, Group, GroupOpsOwned, ScalarMulOwned, }; -pub use halo2curves::{CurveAffine, CurveExt, FieldExt, Group}; +pub use halo2curves::{CurveAffine, CurveExt}; + +/// This represents an element of a group with basic operations that can be +/// performed. This allows an FFT implementation (for example) to operate +/// generically over either a field or elliptic curve group. +pub trait FftGroup: + Copy + Send + Sync + 'static + GroupOpsOwned + ScalarMulOwned +{ +} + +impl FftGroup for T +where + Scalar: Field, + T: Copy + Send + Sync + 'static + GroupOpsOwned + ScalarMulOwned, +{ +} fn multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: &mut C::Curve) { let coeffs: Vec<_> = coeffs.iter().map(|a| a.to_repr()).collect(); @@ -168,7 +183,7 @@ pub fn best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Cu /// by $n$. /// /// This will use multithreading if beneficial. -pub fn best_fft(a: &mut [G], omega: G::Scalar, log_n: u32) { +pub fn best_fft>(a: &mut [G], omega: Scalar, log_n: u32) { fn bitreverse(mut n: usize, l: usize) -> usize { let mut r = 0; for _ in 0..l { @@ -192,9 +207,9 @@ pub fn best_fft(a: &mut [G], omega: G::Scalar, log_n: u32) { // precompute twiddle factors let twiddles: Vec<_> = (0..(n / 2) as usize) - .scan(G::Scalar::one(), |w, _| { + .scan(Scalar::ONE, |w, _| { let tw = *w; - w.group_scale(&omega); + *w *= ω Some(tw) }) .collect(); @@ -211,18 +226,18 @@ pub fn best_fft(a: &mut [G], omega: G::Scalar, log_n: u32) { let (b, right) = right.split_at_mut(1); let t = b[0]; b[0] = a[0]; - a[0].group_add(&t); - b[0].group_sub(&t); + a[0] += &t; + b[0] -= &t; left.iter_mut() .zip(right.iter_mut()) .enumerate() .for_each(|(i, (a, b))| { let mut t = *b; - t.group_scale(&twiddles[(i + 1) * twiddle_chunk]); + t *= &twiddles[(i + 1) * twiddle_chunk]; *b = *a; - a.group_add(&t); - b.group_sub(&t); + *a += &t; + *b -= &t; }); }); chunk *= 2; @@ -234,17 +249,17 @@ pub fn best_fft(a: &mut [G], omega: G::Scalar, log_n: u32) { } /// This perform recursive butterfly arithmetic -pub fn recursive_butterfly_arithmetic( +pub fn recursive_butterfly_arithmetic>( a: &mut [G], n: usize, twiddle_chunk: usize, - twiddles: &[G::Scalar], + twiddles: &[Scalar], ) { if n == 2 { let t = a[1]; a[1] = a[0]; - a[0].group_add(&t); - a[1].group_sub(&t); + a[0] += &t; + a[1] -= &t; } else { let (left, right) = a.split_at_mut(n / 2); rayon::join( @@ -257,18 +272,18 @@ pub fn recursive_butterfly_arithmetic( let (b, right) = right.split_at_mut(1); let t = b[0]; b[0] = a[0]; - a[0].group_add(&t); - b[0].group_sub(&t); + a[0] += &t; + b[0] -= &t; left.iter_mut() .zip(right.iter_mut()) .enumerate() .for_each(|(i, (a, b))| { let mut t = *b; - t.group_scale(&twiddles[(i + 1) * twiddle_chunk]); + t *= &twiddles[(i + 1) * twiddle_chunk]; *b = *a; - a.group_add(&t); - b.group_sub(&t); + *a += &t; + *b -= &t; }); } } @@ -305,7 +320,7 @@ pub fn eval_polynomial(poly: &[F], point: F) -> F { fn evaluate(poly: &[F], point: F) -> F { poly.iter() .rev() - .fold(F::zero(), |acc, coeff| acc * point + coeff) + .fold(F::ZERO, |acc, coeff| acc * point + coeff) } let n = poly.len(); let num_threads = multicore::current_num_threads(); @@ -313,7 +328,7 @@ pub fn eval_polynomial(poly: &[F], point: F) -> F { evaluate(poly, point) } else { let chunk_size = (n + num_threads - 1) / num_threads; - let mut parts = vec![F::zero(); num_threads]; + let mut parts = vec![F::ZERO; num_threads]; multicore::scope(|scope| { for (chunk_idx, (out, poly)) in parts.chunks_mut(1).zip(poly.chunks(chunk_size)).enumerate() @@ -324,7 +339,7 @@ pub fn eval_polynomial(poly: &[F], point: F) -> F { }); } }); - parts.iter().fold(F::zero(), |acc, coeff| acc + coeff) + parts.iter().fold(F::ZERO, |acc, coeff| acc + coeff) } } @@ -335,7 +350,7 @@ pub fn compute_inner_product(a: &[F], b: &[F]) -> F { // TODO: parallelize? assert_eq!(a.len(), b.len()); - let mut acc = F::zero(); + let mut acc = F::ZERO; for (a, b) in a.iter().zip(b.iter()) { acc += (*a) * (*b); } @@ -352,9 +367,9 @@ where b = -b; let a = a.into_iter(); - let mut q = vec![F::zero(); a.len() - 1]; + let mut q = vec![F::ZERO; a.len() - 1]; - let mut tmp = F::zero(); + let mut tmp = F::ZERO; for (q, r) in q.iter_mut().rev().zip(a.rev()) { let mut lead_coeff = *r; lead_coeff.sub_assign(&tmp); @@ -373,7 +388,7 @@ pub fn parallelize(v: &mu let num_threads = multicore::current_num_threads(); let mut chunk = (n as usize) / num_threads; if chunk < num_threads { - chunk = n as usize; + chunk = 1; } multicore::scope(|scope| { @@ -402,7 +417,7 @@ fn log2_floor(num: usize) -> u32 { /// Returns coefficients of an n - 1 degree polynomial given a set of n points /// and their evaluations. This function will panic if two values in `points` /// are the same. -pub fn lagrange_interpolate(points: &[F], evals: &[F]) -> Vec { +pub fn lagrange_interpolate(points: &[F], evals: &[F]) -> Vec { assert_eq!(points.len(), evals.len()); if points.len() == 1 { // Constant polynomial @@ -424,11 +439,11 @@ pub fn lagrange_interpolate(points: &[F], evals: &[F]) -> Vec { // Compute (x_j - x_k)^(-1) for each j != i denoms.iter_mut().flat_map(|v| v.iter_mut()).batch_invert(); - let mut final_poly = vec![F::zero(); points.len()]; + let mut final_poly = vec![F::ZERO; points.len()]; for (j, (denoms, eval)) in denoms.into_iter().zip(evals.iter()).enumerate() { let mut tmp: Vec = Vec::with_capacity(points.len()); let mut product = Vec::with_capacity(points.len() - 1); - tmp.push(F::one()); + tmp.push(F::ONE); for (x_k, denom) in points .iter() .enumerate() @@ -436,11 +451,11 @@ pub fn lagrange_interpolate(points: &[F], evals: &[F]) -> Vec { .map(|a| a.1) .zip(denoms.into_iter()) { - product.resize(tmp.len() + 1, F::zero()); + product.resize(tmp.len() + 1, F::ZERO); for ((a, b), product) in tmp .iter() - .chain(std::iter::once(&F::zero())) - .zip(std::iter::once(&F::zero()).chain(tmp.iter())) + .chain(std::iter::once(&F::ZERO)) + .zip(std::iter::once(&F::ZERO).chain(tmp.iter())) .zip(product.iter_mut()) { *product = *a * (-denom * x_k) + *b * denom; @@ -457,9 +472,9 @@ pub fn lagrange_interpolate(points: &[F], evals: &[F]) -> Vec { } } -pub(crate) fn evaluate_vanishing_polynomial(roots: &[F], z: F) -> F { - fn evaluate(roots: &[F], z: F) -> F { - roots.iter().fold(F::one(), |acc, point| (z - point) * acc) +pub(crate) fn evaluate_vanishing_polynomial(roots: &[F], z: F) -> F { + fn evaluate(roots: &[F], z: F) -> F { + roots.iter().fold(F::ONE, |acc, point| (z - point) * acc) } let n = roots.len(); let num_threads = multicore::current_num_threads(); @@ -467,18 +482,18 @@ pub(crate) fn evaluate_vanishing_polynomial(roots: &[F], z: F) -> F evaluate(roots, z) } else { let chunk_size = (n + num_threads - 1) / num_threads; - let mut parts = vec![F::one(); num_threads]; + let mut parts = vec![F::ONE; num_threads]; multicore::scope(|scope| { for (out, roots) in parts.chunks_mut(1).zip(roots.chunks(chunk_size)) { scope.spawn(move |_| out[0] = evaluate(roots, z)); } }); - parts.iter().fold(F::one(), |acc, part| acc * part) + parts.iter().fold(F::ONE, |acc, part| acc * part) } } -pub(crate) fn powers(base: F) -> impl Iterator { - std::iter::successors(Some(F::one()), move |power| Some(base * power)) +pub(crate) fn powers(base: F) -> impl Iterator { + std::iter::successors(Some(F::ONE), move |power| Some(base * power)) } #[cfg(test)] diff --git a/halo2_proofs/src/circuit.rs b/halo2_proofs/src/circuit.rs index 9451735d..f15205e4 100644 --- a/halo2_proofs/src/circuit.rs +++ b/halo2_proofs/src/circuit.rs @@ -4,9 +4,8 @@ use std::{convert::TryInto, fmt, marker::PhantomData}; use ff::Field; -use crate::{ - arithmetic::FieldExt, - plonk::{Advice, Any, Assigned, Column, Error, Fixed, Instance, Selector, TableColumn}, +use crate::plonk::{ + Advice, Any, Assigned, Challenge, Column, Error, Fixed, Instance, Selector, TableColumn, }; mod value; @@ -25,7 +24,7 @@ pub mod layouter; /// The chip also loads any fixed configuration needed at synthesis time /// using its own implementation of `load`, and stores it in [`Chip::Loaded`]. /// This can be accessed via [`Chip::loaded`]. -pub trait Chip: Sized { +pub trait Chip: Sized { /// A type that holds the configuration for this chip, and any other state it may need /// during circuit synthesis, that can be derived during [`Circuit::configure`]. /// @@ -204,6 +203,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. @@ -441,6 +454,11 @@ pub trait Layouter { row: usize, ) -> Result<(), Error>; + /// Queries the value of the given challenge. + /// + /// Returns `Value::unknown()` if the current synthesis phase is before the challenge can be queried. + fn get_challenge(&self, challenge: Challenge) -> Value; + /// Gets the "root" of this assignment, bypassing the namespacing. /// /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. @@ -506,6 +524,10 @@ impl<'a, F: Field, L: Layouter + 'a> Layouter for NamespacedLayouter<'a, F self.0.constrain_instance(cell, column, row) } + fn get_challenge(&self, challenge: Challenge) -> Value { + self.0.get_challenge(challenge) + } + fn get_root(&mut self) -> &mut Self::Root { self.0.get_root() } diff --git a/halo2_proofs/src/circuit/floor_planner/single_pass.rs b/halo2_proofs/src/circuit/floor_planner/single_pass.rs index 33a66052..54268006 100644 --- a/halo2_proofs/src/circuit/floor_planner/single_pass.rs +++ b/halo2_proofs/src/circuit/floor_planner/single_pass.rs @@ -7,12 +7,12 @@ use ff::Field; use crate::{ circuit::{ - layouter::{RegionColumn, RegionLayouter, RegionShape, TableLayouter}, + layouter::{RegionColumn, RegionLayouter, RegionShape, SyncDeps, TableLayouter}, Cell, Layouter, Region, RegionIndex, RegionStart, Table, Value, }, plonk::{ - Advice, Any, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, Instance, - Selector, TableColumn, + Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, Error, Fixed, FloorPlanner, + Instance, Selector, TableColumn, }, }; @@ -25,7 +25,7 @@ use crate::{ pub struct SimpleFloorPlanner; impl FloorPlanner for SimpleFloorPlanner { - fn synthesize, C: Circuit>( + fn synthesize + SyncDeps, C: Circuit>( cs: &mut CS, circuit: &C, config: C::Config, @@ -73,7 +73,9 @@ impl<'a, F: Field, CS: Assignment> SingleChipLayouter<'a, F, CS> { } } -impl<'a, F: Field, CS: Assignment + 'a> Layouter for SingleChipLayouter<'a, F, CS> { +impl<'a, F: Field, CS: Assignment + 'a + SyncDeps> Layouter + for SingleChipLayouter<'a, F, CS> +{ type Root = Self; fn assign_region(&mut self, name: N, mut assignment: A) -> Result @@ -214,6 +216,10 @@ impl<'a, F: Field, CS: Assignment + 'a> Layouter for SingleChipLayouter<'a ) } + fn get_challenge(&self, challenge: Challenge) -> Value { + self.cs.get_challenge(challenge) + } + fn get_root(&mut self) -> &mut Self::Root { self } @@ -259,7 +265,12 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> SingleChipLayouterRegion<'r, 'a, } } -impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter +impl<'r, 'a, F: Field, CS: Assignment + 'a + SyncDeps> SyncDeps + for SingleChipLayouterRegion<'r, 'a, F, CS> +{ +} + +impl<'r, 'a, F: Field, CS: Assignment + 'a + SyncDeps> RegionLayouter for SingleChipLayouterRegion<'r, 'a, F, CS> { fn enable_selector<'v>( @@ -275,6 +286,14 @@ 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<'v>( &'v mut self, annotation: &'v (dyn Fn() -> String + 'v), @@ -467,6 +486,8 @@ mod tests { impl Circuit for MyCircuit { type Config = Column; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { MyCircuit {} diff --git a/halo2_proofs/src/circuit/floor_planner/v1.rs b/halo2_proofs/src/circuit/floor_planner/v1.rs index 333d4627..3df8c181 100644 --- a/halo2_proofs/src/circuit/floor_planner/v1.rs +++ b/halo2_proofs/src/circuit/floor_planner/v1.rs @@ -5,12 +5,12 @@ use ff::Field; use crate::{ circuit::{ floor_planner::single_pass::SimpleTableLayouter, - layouter::{RegionColumn, RegionLayouter, RegionShape, TableLayouter}, + layouter::{RegionColumn, RegionLayouter, RegionShape, SyncDeps, TableLayouter}, Cell, Layouter, Region, RegionIndex, RegionStart, Table, Value, }, plonk::{ - Advice, Any, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, Instance, - Selector, TableColumn, + Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, Error, Fixed, FloorPlanner, + Instance, Selector, TableColumn, }, }; @@ -43,7 +43,7 @@ impl<'a, F: Field, CS: Assignment + 'a> fmt::Debug for V1Plan<'a, F, CS> { } } -impl<'a, F: Field, CS: Assignment> V1Plan<'a, F, CS> { +impl<'a, F: Field, CS: Assignment + SyncDeps> V1Plan<'a, F, CS> { /// Creates a new v1 layouter. pub fn new(cs: &'a mut CS) -> Result { let ret = V1Plan { @@ -57,7 +57,7 @@ impl<'a, F: Field, CS: Assignment> V1Plan<'a, F, CS> { } impl FloorPlanner for V1 { - fn synthesize, C: Circuit>( + fn synthesize + SyncDeps, C: Circuit>( cs: &mut CS, circuit: &C, config: C::Config, @@ -160,7 +160,7 @@ impl<'p, 'a, F: Field, CS: Assignment + 'a> V1Pass<'p, 'a, F, CS> { } } -impl<'p, 'a, F: Field, CS: Assignment + 'a> Layouter for V1Pass<'p, 'a, F, CS> { +impl<'p, 'a, F: Field, CS: Assignment + SyncDeps> Layouter for V1Pass<'p, 'a, F, CS> { type Root = Self; fn assign_region(&mut self, name: N, assignment: A) -> Result @@ -199,6 +199,13 @@ impl<'p, 'a, F: Field, CS: Assignment + 'a> Layouter for V1Pass<'p, 'a, F, } } + fn get_challenge(&self, challenge: Challenge) -> Value { + match &self.0 { + Pass::Measurement(_) => Value::unknown(), + Pass::Assignment(pass) => pass.plan.cs.get_challenge(challenge), + } + } + fn get_root(&mut self) -> &mut Self::Root { self } @@ -257,7 +264,7 @@ pub struct AssignmentPass<'p, 'a, F: Field, CS: Assignment + 'a> { region_index: usize, } -impl<'p, 'a, F: Field, CS: Assignment + 'a> AssignmentPass<'p, 'a, F, CS> { +impl<'p, 'a, F: Field, CS: Assignment + SyncDeps> AssignmentPass<'p, 'a, F, CS> { fn new(plan: &'p mut V1Plan<'a, F, CS>) -> Self { AssignmentPass { plan, @@ -377,7 +384,9 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> V1Region<'r, 'a, F, CS> { } } -impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r, 'a, F, CS> { +impl<'r, 'a, F: Field, CS: Assignment + SyncDeps> SyncDeps for V1Region<'r, 'a, F, CS> {} + +impl<'r, 'a, F: Field, CS: Assignment + SyncDeps> RegionLayouter for V1Region<'r, 'a, F, CS> { fn enable_selector<'v>( &'v mut self, annotation: &'v (dyn Fn() -> String + 'v), @@ -474,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, @@ -502,6 +519,8 @@ mod tests { impl Circuit for MyCircuit { type Config = Column; type FloorPlanner = super::V1; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { MyCircuit {} diff --git a/halo2_proofs/src/circuit/floor_planner/v1/strategy.rs b/halo2_proofs/src/circuit/floor_planner/v1/strategy.rs index 478fe8ee..f9acd0f5 100644 --- a/halo2_proofs/src/circuit/floor_planner/v1/strategy.rs +++ b/halo2_proofs/src/circuit/floor_planner/v1/strategy.rs @@ -205,7 +205,7 @@ pub fn slot_in_biggest_advice_first( .columns() .iter() .filter(|c| match c { - RegionColumn::Column(c) => matches!(c.column_type(), Any::Advice), + RegionColumn::Column(c) => matches!(c.column_type(), Any::Advice(_)), _ => false, }) .count(); @@ -231,7 +231,7 @@ fn test_slot_in() { let regions = vec![ RegionShape { region_index: 0.into(), - columns: vec![Column::new(0, Any::Advice), Column::new(1, Any::Advice)] + columns: vec![Column::new(0, Any::advice()), Column::new(1, Any::advice())] .into_iter() .map(|a| a.into()) .collect(), @@ -239,7 +239,7 @@ fn test_slot_in() { }, RegionShape { region_index: 1.into(), - columns: vec![Column::new(2, Any::Advice)] + columns: vec![Column::new(2, Any::advice())] .into_iter() .map(|a| a.into()) .collect(), @@ -247,7 +247,7 @@ fn test_slot_in() { }, RegionShape { region_index: 2.into(), - columns: vec![Column::new(2, Any::Advice), Column::new(0, Any::Advice)] + columns: vec![Column::new(2, Any::advice()), Column::new(0, Any::advice())] .into_iter() .map(|a| a.into()) .collect(), diff --git a/halo2_proofs/src/circuit/layouter.rs b/halo2_proofs/src/circuit/layouter.rs index 9436011f..e9eac92a 100644 --- a/halo2_proofs/src/circuit/layouter.rs +++ b/halo2_proofs/src/circuit/layouter.rs @@ -9,12 +9,20 @@ use ff::Field; use super::{Cell, RegionIndex, Value}; use crate::plonk::{Advice, Any, Assigned, Column, Error, Fixed, Instance, Selector, TableColumn}; +/// Intermediate trait requirements for [`RegionLayouter`] when thread-safe regions are enabled. +#[cfg(feature = "thread-safe-region")] +pub trait SyncDeps: Send + Sync {} + +/// Intermediate trait requirements for [`RegionLayouter`]. +#[cfg(not(feature = "thread-safe-region"))] +pub trait SyncDeps {} + /// Helper trait for implementing a custom [`Layouter`]. /// /// This trait is used for implementing region assignments: /// /// ```ignore -/// impl<'a, F: FieldExt, C: Chip, CS: Assignment + 'a> Layouter for MyLayouter<'a, C, CS> { +/// impl<'a, F: Field, C: Chip, CS: Assignment + 'a> Layouter for MyLayouter<'a, C, CS> { /// fn assign_region( /// &mut self, /// assignment: impl FnOnce(Region<'_, F, C>) -> Result<(), Error>, @@ -39,7 +47,7 @@ use crate::plonk::{Advice, Any, Assigned, Column, Error, Fixed, Instance, Select /// `Chip::Config`). /// /// [`Layouter`]: super::Layouter -pub trait RegionLayouter: fmt::Debug { +pub trait RegionLayouter: fmt::Debug + SyncDeps { /// Enables a selector at the given offset. fn enable_selector<'v>( &'v mut self, @@ -48,6 +56,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<'v>( &'v mut self, @@ -131,6 +149,8 @@ pub struct RegionShape { pub(super) row_count: usize, } +impl SyncDeps for RegionShape {} + /// The virtual column involved in a region. This includes concrete columns, /// as well as selectors that are not concrete columns at this stage. #[derive(Eq, PartialEq, Copy, Clone, Debug, Hash)] @@ -275,6 +295,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 68d211cd..a1bb0610 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -7,15 +7,21 @@ use std::iter; use std::ops::{Add, Mul, Neg, Range}; use std::time::{Duration, Instant}; +use blake2b_simd::blake2b; use ff::Field; +use ff::FromUniformBytes; +use group::Group; -use crate::plonk::Assigned; +use crate::circuit::layouter::SyncDeps; +use crate::plonk::permutation::keygen::Assembly; use crate::{ - arithmetic::{FieldExt, Group}, circuit, plonk::{ - permutation, Advice, Any, Assignment, Circuit, Column, ColumnType, ConstraintSystem, Error, - Expression, Fixed, FloorPlanner, Instance, 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, }; @@ -27,6 +33,7 @@ use rayon::{ }; pub mod metadata; +use metadata::Column as ColumnMetadata; mod util; mod failure; @@ -56,7 +63,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>, } @@ -81,34 +90,34 @@ 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), } /// A value within an expression. #[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] -enum Value { +enum Value { Real(F), Poison, } -impl From> for Value { +impl From> for Value { fn from(value: CellValue) -> Self { match value { // Cells that haven't been explicitly assigned to, default to zero. - CellValue::Unassigned => Value::Real(F::zero()), + CellValue::Unassigned => Value::Real(F::ZERO), CellValue::Assigned(v) => Value::Real(v), CellValue::Poison(_) => Value::Poison, } } } -impl Neg for Value { +impl Neg for Value { type Output = Self; fn neg(self) -> Self::Output { @@ -119,7 +128,7 @@ impl Neg for Value { } } -impl Add for Value { +impl Add for Value { type Output = Self; fn add(self, rhs: Self) -> Self::Output { @@ -130,7 +139,7 @@ impl Add for Value { } } -impl Mul for Value { +impl Mul for Value { type Output = Self; fn mul(self, rhs: Self) -> Self::Output { @@ -141,14 +150,14 @@ impl Mul for Value { (Value::Real(x), Value::Poison) | (Value::Poison, Value::Real(x)) if x.is_zero_vartime() => { - Value::Real(F::zero()) + Value::Real(F::ZERO) } _ => Value::Poison, } } } -impl Mul for Value { +impl Mul for Value { type Output = Self; fn mul(self, rhs: F) -> Self::Output { @@ -156,7 +165,7 @@ impl Mul for Value { Value::Real(lhs) => Value::Real(lhs * rhs), // If poison is multiplied by zero, then we treat the poison as unconstrained // and we don't propagate it. - Value::Poison if rhs.is_zero_vartime() => Value::Real(F::zero()), + Value::Poison if rhs.is_zero_vartime() => Value::Real(F::ZERO), _ => Value::Poison, } } @@ -174,12 +183,12 @@ impl Mul for Value { /// /// ``` /// use halo2_proofs::{ -/// arithmetic::FieldExt, /// circuit::{Layouter, SimpleFloorPlanner, Value}, /// dev::{FailureLocation, MockProver, VerifyFailure}, /// plonk::{Advice, Any, Circuit, Column, ConstraintSystem, Error, Selector}, /// poly::Rotation, /// }; +/// use ff::PrimeField; /// use halo2curves::pasta::Fp; /// const K: u32 = 5; /// @@ -197,9 +206,11 @@ impl Mul for Value { /// b: Value, /// } /// -/// impl Circuit for MyCircuit { +/// impl Circuit for MyCircuit { /// type Config = MyConfig; /// type FloorPlanner = SimpleFloorPlanner; +/// #[cfg(feature = "circuit-params")] +/// type Params = (); /// /// fn without_witnesses(&self) -> Self { /// Self::default() @@ -260,23 +271,25 @@ impl Mul for Value { /// offset: 0, /// }, /// cell_values: vec![ -/// (((Any::Advice, 0).into(), 0).into(), "0x2".to_string()), -/// (((Any::Advice, 1).into(), 0).into(), "0x4".to_string()), -/// (((Any::Advice, 2).into(), 0).into(), "0x8".to_string()), +/// (((Any::advice(), 0).into(), 0).into(), "0x2".to_string()), +/// (((Any::advice(), 1).into(), 0).into(), "0x4".to_string()), +/// (((Any::advice(), 2).into(), 0).into(), "0x8".to_string()), /// ], /// }]) /// ); /// -/// // 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 { +pub struct MockProver { k: u32, n: u32, cs: ConstraintSystem, @@ -296,41 +309,84 @@ pub struct MockProver { selectors: Vec>, + challenges: Vec, + permutation: permutation::keygen::Assembly, // A range of available rows for assignment and copies. usable_rows: Range, + + current_phase: sealed::Phase, } -impl Assignment for MockProver { +impl MockProver { + fn in_phase(&self, phase: P) -> bool { + self.current_phase == phase.to_sealed() + } +} + +impl Assignment for MockProver { fn enter_region(&mut self, name: N) where 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 @@ -351,15 +407,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( @@ -375,26 +436,42 @@ impl Assignment for MockProver { 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) { + 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(); + } } - 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().into_field().evaluate().assign() { + Ok(to) => { + let value = self + .advice + .get_mut(column.index()) + .and_then(|v| v.get_mut(row)) + .expect("bounds failure"); + *value = CellValue::Assigned(to); + } + Err(err) => { + // Propagate `assign` error if the column is in current phase. + if self.in_phase(column.column_type().phase) { + return Err(err); + } + } } - *self - .advice - .get_mut(column.index()) - .and_then(|v| v.get_mut(row)) - .ok_or(Error::BoundsFailure)? = - CellValue::Assigned(to().into_field().evaluate().assign()?); - Ok(()) } @@ -411,10 +488,18 @@ impl Assignment for MockProver { 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={}, 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 @@ -428,8 +513,7 @@ impl Assignment for MockProver { .fixed .get_mut(column.index()) .and_then(|v| v.get_mut(row)) - .ok_or(Error::BoundsFailure)? = - CellValue::Assigned(to().into_field().evaluate().assign()?); + .expect("bounds failure") = CellValue::Assigned(to().into_field().evaluate().assign()?); Ok(()) } @@ -441,10 +525,19 @@ impl Assignment for MockProver { right_column: Column, right_row: usize, ) -> Result<(), crate::plonk::Error> { - if !self.usable_rows.contains(&left_row) || !self.usable_rows.contains(&right_row) { - return Err(Error::not_enough_rows_available(self.k)); + if !self.in_phase(FirstPhase) { + return Ok(()); } + 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) } @@ -455,10 +548,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)?; } @@ -466,6 +567,14 @@ impl Assignment for MockProver { Ok(()) } + 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()]) + } + fn push_namespace(&mut self, _: N) where NR: Into, @@ -479,7 +588,7 @@ impl Assignment for MockProver { } } -impl MockProver { +impl + Ord> MockProver { /// Runs a synthetic keygen-and-prove operation on the given circuit, collecting data /// about the constraints and their assignments. pub fn run>( @@ -490,28 +599,37 @@ impl MockProver { let n = 1 << k; let mut cs = ConstraintSystem::default(); + #[cfg(feature = "circuit-params")] + let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params()); + #[cfg(not(feature = "circuit-params"))] 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); - } - - instance.resize(n, F::zero()); - Ok(instance) + 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); + instance }) - .collect::, _>>()?; + .collect::>(); // Fixed columns contain no blinding factors. let fixed = vec![vec![CellValue::Unassigned; n]; cs.num_fixed_columns]; @@ -533,6 +651,17 @@ impl MockProver { let permutation = permutation::keygen::Assembly::new(n, &cs.permutation); let constants = cs.constants.clone(); + // Use hash chain to derive deterministic challenges for testing + let challenges = { + let mut hash: [u8; 64] = blake2b(b"Halo2-MockProver").as_bytes().try_into().unwrap(); + iter::repeat_with(|| { + hash = blake2b(&hash).as_bytes().try_into().unwrap(); + F::from_uniform_bytes(&hash) + }) + .take(cs.num_challenges) + .collect() + }; + let mut prover = MockProver { k, n: n as u32, @@ -543,11 +672,21 @@ impl MockProver { advice, instance, selectors, + 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; @@ -559,9 +698,22 @@ impl MockProver { v })); + #[cfg(feature = "thread-safe-region")] + prover.permutation.build_ordered_mapping(); + Ok(prover) } + /// Return the content of an advice column as assigned by the circuit. + pub fn advice_values(&self, column: Column) -> &[CellValue] { + &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> { @@ -618,12 +770,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, @@ -662,11 +814,12 @@ impl MockProver { &self.cs.instance_queries, &self.instance, ), + &|challenge| Value::Real(self.challenges[challenge.index()]), &|a| -a, &|a, b| a + b, &|a, b| a * b, &|a, scalar| a * scalar, - &Value::Real(F::zero()), + &Value::Real(F::ZERO), ) { Value::Real(x) if x.is_zero_vartime() => None, Value::Real(_) => Some(VerifyFailure::ConstraintNotSatisfied { @@ -722,7 +875,7 @@ impl MockProver { &|scalar| Value::Real(scalar), &|_| panic!("virtual selectors are removed during optimization"), &|query| { - let query = self.cs.fixed_queries[query.index]; + let query = self.cs.fixed_queries[query.index.unwrap()]; let column_index = query.0.index(); let rotation = query.1 .0; self.fixed[column_index] @@ -730,7 +883,7 @@ impl MockProver { .into() }, &|query| { - let query = self.cs.advice_queries[query.index]; + let query = self.cs.advice_queries[query.index.unwrap()]; let column_index = query.0.index(); let rotation = query.1 .0; self.advice[column_index] @@ -738,7 +891,7 @@ impl MockProver { .into() }, &|query| { - let query = self.cs.instance_queries[query.index]; + let query = self.cs.instance_queries[query.index.unwrap()]; let column_index = query.0.index(); let rotation = query.1 .0; Value::Real( @@ -746,11 +899,12 @@ impl MockProver { [(row as i32 + n + rotation) as usize % n as usize], ) }, + &|challenge| Value::Real(self.challenges[challenge.index()]), &|a| -a, &|a, b| a + b, &|a, b| a * b, &|a, scalar| a * scalar, - &Value::Real(F::zero()), + &Value::Real(F::ZERO), ) }; @@ -831,7 +985,7 @@ impl MockProver { assert!(table.binary_search(input).is_err()); Some(VerifyFailure::Lookup { - name: lookup.name, + name: lookup.name.clone(), lookup_index, location: FailureLocation::find_expressions( &self.cs, @@ -846,7 +1000,7 @@ impl MockProver { }) .collect::>() }); - + let mapping = self.permutation.mapping(); // Check that permutations preserve the original values of the cells. let perm_errors = { // Original values of columns involved in the permutation. @@ -856,7 +1010,7 @@ impl MockProver { .get_columns() .get(column) .map(|c: &Column| match c.column_type() { - Any::Advice => self.advice[c.index()][row], + Any::Advice(_) => self.advice[c.index()][row], Any::Fixed => self.fixed[c.index()][row], Any::Instance => CellValue::Assigned(self.instance[c.index()][row]), }) @@ -864,14 +1018,12 @@ impl MockProver { }; // Iterate over each column of the permutation - self.permutation - .mapping - .iter() - .enumerate() - .flat_map(move |(column, values)| { - // Iterate over each row of the column to check that the cell's - // value is preserved by the mapping. - values.iter().enumerate().filter_map(move |(row, cell)| { + mapping.enumerate().flat_map(move |(column, values)| { + // Iterate over each row of the column to check that the cell's + // value is preserved by the mapping. + values + .enumerate() + .filter_map(move |(row, cell)| { let original_cell = original(column, row); let permuted_cell = original(cell.0, cell.1); if original_cell == permuted_cell { @@ -889,7 +1041,8 @@ impl MockProver { }) } }) - }) + .collect::>() + }) }; let mut errors: Vec<_> = iter::empty() @@ -924,8 +1077,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, @@ -984,7 +1136,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 @@ -1029,11 +1186,12 @@ impl MockProver { &self.cs.instance_queries, &self.instance, ), + &|challenge| Value::Real(self.challenges[challenge.index()]), &|a| -a, &|a, b| a + b, &|a, b| a * b, &|a, scalar| a * scalar, - &Value::Real(F::zero()), + &Value::Real(F::ZERO), ) { Value::Real(x) if x.is_zero_vartime() => None, Value::Real(_) => Some(VerifyFailure::ConstraintNotSatisfied { @@ -1106,11 +1264,12 @@ impl MockProver { [(row as i32 + n + query.rotation.0) as usize % n as usize], ) }, + &|challenge| Value::Real(self.challenges[challenge.index()]), &|a| -a, &|a, b| a + b, &|a, b| a * b, &|a, scalar| a * scalar, - &Value::Real(F::zero()), + &Value::Real(F::ZERO), ) }; @@ -1186,7 +1345,7 @@ impl MockProver { .filter_map(move |(input, input_row)| { if table.binary_search(input).is_err() { Some(VerifyFailure::Lookup { - name: lookup.name, + name: lookup.name.clone(), lookup_index, location: FailureLocation::find_expressions( &self.cs, @@ -1201,7 +1360,7 @@ impl MockProver { }) .collect::>() }); - + let mapping = self.permutation.mapping(); // Check that permutations preserve the original values of the cells. let perm_errors = { // Original values of columns involved in the permutation. @@ -1211,7 +1370,7 @@ impl MockProver { .get_columns() .get(column) .map(|c: &Column| match c.column_type() { - Any::Advice => self.advice[c.index()][row], + Any::Advice(_) => self.advice[c.index()][row], Any::Fixed => self.fixed[c.index()][row], Any::Instance => CellValue::Assigned(self.instance[c.index()][row]), }) @@ -1219,36 +1378,31 @@ impl MockProver { }; // Iterate over each column of the permutation - self.permutation - .mapping - .iter() - .enumerate() - .flat_map(move |(column, values)| { - // Iterate over each row of the column to check that the cell's - // value is preserved by the mapping. - values - .par_iter() - .enumerate() - .filter_map(move |(row, cell)| { - let original_cell = original(column, row); - let permuted_cell = original(cell.0, cell.1); - if original_cell == permuted_cell { - None - } else { - let columns = self.cs.permutation.get_columns(); - let column = columns.get(column).unwrap(); - Some(VerifyFailure::Permutation { - column: (*column).into(), - location: FailureLocation::find( - &self.regions, - row, - Some(column).into_iter().cloned().collect(), - ), - }) - } - }) - .collect::>() - }) + mapping.enumerate().flat_map(move |(column, values)| { + // Iterate over each row of the column to check that the cell's + // value is preserved by the mapping. + values + .enumerate() + .filter_map(move |(row, cell)| { + let original_cell = original(column, row); + let permuted_cell = original(cell.0, cell.1); + if original_cell == permuted_cell { + None + } else { + let columns = self.cs.permutation.get_columns(); + let column = columns.get(column).unwrap(); + Some(VerifyFailure::Permutation { + column: (*column).into(), + location: FailureLocation::find( + &self.regions, + row, + Some(column).into_iter().cloned().collect(), + ), + }) + } + }) + .collect::>() + }) }; let mut errors: Vec<_> = iter::empty() @@ -1292,6 +1446,62 @@ 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. + /// + /// Internally, this function uses a parallel aproach in order to verify the `MockProver` contents. + /// + /// Apart from the stderr output, this method is equivalent to: + /// ```ignore + /// assert_eq!(prover.verify_par(), Ok(())); + /// ``` + pub fn assert_satisfied_par(&self) { + if let Err(errs) = self.verify_par() { + for err in errs { + err.emit(self); + eprintln!(); + } + 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)] @@ -1302,8 +1512,8 @@ mod tests { use crate::{ circuit::{Layouter, SimpleFloorPlanner, Value}, plonk::{ - Advice, Any, Circuit, Column, ConstraintSystem, Error, Expression, Selector, - TableColumn, + sealed::SealedPhase, Advice, Any, Circuit, Column, ConstraintSystem, Error, Expression, + FirstPhase, Fixed, Instance, Selector, TableColumn, }, poly::Rotation, }; @@ -1315,6 +1525,7 @@ mod tests { #[derive(Clone)] struct FaultyCircuitConfig { a: Column, + b: Column, q: Selector, } @@ -1323,6 +1534,8 @@ mod tests { impl Circuit for FaultyCircuit { type Config = FaultyCircuitConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn configure(meta: &mut ConstraintSystem) -> Self::Config { let a = meta.advice_column(); @@ -1338,7 +1551,7 @@ mod tests { vec![q * (a - b)] }); - FaultyCircuitConfig { a, q } + FaultyCircuitConfig { a, b, q } } fn without_witnesses(&self) -> Self { @@ -1359,6 +1572,12 @@ mod tests { // Assign a = 0. region.assign_advice(|| "a", config.a, 0, || Value::known(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 // case is fine, but for other assignments would be broken. @@ -1375,14 +1594,191 @@ 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; + #[cfg(feature = "circuit-params")] + type Params = (); + + 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( + || "a = 2", + config.a, + 0, + || Value::known(Fp::from(2)), + )?; + region.assign_advice( + || "a = 6", + 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( + || "a = 4", + config.a, + 0, + || Value::known(Fp::from(4)), + )?; + + // BUG: Assign a = 5, which doesn't exist in the table! + region.assign_advice( + || "a = 5", + 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".to_string(), + lookup_index: 0, + location: FailureLocation::InRegion { + region: (1, "Faulty synthesis").into(), + offset: 1, + } + }]) + ); + } + + #[test] + fn bad_fixed_lookup() { const K: u32 = 4; #[derive(Clone)] @@ -1397,11 +1793,14 @@ mod tests { impl Circuit for FaultyCircuit { type Config = FaultyCircuitConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn configure(meta: &mut ConstraintSystem) -> Self::Config { 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()); @@ -1490,6 +1889,8 @@ mod tests { || Value::known(Fp::from(5)), )?; + region.name_column(|| "Witness example", config.a); + Ok(()) }, ) @@ -1500,7 +1901,7 @@ mod tests { assert_eq!( prover.verify(), Err(vec![VerifyFailure::Lookup { - name: "lookup", + name: "lookup".to_string(), lookup_index: 0, location: FailureLocation::InRegion { region: (2, "Faulty synthesis").into(), @@ -1509,4 +1910,189 @@ 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; + #[cfg(feature = "circuit-params")] + type Params = (); + + 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(|| "a", config.a, 0, || Value::known(Fp::one()))?; + + // Assign b = 1. + region.assign_advice(|| "b", config.b, 0, || Value::known(Fp::one()))?; + + // Assign c = 5. + region.assign_advice( + || "c", + config.c, + 0, + || Value::known(Fp::from(5u64)), + )?; + // Assign d = 7. + region.assign_fixed( + || "d", + config.d, + 0, + || Value::known(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(|| "a", config.a, 0, || Value::known(Fp::one()))?; + + // Assign b = 0. + region.assign_advice(|| "b", 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( + || "c", + config.c, + 0, + || Value::known(Fp::from(5u64)), + )?; + // Assign d = 7. + region.assign_fixed( + || "d", + config.d, + 0, + || Value::known(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()), + ], + },]) + ) + } } + +impl SyncDeps for MockProver {} diff --git a/halo2_proofs/src/dev/cost.rs b/halo2_proofs/src/dev/cost.rs index 35208a05..f2c4e9f2 100644 --- a/halo2_proofs/src/dev/cost.rs +++ b/halo2_proofs/src/dev/cost.rs @@ -11,10 +11,10 @@ use ff::{Field, PrimeField}; use group::prime::PrimeGroup; use crate::{ - circuit::Value, + circuit::{layouter::SyncDeps, Value}, plonk::{ - Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed, - FloorPlanner, Instance, Selector, + Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, + Fixed, FloorPlanner, Instance, Selector, }, poly::Rotation, }; @@ -46,6 +46,8 @@ struct Assembly { selectors: Vec>, } +impl SyncDeps for Assembly {} + impl Assignment for Assembly { fn enter_region(&mut self, _: N) where @@ -118,6 +120,18 @@ impl Assignment for Assembly { Ok(()) } + fn get_challenge(&self, _: Challenge) -> Value { + 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, @@ -138,6 +152,9 @@ impl> CircuitCost Self { // Collect the layout details. let mut cs = ConstraintSystem::default(); + #[cfg(feature = "circuit-params")] + let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params()); + #[cfg(not(feature = "circuit-params"))] let config = ConcreteCircuit::configure(&mut cs); let mut assembly = Assembly { selectors: vec![vec![false; 1 << k]; cs.num_selectors], diff --git a/halo2_proofs/src/dev/failure.rs b/halo2_proofs/src/dev/failure.rs index e98d9646..706ab76b 100644 --- a/halo2_proofs/src/dev/failure.rs +++ b/halo2_proofs/src/dev/failure.rs @@ -1,17 +1,18 @@ -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::Value, + dev::{Instance, Value}, plonk::{Any, Column, ConstraintSystem, Expression, Gate}, poly::Rotation, }; @@ -19,7 +20,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 { @@ -48,6 +49,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], @@ -59,9 +70,10 @@ impl FailureLocation { expression.evaluate( &|_| vec![], &|_| panic!("virtual selectors are removed during optimization"), - &|query| vec![cs.fixed_queries[query.index].0.into()], - &|query| vec![cs.advice_queries[query.index].0.into()], - &|query| vec![cs.instance_queries[query.index].0.into()], + &|query| vec![cs.fixed_queries[query.index.unwrap()].0.into()], + &|query| vec![cs.advice_queries[query.index.unwrap()].0.into()], + &|query| vec![cs.instance_queries[query.index.unwrap()].0.into()], + &|_| vec![], &|a| a, &|mut a, mut b| { a.append(&mut b); @@ -89,6 +101,9 @@ impl FailureLocation { .iter() .enumerate() .find(|(_, r)| { + if r.rows.is_none() { + return false; + } let (start, end) = r.rows.unwrap(); // We match the region if any input columns overlap, rather than all of // them, because matching complex selector columns is hard. As long as @@ -98,17 +113,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 { @@ -146,7 +159,7 @@ pub enum VerifyFailure { /// A lookup input did not exist in its corresponding table. Lookup { /// The name of the lookup that is not satisfied. - name: &'static str, + name: String, /// The index of the lookup that is not satisfied. These indices are assigned in /// the order in which `ConstraintSystem::lookup` is called during /// `Circuit::configure`. @@ -186,8 +199,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 { @@ -196,8 +209,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(()) } @@ -222,14 +244,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.clone(), + 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 @@ -376,7 +443,7 @@ fn render_constraint_not_satisfied( /// | x0 = 0x5 /// | x1 = 1 /// ``` -fn render_lookup( +fn render_lookup( prover: &MockProver, name: &str, lookup_index: usize, @@ -397,26 +464,57 @@ fn render_lookup( // Recover the fixed columns from the table expressions. We don't allow composite // expressions for the table side of lookups. - let table_columns = lookup.table_expressions.iter().map(|expr| { + 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), - &|_| panic!("no advice columns in table expressions"), - &|_| panic!("no instance columns in table expressions"), - &|_| panic!("no negations in table expressions"), - &|_, _| panic!("no sums in table expressions"), - &|_, _| panic!("no products in table expressions"), - &|_, _| panic!("no scaling in table expressions"), + &|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}, + &|a, b| format! {"{} * {}", a,b}, + &|a, b| format! {"{} * {:?}", a, b}, ) }); - fn cell_value<'a, F: FieldExt, Q: Into + Copy>( - column_type: Any, + fn cell_value<'a, F: Field, Q: Into + Copy>( load: impl Fn(Q) -> Value + 'a, ) -> impl Fn(Q) -> BTreeMap + 'a { move |query| { let AnyQuery { + column_type, column_index, rotation, .. @@ -438,8 +536,9 @@ fn render_lookup( for i in 0..lookup.input_expressions.len() { eprint!("{}L{}", if i == 0 { "" } else { ", " }, i); } + eprint!(") ∉ ("); - for (i, column) in table_columns.enumerate() { + for (i, column) in lookup_columns.enumerate() { eprint!("{}{}", if i == 0 { "" } else { ", " }, column); } eprintln!(")"); @@ -451,18 +550,15 @@ fn render_lookup( let cell_values = input.evaluate( &|_| BTreeMap::default(), &|_| panic!("virtual selectors are removed during optimization"), - &cell_value( - Any::Fixed, - &util::load(n, row, &cs.fixed_queries, &prover.fixed), - ), - &cell_value( - Any::Advice, - &util::load(n, row, &cs.advice_queries, &prover.advice), - ), - &cell_value( - Any::Instance, - &util::load_instance(n, row, &cs.instance_queries, &prover.instance), - ), + &cell_value(&util::load(n, row, &cs.fixed_queries, &prover.fixed)), + &cell_value(&util::load(n, row, &cs.advice_queries, &prover.advice)), + &cell_value(&util::load_instance( + n, + row, + &cs.instance_queries, + &prover.instance, + )), + &|_| BTreeMap::default(), &|a| a, &|mut a, mut b| { a.append(&mut b); @@ -499,6 +595,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); @@ -516,7 +613,7 @@ fn render_lookup( impl VerifyFailure { /// Emits this failure in pretty-printed format to stderr. - pub(super) fn emit(&self, prover: &MockProver) { + pub(super) fn emit(&self, prover: &MockProver) { match self { Self::CellNotAssigned { gate, diff --git a/halo2_proofs/src/dev/failure/emitter.rs b/halo2_proofs/src/dev/failure/emitter.rs index 071dca6f..edd61f30 100644 --- a/halo2_proofs/src/dev/failure/emitter.rs +++ b/halo2_proofs/src/dev/failure/emitter.rs @@ -6,11 +6,12 @@ use group::ff::Field; use super::FailureLocation; use crate::{ dev::{metadata, util}, - plonk::{Any, Expression}, + plonk::{Advice, Any, Expression}, }; 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); - // Print the assigned cells, and their region offset or rotation. - for (column, cells) in columns { - let width = col_width(*cells); + 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 + 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( @@ -111,7 +153,7 @@ pub(super) fn expression_to_string( label.clone() } else if query.rotation.0 == 0 { // This is most likely a merged selector - format!("S{}", query.index) + format!("S{}", query.index.unwrap()) } else { // No idea how we'd get here... format!("F{}@{}", query.column_index, query.rotation.0) @@ -120,10 +162,17 @@ pub(super) fn expression_to_string( &|query| { layout .get(&query.rotation.0) - .unwrap() - .get(&(Any::Advice, query.column_index).into()) - .unwrap() - .clone() + .and_then(|map| { + map.get( + &( + Any::Advice(Advice { phase: query.phase }), + query.column_index, + ) + .into(), + ) + }) + .cloned() + .unwrap_or_default() }, &|query| { layout @@ -133,6 +182,7 @@ pub(super) fn expression_to_string( .unwrap() .clone() }, + &|challenge| format!("C{}({})", challenge.index(), challenge.phase()), &|a| { if a.contains(' ') { format!("-({})", a) diff --git a/halo2_proofs/src/dev/gates.rs b/halo2_proofs/src/dev/gates.rs index b988b723..fd4c0396 100644 --- a/halo2_proofs/src/dev/gates.rs +++ b/halo2_proofs/src/dev/gates.rs @@ -7,19 +7,22 @@ use ff::PrimeField; use crate::{ dev::util, - plonk::{Circuit, ConstraintSystem}, + plonk::{ + sealed::{self, SealedPhase}, + Circuit, ConstraintSystem, FirstPhase, + }, }; #[derive(Debug)] struct Constraint { - name: &'static str, + name: String, expression: String, queries: BTreeSet, } #[derive(Debug)] struct Gate { - name: &'static str, + name: String, constraints: Vec, } @@ -46,6 +49,8 @@ struct Gate { /// impl Circuit for MyCircuit { /// type Config = MyConfig; /// type FloorPlanner = SimpleFloorPlanner; +/// #[cfg(feature = "circuit-params")] +/// type Params = (); /// /// fn without_witnesses(&self) -> Self { /// Self::default() @@ -76,6 +81,9 @@ struct Gate { /// } /// } /// +/// #[cfg(feature = "circuit-params")] +/// let gates = CircuitGates::collect::(()); +/// #[cfg(not(feature = "circuit-params"))] /// let gates = CircuitGates::collect::(); /// assert_eq!( /// format!("{}", gates), @@ -100,28 +108,45 @@ pub struct CircuitGates { impl CircuitGates { /// Collects the gates from within the circuit. - pub fn collect>() -> Self { + pub fn collect>( + #[cfg(feature = "circuit-params")] params: C::Params, + ) -> Self { // Collect the graph details. let mut cs = ConstraintSystem::default(); + #[cfg(feature = "circuit-params")] + let _ = C::configure_with_params(&mut cs, params); + #[cfg(not(feature = "circuit-params"))] let _ = C::configure(&mut cs); let gates = cs .gates .iter() .map(|gate| Gate { - name: gate.name(), + name: gate.name().to_string(), constraints: gate .polynomials() .iter() .enumerate() .map(|(i, constraint)| Constraint { - name: gate.constraint_name(i), + name: gate.constraint_name(i).to_string(), expression: constraint.evaluate( &util::format_value, &|selector| format!("S{}", selector.0), &|query| format!("F{}@{}", query.column_index, query.rotation.0), - &|query| format!("A{}@{}", query.column_index, query.rotation.0), + &|query| { + if query.phase == FirstPhase.to_sealed() { + format!("A{}@{}", query.column_index, query.rotation.0) + } else { + format!( + "A{}({})@{}", + query.column_index, + query.phase(), + query.rotation.0 + ) + } + }, &|query| format!("I{}@{}", query.column_index, query.rotation.0), + &|challenge| format!("C{}({})", challenge.index(), challenge.phase()), &|a| { if a.contains(' ') { format!("-({})", a) @@ -159,15 +184,28 @@ impl CircuitGates { .collect() }, &|query| { - vec![format!("A{}@{}", query.column_index, query.rotation.0)] - .into_iter() - .collect() + let query = if query.phase == FirstPhase.to_sealed() { + format!("A{}@{}", query.column_index, query.rotation.0) + } else { + format!( + "A{}({})@{}", + query.column_index, + query.phase(), + query.rotation.0 + ) + }; + vec![query].into_iter().collect() }, &|query| { vec![format!("I{}@{}", query.column_index, query.rotation.0)] .into_iter() .collect() }, + &|challenge| { + vec![format!("C{}({})", challenge.index(), challenge.phase())] + .into_iter() + .collect() + }, &|a| a, &|mut a, mut b| { a.append(&mut b); @@ -195,6 +233,7 @@ impl CircuitGates { &|_| (0, 0, 0), &|_| (0, 0, 0), &|_| (0, 0, 0), + &|_| (0, 0, 0), &|(a_n, a_a, a_m)| (a_n + 1, a_a, a_m), &|(a_n, a_a, a_m), (b_n, b_a, b_m)| (a_n + b_n, a_a + b_a + 1, a_m + b_m), &|(a_n, a_a, a_m), (b_n, b_a, b_m)| (a_n + b_n, a_a + b_a, a_m + b_m + 1), diff --git a/halo2_proofs/src/dev/graph.rs b/halo2_proofs/src/dev/graph.rs index 18ef8154..008d5840 100644 --- a/halo2_proofs/src/dev/graph.rs +++ b/halo2_proofs/src/dev/graph.rs @@ -4,8 +4,8 @@ use tabbycat::{AttrList, Edge, GraphBuilder, GraphType, Identity, StmtList}; use crate::{ circuit::Value, plonk::{ - Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed, - FloorPlanner, Instance, Selector, + Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, + Fixed, FloorPlanner, Instance, Selector, }, }; @@ -22,6 +22,9 @@ pub fn circuit_dot_graph>( ) -> String { // Collect the graph details. let mut cs = ConstraintSystem::default(); + #[cfg(feature = "circuit-params")] + let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params()); + #[cfg(not(feature = "circuit-params"))] let config = ConcreteCircuit::configure(&mut cs); let mut graph = Graph::default(); ConcreteCircuit::FloorPlanner::synthesize(&mut graph, circuit, config, cs.constants).unwrap(); @@ -77,6 +80,8 @@ struct Graph { current_namespace: Vec, } +impl crate::dev::SyncDeps for Graph {} + impl Assignment for Graph { fn enter_region(&mut self, _: N) where @@ -99,6 +104,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()) } @@ -157,6 +170,10 @@ impl Assignment for Graph { Ok(()) } + fn get_challenge(&self, _: Challenge) -> Value { + Value::unknown() + } + fn push_namespace(&mut self, name_fn: N) where NR: Into, diff --git a/halo2_proofs/src/dev/graph/layout.rs b/halo2_proofs/src/dev/graph/layout.rs index 7d9d56c0..4782fd8a 100644 --- a/halo2_proofs/src/dev/graph/layout.rs +++ b/halo2_proofs/src/dev/graph/layout.rs @@ -10,8 +10,8 @@ use std::ops::Range; use crate::{ circuit::{layouter::RegionColumn, Value}, plonk::{ - Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed, - FloorPlanner, Instance, Selector, + Advice, Any, Assigned, Assignment, Challenge, Circuit, Column, ConstraintSystem, Error, + Fixed, FloorPlanner, Instance, Selector, }, }; @@ -97,6 +97,9 @@ impl CircuitLayout { let n = 1 << k; // Collect the layout details. let mut cs = ConstraintSystem::default(); + #[cfg(feature = "circuit-params")] + let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params()); + #[cfg(not(feature = "circuit-params"))] let config = ConcreteCircuit::configure(&mut cs); let mut layout = Layout::new(k, n, cs.num_selectors); ConcreteCircuit::FloorPlanner::synthesize( @@ -120,7 +123,7 @@ impl CircuitLayout { column.index() + match column.column_type() { Any::Instance => 0, - Any::Advice => cs.num_instance_columns, + Any::Advice(_) => cs.num_instance_columns, Any::Fixed => cs.num_instance_columns + cs.num_advice_columns, } }; @@ -352,6 +355,8 @@ struct Layout { selectors: Vec>, } +impl crate::dev::SyncDeps for Layout {} + impl Layout { fn new(k: u32, n: usize, num_selectors: usize) -> Self { Layout { @@ -490,6 +495,18 @@ impl Assignment for Layout { Ok(()) } + fn get_challenge(&self, _: Challenge) -> Value { + 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 d7d2443e..f81bfa67 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, @@ -12,6 +15,17 @@ pub struct Column { pub(super) index: usize, } +impl Column { + /// Return the column type. + pub fn column_type(&self) -> Any { + self.column_type + } + /// Return the column index. + pub fn index(&self) -> usize { + self.index + } +} + impl fmt::Display for Column { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Column('{:?}', {})", self.column_type, self.index) @@ -33,11 +47,46 @@ 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)] pub struct VirtualCell { - name: &'static str, + name: String, pub(super) column: Column, pub(super) rotation: i32, } @@ -45,17 +94,17 @@ pub struct VirtualCell { impl From<(Column, i32)> for VirtualCell { fn from((column, rotation): (Column, i32)) -> Self { VirtualCell { - name: "", + name: "".to_string(), column, rotation, } } } -impl From<(&'static str, Column, i32)> for VirtualCell { - fn from((name, column, rotation): (&'static str, Column, i32)) -> Self { +impl> From<(S, Column, i32)> for VirtualCell { + fn from((name, column, rotation): (S, Column, i32)) -> Self { VirtualCell { - name, + name: name.as_ref().to_string(), column, rotation, } @@ -65,7 +114,7 @@ impl From<(&'static str, Column, i32)> for VirtualCell { impl From for VirtualCell { fn from(c: plonk::VirtualCell) -> Self { VirtualCell { - name: "", + name: "".to_string(), column: c.column.into(), rotation: c.rotation.0, } @@ -73,6 +122,34 @@ impl From for VirtualCell { } impl fmt::Display for VirtualCell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}@{}", self.column, self.rotation)?; + if !self.name.is_empty() { + write!(f, "({})", self.name.as_str())?; + } + Ok(()) + } +} + +/// Helper structure used to be able to inject Column annotations inside a `Display` or `Debug` call. +#[derive(Clone, Debug)] +pub(super) struct DebugVirtualCell { + name: String, + column: DebugColumn, + rotation: i32, +} + +impl From<(&VirtualCell, Option<&HashMap>)> for DebugVirtualCell { + fn from(info: (&VirtualCell, Option<&HashMap>)) -> Self { + DebugVirtualCell { + name: info.0.name.clone(), + 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() { @@ -83,30 +160,33 @@ impl fmt::Display for VirtualCell { } /// Metadata about a configured gate within a circuit. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, Clone)] 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`. pub(super) index: usize, /// The name of the active gate. These are specified by the gate creator (such as /// a chip implementation), and is not enforced to be unique. - pub(super) name: &'static str, + pub(super) name: String, } impl fmt::Display for Gate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Gate {} ('{}')", self.index, self.name) + write!(f, "Gate {} ('{}')", self.index, self.name.as_str()) } } -impl From<(usize, &'static str)> for Gate { - fn from((index, name): (usize, &'static str)) -> Self { - Gate { index, name } +impl> From<(usize, S)> for Gate { + fn from((index, name): (usize, S)) -> Self { + Gate { + index, + name: name.as_ref().to_string(), + } } } /// Metadata about a configured constraint within a circuit. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct Constraint { /// The gate containing the constraint. pub(super) gate: Gate, @@ -116,7 +196,7 @@ pub struct Constraint { pub(super) index: usize, /// The name of the constraint. This is specified by the gate creator (such as a chip /// implementation), and is not enforced to be unique. - pub(super) name: &'static str, + pub(super) name: String, } impl fmt::Display for Constraint { @@ -128,7 +208,7 @@ impl fmt::Display for Constraint { if self.name.is_empty() { String::new() } else { - format!(" ('{}')", self.name) + format!(" ('{}')", self.name.as_str()) }, self.gate.index, self.gate.name, @@ -136,14 +216,18 @@ impl fmt::Display for Constraint { } } -impl From<(Gate, usize, &'static str)> for Constraint { - fn from((gate, index, name): (Gate, usize, &'static str)) -> Self { - Constraint { gate, index, name } +impl> From<(Gate, usize, S)> for Constraint { + fn from((gate, index, name): (Gate, usize, S)) -> Self { + Constraint { + gate, + index, + name: name.as_ref().to_string(), + } } } /// 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,17 +235,50 @@ 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 fmt::Display for Region { +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 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Region {} ('{}')", self.index, self.name.as_str()) + } +} + impl From<(usize, String)> for Region { fn from((index, name): (usize, String)) -> Self { - Region { index, name } + Region { + index, + name, + column_annotations: None, + } } } @@ -170,6 +287,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/dev/util.rs b/halo2_proofs/src/dev/util.rs index 7088c7e3..f09695a4 100644 --- a/halo2_proofs/src/dev/util.rs +++ b/halo2_proofs/src/dev/util.rs @@ -1,12 +1,10 @@ -use std::collections::BTreeMap; - use group::ff::Field; -use halo2curves::FieldExt; +use std::collections::BTreeMap; use super::{metadata, CellValue, Value}; use crate::{ plonk::{ - AdviceQuery, Any, Column, ColumnType, Expression, FixedQuery, Gate, InstanceQuery, + Advice, AdviceQuery, Any, Column, ColumnType, Expression, FixedQuery, Gate, InstanceQuery, VirtualCell, }, poly::Rotation, @@ -14,7 +12,7 @@ use crate::{ pub(crate) struct AnyQuery { /// Query index - pub index: usize, + pub index: Option, /// Column type pub column_type: Any, /// Column index @@ -38,7 +36,7 @@ impl From for AnyQuery { fn from(query: AdviceQuery) -> Self { Self { index: query.index, - column_type: Any::Advice, + column_type: Any::Advice(Advice { phase: query.phase }), column_index: query.column_index, rotation: query.rotation, } @@ -59,9 +57,9 @@ impl From for AnyQuery { pub(super) fn format_value(v: F) -> String { if v.is_zero_vartime() { "0".into() - } else if v == F::one() { + } else if v == F::ONE { "1".into() - } else if v == -F::one() { + } else if v == -F::ONE { "-1".into() } else { // Format value as hex. @@ -73,33 +71,33 @@ pub(super) fn format_value(v: F) -> String { } } -pub(super) fn load<'a, F: FieldExt, T: ColumnType, Q: Into + Copy>( +pub(super) fn load<'a, F: Field, T: ColumnType, Q: Into + Copy>( n: i32, row: i32, queries: &'a [(Column, Rotation)], cells: &'a [Vec>], ) -> impl Fn(Q) -> Value + 'a { move |query| { - let (column, at) = &queries[query.into().index]; + let (column, at) = &queries[query.into().index.unwrap()]; let resolved_row = (row + at.0) % n; cells[column.index()][resolved_row as usize].into() } } -pub(super) fn load_instance<'a, F: FieldExt, T: ColumnType, Q: Into + Copy>( +pub(super) fn load_instance<'a, F: Field, T: ColumnType, Q: Into + Copy>( n: i32, row: i32, queries: &'a [(Column, Rotation)], cells: &'a [Vec], ) -> impl Fn(Q) -> Value + 'a { move |query| { - let (column, at) = &queries[query.into().index]; + let (column, at) = &queries[query.into().index.unwrap()]; let resolved_row = (row + at.0) % n; Value::Real(cells[column.index()][resolved_row as usize]) } } -fn cell_value<'a, F: FieldExt, Q: Into + Copy>( +fn cell_value<'a, F: Field, Q: Into + Copy>( virtual_cells: &'a [VirtualCell], load: impl Fn(Q) -> Value + 'a, ) -> impl Fn(Q) -> BTreeMap + 'a { @@ -132,7 +130,7 @@ fn cell_value<'a, F: FieldExt, Q: Into + Copy>( } } -pub(super) fn cell_values<'a, F: FieldExt>( +pub(super) fn cell_values<'a, F: Field>( gate: &Gate, poly: &Expression, load_fixed: impl Fn(FixedQuery) -> Value + 'a, @@ -146,6 +144,7 @@ pub(super) fn cell_values<'a, F: FieldExt>( &cell_value(virtual_cells, load_fixed), &cell_value(virtual_cells, load_advice), &cell_value(virtual_cells, load_instance), + &|_| BTreeMap::default(), &|a| a, &|mut a, mut b| { a.append(&mut b); diff --git a/halo2_proofs/src/helpers.rs b/halo2_proofs/src/helpers.rs index 297fd7b9..41fc2e8d 100644 --- a/halo2_proofs/src/helpers.rs +++ b/halo2_proofs/src/helpers.rs @@ -1,7 +1,25 @@ +use crate::poly::Polynomial; +use ff::PrimeField; +use halo2curves::{pairing::Engine, serde::SerdeObject, CurveAffine}; use std::io; -use halo2curves::CurveAffine; +/// This enum specifies how various types are serialized and deserialized. +#[derive(Clone, Copy, Debug)] +pub enum SerdeFormat { + /// Curve elements are serialized in compressed form. + /// Field elements are serialized in standard form, with endianness specified by the + /// `PrimeField` implementation. + Processed, + /// Curve elements are serialized in uncompressed form. Field elements are serialized + /// in their internal Montgomery representation. + /// When deserializing, checks are performed to ensure curve elements indeed lie on the curve and field elements + /// are less than modulus. + RawBytes, + /// Serialization is the same as `RawBytes`, but no checks are performed. + RawBytesUnchecked, +} +// Keep this trait for compatibility with IPA serialization pub(crate) trait CurveRead: CurveAffine { /// Reads a compressed element from the buffer and attempts to parse it /// using `from_bytes`. @@ -9,8 +27,120 @@ pub(crate) trait CurveRead: CurveAffine { let mut compressed = Self::Repr::default(); reader.read_exact(compressed.as_mut())?; Option::from(Self::from_bytes(&compressed)) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid point encoding in proof")) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid point encoding in proof")) } } - impl CurveRead for C {} + +pub trait SerdeCurveAffine: CurveAffine + SerdeObject { + /// Reads an element from the buffer and parses it according to the `format`: + /// - `Processed`: Reads a compressed curve element and decompress it + /// - `RawBytes`: Reads an uncompressed curve element with coordinates in Montgomery form. + /// Checks that field elements are less than modulus, and then checks that the point is on the curve. + /// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form; + /// does not perform any checks + fn read(reader: &mut R, format: SerdeFormat) -> io::Result { + match format { + SerdeFormat::Processed => ::read(reader), + SerdeFormat::RawBytes => ::read_raw(reader), + SerdeFormat::RawBytesUnchecked => Ok(::read_raw_unchecked(reader)), + } + } + /// Writes a curve element according to `format`: + /// - `Processed`: Writes a compressed curve element + /// - Otherwise: Writes an uncompressed curve element with coordinates in Montgomery form + fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> { + match format { + SerdeFormat::Processed => writer.write_all(self.to_bytes().as_ref()), + _ => self.write_raw(writer), + } + } +} +impl SerdeCurveAffine for C {} + +pub trait SerdePrimeField: PrimeField + SerdeObject { + /// Reads a field element as bytes from the buffer according to the `format`: + /// - `Processed`: Reads a field element in standard form, with endianness specified by the + /// `PrimeField` implementation, and checks that the element is less than the modulus. + /// - `RawBytes`: Reads a field element from raw bytes in its internal Montgomery representations, + /// and checks that the element is less than the modulus. + /// - `RawBytesUnchecked`: Reads a field element in Montgomery form and performs no checks. + fn read(reader: &mut R, format: SerdeFormat) -> io::Result { + match format { + SerdeFormat::Processed => { + let mut compressed = Self::Repr::default(); + reader.read_exact(compressed.as_mut())?; + Option::from(Self::from_repr(compressed)).ok_or_else(|| { + io::Error::new(io::ErrorKind::Other, "Invalid prime field point encoding") + }) + } + SerdeFormat::RawBytes => ::read_raw(reader), + SerdeFormat::RawBytesUnchecked => Ok(::read_raw_unchecked(reader)), + } + } + + /// Writes a field element as bytes to the buffer according to the `format`: + /// - `Processed`: Writes a field element in standard form, with endianness specified by the + /// `PrimeField` implementation. + /// - Otherwise: Writes a field element into raw bytes in its internal Montgomery representation, + /// WITHOUT performing the expensive Montgomery reduction. + fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> { + match format { + SerdeFormat::Processed => writer.write_all(self.to_repr().as_ref()), + _ => self.write_raw(writer), + } + } +} +impl SerdePrimeField for F {} + +/// Convert a slice of `bool` into a `u8`. +/// +/// Panics if the slice has length greater than 8. +pub fn pack(bits: &[bool]) -> u8 { + let mut value = 0u8; + assert!(bits.len() <= 8); + for (bit_index, bit) in bits.iter().enumerate() { + value |= (*bit as u8) << bit_index; + } + value +} + +/// Writes the first `bits.len()` bits of a `u8` into `bits`. +pub fn unpack(byte: u8, bits: &mut [bool]) { + for (bit_index, bit) in bits.iter_mut().enumerate() { + *bit = (byte >> bit_index) & 1 == 1; + } +} + +/// Reads a vector of polynomials from buffer +pub(crate) fn read_polynomial_vec( + reader: &mut R, + format: SerdeFormat, +) -> io::Result>> { + let mut len = [0u8; 4]; + reader.read_exact(&mut len)?; + let len = u32::from_be_bytes(len); + + (0..len) + .map(|_| Polynomial::::read(reader, format)) + .collect::>>() +} + +/// Writes a slice of polynomials to buffer +pub(crate) fn write_polynomial_slice( + slice: &[Polynomial], + writer: &mut W, + format: SerdeFormat, +) -> io::Result<()> { + writer.write_all(&(slice.len() as u32).to_be_bytes())?; + for poly in slice.iter() { + poly.write(writer, format)?; + } + Ok(()) +} + +/// Gets the total number of bytes of a slice of polynomials, assuming all polynomials are the same length +pub(crate) fn polynomial_slice_byte_length(slice: &[Polynomial]) -> usize { + let field_len = F::default().to_repr().as_ref().len(); + 4 + slice.len() * (4 + field_len * slice.get(0).map(|poly| poly.len()).unwrap_or(0)) +} diff --git a/halo2_proofs/src/lib.rs b/halo2_proofs/src/lib.rs index c84e4826..52676ddb 100644 --- a/halo2_proofs/src/lib.rs +++ b/halo2_proofs/src/lib.rs @@ -35,3 +35,4 @@ pub mod transcript; pub mod dev; mod helpers; +pub use helpers::SerdeFormat; diff --git a/halo2_proofs/src/plonk.rs b/halo2_proofs/src/plonk.rs index f9a6587a..5485d39f 100644 --- a/halo2_proofs/src/plonk.rs +++ b/halo2_proofs/src/plonk.rs @@ -6,15 +6,19 @@ //! [plonk]: https://eprint.iacr.org/2019/953 use blake2b_simd::Params as Blake2bParams; -use group::ff::Field; +use group::ff::{Field, FromUniformBytes, PrimeField}; -use crate::arithmetic::{CurveAffine, FieldExt}; -use crate::helpers::CurveRead; +use crate::arithmetic::CurveAffine; +use crate::helpers::{ + polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, SerdeCurveAffine, + SerdePrimeField, +}; use crate::poly::{ commitment::Params, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, PinnedEvaluationDomain, Polynomial, }; use crate::transcript::{ChallengeScalar, EncodedChallenge, Transcript}; +use crate::SerdeFormat; mod assigned; mod circuit; @@ -22,7 +26,7 @@ mod error; mod evaluation; mod keygen; mod lookup; -pub(crate) mod permutation; +pub mod permutation; mod vanishing; mod prover; @@ -50,14 +54,139 @@ pub struct VerifyingKey { cs_degree: usize, /// The representative of this `VerifyingKey` in transcripts. transcript_repr: C::Scalar, + selectors: Vec>, } -impl VerifyingKey { +impl VerifyingKey +where + C::Scalar: SerdePrimeField + FromUniformBytes<64>, +{ + /// Writes a verifying key to a buffer. + /// + /// Writes a curve element according to `format`: + /// - `Processed`: Writes a compressed curve element with coordinates in standard form. + /// Writes a field element in standard form, with endianness specified by the + /// `PrimeField` implementation. + /// - Otherwise: Writes an uncompressed curve element with coordinates in Montgomery form + /// Writes a field element into raw bytes in its internal Montgomery representation, + /// WITHOUT performing the expensive Montgomery reduction. + pub fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> { + writer.write_all(&self.domain.k().to_be_bytes())?; + writer.write_all(&(self.fixed_commitments.len() as u32).to_be_bytes())?; + for commitment in &self.fixed_commitments { + commitment.write(writer, format)?; + } + self.permutation.write(writer, format)?; + + // write self.selectors + for selector in &self.selectors { + // since `selector` is filled with `bool`, we pack them 8 at a time into bytes and then write + for bits in selector.chunks(8) { + writer.write_all(&[crate::helpers::pack(bits)])?; + } + } + Ok(()) + } + + /// Reads a verification key from a buffer. + /// + /// Reads a curve element from the buffer and parses it according to the `format`: + /// - `Processed`: Reads a compressed curve element and decompresses it. + /// Reads a field element in standard form, with endianness specified by the + /// `PrimeField` implementation, and checks that the element is less than the modulus. + /// - `RawBytes`: Reads an uncompressed curve element with coordinates in Montgomery form. + /// Checks that field elements are less than modulus, and then checks that the point is on the curve. + /// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form; + /// does not perform any checks + pub fn read>( + reader: &mut R, + format: SerdeFormat, + #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, + ) -> io::Result { + let mut k = [0u8; 4]; + reader.read_exact(&mut k)?; + let k = u32::from_be_bytes(k); + let (domain, cs, _) = keygen::create_domain::( + k, + #[cfg(feature = "circuit-params")] + params, + ); + let mut num_fixed_columns = [0u8; 4]; + reader.read_exact(&mut num_fixed_columns)?; + let num_fixed_columns = u32::from_be_bytes(num_fixed_columns); + + let fixed_commitments: Vec<_> = (0..num_fixed_columns) + .map(|_| C::read(reader, format)) + .collect::>()?; + + let permutation = permutation::VerifyingKey::read(reader, &cs.permutation, format)?; + + // read selectors + let selectors: Vec> = vec![vec![false; 1 << k]; cs.num_selectors] + .into_iter() + .map(|mut selector| { + let mut selector_bytes = vec![0u8; (selector.len() + 7) / 8]; + reader.read_exact(&mut selector_bytes)?; + for (bits, byte) in selector.chunks_mut(8).into_iter().zip(selector_bytes) { + crate::helpers::unpack(byte, bits); + } + Ok(selector) + }) + .collect::>()?; + let (cs, _) = cs.compress_selectors(selectors.clone()); + + Ok(Self::from_parts( + domain, + fixed_commitments, + permutation, + cs, + selectors, + )) + } + + /// Writes a verifying key to a vector of bytes using [`Self::write`]. + pub fn to_bytes(&self, format: SerdeFormat) -> Vec { + let mut bytes = Vec::::with_capacity(self.bytes_length()); + Self::write(self, &mut bytes, format).expect("Writing to vector should not fail"); + bytes + } + + /// Reads a verification key from a slice of bytes using [`Self::read`]. + pub fn from_bytes>( + mut bytes: &[u8], + format: SerdeFormat, + #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, + ) -> io::Result { + Self::read::<_, ConcreteCircuit>( + &mut bytes, + format, + #[cfg(feature = "circuit-params")] + params, + ) + } +} + +impl VerifyingKey +where + C::ScalarExt: FromUniformBytes<64>, +{ + fn bytes_length(&self) -> usize { + 8 + (self.fixed_commitments.len() * C::default().to_bytes().as_ref().len()) + + self.permutation.bytes_length() + + self.selectors.len() + * (self + .selectors + .get(0) + .map(|selector| (selector.len() + 7) / 8) + .unwrap_or(0)) + } + fn from_parts( domain: EvaluationDomain, fixed_commitments: Vec, permutation: permutation::VerifyingKey, cs: ConstraintSystem, + selectors: Vec>, ) -> Self { // Compute cached values. let cs_degree = cs.degree(); @@ -69,7 +198,8 @@ impl VerifyingKey { cs, cs_degree, // Temporary, this is not pinned. - transcript_repr: C::Scalar::zero(), + transcript_repr: C::Scalar::ZERO, + selectors, }; let mut hasher = Blake2bParams::new() @@ -83,7 +213,7 @@ impl VerifyingKey { hasher.update(s.as_bytes()); // Hash in final Blake2bState - vk.transcript_repr = C::Scalar::from_bytes_wide(hasher.finalize().as_array()); + vk.transcript_repr = C::Scalar::from_uniform_bytes(hasher.finalize().as_array()); vk } @@ -154,11 +284,117 @@ pub struct ProvingKey { ev: Evaluator, } -impl ProvingKey { +impl ProvingKey +where + C::Scalar: FromUniformBytes<64>, +{ /// Get the underlying [`VerifyingKey`]. pub fn get_vk(&self) -> &VerifyingKey { &self.vk } + + /// Gets the total number of bytes in the serialization of `self` + fn bytes_length(&self) -> usize { + let scalar_len = C::Scalar::default().to_repr().as_ref().len(); + self.vk.bytes_length() + + 12 + + scalar_len * (self.l0.len() + self.l_last.len() + self.l_active_row.len()) + + polynomial_slice_byte_length(&self.fixed_values) + + polynomial_slice_byte_length(&self.fixed_polys) + + polynomial_slice_byte_length(&self.fixed_cosets) + + self.permutation.bytes_length() + } +} + +impl ProvingKey +where + C::Scalar: SerdePrimeField + FromUniformBytes<64>, +{ + /// Writes a proving key to a buffer. + /// + /// Writes a curve element according to `format`: + /// - `Processed`: Writes a compressed curve element with coordinates in standard form. + /// Writes a field element in standard form, with endianness specified by the + /// `PrimeField` implementation. + /// - Otherwise: Writes an uncompressed curve element with coordinates in Montgomery form + /// Writes a field element into raw bytes in its internal Montgomery representation, + /// WITHOUT performing the expensive Montgomery reduction. + /// Does so by first writing the verifying key and then serializing the rest of the data (in the form of field polynomials) + pub fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> { + self.vk.write(writer, format)?; + self.l0.write(writer, format)?; + self.l_last.write(writer, format)?; + self.l_active_row.write(writer, format)?; + write_polynomial_slice(&self.fixed_values, writer, format)?; + write_polynomial_slice(&self.fixed_polys, writer, format)?; + write_polynomial_slice(&self.fixed_cosets, writer, format)?; + self.permutation.write(writer, format)?; + Ok(()) + } + + /// Reads a proving key from a buffer. + /// Does so by reading verification key first, and then deserializing the rest of the file into the remaining proving key data. + /// + /// Reads a curve element from the buffer and parses it according to the `format`: + /// - `Processed`: Reads a compressed curve element and decompresses it. + /// Reads a field element in standard form, with endianness specified by the + /// `PrimeField` implementation, and checks that the element is less than the modulus. + /// - `RawBytes`: Reads an uncompressed curve element with coordinates in Montgomery form. + /// Checks that field elements are less than modulus, and then checks that the point is on the curve. + /// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form; + /// does not perform any checks + pub fn read>( + reader: &mut R, + format: SerdeFormat, + #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, + ) -> io::Result { + let vk = VerifyingKey::::read::( + reader, + format, + #[cfg(feature = "circuit-params")] + params, + )?; + let l0 = Polynomial::read(reader, format)?; + let l_last = Polynomial::read(reader, format)?; + let l_active_row = Polynomial::read(reader, format)?; + let fixed_values = read_polynomial_vec(reader, format)?; + let fixed_polys = read_polynomial_vec(reader, format)?; + let fixed_cosets = read_polynomial_vec(reader, format)?; + let permutation = permutation::ProvingKey::read(reader, format)?; + let ev = Evaluator::new(vk.cs()); + Ok(Self { + vk, + l0, + l_last, + l_active_row, + fixed_values, + fixed_polys, + fixed_cosets, + permutation, + ev, + }) + } + + /// Writes a proving key to a vector of bytes using [`Self::write`]. + pub fn to_bytes(&self, format: SerdeFormat) -> Vec { + let mut bytes = Vec::::with_capacity(self.bytes_length()); + Self::write(self, &mut bytes, format).expect("Writing to vector should not fail"); + bytes + } + + /// Reads a proving key from a slice of bytes using [`Self::read`]. + pub fn from_bytes>( + mut bytes: &[u8], + format: SerdeFormat, + #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, + ) -> io::Result { + Self::read::<_, ConcreteCircuit>( + &mut bytes, + format, + #[cfg(feature = "circuit-params")] + params, + ) + } } impl VerifyingKey { diff --git a/halo2_proofs/src/plonk/assigned.rs b/halo2_proofs/src/plonk/assigned.rs index 7524291e..46d527b9 100644 --- a/halo2_proofs/src/plonk/assigned.rs +++ b/halo2_proofs/src/plonk/assigned.rs @@ -280,7 +280,7 @@ impl Assigned { /// Returns the numerator. pub fn numerator(&self) -> F { match self { - Self::Zero => F::zero(), + Self::Zero => F::ZERO, Self::Trivial(x) => *x, Self::Rational(numerator, _) => *numerator, } @@ -341,7 +341,7 @@ impl Assigned { pub fn invert(&self) -> Self { match self { Self::Zero => Self::Zero, - Self::Trivial(x) => Self::Rational(F::one(), *x), + Self::Trivial(x) => Self::Rational(F::ONE, *x), Self::Rational(numerator, denominator) => Self::Rational(*denominator, *numerator), } } @@ -352,13 +352,13 @@ impl Assigned { /// If the denominator is zero, this returns zero. pub fn evaluate(self) -> F { match self { - Self::Zero => F::zero(), + Self::Zero => F::ZERO, Self::Trivial(x) => x, Self::Rational(numerator, denominator) => { - if denominator == F::one() { + if denominator == F::ONE { numerator } else { - numerator * denominator.invert().unwrap_or(F::zero()) + numerator * denominator.invert().unwrap_or(F::ZERO) } } } @@ -451,7 +451,7 @@ mod proptests { }; use group::ff::Field; - use halo2curves::{pasta::Fp, FieldExt}; + use halo2curves::pasta::Fp; use proptest::{collection::vec, prelude::*, sample::select}; use super::Assigned; @@ -477,7 +477,7 @@ mod proptests { } fn inv0(&self) -> Self { - self.invert().unwrap_or(F::zero()) + self.invert().unwrap_or(F::ZERO) } } diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index 6dbd4a54..1c55eb5c 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -1,23 +1,30 @@ +use super::{lookup, permutation, Assigned, Error}; +use crate::circuit::layouter::SyncDeps; +use crate::dev::metadata; +use crate::{ + circuit::{Layouter, Region, Value}, + poly::Rotation, +}; use core::cmp::max; use core::ops::{Add, Mul}; use ff::Field; +use sealed::SealedPhase; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; use std::{ convert::TryFrom, ops::{Neg, Sub}, }; -use super::{lookup, permutation, Assigned, Error}; -use crate::{ - circuit::{Layouter, Region, Value}, - poly::Rotation, -}; - mod compress_selectors; /// A column type pub trait ColumnType: 'static + Sized + Copy + std::fmt::Debug + PartialEq + Eq + Into { + /// Return expression from cell + fn query_cell(&self, index: usize, at: Rotation) -> Expression; } /// A column with an index and type @@ -42,6 +49,31 @@ impl Column { pub fn column_type(&self) -> &C { &self.column_type } + + /// Return expression from column at a relative position + pub fn query_cell(&self, at: Rotation) -> Expression { + self.column_type.query_cell(self.index, at) + } + + /// Return expression from column at the current row + pub fn cur(&self) -> Expression { + self.query_cell(Rotation::cur()) + } + + /// Return expression from column at the next row + pub fn next(&self) -> Expression { + self.query_cell(Rotation::next()) + } + + /// Return expression from column at the previous row + pub fn prev(&self) -> Expression { + self.query_cell(Rotation::prev()) + } + + /// Return expression from column at the specified rotation + pub fn rot(&self, rotation: i32) -> Expression { + self.query_cell(Rotation(rotation)) + } } impl Ord for Column { @@ -62,9 +94,102 @@ impl PartialOrd for Column { } } +pub(crate) mod sealed { + /// Phase of advice column + #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] + pub struct Phase(pub(super) u8); + + impl Phase { + pub fn prev(&self) -> Option { + self.0.checked_sub(1).map(Phase) + } + } + + 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; + } +} + +/// Phase of advice column +pub trait Phase: SealedPhase {} + +impl Phase for P {} + +/// First phase +#[derive(Debug)] +pub struct FirstPhase; + +impl SealedPhase for super::FirstPhase { + fn to_sealed(self) -> sealed::Phase { + sealed::Phase(0) + } +} + +/// Second phase +#[derive(Debug)] +pub struct SecondPhase; + +impl SealedPhase for super::SecondPhase { + fn to_sealed(self) -> sealed::Phase { + sealed::Phase(1) + } +} + +/// Third phase +#[derive(Debug)] +pub struct ThirdPhase; + +impl SealedPhase for super::ThirdPhase { + fn to_sealed(self) -> sealed::Phase { + sealed::Phase(2) + } +} + /// An advice column -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -pub struct Advice; +#[derive(Clone, Copy, Eq, PartialEq, Hash)] +pub struct Advice { + pub(crate) phase: sealed::Phase, +} + +impl Default for Advice { + fn default() -> Advice { + Advice { + phase: FirstPhase.to_sealed(), + } + } +} + +impl Advice { + /// Returns `Advice` in given `Phase` + pub fn new(phase: P) -> Advice { + Advice { + phase: phase.to_sealed(), + } + } + + /// Phase of this column + pub fn phase(&self) -> u8 { + self.phase.0 + } +} + +impl std::fmt::Debug for Advice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug_struct = f.debug_struct("Advice"); + // Only show advice's phase if it's not in first phase. + if self.phase != FirstPhase.to_sealed() { + debug_struct.field("phase", &self.phase); + } + debug_struct.finish() + } +} /// A fixed column #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] @@ -75,31 +200,59 @@ pub struct Fixed; pub struct Instance; /// An enum over the Advice, Fixed, Instance structs -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, Eq, PartialEq, Hash)] pub enum Any { /// An Advice variant - Advice, + Advice(Advice), /// A Fixed variant Fixed, /// An Instance variant Instance, } +impl Any { + /// Returns Advice variant in `FirstPhase` + pub fn advice() -> Any { + Any::Advice(Advice::default()) + } + + /// Returns Advice variant in given `Phase` + pub fn advice_in(phase: P) -> Any { + Any::Advice(Advice::new(phase)) + } +} + +impl std::fmt::Debug for Any { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Any::Advice(advice) => { + let mut debug_struct = f.debug_struct("Advice"); + // Only show advice's phase if it's not in first phase. + if advice.phase != FirstPhase.to_sealed() { + debug_struct.field("phase", &advice.phase); + } + debug_struct.finish() + } + Any::Fixed => f.debug_struct("Fixed").finish(), + Any::Instance => f.debug_struct("Instance").finish(), + } + } +} + impl Ord for Any { fn cmp(&self, other: &Self) -> std::cmp::Ordering { // This ordering is consensus-critical! The layouters rely on deterministic column // orderings. match (self, other) { - (Any::Instance, Any::Instance) - | (Any::Advice, Any::Advice) - | (Any::Fixed, Any::Fixed) => std::cmp::Ordering::Equal, + (Any::Instance, Any::Instance) | (Any::Fixed, Any::Fixed) => std::cmp::Ordering::Equal, + (Any::Advice(lhs), Any::Advice(rhs)) => lhs.phase.cmp(&rhs.phase), // Across column types, sort Instance < Advice < Fixed. - (Any::Instance, Any::Advice) - | (Any::Advice, Any::Fixed) + (Any::Instance, Any::Advice(_)) + | (Any::Advice(_), Any::Fixed) | (Any::Instance, Any::Fixed) => std::cmp::Ordering::Less, (Any::Fixed, Any::Instance) - | (Any::Fixed, Any::Advice) - | (Any::Advice, Any::Instance) => std::cmp::Ordering::Greater, + | (Any::Fixed, Any::Advice(_)) + | (Any::Advice(_), Any::Instance) => std::cmp::Ordering::Greater, } } } @@ -110,14 +263,60 @@ impl PartialOrd for Any { } } -impl ColumnType for Advice {} -impl ColumnType for Fixed {} -impl ColumnType for Instance {} -impl ColumnType for Any {} +impl ColumnType for Advice { + fn query_cell(&self, index: usize, at: Rotation) -> Expression { + Expression::Advice(AdviceQuery { + index: None, + column_index: index, + rotation: at, + phase: self.phase, + }) + } +} +impl ColumnType for Fixed { + fn query_cell(&self, index: usize, at: Rotation) -> Expression { + Expression::Fixed(FixedQuery { + index: None, + column_index: index, + rotation: at, + }) + } +} +impl ColumnType for Instance { + fn query_cell(&self, index: usize, at: Rotation) -> Expression { + Expression::Instance(InstanceQuery { + index: None, + column_index: index, + rotation: at, + }) + } +} +impl ColumnType for Any { + fn query_cell(&self, index: usize, at: Rotation) -> Expression { + match self { + Any::Advice(Advice { phase }) => Expression::Advice(AdviceQuery { + index: None, + column_index: index, + rotation: at, + phase: *phase, + }), + Any::Fixed => Expression::Fixed(FixedQuery { + index: None, + column_index: index, + rotation: at, + }), + Any::Instance => Expression::Instance(InstanceQuery { + index: None, + column_index: index, + rotation: at, + }), + } + } +} impl From for Any { - fn from(_: Advice) -> Any { - Any::Advice + fn from(advice: Advice) -> Any { + Any::Advice(advice) } } @@ -137,7 +336,7 @@ impl From> for Column { fn from(advice: Column) -> Column { Column { index: advice.index(), - column_type: Any::Advice, + column_type: Any::Advice(advice.column_type), } } } @@ -165,9 +364,9 @@ impl TryFrom> for Column { fn try_from(any: Column) -> Result { match any.column_type() { - Any::Advice => Ok(Column { + Any::Advice(advice) => Ok(Column { index: any.index(), - column_type: Advice, + column_type: *advice, }), _ => Err("Cannot convert into Column"), } @@ -230,11 +429,10 @@ impl TryFrom> for Column { /// row when required: /// ``` /// use halo2_proofs::{ -/// arithmetic::FieldExt, /// circuit::{Chip, Layouter, Value}, /// plonk::{Advice, Column, Error, Selector}, /// }; -/// # use ff::Field; +/// use ff::Field; /// # use halo2_proofs::plonk::Fixed; /// /// struct Config { @@ -243,12 +441,12 @@ impl TryFrom> for Column { /// s: Selector, /// } /// -/// fn circuit_logic>(chip: C, mut layouter: impl Layouter) -> Result<(), Error> { +/// fn circuit_logic>(chip: C, mut layouter: impl Layouter) -> Result<(), Error> { /// let config = chip.config(); /// # let config: Config = todo!(); /// layouter.assign_region(|| "bar", |mut region| { -/// region.assign_advice(|| "a", config.a, 0, || Value::known(F::one()))?; -/// region.assign_advice(|| "a", config.b, 1, || Value::known(F::one()))?; +/// region.assign_advice(|| "a", config.a, 0, || Value::known(F::ONE))?; +/// region.assign_advice(|| "a", config.b, 1, || Value::known(F::ONE))?; /// config.s.enable(&mut region, 1) /// })?; /// Ok(()) @@ -268,13 +466,23 @@ impl Selector { pub fn is_simple(&self) -> bool { self.1 } + + /// Returns index of this selector + pub fn index(&self) -> usize { + self.0 + } + + /// Return expression from selector + pub fn expr(&self) -> Expression { + Expression::Selector(*self) + } } /// Query of fixed column at a certain relative location #[derive(Copy, Clone, Debug)] pub struct FixedQuery { /// Query index - pub(crate) index: usize, + pub(crate) index: Option, /// Column index pub(crate) column_index: usize, /// Rotation of this query @@ -297,11 +505,13 @@ impl FixedQuery { #[derive(Copy, Clone, Debug)] pub struct AdviceQuery { /// Query index - pub(crate) index: usize, + pub(crate) index: Option, /// Column index pub(crate) column_index: usize, /// Rotation of this query pub(crate) rotation: Rotation, + /// Phase of this advice column + pub(crate) phase: sealed::Phase, } impl AdviceQuery { @@ -314,13 +524,18 @@ impl AdviceQuery { pub fn rotation(&self) -> Rotation { self.rotation } + + /// Phase of this advice column + pub fn phase(&self) -> u8 { + self.phase.0 + } } /// Query of instance column at a certain relative location #[derive(Copy, Clone, Debug)] pub struct InstanceQuery { /// Query index - pub(crate) index: usize, + pub(crate) index: Option, /// Column index pub(crate) column_index: usize, /// Rotation of this query @@ -367,6 +582,30 @@ impl TableColumn { } } +/// A challenge squeezed from transcript after advice columns at the phase have been committed. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub struct Challenge { + index: usize, + pub(crate) phase: sealed::Phase, +} + +impl Challenge { + /// Index of this challenge. + pub fn index(&self) -> usize { + self.index + } + + /// Phase of this challenge. + pub fn phase(&self) -> u8 { + self.phase.0 + } + + /// Return Expression + pub fn expr(&self) -> Expression { + Expression::Challenge(*self) + } +} + /// This trait allows a [`Circuit`] to direct some backend to assign a witness /// for a constraint system. pub trait Assignment { @@ -382,6 +621,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). @@ -452,6 +699,11 @@ pub trait Assignment { to: Value>, ) -> Result<(), Error>; + /// Queries the value of the given challenge. + /// + /// Returns `Value::unknown()` if the current synthesis phase is before the challenge can be queried. + fn get_challenge(&self, challenge: Challenge) -> Value; + /// Creates a new (sub)namespace and enters into it. /// /// Not intended for downstream consumption; use [`Layouter::namespace`] instead. @@ -485,7 +737,7 @@ pub trait FloorPlanner { /// - Perform any necessary setup or measurement tasks, which may involve one or more /// calls to `Circuit::default().synthesize(config, &mut layouter)`. /// - Call `circuit.synthesize(config, &mut layouter)` exactly once. - fn synthesize, C: Circuit>( + fn synthesize + SyncDeps, C: Circuit>( cs: &mut CS, circuit: &C, config: C::Config, @@ -502,11 +754,33 @@ pub trait Circuit { /// The floor planner used for this circuit. This is an associated type of the /// `Circuit` trait because its behaviour is circuit-critical. type FloorPlanner: FloorPlanner; + /// Optional circuit configuration parameters. Requires the `circuit-params` feature. + #[cfg(feature = "circuit-params")] + type Params: Default; /// Returns a copy of this circuit with no witness values (i.e. all witnesses set to /// `None`). For most circuits, this will be equal to `Self::default()`. fn without_witnesses(&self) -> Self; + /// Returns a reference to the parameters that should be used to configure the circuit. + /// Requires the `circuit-params` feature. + #[cfg(feature = "circuit-params")] + fn params(&self) -> Self::Params { + Self::Params::default() + } + + /// The circuit is given an opportunity to describe the exact gate + /// arrangement, column arrangement, etc. Takes a runtime parameter. The default + /// implementation calls `configure` ignoring the `_params` argument in order to easily support + /// circuits that don't use configuration parameters. + #[cfg(feature = "circuit-params")] + fn configure_with_params( + meta: &mut ConstraintSystem, + _params: Self::Params, + ) -> Self::Config { + Self::configure(meta) + } + /// The circuit is given an opportunity to describe the exact gate /// arrangement, column arrangement, etc. fn configure(meta: &mut ConstraintSystem) -> Self::Config; @@ -530,6 +804,8 @@ pub enum Expression { Advice(AdviceQuery), /// This is an instance (external) column queried at a certain relative location Instance(InstanceQuery), + /// This is a challenge + Challenge(Challenge), /// This is a negated polynomial Negated(Box>), /// This is the sum of two polynomials @@ -541,6 +817,59 @@ pub enum Expression { } impl Expression { + /// Make side effects + pub fn query_cells(&mut self, cells: &mut VirtualCells<'_, F>) { + match self { + Expression::Constant(_) => (), + Expression::Selector(selector) => { + if !cells.queried_selectors.contains(selector) { + cells.queried_selectors.push(*selector); + } + } + Expression::Fixed(query) => { + if query.index.is_none() { + let col = Column { + index: query.column_index, + column_type: Fixed, + }; + cells.queried_cells.push((col, query.rotation).into()); + query.index = Some(cells.meta.query_fixed_index(col, query.rotation)); + } + } + Expression::Advice(query) => { + if query.index.is_none() { + let col = Column { + index: query.column_index, + column_type: Advice { phase: query.phase }, + }; + cells.queried_cells.push((col, query.rotation).into()); + query.index = Some(cells.meta.query_advice_index(col, query.rotation)); + } + } + Expression::Instance(query) => { + if query.index.is_none() { + let col = Column { + index: query.column_index, + column_type: Instance, + }; + cells.queried_cells.push((col, query.rotation).into()); + query.index = Some(cells.meta.query_instance_index(col, query.rotation)); + } + } + Expression::Challenge(_) => (), + Expression::Negated(a) => a.query_cells(cells), + Expression::Sum(a, b) => { + a.query_cells(cells); + b.query_cells(cells); + } + Expression::Product(a, b) => { + a.query_cells(cells); + b.query_cells(cells); + } + Expression::Scaled(a, _) => a.query_cells(cells), + }; + } + /// Evaluate the polynomial using the provided closures to perform the /// operations. pub fn evaluate( @@ -550,6 +879,7 @@ impl Expression { fixed_column: &impl Fn(FixedQuery) -> T, advice_column: &impl Fn(AdviceQuery) -> T, instance_column: &impl Fn(InstanceQuery) -> T, + challenge: &impl Fn(Challenge) -> T, negated: &impl Fn(T) -> T, sum: &impl Fn(T, T) -> T, product: &impl Fn(T, T) -> T, @@ -561,6 +891,7 @@ impl Expression { Expression::Fixed(query) => fixed_column(*query), Expression::Advice(query) => advice_column(*query), Expression::Instance(query) => instance_column(*query), + Expression::Challenge(value) => challenge(*value), Expression::Negated(a) => { let a = a.evaluate( constant, @@ -568,6 +899,7 @@ impl Expression { fixed_column, advice_column, instance_column, + challenge, negated, sum, product, @@ -582,6 +914,7 @@ impl Expression { fixed_column, advice_column, instance_column, + challenge, negated, sum, product, @@ -593,6 +926,7 @@ impl Expression { fixed_column, advice_column, instance_column, + challenge, negated, sum, product, @@ -607,6 +941,7 @@ impl Expression { fixed_column, advice_column, instance_column, + challenge, negated, sum, product, @@ -618,6 +953,7 @@ impl Expression { fixed_column, advice_column, instance_column, + challenge, negated, sum, product, @@ -632,6 +968,7 @@ impl Expression { fixed_column, advice_column, instance_column, + challenge, negated, sum, product, @@ -651,6 +988,7 @@ impl Expression { fixed_column: &impl Fn(FixedQuery) -> T, advice_column: &impl Fn(AdviceQuery) -> T, instance_column: &impl Fn(InstanceQuery) -> T, + challenge: &impl Fn(Challenge) -> T, negated: &impl Fn(T) -> T, sum: &impl Fn(T, T) -> T, product: &impl Fn(T, T) -> T, @@ -663,6 +1001,7 @@ impl Expression { Expression::Fixed(query) => fixed_column(*query), Expression::Advice(query) => advice_column(*query), Expression::Instance(query) => instance_column(*query), + Expression::Challenge(value) => challenge(*value), Expression::Negated(a) => { let a = a.evaluate_lazy( constant, @@ -670,6 +1009,7 @@ impl Expression { fixed_column, advice_column, instance_column, + challenge, negated, sum, product, @@ -685,6 +1025,7 @@ impl Expression { fixed_column, advice_column, instance_column, + challenge, negated, sum, product, @@ -697,6 +1038,7 @@ impl Expression { fixed_column, advice_column, instance_column, + challenge, negated, sum, product, @@ -717,6 +1059,7 @@ impl Expression { fixed_column, advice_column, instance_column, + challenge, negated, sum, product, @@ -733,6 +1076,7 @@ impl Expression { fixed_column, advice_column, instance_column, + challenge, negated, sum, product, @@ -749,6 +1093,7 @@ impl Expression { fixed_column, advice_column, instance_column, + challenge, negated, sum, product, @@ -760,37 +1105,69 @@ impl Expression { } } - /// Identifier for this expression. Expressions with identical identifiers - /// do the same calculation (but the expressions don't need to be exactly equal - /// in how they are composed e.g. `1 + 2` and `2 + 1` can have the same identifier). - pub fn identifier(&self) -> String { + fn write_identifier(&self, writer: &mut W) -> std::io::Result<()> { match self { - Expression::Constant(scalar) => format!("{:?}", scalar), - Expression::Selector(selector) => format!("selector[{}]", selector.0), + Expression::Constant(scalar) => write!(writer, "{:?}", scalar), + Expression::Selector(selector) => write!(writer, "selector[{}]", selector.0), Expression::Fixed(query) => { - format!("fixed[{}][{}]", query.column_index, query.rotation.0) + write!( + writer, + "fixed[{}][{}]", + query.column_index, query.rotation.0 + ) } Expression::Advice(query) => { - format!("advice[{}][{}]", query.column_index, query.rotation.0) + write!( + writer, + "advice[{}][{}]", + query.column_index, query.rotation.0 + ) } Expression::Instance(query) => { - format!("instance[{}][{}]", query.column_index, query.rotation.0) + write!( + writer, + "instance[{}][{}]", + query.column_index, query.rotation.0 + ) + } + Expression::Challenge(challenge) => { + write!(writer, "challenge[{}]", challenge.index()) } Expression::Negated(a) => { - format!("(-{})", a.identifier()) + writer.write_all(b"(-")?; + a.write_identifier(writer)?; + writer.write_all(b")") } Expression::Sum(a, b) => { - format!("({}+{})", a.identifier(), b.identifier()) + writer.write_all(b"(")?; + a.write_identifier(writer)?; + writer.write_all(b"+")?; + b.write_identifier(writer)?; + writer.write_all(b")") } Expression::Product(a, b) => { - format!("({}*{})", a.identifier(), b.identifier()) + writer.write_all(b"(")?; + a.write_identifier(writer)?; + writer.write_all(b"*")?; + b.write_identifier(writer)?; + writer.write_all(b")") } Expression::Scaled(a, f) => { - format!("{}*{:?}", a.identifier(), f) + a.write_identifier(writer)?; + write!(writer, "*{:?}", f) } } } + /// Identifier for this expression. Expressions with identical identifiers + /// do the same calculation (but the expressions don't need to be exactly equal + /// in how they are composed e.g. `1 + 2` and `2 + 1` can have the same identifier). + pub fn identifier(&self) -> String { + let mut cursor = std::io::Cursor::new(Vec::new()); + self.write_identifier(&mut cursor).unwrap(); + String::from_utf8(cursor.into_inner()).unwrap() + } + /// Compute the degree of this polynomial pub fn degree(&self) -> usize { match self { @@ -799,6 +1176,7 @@ impl Expression { Expression::Fixed(_) => 1, Expression::Advice(_) => 1, Expression::Instance(_) => 1, + Expression::Challenge(_) => 0, Expression::Negated(poly) => poly.degree(), Expression::Sum(a, b) => max(a.degree(), b.degree()), Expression::Product(a, b) => a.degree() + b.degree(), @@ -814,6 +1192,7 @@ impl Expression { Expression::Fixed(_) => 1, Expression::Advice(_) => 1, Expression::Instance(_) => 1, + Expression::Challenge(_) => 0, Expression::Negated(poly) => poly.complexity() + 5, Expression::Sum(a, b) => a.complexity() + b.complexity() + 15, Expression::Product(a, b) => a.complexity() + b.complexity() + 30, @@ -834,6 +1213,7 @@ impl Expression { &|_| false, &|_| false, &|_| false, + &|_| false, &|a| a, &|a, b| a || b, &|a, b| a || b, @@ -861,6 +1241,7 @@ impl Expression { &|_| None, &|_| None, &|_| None, + &|_| None, &|a| a, &op, &op, @@ -875,36 +1256,46 @@ impl std::fmt::Debug for Expression { Expression::Constant(scalar) => f.debug_tuple("Constant").field(scalar).finish(), Expression::Selector(selector) => f.debug_tuple("Selector").field(selector).finish(), // Skip enum variant and print query struct directly to maintain backwards compatibility. - Expression::Fixed(FixedQuery { - index, - column_index, - rotation, - }) => f - .debug_struct("Fixed") - .field("query_index", index) - .field("column_index", column_index) - .field("rotation", rotation) - .finish(), - Expression::Advice(AdviceQuery { - index, - column_index, - rotation, - }) => f - .debug_struct("Advice") - .field("query_index", index) - .field("column_index", column_index) - .field("rotation", rotation) - .finish(), - Expression::Instance(InstanceQuery { - index, - column_index, - rotation, - }) => f - .debug_struct("Instance") - .field("query_index", index) - .field("column_index", column_index) - .field("rotation", rotation) - .finish(), + Expression::Fixed(query) => { + let mut debug_struct = f.debug_struct("Fixed"); + match query.index { + None => debug_struct.field("query_index", &query.index), + Some(idx) => debug_struct.field("query_index", &idx), + }; + debug_struct + .field("column_index", &query.column_index) + .field("rotation", &query.rotation) + .finish() + } + Expression::Advice(query) => { + let mut debug_struct = f.debug_struct("Advice"); + match query.index { + None => debug_struct.field("query_index", &query.index), + Some(idx) => debug_struct.field("query_index", &idx), + }; + debug_struct + .field("column_index", &query.column_index) + .field("rotation", &query.rotation); + // Only show advice's phase if it's not in first phase. + if query.phase != FirstPhase.to_sealed() { + debug_struct.field("phase", &query.phase); + } + debug_struct.finish() + } + Expression::Instance(query) => { + let mut debug_struct = f.debug_struct("Instance"); + match query.index { + None => debug_struct.field("query_index", &query.index), + Some(idx) => debug_struct.field("query_index", &idx), + }; + debug_struct + .field("column_index", &query.column_index) + .field("rotation", &query.rotation) + .finish() + } + Expression::Challenge(challenge) => { + f.debug_tuple("Challenge").field(challenge).finish() + } Expression::Negated(poly) => f.debug_tuple("Negated").field(poly).finish(), Expression::Sum(a, b) => f.debug_tuple("Sum").field(a).field(b).finish(), Expression::Product(a, b) => f.debug_tuple("Product").field(a).field(b).finish(), @@ -986,25 +1377,34 @@ impl>> From<(Col, Rotation)> for VirtualCell { /// These are returned by the closures passed to `ConstraintSystem::create_gate`. #[derive(Debug)] pub struct Constraint { - name: &'static str, + name: String, poly: Expression, } impl From> for Constraint { fn from(poly: Expression) -> Self { - Constraint { name: "", poly } + Constraint { + name: "".to_string(), + poly, + } } } -impl From<(&'static str, Expression)> for Constraint { - fn from((name, poly): (&'static str, Expression)) -> Self { - Constraint { name, poly } +impl> From<(S, Expression)> for Constraint { + fn from((name, poly): (S, Expression)) -> Self { + Constraint { + name: name.as_ref().to_string(), + poly, + } } } impl From> for Vec> { fn from(poly: Expression) -> Self { - vec![Constraint { name: "", poly }] + vec![Constraint { + name: "".to_string(), + poly, + }] } } @@ -1094,8 +1494,8 @@ impl>, Iter: IntoIterator> IntoIterato /// Gate #[derive(Clone, Debug)] pub struct Gate { - name: &'static str, - constraint_names: Vec<&'static str>, + name: String, + constraint_names: Vec, polys: Vec>, /// We track queried selectors separately from other cells, so that we can use them to /// trigger debug checks on gates. @@ -1104,12 +1504,14 @@ pub struct Gate { } impl Gate { - pub(crate) fn name(&self) -> &'static str { - self.name + /// Returns the gate name. + pub fn name(&self) -> &str { + self.name.as_str() } - pub(crate) fn constraint_name(&self, constraint_index: usize) -> &'static str { - self.constraint_names[constraint_index] + /// Returns the name of the constraint at index `constraint_index`. + pub fn constraint_name(&self, constraint_index: usize) -> &str { + self.constraint_names[constraint_index].as_str() } /// Returns constraints of this gate @@ -1134,6 +1536,12 @@ pub struct ConstraintSystem { pub(crate) num_advice_columns: usize, pub(crate) num_instance_columns: usize, pub(crate) num_selectors: usize, + pub(crate) num_challenges: usize, + + /// Contains the phase for each advice column. Should have same length as num_advice_columns. + pub(crate) advice_column_phase: Vec, + /// Contains the phase for each challenge. Should have same length as num_challenges. + pub(crate) challenge_phase: Vec, /// This is a cached vector that maps virtual selectors to the concrete /// fixed column that they were compressed into. This is just used by dev @@ -1156,6 +1564,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>, @@ -1165,12 +1576,14 @@ pub struct ConstraintSystem { /// Represents the minimal parameters that determine a `ConstraintSystem`. #[allow(dead_code)] -#[derive(Debug)] pub struct PinnedConstraintSystem<'a, F: Field> { num_fixed_columns: &'a usize, num_advice_columns: &'a usize, num_instance_columns: &'a usize, num_selectors: &'a usize, + num_challenges: &'a usize, + advice_column_phase: &'a Vec, + challenge_phase: &'a Vec, gates: PinnedGates<'a, F>, advice_queries: &'a Vec<(Column, Rotation)>, instance_queries: &'a Vec<(Column, Rotation)>, @@ -1181,6 +1594,34 @@ pub struct PinnedConstraintSystem<'a, F: Field> { minimum_degree: &'a Option, } +impl<'a, F: Field> std::fmt::Debug for PinnedConstraintSystem<'a, F> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug_struct = f.debug_struct("PinnedConstraintSystem"); + debug_struct + .field("num_fixed_columns", self.num_fixed_columns) + .field("num_advice_columns", self.num_advice_columns) + .field("num_instance_columns", self.num_instance_columns) + .field("num_selectors", self.num_selectors); + // Only show multi-phase related fields if it's used. + if *self.num_challenges > 0 { + debug_struct + .field("num_challenges", self.num_challenges) + .field("advice_column_phase", self.advice_column_phase) + .field("challenge_phase", self.challenge_phase); + } + debug_struct + .field("gates", &self.gates) + .field("advice_queries", self.advice_queries) + .field("instance_queries", self.instance_queries) + .field("fixed_queries", self.fixed_queries) + .field("permutation", self.permutation) + .field("lookups", self.lookups) + .field("constants", self.constants) + .field("minimum_degree", self.minimum_degree); + debug_struct.finish() + } +} + struct PinnedGates<'a, F: Field>(&'a Vec>); impl<'a, F: Field> std::fmt::Debug for PinnedGates<'a, F> { @@ -1198,6 +1639,9 @@ impl Default for ConstraintSystem { num_advice_columns: 0, num_instance_columns: 0, num_selectors: 0, + num_challenges: 0, + advice_column_phase: Vec::new(), + challenge_phase: Vec::new(), selector_map: vec![], gates: vec![], fixed_queries: Vec::new(), @@ -1206,6 +1650,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, } @@ -1222,6 +1667,9 @@ impl ConstraintSystem { num_advice_columns: &self.num_advice_columns, num_instance_columns: &self.num_instance_columns, num_selectors: &self.num_selectors, + num_challenges: &self.num_challenges, + advice_column_phase: &self.advice_column_phase, + challenge_phase: &self.challenge_phase, gates: PinnedGates(&self.gates), fixed_queries: &self.fixed_queries, advice_queries: &self.advice_queries, @@ -1256,28 +1704,28 @@ impl ConstraintSystem { /// /// `table_map` returns a map between input expressions and the table columns /// they need to match. - pub fn lookup( + pub fn lookup>( &mut self, - name: &'static str, + name: S, table_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, TableColumn)>, ) -> usize { let mut cells = VirtualCells::new(self); let table_map = table_map(&mut cells) .into_iter() - .map(|(input, table)| { + .map(|(mut input, table)| { if input.contains_simple_selector() { panic!("expression containing simple selector supplied to lookup argument"); } - - let table = cells.query_fixed(table.inner(), Rotation::cur()); - + let mut table = cells.query_fixed(table.inner(), Rotation::cur()); + input.query_cells(&mut cells); + table.query_cells(&mut cells); (input, table) }) .collect(); - let index = self.lookups.len(); - self.lookups.push(lookup::Argument::new(name, table_map)); + self.lookups + .push(lookup::Argument::new(name.as_ref(), table_map)); index } @@ -1286,17 +1734,24 @@ impl ConstraintSystem { /// /// `table_map` returns a map between input expressions and the table expressions /// they need to match. - pub fn lookup_any( + pub fn lookup_any>( &mut self, - name: &'static str, + name: S, table_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, Expression)>, ) -> usize { let mut cells = VirtualCells::new(self); - let table_map = table_map(&mut cells); - + let table_map = table_map(&mut cells) + .into_iter() + .map(|(mut input, mut table)| { + input.query_cells(&mut cells); + table.query_cells(&mut cells); + (input, table) + }) + .collect(); let index = self.lookups.len(); - self.lookups.push(lookup::Argument::new(name, table_map)); + self.lookups + .push(lookup::Argument::new(name.as_ref(), table_map)); index } @@ -1349,7 +1804,9 @@ impl ConstraintSystem { fn query_any_index(&mut self, column: Column, at: Rotation) -> usize { match column.column_type() { - Any::Advice => self.query_advice_index(Column::::try_from(column).unwrap(), at), + Any::Advice(_) => { + self.query_advice_index(Column::::try_from(column).unwrap(), at) + } Any::Fixed => self.query_fixed_index(Column::::try_from(column).unwrap(), at), Any::Instance => { self.query_instance_index(Column::::try_from(column).unwrap(), at) @@ -1389,7 +1846,7 @@ impl ConstraintSystem { pub(crate) fn get_any_query_index(&self, column: Column, at: Rotation) -> usize { match column.column_type() { - Any::Advice => { + Any::Advice(_) => { self.get_advice_query_index(Column::::try_from(column).unwrap(), at) } Any::Fixed => { @@ -1414,29 +1871,32 @@ impl ConstraintSystem { /// /// A gate is required to contain polynomial constraints. This method will panic if /// `constraints` returns an empty iterator. - pub fn create_gate>, Iter: IntoIterator>( + pub fn create_gate>, Iter: IntoIterator, S: AsRef>( &mut self, - name: &'static str, + name: S, constraints: impl FnOnce(&mut VirtualCells<'_, F>) -> Iter, ) { let mut cells = VirtualCells::new(self); let constraints = constraints(&mut cells); - let queried_selectors = cells.queried_selectors; - let queried_cells = cells.queried_cells; - let (constraint_names, polys): (_, Vec<_>) = constraints .into_iter() .map(|c| c.into()) - .map(|c| (c.name, c.poly)) + .map(|mut c: Constraint| { + c.poly.query_cells(&mut cells); + (c.name, c.poly) + }) .unzip(); + let queried_selectors = cells.queried_selectors; + let queried_cells = cells.queried_cells; + assert!( !polys.is_empty(), "Gates must contain at least one constraint." ); self.gates.push(Gate { - name, + name: name.as_ref().to_string(), constraint_names, polys, queried_selectors, @@ -1451,7 +1911,7 @@ impl ConstraintSystem { /// find which fixed column corresponds with a given `Selector`. /// /// Do not call this twice. Yes, this should be a builder pattern instead. - pub(crate) fn compress_selectors(mut self, selectors: Vec>) -> (Self, Vec>) { + pub fn compress_selectors(mut self, selectors: Vec>) -> (Self, Vec>) { // The number of provided selector assignments must be the number we // counted for this constraint system. assert_eq!(selectors.len(), self.num_selectors); @@ -1490,7 +1950,7 @@ impl ConstraintSystem { let column = self.fixed_column(); new_columns.push(column); Expression::Fixed(FixedQuery { - index: self.query_fixed_index(column, Rotation::cur()), + index: Some(self.query_fixed_index(column, Rotation::cur())), column_index: column.index, rotation: Rotation::cur(), }) @@ -1533,6 +1993,7 @@ impl ConstraintSystem { &|query| Expression::Fixed(query), &|query| Expression::Advice(query), &|query| Expression::Instance(query), + &|challenge| Expression::Challenge(challenge), &|a| -a, &|a, b| a + b, &|a, b| a * b, @@ -1584,6 +2045,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 { @@ -1594,14 +2083,28 @@ impl ConstraintSystem { tmp } - /// Allocate a new advice column + /// Allocate a new advice column at `FirstPhase` pub fn advice_column(&mut self) -> Column { + self.advice_column_in(FirstPhase) + } + + /// Allocate a new advice column in given phase + pub fn advice_column_in(&mut self, phase: P) -> Column { + let phase = phase.to_sealed(); + if let Some(previous_phase) = phase.prev() { + self.assert_phase_exists( + previous_phase, + format!("Column in later phase {:?}", phase).as_str(), + ); + } + let tmp = Column { index: self.num_advice_columns, - column_type: Advice, + column_type: Advice { phase }, }; self.num_advice_columns += 1; self.num_advice_queries.push(0); + self.advice_column_phase.push(phase); tmp } @@ -1615,6 +2118,48 @@ impl ConstraintSystem { tmp } + /// Requests a challenge that is usable after the given phase. + pub fn challenge_usable_after(&mut self, phase: P) -> Challenge { + let phase = phase.to_sealed(); + self.assert_phase_exists( + phase, + format!("Challenge usable after phase {:?}", phase).as_str(), + ); + + let tmp = Challenge { + index: self.num_challenges, + phase, + }; + self.num_challenges += 1; + self.challenge_phase.push(phase); + tmp + } + + /// Helper funciotn to assert phase exists, to make sure phase-aware resources + /// are allocated in order, and to avoid any phase to be skipped accidentally + /// to cause unexpected issue in the future. + fn assert_phase_exists(&self, phase: sealed::Phase, resource: &str) { + self.advice_column_phase + .iter() + .find(|advice_column_phase| **advice_column_phase == phase) + .unwrap_or_else(|| { + panic!( + "No Column is used in phase {:?} while allocating a new {:?}", + phase, resource + ) + }); + } + + pub(crate) fn phases(&self) -> impl Iterator { + let max_phase = self + .advice_column_phase + .iter() + .max() + .map(|phase| phase.0) + .unwrap_or_default(); + (0..=max_phase).map(sealed::Phase) + } + /// Compute the degree of the constraint system (the maximum degree of all /// constraints). pub fn degree(&self) -> usize { @@ -1703,11 +2248,39 @@ impl ConstraintSystem { self.num_instance_columns } + /// Returns number of selectors + pub fn num_selectors(&self) -> usize { + self.num_selectors + } + + /// Returns number of challenges + pub fn num_challenges(&self) -> usize { + self.num_challenges + } + + /// Returns phase of advice columns + pub fn advice_column_phase(&self) -> Vec { + self.advice_column_phase + .iter() + .map(|phase| phase.0) + .collect() + } + + /// Returns phase of challenges + pub fn challenge_phase(&self) -> Vec { + self.challenge_phase.iter().map(|phase| phase.0).collect() + } + /// Returns gates pub fn gates(&self) -> &Vec> { &self.gates } + /// Returns general column annotations + pub fn general_column_annotations(&self) -> &HashMap { + &self.general_column_annotations + } + /// Returns advice queries pub fn advice_queries(&self) -> &Vec<(Column, Rotation)> { &self.advice_queries @@ -1767,7 +2340,7 @@ impl<'a, F: Field> VirtualCells<'a, F> { pub fn query_fixed(&mut self, column: Column, at: Rotation) -> Expression { self.queried_cells.push((column, at).into()); Expression::Fixed(FixedQuery { - index: self.meta.query_fixed_index(column, at), + index: Some(self.meta.query_fixed_index(column, at)), column_index: column.index, rotation: at, }) @@ -1777,9 +2350,10 @@ impl<'a, F: Field> VirtualCells<'a, F> { pub fn query_advice(&mut self, column: Column, at: Rotation) -> Expression { self.queried_cells.push((column, at).into()); Expression::Advice(AdviceQuery { - index: self.meta.query_advice_index(column, at), + index: Some(self.meta.query_advice_index(column, at)), column_index: column.index, rotation: at, + phase: column.column_type().phase, }) } @@ -1787,7 +2361,7 @@ impl<'a, F: Field> VirtualCells<'a, F> { pub fn query_instance(&mut self, column: Column, at: Rotation) -> Expression { self.queried_cells.push((column, at).into()); Expression::Instance(InstanceQuery { - index: self.meta.query_instance_index(column, at), + index: Some(self.meta.query_instance_index(column, at)), column_index: column.index, rotation: at, }) @@ -1797,9 +2371,14 @@ impl<'a, F: Field> VirtualCells<'a, F> { pub fn query_any>>(&mut self, column: C, at: Rotation) -> Expression { let column = column.into(); match column.column_type() { - Any::Advice => self.query_advice(Column::::try_from(column).unwrap(), at), + Any::Advice(_) => self.query_advice(Column::::try_from(column).unwrap(), at), Any::Fixed => self.query_fixed(Column::::try_from(column).unwrap(), at), Any::Instance => self.query_instance(Column::::try_from(column).unwrap(), at), } } + + /// Query a challenge + pub fn query_challenge(&mut self, challenge: Challenge) -> Expression { + Expression::Challenge(challenge) + } } diff --git a/halo2_proofs/src/plonk/circuit/compress_selectors.rs b/halo2_proofs/src/plonk/circuit/compress_selectors.rs index c141d12d..053ebe31 100644 --- a/halo2_proofs/src/plonk/circuit/compress_selectors.rs +++ b/halo2_proofs/src/plonk/circuit/compress_selectors.rs @@ -79,7 +79,7 @@ where let combination_assignment = selector .activations .iter() - .map(|b| if *b { F::one() } else { F::zero() }) + .map(|b| if *b { F::ONE } else { F::ZERO }) .collect::>(); let combination_index = combination_assignments.len(); combination_assignments.push(combination_assignment); @@ -177,12 +177,12 @@ where } // Now, compute the selector and combination assignments. - let mut combination_assignment = vec![F::zero(); n]; + let mut combination_assignment = vec![F::ZERO; n]; let combination_len = combination.len(); let combination_index = combination_assignments.len(); let query = allocate_fixed_column(); - let mut assigned_root = F::one(); + let mut assigned_root = F::ONE; selector_assignments.extend(combination.into_iter().map(|selector| { // Compute the expression for substitution. This produces an expression of the // form @@ -192,12 +192,12 @@ where // `assigned_root`. In particular, rows set to 0 correspond to all selectors // being disabled. let mut expression = query.clone(); - let mut root = F::one(); + let mut root = F::ONE; for _ in 0..combination_len { if root != assigned_root { expression = expression * (Expression::Constant(root) - query.clone()); } - root += F::one(); + root += F::ONE; } // Update the combination assignment @@ -212,7 +212,7 @@ where } } - assigned_root += F::one(); + assigned_root += F::ONE; SelectorAssignment { selector: selector.selector, @@ -281,7 +281,7 @@ mod tests { let (combination_assignments, selector_assignments) = process::(selectors.clone(), max_degree, || { let tmp = Expression::Fixed(FixedQuery { - index: query, + index: Some(query), column_index: query, rotation: Rotation::cur(), }); @@ -320,11 +320,12 @@ mod tests { &|_| panic!("should not occur in returned expressions"), &|query| { // Should be the correct combination in the expression - assert_eq!(selector.combination_index, query.index); + assert_eq!(selector.combination_index, query.index.unwrap()); assignment }, &|_| panic!("should not occur in returned expressions"), &|_| panic!("should not occur in returned expressions"), + &|_| panic!("should not occur in returned expressions"), &|a| -a, &|a, b| a + b, &|a, b| a * b, diff --git a/halo2_proofs/src/plonk/evaluation.rs b/halo2_proofs/src/plonk/evaluation.rs index 06c13fe9..d5fb9840 100644 --- a/halo2_proofs/src/plonk/evaluation.rs +++ b/halo2_proofs/src/plonk/evaluation.rs @@ -1,10 +1,10 @@ use crate::multicore; use crate::plonk::lookup::prover::Committed; use crate::plonk::permutation::Argument; -use crate::plonk::{lookup, permutation, Any, ProvingKey}; +use crate::plonk::{lookup, permutation, AdviceQuery, Any, FixedQuery, InstanceQuery, ProvingKey}; use crate::poly::Basis; use crate::{ - arithmetic::{eval_polynomial, parallelize, CurveAffine, FieldExt}, + arithmetic::{eval_polynomial, parallelize, CurveAffine}, poly::{ commitment::Params, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, ProverQuery, Rotation, @@ -13,7 +13,7 @@ use crate::{ }; use group::prime::PrimeCurve; use group::{ - ff::{BatchInvert, Field}, + ff::{BatchInvert, Field, PrimeField, WithSmallOrderMulGroup}, Curve, }; use std::any::TypeId; @@ -46,6 +46,8 @@ pub enum ValueSource { Advice(usize, usize), /// This is an instance (external) column Instance(usize, usize), + /// This is a challenge + Challenge(usize), /// beta Beta(), /// gamma @@ -74,6 +76,7 @@ impl ValueSource { fixed_values: &[Polynomial], advice_values: &[Polynomial], instance_values: &[Polynomial], + challenges: &[F], beta: &F, gamma: &F, theta: &F, @@ -92,6 +95,7 @@ impl ValueSource { ValueSource::Instance(column_index, rotation) => { instance_values[*column_index][rotations[*rotation]] } + ValueSource::Challenge(index) => challenges[*index], ValueSource::Beta() => *beta, ValueSource::Gamma() => *gamma, ValueSource::Theta() => *theta, @@ -132,6 +136,7 @@ impl Calculation { fixed_values: &[Polynomial], advice_values: &[Polynomial], instance_values: &[Polynomial], + challenges: &[F], beta: &F, gamma: &F, theta: &F, @@ -146,6 +151,7 @@ impl Calculation { fixed_values, advice_values, instance_values, + challenges, beta, gamma, theta, @@ -276,6 +282,7 @@ impl Evaluator { pk: &ProvingKey, advice_polys: &[&[Polynomial]], instance_polys: &[&[Polynomial]], + challenges: &[C::ScalarExt], y: C::ScalarExt, beta: C::ScalarExt, gamma: C::ScalarExt, @@ -289,7 +296,7 @@ impl Evaluator { let fixed = &pk.fixed_cosets[..]; let extended_omega = domain.get_extended_omega(); let isize = size as i32; - let one = C::ScalarExt::one(); + let one = C::ScalarExt::ONE; let l0 = &pk.l0; let l_last = &pk.l_last; let l_active_row = &pk.l_active_row; @@ -339,6 +346,7 @@ impl Evaluator { fixed, advice, instance, + challenges, &beta, &gamma, &theta, @@ -409,7 +417,7 @@ impl Evaluator { for (values, permutation) in columns .iter() .map(|&column| match column.column_type() { - Any::Advice => &advice[column.index()], + Any::Advice(_) => &advice[column.index()], Any::Fixed => &fixed[column.index()], Any::Instance => &instance[column.index()], }) @@ -420,7 +428,7 @@ impl Evaluator { let mut right = set.permutation_product_coset[idx]; for values in columns.iter().map(|&column| match column.column_type() { - Any::Advice => &advice[column.index()], + Any::Advice(_) => &advice[column.index()], Any::Fixed => &fixed[column.index()], Any::Instance => &instance[column.index()], }) { @@ -462,11 +470,12 @@ impl Evaluator { fixed, advice, instance, + challenges, &beta, &gamma, &theta, &y, - &C::ScalarExt::zero(), + &C::ScalarExt::ZERO, idx, rot_scale, isize, @@ -518,8 +527,8 @@ impl Default for GraphEvaluator { Self { // Fixed positions to allow easy access constants: vec![ - C::ScalarExt::zero(), - C::ScalarExt::one(), + C::ScalarExt::ZERO, + C::ScalarExt::ONE, C::ScalarExt::from(2u64), ], rotations: Vec::new(), @@ -603,6 +612,9 @@ impl GraphEvaluator { rot_idx, ))) } + Expression::Challenge(challenge) => self.add_calculation(Calculation::Store( + ValueSource::Challenge(challenge.index()), + )), Expression::Negated(a) => match **a { Expression::Constant(scalar) => self.add_constant(&-scalar), _ => { @@ -664,9 +676,9 @@ impl GraphEvaluator { } } Expression::Scaled(a, f) => { - if *f == C::ScalarExt::zero() { + if *f == C::ScalarExt::ZERO { ValueSource::Constant(0) - } else if *f == C::ScalarExt::one() { + } else if *f == C::ScalarExt::ONE { self.add_expression(a) } else { let cst = self.add_constant(f); @@ -680,7 +692,7 @@ impl GraphEvaluator { /// Creates a new evaluation structure pub fn instance(&self) -> EvaluationData { EvaluationData { - intermediates: vec![C::ScalarExt::zero(); self.num_intermediates], + intermediates: vec![C::ScalarExt::ZERO; self.num_intermediates], rotations: vec![0usize; self.rotations.len()], } } @@ -691,6 +703,7 @@ impl GraphEvaluator { fixed: &[Polynomial], advice: &[Polynomial], instance: &[Polynomial], + challenges: &[C::ScalarExt], beta: &C::ScalarExt, gamma: &C::ScalarExt, theta: &C::ScalarExt, @@ -714,6 +727,7 @@ impl GraphEvaluator { fixed, advice, instance, + challenges, beta, gamma, theta, @@ -726,21 +740,22 @@ impl GraphEvaluator { if let Some(calc) = self.calculations.last() { data.intermediates[calc.target] } else { - C::ScalarExt::zero() + C::ScalarExt::ZERO } } } /// Simple evaluation of an expression -pub fn evaluate( +pub fn evaluate( expression: &Expression, size: usize, rot_scale: i32, fixed: &[Polynomial], advice: &[Polynomial], instance: &[Polynomial], + challenges: &[F], ) -> Vec { - let mut values = vec![F::zero(); size]; + let mut values = vec![F::ZERO; size]; let isize = size as i32; parallelize(&mut values, |values, start| { for (i, value) in values.iter_mut().enumerate() { @@ -760,6 +775,7 @@ pub fn evaluate( instance[query.column_index] [get_rotation_idx(idx, query.rotation.0, rot_scale, isize)] }, + &|challenge| challenges[challenge.index()], &|a| -a, &|a, b| a + &b, &|a, b| a * b, diff --git a/halo2_proofs/src/plonk/keygen.rs b/halo2_proofs/src/plonk/keygen.rs index 3480af2a..91922b69 100644 --- a/halo2_proofs/src/plonk/keygen.rs +++ b/halo2_proofs/src/plonk/keygen.rs @@ -2,7 +2,7 @@ use std::ops::Range; -use ff::Field; +use ff::{Field, FromUniformBytes}; use group::Curve; use super::{ @@ -11,11 +11,12 @@ use super::{ Selector, }, evaluation::Evaluator, - permutation, Assigned, Error, Expression, LagrangeCoeff, Polynomial, ProvingKey, VerifyingKey, + permutation, Assigned, Challenge, Error, Expression, LagrangeCoeff, Polynomial, ProvingKey, + VerifyingKey, }; use crate::{ arithmetic::{parallelize, CurveAffine}, - circuit::Value, + circuit::{layouter::SyncDeps, Value}, poly::{ batch_invert_assigned, commitment::{Blind, Params, MSM}, @@ -25,6 +26,7 @@ use crate::{ pub(crate) fn create_domain( k: u32, + #[cfg(feature = "circuit-params")] params: ConcreteCircuit::Params, ) -> ( EvaluationDomain, ConstraintSystem, @@ -35,6 +37,9 @@ where ConcreteCircuit: Circuit, { let mut cs = ConstraintSystem::default(); + #[cfg(feature = "circuit-params")] + let config = ConcreteCircuit::configure_with_params(&mut cs, params); + #[cfg(not(feature = "circuit-params"))] let config = ConcreteCircuit::configure(&mut cs); let degree = cs.degree(); @@ -56,6 +61,8 @@ struct Assembly { _marker: std::marker::PhantomData, } +impl SyncDeps for Assembly {} + impl Assignment for Assembly { fn enter_region(&mut self, _: N) where @@ -173,6 +180,18 @@ impl Assignment for Assembly { Ok(()) } + fn get_challenge(&self, _: Challenge) -> Value { + 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, @@ -195,8 +214,13 @@ where C: CurveAffine, P: Params<'params, C>, ConcreteCircuit: Circuit, + C::Scalar: FromUniformBytes<64>, { - let (domain, cs, config) = create_domain::(params.k()); + let (domain, cs, config) = create_domain::( + params.k(), + #[cfg(feature = "circuit-params")] + circuit.params(), + ); if (params.n() as usize) < cs.minimum_rows() { return Err(Error::not_enough_rows_available(params.k())); @@ -220,7 +244,7 @@ where )?; let mut fixed = batch_invert_assigned(assembly.fixed); - let (cs, selector_polys) = cs.compress_selectors(assembly.selectors); + let (cs, selector_polys) = cs.compress_selectors(assembly.selectors.clone()); fixed.extend( selector_polys .into_iter() @@ -241,6 +265,7 @@ where fixed_commitments, permutation_vk, cs, + assembly.selectors, )) } @@ -256,6 +281,9 @@ where ConcreteCircuit: Circuit, { let mut cs = ConstraintSystem::default(); + #[cfg(feature = "circuit-params")] + let config = ConcreteCircuit::configure_with_params(&mut cs, circuit.params()); + #[cfg(not(feature = "circuit-params"))] let config = ConcreteCircuit::configure(&mut cs); let cs = cs; @@ -306,7 +334,7 @@ where // Compute l_0(X) // TODO: this can be done more efficiently let mut l0 = vk.domain.empty_lagrange(); - l0[0] = C::Scalar::one(); + l0[0] = C::Scalar::ONE; let l0 = vk.domain.lagrange_to_coeff(l0); let l0 = vk.domain.coeff_to_extended(l0); @@ -314,7 +342,7 @@ where // and 0 otherwise over the domain. let mut l_blind = vk.domain.empty_lagrange(); for evaluation in l_blind[..].iter_mut().rev().take(cs.blinding_factors()) { - *evaluation = C::Scalar::one(); + *evaluation = C::Scalar::ONE; } let l_blind = vk.domain.lagrange_to_coeff(l_blind); let l_blind = vk.domain.coeff_to_extended(l_blind); @@ -322,12 +350,12 @@ where // 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(); + 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); // Compute l_active_row(X) - let one = C::Scalar::one(); + let one = C::Scalar::ONE; let mut l_active_row = vk.domain.empty_extended(); parallelize(&mut l_active_row, |values, start| { for (i, value) in values.iter_mut().enumerate() { diff --git a/halo2_proofs/src/plonk/lookup.rs b/halo2_proofs/src/plonk/lookup.rs index 68cda75d..a7c4f68a 100644 --- a/halo2_proofs/src/plonk/lookup.rs +++ b/halo2_proofs/src/plonk/lookup.rs @@ -7,7 +7,7 @@ pub(crate) mod verifier; #[derive(Clone)] pub struct Argument { - pub(crate) name: &'static str, + pub(crate) name: String, pub(crate) input_expressions: Vec>, pub(crate) table_expressions: Vec>, } @@ -25,10 +25,10 @@ impl Argument { /// Constructs a new lookup argument. /// /// `table_map` is a sequence of `(input, table)` tuples. - pub fn new(name: &'static str, table_map: Vec<(Expression, Expression)>) -> Self { + pub fn new>(name: S, table_map: Vec<(Expression, Expression)>) -> Self { let (input_expressions, table_expressions) = table_map.into_iter().unzip(); Argument { - name, + name: name.as_ref().to_string(), input_expressions, table_expressions, } @@ -91,4 +91,9 @@ impl Argument { pub fn table_expressions(&self) -> &Vec> { &self.table_expressions } + + /// Returns name of this argument + pub fn name(&self) -> &str { + &self.name + } } diff --git a/halo2_proofs/src/plonk/lookup/prover.rs b/halo2_proofs/src/plonk/lookup/prover.rs index 4b0ba9a6..0f0c85d7 100644 --- a/halo2_proofs/src/plonk/lookup/prover.rs +++ b/halo2_proofs/src/plonk/lookup/prover.rs @@ -5,7 +5,7 @@ use super::super::{ use super::Argument; use crate::plonk::evaluation::evaluate; use crate::{ - arithmetic::{eval_polynomial, parallelize, CurveAffine, FieldExt}, + arithmetic::{eval_polynomial, parallelize, CurveAffine}, poly::{ commitment::{Blind, Params}, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, ProverQuery, @@ -13,6 +13,7 @@ use crate::{ }, transcript::{EncodedChallenge, TranscriptWrite}, }; +use ff::WithSmallOrderMulGroup; use group::{ ff::{BatchInvert, Field}, Curve, @@ -51,7 +52,7 @@ pub(in crate::plonk) struct Evaluated { constructed: Committed, } -impl Argument { +impl> Argument { /// Given a Lookup with input expressions [A_0, A_1, ..., A_{m-1}] and table expressions /// [S_0, S_1, ..., S_{m-1}], this method /// - constructs A_compressed = \theta^{m-1} A_0 + theta^{m-2} A_1 + ... + \theta A_{m-2} + A_{m-1} @@ -78,6 +79,7 @@ impl Argument { advice_values: &'a [Polynomial], fixed_values: &'a [Polynomial], instance_values: &'a [Polynomial], + challenges: &'a [C::Scalar], mut rng: R, transcript: &mut T, ) -> Result, Error> @@ -97,6 +99,7 @@ impl Argument { fixed_values, advice_values, instance_values, + challenges, )) }) .fold(domain.empty_lagrange(), |acc, expression| { @@ -189,7 +192,7 @@ impl Permuted { // s_j(X) is the jth table expression in this lookup, // s'(X) is the compression of the permuted table expressions, // and i is the ith row of the expression. - let mut lookup_product = vec![C::Scalar::zero(); params.n() as usize]; + let mut lookup_product = vec![C::Scalar::ZERO; params.n() as usize]; // Denominator uses the permuted input expression and permuted table expression parallelize(&mut lookup_product, |lookup_product, start| { for ((lookup_product, permuted_input_value), permuted_table_value) in lookup_product @@ -232,9 +235,9 @@ impl Permuted { // Compute the evaluations of the lookup product polynomial // over our domain, starting with z[0] = 1 - let z = iter::once(C::Scalar::one()) + let z = iter::once(C::Scalar::ONE) .chain(lookup_product) - .scan(C::Scalar::one(), |state, cur| { + .scan(C::Scalar::ONE, |state, cur| { *state *= &cur; Some(*state) }) @@ -255,7 +258,7 @@ impl Permuted { let u = (params.n() as usize) - (blinding_factors + 1); // l_0(X) * (1 - z(X)) = 0 - assert_eq!(z[0], C::Scalar::one()); + assert_eq!(z[0], C::Scalar::ONE); // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) // - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma) @@ -282,7 +285,7 @@ impl Permuted { // l_last(X) * (z(X)^2 - z(X)) = 0 // Assertion will fail only when soundness is broken, in which // case this z[u] value will be zero. (bad!) - assert_eq!(z[u], C::Scalar::one()); + assert_eq!(z[u], C::Scalar::ONE); } let product_blind = Blind(C::Scalar::random(rng)); @@ -411,7 +414,7 @@ fn permute_expression_pair<'params, C: CurveAffine, P: Params<'params, C>, R: Rn *acc.entry(*coeff).or_insert(0) += 1; acc }); - let mut permuted_table_coeffs = vec![C::Scalar::zero(); usable_rows]; + let mut permuted_table_coeffs = vec![C::Scalar::ZERO; usable_rows]; let mut repeated_input_rows = permuted_input_expression .iter() diff --git a/halo2_proofs/src/plonk/lookup/verifier.rs b/halo2_proofs/src/plonk/lookup/verifier.rs index 07522e47..1dc111f2 100644 --- a/halo2_proofs/src/plonk/lookup/verifier.rs +++ b/halo2_proofs/src/plonk/lookup/verifier.rs @@ -5,7 +5,7 @@ use super::super::{ }; use super::Argument; use crate::{ - arithmetic::{CurveAffine, FieldExt}, + arithmetic::CurveAffine, plonk::{Error, VerifyingKey}, poly::{commitment::MSM, Rotation, VerifierQuery}, transcript::{EncodedChallenge, TranscriptRead}, @@ -31,7 +31,7 @@ pub struct Evaluated { permuted_table_eval: C::Scalar, } -impl Argument { +impl Argument { pub(in crate::plonk) fn read_permuted_commitments< C: CurveAffine, E: EncodedChallenge, @@ -102,8 +102,9 @@ impl Evaluated { advice_evals: &[C::Scalar], fixed_evals: &[C::Scalar], instance_evals: &[C::Scalar], + challenges: &[C::Scalar], ) -> impl Iterator + 'a { - let active_rows = C::Scalar::one() - (l_last + l_blind); + let active_rows = C::Scalar::ONE - (l_last + l_blind); let product_expression = || { // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) @@ -119,16 +120,17 @@ impl Evaluated { expression.evaluate( &|scalar| scalar, &|_| panic!("virtual selectors are removed during optimization"), - &|query| fixed_evals[query.index], - &|query| advice_evals[query.index], - &|query| instance_evals[query.index], + &|query| fixed_evals[query.index.unwrap()], + &|query| advice_evals[query.index.unwrap()], + &|query| instance_evals[query.index.unwrap()], + &|challenge| challenges[challenge.index()], &|a| -a, &|a, b| a + &b, &|a, b| a * &b, &|a, scalar| a * &scalar, ) }) - .fold(C::Scalar::zero(), |acc, eval| acc * &*theta + &eval) + .fold(C::Scalar::ZERO, |acc, eval| acc * &*theta + &eval) }; let right = self.product_eval * &(compress_expressions(&argument.input_expressions) + &*beta) @@ -140,7 +142,7 @@ impl Evaluated { std::iter::empty() .chain( // l_0(X) * (1 - z'(X)) = 0 - Some(l_0 * &(C::Scalar::one() - &self.product_eval)), + Some(l_0 * &(C::Scalar::ONE - &self.product_eval)), ) .chain( // l_last(X) * (z(X)^2 - z(X)) = 0 diff --git a/halo2_proofs/src/plonk/permutation.rs b/halo2_proofs/src/plonk/permutation.rs index 26a4d805..0e92ccc7 100644 --- a/halo2_proofs/src/plonk/permutation.rs +++ b/halo2_proofs/src/plonk/permutation.rs @@ -1,14 +1,23 @@ +//! Implementation of permutation argument. + use super::circuit::{Any, Column}; use crate::{ arithmetic::CurveAffine, - helpers::CurveRead, + helpers::{ + polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, + SerdeCurveAffine, SerdePrimeField, + }, poly::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial}, + SerdeFormat, }; +use ff::PrimeField; pub(crate) mod keygen; pub(crate) mod prover; pub(crate) mod verifier; +pub use keygen::Assembly; + use std::io; /// A permutation argument. @@ -67,6 +76,7 @@ impl Argument { } } + /// Returns columns that participate on the permutation argument. pub fn get_columns(&self) -> Vec> { self.columns.clone() } @@ -83,6 +93,34 @@ impl VerifyingKey { pub fn commitments(&self) -> &Vec { &self.commitments } + + pub(crate) fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> + where + C: SerdeCurveAffine, + { + for commitment in &self.commitments { + commitment.write(writer, format)?; + } + Ok(()) + } + + pub(crate) fn read( + reader: &mut R, + argument: &Argument, + format: SerdeFormat, + ) -> io::Result + where + C: SerdeCurveAffine, + { + let commitments = (0..argument.columns.len()) + .map(|_| C::read(reader, format)) + .collect::, _>>()?; + Ok(VerifyingKey { commitments }) + } + + pub(crate) fn bytes_length(&self) -> usize { + self.commitments.len() * C::default().to_bytes().as_ref().len() + } } /// The proving key for a single permutation argument. @@ -92,3 +130,41 @@ pub(crate) struct ProvingKey { polys: Vec>, pub(super) cosets: Vec>, } + +impl ProvingKey +where + C::Scalar: SerdePrimeField, +{ + /// Reads proving key for a single permutation argument from buffer using `Polynomial::read`. + pub(super) fn read(reader: &mut R, format: SerdeFormat) -> io::Result { + let permutations = read_polynomial_vec(reader, format)?; + let polys = read_polynomial_vec(reader, format)?; + let cosets = read_polynomial_vec(reader, format)?; + Ok(ProvingKey { + permutations, + polys, + cosets, + }) + } + + /// Writes proving key for a single permutation argument to buffer using `Polynomial::write`. + pub(super) fn write( + &self, + writer: &mut W, + format: SerdeFormat, + ) -> io::Result<()> { + write_polynomial_slice(&self.permutations, writer, format)?; + write_polynomial_slice(&self.polys, writer, format)?; + write_polynomial_slice(&self.cosets, writer, format)?; + Ok(()) + } +} + +impl ProvingKey { + /// Gets the total number of bytes in the serialization of `self` + pub(super) fn bytes_length(&self) -> usize { + polynomial_slice_byte_length(&self.permutations) + + polynomial_slice_byte_length(&self.polys) + + polynomial_slice_byte_length(&self.cosets) + } +} diff --git a/halo2_proofs/src/plonk/permutation/keygen.rs b/halo2_proofs/src/plonk/permutation/keygen.rs index cdb8cc02..57882fad 100644 --- a/halo2_proofs/src/plonk/permutation/keygen.rs +++ b/halo2_proofs/src/plonk/permutation/keygen.rs @@ -1,9 +1,15 @@ -use ff::Field; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; + +use ff::{Field, PrimeField}; use group::Curve; +use rayon::prelude::{ + IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, + IntoParallelRefMutIterator, ParallelIterator, ParallelSliceMut, +}; use super::{Argument, ProvingKey, VerifyingKey}; use crate::{ - arithmetic::{CurveAffine, FieldExt}, + arithmetic::{parallelize, CurveAffine}, plonk::{Any, Column, Error}, poly::{ commitment::{Blind, CommitmentScheme, Params}, @@ -11,14 +17,21 @@ use crate::{ }, }; -#[derive(Debug)] -pub(crate) struct Assembly { +#[cfg(not(feature = "thread-safe-region"))] +/// 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>, } +#[cfg(not(feature = "thread-safe-region"))] impl Assembly { pub(crate) fn new(n: usize, p: &Argument) -> Self { // Initialize the copy vector to keep track of copy constraints in all @@ -103,108 +116,340 @@ impl Assembly { domain: &EvaluationDomain, 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 cur = C::Scalar::one(); - for _ in 0..params.n() { - omega_powers.push(cur); - cur *= &domain.get_omega(); - } + build_vk(params, domain, p, |i, j| self.mapping[i][j]) + } + + pub(crate) fn build_pk<'params, C: CurveAffine, P: Params<'params, C>>( + self, + params: &P, + domain: &EvaluationDomain, + p: &Argument, + ) -> ProvingKey { + build_pk(params, domain, p, |i, j| self.mapping[i][j]) + } + + /// Returns columns that participate in the permutation argument. + pub fn columns(&self) -> &[Column] { + &self.columns + } + + /// Returns mappings of the copies. + pub fn mapping( + &self, + ) -> impl Iterator + '_> { + self.mapping.iter().map(|c| c.par_iter().copied()) + } +} + +#[cfg(feature = "thread-safe-region")] +/// 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>, + /// Mapping of the actual copies done. + cycles: Vec>, + /// Mapping of the actual copies done. + ordered_cycles: Vec>, + /// Mapping of the actual copies done. + aux: HashMap<(usize, usize), usize>, + /// total length of a column + col_len: usize, + /// number of columns + num_cols: usize, +} + +#[cfg(feature = "thread-safe-region")] +impl Assembly { + pub(crate) fn new(n: usize, p: &Argument) -> Self { + Assembly { + columns: p.columns.clone(), + cycles: Vec::with_capacity(n), + ordered_cycles: Vec::with_capacity(n), + aux: HashMap::new(), + col_len: n, + num_cols: p.columns.len(), } + } - // Compute [omega_powers * \delta^0, omega_powers * \delta^1, ..., omega_powers * \delta^m] - let mut deltaomega = Vec::with_capacity(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; - } + pub(crate) fn copy( + &mut self, + left_column: Column, + left_row: usize, + right_column: Column, + right_row: usize, + ) -> Result<(), Error> { + let left_column = self + .columns + .iter() + .position(|c| c == &left_column) + .ok_or(Error::ColumnNotInPermutation(left_column))?; + let right_column = self + .columns + .iter() + .position(|c| c == &right_column) + .ok_or(Error::ColumnNotInPermutation(right_column))?; - deltaomega.push(omega_powers); + // Check bounds + if left_row >= self.col_len || right_row >= self.col_len { + return Err(Error::BoundsFailure); + } - cur *= &C::Scalar::DELTA; + let left_cycle = self.aux.get(&(left_column, left_row)); + let right_cycle = self.aux.get(&(right_column, right_row)); + + // extract cycle elements + let right_cycle_elems = match right_cycle { + Some(i) => { + let entry = self.cycles[*i].clone(); + self.cycles[*i] = vec![]; + entry } - } + None => [(right_column, right_row)].into(), + }; - // 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]; + assert!(right_cycle_elems.contains(&(right_column, right_row))); + + // merge cycles + let cycle_idx = match left_cycle { + Some(i) => { + let entry = &mut self.cycles[*i]; + entry.extend(right_cycle_elems.clone()); + *i } + // if they were singletons -- create a new cycle entry + None => { + let mut set: Vec<(usize, usize)> = right_cycle_elems.clone(); + set.push((left_column, left_row)); + self.cycles.push(set); + let cycle_idx = self.cycles.len() - 1; + self.aux.insert((left_column, left_row), cycle_idx); + cycle_idx + } + }; + + let index_updates = vec![cycle_idx; right_cycle_elems.len()].into_iter(); + let updates = right_cycle_elems.into_iter().zip(index_updates); + + self.aux.extend(updates); - // Compute commitment to permutation polynomial - commitments.push( - params - .commit_lagrange(&permutation_poly, Blind::default()) - .to_affine(), - ); + Ok(()) + } + + /// Builds the ordered mapping of the cycles. + /// This will only get executed once. + pub fn build_ordered_mapping(&mut self) { + // will only get called once + if self.ordered_cycles.is_empty() && !self.cycles.is_empty() { + self.ordered_cycles = self + .cycles + .par_iter_mut() + .map(|col| { + let mut set = BTreeSet::new(); + set.extend(col.clone()); + // free up memory + *col = vec![]; + set + }) + .collect(); + } + } + + fn mapping_at_idx(&self, col: usize, row: usize) -> (usize, usize) { + assert!( + !self.ordered_cycles.is_empty() || self.cycles.is_empty(), + "cycles have not been ordered" + ); + + if let Some(cycle_idx) = self.aux.get(&(col, row)) { + let cycle = &self.ordered_cycles[*cycle_idx]; + let mut cycle_iter = cycle.range(( + std::ops::Bound::Excluded((col, row)), + std::ops::Bound::Unbounded, + )); + // point to the next node in the cycle + match cycle_iter.next() { + Some((i, j)) => (*i, *j), + // wrap back around to the first element which SHOULD exist + None => *(cycle.iter().next().unwrap()), + } + // is a singleton + } else { + (col, row) } - VerifyingKey { commitments } + } + + pub(crate) fn build_vk<'params, C: CurveAffine, P: Params<'params, C>>( + &mut self, + params: &P, + domain: &EvaluationDomain, + p: &Argument, + ) -> VerifyingKey { + self.build_ordered_mapping(); + build_vk(params, domain, p, |i, j| self.mapping_at_idx(i, j)) } pub(crate) fn build_pk<'params, C: CurveAffine, P: Params<'params, C>>( - self, + &mut self, params: &P, domain: &EvaluationDomain, 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 cur = C::Scalar::one(); - for _ in 0..params.n() { - omega_powers.push(cur); - cur *= &domain.get_omega(); + self.build_ordered_mapping(); + build_pk(params, domain, p, |i, j| self.mapping_at_idx(i, j)) + } + + /// Returns columns that participate in the permutation argument. + pub fn columns(&self) -> &[Column] { + &self.columns + } + + /// Returns mappings of the copies. + pub fn mapping( + &self, + ) -> impl Iterator + '_> { + (0..self.num_cols).map(move |i| { + (0..self.col_len) + .into_par_iter() + .map(move |j| self.mapping_at_idx(i, j)) + }) + } +} + +pub(crate) fn build_pk<'params, C: CurveAffine, P: Params<'params, C>>( + params: &P, + domain: &EvaluationDomain, + p: &Argument, + mapping: impl Fn(usize, usize) -> (usize, usize) + Sync, +) -> ProvingKey { + // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] + let mut omega_powers = vec![C::Scalar::ZERO; params.n() as usize]; + { + 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 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; + // Compute [omega_powers * \delta^0, omega_powers * \delta^1, ..., omega_powers * \delta^m] + let mut deltaomega = vec![omega_powers; p.columns.len()]; + { + 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); + // Compute permutation polynomials, convert to coset form. + 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) = mapping(i, j); + *p = deltaomega[permuted_i][permuted_j]; + } + } + }); + } - cur *= &C::Scalar::DELTA; + 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); } - } + }); + } - // 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 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); } + }); + } - // 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)); - } - ProvingKey { - permutations, - polys, - cosets, - } + ProvingKey { + permutations, + polys, + cosets, + } +} + +pub(crate) fn build_vk<'params, C: CurveAffine, P: Params<'params, C>>( + params: &P, + domain: &EvaluationDomain, + p: &Argument, + mapping: impl Fn(usize, usize) -> (usize, usize) + Sync, +) -> VerifyingKey { + // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] + let mut omega_powers = vec![C::Scalar::ZERO; params.n() as usize]; + { + 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![omega_powers; p.columns.len()]; + { + 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 *= &::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) = mapping(i, j); + *p = deltaomega[permuted_i][permuted_j]; + } + } + }); + } + + // Pre-compute commitments for the URS. + let mut commitments = Vec::with_capacity(p.columns.len()); + for permutation in &permutations { + // Compute commitment to permutation polynomial + commitments.push( + params + .commit_lagrange(permutation, Blind::default()) + .to_affine(), + ); + } + + VerifyingKey { commitments } } diff --git a/halo2_proofs/src/plonk/permutation/prover.rs b/halo2_proofs/src/plonk/permutation/prover.rs index 8a9c5bbe..2dbc2dea 100644 --- a/halo2_proofs/src/plonk/permutation/prover.rs +++ b/halo2_proofs/src/plonk/permutation/prover.rs @@ -1,3 +1,4 @@ +use ff::PrimeField; use group::{ ff::{BatchInvert, Field}, Curve, @@ -8,7 +9,7 @@ use std::iter::{self, ExactSizeIterator}; use super::super::{circuit::Any, ChallengeBeta, ChallengeGamma, ChallengeX}; use super::{Argument, ProvingKey}; use crate::{ - arithmetic::{eval_polynomial, parallelize, CurveAffine, FieldExt}, + arithmetic::{eval_polynomial, parallelize, CurveAffine}, plonk::{self, Error}, poly::{ self, @@ -73,10 +74,10 @@ impl Argument { let blinding_factors = pk.vk.cs.blinding_factors(); // Each column gets its own delta power. - let mut deltaomega = C::Scalar::one(); + let mut deltaomega = C::Scalar::ONE; // Track the "last" value from the previous column set - let mut last_z = C::Scalar::one(); + let mut last_z = C::Scalar::ONE; let mut sets = vec![]; @@ -93,12 +94,12 @@ impl Argument { // where p_j(X) is the jth column in this permutation, // and i is the ith row of the column. - let mut modified_values = vec![C::Scalar::one(); params.n() as usize]; + let mut modified_values = vec![C::Scalar::ONE; params.n() as usize]; // Iterate over each column of the permutation for (&column, permuted_column_values) in columns.iter().zip(permutations.iter()) { let values = match column.column_type() { - Any::Advice => advice, + Any::Advice(_) => advice, Any::Fixed => fixed, Any::Instance => instance, }; @@ -121,7 +122,7 @@ impl Argument { for &column in columns.iter() { let omega = domain.get_omega(); let values = match column.column_type() { - Any::Advice => advice, + Any::Advice(_) => advice, Any::Fixed => fixed, Any::Instance => instance, }; @@ -136,7 +137,7 @@ impl Argument { deltaomega *= ω } }); - deltaomega *= &C::Scalar::DELTA; + deltaomega *= &::DELTA; } // The modified_values vector is a vector of products of fractions diff --git a/halo2_proofs/src/plonk/permutation/verifier.rs b/halo2_proofs/src/plonk/permutation/verifier.rs index 2ac18a67..ce9fe9bb 100644 --- a/halo2_proofs/src/plonk/permutation/verifier.rs +++ b/halo2_proofs/src/plonk/permutation/verifier.rs @@ -1,10 +1,10 @@ -use ff::Field; +use ff::{Field, PrimeField}; use std::iter; use super::super::{circuit::Any, ChallengeBeta, ChallengeGamma, ChallengeX}; use super::{Argument, VerifyingKey}; use crate::{ - arithmetic::{CurveAffine, FieldExt}, + arithmetic::CurveAffine, plonk::{self, Error}, poly::{commitment::MSM, Rotation, VerifierQuery}, transcript::{EncodedChallenge, TranscriptRead}, @@ -119,9 +119,9 @@ impl Evaluated { // Enforce only for the first set. // l_0(X) * (1 - z_0(X)) = 0 .chain( - self.sets.first().map(|first_set| { - l_0 * &(C::Scalar::one() - &first_set.permutation_product_eval) - }), + self.sets + .first() + .map(|first_set| l_0 * &(C::Scalar::ONE - &first_set.permutation_product_eval)), ) // Enforce only for the last set. // l_last(X) * (z_l(X)^2 - z_l(X)) = 0 @@ -160,7 +160,7 @@ impl Evaluated { for (eval, permutation_eval) in columns .iter() .map(|&column| match column.column_type() { - Any::Advice => { + Any::Advice(_) => { advice_evals[vk.cs.get_any_query_index(column, Rotation::cur())] } Any::Fixed => { @@ -178,9 +178,10 @@ 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])); + * &(::DELTA + .pow_vartime(&[(chunk_index * chunk_len) as u64])); for eval in columns.iter().map(|&column| match column.column_type() { - Any::Advice => { + Any::Advice(_) => { advice_evals[vk.cs.get_any_query_index(column, Rotation::cur())] } Any::Fixed => { @@ -194,7 +195,7 @@ impl Evaluated { current_delta *= &C::Scalar::DELTA; } - (left - &right) * (C::Scalar::one() - &(l_last + &l_blind)) + (left - &right) * (C::Scalar::ONE - &(l_last + &l_blind)) }), ) } diff --git a/halo2_proofs/src/plonk/prover.rs b/halo2_proofs/src/plonk/prover.rs index 48d89de6..6314481d 100644 --- a/halo2_proofs/src/plonk/prover.rs +++ b/halo2_proofs/src/plonk/prover.rs @@ -1,23 +1,26 @@ -use ff::Field; +use ff::{Field, FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; use group::Curve; use halo2curves::CurveExt; use rand_core::RngCore; +use std::collections::BTreeSet; use std::env::var; use std::ops::RangeTo; use std::sync::atomic::AtomicUsize; use std::time::Instant; -use std::{iter, sync::atomic::Ordering}; +use std::{collections::HashMap, iter, mem, sync::atomic::Ordering}; use super::{ circuit::{ - Advice, Any, Assignment, Circuit, Column, ConstraintSystem, Fixed, FloorPlanner, Instance, - Selector, + sealed::{self, SealedPhase}, + Advice, Any, Assignment, Challenge, Circuit, Column, ConstraintSystem, FirstPhase, Fixed, + FloorPlanner, Instance, Selector, }, lookup, permutation, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error, Expression, ProvingKey, }; +use crate::circuit::layouter::SyncDeps; use crate::{ - arithmetic::{eval_polynomial, CurveAffine, FieldExt}, + arithmetic::{eval_polynomial, CurveAffine}, circuit::Value, plonk::Assigned, poly::{ @@ -51,7 +54,10 @@ pub fn create_proof< instances: &[&[&[Scheme::Scalar]]], mut rng: R, transcript: &mut T, -) -> Result<(), Error> { +) -> Result<(), Error> +where + Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, +{ for instance in instances.iter() { if instance.len() != pk.vk.cs.num_instance_columns { return Err(Error::InvalidInstances); @@ -63,6 +69,9 @@ pub fn create_proof< let domain = &pk.vk.domain; let mut meta = ConstraintSystem::default(); + #[cfg(feature = "circuit-params")] + let config = ConcreteCircuit::configure_with_params(&mut meta, circuits[0].params()); + #[cfg(not(feature = "circuit-params"))] let config = ConcreteCircuit::configure(&mut meta); // Selector optimizations cannot be applied here; use the ConstraintSystem @@ -86,26 +95,32 @@ pub fn create_proof< return Err(Error::InstanceTooLarge); } for (poly, value) in poly.iter_mut().zip(values.iter()) { + if !P::QUERY_INSTANCE { + transcript.common_scalar(*value)?; + } *poly = *value; } Ok(poly) }) .collect::, _>>()?; - let instance_commitments_projective: Vec<_> = instance_values - .iter() - .map(|poly| params.commit_lagrange(poly, Blind::default())) - .collect(); - let mut instance_commitments = - vec![Scheme::Curve::identity(); instance_commitments_projective.len()]; - ::CurveExt::batch_normalize( - &instance_commitments_projective, - &mut instance_commitments, - ); - let instance_commitments = instance_commitments; - drop(instance_commitments_projective); - - for commitment in &instance_commitments { - transcript.common_point(*commitment)?; + + if P::QUERY_INSTANCE { + let instance_commitments_projective: Vec<_> = instance_values + .iter() + .map(|poly| params.commit_lagrange(poly, Blind::default())) + .collect(); + let mut instance_commitments = + vec![Scheme::Curve::identity(); instance_commitments_projective.len()]; + ::CurveExt::batch_normalize( + &instance_commitments_projective, + &mut instance_commitments, + ); + let instance_commitments = instance_commitments; + drop(instance_commitments_projective); + + for commitment in &instance_commitments { + transcript.common_point(*commitment)?; + } } let instance_polys: Vec<_> = instance_values @@ -123,152 +138,192 @@ pub fn create_proof< }) .collect::, _>>()?; + #[derive(Clone)] struct AdviceSingle { pub advice_polys: Vec>, pub advice_blinds: Vec>, } - let advice: Vec> = circuits - .iter() - .zip(instances.iter()) - .map( - |(circuit, instances)| -> Result, Error> { - struct WitnessCollection<'a, F: Field> { - k: u32, - pub advice: Vec, LagrangeCoeff>>, - instances: &'a [&'a [F]], - usable_rows: RangeTo, - _marker: std::marker::PhantomData, - } + struct WitnessCollection<'a, F: Field> { + k: u32, + current_phase: sealed::Phase, + advice: Vec, LagrangeCoeff>>, + challenges: &'a HashMap, + instances: &'a [&'a [F]], + usable_rows: RangeTo, + _marker: std::marker::PhantomData, + } - impl<'a, F: Field> Assignment for WitnessCollection<'a, F> { - fn enter_region(&mut self, _: N) - where - NR: Into, - N: FnOnce() -> NR, - { - // Do nothing; we don't care about regions in this context. - } + impl<'a, F: Field> SyncDeps for WitnessCollection<'a, F> {} - fn exit_region(&mut self) { - // Do nothing; we don't care about regions in this context. - } + impl<'a, F: Field> Assignment for WitnessCollection<'a, F> { + fn enter_region(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + // Do nothing; we don't care about regions in this context. + } - fn enable_selector( - &mut self, - _: A, - _: &Selector, - _: usize, - ) -> Result<(), Error> - where - A: FnOnce() -> AR, - AR: Into, - { - // We only care about advice columns here - - Ok(()) - } + fn exit_region(&mut self) { + // Do nothing; we don't care about regions in this context. + } - fn query_instance( - &self, - column: Column, - row: usize, - ) -> Result, Error> { - if !self.usable_rows.contains(&row) { - return Err(Error::not_enough_rows_available(self.k)); - } + fn enable_selector(&mut self, _: A, _: &Selector, _: usize) -> Result<(), Error> + where + A: FnOnce() -> AR, + AR: Into, + { + // We only care about advice columns here - self.instances - .get(column.index()) - .and_then(|column| column.get(row)) - .map(|v| Value::known(*v)) - .ok_or(Error::BoundsFailure) - } + Ok(()) + } - fn assign_advice( - &mut self, - _: A, - column: Column, - row: usize, - to: V, - ) -> Result<(), Error> - where - V: FnOnce() -> Value, - VR: Into>, - A: FnOnce() -> AR, - AR: Into, - { - if !self.usable_rows.contains(&row) { - return Err(Error::not_enough_rows_available(self.k)); - } + fn annotate_column(&mut self, _annotation: A, _column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + // Do nothing + } - *self - .advice - .get_mut(column.index()) - .and_then(|v| v.get_mut(row)) - .ok_or(Error::BoundsFailure)? = to().into_field().assign()?; + fn query_instance(&self, column: Column, row: usize) -> Result, Error> { + if !self.usable_rows.contains(&row) { + return Err(Error::not_enough_rows_available(self.k)); + } - Ok(()) - } + self.instances + .get(column.index()) + .and_then(|column| column.get(row)) + .map(|v| Value::known(*v)) + .ok_or(Error::BoundsFailure) + } - fn assign_fixed( - &mut self, - _: A, - _: Column, - _: usize, - _: V, - ) -> Result<(), Error> - where - V: FnOnce() -> Value, - VR: Into>, - A: FnOnce() -> AR, - AR: Into, - { - // We only care about advice columns here - - Ok(()) - } + fn assign_advice( + &mut self, + _: A, + column: Column, + row: usize, + to: V, + ) -> Result<(), Error> + where + V: FnOnce() -> Value, + VR: Into>, + A: FnOnce() -> AR, + AR: Into, + { + // Ignore assignment of advice column in different phase than current one. + if self.current_phase != column.column_type().phase { + return Ok(()); + } - fn copy( - &mut self, - _: Column, - _: usize, - _: Column, - _: usize, - ) -> Result<(), Error> { - // We only care about advice columns here + if !self.usable_rows.contains(&row) { + return Err(Error::not_enough_rows_available(self.k)); + } - Ok(()) - } + *self + .advice + .get_mut(column.index()) + .and_then(|v| v.get_mut(row)) + .ok_or(Error::BoundsFailure)? = to().into_field().assign()?; - fn fill_from_row( - &mut self, - _: Column, - _: usize, - _: Value>, - ) -> Result<(), Error> { - Ok(()) - } + Ok(()) + } - fn push_namespace(&mut self, _: N) - where - NR: Into, - N: FnOnce() -> NR, - { - // Do nothing; we don't care about namespaces in this context. - } + fn assign_fixed( + &mut self, + _: A, + _: Column, + _: usize, + _: V, + ) -> Result<(), Error> + where + V: FnOnce() -> Value, + VR: Into>, + A: FnOnce() -> AR, + AR: Into, + { + // We only care about advice columns here + + Ok(()) + } - fn pop_namespace(&mut self, _: Option) { - // Do nothing; we don't care about namespaces in this context. - } - } + fn copy( + &mut self, + _: Column, + _: usize, + _: Column, + _: usize, + ) -> Result<(), Error> { + // We only care about advice columns here + + Ok(()) + } + + fn fill_from_row( + &mut self, + _: Column, + _: usize, + _: Value>, + ) -> Result<(), Error> { + Ok(()) + } + + fn get_challenge(&self, challenge: Challenge) -> Value { + self.challenges + .get(&challenge.index()) + .cloned() + .map(Value::known) + .unwrap_or_else(Value::unknown) + } - let unusable_rows_start = params.n() as usize - (meta.blinding_factors() + 1); + fn push_namespace(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + // Do nothing; we don't care about namespaces in this context. + } + + fn pop_namespace(&mut self, _: Option) { + // Do nothing; we don't care about namespaces in this context. + } + } + let (advice, challenges) = { + let mut advice = vec![ + AdviceSingle:: { + advice_polys: vec![domain.empty_lagrange(); meta.num_advice_columns], + advice_blinds: vec![Blind::default(); meta.num_advice_columns], + }; + instances.len() + ]; + let mut challenges = HashMap::::with_capacity(meta.num_challenges); + + let unusable_rows_start = params.n() as usize - (meta.blinding_factors() + 1); + for current_phase in pk.vk.cs.phases() { + let column_indices = meta + .advice_column_phase + .iter() + .enumerate() + .filter_map(|(column_index, phase)| { + if current_phase == *phase { + Some(column_index) + } else { + None + } + }) + .collect::>(); + + for ((circuit, advice), instances) in + circuits.iter().zip(advice.iter_mut()).zip(instances) + { let mut witness = WitnessCollection { k: params.k(), + current_phase, advice: vec![domain.empty_lagrange_assigned(); meta.num_advice_columns], instances, + challenges: &challenges, // The prover will not be allowed to assign values to advice // cells that exist within inactive rows, which include some // number of blinding factors and an extra row for use in the @@ -285,23 +340,36 @@ pub fn create_proof< meta.constants.clone(), )?; - let mut advice = batch_invert_assigned(witness.advice); + let mut advice_values = batch_invert_assigned::( + witness + .advice + .into_iter() + .enumerate() + .filter_map(|(column_index, advice)| { + if column_indices.contains(&column_index) { + Some(advice) + } else { + None + } + }) + .collect(), + ); // Add blinding factors to advice columns - for advice in &mut advice { - for cell in &mut advice[unusable_rows_start..] { + for advice_values in &mut advice_values { + for cell in &mut advice_values[unusable_rows_start..] { *cell = Scheme::Scalar::random(&mut rng); } } // Compute commitments to advice column polynomials - let advice_blinds: Vec<_> = advice + let blinds: Vec<_> = advice_values .iter() .map(|_| Blind(Scheme::Scalar::random(&mut rng))) .collect(); - let advice_commitments_projective: Vec<_> = advice + let advice_commitments_projective: Vec<_> = advice_values .iter() - .zip(advice_blinds.iter()) + .zip(blinds.iter()) .map(|(poly, blind)| params.commit_lagrange(poly, *blind)) .collect(); let mut advice_commitments = @@ -316,14 +384,30 @@ pub fn create_proof< for commitment in &advice_commitments { transcript.write_point(*commitment)?; } + for ((column_index, advice_values), blind) in + column_indices.iter().zip(advice_values).zip(blinds) + { + advice.advice_polys[*column_index] = advice_values; + advice.advice_blinds[*column_index] = blind; + } + } - Ok(AdviceSingle { - advice_polys: advice, - advice_blinds, - }) - }, - ) - .collect::, _>>()?; + for (index, phase) in meta.challenge_phase.iter().enumerate() { + if current_phase == *phase { + let existing = + challenges.insert(index, *transcript.squeeze_challenge_scalar::<()>()); + assert!(existing.is_none()); + } + } + } + + assert_eq!(challenges.len(), meta.num_challenges); + let challenges = (0..meta.num_challenges) + .map(|index| challenges.remove(&index).unwrap()) + .collect::>(); + + (advice, challenges) + }; // Sample theta challenge for keeping lookup columns linearly independent let theta: ChallengeTheta<_> = transcript.squeeze_challenge_scalar(); @@ -346,6 +430,7 @@ pub fn create_proof< &advice.advice_polys, &pk.fixed_values, &instance.instance_values, + &challenges, &mut rng, transcript, ) @@ -427,6 +512,7 @@ pub fn create_proof< .iter() .map(|i| i.instance_polys.as_slice()) .collect::>(), + &challenges, *y, *beta, *gamma, @@ -441,23 +527,25 @@ pub fn create_proof< let x: ChallengeX<_> = transcript.squeeze_challenge_scalar(); let xn = x.pow(&[params.n() as u64, 0, 0, 0]); - // Compute and hash instance evals for each circuit instance - for instance in instance.iter() { - // Evaluate polynomials at omega^i x - let instance_evals: Vec<_> = meta - .instance_queries - .iter() - .map(|&(column, at)| { - eval_polynomial( - &instance.instance_polys[column.index()], - domain.rotate_omega(*x, at), - ) - }) - .collect(); + if P::QUERY_INSTANCE { + // Compute and hash instance evals for each circuit instance + for instance in instance.iter() { + // Evaluate polynomials at omega^i x + let instance_evals: Vec<_> = meta + .instance_queries + .iter() + .map(|&(column, at)| { + eval_polynomial( + &instance.instance_polys[column.index()], + domain.rotate_omega(*x, at), + ) + }) + .collect(); - // Hash each instance column evaluation - for eval in instance_evals.iter() { - transcript.write_scalar(*eval)?; + // Hash each instance column evaluation + for eval in instance_evals.iter() { + transcript.write_scalar(*eval)?; + } } } @@ -525,15 +613,16 @@ pub fn create_proof< .flat_map(|(((instance, advice), permutation), lookups)| { iter::empty() .chain( - pk.vk - .cs - .instance_queries - .iter() - .map(move |&(column, at)| ProverQuery { - point: domain.rotate_omega(*x, at), - poly: &instance.instance_polys[column.index()], - blind: Blind::default(), - }), + P::QUERY_INSTANCE + .then_some(pk.vk.cs.instance_queries.iter().map(move |&(column, at)| { + ProverQuery { + point: domain.rotate_omega(*x, at), + poly: &instance.instance_polys[column.index()], + blind: Blind::default(), + } + })) + .into_iter() + .flatten(), ) .chain( pk.vk diff --git a/halo2_proofs/src/plonk/vanishing/prover.rs b/halo2_proofs/src/plonk/vanishing/prover.rs index cc52273b..6c215a96 100644 --- a/halo2_proofs/src/plonk/vanishing/prover.rs +++ b/halo2_proofs/src/plonk/vanishing/prover.rs @@ -1,12 +1,14 @@ use std::iter; -use ff::Field; +use ff::{Field, PrimeField}; use group::Curve; -use rand_core::RngCore; +use rand_chacha::ChaCha20Rng; +use rand_core::{RngCore, SeedableRng}; +use rayon::{current_num_threads, prelude::*}; use super::Argument; use crate::{ - arithmetic::{eval_polynomial, CurveAffine, FieldExt}, + arithmetic::{eval_polynomial, CurveAffine}, plonk::{ChallengeX, ChallengeY, Error}, poly::{ self, @@ -47,10 +49,31 @@ impl Argument { 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); - } + let n = 1usize << domain.k() as usize; + let chunk_size = (n as f64 / current_num_threads() as f64).ceil() as usize; + let num_chunks = (n as f64 / chunk_size as f64).ceil() as usize; + let mut rand_vec = vec![C::Scalar::ZERO; n]; + + let mut thread_seeds: Vec = (0..num_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(chunk_size)) + .for_each(|(mut rng, chunk)| { + chunk + .iter_mut() + .for_each(|v| *v = C::Scalar::random(&mut rng)) + }); + + let random_poly: Polynomial = domain.coeff_from_vec(rand_vec); + // Sample a random blinding factor let random_blind = Blind(C::Scalar::random(rng)); @@ -138,9 +161,7 @@ impl Constructed { .h_blinds .iter() .rev() - .fold(Blind(C::Scalar::zero()), |acc, eval| { - acc * Blind(xn) + *eval - }); + .fold(Blind(C::Scalar::ZERO), |acc, eval| acc * Blind(xn) + *eval); let random_eval = eval_polynomial(&self.committed.random_poly, *x); transcript.write_scalar(random_eval)?; diff --git a/halo2_proofs/src/plonk/vanishing/verifier.rs b/halo2_proofs/src/plonk/vanishing/verifier.rs index 3570dee6..0881dfb2 100644 --- a/halo2_proofs/src/plonk/vanishing/verifier.rs +++ b/halo2_proofs/src/plonk/vanishing/verifier.rs @@ -94,8 +94,8 @@ impl PartiallyEvaluated { y: ChallengeY, xn: C::Scalar, ) -> Evaluated { - let expected_h_eval = expressions.fold(C::Scalar::zero(), |h_eval, v| h_eval * &*y + &v); - let expected_h_eval = expected_h_eval * ((xn - C::Scalar::one()).invert().unwrap()); + let expected_h_eval = expressions.fold(C::Scalar::ZERO, |h_eval, v| h_eval * &*y + &v); + let expected_h_eval = expected_h_eval * ((xn - C::Scalar::ONE).invert().unwrap()); let h_commitment = self.h_commitments @@ -104,7 +104,7 @@ impl PartiallyEvaluated { .fold(params.empty_msm(), |mut acc, commitment| { acc.scale(xn); let commitment: C::CurveExt = (*commitment).into(); - acc.append_term(C::Scalar::one(), commitment); + acc.append_term(C::Scalar::ONE, commitment); acc }); diff --git a/halo2_proofs/src/plonk/verifier.rs b/halo2_proofs/src/plonk/verifier.rs index db6366de..72ec7203 100644 --- a/halo2_proofs/src/plonk/verifier.rs +++ b/halo2_proofs/src/plonk/verifier.rs @@ -1,4 +1,4 @@ -use ff::Field; +use ff::{Field, FromUniformBytes, WithSmallOrderMulGroup}; use group::Curve; use rand_core::RngCore; use std::iter; @@ -7,7 +7,7 @@ use super::{ vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error, VerifyingKey, }; -use crate::arithmetic::{CurveAffine, FieldExt}; +use crate::arithmetic::{compute_inner_product, CurveAffine}; use crate::poly::commitment::{CommitmentScheme, Verifier}; use crate::poly::VerificationStrategy; use crate::poly::{ @@ -37,7 +37,10 @@ pub fn verify_proof< strategy: Strategy, instances: &[&[&[Scheme::Scalar]]], transcript: &mut T, -) -> Result { +) -> Result +where + Scheme::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64>, +{ // Check that instances matches the expected number of instance columns for instances in instances.iter() { if instances.len() != vk.cs.num_instance_columns { @@ -45,43 +48,79 @@ pub fn verify_proof< } } - let instance_commitments = instances - .iter() - .map(|instance| { - instance - .iter() - .map(|instance| { - if instance.len() > params.n() as usize - (vk.cs.blinding_factors() + 1) { - return Err(Error::InstanceTooLarge); - } - let mut poly = instance.to_vec(); - poly.resize(params.n() as usize, Scheme::Scalar::zero()); - let poly = vk.domain.lagrange_from_vec(poly); - - Ok(params.commit_lagrange(&poly, Blind::default()).to_affine()) - }) - .collect::, _>>() - }) - .collect::, _>>()?; + let instance_commitments = if V::QUERY_INSTANCE { + instances + .iter() + .map(|instance| { + instance + .iter() + .map(|instance| { + if instance.len() > params.n() as usize - (vk.cs.blinding_factors() + 1) { + return Err(Error::InstanceTooLarge); + } + let mut poly = instance.to_vec(); + poly.resize(params.n() as usize, Scheme::Scalar::ZERO); + let poly = vk.domain.lagrange_from_vec(poly); + + Ok(params.commit_lagrange(&poly, Blind::default()).to_affine()) + }) + .collect::, _>>() + }) + .collect::, _>>()? + } else { + vec![vec![]; instances.len()] + }; let num_proofs = instance_commitments.len(); // Hash verification key into transcript vk.hash_into(transcript)?; - for instance_commitments in instance_commitments.iter() { - // Hash the instance (external) commitments into the transcript - for commitment in instance_commitments { - transcript.common_point(*commitment)? + if V::QUERY_INSTANCE { + for instance_commitments in instance_commitments.iter() { + // Hash the instance (external) commitments into the transcript + for commitment in instance_commitments { + transcript.common_point(*commitment)? + } + } + } else { + for instance in instances.iter() { + for instance in instance.iter() { + for value in instance.iter() { + transcript.common_scalar(*value)?; + } + } } } - let advice_commitments = (0..num_proofs) - .map(|_| -> Result, _> { - // Hash the prover's advice commitments into the transcript - read_n_points(transcript, vk.cs.num_advice_columns) - }) - .collect::, _>>()?; + // Hash the prover's advice commitments into the transcript and squeeze challenges + let (advice_commitments, challenges) = { + let mut advice_commitments = + vec![vec![Scheme::Curve::default(); vk.cs.num_advice_columns]; num_proofs]; + let mut challenges = vec![Scheme::Scalar::ZERO; vk.cs.num_challenges]; + + for current_phase in vk.cs.phases() { + for advice_commitments in advice_commitments.iter_mut() { + for (phase, commitment) in vk + .cs + .advice_column_phase + .iter() + .zip(advice_commitments.iter_mut()) + { + if current_phase == *phase { + *commitment = transcript.read_point()?; + } + } + } + for (phase, challenge) in vk.cs.challenge_phase.iter().zip(challenges.iter_mut()) { + if current_phase == *phase { + *challenge = *transcript.squeeze_challenge_scalar::<()>(); + } + } + } + + (advice_commitments, challenges) + }; // Sample theta challenge for keeping lookup columns linearly independent let theta: ChallengeTheta<_> = transcript.squeeze_challenge_scalar(); @@ -131,9 +170,52 @@ pub fn verify_proof< // Sample x challenge, which is used to ensure the circuit is // satisfied with high probability. let x: ChallengeX<_> = transcript.squeeze_challenge_scalar(); - let instance_evals = (0..num_proofs) - .map(|_| -> Result, _> { read_n_scalars(transcript, vk.cs.instance_queries.len()) }) - .collect::, _>>()?; + let instance_evals = if V::QUERY_INSTANCE { + (0..num_proofs) + .map(|_| -> Result, _> { + read_n_scalars(transcript, vk.cs.instance_queries.len()) + }) + .collect::, _>>()? + } else { + let xn = x.pow(&[params.n() as u64, 0, 0, 0]); + let (min_rotation, max_rotation) = + vk.cs + .instance_queries + .iter() + .fold((0, 0), |(min, max), (_, rotation)| { + if rotation.0 < min { + (rotation.0, max) + } else if rotation.0 > max { + (min, rotation.0) + } else { + (min, max) + } + }); + let max_instance_len = instances + .iter() + .flat_map(|instance| instance.iter().map(|instance| instance.len())) + .max_by(Ord::cmp) + .unwrap_or_default(); + let l_i_s = &vk.domain.l_i_range( + *x, + xn, + -max_rotation..max_instance_len as i32 + min_rotation.abs(), + ); + instances + .iter() + .map(|instances| { + vk.cs + .instance_queries + .iter() + .map(|(column, rotation)| { + let instances = instances[column.index()]; + let offset = (max_rotation - rotation.0) as usize; + compute_inner_product(instances, &l_i_s[offset..offset + instances.len()]) + }) + .collect::>() + }) + .collect::>() + }; let advice_evals = (0..num_proofs) .map(|_| -> Result, _> { read_n_scalars(transcript, vk.cs.advice_queries.len()) }) @@ -174,7 +256,7 @@ pub fn verify_proof< let l_last = l_evals[0]; let l_blind: Scheme::Scalar = l_evals[1..(1 + blinding_factors)] .iter() - .fold(Scheme::Scalar::zero(), |acc, eval| acc + eval); + .fold(Scheme::Scalar::ZERO, |acc, eval| acc + eval); let l_0 = l_evals[1 + blinding_factors]; // Compute the expected value of h(x) @@ -184,6 +266,7 @@ pub fn verify_proof< .zip(permutations_evaluated.iter()) .zip(lookups_evaluated.iter()) .flat_map(|(((advice_evals, instance_evals), permutation), lookups)| { + let challenges = &challenges; let fixed_evals = &fixed_evals; std::iter::empty() // Evaluate the circuit using the custom gates provided @@ -192,9 +275,10 @@ pub fn verify_proof< poly.evaluate( &|scalar| scalar, &|_| panic!("virtual selectors are removed during optimization"), - &|query| fixed_evals[query.index], - &|query| advice_evals[query.index], - &|query| instance_evals[query.index], + &|query| fixed_evals[query.index.unwrap()], + &|query| advice_evals[query.index.unwrap()], + &|query| instance_evals[query.index.unwrap()], + &|challenge| challenges[challenge.index()], &|a| -a, &|a, b| a + &b, &|a, b| a * &b, @@ -232,6 +316,7 @@ pub fn verify_proof< advice_evals, fixed_evals, instance_evals, + challenges, ) }) .into_iter(), @@ -257,15 +342,20 @@ pub fn verify_proof< lookups, )| { iter::empty() - .chain(vk.cs.instance_queries.iter().enumerate().map( - move |(query_index, &(column, at))| { - VerifierQuery::new_commitment( - &instance_commitments[column.index()], - vk.domain.rotate_omega(*x, at), - instance_evals[query_index], - ) - }, - )) + .chain( + V::QUERY_INSTANCE + .then_some(vk.cs.instance_queries.iter().enumerate().map( + move |(query_index, &(column, at))| { + VerifierQuery::new_commitment( + &instance_commitments[column.index()], + vk.domain.rotate_omega(*x, at), + instance_evals[query_index], + ) + }, + )) + .into_iter() + .flatten(), + ) .chain(vk.cs.advice_queries.iter().enumerate().map( move |(query_index, &(column, at))| { VerifierQuery::new_commitment( diff --git a/halo2_proofs/src/plonk/verifier/batch.rs b/halo2_proofs/src/plonk/verifier/batch.rs index f07ba414..04e08be4 100644 --- a/halo2_proofs/src/plonk/verifier/batch.rs +++ b/halo2_proofs/src/plonk/verifier/batch.rs @@ -1,5 +1,6 @@ use std::{io, marker::PhantomData}; +use ff::FromUniformBytes; use group::ff::Field; use halo2curves::CurveAffine; use rand_core::{OsRng, RngCore}; @@ -67,7 +68,10 @@ pub struct BatchVerifier { items: Vec>, } -impl BatchVerifier { +impl BatchVerifier +where + C::Scalar: FromUniformBytes<64>, +{ /// Constructs a new batch verifier. pub fn new() -> Self { Self { items: vec![] } diff --git a/halo2_proofs/src/poly.rs b/halo2_proofs/src/poly.rs index ecf66a6e..7d9c2c4c 100644 --- a/halo2_proofs/src/poly.rs +++ b/halo2_proofs/src/poly.rs @@ -3,11 +3,14 @@ //! the committed polynomials at arbitrary points. use crate::arithmetic::parallelize; +use crate::helpers::SerdePrimeField; use crate::plonk::Assigned; +use crate::SerdeFormat; +use ff::PrimeField; use group::ff::{BatchInvert, Field}; -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}; @@ -143,7 +146,37 @@ impl Polynomial { } } -pub(crate) fn batch_invert_assigned( +impl Polynomial { + /// Reads polynomial from buffer using `SerdePrimeField::read`. + pub(crate) fn read(reader: &mut R, format: SerdeFormat) -> io::Result { + let mut poly_len = [0u8; 4]; + reader.read_exact(&mut poly_len)?; + let poly_len = u32::from_be_bytes(poly_len); + + (0..poly_len) + .map(|_| F::read(reader, format)) + .collect::>>() + .map(|values| Self { + values, + _marker: PhantomData, + }) + } + + /// Writes polynomial to buffer using `SerdePrimeField::write`. + pub(crate) fn write( + &self, + writer: &mut W, + format: SerdeFormat, + ) -> io::Result<()> { + writer.write_all(&(self.values.len() as u32).to_be_bytes())?; + for value in self.values.iter() { + value.write(writer, format)?; + } + Ok(()) + } +} + +pub(crate) fn batch_invert_assigned( assigned: Vec, LagrangeCoeff>>, ) -> Vec> { let mut assigned_denominators: Vec<_> = assigned @@ -168,9 +201,7 @@ pub(crate) fn batch_invert_assigned( assigned .iter() .zip(assigned_denominators.into_iter()) - .map(|(poly, inv_denoms)| { - poly.invert(inv_denoms.into_iter().map(|d| d.unwrap_or_else(F::one))) - }) + .map(|(poly, inv_denoms)| poly.invert(inv_denoms.into_iter().map(|d| d.unwrap_or(F::ONE)))) .collect() } @@ -240,13 +271,13 @@ impl Mul for Polynomial { type Output = Polynomial; fn mul(mut self, rhs: F) -> Polynomial { - if rhs == F::zero() { + if rhs == F::ZERO { return Polynomial { - values: vec![F::zero(); self.len()], + values: vec![F::ZERO; self.len()], _marker: PhantomData, }; } - if rhs == F::one() { + if rhs == F::ONE { return self; } diff --git a/halo2_proofs/src/poly/commitment.rs b/halo2_proofs/src/poly/commitment.rs index ef336d59..3a0b68f6 100644 --- a/halo2_proofs/src/poly/commitment.rs +++ b/halo2_proofs/src/poly/commitment.rs @@ -6,8 +6,8 @@ use super::{ use crate::poly::Error; use crate::transcript::{EncodedChallenge, TranscriptRead, TranscriptWrite}; use ff::Field; -use group::Curve; -use halo2curves::{CurveAffine, CurveExt, FieldExt}; +use group::{Curve, Group}; +use halo2curves::{CurveAffine, CurveExt}; use rand_core::RngCore; use std::{ fmt::Debug, @@ -18,7 +18,7 @@ use std::{ /// Defines components of a commitment scheme. pub trait CommitmentScheme { /// Application field of this commitment scheme - type Scalar: FieldExt + halo2curves::Group; + type Scalar: Field; /// Elliptic curve used to commit the application and witnesses type Curve: CurveAffine; @@ -99,7 +99,7 @@ pub trait ParamsProver<'params, C: CurveAffine>: Params<'params, C> { pub trait ParamsVerifier<'params, C: CurveAffine>: Params<'params, C> {} /// Multi scalar multiplication engine -pub trait MSM: Clone + Debug { +pub trait MSM: Clone + Debug + Send + Sync { /// Add arbitrary term (the scalar and the point) fn append_term(&mut self, scalar: C::Scalar, point: C::CurveExt); @@ -126,6 +126,9 @@ pub trait MSM: Clone + Debug { /// Common multi-open prover interface for various commitment schemes pub trait Prover<'params, Scheme: CommitmentScheme> { + /// Query instance or not + const QUERY_INSTANCE: bool; + /// Creates new prover instance fn new(params: &'params Scheme::ParamsProver) -> Self; @@ -156,6 +159,9 @@ pub trait Verifier<'params, Scheme: CommitmentScheme> { /// Accumulator fot comressed verification type MSMAccumulator; + /// Query instance or not + const QUERY_INSTANCE: bool; + /// Creates new verifier instance fn new(params: &'params Scheme::ParamsVerifier) -> Self; @@ -186,20 +192,20 @@ pub trait Verifier<'params, Scheme: CommitmentScheme> { #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct Blind(pub F); -impl Default for Blind { +impl Default for Blind { fn default() -> Self { - Blind(F::one()) + Blind(F::ONE) } } -impl Blind { +impl Blind { /// Given `rng` creates new blinding scalar pub fn new(rng: &mut R) -> Self { Blind(F::random(rng)) } } -impl Add for Blind { +impl Add for Blind { type Output = Self; fn add(self, rhs: Blind) -> Self { @@ -207,7 +213,7 @@ impl Add for Blind { } } -impl Mul for Blind { +impl Mul for Blind { type Output = Self; fn mul(self, rhs: Blind) -> Self { @@ -215,25 +221,25 @@ impl Mul for Blind { } } -impl AddAssign for Blind { +impl AddAssign for Blind { fn add_assign(&mut self, rhs: Blind) { self.0 += rhs.0; } } -impl MulAssign for Blind { +impl MulAssign for Blind { fn mul_assign(&mut self, rhs: Blind) { self.0 *= rhs.0; } } -impl AddAssign for Blind { +impl AddAssign for Blind { fn add_assign(&mut self, rhs: F) { self.0 += rhs; } } -impl MulAssign for Blind { +impl MulAssign for Blind { fn mul_assign(&mut self, rhs: F) { self.0 *= rhs; } diff --git a/halo2_proofs/src/poly/domain.rs b/halo2_proofs/src/poly/domain.rs index a753cb85..b442dbc8 100644 --- a/halo2_proofs/src/poly/domain.rs +++ b/halo2_proofs/src/poly/domain.rs @@ -2,13 +2,16 @@ //! domain that is of a suitable size for the application. use crate::{ - arithmetic::{best_fft, parallelize, FieldExt, Group}, + arithmetic::{best_fft, parallelize}, plonk::Assigned, }; use super::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation}; - -use group::ff::{BatchInvert, Field, PrimeField}; +use ff::WithSmallOrderMulGroup; +use group::{ + ff::{BatchInvert, Field, PrimeField}, + Group, +}; use std::marker::PhantomData; @@ -16,24 +19,24 @@ use std::marker::PhantomData; /// performing operations on an evaluation domain of size $2^k$ and an extended /// domain of size $2^{k} * j$ with $j \neq 0$. #[derive(Clone, Debug)] -pub struct EvaluationDomain { +pub struct EvaluationDomain { n: u64, k: u32, extended_k: u32, - omega: G::Scalar, - omega_inv: G::Scalar, - extended_omega: G::Scalar, - extended_omega_inv: G::Scalar, - g_coset: G::Scalar, - g_coset_inv: G::Scalar, + omega: F, + omega_inv: F, + extended_omega: F, + extended_omega_inv: F, + g_coset: F, + g_coset_inv: F, quotient_poly_degree: u64, - ifft_divisor: G::Scalar, - extended_ifft_divisor: G::Scalar, - t_evaluations: Vec, - barycentric_weight: G::Scalar, + ifft_divisor: F, + extended_ifft_divisor: F, + t_evaluations: Vec, + barycentric_weight: F, } -impl EvaluationDomain { +impl> EvaluationDomain { /// This constructs a new evaluation domain object based on the provided /// values $j, k$. pub fn new(j: u32, k: u32) -> Self { @@ -51,12 +54,12 @@ impl EvaluationDomain { extended_k += 1; } - let mut extended_omega = G::Scalar::root_of_unity(); + let mut extended_omega = F::ROOT_OF_UNITY; // Get extended_omega, the 2^{extended_k}'th root of unity // The loop computes extended_omega = omega^{2 ^ (S - extended_k)} // Notice that extended_omega ^ {2 ^ extended_k} = omega ^ {2^S} = 1. - for _ in extended_k..G::Scalar::S { + for _ in extended_k..F::S { extended_omega = extended_omega.square(); } let extended_omega = extended_omega; @@ -78,14 +81,14 @@ impl EvaluationDomain { // already. // The coset evaluation domain is: // zeta {1, extended_omega, extended_omega^2, ..., extended_omega^{(2^extended_k) - 1}} - let g_coset = G::Scalar::ZETA; + let g_coset = F::ZETA; let g_coset_inv = g_coset.square(); let mut t_evaluations = Vec::with_capacity(1 << (extended_k - k)); { // Compute the evaluations of t(X) = X^n - 1 in the coset evaluation domain. // We don't have to compute all of them, because it will repeat. - let orig = G::Scalar::ZETA.pow_vartime(&[n as u64, 0, 0, 0]); + let orig = F::ZETA.pow_vartime(&[n as u64, 0, 0, 0]); let step = extended_omega.pow_vartime(&[n as u64, 0, 0, 0]); let mut cur = orig; loop { @@ -99,19 +102,19 @@ impl EvaluationDomain { // Subtract 1 from each to give us t_evaluations[i] = t(zeta * extended_omega^i) for coeff in &mut t_evaluations { - *coeff -= &G::Scalar::one(); + *coeff -= &F::ONE; } // Invert, because we're dividing by this polynomial. // We invert in a batch, below. } - let mut ifft_divisor = G::Scalar::from(1 << k); // Inversion computed later - let mut extended_ifft_divisor = G::Scalar::from(1 << extended_k); // Inversion computed later + let mut ifft_divisor = F::from(1 << k); // Inversion computed later + let mut extended_ifft_divisor = F::from(1 << extended_k); // Inversion computed later // The barycentric weight of 1 over the evaluation domain // 1 / \prod_{i != 0} (1 - omega^i) - let mut barycentric_weight = G::Scalar::from(n); // Inversion computed later + let mut barycentric_weight = F::from(n); // Inversion computed later // Compute batch inversion t_evaluations @@ -144,7 +147,7 @@ impl EvaluationDomain { /// Obtains a polynomial in Lagrange form when given a vector of Lagrange /// coefficients of size `n`; panics if the provided vector is the wrong /// length. - pub fn lagrange_from_vec(&self, values: Vec) -> Polynomial { + pub fn lagrange_from_vec(&self, values: Vec) -> Polynomial { assert_eq!(values.len(), self.n as usize); Polynomial { @@ -156,7 +159,7 @@ impl EvaluationDomain { /// Obtains a polynomial in coefficient form when given a vector of /// coefficients of size `n`; panics if the provided vector is the wrong /// length. - pub fn coeff_from_vec(&self, values: Vec) -> Polynomial { + pub fn coeff_from_vec(&self, values: Vec) -> Polynomial { assert_eq!(values.len(), self.n as usize); Polynomial { @@ -166,35 +169,32 @@ impl EvaluationDomain { } /// Returns an empty (zero) polynomial in the coefficient basis - pub fn empty_coeff(&self) -> Polynomial { + pub fn empty_coeff(&self) -> Polynomial { Polynomial { - values: vec![G::group_zero(); self.n as usize], + values: vec![F::ZERO; self.n as usize], _marker: PhantomData, } } /// Returns an empty (zero) polynomial in the Lagrange coefficient basis - pub fn empty_lagrange(&self) -> Polynomial { + pub fn empty_lagrange(&self) -> Polynomial { Polynomial { - values: vec![G::group_zero(); self.n as usize], + values: vec![F::ZERO; self.n as usize], _marker: PhantomData, } } /// Returns an empty (zero) polynomial in the Lagrange coefficient basis, with /// deferred inversions. - pub(crate) fn empty_lagrange_assigned(&self) -> Polynomial, LagrangeCoeff> - where - G: Field, - { + pub(crate) fn empty_lagrange_assigned(&self) -> Polynomial, LagrangeCoeff> { Polynomial { - values: vec![G::group_zero().into(); self.n as usize], + values: vec![F::ZERO.into(); self.n as usize], _marker: PhantomData, } } /// Returns a constant polynomial in the Lagrange coefficient basis - pub fn constant_lagrange(&self, scalar: G) -> Polynomial { + pub fn constant_lagrange(&self, scalar: F) -> Polynomial { Polynomial { values: vec![scalar; self.n as usize], _marker: PhantomData, @@ -203,16 +203,16 @@ impl EvaluationDomain { /// Returns an empty (zero) polynomial in the extended Lagrange coefficient /// basis - pub fn empty_extended(&self) -> Polynomial { + pub fn empty_extended(&self) -> Polynomial { Polynomial { - values: vec![G::group_zero(); self.extended_len()], + values: vec![F::ZERO; self.extended_len()], _marker: PhantomData, } } /// Returns a constant polynomial in the extended Lagrange coefficient /// basis - pub fn constant_extended(&self, scalar: G) -> Polynomial { + pub fn constant_extended(&self, scalar: F) -> Polynomial { Polynomial { values: vec![scalar; self.extended_len()], _marker: PhantomData, @@ -223,7 +223,7 @@ 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 @@ -239,12 +239,12 @@ impl EvaluationDomain { /// evaluation domain, rotating by `rotation` if desired. pub fn coeff_to_extended( &self, - mut a: Polynomial, - ) -> Polynomial { + mut a: Polynomial, + ) -> Polynomial { assert_eq!(a.values.len(), 1 << self.k); self.distribute_powers_zeta(&mut a.values, true); - a.values.resize(self.extended_len(), G::group_zero()); + a.values.resize(self.extended_len(), F::ZERO); best_fft(&mut a.values, self.extended_omega, self.extended_k); Polynomial { @@ -256,9 +256,9 @@ impl EvaluationDomain { /// Rotate the extended domain polynomial over the original domain. pub fn rotate_extended( &self, - poly: &Polynomial, + poly: &Polynomial, rotation: Rotation, - ) -> Polynomial { + ) -> Polynomial { let new_rotation = ((1 << (self.extended_k - self.k)) * rotation.0.abs()) as usize; let mut poly = poly.clone(); @@ -278,7 +278,7 @@ 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 @@ -306,15 +306,15 @@ impl EvaluationDomain { /// polynomial of the $2^k$ size domain. pub fn divide_by_vanishing_poly( &self, - mut a: Polynomial, - ) -> Polynomial { + mut a: Polynomial, + ) -> Polynomial { assert_eq!(a.values.len(), self.extended_len()); // Divide to obtain the quotient polynomial in the coset evaluation // domain. parallelize(&mut a.values, |h, mut index| { for h in h { - h.group_scale(&self.t_evaluations[index % self.t_evaluations.len()]); + *h *= &self.t_evaluations[index % self.t_evaluations.len()]; index += 1; } }); @@ -332,7 +332,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 [F], into_coset: bool) { let coset_powers = if into_coset { [self.g_coset, self.g_coset_inv] } else { @@ -343,19 +343,19 @@ impl EvaluationDomain { // Distribute powers to move into/from coset let i = index % (coset_powers.len() + 1); if i != 0 { - a.group_scale(&coset_powers[i - 1]); + *a *= &coset_powers[i - 1]; } index += 1; } }); } - fn ifft(a: &mut [G], omega_inv: G::Scalar, log_n: u32, divisor: G::Scalar) { + fn ifft(a: &mut [F], omega_inv: F, log_n: u32, divisor: F) { best_fft(a, omega_inv, log_n); parallelize(a, |a, _| { for a in a { // Finish iFFT - a.group_scale(&divisor); + *a *= &divisor; } }); } @@ -376,24 +376,24 @@ impl EvaluationDomain { } /// Get $\omega$, the generator of the $2^k$ order multiplicative subgroup. - pub fn get_omega(&self) -> G::Scalar { + pub fn get_omega(&self) -> F { self.omega } /// Get $\omega^{-1}$, the inverse of the generator of the $2^k$ order /// multiplicative subgroup. - pub fn get_omega_inv(&self) -> G::Scalar { + pub fn get_omega_inv(&self) -> F { self.omega_inv } /// Get the generator of the extended domain's multiplicative subgroup. - pub fn get_extended_omega(&self) -> G::Scalar { + pub fn get_extended_omega(&self) -> F { self.extended_omega } /// Multiplies a value by some power of $\omega$, essentially rotating over /// the domain. - pub fn rotate_omega(&self, value: G::Scalar, rotation: Rotation) -> G::Scalar { + pub fn rotate_omega(&self, value: F, rotation: Rotation) -> F { let mut point = value; if rotation.0 >= 0 { point *= &self.get_omega().pow_vartime(&[rotation.0 as u64]); @@ -434,23 +434,23 @@ impl EvaluationDomain { /// which is the barycentric weight of $\omega^i$. pub fn l_i_range + Clone>( &self, - x: G::Scalar, - xn: G::Scalar, + x: F, + xn: F, rotations: I, - ) -> Vec { + ) -> Vec { let mut results; { let rotations = rotations.clone().into_iter(); results = Vec::with_capacity(rotations.size_hint().1.unwrap_or(0)); for rotation in rotations { let rotation = Rotation(rotation); - let result = x - self.rotate_omega(G::Scalar::one(), rotation); + let result = x - self.rotate_omega(F::ONE, rotation); results.push(result); } results.iter_mut().batch_invert(); } - let common = (xn - G::Scalar::one()) * self.barycentric_weight; + let common = (xn - F::ONE) * self.barycentric_weight; for (rotation, result) in rotations.into_iter().zip(results.iter_mut()) { let rotation = Rotation(rotation); *result = self.rotate_omega(*result * common, rotation); @@ -467,7 +467,7 @@ impl EvaluationDomain { /// Obtain a pinned version of this evaluation domain; a structure with the /// minimal parameters needed to determine the rest of the evaluation /// domain. - pub fn pinned(&self) -> PinnedEvaluationDomain<'_, G> { + pub fn pinned(&self) -> PinnedEvaluationDomain<'_, F> { PinnedEvaluationDomain { k: &self.k, extended_k: &self.extended_k, @@ -479,10 +479,10 @@ impl EvaluationDomain { /// Represents the minimal parameters that determine an `EvaluationDomain`. #[allow(dead_code)] #[derive(Debug)] -pub struct PinnedEvaluationDomain<'a, G: Group> { +pub struct PinnedEvaluationDomain<'a, F: Field> { k: &'a u32, extended_k: &'a u32, - omega: &'a G::Scalar, + omega: &'a F, } #[test] @@ -541,7 +541,7 @@ fn test_l_i() { } for i in 0..8 { let mut l_i = vec![Scalar::zero(); 8]; - l_i[i] = Scalar::one(); + l_i[i] = Scalar::ONE; let l_i = lagrange_interpolate(&points[..], &l_i[..]); l.push(l_i); } diff --git a/halo2_proofs/src/poly/evaluator.rs b/halo2_proofs/src/poly/evaluator.rs index d1ba853c..5d202212 100644 --- a/halo2_proofs/src/poly/evaluator.rs +++ b/halo2_proofs/src/poly/evaluator.rs @@ -9,7 +9,7 @@ use std::{ }; use group::ff::Field; -use halo2curves::FieldExt; +use halo2curves::Field; use super::{ Basis, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation, @@ -135,7 +135,7 @@ impl Evaluator { ) -> Polynomial where E: Copy + Send + Sync, - F: FieldExt, + F: Field, B: BasisOps, { // Traverse `ast` to collect the used leaves. @@ -192,7 +192,7 @@ impl Evaluator { }) .collect(); - struct AstContext<'a, E, F: FieldExt, B: Basis> { + struct AstContext<'a, E, F: Field, B: Basis> { domain: &'a EvaluationDomain, poly_len: usize, chunk_size: usize, @@ -200,7 +200,7 @@ impl Evaluator { leaves: &'a HashMap, &'a [F]>, } - fn recurse( + fn recurse( ast: &Ast, ctx: &AstContext<'_, E, F, B>, ) -> Vec { @@ -230,7 +230,7 @@ impl Evaluator { lhs } Ast::DistributePowers(terms, base) => terms.iter().fold( - B::constant_term(ctx.poly_len, ctx.chunk_size, ctx.chunk_index, F::zero()), + B::constant_term(ctx.poly_len, ctx.chunk_size, ctx.chunk_index, F::ZERO), |mut acc, term| { let term = recurse(term, ctx); for (acc, term) in acc.iter_mut().zip(term) { @@ -347,7 +347,7 @@ impl From> for Ast { impl Ast { pub(crate) fn one() -> Self { - Self::ConstantTerm(F::one()) + Self::ConstantTerm(F::ONE) } } @@ -355,7 +355,7 @@ impl Neg for Ast { type Output = Ast; fn neg(self) -> Self::Output { - Ast::Scale(Arc::new(self), -F::one()) + Ast::Scale(Arc::new(self), -F::ONE) } } @@ -489,21 +489,21 @@ impl MulAssign for Ast { /// Operations which can be performed over a given basis. pub(crate) trait BasisOps: Basis { - fn empty_poly(domain: &EvaluationDomain) -> Polynomial; - fn constant_term( + fn empty_poly(domain: &EvaluationDomain) -> Polynomial; + fn constant_term( poly_len: usize, chunk_size: usize, chunk_index: usize, scalar: F, ) -> Vec; - fn linear_term( + fn linear_term( domain: &EvaluationDomain, poly_len: usize, chunk_size: usize, chunk_index: usize, scalar: F, ) -> Vec; - fn rotate( + fn rotate( domain: &EvaluationDomain, poly: &Polynomial, rotation: Rotation, @@ -511,31 +511,31 @@ pub(crate) trait BasisOps: Basis { } impl BasisOps for Coeff { - fn empty_poly(domain: &EvaluationDomain) -> Polynomial { + fn empty_poly(domain: &EvaluationDomain) -> Polynomial { domain.empty_coeff() } - fn constant_term( + fn constant_term( poly_len: usize, chunk_size: usize, chunk_index: usize, scalar: F, ) -> Vec { - let mut chunk = vec![F::zero(); cmp::min(chunk_size, poly_len - chunk_size * chunk_index)]; + let mut chunk = vec![F::ZERO; cmp::min(chunk_size, poly_len - chunk_size * chunk_index)]; if chunk_index == 0 { chunk[0] = scalar; } chunk } - fn linear_term( + fn linear_term( _: &EvaluationDomain, poly_len: usize, chunk_size: usize, chunk_index: usize, scalar: F, ) -> Vec { - let mut chunk = vec![F::zero(); cmp::min(chunk_size, poly_len - chunk_size * chunk_index)]; + let mut chunk = vec![F::ZERO; cmp::min(chunk_size, poly_len - chunk_size * chunk_index)]; // If the chunk size is 1 (e.g. if we have a small k and many threads), then the // linear coefficient is the second chunk. Otherwise, the chunk size is greater // than one, and the linear coefficient is the second element of the first chunk. @@ -550,7 +550,7 @@ impl BasisOps for Coeff { chunk } - fn rotate( + fn rotate( _: &EvaluationDomain, _: &Polynomial, _: Rotation, @@ -560,11 +560,11 @@ impl BasisOps for Coeff { } impl BasisOps for LagrangeCoeff { - fn empty_poly(domain: &EvaluationDomain) -> Polynomial { + fn empty_poly(domain: &EvaluationDomain) -> Polynomial { domain.empty_lagrange() } - fn constant_term( + fn constant_term( poly_len: usize, chunk_size: usize, chunk_index: usize, @@ -573,7 +573,7 @@ impl BasisOps for LagrangeCoeff { vec![scalar; cmp::min(chunk_size, poly_len - chunk_size * chunk_index)] } - fn linear_term( + fn linear_term( domain: &EvaluationDomain, poly_len: usize, chunk_size: usize, @@ -592,7 +592,7 @@ impl BasisOps for LagrangeCoeff { .collect() } - fn rotate( + fn rotate( _: &EvaluationDomain, poly: &Polynomial, rotation: Rotation, @@ -602,11 +602,11 @@ impl BasisOps for LagrangeCoeff { } impl BasisOps for ExtendedLagrangeCoeff { - fn empty_poly(domain: &EvaluationDomain) -> Polynomial { + fn empty_poly(domain: &EvaluationDomain) -> Polynomial { domain.empty_extended() } - fn constant_term( + fn constant_term( poly_len: usize, chunk_size: usize, chunk_index: usize, @@ -615,7 +615,7 @@ impl BasisOps for ExtendedLagrangeCoeff { vec![scalar; cmp::min(chunk_size, poly_len - chunk_size * chunk_index)] } - fn linear_term( + fn linear_term( domain: &EvaluationDomain, poly_len: usize, chunk_size: usize, @@ -637,7 +637,7 @@ impl BasisOps for ExtendedLagrangeCoeff { .collect() } - fn rotate( + fn rotate( domain: &EvaluationDomain, poly: &Polynomial, rotation: Rotation, diff --git a/halo2_proofs/src/poly/ipa/commitment.rs b/halo2_proofs/src/poly/ipa/commitment.rs index 9060e831..f9b4ad05 100644 --- a/halo2_proofs/src/poly/ipa/commitment.rs +++ b/halo2_proofs/src/poly/ipa/commitment.rs @@ -4,7 +4,7 @@ //! [halo]: https://eprint.iacr.org/2019/1021 use crate::arithmetic::{ - best_fft, best_multiexp, g_to_lagrange, parallelize, CurveAffine, CurveExt, FieldExt, Group, + best_fft, best_multiexp, g_to_lagrange, parallelize, CurveAffine, CurveExt, }; use crate::helpers::CurveRead; use crate::poly::commitment::{Blind, CommitmentScheme, Params, ParamsProver, ParamsVerifier, MSM}; @@ -12,7 +12,7 @@ use crate::poly::ipa::msm::MSMIPA; use crate::poly::{Coeff, LagrangeCoeff, Polynomial}; use ff::{Field, PrimeField}; -use group::{prime::PrimeCurveAffine, Curve, Group as _}; +use group::{prime::PrimeCurveAffine, Curve, Group}; use std::marker::PhantomData; use std::ops::{Add, AddAssign, Mul, MulAssign}; @@ -234,9 +234,7 @@ impl<'params, C: CurveAffine> ParamsProver<'params, C> for ParamsIPA { #[cfg(test)] mod test { - use crate::arithmetic::{ - best_fft, best_multiexp, parallelize, CurveAffine, CurveExt, FieldExt, Group, - }; + use crate::arithmetic::{best_fft, best_multiexp, parallelize, CurveAffine, CurveExt}; use crate::helpers::CurveRead; use crate::poly::commitment::ParamsProver; use crate::poly::commitment::{Blind, CommitmentScheme, Params, MSM}; @@ -245,7 +243,7 @@ mod test { use crate::poly::{Coeff, LagrangeCoeff, Polynomial}; use ff::{Field, PrimeField}; - use group::{prime::PrimeCurveAffine, Curve, Group as _}; + use group::{prime::PrimeCurveAffine, Curve, Group}; use std::marker::PhantomData; use std::ops::{Add, AddAssign, Mul, MulAssign}; @@ -309,7 +307,7 @@ mod test { use rand_core::OsRng; use super::super::commitment::{Blind, Params}; - use crate::arithmetic::{eval_polynomial, FieldExt}; + use crate::arithmetic::eval_polynomial; use crate::halo2curves::pasta::{EpAffine, Fq}; use crate::poly::EvaluationDomain; use crate::transcript::{ @@ -363,7 +361,7 @@ mod test { assert_eq!(v, v_prime); let mut commitment_msm = MSMIPA::new(¶ms); - commitment_msm.append_term(Field::one(), p.into()); + commitment_msm.append_term(Fq::one(), p.into()); let guard = verify_proof(¶ms, commitment_msm, &mut transcript, *x, v).unwrap(); let ch_verifier = transcript.squeeze_challenge(); diff --git a/halo2_proofs/src/poly/ipa/commitment/prover.rs b/halo2_proofs/src/poly/ipa/commitment/prover.rs index 3b22b31b..d176987c 100644 --- a/halo2_proofs/src/poly/ipa/commitment/prover.rs +++ b/halo2_proofs/src/poly/ipa/commitment/prover.rs @@ -3,7 +3,7 @@ use rand_core::RngCore; use super::{Params, ParamsIPA}; use crate::arithmetic::{ - best_multiexp, compute_inner_product, eval_polynomial, parallelize, CurveAffine, FieldExt, + best_multiexp, compute_inner_product, eval_polynomial, parallelize, CurveAffine, }; use crate::poly::commitment::ParamsProver; @@ -87,7 +87,7 @@ pub fn create_proof< // `p_prime` and `b` is the evaluation of the polynomial at `x_3`. let mut b = Vec::with_capacity(1 << params.k); { - let mut cur = C::Scalar::one(); + let mut cur = C::Scalar::ONE; for _ in 0..(1 << params.k) { b.push(cur); cur *= &x_3; diff --git a/halo2_proofs/src/poly/ipa/commitment/verifier.rs b/halo2_proofs/src/poly/ipa/commitment/verifier.rs index b3b30e0b..0b608428 100644 --- a/halo2_proofs/src/poly/ipa/commitment/verifier.rs +++ b/halo2_proofs/src/poly/ipa/commitment/verifier.rs @@ -96,10 +96,10 @@ pub fn verify_proof<'params, C: CurveAffine, E: EncodedChallenge, T: Transcri /// Computes $\prod\limits_{i=0}^{k-1} (1 + u_{k - 1 - i} x^{2^i})$. fn compute_b(x: F, u: &[F]) -> F { - let mut tmp = F::one(); + let mut tmp = F::ONE; let mut cur = x; for u_j in u.iter().rev() { - tmp *= F::one() + &(*u_j * &cur); + tmp *= F::ONE + &(*u_j * &cur); cur *= cur; } tmp diff --git a/halo2_proofs/src/poly/ipa/msm.rs b/halo2_proofs/src/poly/ipa/msm.rs index 63f994b4..3316e253 100644 --- a/halo2_proofs/src/poly/ipa/msm.rs +++ b/halo2_proofs/src/poly/ipa/msm.rs @@ -191,7 +191,7 @@ impl<'a, C: CurveAffine> MSMIPA<'a, C> { if let Some(g_scalars) = self.g_scalars.as_mut() { g_scalars[0] += &constant; } else { - let mut g_scalars = vec![C::Scalar::zero(); self.params.n as usize]; + let mut g_scalars = vec![C::Scalar::ZERO; self.params.n as usize]; g_scalars[0] += &constant; self.g_scalars = Some(g_scalars); } diff --git a/halo2_proofs/src/poly/ipa/multiopen.rs b/halo2_proofs/src/poly/ipa/multiopen.rs index b724139a..fd6aa785 100644 --- a/halo2_proofs/src/poly/ipa/multiopen.rs +++ b/halo2_proofs/src/poly/ipa/multiopen.rs @@ -3,14 +3,10 @@ //! //! [halo]: https://eprint.iacr.org/2019/1021 -use std::collections::{BTreeMap, BTreeSet}; - use super::*; -use crate::{ - arithmetic::{CurveAffine, FieldExt}, - poly::query::Query, - transcript::ChallengeScalar, -}; +use crate::{arithmetic::CurveAffine, poly::query::Query, transcript::ChallengeScalar}; +use ff::Field; +use std::collections::{BTreeMap, BTreeSet}; mod prover; mod verifier; @@ -63,7 +59,7 @@ type IntermediateSets = ( Vec>, ); -fn construct_intermediate_sets>(queries: I) -> IntermediateSets +fn construct_intermediate_sets>(queries: I) -> IntermediateSets where I: IntoIterator + Clone, { diff --git a/halo2_proofs/src/poly/ipa/multiopen/prover.rs b/halo2_proofs/src/poly/ipa/multiopen/prover.rs index e01b9d17..6d65e7ae 100644 --- a/halo2_proofs/src/poly/ipa/multiopen/prover.rs +++ b/halo2_proofs/src/poly/ipa/multiopen/prover.rs @@ -1,7 +1,7 @@ use super::{ construct_intermediate_sets, ChallengeX1, ChallengeX2, ChallengeX3, ChallengeX4, Query, }; -use crate::arithmetic::{eval_polynomial, kate_division, CurveAffine, FieldExt}; +use crate::arithmetic::{eval_polynomial, kate_division, CurveAffine}; use crate::poly::commitment::ParamsProver; use crate::poly::commitment::{Blind, Params, Prover}; use crate::poly::ipa::commitment::{self, IPACommitmentScheme, ParamsIPA}; @@ -22,6 +22,8 @@ pub struct ProverIPA<'params, C: CurveAffine> { } impl<'params, C: CurveAffine> Prover<'params, IPACommitmentScheme> for ProverIPA<'params, C> { + const QUERY_INSTANCE: bool = true; + fn new(params: &'params ParamsIPA) -> Self { Self { params } } @@ -45,7 +47,7 @@ impl<'params, C: CurveAffine> Prover<'params, IPACommitmentScheme> for Prover // Collapse openings at same point sets together into single openings using // x_1 challenge. let mut q_polys: Vec>> = vec![None; point_sets.len()]; - let mut q_blinds = vec![Blind(C::Scalar::zero()); point_sets.len()]; + let mut q_blinds = vec![Blind(C::Scalar::ZERO); point_sets.len()]; { let mut accumulate = |set_idx: usize, @@ -78,7 +80,7 @@ impl<'params, C: CurveAffine> Prover<'params, IPACommitmentScheme> for Prover .fold(poly.clone().unwrap().values, |poly, point| { kate_division(&poly, *point) }); - poly.resize(self.params.n as usize, C::Scalar::zero()); + poly.resize(self.params.n as usize, C::Scalar::ZERO); let poly = Polynomial { values: poly, _marker: PhantomData, diff --git a/halo2_proofs/src/poly/ipa/multiopen/verifier.rs b/halo2_proofs/src/poly/ipa/multiopen/verifier.rs index 1c840b6d..391f89e1 100644 --- a/halo2_proofs/src/poly/ipa/multiopen/verifier.rs +++ b/halo2_proofs/src/poly/ipa/multiopen/verifier.rs @@ -8,7 +8,7 @@ use rand_core::RngCore; use super::{ construct_intermediate_sets, ChallengeX1, ChallengeX2, ChallengeX3, ChallengeX4, Query, }; -use crate::arithmetic::{eval_polynomial, lagrange_interpolate, CurveAffine, FieldExt}; +use crate::arithmetic::{eval_polynomial, lagrange_interpolate, CurveAffine}; use crate::poly::commitment::{Params, Verifier, MSM}; use crate::poly::ipa::commitment::{IPACommitmentScheme, ParamsIPA, ParamsVerifierIPA}; use crate::poly::ipa::msm::MSMIPA; @@ -30,6 +30,8 @@ impl<'params, C: CurveAffine> Verifier<'params, IPACommitmentScheme> type Guard = GuardIPA<'params, C>; type MSMAccumulator = MSMIPA<'params, C>; + const QUERY_INSTANCE: bool = true; + fn new(params: &'params ParamsVerifierIPA) -> Self { Self { params } } @@ -61,7 +63,7 @@ impl<'params, C: CurveAffine> Verifier<'params, IPACommitmentScheme> // while the inner vec corresponds to the points in a particular set. let mut q_eval_sets = Vec::with_capacity(point_sets.len()); for point_set in point_sets.iter() { - q_eval_sets.push(vec![C::Scalar::zero(); point_set.len()]); + q_eval_sets.push(vec![C::Scalar::ZERO; point_set.len()]); } { let mut accumulate = |set_idx: usize, @@ -70,7 +72,7 @@ impl<'params, C: CurveAffine> Verifier<'params, IPACommitmentScheme> q_commitments[set_idx].scale(*x_1); match new_commitment { CommitmentReference::Commitment(c) => { - q_commitments[set_idx].append_term(C::Scalar::one(), (*c).into()); + q_commitments[set_idx].append_term(C::Scalar::ONE, (*c).into()); } CommitmentReference::MSM(msm) => { q_commitments[set_idx].add_msm(msm); @@ -114,7 +116,7 @@ impl<'params, C: CurveAffine> Verifier<'params, IPACommitmentScheme> .zip(q_eval_sets.iter()) .zip(u.iter()) .fold( - C::Scalar::zero(), + C::Scalar::ZERO, |msm_eval, ((points, evals), proof_eval)| { let r_poly = lagrange_interpolate(points, evals); let r_eval = eval_polynomial(&r_poly, *x_3); @@ -130,7 +132,7 @@ impl<'params, C: CurveAffine> Verifier<'params, IPACommitmentScheme> let x_4: ChallengeX4<_> = transcript.squeeze_challenge_scalar(); // Compute the final commitment that has to be opened - msm.append_term(C::Scalar::one(), q_prime_commitment.into()); + msm.append_term(C::Scalar::ONE, q_prime_commitment.into()); let (msm, v) = q_commitments.into_iter().zip(u.iter()).fold( (msm, msm_eval), |(mut msm, msm_eval), (q_commitment, q_eval)| { diff --git a/halo2_proofs/src/poly/ipa/strategy.rs b/halo2_proofs/src/poly/ipa/strategy.rs index 6f3b4b72..c8d385f9 100644 --- a/halo2_proofs/src/poly/ipa/strategy.rs +++ b/halo2_proofs/src/poly/ipa/strategy.rs @@ -70,7 +70,7 @@ impl<'params, C: CurveAffine> GuardIPA<'params, C> { /// Computes G = ⟨s, params.g⟩ pub fn compute_g(&self) -> C { - let s = compute_s(&self.u, C::Scalar::one()); + let s = compute_s(&self.u, C::Scalar::ONE); best_multiexp(&s, &self.msm.params.g).to_affine() } @@ -160,7 +160,7 @@ impl<'params, C: CurveAffine> /// Computes the coefficients of $g(X) = \prod\limits_{i=0}^{k-1} (1 + u_{k - 1 - i} X^{2^i})$. fn compute_s(u: &[F], init: F) -> Vec { assert!(!u.is_empty()); - let mut v = vec![F::zero(); 1 << u.len()]; + let mut v = vec![F::ZERO; 1 << u.len()]; v[0] = init; for (len, u_j) in u.iter().rev().enumerate().map(|(i, u_j)| (1 << i, u_j)) { diff --git a/halo2_proofs/src/poly/kzg/commitment.rs b/halo2_proofs/src/poly/kzg/commitment.rs index 3e8cce6d..51f0c660 100644 --- a/halo2_proofs/src/poly/kzg/commitment.rs +++ b/halo2_proofs/src/poly/kzg/commitment.rs @@ -1,12 +1,13 @@ use crate::arithmetic::{ - best_fft, best_multiexp, g_to_lagrange, parallelize, CurveAffine, CurveExt, FieldExt, Group, + best_fft, best_multiexp, g_to_lagrange, parallelize, CurveAffine, CurveExt, }; -use crate::helpers::CurveRead; +use crate::helpers::SerdeCurveAffine; use crate::poly::commitment::{Blind, CommitmentScheme, Params, ParamsProver, ParamsVerifier, MSM}; use crate::poly::{Coeff, LagrangeCoeff, Polynomial}; +use crate::SerdeFormat; use ff::{Field, PrimeField}; -use group::{prime::PrimeCurveAffine, Curve, Group as _}; +use group::{prime::PrimeCurveAffine, Curve, Group}; use halo2curves::pairing::Engine; use rand_core::{OsRng, RngCore}; use std::fmt::Debug; @@ -34,7 +35,12 @@ pub struct KZGCommitmentScheme { _marker: PhantomData, } -impl CommitmentScheme for KZGCommitmentScheme { +impl CommitmentScheme for KZGCommitmentScheme +where + E::Scalar: PrimeField, + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, +{ type Scalar = E::Scalar; type Curve = E::G1Affine; @@ -50,7 +56,10 @@ impl CommitmentScheme for KZGCommitmentScheme { } } -impl ParamsKZG { +impl ParamsKZG +where + E::Scalar: PrimeField, +{ /// Initializes parameters for the curve, draws toxic secret from given rng. /// MUST NOT be used in production. pub fn setup(k: u32, rng: R) -> Self { @@ -63,7 +72,7 @@ impl ParamsKZG { let g1 = E::G1Affine::generator(); let s = ::random(rng); - let mut g_projective = vec![E::G1::group_zero(); n as usize]; + let mut g_projective = vec![E::G1::identity(); 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]); @@ -81,14 +90,14 @@ impl ParamsKZG { g }; - let mut g_lagrange_projective = vec![E::G1::group_zero(); n as usize]; + let mut g_lagrange_projective = vec![E::G1::identity(); n as usize]; let mut root = E::Scalar::ROOT_OF_UNITY_INV.invert().unwrap(); for _ in k..E::Scalar::S { root = root.square(); } let n_inv = Option::::from(E::Scalar::from(n).invert()) .expect("inversion should be ok for n = 1< ParamsKZG { } } + /// Initializes parameters for the curve through existing parameters + /// k, g, g_lagrange (optional), g2, s_g2 + pub fn from_parts( + &self, + k: u32, + g: Vec, + g_lagrange: Option>, + g2: E::G2Affine, + s_g2: E::G2Affine, + ) -> Self { + Self { + k, + n: 1 << k, + g_lagrange: if let Some(g_l) = g_lagrange { + g_l + } else { + g_to_lagrange(g.iter().map(PrimeCurveAffine::to_curve).collect(), k) + }, + g, + g2, + s_g2, + } + } + /// Returns gernerator on G2 pub fn g2(&self) -> E::G2Affine { self.g2 @@ -132,6 +165,111 @@ impl ParamsKZG { pub fn s_g2(&self) -> E::G2Affine { self.s_g2 } + + /// Writes parameters to buffer + pub fn write_custom(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> + where + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, + { + writer.write_all(&self.k.to_le_bytes())?; + for el in self.g.iter() { + el.write(writer, format)?; + } + for el in self.g_lagrange.iter() { + el.write(writer, format)?; + } + self.g2.write(writer, format)?; + self.s_g2.write(writer, format)?; + Ok(()) + } + + /// Reads params from a buffer. + pub fn read_custom(reader: &mut R, format: SerdeFormat) -> io::Result + where + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, + { + let mut k = [0u8; 4]; + reader.read_exact(&mut k[..])?; + let k = u32::from_le_bytes(k); + let n = 1 << k; + + let (g, g_lagrange) = match format { + SerdeFormat::Processed => { + use group::GroupEncoding; + let load_points_from_file_parallelly = + |reader: &mut R| -> io::Result>> { + let mut points_compressed = + vec![<::G1Affine as GroupEncoding>::Repr::default(); n]; + for points_compressed in points_compressed.iter_mut() { + reader.read_exact((*points_compressed).as_mut())?; + } + + let mut points = vec![Option::::None; n]; + parallelize(&mut points, |points, chunks| { + for (i, point) in points.iter_mut().enumerate() { + *point = Option::from(E::G1Affine::from_bytes( + &points_compressed[chunks + i], + )); + } + }); + Ok(points) + }; + + let g = load_points_from_file_parallelly(reader)?; + let g: Vec<::G1Affine> = g + .iter() + .map(|point| { + point.ok_or_else(|| { + io::Error::new(io::ErrorKind::Other, "invalid point encoding") + }) + }) + .collect::>()?; + let g_lagrange = load_points_from_file_parallelly(reader)?; + let g_lagrange: Vec<::G1Affine> = g_lagrange + .iter() + .map(|point| { + point.ok_or_else(|| { + io::Error::new(io::ErrorKind::Other, "invalid point encoding") + }) + }) + .collect::>()?; + (g, g_lagrange) + } + SerdeFormat::RawBytes => { + let g = (0..n) + .map(|_| ::read(reader, format)) + .collect::, _>>()?; + let g_lagrange = (0..n) + .map(|_| ::read(reader, format)) + .collect::, _>>()?; + (g, g_lagrange) + } + SerdeFormat::RawBytesUnchecked => { + // avoid try branching for performance + let g = (0..n) + .map(|_| ::read(reader, format).unwrap()) + .collect::>(); + let g_lagrange = (0..n) + .map(|_| ::read(reader, format).unwrap()) + .collect::>(); + (g, g_lagrange) + } + }; + + let g2 = E::G2Affine::read(reader, format)?; + let s_g2 = E::G2Affine::read(reader, format)?; + + Ok(Self { + k, + n: n as u64, + g, + g_lagrange, + g2, + s_g2, + }) + } } // TODO: see the issue at https://github.com/appliedzkp/halo2/issues/45 @@ -139,7 +277,12 @@ impl ParamsKZG { /// KZG multi-open verification parameters pub type ParamsVerifierKZG = ParamsKZG; -impl<'params, E: Engine + Debug> Params<'params, E::G1Affine> for ParamsKZG { +impl<'params, E: Engine + Debug> Params<'params, E::G1Affine> for ParamsKZG +where + E::Scalar: PrimeField, + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, +{ type MSM = MSMKZG; fn k(&self) -> u32 { @@ -179,79 +322,29 @@ impl<'params, E: Engine + Debug> Params<'params, E::G1Affine> for ParamsKZG { /// Writes params to a buffer. fn write(&self, writer: &mut W) -> io::Result<()> { - use group::GroupEncoding; - writer.write_all(&self.k.to_le_bytes())?; - for el in self.g.iter() { - writer.write_all(el.to_bytes().as_ref())?; - } - for el in self.g_lagrange.iter() { - writer.write_all(el.to_bytes().as_ref())?; - } - writer.write_all(self.g2.to_bytes().as_ref())?; - writer.write_all(self.s_g2.to_bytes().as_ref())?; - Ok(()) + self.write_custom(writer, SerdeFormat::RawBytes) } /// Reads params from a buffer. fn read(reader: &mut R) -> io::Result { - use group::GroupEncoding; - - let mut k = [0u8; 4]; - reader.read_exact(&mut k[..])?; - let k = u32::from_le_bytes(k); - let n = 1 << k; - - let load_points_from_file_parallelly = - |reader: &mut R| -> io::Result>> { - let mut points_compressed = - vec![<::G1Affine as GroupEncoding>::Repr::default(); n]; - for points_compressed in points_compressed.iter_mut() { - reader.read_exact((*points_compressed).as_mut())?; - } - - let mut points = vec![Option::::None; n]; - parallelize(&mut points, |points, chunks| { - for (i, point) in points.iter_mut().enumerate() { - *point = - Option::from(E::G1Affine::from_bytes(&points_compressed[chunks + i])); - } - }); - Ok(points) - }; - - let g = load_points_from_file_parallelly(reader)?; - let g: Vec<::G1Affine> = g - .iter() - .map(|point| { - point.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid point encoding")) - }) - .collect::>()?; - - let g_lagrange = load_points_from_file_parallelly(reader)?; - let g_lagrange: Vec<::G1Affine> = g_lagrange - .iter() - .map(|point| { - point.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid point encoding")) - }) - .collect::>()?; - - let g2 = E::G2Affine::read(reader)?; - let s_g2 = E::G2Affine::read(reader)?; - - Ok(Self { - k, - n: n as u64, - g, - g_lagrange, - g2, - s_g2, - }) + Self::read_custom(reader, SerdeFormat::RawBytes) } } -impl<'params, E: Engine + Debug> ParamsVerifier<'params, E::G1Affine> for ParamsKZG {} +impl<'params, E: Engine + Debug> ParamsVerifier<'params, E::G1Affine> for ParamsKZG +where + E::Scalar: PrimeField, + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, +{ +} -impl<'params, E: Engine + Debug> ParamsProver<'params, E::G1Affine> for ParamsKZG { +impl<'params, E: Engine + Debug> ParamsProver<'params, E::G1Affine> for ParamsKZG +where + E::Scalar: PrimeField, + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, +{ type ParamsVerifier = ParamsVerifierKZG; fn verifier_params(&'params self) -> &'params Self::ParamsVerifier { @@ -278,11 +371,7 @@ impl<'params, E: Engine + Debug> ParamsProver<'params, E::G1Affine> for ParamsKZ #[cfg(test)] mod test { - - use crate::arithmetic::{ - best_fft, best_multiexp, parallelize, CurveAffine, CurveExt, FieldExt, Group, - }; - use crate::helpers::CurveRead; + use crate::arithmetic::{best_fft, best_multiexp, parallelize, CurveAffine, CurveExt}; use crate::poly::commitment::ParamsProver; use crate::poly::commitment::{Blind, CommitmentScheme, Params, MSM}; use crate::poly::kzg::commitment::{ParamsKZG, ParamsVerifierKZG}; @@ -291,7 +380,7 @@ mod test { use crate::poly::{Coeff, LagrangeCoeff, Polynomial}; use ff::{Field, PrimeField}; - use group::{prime::PrimeCurveAffine, Curve, Group as _}; + use group::{prime::PrimeCurveAffine, Curve, Group}; use halo2curves::bn256::G1Affine; use std::marker::PhantomData; use std::ops::{Add, AddAssign, Mul, MulAssign}; @@ -331,7 +420,7 @@ mod test { use rand_core::OsRng; use super::super::commitment::{Blind, Params}; - use crate::arithmetic::{eval_polynomial, FieldExt}; + use crate::arithmetic::eval_polynomial; use crate::halo2curves::bn256::{Bn256, Fr}; use crate::poly::EvaluationDomain; diff --git a/halo2_proofs/src/poly/kzg/msm.rs b/halo2_proofs/src/poly/kzg/msm.rs index 19754146..667a5c36 100644 --- a/halo2_proofs/src/poly/kzg/msm.rs +++ b/halo2_proofs/src/poly/kzg/msm.rs @@ -27,7 +27,7 @@ impl MSMKZG { /// Prepares all scalars in the MSM to linear combination pub fn combine_with_base(&mut self, base: E::Scalar) { use ff::Field; - let mut acc = E::Scalar::one(); + let mut acc = E::Scalar::ONE; if !self.scalars.is_empty() { for scalar in self.scalars.iter_mut().rev() { *scalar *= &acc; diff --git a/halo2_proofs/src/poly/kzg/multiopen/gwc.rs b/halo2_proofs/src/poly/kzg/multiopen/gwc.rs index 4869238a..8e7d742f 100644 --- a/halo2_proofs/src/poly/kzg/multiopen/gwc.rs +++ b/halo2_proofs/src/poly/kzg/multiopen/gwc.rs @@ -5,7 +5,7 @@ pub use prover::ProverGWC; pub use verifier::VerifierGWC; use crate::{ - arithmetic::{eval_polynomial, CurveAffine, FieldExt}, + arithmetic::{eval_polynomial, CurveAffine}, poly::{ commitment::{Params, ParamsVerifier}, query::Query, @@ -13,7 +13,7 @@ use crate::{ }, transcript::ChallengeScalar, }; - +use ff::Field; use std::{ collections::{BTreeMap, BTreeSet}, marker::PhantomData, @@ -27,13 +27,13 @@ type ChallengeU = ChallengeScalar; struct V {} type ChallengeV = ChallengeScalar; -struct CommitmentData> { +struct CommitmentData> { queries: Vec, point: F, _marker: PhantomData, } -fn construct_intermediate_sets>(queries: I) -> Vec> +fn construct_intermediate_sets>(queries: I) -> Vec> where I: IntoIterator + Clone, { diff --git a/halo2_proofs/src/poly/kzg/multiopen/gwc/prover.rs b/halo2_proofs/src/poly/kzg/multiopen/gwc/prover.rs index e3575b39..12e3d1f8 100644 --- a/halo2_proofs/src/poly/kzg/multiopen/gwc/prover.rs +++ b/halo2_proofs/src/poly/kzg/multiopen/gwc/prover.rs @@ -1,6 +1,6 @@ use super::{construct_intermediate_sets, ChallengeV, Query}; -use crate::arithmetic::{eval_polynomial, kate_division, powers, CurveAffine, FieldExt}; - +use crate::arithmetic::{eval_polynomial, kate_division, powers, CurveAffine}; +use crate::helpers::SerdeCurveAffine; use crate::poly::commitment::ParamsProver; use crate::poly::commitment::Prover; use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; @@ -12,7 +12,7 @@ use crate::poly::{ }; use crate::transcript::{EncodedChallenge, TranscriptWrite}; -use ff::Field; +use ff::{Field, PrimeField}; use group::Curve; use halo2curves::pairing::Engine; use rand_core::RngCore; @@ -27,7 +27,14 @@ pub struct ProverGWC<'params, E: Engine> { } /// Create a multi-opening proof -impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> for ProverGWC<'params, E> { +impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> for ProverGWC<'params, E> +where + E::Scalar: PrimeField, + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, +{ + const QUERY_INSTANCE: bool = false; + fn new(params: &'params ParamsKZG) -> Self { Self { params } } diff --git a/halo2_proofs/src/poly/kzg/multiopen/gwc/verifier.rs b/halo2_proofs/src/poly/kzg/multiopen/gwc/verifier.rs index e5218d36..ead5670b 100644 --- a/halo2_proofs/src/poly/kzg/multiopen/gwc/verifier.rs +++ b/halo2_proofs/src/poly/kzg/multiopen/gwc/verifier.rs @@ -3,8 +3,8 @@ use std::io::Read; use std::marker::PhantomData; use super::{construct_intermediate_sets, ChallengeU, ChallengeV}; -use crate::arithmetic::{eval_polynomial, lagrange_interpolate, powers, CurveAffine, FieldExt}; - +use crate::arithmetic::{eval_polynomial, lagrange_interpolate, powers, CurveAffine}; +use crate::helpers::SerdeCurveAffine; use crate::poly::commitment::Verifier; use crate::poly::commitment::MSM; use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; @@ -19,7 +19,7 @@ use crate::poly::{ }; use crate::transcript::{EncodedChallenge, TranscriptRead}; -use ff::Field; +use ff::{Field, PrimeField}; use group::Group; use halo2curves::pairing::{Engine, MillerLoopResult, MultiMillerLoop}; use rand_core::OsRng; @@ -30,12 +30,18 @@ pub struct VerifierGWC<'params, E: Engine> { params: &'params ParamsKZG, } -impl<'params, E: MultiMillerLoop + Debug> Verifier<'params, KZGCommitmentScheme> - for VerifierGWC<'params, E> +impl<'params, E> Verifier<'params, KZGCommitmentScheme> for VerifierGWC<'params, E> +where + E: MultiMillerLoop + Debug, + E::Scalar: PrimeField, + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, { type Guard = GuardKZG<'params, E>; type MSMAccumulator = DualMSM<'params, E>; + const QUERY_INSTANCE: bool = false; + fn new(params: &'params ParamsKZG) -> Self { Self { params } } @@ -65,7 +71,7 @@ impl<'params, E: MultiMillerLoop + Debug> Verifier<'params, KZGCommitmentScheme< let u: ChallengeU<_> = transcript.squeeze_challenge_scalar(); let mut commitment_multi = MSMKZG::::new(); - let mut eval_multi = E::Scalar::zero(); + let mut eval_multi = E::Scalar::ZERO; let mut witness = MSMKZG::::new(); let mut witness_with_aux = MSMKZG::::new(); diff --git a/halo2_proofs/src/poly/kzg/multiopen/shplonk.rs b/halo2_proofs/src/poly/kzg/multiopen/shplonk.rs index 12593622..d14132fb 100644 --- a/halo2_proofs/src/poly/kzg/multiopen/shplonk.rs +++ b/halo2_proofs/src/poly/kzg/multiopen/shplonk.rs @@ -1,19 +1,20 @@ mod prover; mod verifier; -pub use prover::ProverSHPLONK; -pub use verifier::VerifierSHPLONK; - use crate::{ - arithmetic::{eval_polynomial, lagrange_interpolate, CurveAffine, FieldExt}, + arithmetic::{eval_polynomial, lagrange_interpolate, CurveAffine}, poly::{query::Query, Coeff, Polynomial}, transcript::ChallengeScalar, }; - +use ff::Field; +pub use prover::ProverSHPLONK; +use rayon::prelude::*; use std::{ - collections::{btree_map::Entry, BTreeMap, BTreeSet}, + collections::{btree_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet}, marker::PhantomData, + sync::Arc, }; +pub use verifier::VerifierSHPLONK; #[derive(Clone, Copy, Debug)] struct U {} @@ -28,9 +29,9 @@ struct Y {} type ChallengeY = ChallengeScalar; #[derive(Debug, Clone, PartialEq)] -struct Commitment((T, Vec)); +struct Commitment((T, Vec)); -impl Commitment { +impl Commitment { fn get(&self) -> T { self.0 .0.clone() } @@ -41,18 +42,18 @@ impl Commitment { } #[derive(Debug, Clone, PartialEq)] -struct RotationSet { +struct RotationSet { commitments: Vec>, points: Vec, } #[derive(Debug, PartialEq)] -struct IntermediateSets> { +struct IntermediateSets> { rotation_sets: Vec>, - super_point_set: Vec, + super_point_set: BTreeSet, } -fn construct_intermediate_sets>( +fn construct_intermediate_sets>( queries: I, ) -> IntermediateSets where @@ -69,18 +70,8 @@ where .get_eval() }; - // Order points according to their rotation - let mut rotation_point_map = BTreeMap::new(); - for query in queries.clone() { - let point = rotation_point_map - .entry(query.get_point()) - .or_insert_with(|| query.get_point()); - - // Assert rotation point matching consistency - assert_eq!(*point, query.get_point()); - } - // All points appear in queries - let super_point_set: Vec = rotation_point_map.values().cloned().collect(); + // All points that appear in queries + let mut super_point_set = BTreeSet::new(); // Collect rotation sets for each commitment // Example elements in the vector: @@ -89,19 +80,21 @@ where // (C_2, {r_2, r_3, r_4}), // (C_3, {r_2, r_3, r_4}), // ... - let mut commitment_rotation_set_map: Vec<(Q::Commitment, Vec)> = vec![]; - for query in queries.clone() { + let mut commitment_rotation_set_map: Vec<(Q::Commitment, BTreeSet)> = vec![]; + for query in queries.iter() { let rotation = query.get_point(); - if let Some(pos) = commitment_rotation_set_map - .iter() - .position(|(commitment, _)| *commitment == query.get_commitment()) + super_point_set.insert(rotation); + if let Some(commitment_rotation_set) = commitment_rotation_set_map + .iter_mut() + .find(|(commitment, _)| *commitment == query.get_commitment()) { - let (_, rotation_set) = &mut commitment_rotation_set_map[pos]; - if !rotation_set.contains(&rotation) { - rotation_set.push(rotation); - } + let (_, rotation_set) = commitment_rotation_set; + rotation_set.insert(rotation); } else { - commitment_rotation_set_map.push((query.get_commitment(), vec![rotation])); + commitment_rotation_set_map.push(( + query.get_commitment(), + BTreeSet::from_iter(std::iter::once(rotation)), + )); }; } @@ -111,41 +104,38 @@ where // {r_1, r_2, r_3} : [C_1] // {r_2, r_3, r_4} : [C_2, C_3], // ... - let mut rotation_set_commitment_map = Vec::<(Vec<_>, Vec)>::new(); - for (commitment, rotation_set) in commitment_rotation_set_map.iter() { - if let Some(pos) = rotation_set_commitment_map.iter().position(|(set, _)| { - BTreeSet::::from_iter(set.iter().cloned()) - == BTreeSet::::from_iter(rotation_set.iter().cloned()) - }) { - let (_, commitments) = &mut rotation_set_commitment_map[pos]; - if !commitments.contains(commitment) { - commitments.push(*commitment); - } + // NOTE: we want to make the order of the collection of rotation sets independent of the opening points, to ease the verifier computation + let mut rotation_set_commitment_map: Vec<(BTreeSet, Vec)> = vec![]; + for (commitment, rotation_set) in commitment_rotation_set_map.into_iter() { + if let Some(rotation_set_commitment) = rotation_set_commitment_map + .iter_mut() + .find(|(set, _)| set == &rotation_set) + { + let (_, commitments) = rotation_set_commitment; + commitments.push(commitment); } else { - rotation_set_commitment_map.push((rotation_set.clone(), vec![*commitment])) - } + rotation_set_commitment_map.push((rotation_set, vec![commitment])); + }; } let rotation_sets = rotation_set_commitment_map - .into_iter() + .into_par_iter() .map(|(rotations, commitments)| { + let rotations_vec = rotations.iter().collect::>(); let commitments: Vec> = commitments - .iter() + .into_par_iter() .map(|commitment| { - let evals: Vec = rotations - .iter() - .map(|rotation| get_eval(*commitment, *rotation)) + let evals: Vec = rotations_vec + .par_iter() + .map(|&&rotation| get_eval(commitment, rotation)) .collect(); - Commitment((*commitment, evals)) + Commitment((commitment, evals)) }) .collect(); RotationSet { commitments, - points: rotations - .iter() - .map(|rotation| *rotation_point_map.get(rotation).unwrap()) - .collect(), + points: rotations.into_iter().collect(), } }) .collect::>>(); @@ -167,8 +157,8 @@ mod proptests { use super::{construct_intermediate_sets, Commitment, IntermediateSets}; use crate::poly::Rotation; - use halo2curves::{pasta::Fp, FieldExt}; - + use ff::{Field, FromUniformBytes}; + use halo2curves::pasta::Fp; use std::collections::BTreeMap; use std::convert::TryFrom; @@ -200,7 +190,7 @@ mod proptests { fn arb_point()( bytes in vec(any::(), 64) ) -> Fp { - Fp::from_bytes_wide(&<[u8; 64]>::try_from(bytes).unwrap()) + Fp::from_uniform_bytes(&<[u8; 64]>::try_from(bytes).unwrap()) } } diff --git a/halo2_proofs/src/poly/kzg/multiopen/shplonk/prover.rs b/halo2_proofs/src/poly/kzg/multiopen/shplonk/prover.rs index e99e0b91..bb50eafe 100644 --- a/halo2_proofs/src/poly/kzg/multiopen/shplonk/prover.rs +++ b/halo2_proofs/src/poly/kzg/multiopen/shplonk/prover.rs @@ -3,9 +3,9 @@ use super::{ }; use crate::arithmetic::{ eval_polynomial, evaluate_vanishing_polynomial, kate_division, lagrange_interpolate, - parallelize, powers, CurveAffine, FieldExt, + parallelize, powers, CurveAffine, }; - +use crate::helpers::SerdeCurveAffine; use crate::poly::commitment::{Blind, ParamsProver, Prover}; use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; use crate::poly::query::{PolynomialPointer, ProverQuery}; @@ -13,16 +13,17 @@ use crate::poly::Rotation; use crate::poly::{commitment::Params, Coeff, Polynomial}; use crate::transcript::{EncodedChallenge, TranscriptWrite}; -use ff::Field; +use ff::{Field, PrimeField}; use group::Curve; use halo2curves::pairing::Engine; use rand_core::RngCore; +use rayon::prelude::*; use std::fmt::Debug; use std::io::{self, Write}; use std::marker::PhantomData; use std::ops::MulAssign; -fn div_by_vanishing(poly: Polynomial, roots: &[F]) -> Vec { +fn div_by_vanishing(poly: Polynomial, roots: &[F]) -> Vec { let poly = roots .iter() .fold(poly.values, |poly, point| kate_division(&poly, *point)); @@ -36,8 +37,8 @@ struct CommitmentExtension<'a, C: CurveAffine> { } impl<'a, C: CurveAffine> Commitment> { - fn extend(&self, points: Vec) -> CommitmentExtension<'a, C> { - let poly = lagrange_interpolate(&points[..], &self.evals()[..]); + fn extend(&self, points: &[C::Scalar]) -> CommitmentExtension<'a, C> { + let poly = lagrange_interpolate(points, &self.evals()[..]); let low_degree_equivalent = Polynomial { values: poly, @@ -79,10 +80,10 @@ struct RotationSetExtension<'a, C: CurveAffine> { } impl<'a, C: CurveAffine> RotationSet> { - fn extend(&self, commitments: Vec>) -> RotationSetExtension<'a, C> { + fn extend(self, commitments: Vec>) -> RotationSetExtension<'a, C> { RotationSetExtension { commitments, - points: self.points.clone(), + points: self.points, } } } @@ -103,7 +104,13 @@ impl<'a, E: Engine> ProverSHPLONK<'a, E> { /// Create a multi-opening proof impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> for ProverSHPLONK<'params, E> +where + E::Scalar: Ord + PrimeField, + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, { + const QUERY_INSTANCE: bool = false; + fn new(params: &'params ParamsKZG) -> Self { Self { params } } @@ -134,8 +141,9 @@ impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> // [P_i_0(X) - R_i_0(X), P_i_1(X) - R_i_1(X), ... ] let numerators = rotation_set .commitments - .iter() - .map(|commitment| commitment.quotient_contribution()); + .par_iter() + .map(|commitment| commitment.quotient_contribution()) + .collect::>(); // define numerator polynomial as // N_i_j(X) = (P_i_j(X) - R_i_j(X)) @@ -143,6 +151,7 @@ impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> // N_i(X) = linear_combinination(y, N_i_j(X)) // where y is random scalar to combine numerator polynomials let n_x = numerators + .into_iter() .zip(powers(*y)) .map(|(numerator, power_of_y)| numerator * power_of_y) .reduce(|acc, numerator| acc + &numerator) @@ -154,7 +163,7 @@ impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> // Q_i(X) = N_i(X) / Z_i(X) where // Z_i(X) = (x - r_i_0) * (x - r_i_1) * ... let mut poly = div_by_vanishing(n_x, points); - poly.resize(self.params.n as usize, E::Scalar::zero()); + poly.resize(self.params.n as usize, E::Scalar::ZERO); Polynomial { values: poly, @@ -169,12 +178,12 @@ impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> ); let rotation_sets: Vec> = rotation_sets - .iter() + .into_par_iter() .map(|rotation_set| { let commitments: Vec> = rotation_set .commitments - .iter() - .map(|commitment_data| commitment_data.extend(rotation_set.points.clone())) + .par_iter() + .map(|commitment_data| commitment_data.extend(&rotation_set.points)) .collect(); rotation_set.extend(commitments) }) @@ -182,9 +191,13 @@ impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> let v: ChallengeV<_> = transcript.squeeze_challenge_scalar(); - let quotient_polynomials = rotation_sets.iter().map(quotient_contribution); + let quotient_polynomials = rotation_sets + .par_iter() + .map(quotient_contribution) + .collect::>(); let h_x: Polynomial = quotient_polynomials + .into_iter() .zip(powers(*v)) .map(|(poly, power_of_v)| poly * power_of_v) .reduce(|acc, poly| acc + &poly) @@ -194,18 +207,15 @@ impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> transcript.write_point(h)?; let u: ChallengeU<_> = transcript.squeeze_challenge_scalar(); - let zt_eval = evaluate_vanishing_polynomial(&super_point_set[..], *u); - let linearisation_contribution = |rotation_set: RotationSetExtension| -> (Polynomial, E::Scalar) { - let diffs: Vec = super_point_set - .iter() - .filter(|point| !rotation_set.points.contains(point)) - .copied() - .collect(); + let mut diffs = super_point_set.clone(); + for point in rotation_set.points.iter() { + diffs.remove(point); + } + let diffs = diffs.into_iter().collect::>(); // calculate difference vanishing polynomial evaluation - let z_i = evaluate_vanishing_polynomial(&diffs[..], *u); // inner linearisation contibutions are @@ -214,15 +224,15 @@ impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> // where u is random evaluation point let inner_contributions = rotation_set .commitments - .iter() - .map(|commitment| commitment.linearisation_contribution(*u)); + .par_iter() + .map(|commitment| commitment.linearisation_contribution(*u)).collect::>(); // define inner contributor polynomial as // L_i_j(X) = (P_i_j(X) - r_i_j) // and combine polynomials with same evaluation point set // L_i(X) = linear_combinination(y, L_i_j(X)) // where y is random scalar to combine inner contibutors - let l_x: Polynomial = inner_contributions.zip(powers(*y)).map(|(poly, power_of_y)| poly * power_of_y).reduce(|acc, poly| acc + &poly).unwrap(); + let l_x: Polynomial = inner_contributions.into_iter().zip(powers(*y)).map(|(poly, power_of_y)| poly * power_of_y).reduce(|acc, poly| acc + &poly).unwrap(); // finally scale l_x by difference vanishing polynomial evaluation z_i (l_x * z_i, z_i) @@ -233,7 +243,7 @@ impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> Vec>, Vec, ) = rotation_sets - .into_iter() + .into_par_iter() .map(linearisation_contribution) .unzip(); @@ -244,12 +254,15 @@ impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> .reduce(|acc, poly| acc + &poly) .unwrap(); + let super_point_set = super_point_set.into_iter().collect::>(); + let zt_eval = evaluate_vanishing_polynomial(&super_point_set[..], *u); let l_x = l_x - &(h_x * zt_eval); // sanity check + #[cfg(debug_assertions)] { let must_be_zero = eval_polynomial(&l_x.values[..], *u); - assert_eq!(must_be_zero, E::Scalar::zero()); + assert_eq!(must_be_zero, E::Scalar::ZERO); } let mut h_x = div_by_vanishing(l_x, &[*u]); diff --git a/halo2_proofs/src/poly/kzg/multiopen/shplonk/verifier.rs b/halo2_proofs/src/poly/kzg/multiopen/shplonk/verifier.rs index 5438d414..d1d1660e 100644 --- a/halo2_proofs/src/poly/kzg/multiopen/shplonk/verifier.rs +++ b/halo2_proofs/src/poly/kzg/multiopen/shplonk/verifier.rs @@ -5,8 +5,8 @@ use super::ChallengeY; use super::{construct_intermediate_sets, ChallengeU, ChallengeV}; use crate::arithmetic::{ eval_polynomial, evaluate_vanishing_polynomial, lagrange_interpolate, powers, CurveAffine, - FieldExt, }; +use crate::helpers::SerdeCurveAffine; use crate::poly::commitment::Verifier; use crate::poly::commitment::MSM; use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; @@ -21,7 +21,7 @@ use crate::poly::{ Error, }; use crate::transcript::{EncodedChallenge, TranscriptRead}; -use ff::Field; +use ff::{Field, PrimeField}; use group::Group; use halo2curves::pairing::{Engine, MillerLoopResult, MultiMillerLoop}; use rand_core::OsRng; @@ -33,12 +33,18 @@ pub struct VerifierSHPLONK<'params, E: Engine> { params: &'params ParamsKZG, } -impl<'params, E: MultiMillerLoop + Debug> Verifier<'params, KZGCommitmentScheme> - for VerifierSHPLONK<'params, E> +impl<'params, E> Verifier<'params, KZGCommitmentScheme> for VerifierSHPLONK<'params, E> +where + E: MultiMillerLoop + Debug, + E::Scalar: PrimeField + Ord, + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, { type Guard = GuardKZG<'params, E>; type MSMAccumulator = DualMSM<'params, E>; + const QUERY_INSTANCE: bool = false; + fn new(params: &'params ParamsKZG) -> Self { Self { params } } @@ -71,8 +77,8 @@ impl<'params, E: MultiMillerLoop + Debug> Verifier<'params, KZGCommitmentScheme< let u: ChallengeU<_> = transcript.squeeze_challenge_scalar(); let h2 = transcript.read_point().map_err(|_| Error::SamplingError)?; - let (mut z_0_diff_inverse, mut z_0) = (E::Scalar::zero(), E::Scalar::zero()); - let (mut outer_msm, mut r_outer_acc) = (PreMSM::::new(), E::Scalar::zero()); + let (mut z_0_diff_inverse, mut z_0) = (E::Scalar::ZERO, E::Scalar::ZERO); + let (mut outer_msm, mut r_outer_acc) = (PreMSM::::new(), E::Scalar::ZERO); for (i, (rotation_set, power_of_v)) in rotation_sets.iter().zip(powers(*v)).enumerate() { let diffs: Vec = super_point_set .iter() @@ -85,7 +91,7 @@ impl<'params, E: MultiMillerLoop + Debug> Verifier<'params, KZGCommitmentScheme< if i == 0 { z_0 = evaluate_vanishing_polynomial(&rotation_set.points[..], *u); z_0_diff_inverse = z_diff_i.invert().unwrap(); - z_diff_i = E::Scalar::one(); + z_diff_i = E::Scalar::ONE; } else { z_diff_i.mul_assign(z_0_diff_inverse); } @@ -131,9 +137,7 @@ impl<'params, E: MultiMillerLoop + Debug> Verifier<'params, KZGCommitmentScheme< outer_msm.append_term(-z_0, h1.into()); outer_msm.append_term(*u, h2.into()); - msm_accumulator - .left - .append_term(E::Scalar::one(), h2.into()); + msm_accumulator.left.append_term(E::Scalar::ONE, h2.into()); msm_accumulator.right.add_msm(&outer_msm); diff --git a/halo2_proofs/src/poly/kzg/strategy.rs b/halo2_proofs/src/poly/kzg/strategy.rs index 89676006..15cdba20 100644 --- a/halo2_proofs/src/poly/kzg/strategy.rs +++ b/halo2_proofs/src/poly/kzg/strategy.rs @@ -6,6 +6,7 @@ use super::{ multiopen::VerifierGWC, }; use crate::{ + helpers::SerdeCurveAffine, plonk::Error, poly::{ commitment::{Verifier, MSM}, @@ -14,7 +15,7 @@ use crate::{ }, transcript::{EncodedChallenge, TranscriptRead}, }; -use ff::Field; +use ff::{Field, PrimeField}; use group::Group; use halo2curves::{ pairing::{Engine, MillerLoopResult, MultiMillerLoop}, @@ -29,7 +30,13 @@ pub struct GuardKZG<'params, E: MultiMillerLoop + Debug> { } /// Define accumulator type as `DualMSM` -impl<'params, E: MultiMillerLoop + Debug> Guard> for GuardKZG<'params, E> { +impl<'params, E> Guard> for GuardKZG<'params, E> +where + E::Scalar: PrimeField, + E: MultiMillerLoop + Debug, + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, +{ type MSMAccumulator = DualMSM<'params, E>; } @@ -85,6 +92,10 @@ impl< Guard = GuardKZG<'params, E>, >, > VerificationStrategy<'params, KZGCommitmentScheme, V> for AccumulatorStrategy<'params, E> +where + E::Scalar: PrimeField, + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, { type Output = Self; @@ -120,6 +131,10 @@ impl< Guard = GuardKZG<'params, E>, >, > VerificationStrategy<'params, KZGCommitmentScheme, V> for SingleStrategy<'params, E> +where + E::Scalar: PrimeField, + E::G1Affine: SerdeCurveAffine, + E::G2Affine: SerdeCurveAffine, { type Output = (); diff --git a/halo2_proofs/src/poly/multiopen_test.rs b/halo2_proofs/src/poly/multiopen_test.rs index 1df8edaa..d57243f7 100644 --- a/halo2_proofs/src/poly/multiopen_test.rs +++ b/halo2_proofs/src/poly/multiopen_test.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod test { - use crate::arithmetic::{eval_polynomial, FieldExt}; + use crate::arithmetic::eval_polynomial; use crate::plonk::Error; use crate::poly::commitment::ParamsProver; use crate::poly::commitment::{Blind, ParamsVerifier, MSM}; @@ -13,10 +13,11 @@ 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 ff::{Field, PrimeField, WithSmallOrderMulGroup}; use group::{Curve, Group}; use halo2curves::CurveAffine; use rand_core::{OsRng, RngCore}; @@ -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}; @@ -195,28 +233,25 @@ mod test { T: TranscriptWriterBuffer, Scheme::Curve, E>, >( params: &'params Scheme::ParamsProver, - ) -> Vec { + ) -> Vec + where + Scheme::Scalar: WithSmallOrderMulGroup<3>, + { let domain = EvaluationDomain::new(1, params.k()); let mut ax = domain.empty_coeff(); for (i, a) in ax.iter_mut().enumerate() { - *a = <::Curve as CurveAffine>::ScalarExt::from( - 10 + i as u64, - ); + *a = <::Scalar>::from(10 + i as u64); } let mut bx = domain.empty_coeff(); for (i, a) in bx.iter_mut().enumerate() { - *a = <::Curve as CurveAffine>::ScalarExt::from( - 100 + i as u64, - ); + *a = <::Scalar>::from(100 + i as u64); } let mut cx = domain.empty_coeff(); for (i, a) in cx.iter_mut().enumerate() { - *a = <::Curve as CurveAffine>::ScalarExt::from( - 100 + i as u64, - ); + *a = <::Scalar>::from(100 + i as u64); } let mut transcript = T::init(vec![]); diff --git a/halo2_proofs/src/poly/query.rs b/halo2_proofs/src/poly/query.rs index f13cc25a..c596e6a7 100644 --- a/halo2_proofs/src/poly/query.rs +++ b/halo2_proofs/src/poly/query.rs @@ -8,8 +8,8 @@ use crate::{ use ff::Field; use halo2curves::CurveAffine; -pub trait Query: Sized + Clone { - type Commitment: PartialEq + Copy; +pub trait Query: Sized + Clone + Send + Sync { + type Commitment: PartialEq + Copy + Send + Sync; type Eval: Clone + Default + Debug; fn get_point(&self) -> F; diff --git a/halo2_proofs/src/transcript.rs b/halo2_proofs/src/transcript.rs index 5262f3c1..25ec8236 100644 --- a/halo2_proofs/src/transcript.rs +++ b/halo2_proofs/src/transcript.rs @@ -2,10 +2,11 @@ //! transcripts. use blake2b_simd::{Params as Blake2bParams, State as Blake2bState}; -use group::ff::PrimeField; +use group::ff::{FromUniformBytes, PrimeField}; +use sha3::{Digest, Keccak256}; use std::convert::TryInto; -use halo2curves::{Coordinates, CurveAffine, FieldExt}; +use halo2curves::{Coordinates, CurveAffine}; use std::io::{self, Read, Write}; use std::marker::PhantomData; @@ -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,8 +106,18 @@ 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> +where + C::Scalar: FromUniformBytes<64>, { /// Initialize a transcript given an input buffer. fn init(reader: R) -> Self { @@ -104,8 +132,27 @@ impl TranscriptReadBuffer> } } +impl TranscriptReadBuffer> + for Keccak256Read> +where + C::Scalar: FromUniformBytes<64>, +{ + /// 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> +where + C::Scalar: FromUniformBytes<64>, { fn read_point(&mut self) -> io::Result { let mut compressed = C::Repr::default(); @@ -133,8 +180,40 @@ impl TranscriptRead> } } -impl Transcript> - for Blake2bRead> +impl TranscriptRead> + for Keccak256Read> +where + C::Scalar: FromUniformBytes<64>, +{ + 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> +where + C::Scalar: FromUniformBytes<64>, { fn squeeze_challenge(&mut self) -> Challenge255 { self.state.update(&[BLAKE2B_PREFIX_CHALLENGE]); @@ -165,6 +244,50 @@ impl Transcript> } } +impl Transcript> + for Keccak256Read> +where + C::Scalar: FromUniformBytes<64>, +{ + 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 +296,20 @@ 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> +where + C::Scalar: FromUniformBytes<64>, { + /// Initialize a transcript given an output buffer. fn init(writer: W) -> Self { Blake2bWrite { state: Blake2bParams::new() @@ -193,8 +327,50 @@ impl TranscriptWriterBuffer> } } +impl TranscriptWriterBuffer> + for Keccak256Write> +where + C::Scalar: FromUniformBytes<64>, +{ + /// 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> +where + C::Scalar: FromUniformBytes<64>, +{ + 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 TranscriptWrite> + for Keccak256Write> +where + C::Scalar: FromUniformBytes<64>, { fn write_point(&mut self, point: C) -> io::Result<()> { self.common_point(point)?; @@ -210,6 +386,8 @@ impl TranscriptWrite> impl Transcript> for Blake2bWrite> +where + C::Scalar: FromUniformBytes<64>, { fn squeeze_challenge(&mut self) -> Challenge255 { self.state.update(&[BLAKE2B_PREFIX_CHALLENGE]); @@ -240,6 +418,50 @@ impl Transcript> } } +impl Transcript> + for Keccak256Write> +where + C::Scalar: FromUniformBytes<64>, +{ + 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 @@ -294,12 +516,15 @@ impl std::ops::Deref for Challenge255 { } } -impl EncodedChallenge for Challenge255 { +impl EncodedChallenge for Challenge255 +where + C::Scalar: FromUniformBytes<64>, +{ type Input = [u8; 64]; fn new(challenge_input: &[u8; 64]) -> Self { Challenge255( - C::Scalar::from_bytes_wide(challenge_input) + C::Scalar::from_uniform_bytes(challenge_input) .to_repr() .as_ref() .try_into() diff --git a/halo2_proofs/tests/plonk_api.rs b/halo2_proofs/tests/plonk_api.rs index af63b5fb..36df1839 100644 --- a/halo2_proofs/tests/plonk_api.rs +++ b/halo2_proofs/tests/plonk_api.rs @@ -2,7 +2,8 @@ #![allow(clippy::op_ref)] use assert_matches::assert_matches; -use halo2_proofs::arithmetic::{Field, FieldExt}; +use ff::{FromUniformBytes, WithSmallOrderMulGroup}; +use halo2_proofs::arithmetic::Field; use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; use halo2_proofs::dev::MockProver; use halo2_proofs::plonk::{ @@ -45,7 +46,7 @@ fn plonk_api() { } #[allow(clippy::type_complexity)] - trait StandardCs { + trait StandardCs { fn raw_multiply( &self, layouter: &mut impl Layouter, @@ -72,17 +73,17 @@ fn plonk_api() { } #[derive(Clone)] - struct MyCircuit { + struct MyCircuit { a: Value, lookup_table: Vec, } - struct StandardPlonk { + struct StandardPlonk { config: PlonkConfig, _marker: PhantomData, } - impl StandardPlonk { + impl StandardPlonk { fn new(config: PlonkConfig) -> Self { StandardPlonk { config, @@ -91,7 +92,7 @@ fn plonk_api() { } } - impl StandardCs for StandardPlonk { + impl StandardCs for StandardPlonk { fn raw_multiply( &self, layouter: &mut impl Layouter, @@ -138,15 +139,10 @@ fn plonk_api() { || 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()), - )?; + 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())) }, ) @@ -197,14 +193,14 @@ fn plonk_api() { || 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", 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()), + || Value::known(FF::ZERO), )?; Ok((lhs.cell(), rhs.cell(), out.cell())) }, @@ -236,7 +232,7 @@ fn plonk_api() { || "public", self.config.sp, 0, - || Value::known(FF::one()), + || Value::known(FF::ONE), )?; Ok(value.cell()) @@ -266,9 +262,11 @@ fn plonk_api() { } } - impl Circuit for MyCircuit { + impl Circuit for MyCircuit { type Config = PlonkConfig; type FloorPlanner = SimpleFloorPlanner; + #[cfg(feature = "circuit-params")] + type Params = (); fn without_witnesses(&self) -> Self { Self { @@ -374,7 +372,7 @@ fn plonk_api() { ) -> Result<(), Error> { let cs = StandardPlonk::new(config); - let _ = cs.public_input(&mut layouter, || Value::known(F::one() + F::one()))?; + let _ = cs.public_input(&mut layouter, || Value::known(F::ONE + F::ONE))?; for _ in 0..10 { let a: Value> = self.a.into(); @@ -403,14 +401,9 @@ fn plonk_api() { ($scheme:ident) => {{ let a = <$scheme as CommitmentScheme>::Scalar::from(2834758237) * <$scheme as CommitmentScheme>::Scalar::ZETA; - let instance = <$scheme as CommitmentScheme>::Scalar::one() - + <$scheme as CommitmentScheme>::Scalar::one(); - let lookup_table = vec![ - instance, - a, - a, - <$scheme as CommitmentScheme>::Scalar::zero(), - ]; + let instance = <$scheme as CommitmentScheme>::Scalar::ONE + + <$scheme as CommitmentScheme>::Scalar::ONE; + let lookup_table = vec![instance, a, a, <$scheme as CommitmentScheme>::Scalar::ZERO]; (a, instance, lookup_table) }}; } @@ -445,9 +438,10 @@ fn plonk_api() { }}; } - fn keygen( - params: &Scheme::ParamsProver, - ) -> ProvingKey { + fn keygen(params: &Scheme::ParamsProver) -> ProvingKey + where + Scheme::Scalar: FromUniformBytes<64> + WithSmallOrderMulGroup<3>, + { let (_, _, lookup_table) = common!(Scheme); let empty_circuit: MyCircuit = MyCircuit { a: Value::unknown(), @@ -471,7 +465,10 @@ fn plonk_api() { rng: R, params: &'params Scheme::ParamsProver, pk: &ProvingKey, - ) -> Vec { + ) -> Vec + where + Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + { let (a, instance, lookup_table) = common!(Scheme); let circuit: MyCircuit = MyCircuit { @@ -513,7 +510,9 @@ fn plonk_api() { params_verifier: &'params Scheme::ParamsVerifier, vk: &VerifyingKey, proof: &'a [u8], - ) { + ) where + Scheme::Scalar: Ord + WithSmallOrderMulGroup<3> + FromUniformBytes<64>, + { let (_, instance, _) = common!(Scheme); let pubinputs = vec![instance]; diff --git a/rust-toolchain b/rust-toolchain index cc99c5da..94057304 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2022-07-26 +1.64.0