diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml deleted file mode 100644 index c948247ae3..0000000000 --- a/.github/workflows/bench.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Benchmarks -on: - push: - branches: - - main - -permissions: - contents: write - deployments: write - -jobs: - benchmark: - name: Performance regression check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.56.1 - override: true - - name: Run benchmark - run: cargo bench -- --output-format bencher | tee output.txt - - name: Store benchmark result - uses: benchmark-action/github-action-benchmark@v1 - with: - name: halo2 Benchmark - tool: 'cargo' - output-file-path: output.txt - github-token: ${{ secrets.GITHUB_TOKEN }} - auto-push: true - # Show alert with commit comment on detecting possible performance regression - alert-threshold: '200%' - comment-on-alert: true - fail-on-alert: true - alert-comment-cc-users: '@str4d' diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml deleted file mode 100644 index c667767d17..0000000000 --- a/.github/workflows/book.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: halo2 book - -on: - push: - branches: - - main - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true - - - name: Setup mdBook - uses: peaceiris/actions-mdbook@v1 - with: - mdbook-version: 'latest' - - - name: Install mdbook-katex - uses: actions-rs/cargo@v1 - with: - command: install - args: mdbook-katex - - - name: Build halo2 book - run: mdbook build book/ - - - name: Build latest rustdocs - uses: actions-rs/cargo@v1 - with: - command: doc - args: --no-deps --workspace --all-features - env: - RUSTDOCFLAGS: -Z unstable-options --enable-index-page --cfg docsrs --html-in-header ${{ github.workspace }}/halo2_proofs/katex-header.html - - - name: Move latest rustdocs into book - run: | - mkdir -p ./book/book/rustdoc - mv ./target/doc ./book/book/rustdoc/latest - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./book/book diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f2185fcc8..c6cd03bdc7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,8 +14,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.56.1 - override: true + override: false - name: Run tests uses: actions-rs/cargo@v1 with: @@ -35,8 +34,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.56.1 - override: true + override: false - name: Add target run: rustup target add ${{ matrix.target }} - name: cargo build @@ -53,8 +51,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.56.1 - override: true + override: false # Build benchmarks to prevent bitrot - name: Build benchmarks uses: actions-rs/cargo@v1 @@ -62,26 +59,6 @@ jobs: command: build args: --benches --examples --all-features - book: - name: Book tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.56.1 - override: true - - name: cargo build - uses: actions-rs/cargo@v1 - with: - command: build - - name: Setup mdBook - uses: peaceiris/actions-mdbook@v1 - with: - mdbook-version: '0.4.5' - - name: Test halo2 book - run: mdbook test -L target/debug/deps book/ - codecov: name: Code coverage runs-on: ubuntu-latest @@ -91,8 +68,7 @@ jobs: # Use stable for this to ensure that cargo-tarpaulin can be built. - uses: actions-rs/toolchain@v1 with: - toolchain: stable - override: true + override: false - name: Install cargo-tarpaulin uses: actions-rs/cargo@v1 with: @@ -114,8 +90,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.56.1 - override: true + override: false - name: cargo fetch uses: actions-rs/cargo@v1 with: @@ -137,8 +112,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.56.1 - override: true + override: false - run: rustup component add rustfmt - uses: actions-rs/cargo@v1 with: diff --git a/.github/workflows/lints-beta.yml b/.github/workflows/lints-beta.yml index 71d183e370..e7fffa7a76 100644 --- a/.github/workflows/lints-beta.yml +++ b/.github/workflows/lints-beta.yml @@ -15,9 +15,8 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: beta components: clippy - override: true + override: false - name: Run Clippy (beta) uses: actions-rs/clippy-check@v1 continue-on-error: true diff --git a/.github/workflows/lints-stable.yml b/.github/workflows/lints-stable.yml index b12d3c0de8..7666dae1df 100644 --- a/.github/workflows/lints-stable.yml +++ b/.github/workflows/lints-stable.yml @@ -13,9 +13,8 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.56.1 components: clippy - override: true + override: false - name: Run clippy uses: actions-rs/clippy-check@v1 with: diff --git a/.github/workflows/trigger_proverbench_dispatch.yml b/.github/workflows/trigger_proverbench_dispatch.yml new file mode 100644 index 0000000000..a17380deb5 --- /dev/null +++ b/.github/workflows/trigger_proverbench_dispatch.yml @@ -0,0 +1,27 @@ +name: Prover Bench on halo2 PR +on: + pull_request: + types: [labeled , ready_for_review] +jobs: + Prover-benches-via-repo-dispatch-from-halo2-fork: + if: ${{ github.event.label.name == 'benchmarks' }} + runs-on: ubuntu-latest + env: + GH_USER: ${{ github.actor }} + _TOKEN: ${{ secrets.BENCHMARKER }} + REVISION: ${{ github.event.pull_request.head.sha }} + REPO: ${{ github.event.repository.name }} + PR_NUMBER: ${{ github.event.number }} + steps: + - name: Install curl + run: | + sudo apt-get update + sudo apt-get install curl + - name: Send repo api call + run: | + curl \ + -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -u ZKEVMBOT:${{ env._TOKEN }} \ + https://api.github.com/repos/appliedzkp/zkevm-circuits/actions/workflows/ProverBenchFromHalo2.yml/dispatches \ + -d "{\"ref\":\"main\",\"inputs\":{\"halo2pr\":\"${{ env.PR_NUMBER }}\",\"revision\":\"${{ env.REVISION }}\",\"event-type\":\"halo2_wfdispatch\",\"ghuser\": \"${{ env.GH_USER }}\"}}" diff --git a/halo2_gadgets/Cargo.toml b/halo2_gadgets/Cargo.toml index a96fd08663..35a2432877 100644 --- a/halo2_gadgets/Cargo.toml +++ b/halo2_gadgets/Cargo.toml @@ -28,7 +28,7 @@ ff = "0.12" group = "0.12" halo2_proofs = { version = "0.2", path = "../halo2_proofs" } lazy_static = "1" -pasta_curves = "0.4" +halo2curves = { git = 'https://github.com/privacy-scaling-explorations/halo2curves', tag = 'v0.2.0' } proptest = { version = "1.0.0", optional = true } rand = "0.8" subtle = "2.3" diff --git a/halo2_gadgets/benches/poseidon.rs b/halo2_gadgets/benches/poseidon.rs index 1216c41357..744546129f 100644 --- a/halo2_gadgets/benches/poseidon.rs +++ b/halo2_gadgets/benches/poseidon.rs @@ -1,15 +1,13 @@ use ff::Field; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, - pasta::Fp, plonk::{ create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, - ConstraintSystem, Error, SingleVerifier, + ConstraintSystem, Error, }, - poly::commitment::Params, transcript::{Blake2bRead, Blake2bWrite, Challenge255}, }; -use pasta_curves::{pallas, vesta}; +use halo2curves::pasta::{pallas, vesta, EqAffine, Fp}; use halo2_gadgets::poseidon::{ primitives::{self as poseidon, ConstantLength, Spec}, @@ -21,6 +19,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 @@ -184,7 +195,7 @@ fn bench_poseidon( S: Spec + Copy + Clone, { // Initialize the polynomial commitment parameters - let params: Params = Params::new(K); + let params: ParamsIPA = ParamsIPA::new(K); let empty_circuit = HashCircuit:: { message: Value::unknown(), @@ -216,21 +227,35 @@ fn bench_poseidon( c.bench_function(&prover_name, |b| { b.iter(|| { // Create a proof - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - create_proof(¶ms, &pk, &[circuit], &[&[]], &mut rng, &mut transcript) - .expect("proof generation should not fail") + let mut transcript = Blake2bWrite::<_, EqAffine, Challenge255<_>>::init(vec![]); + create_proof::, ProverIPA<_>, _, _, _, _>( + ¶ms, + &pk, + &[circuit], + &[&[]], + &mut rng, + &mut transcript, + ) + .expect("proof generation should not fail") }) }); // Create a proof - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - create_proof(¶ms, &pk, &[circuit], &[&[]], &mut rng, &mut transcript) - .expect("proof generation should not fail"); + let mut transcript = Blake2bWrite::<_, EqAffine, Challenge255<_>>::init(vec![]); + create_proof::, ProverIPA<_>, _, _, _, _>( + ¶ms, + &pk, + &[circuit], + &[&[]], + &mut rng, + &mut transcript, + ) + .expect("proof generation should not fail"); let proof = transcript.finalize(); c.bench_function(&verifier_name, |b| { b.iter(|| { - let strategy = SingleVerifier::new(¶ms); + let strategy = SingleStrategy::new(¶ms); let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); assert!(verify_proof(¶ms, pk.get_vk(), strategy, &[&[]], &mut transcript).is_ok()); }); diff --git a/halo2_gadgets/benches/primitives.rs b/halo2_gadgets/benches/primitives.rs index a6cb824ac7..5397efce05 100644 --- a/halo2_gadgets/benches/primitives.rs +++ b/halo2_gadgets/benches/primitives.rs @@ -5,7 +5,7 @@ use halo2_gadgets::{ sinsemilla::primitives as sinsemilla, }; -use pasta_curves::pallas; +use halo2curves::pasta::pallas; #[cfg(unix)] use pprof::criterion::{Output, PProfProfiler}; use rand::{rngs::OsRng, Rng}; diff --git a/halo2_gadgets/benches/sha256.rs b/halo2_gadgets/benches/sha256.rs index baf9c977a3..670956cb0d 100644 --- a/halo2_gadgets/benches/sha256.rs +++ b/halo2_gadgets/benches/sha256.rs @@ -1,13 +1,10 @@ use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, - pasta::{pallas, EqAffine}, - plonk::{ - create_proof, keygen_pk, keygen_vk, verify_proof, Circuit, ConstraintSystem, Error, - SingleVerifier, - }, + plonk::{create_proof, keygen_pk, keygen_vk, verify_proof, Circuit, ConstraintSystem, Error}, poly::commitment::Params, transcript::{Blake2bRead, Blake2bWrite, Challenge255}, }; +use halo2curves::pasta::{pallas, EqAffine}; use rand::rngs::OsRng; use std::{ @@ -20,6 +17,18 @@ use criterion::{criterion_group, criterion_main, Criterion}; use halo2_gadgets::sha256::{BlockWord, Sha256, Table16Chip, Table16Config, BLOCK_SIZE}; +use halo2_proofs::{ + poly::{ + commitment::ParamsProver, + ipa::{ + commitment::{IPACommitmentScheme, ParamsIPA}, + multiopen::{ProverIPA, VerifierIPA}, + strategy::AccumulatorStrategy, + }, + }, + transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}, +}; + #[allow(dead_code)] fn bench(name: &str, k: u32, c: &mut Criterion) { #[derive(Default)] @@ -80,7 +89,7 @@ fn bench(name: &str, k: u32, c: &mut Criterion) { // Initialize the polynomial commitment parameters let params_path = Path::new("./benches/sha256_assets/sha256_params"); if File::open(¶ms_path).is_err() { - let params: Params = Params::new(k); + let params: ParamsIPA = ParamsIPA::new(k); let mut buf = Vec::new(); params.write(&mut buf).expect("Failed to write params"); @@ -91,8 +100,8 @@ fn bench(name: &str, k: u32, c: &mut Criterion) { } let params_fs = File::open(¶ms_path).expect("couldn't load sha256_params"); - let params: Params = - Params::read::<_>(&mut BufReader::new(params_fs)).expect("Failed to read params"); + let params: ParamsIPA = + ParamsIPA::read::<_>(&mut BufReader::new(params_fs)).expect("Failed to read params"); let empty_circuit: MyCircuit = MyCircuit {}; @@ -119,8 +128,15 @@ fn bench(name: &str, k: u32, c: &mut Criterion) { let proof_path = Path::new("./benches/sha256_assets/sha256_proof"); if File::open(&proof_path).is_err() { let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - create_proof(¶ms, &pk, &[circuit], &[], OsRng, &mut transcript) - .expect("proof generation should not fail"); + create_proof::, ProverIPA<_>, _, _, _, _>( + ¶ms, + &pk, + &[circuit], + &[], + OsRng, + &mut transcript, + ) + .expect("proof generation should not fail"); let proof: Vec = transcript.finalize(); let mut file = File::create(&proof_path).expect("Failed to create sha256_proof"); file.write_all(&proof[..]).expect("Failed to write proof"); @@ -134,9 +150,18 @@ fn bench(name: &str, k: u32, c: &mut Criterion) { c.bench_function(&verifier_name, |b| { b.iter(|| { - let strategy = SingleVerifier::new(¶ms); + use halo2_proofs::poly::VerificationStrategy; + let strategy = AccumulatorStrategy::new(¶ms); let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - assert!(verify_proof(¶ms, pk.get_vk(), strategy, &[], &mut transcript).is_ok()); + let strategy = verify_proof::, VerifierIPA<_>, _, _, _>( + ¶ms, + pk.get_vk(), + strategy, + &[], + &mut transcript, + ) + .unwrap(); + assert!(strategy.finalize()); }); }); } diff --git a/halo2_gadgets/src/ecc.rs b/halo2_gadgets/src/ecc.rs index b862947b9b..08f34b15f2 100644 --- a/halo2_gadgets/src/ecc.rs +++ b/halo2_gadgets/src/ecc.rs @@ -585,8 +585,8 @@ pub(crate) mod tests { dev::MockProver, plonk::{Circuit, ConstraintSystem, Error}, }; + use halo2curves::pasta::pallas; use lazy_static::lazy_static; - use pasta_curves::pallas; use super::{ chip::{ diff --git a/halo2_gadgets/src/ecc/chip.rs b/halo2_gadgets/src/ecc/chip.rs index bd28de3058..466234e6b7 100644 --- a/halo2_gadgets/src/ecc/chip.rs +++ b/halo2_gadgets/src/ecc/chip.rs @@ -13,7 +13,7 @@ use halo2_proofs::{ circuit::{AssignedCell, Chip, Layouter, Value}, plonk::{Advice, Assigned, Column, ConstraintSystem, Error, Fixed}, }; -use pasta_curves::{arithmetic::CurveAffine, pallas}; +use halo2curves::{pasta::pallas, CurveAffine}; use std::convert::TryInto; diff --git a/halo2_gadgets/src/ecc/chip/add.rs b/halo2_gadgets/src/ecc/chip/add.rs index 55e3d0115e..9f24d0d7dd 100644 --- a/halo2_gadgets/src/ecc/chip/add.rs +++ b/halo2_gadgets/src/ecc/chip/add.rs @@ -4,7 +4,7 @@ use halo2_proofs::{ plonk::{Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, poly::Rotation, }; -use pasta_curves::{arithmetic::FieldExt, pallas}; +use halo2curves::{pasta::pallas, FieldExt}; use std::collections::HashSet; #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -346,7 +346,7 @@ pub mod tests { circuit::{Layouter, Value}, plonk::Error, }; - use pasta_curves::{arithmetic::CurveExt, pallas}; + use halo2curves::{pasta::pallas, CurveExt}; use crate::ecc::{chip::EccPoint, EccInstructions, NonIdentityPoint}; diff --git a/halo2_gadgets/src/ecc/chip/add_incomplete.rs b/halo2_gadgets/src/ecc/chip/add_incomplete.rs index fd828ce293..a28391c9e1 100644 --- a/halo2_gadgets/src/ecc/chip/add_incomplete.rs +++ b/halo2_gadgets/src/ecc/chip/add_incomplete.rs @@ -6,7 +6,7 @@ use halo2_proofs::{ plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, poly::Rotation, }; -use pasta_curves::pallas; +use halo2curves::pasta::pallas; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Config { @@ -152,7 +152,7 @@ pub mod tests { circuit::{Layouter, Value}, plonk::Error, }; - use pasta_curves::pallas; + use halo2curves::pasta::pallas; use crate::ecc::{EccInstructions, NonIdentityPoint}; diff --git a/halo2_gadgets/src/ecc/chip/constants.rs b/halo2_gadgets/src/ecc/chip/constants.rs index 6757a785e8..cecbbae99a 100644 --- a/halo2_gadgets/src/ecc/chip/constants.rs +++ b/halo2_gadgets/src/ecc/chip/constants.rs @@ -6,10 +6,7 @@ use group::{ Curve, }; use halo2_proofs::arithmetic::lagrange_interpolate; -use pasta_curves::{ - arithmetic::{CurveAffine, FieldExt}, - pallas, -}; +use halo2curves::{pasta::pallas, CurveAffine, FieldExt}; /// Window size for fixed-base scalar multiplication pub const FIXED_BASE_WINDOW_SIZE: usize = 3; @@ -233,10 +230,7 @@ pub fn test_lagrange_coeffs(base: C, num_windows: usize) { #[cfg(test)] mod tests { use group::{ff::Field, Curve, Group}; - use pasta_curves::{ - arithmetic::{CurveAffine, FieldExt}, - pallas, - }; + use halo2curves::{pasta::pallas, CurveAffine, FieldExt}; use proptest::prelude::*; use super::{compute_window_table, find_zs_and_us, test_lagrange_coeffs, H, NUM_WINDOWS}; diff --git a/halo2_gadgets/src/ecc/chip/mul.rs b/halo2_gadgets/src/ecc/chip/mul.rs index d0842ed839..26c705a17c 100644 --- a/halo2_gadgets/src/ecc/chip/mul.rs +++ b/halo2_gadgets/src/ecc/chip/mul.rs @@ -17,7 +17,7 @@ use halo2_proofs::{ }; use uint::construct_uint; -use pasta_curves::pallas; +use halo2curves::pasta::pallas; mod complete; pub(super) mod incomplete; @@ -469,7 +469,7 @@ pub mod tests { circuit::{Chip, Layouter, Value}, plonk::Error, }; - use pasta_curves::pallas; + use halo2curves::pasta::pallas; use rand::rngs::OsRng; use crate::{ diff --git a/halo2_gadgets/src/ecc/chip/mul/complete.rs b/halo2_gadgets/src/ecc/chip/mul/complete.rs index 944becf697..d23294d2fb 100644 --- a/halo2_gadgets/src/ecc/chip/mul/complete.rs +++ b/halo2_gadgets/src/ecc/chip/mul/complete.rs @@ -8,7 +8,7 @@ use halo2_proofs::{ poly::Rotation, }; -use pasta_curves::pallas; +use halo2curves::pasta::pallas; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Config { diff --git a/halo2_gadgets/src/ecc/chip/mul/incomplete.rs b/halo2_gadgets/src/ecc/chip/mul/incomplete.rs index b82ffecc92..bfe51c7e2e 100644 --- a/halo2_gadgets/src/ecc/chip/mul/incomplete.rs +++ b/halo2_gadgets/src/ecc/chip/mul/incomplete.rs @@ -8,8 +8,7 @@ use halo2_proofs::{ }, poly::Rotation, }; - -use pasta_curves::{arithmetic::FieldExt, pallas}; +use halo2curves::{pasta::pallas, FieldExt}; /// 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 a80688dc31..0d7bd69692 100644 --- a/halo2_gadgets/src/ecc/chip/mul/overflow.rs +++ b/halo2_gadgets/src/ecc/chip/mul/overflow.rs @@ -10,7 +10,7 @@ use halo2_proofs::{ poly::Rotation, }; -use pasta_curves::{arithmetic::FieldExt, pallas}; +use halo2curves::{pasta::pallas, FieldExt}; use std::iter; diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed.rs b/halo2_gadgets/src/ecc/chip/mul_fixed.rs index 0dbda61e50..909dd171d3 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed.rs @@ -18,11 +18,8 @@ use halo2_proofs::{ }, poly::Rotation, }; +use halo2curves::{pasta::pallas, CurveAffine, FieldExt}; use lazy_static::lazy_static; -use pasta_curves::{ - arithmetic::{CurveAffine, FieldExt}, - pallas, -}; pub mod base_field_elem; pub mod full_width; 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 5563ee645f..08fd34e313 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 pasta_curves::{arithmetic::FieldExt, pallas}; +use halo2curves::{pasta::pallas, FieldExt}; use std::convert::TryInto; @@ -385,7 +385,7 @@ pub mod tests { circuit::{Chip, Layouter, Value}, plonk::Error, }; - use pasta_curves::pallas; + use halo2curves::pasta::pallas; use rand::rngs::OsRng; use crate::{ 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 886f86bbba..b82620c283 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/full_width.rs @@ -8,7 +8,7 @@ use halo2_proofs::{ plonk::{ConstraintSystem, Constraints, Error, Selector}, poly::Rotation, }; -use pasta_curves::pallas; +use halo2curves::pasta::pallas; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Config> { @@ -184,7 +184,7 @@ pub mod tests { circuit::{Layouter, Value}, plonk::Error, }; - use pasta_curves::pallas; + use halo2curves::pasta::pallas; use rand::rngs::OsRng; use crate::ecc::{ diff --git a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs index 814b632ae2..47b2aa14d9 100644 --- a/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs +++ b/halo2_gadgets/src/ecc/chip/mul_fixed/short.rs @@ -8,7 +8,7 @@ use halo2_proofs::{ plonk::{ConstraintSystem, Constraints, Error, Expression, Selector}, poly::Rotation, }; -use pasta_curves::pallas; +use halo2curves::pasta::pallas; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Config> { @@ -254,7 +254,7 @@ pub mod tests { circuit::{AssignedCell, Chip, Layouter, Value}, plonk::{Any, Error}, }; - use pasta_curves::{arithmetic::FieldExt, pallas}; + use halo2curves::{pasta::pallas, FieldExt}; use crate::{ ecc::{ diff --git a/halo2_gadgets/src/ecc/chip/witness_point.rs b/halo2_gadgets/src/ecc/chip/witness_point.rs index b6a1d34cc3..ef83bbde54 100644 --- a/halo2_gadgets/src/ecc/chip/witness_point.rs +++ b/halo2_gadgets/src/ecc/chip/witness_point.rs @@ -10,7 +10,7 @@ use halo2_proofs::{ }, poly::Rotation, }; -use pasta_curves::{arithmetic::CurveAffine, pallas}; +use halo2curves::{pasta::pallas, CurveAffine}; type Coordinates = ( AssignedCell, pallas::Base>, @@ -152,7 +152,7 @@ impl Config { #[cfg(test)] pub mod tests { use halo2_proofs::circuit::Layouter; - use pasta_curves::pallas; + use halo2curves::pasta::pallas; use super::*; use crate::ecc::{EccInstructions, NonIdentityPoint}; diff --git a/halo2_gadgets/src/poseidon/pow5.rs b/halo2_gadgets/src/poseidon/pow5.rs index a167377bab..7b9862e5b6 100644 --- a/halo2_gadgets/src/poseidon/pow5.rs +++ b/halo2_gadgets/src/poseidon/pow5.rs @@ -598,10 +598,9 @@ mod tests { use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, dev::MockProver, - pasta::Fp, plonk::{Circuit, ConstraintSystem, Error}, }; - use pasta_curves::pallas; + use halo2curves::pasta::{pallas, Fp}; use rand::rngs::OsRng; use super::{PoseidonInstructions, Pow5Chip, Pow5Config, StateWord}; diff --git a/halo2_gadgets/src/poseidon/primitives.rs b/halo2_gadgets/src/poseidon/primitives.rs index 6664d1d031..4d7d5b038a 100644 --- a/halo2_gadgets/src/poseidon/primitives.rs +++ b/halo2_gadgets/src/poseidon/primitives.rs @@ -374,8 +374,7 @@ impl, const T: usize, const RATE: usize, const #[cfg(test)] mod tests { - use halo2_proofs::arithmetic::FieldExt; - use pasta_curves::pallas; + use halo2curves::{pasta::pallas, FieldExt}; use super::{permute, ConstantLength, Hash, P128Pow5T3 as OrchardNullifier, Spec}; diff --git a/halo2_gadgets/src/poseidon/primitives/fp.rs b/halo2_gadgets/src/poseidon/primitives/fp.rs index 7041d20790..38b38937fb 100644 --- a/halo2_gadgets/src/poseidon/primitives/fp.rs +++ b/halo2_gadgets/src/poseidon/primitives/fp.rs @@ -6,7 +6,7 @@ //! ```text //! $ sage generate_parameters_grain.sage 1 0 255 3 8 56 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001 //! ``` -use pasta_curves::pallas; +use halo2curves::pasta::pallas; // Number of round constants: 192 // Round constants for GF(p): diff --git a/halo2_gadgets/src/poseidon/primitives/fq.rs b/halo2_gadgets/src/poseidon/primitives/fq.rs index a22f25aea8..3629318adc 100644 --- a/halo2_gadgets/src/poseidon/primitives/fq.rs +++ b/halo2_gadgets/src/poseidon/primitives/fq.rs @@ -6,7 +6,7 @@ //! ```text //! sage generate_parameters_grain.sage 1 0 255 3 8 56 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001 //! ``` -use pasta_curves::vesta; +use halo2curves::pasta::vesta; // Number of round constants: 192 // Round constants for GF(p): diff --git a/halo2_gadgets/src/poseidon/primitives/grain.rs b/halo2_gadgets/src/poseidon/primitives/grain.rs index 1d9ad45ba0..f3cf94f761 100644 --- a/halo2_gadgets/src/poseidon/primitives/grain.rs +++ b/halo2_gadgets/src/poseidon/primitives/grain.rs @@ -181,7 +181,7 @@ impl Iterator for Grain { #[cfg(test)] mod tests { - use pasta_curves::Fp; + use halo2curves::pasta::Fp; use super::{Grain, SboxType}; diff --git a/halo2_gadgets/src/poseidon/primitives/mds.rs b/halo2_gadgets/src/poseidon/primitives/mds.rs index 45a87c92ba..fb809e3a79 100644 --- a/halo2_gadgets/src/poseidon/primitives/mds.rs +++ b/halo2_gadgets/src/poseidon/primitives/mds.rs @@ -98,7 +98,7 @@ pub(super) fn generate_mds( #[cfg(test)] mod tests { - use pasta_curves::Fp; + use halo2curves::pasta::Fp; use super::{generate_mds, Grain}; diff --git a/halo2_gadgets/src/poseidon/primitives/p128pow5t3.rs b/halo2_gadgets/src/poseidon/primitives/p128pow5t3.rs index c1ef97c6f0..379c399b4e 100644 --- a/halo2_gadgets/src/poseidon/primitives/p128pow5t3.rs +++ b/halo2_gadgets/src/poseidon/primitives/p128pow5t3.rs @@ -1,5 +1,5 @@ use halo2_proofs::arithmetic::Field; -use pasta_curves::{pallas::Base as Fp, vesta::Base as Fq}; +use halo2curves::pasta::{pallas::Base as Fp, vesta::Base as Fq}; use super::{Mds, Spec}; @@ -69,7 +69,7 @@ mod tests { use ff::PrimeField; use std::marker::PhantomData; - use pasta_curves::arithmetic::FieldExt; + use halo2curves::FieldExt; use super::{ super::{fp, fq}, diff --git a/halo2_gadgets/src/sha256/table16.rs b/halo2_gadgets/src/sha256/table16.rs index 7a2d8caa66..bf9b35aada 100644 --- a/halo2_gadgets/src/sha256/table16.rs +++ b/halo2_gadgets/src/sha256/table16.rs @@ -4,9 +4,9 @@ use std::marker::PhantomData; use super::Sha256Instructions; use halo2_proofs::{ circuit::{AssignedCell, Chip, Layouter, Region, Value}, - pasta::pallas, plonk::{Advice, Any, Assigned, Column, ConstraintSystem, Error}, }; +use halo2curves::pasta::pallas; mod compression; mod gates; @@ -456,9 +456,9 @@ mod tests { use super::{message_schedule::msg_schedule_test_input, Table16Chip, Table16Config}; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, - pasta::pallas, plonk::{Circuit, ConstraintSystem, Error}, }; + use halo2curves::pasta::pallas; #[test] fn print_sha256_circuit() { diff --git a/halo2_gadgets/src/sha256/table16/compression.rs b/halo2_gadgets/src/sha256/table16/compression.rs index 528b1d9a98..62deb42937 100644 --- a/halo2_gadgets/src/sha256/table16/compression.rs +++ b/halo2_gadgets/src/sha256/table16/compression.rs @@ -5,10 +5,10 @@ use super::{ }; use halo2_proofs::{ circuit::{Layouter, Value}, - pasta::pallas, plonk::{Advice, Column, ConstraintSystem, Error, Selector}, poly::Rotation, }; +use halo2curves::pasta::pallas; use std::convert::TryInto; use std::ops::Range; @@ -943,9 +943,9 @@ mod tests { use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, dev::MockProver, - pasta::pallas, plonk::{Circuit, ConstraintSystem, Error}, }; + use halo2curves::pasta::pallas; #[test] fn compress() { diff --git a/halo2_gadgets/src/sha256/table16/compression/compression_util.rs b/halo2_gadgets/src/sha256/table16/compression/compression_util.rs index cf4c366ecf..324fe8f695 100644 --- a/halo2_gadgets/src/sha256/table16/compression/compression_util.rs +++ b/halo2_gadgets/src/sha256/table16/compression/compression_util.rs @@ -7,9 +7,9 @@ use crate::sha256::table16::{ }; use halo2_proofs::{ circuit::{Region, Value}, - pasta::pallas, plonk::{Advice, Column, Error}, }; +use halo2curves::pasta::pallas; use std::convert::TryInto; // Test vector 'abc' diff --git a/halo2_gadgets/src/sha256/table16/compression/subregion_digest.rs b/halo2_gadgets/src/sha256/table16/compression/subregion_digest.rs index 28df0ba9e3..aa30f80af7 100644 --- a/halo2_gadgets/src/sha256/table16/compression/subregion_digest.rs +++ b/halo2_gadgets/src/sha256/table16/compression/subregion_digest.rs @@ -2,9 +2,9 @@ use super::super::{super::DIGEST_SIZE, BlockWord, RoundWordDense}; use super::{compression_util::*, CompressionConfig, State}; use halo2_proofs::{ circuit::{Region, Value}, - pasta::pallas, plonk::{Advice, Column, Error}, }; +use halo2curves::pasta::pallas; impl CompressionConfig { #[allow(clippy::many_single_char_names)] diff --git a/halo2_gadgets/src/sha256/table16/compression/subregion_initial.rs b/halo2_gadgets/src/sha256/table16/compression/subregion_initial.rs index 56e7dc6178..a487dc0c87 100644 --- a/halo2_gadgets/src/sha256/table16/compression/subregion_initial.rs +++ b/halo2_gadgets/src/sha256/table16/compression/subregion_initial.rs @@ -1,10 +1,11 @@ use super::super::{RoundWord, StateWord, STATE}; use super::{compression_util::*, CompressionConfig, State}; + use halo2_proofs::{ circuit::{Region, Value}, - pasta::pallas, plonk::Error, }; +use halo2curves::pasta::pallas; impl CompressionConfig { #[allow(clippy::many_single_char_names)] diff --git a/halo2_gadgets/src/sha256/table16/compression/subregion_main.rs b/halo2_gadgets/src/sha256/table16/compression/subregion_main.rs index 2a0f433c14..bda188a866 100644 --- a/halo2_gadgets/src/sha256/table16/compression/subregion_main.rs +++ b/halo2_gadgets/src/sha256/table16/compression/subregion_main.rs @@ -1,6 +1,7 @@ use super::super::{AssignedBits, RoundWord, RoundWordA, RoundWordE, StateWord, ROUND_CONSTANTS}; use super::{compression_util::*, CompressionConfig, State}; -use halo2_proofs::{circuit::Region, pasta::pallas, plonk::Error}; +use halo2_proofs::{circuit::Region, plonk::Error}; +use halo2curves::pasta::pallas; impl CompressionConfig { #[allow(clippy::many_single_char_names)] diff --git a/halo2_gadgets/src/sha256/table16/message_schedule.rs b/halo2_gadgets/src/sha256/table16/message_schedule.rs index 0f92b2e132..690e086c49 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule.rs @@ -3,10 +3,10 @@ use std::convert::TryInto; use super::{super::BLOCK_SIZE, AssignedBits, BlockWord, SpreadInputs, Table16Assignment, ROUNDS}; use halo2_proofs::{ circuit::Layouter, - pasta::pallas, plonk::{Advice, Column, ConstraintSystem, Error, Selector}, poly::Rotation, }; +use halo2curves::pasta::pallas; mod schedule_gates; mod schedule_util; @@ -400,9 +400,9 @@ mod tests { use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, dev::MockProver, - pasta::pallas, plonk::{Circuit, ConstraintSystem, Error}, }; + use halo2curves::pasta::pallas; #[test] fn message_schedule() { diff --git a/halo2_gadgets/src/sha256/table16/message_schedule/schedule_util.rs b/halo2_gadgets/src/sha256/table16/message_schedule/schedule_util.rs index 93f16430fa..79a9fa2621 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule/schedule_util.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule/schedule_util.rs @@ -2,9 +2,9 @@ use super::super::AssignedBits; use super::MessageScheduleConfig; use halo2_proofs::{ circuit::{Region, Value}, - pasta::pallas, plonk::Error, }; +use halo2curves::pasta::pallas; #[cfg(test)] use super::super::{super::BLOCK_SIZE, BlockWord, ROUNDS}; diff --git a/halo2_gadgets/src/sha256/table16/message_schedule/subregion1.rs b/halo2_gadgets/src/sha256/table16/message_schedule/subregion1.rs index e30c32ee70..947c9dda2a 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule/subregion1.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule/subregion1.rs @@ -2,9 +2,9 @@ use super::super::{util::*, AssignedBits, BlockWord, SpreadVar, SpreadWord, Tabl use super::{schedule_util::*, MessageScheduleConfig}; use halo2_proofs::{ circuit::{Region, Value}, - pasta::pallas, plonk::Error, }; +use halo2curves::pasta::pallas; use std::convert::TryInto; // A word in subregion 1 diff --git a/halo2_gadgets/src/sha256/table16/message_schedule/subregion2.rs b/halo2_gadgets/src/sha256/table16/message_schedule/subregion2.rs index e5e25fae21..43e96c934f 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule/subregion2.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule/subregion2.rs @@ -2,9 +2,9 @@ use super::super::{util::*, AssignedBits, Bits, SpreadVar, SpreadWord, Table16As use super::{schedule_util::*, MessageScheduleConfig, MessageWord}; use halo2_proofs::{ circuit::{Region, Value}, - pasta::pallas, plonk::Error, }; +use halo2curves::pasta::pallas; use std::convert::TryInto; /// A word in subregion 2 diff --git a/halo2_gadgets/src/sha256/table16/message_schedule/subregion3.rs b/halo2_gadgets/src/sha256/table16/message_schedule/subregion3.rs index 6ce3d29080..b23046e42e 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule/subregion3.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule/subregion3.rs @@ -2,9 +2,9 @@ use super::super::{util::*, AssignedBits, Bits, SpreadVar, SpreadWord, Table16As use super::{schedule_util::*, MessageScheduleConfig, MessageWord}; use halo2_proofs::{ circuit::{Region, Value}, - pasta::pallas, plonk::Error, }; +use halo2curves::pasta::pallas; use std::convert::TryInto; // A word in subregion 3 diff --git a/halo2_gadgets/src/sha256/table16/spread_table.rs b/halo2_gadgets/src/sha256/table16/spread_table.rs index dc04f7ee5e..3e1488e9ac 100644 --- a/halo2_gadgets/src/sha256/table16/spread_table.rs +++ b/halo2_gadgets/src/sha256/table16/spread_table.rs @@ -2,10 +2,10 @@ use super::{util::*, AssignedBits}; use halo2_proofs::{ arithmetic::FieldExt, circuit::{Chip, Layouter, Region, Value}, - pasta::pallas, plonk::{Advice, Column, ConstraintSystem, Error, TableColumn}, poly::Rotation, }; +use halo2curves::pasta::pallas; use std::convert::TryInto; use std::marker::PhantomData; @@ -182,7 +182,7 @@ impl SpreadTableChip { let table_dense = meta.lookup_table_column(); let table_spread = meta.lookup_table_column(); - meta.lookup(|meta| { + meta.lookup("lookup", |meta| { let tag_cur = meta.query_advice(input_tag, Rotation::cur()); let dense_cur = meta.query_advice(input_dense, Rotation::cur()); let spread_cur = meta.query_advice(input_spread, Rotation::cur()); @@ -291,9 +291,9 @@ mod tests { arithmetic::FieldExt, circuit::{Layouter, SimpleFloorPlanner, Value}, dev::MockProver, - pasta::Fp, plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, }; + use halo2curves::pasta::Fp; #[test] fn lookup_table() { diff --git a/halo2_gadgets/src/sinsemilla.rs b/halo2_gadgets/src/sinsemilla.rs index f0857a1313..3cec450ea1 100644 --- a/halo2_gadgets/src/sinsemilla.rs +++ b/halo2_gadgets/src/sinsemilla.rs @@ -10,7 +10,7 @@ use halo2_proofs::{ circuit::{Layouter, Value}, plonk::Error, }; -use pasta_curves::arithmetic::CurveAffine; +use halo2curves::CurveAffine; use std::fmt::Debug; pub mod chip; @@ -478,8 +478,8 @@ pub(crate) mod tests { }; use group::{ff::Field, Curve}; + use halo2curves::pasta::pallas; use lazy_static::lazy_static; - use pasta_curves::pallas; use std::convert::TryInto; diff --git a/halo2_gadgets/src/sinsemilla/chip.rs b/halo2_gadgets/src/sinsemilla/chip.rs index de4bbb1b68..a6788e55e1 100644 --- a/halo2_gadgets/src/sinsemilla/chip.rs +++ b/halo2_gadgets/src/sinsemilla/chip.rs @@ -21,7 +21,7 @@ use halo2_proofs::{ }, poly::Rotation, }; -use pasta_curves::pallas; +use halo2curves::pasta::pallas; mod generator_table; use generator_table::GeneratorTableConfig; diff --git a/halo2_gadgets/src/sinsemilla/chip/generator_table.rs b/halo2_gadgets/src/sinsemilla/chip/generator_table.rs index b9c3ca25a0..a653c13b35 100644 --- a/halo2_gadgets/src/sinsemilla/chip/generator_table.rs +++ b/halo2_gadgets/src/sinsemilla/chip/generator_table.rs @@ -6,7 +6,7 @@ use halo2_proofs::{ use super::{CommitDomains, FixedPoints, HashDomains}; use crate::sinsemilla::primitives::{self as sinsemilla, SINSEMILLA_S}; -use pasta_curves::{arithmetic::FieldExt, pallas}; +use halo2curves::{pasta::pallas, FieldExt}; /// Table containing independent generators S[0..2^k] #[derive(Eq, PartialEq, Copy, Clone, Debug)] @@ -37,7 +37,7 @@ impl GeneratorTableConfig { ); // https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial - meta.lookup(|meta| { + meta.lookup("lookup", |meta| { let q_s1 = meta.query_selector(config.q_sinsemilla1); let q_s2 = meta.query_fixed(config.q_sinsemilla2, Rotation::cur()); let q_s3 = config.q_s3(meta); diff --git a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs index 25be0efb36..70eab6b86d 100644 --- a/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs +++ b/halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs @@ -11,10 +11,7 @@ use halo2_proofs::{ }; use group::ff::{PrimeField, PrimeFieldBits}; -use pasta_curves::{ - arithmetic::{CurveAffine, FieldExt}, - pallas, -}; +use halo2curves::{pasta::pallas, CurveAffine, FieldExt}; use std::ops::Deref; @@ -130,7 +127,7 @@ where use crate::sinsemilla::primitives::{K, S_PERSONALIZATION}; use group::{prime::PrimeCurveAffine, Curve}; - use pasta_curves::arithmetic::CurveExt; + use halo2curves::CurveExt; let field_elems: Value> = message .iter() diff --git a/halo2_gadgets/src/sinsemilla/merkle.rs b/halo2_gadgets/src/sinsemilla/merkle.rs index aa6c420bd3..a9ae781d5c 100644 --- a/halo2_gadgets/src/sinsemilla/merkle.rs +++ b/halo2_gadgets/src/sinsemilla/merkle.rs @@ -4,7 +4,7 @@ use halo2_proofs::{ circuit::{Chip, Layouter, Value}, plonk::Error, }; -use pasta_curves::arithmetic::CurveAffine; +use halo2curves::CurveAffine; use super::{HashDomains, SinsemillaInstructions}; @@ -191,9 +191,9 @@ pub mod tests { use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, dev::MockProver, - pasta::pallas, plonk::{Circuit, ConstraintSystem, Error}, }; + use halo2curves::pasta::pallas; use rand::{rngs::OsRng, RngCore}; use std::{convert::TryInto, iter}; diff --git a/halo2_gadgets/src/sinsemilla/merkle/chip.rs b/halo2_gadgets/src/sinsemilla/merkle/chip.rs index dcedb6b042..97da766d0c 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 pasta_curves::{arithmetic::FieldExt, pallas}; +use halo2curves::{pasta::pallas, FieldExt}; use super::MerkleInstructions; diff --git a/halo2_gadgets/src/sinsemilla/primitives.rs b/halo2_gadgets/src/sinsemilla/primitives.rs index 9bf6a72332..fd2e4aea72 100644 --- a/halo2_gadgets/src/sinsemilla/primitives.rs +++ b/halo2_gadgets/src/sinsemilla/primitives.rs @@ -2,7 +2,7 @@ use group::{Curve, Wnaf}; use halo2_proofs::arithmetic::{CurveAffine, CurveExt}; -use pasta_curves::pallas; +use halo2curves::pasta::pallas; use subtle::CtOption; mod addition; @@ -245,7 +245,7 @@ impl CommitDomain { #[cfg(test)] mod tests { use super::{Pad, K}; - use pasta_curves::{arithmetic::CurveExt, pallas}; + use halo2curves::{pasta::pallas, CurveExt}; #[test] fn pad() { @@ -292,7 +292,7 @@ mod tests { fn sinsemilla_s() { use super::sinsemilla_s::SINSEMILLA_S; use group::Curve; - use pasta_curves::arithmetic::CurveAffine; + use halo2curves::CurveAffine; let hasher = pallas::Point::hash_to_curve(super::S_PERSONALIZATION); diff --git a/halo2_gadgets/src/sinsemilla/primitives/addition.rs b/halo2_gadgets/src/sinsemilla/primitives/addition.rs index 3949addaa1..9d4574dca1 100644 --- a/halo2_gadgets/src/sinsemilla/primitives/addition.rs +++ b/halo2_gadgets/src/sinsemilla/primitives/addition.rs @@ -1,7 +1,7 @@ use std::ops::Add; use group::{cofactor::CofactorCurveAffine, Group}; -use pasta_curves::pallas; +use halo2curves::pasta::pallas; use subtle::{ConstantTimeEq, CtOption}; /// P ∪ {⊥} diff --git a/halo2_gadgets/src/sinsemilla/primitives/sinsemilla_s.rs b/halo2_gadgets/src/sinsemilla/primitives/sinsemilla_s.rs index 739d5a793b..c72fe5841f 100644 --- a/halo2_gadgets/src/sinsemilla/primitives/sinsemilla_s.rs +++ b/halo2_gadgets/src/sinsemilla/primitives/sinsemilla_s.rs @@ -1,5 +1,5 @@ use super::K; -use pasta_curves::pallas; +use halo2curves::pasta::pallas; /// The precomputed bases for the [Sinsemilla hash function][concretesinsemillahash]. /// diff --git a/halo2_gadgets/src/utilities.rs b/halo2_gadgets/src/utilities.rs index 42b09f637f..c686878475 100644 --- a/halo2_gadgets/src/utilities.rs +++ b/halo2_gadgets/src/utilities.rs @@ -5,7 +5,7 @@ use halo2_proofs::{ circuit::{AssignedCell, Cell, Layouter, Value}, plonk::{Advice, Column, Error, Expression}, }; -use pasta_curves::arithmetic::FieldExt; +use halo2curves::FieldExt; use std::marker::PhantomData; use std::ops::Range; @@ -247,7 +247,7 @@ mod tests { plonk::{Any, Circuit, ConstraintSystem, Constraints, Error, Selector}, poly::Rotation, }; - use pasta_curves::{arithmetic::FieldExt, pallas}; + use halo2curves::{pasta::pallas, FieldExt}; use proptest::prelude::*; use rand::rngs::OsRng; use std::convert::TryInto; diff --git a/halo2_gadgets/src/utilities/cond_swap.rs b/halo2_gadgets/src/utilities/cond_swap.rs index 86194bf279..9dc1afa3ef 100644 --- a/halo2_gadgets/src/utilities/cond_swap.rs +++ b/halo2_gadgets/src/utilities/cond_swap.rs @@ -6,7 +6,7 @@ use halo2_proofs::{ plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector}, poly::Rotation, }; -use pasta_curves::arithmetic::FieldExt; +use halo2curves::FieldExt; use std::marker::PhantomData; /// Instructions for a conditional swap gadget. @@ -201,7 +201,7 @@ mod tests { dev::MockProver, plonk::{Circuit, ConstraintSystem, Error}, }; - use pasta_curves::{arithmetic::FieldExt, pallas::Base}; + use halo2curves::{pasta::pallas::Base, FieldExt}; use rand::rngs::OsRng; #[test] diff --git a/halo2_gadgets/src/utilities/decompose_running_sum.rs b/halo2_gadgets/src/utilities/decompose_running_sum.rs index e6364f219b..ba98a047bd 100644 --- a/halo2_gadgets/src/utilities/decompose_running_sum.rs +++ b/halo2_gadgets/src/utilities/decompose_running_sum.rs @@ -30,7 +30,7 @@ use halo2_proofs::{ }; use super::range_check; -use pasta_curves::arithmetic::FieldExt; +use halo2curves::FieldExt; use std::marker::PhantomData; /// The running sum $[z_0, ..., z_W]$. If created in strict mode, $z_W = 0$. @@ -216,7 +216,7 @@ mod tests { dev::{FailureLocation, MockProver, VerifyFailure}, plonk::{Any, Circuit, ConstraintSystem, Error}, }; - use pasta_curves::{arithmetic::FieldExt, pallas}; + use halo2curves::{pasta::pallas, FieldExt}; use rand::rngs::OsRng; use crate::ecc::chip::{ diff --git a/halo2_gadgets/src/utilities/lookup_range_check.rs b/halo2_gadgets/src/utilities/lookup_range_check.rs index b0f329bd77..f97654c38b 100644 --- a/halo2_gadgets/src/utilities/lookup_range_check.rs +++ b/halo2_gadgets/src/utilities/lookup_range_check.rs @@ -97,7 +97,7 @@ impl LookupRangeCheckConfig }; // https://p.z.cash/halo2-0.1:decompose-combined-lookup - meta.lookup(|meta| { + meta.lookup("lookup", |meta| { let q_lookup = meta.query_selector(config.q_lookup); let q_running = meta.query_selector(config.q_running); let z_cur = meta.query_advice(config.running_sum, Rotation::cur()); @@ -395,7 +395,7 @@ mod tests { dev::{FailureLocation, MockProver, VerifyFailure}, plonk::{Circuit, ConstraintSystem, Error}, }; - use pasta_curves::{arithmetic::FieldExt, pallas}; + use halo2curves::{pasta::pallas, FieldExt}; use std::{convert::TryInto, marker::PhantomData}; @@ -582,6 +582,7 @@ mod tests { assert_eq!( prover.verify(), Err(vec![VerifyFailure::Lookup { + name: "lookup", lookup_index: 0, location: FailureLocation::InRegion { region: (1, "Range check 6 bits").into(), @@ -602,6 +603,7 @@ mod tests { prover.verify(), Err(vec![ VerifyFailure::Lookup { + name: "lookup", lookup_index: 0, location: FailureLocation::InRegion { region: (1, "Range check 6 bits").into(), @@ -609,6 +611,7 @@ mod tests { }, }, VerifyFailure::Lookup { + name: "lookup", lookup_index: 0, location: FailureLocation::InRegion { region: (1, "Range check 6 bits").into(), @@ -638,6 +641,7 @@ mod tests { assert_eq!( prover.verify(), Err(vec![VerifyFailure::Lookup { + name: "lookup", 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 97437018af..8b8dee7f18 100644 --- a/halo2_proofs/Cargo.toml +++ b/halo2_proofs/Cargo.toml @@ -48,7 +48,7 @@ backtrace = { version = "0.3", optional = true } rayon = "1.5.1" ff = "0.12" group = "0.12" -pasta_curves = "0.4" +halo2curves = { git = 'https://github.com/privacy-scaling-explorations/halo2curves', tag = "0.2.1" } rand_core = { version = "0.6", default-features = false } tracing = "0.1" blake2b_simd = "1" diff --git a/halo2_proofs/benches/arithmetic.rs b/halo2_proofs/benches/arithmetic.rs index ef37964390..4ae88af137 100644 --- a/halo2_proofs/benches/arithmetic.rs +++ b/halo2_proofs/benches/arithmetic.rs @@ -2,11 +2,12 @@ extern crate criterion; use crate::arithmetic::small_multiexp; -use crate::pasta::{EqAffine, Fp}; -use crate::poly::commitment::Params; +use crate::halo2curves::pasta::{EqAffine, Fp}; use group::ff::Field; use halo2_proofs::*; +use halo2_proofs::poly::{commitment::ParamsProver, ipa::commitment::ParamsIPA}; + use criterion::{black_box, Criterion}; use rand_core::OsRng; @@ -15,8 +16,8 @@ fn criterion_benchmark(c: &mut Criterion) { // small multiexp { - let params: Params = Params::new(5); - let g = &mut params.get_g(); + let params: ParamsIPA = ParamsIPA::new(5); + let g = &mut params.get_g().to_vec(); let len = g.len() / 2; let (g_lo, g_hi) = g.split_at_mut(len); diff --git a/halo2_proofs/benches/dev_lookup.rs b/halo2_proofs/benches/dev_lookup.rs index 7592b49812..bb6cfdadbf 100644 --- a/halo2_proofs/benches/dev_lookup.rs +++ b/halo2_proofs/benches/dev_lookup.rs @@ -6,7 +6,7 @@ use halo2_proofs::circuit::{Layouter, SimpleFloorPlanner, Value}; use halo2_proofs::dev::MockProver; use halo2_proofs::plonk::*; use halo2_proofs::poly::Rotation; -use pasta_curves::pallas; +use halo2curves::pasta::pallas; use std::marker::PhantomData; @@ -40,7 +40,7 @@ fn criterion_benchmark(c: &mut Criterion) { advice: meta.advice_column(), }; - meta.lookup(|meta| { + meta.lookup("lookup", |meta| { let selector = meta.query_selector(config.selector); let not_selector = Expression::Constant(F::one()) - selector.clone(); let advice = meta.query_advice(config.advice, Rotation::cur()); diff --git a/halo2_proofs/benches/fft.rs b/halo2_proofs/benches/fft.rs index d9564958e2..0de72a0380 100644 --- a/halo2_proofs/benches/fft.rs +++ b/halo2_proofs/benches/fft.rs @@ -2,9 +2,9 @@ extern crate criterion; use crate::arithmetic::best_fft; -use crate::pasta::Fp; use group::ff::Field; use halo2_proofs::*; +use halo2curves::pasta::Fp; use criterion::{BenchmarkId, Criterion}; use rand_core::OsRng; diff --git a/halo2_proofs/benches/hashtocurve.rs b/halo2_proofs/benches/hashtocurve.rs index fbbd603ae6..a3805f3b9e 100644 --- a/halo2_proofs/benches/hashtocurve.rs +++ b/halo2_proofs/benches/hashtocurve.rs @@ -3,7 +3,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; use halo2_proofs::arithmetic::CurveExt; -use halo2_proofs::pasta::{pallas, vesta}; +use halo2curves::pasta::{pallas, vesta}; fn criterion_benchmark(c: &mut Criterion) { bench_hash_to_curve(c); diff --git a/halo2_proofs/benches/plonk.rs b/halo2_proofs/benches/plonk.rs index 9cc167ecc8..a679961463 100644 --- a/halo2_proofs/benches/plonk.rs +++ b/halo2_proofs/benches/plonk.rs @@ -4,12 +4,24 @@ extern crate criterion; use group::ff::Field; use halo2_proofs::arithmetic::FieldExt; use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; -use halo2_proofs::pasta::{EqAffine, Fp}; use halo2_proofs::plonk::*; -use halo2_proofs::poly::{commitment::Params, Rotation}; +use halo2_proofs::poly::{commitment::ParamsProver, Rotation}; use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite, Challenge255}; +use halo2curves::pasta::{EqAffine, Fp}; use rand_core::OsRng; +use halo2_proofs::{ + poly::{ + ipa::{ + commitment::{IPACommitmentScheme, ParamsIPA}, + multiopen::ProverIPA, + strategy::SingleStrategy, + }, + VerificationStrategy, + }, + transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}, +}; + use std::marker::PhantomData; use criterion::{BenchmarkId, Criterion}; @@ -253,8 +265,8 @@ fn criterion_benchmark(c: &mut Criterion) { } } - fn keygen(k: u32) -> (Params, ProvingKey) { - let params: Params = Params::new(k); + fn keygen(k: u32) -> (ParamsIPA, ProvingKey) { + let params: ParamsIPA = ParamsIPA::new(k); let empty_circuit: MyCircuit = MyCircuit { a: Value::unknown(), k, @@ -264,7 +276,7 @@ fn criterion_benchmark(c: &mut Criterion) { (params, pk) } - fn prover(k: u32, params: &Params, pk: &ProvingKey) -> Vec { + fn prover(k: u32, params: &ParamsIPA, pk: &ProvingKey) -> Vec { let rng = OsRng; let circuit: MyCircuit = MyCircuit { @@ -272,14 +284,21 @@ fn criterion_benchmark(c: &mut Criterion) { k, }; - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - create_proof(params, pk, &[circuit], &[&[]], rng, &mut transcript) - .expect("proof generation should not fail"); + let mut transcript = Blake2bWrite::<_, _, Challenge255>::init(vec![]); + create_proof::, ProverIPA, _, _, _, _>( + params, + pk, + &[circuit], + &[&[]], + rng, + &mut transcript, + ) + .expect("proof generation should not fail"); transcript.finalize() } - fn verifier(params: &Params, vk: &VerifyingKey, proof: &[u8]) { - let strategy = SingleVerifier::new(params); + fn verifier(params: &ParamsIPA, vk: &VerifyingKey, proof: &[u8]) { + let strategy = SingleStrategy::new(params); let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(proof); assert!(verify_proof(params, vk, strategy, &[&[]], &mut transcript).is_ok()); } diff --git a/halo2_proofs/examples/circuit-layout.rs b/halo2_proofs/examples/circuit-layout.rs index 539718733d..beb99502bd 100644 --- a/halo2_proofs/examples/circuit-layout.rs +++ b/halo2_proofs/examples/circuit-layout.rs @@ -2,10 +2,10 @@ use ff::Field; use halo2_proofs::{ arithmetic::FieldExt, circuit::{Cell, Layouter, Region, SimpleFloorPlanner, Value}, - pasta::Fp, plonk::{Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, TableColumn}, poly::Rotation, }; +use halo2curves::pasta::Fp; use rand_core::OsRng; use std::marker::PhantomData; @@ -204,7 +204,7 @@ impl Circuit for MyCircuit { * ... ... ... 0 * ] */ - meta.lookup(|meta| { + meta.lookup("lookup", |meta| { let a_ = meta.query_any(a, Rotation::cur()); vec![(a_, sl)] }); diff --git a/halo2_proofs/examples/cost-model.rs b/halo2_proofs/examples/cost-model.rs index bca953e797..100f047fac 100644 --- a/halo2_proofs/examples/cost-model.rs +++ b/halo2_proofs/examples/cost-model.rs @@ -8,7 +8,8 @@ use std::{ use ff::Field; use group::{Curve, Group}; use gumdrop::Options; -use halo2_proofs::{arithmetic::best_multiexp, pasta::pallas}; +use halo2_proofs::arithmetic::best_multiexp; +use halo2curves::pasta::pallas; struct Estimator { /// Scalars for estimating multiexp performance. diff --git a/halo2_proofs/examples/simple-example.rs b/halo2_proofs/examples/simple-example.rs index 5691ba60d0..c5e7a9f282 100644 --- a/halo2_proofs/examples/simple-example.rs +++ b/halo2_proofs/examples/simple-example.rs @@ -303,7 +303,8 @@ impl Circuit for MyCircuit { // ANCHOR_END: circuit fn main() { - use halo2_proofs::{dev::MockProver, pasta::Fp}; + use halo2_proofs::dev::MockProver; + use halo2curves::pasta::Fp; // ANCHOR: test-circuit // The number of rows in our circuit cannot exceed 2^k. Since our example diff --git a/halo2_proofs/examples/two-chip.rs b/halo2_proofs/examples/two-chip.rs index f5871c2c8a..61d40f93ca 100644 --- a/halo2_proofs/examples/two-chip.rs +++ b/halo2_proofs/examples/two-chip.rs @@ -497,7 +497,8 @@ impl Circuit for MyCircuit { #[allow(clippy::many_single_char_names)] fn main() { use group::ff::Field; - use halo2_proofs::{dev::MockProver, pasta::Fp}; + use halo2_proofs::dev::MockProver; + use halo2curves::pasta::Fp; use rand_core::OsRng; // ANCHOR: test-circuit diff --git a/halo2_proofs/src/arithmetic.rs b/halo2_proofs/src/arithmetic.rs index f2afdc0265..69b63502bf 100644 --- a/halo2_proofs/src/arithmetic.rs +++ b/halo2_proofs/src/arithmetic.rs @@ -5,10 +5,10 @@ use super::multicore; pub use ff::Field; use group::{ ff::{BatchInvert, PrimeField}, - Group as _, + Curve, Group as _, }; -pub use pasta_curves::arithmetic::*; +pub use halo2curves::{CurveAffine, CurveExt, FieldExt, Group}; fn multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: &mut C::Curve) { let coeffs: Vec<_> = coeffs.iter().map(|a| a.to_repr()).collect(); @@ -273,12 +273,59 @@ pub fn recursive_butterfly_arithmetic( } } +/// Convert coefficient bases group elements to lagrange basis by inverse FFT. +pub fn g_to_lagrange(g_projective: Vec, k: u32) -> Vec { + let n_inv = C::Scalar::TWO_INV.pow_vartime(&[k as u64, 0, 0, 0]); + let mut omega_inv = C::Scalar::ROOT_OF_UNITY_INV; + for _ in k..C::Scalar::S { + omega_inv = omega_inv.square(); + } + + let mut g_lagrange_projective = g_projective; + best_fft(&mut g_lagrange_projective, omega_inv, k); + parallelize(&mut g_lagrange_projective, |g, _| { + for g in g.iter_mut() { + *g *= n_inv; + } + }); + + let mut g_lagrange = vec![C::identity(); 1 << k]; + parallelize(&mut g_lagrange, |g_lagrange, starts| { + C::Curve::batch_normalize( + &g_lagrange_projective[starts..(starts + g_lagrange.len())], + g_lagrange, + ); + }); + + g_lagrange +} + /// This evaluates a provided polynomial (in coefficient form) at `point`. pub fn eval_polynomial(poly: &[F], point: F) -> F { - // TODO: parallelize? - poly.iter() - .rev() - .fold(F::zero(), |acc, coeff| acc * point + coeff) + fn evaluate(poly: &[F], point: F) -> F { + poly.iter() + .rev() + .fold(F::zero(), |acc, coeff| acc * point + coeff) + } + let n = poly.len(); + let num_threads = multicore::current_num_threads(); + if n * 2 < num_threads { + evaluate(poly, point) + } else { + let chunk_size = (n + num_threads - 1) / 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() + { + scope.spawn(move |_| { + let start = chunk_idx * chunk_size; + out[0] = evaluate(poly, point) * point.pow_vartime(&[start as u64, 0, 0, 0]); + }); + } + }); + parts.iter().fold(F::zero(), |acc, coeff| acc + coeff) + } } /// This computes the inner product of two vectors `a` and `b`. @@ -359,7 +406,7 @@ pub fn lagrange_interpolate(points: &[F], evals: &[F]) -> Vec { assert_eq!(points.len(), evals.len()); if points.len() == 1 { // Constant polynomial - return vec![evals[0]]; + vec![evals[0]] } else { let mut denoms = Vec::with_capacity(points.len()); for (j, x_j) in points.iter().enumerate() { @@ -410,11 +457,35 @@ 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) + } + let n = roots.len(); + let num_threads = multicore::current_num_threads(); + if n * 2 < num_threads { + evaluate(roots, z) + } else { + let chunk_size = (n + num_threads - 1) / 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) + } +} + +pub(crate) fn powers(base: F) -> impl Iterator { + std::iter::successors(Some(F::one()), move |power| Some(base * power)) +} + #[cfg(test)] use rand_core::OsRng; #[cfg(test)] -use crate::pasta::Fp; +use crate::halo2curves::pasta::Fp; #[test] fn test_lagrange_interpolate() { diff --git a/halo2_proofs/src/circuit/floor_planner/single_pass.rs b/halo2_proofs/src/circuit/floor_planner/single_pass.rs index d00c3cd0f8..33a6605206 100644 --- a/halo2_proofs/src/circuit/floor_planner/single_pass.rs +++ b/halo2_proofs/src/circuit/floor_planner/single_pass.rs @@ -452,7 +452,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> TableLayouter #[cfg(test)] mod tests { - use pasta_curves::vesta; + use halo2curves::pasta::vesta; use super::SimpleFloorPlanner; use crate::{ diff --git a/halo2_proofs/src/circuit/floor_planner/v1.rs b/halo2_proofs/src/circuit/floor_planner/v1.rs index a36cb4a2cb..333d46278a 100644 --- a/halo2_proofs/src/circuit/floor_planner/v1.rs +++ b/halo2_proofs/src/circuit/floor_planner/v1.rs @@ -488,7 +488,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r #[cfg(test)] mod tests { - use pasta_curves::vesta; + use halo2curves::pasta::vesta; use crate::{ dev::MockProver, diff --git a/halo2_proofs/src/circuit/value.rs b/halo2_proofs/src/circuit/value.rs index 50b30c2676..e6ae26cd1b 100644 --- a/halo2_proofs/src/circuit/value.rs +++ b/halo2_proofs/src/circuit/value.rs @@ -639,7 +639,7 @@ impl Value { /// /// If you have a `Value`, convert it to `Value>` first: /// ``` - /// # use pasta_curves::pallas::Base as F; + /// # use halo2curves::pasta::pallas::Base as F; /// use halo2_proofs::{circuit::Value, plonk::Assigned}; /// /// let v = Value::known(F::from(2)); diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index 2ac33cc44b..68d211cde7 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -5,6 +5,7 @@ use std::collections::HashSet; use std::fmt; use std::iter; use std::ops::{Add, Mul, Neg, Range}; +use std::time::{Duration, Instant}; use ff::Field; @@ -18,6 +19,12 @@ use crate::{ }, poly::Rotation, }; +use rayon::{ + iter::{ + IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, + }, + slice::ParallelSliceMut, +}; pub mod metadata; mod util; @@ -49,9 +56,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 `Vec` so that if any cells + /// The cells assigned in this region. We store this as a `HashMap` with count so that if any cells /// are double-assigned, they will be visibly darker. - cells: Vec<(Column, usize)>, + cells: HashMap<(Column, usize), usize>, } impl Region { @@ -170,10 +177,10 @@ impl Mul for Value { /// arithmetic::FieldExt, /// circuit::{Layouter, SimpleFloorPlanner, Value}, /// dev::{FailureLocation, MockProver, VerifyFailure}, -/// pasta::Fp, /// plonk::{Advice, Any, Circuit, Column, ConstraintSystem, Error, Selector}, /// poly::Rotation, /// }; +/// use halo2curves::pasta::Fp; /// const K: u32 = 5; /// /// #[derive(Copy, Clone)] @@ -307,7 +314,7 @@ impl Assignment for MockProver { columns: HashSet::default(), rows: None, enabled_selectors: HashMap::default(), - cells: vec![], + cells: HashMap::default(), }); } @@ -374,7 +381,11 @@ impl Assignment for MockProver { if let Some(region) = self.current_region.as_mut() { region.update_extent(column.into(), row); - region.cells.push((column.into(), row)); + region + .cells + .entry((column.into(), row)) + .and_modify(|count| *count += 1) + .or_default(); } *self @@ -406,7 +417,11 @@ impl Assignment for MockProver { if let Some(region) = self.current_region.as_mut() { region.update_extent(column.into(), row); - region.cells.push((column.into(), row)); + region + .cells + .entry((column.into(), row)) + .and_modify(|count| *count += 1) + .or_default(); } *self @@ -550,8 +565,32 @@ impl MockProver { /// 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> { + self.verify_at_rows(self.usable_rows.clone(), self.usable_rows.clone()) + } + + /// 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` + pub fn verify_at_rows>( + &self, + gate_row_ids: I, + lookup_input_row_ids: I, + ) -> Result<(), Vec> { let n = self.n as i32; + // check all the row ids are valid + for row_id in gate_row_ids.clone() { + if !self.usable_rows.contains(&row_id) { + panic!("invalid gate row id {}", row_id) + } + } + for row_id in lookup_input_row_ids.clone() { + if !self.usable_rows.contains(&row_id) { + panic!("invalid lookup row id {}", row_id) + } + } + // Check that within each region, all cells used in instantiated gates have been // assigned to. let selector_errors = self.regions.iter().enumerate().flat_map(|(r_i, r)| { @@ -579,7 +618,7 @@ impl MockProver { let cell_row = ((gate_row + n + cell.rotation.0) % n) as usize; // Check that it was assigned! - if r.cells.contains(&(cell.column, cell_row)) { + if r.cells.contains_key(&(cell.column, cell_row)) { None } else { Some(VerifyFailure::CellNotAssigned { @@ -603,10 +642,16 @@ impl MockProver { .iter() .enumerate() .flat_map(|(gate_index, gate)| { - // We iterate from n..2n so we can just reduce to handle wrapping. - (n..(2 * n)).flat_map(move |row| { + let blinding_rows = + (self.n as usize - (self.cs.blinding_factors() + 1))..(self.n as usize); + (gate_row_ids + .clone() + .into_iter() + .chain(blinding_rows.into_iter())) + .flat_map(move |row| { + let row = row as i32 + n; gate.polynomials().iter().enumerate().filter_map( - move |(poly_index, poly)| match poly.evaluate( + move |(poly_index, poly)| match poly.evaluate_lazy( &|scalar| Value::Real(scalar), &|_| panic!("virtual selectors are removed during optimization"), &util::load(n, row, &self.cs.fixed_queries, &self.fixed), @@ -621,6 +666,7 @@ impl MockProver { &|a, b| a + b, &|a, b| a * b, &|a, scalar| a * scalar, + &Value::Real(F::zero()), ) { Value::Real(x) if x.is_zero_vartime() => None, Value::Real(_) => Some(VerifyFailure::ConstraintNotSatisfied { @@ -662,6 +708,8 @@ impl MockProver { }) }); + let mut cached_table = Vec::new(); + let mut cached_table_identifier = Vec::new(); // Check that all lookups exist in their respective tables. let lookup_errors = self.cs @@ -670,7 +718,7 @@ impl MockProver { .enumerate() .flat_map(|(lookup_index, lookup)| { let load = |expression: &Expression, row| { - expression.evaluate( + expression.evaluate_lazy( &|scalar| Value::Real(scalar), &|_| panic!("virtual selectors are removed during optimization"), &|query| { @@ -702,6 +750,7 @@ impl MockProver { &|a, b| a + b, &|a, b| a * b, &|a, scalar| a * scalar, + &Value::Real(F::zero()), ) }; @@ -720,30 +769,40 @@ impl MockProver { .map(move |c| load(c, self.usable_rows.end - 1)) .collect(); - // In the real prover, the lookup expressions are never enforced on - // unusable rows, due to the (1 - (l_last(X) + l_blind(X))) term. - let mut table: Vec> = self - .usable_rows - .clone() - .filter_map(|table_row| { - let t = lookup - .table_expressions - .iter() - .map(move |c| load(c, table_row)) - .collect(); - - if t != fill_row { - Some(t) - } else { - None - } - }) - .collect(); - table.sort_unstable(); + let table_identifier = lookup + .table_expressions + .iter() + .map(Expression::identifier) + .collect::>(); + if table_identifier != cached_table_identifier { + cached_table_identifier = table_identifier; + + // In the real prover, the lookup expressions are never enforced on + // unusable rows, due to the (1 - (l_last(X) + l_blind(X))) term. + cached_table = self + .usable_rows + .clone() + .filter_map(|table_row| { + let t = lookup + .table_expressions + .iter() + .map(move |c| load(c, table_row)) + .collect(); + + if t != fill_row { + Some(t) + } else { + None + } + }) + .collect(); + cached_table.sort_unstable(); + } + let table = &cached_table; - let mut inputs: Vec<(Vec<_>, usize)> = self - .usable_rows + let mut inputs: Vec<(Vec<_>, usize)> = lookup_input_row_ids .clone() + .into_iter() .filter_map(|input_row| { let t = lookup .input_expressions @@ -772,6 +831,7 @@ impl MockProver { assert!(table.binary_search(input).is_err()); Some(VerifyFailure::Lookup { + name: lookup.name, lookup_index, location: FailureLocation::find_expressions( &self.cs, @@ -855,6 +915,365 @@ impl MockProver { } } + /// Returns `Ok(())` if this `MockProver` is satisfied, or a list of errors indicating + /// the reasons that the circuit is not satisfied. + /// Constraints and lookup are checked at `usable_rows`, parallelly. + pub fn verify_par(&self) -> Result<(), Vec> { + self.verify_at_rows_par(self.usable_rows.clone(), self.usable_rows.clone()) + } + + /// 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. + pub fn verify_at_rows_par>( + &self, + gate_row_ids: I, + lookup_input_row_ids: I, + ) -> Result<(), Vec> { + let n = self.n as i32; + + let gate_row_ids = gate_row_ids.collect::>(); + let lookup_input_row_ids = lookup_input_row_ids.collect::>(); + + // check all the row ids are valid + gate_row_ids.par_iter().for_each(|row_id| { + if !self.usable_rows.contains(row_id) { + panic!("invalid gate row id {}", row_id); + } + }); + lookup_input_row_ids.par_iter().for_each(|row_id| { + if !self.usable_rows.contains(row_id) { + panic!("invalid gate row id {}", row_id); + } + }); + + // Check that within each region, all cells used in instantiated gates have been + // assigned to. + let selector_errors = self.regions.iter().enumerate().flat_map(|(r_i, r)| { + r.enabled_selectors.iter().flat_map(move |(selector, at)| { + // Find the gates enabled by this selector + self.cs + .gates + .iter() + // Assume that if a queried selector is enabled, the user wants to use the + // corresponding gate in some way. + // + // TODO: This will trip up on the reverse case, where leaving a selector + // un-enabled keeps a gate enabled. We could alternatively require that + // every selector is explicitly enabled or disabled on every row? But that + // seems messy and confusing. + .enumerate() + .filter(move |(_, g)| g.queried_selectors().contains(selector)) + .flat_map(move |(gate_index, gate)| { + at.par_iter() + .flat_map(move |selector_row| { + // Selectors are queried with no rotation. + let gate_row = *selector_row as i32; + + gate.queried_cells() + .iter() + .filter_map(move |cell| { + // Determine where this cell should have been assigned. + 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)) { + None + } else { + Some(VerifyFailure::CellNotAssigned { + gate: (gate_index, gate.name()).into(), + region: (r_i, r.name.clone()).into(), + gate_offset: *selector_row, + column: cell.column, + offset: cell_row as isize + - r.rows.unwrap().0 as isize, + }) + } + }) + .collect::>() + }) + .collect::>() + }) + }) + }); + + // Check that all gates are satisfied for all rows. + let gate_errors = self + .cs + .gates + .iter() + .enumerate() + .flat_map(|(gate_index, gate)| { + let blinding_rows = + (self.n as usize - (self.cs.blinding_factors() + 1))..(self.n as usize); + (gate_row_ids + .clone() + .into_par_iter() + .chain(blinding_rows.into_par_iter())) + .flat_map(move |row| { + let row = row as i32 + n; + gate.polynomials() + .iter() + .enumerate() + .filter_map(move |(poly_index, poly)| { + match poly.evaluate_lazy( + &|scalar| Value::Real(scalar), + &|_| panic!("virtual selectors are removed during optimization"), + &util::load(n, row, &self.cs.fixed_queries, &self.fixed), + &util::load(n, row, &self.cs.advice_queries, &self.advice), + &util::load_instance( + n, + row, + &self.cs.instance_queries, + &self.instance, + ), + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, scalar| a * scalar, + &Value::Real(F::zero()), + ) { + Value::Real(x) if x.is_zero_vartime() => None, + Value::Real(_) => Some(VerifyFailure::ConstraintNotSatisfied { + constraint: ( + (gate_index, gate.name()).into(), + poly_index, + gate.constraint_name(poly_index), + ) + .into(), + location: FailureLocation::find_expressions( + &self.cs, + &self.regions, + (row - n) as usize, + Some(poly).into_iter(), + ), + cell_values: util::cell_values( + gate, + poly, + &util::load(n, row, &self.cs.fixed_queries, &self.fixed), + &util::load(n, row, &self.cs.advice_queries, &self.advice), + &util::load_instance( + n, + row, + &self.cs.instance_queries, + &self.instance, + ), + ), + }), + Value::Poison => Some(VerifyFailure::ConstraintPoisoned { + constraint: ( + (gate_index, gate.name()).into(), + poly_index, + gate.constraint_name(poly_index), + ) + .into(), + }), + } + }) + .collect::>() + }) + .collect::>() + }); + + let mut cached_table = Vec::new(); + let mut cached_table_identifier = Vec::new(); + // Check that all lookups exist in their respective tables. + let lookup_errors = + self.cs + .lookups + .iter() + .enumerate() + .flat_map(|(lookup_index, lookup)| { + let load = |expression: &Expression, row| { + expression.evaluate_lazy( + &|scalar| Value::Real(scalar), + &|_| panic!("virtual selectors are removed during optimization"), + &|query| { + self.fixed[query.column_index] + [(row as i32 + n + query.rotation.0) as usize % n as usize] + .into() + }, + &|query| { + self.advice[query.column_index] + [(row as i32 + n + query.rotation.0) as usize % n as usize] + .into() + }, + &|query| { + Value::Real( + self.instance[query.column_index] + [(row as i32 + n + query.rotation.0) as usize % n as usize], + ) + }, + &|a| -a, + &|a, b| a + b, + &|a, b| a * b, + &|a, scalar| a * scalar, + &Value::Real(F::zero()), + ) + }; + + assert!(lookup.table_expressions.len() == lookup.input_expressions.len()); + assert!(self.usable_rows.end > 0); + + // We optimize on the basis that the table might have been filled so that the last + // usable row now has the fill contents (it doesn't matter if there was no filling). + // Note that this "fill row" necessarily exists in the table, and we use that fact to + // slightly simplify the optimization: we're only trying to check that all input rows + // are contained in the table, and so we can safely just drop input rows that + // match the fill row. + let fill_row: Vec<_> = lookup + .table_expressions + .iter() + .map(move |c| load(c, self.usable_rows.end - 1)) + .collect(); + + let table_identifier = lookup + .table_expressions + .iter() + .map(Expression::identifier) + .collect::>(); + if table_identifier != cached_table_identifier { + cached_table_identifier = table_identifier; + + // In the real prover, the lookup expressions are never enforced on + // unusable rows, due to the (1 - (l_last(X) + l_blind(X))) term. + cached_table = self + .usable_rows + .clone() + .into_par_iter() + .filter_map(|table_row| { + let t = lookup + .table_expressions + .iter() + .map(move |c| load(c, table_row)) + .collect(); + + if t != fill_row { + Some(t) + } else { + None + } + }) + .collect(); + cached_table.par_sort_unstable(); + } + let table = &cached_table; + + let mut inputs: Vec<(Vec<_>, usize)> = lookup_input_row_ids + .clone() + .into_par_iter() + .filter_map(|input_row| { + let t = lookup + .input_expressions + .iter() + .map(move |c| load(c, input_row)) + .collect(); + + if t != fill_row { + // Also keep track of the original input row, since we're going to sort. + Some((t, input_row)) + } else { + None + } + }) + .collect(); + inputs.par_sort_unstable(); + + inputs + .par_iter() + .filter_map(move |(input, input_row)| { + if table.binary_search(input).is_err() { + Some(VerifyFailure::Lookup { + name: lookup.name, + lookup_index, + location: FailureLocation::find_expressions( + &self.cs, + &self.regions, + *input_row, + lookup.input_expressions.iter(), + ), + }) + } else { + None + } + }) + .collect::>() + }); + + // Check that permutations preserve the original values of the cells. + let perm_errors = { + // Original values of columns involved in the permutation. + let original = |column, row| { + self.cs + .permutation + .get_columns() + .get(column) + .map(|c: &Column| match c.column_type() { + Any::Advice => self.advice[c.index()][row], + Any::Fixed => self.fixed[c.index()][row], + Any::Instance => CellValue::Assigned(self.instance[c.index()][row]), + }) + .unwrap() + }; + + // 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::>() + }) + }; + + let mut errors: Vec<_> = iter::empty() + .chain(selector_errors) + .chain(gate_errors) + .chain(lookup_errors) + .chain(perm_errors) + .collect(); + if errors.is_empty() { + Ok(()) + } else { + // Remove any duplicate `ConstraintPoisoned` errors (we check all unavailable + // rows in case the trigger is row-specific, but the error message only points + // at the constraint). + errors.dedup_by(|a, b| match (a, b) { + ( + a @ VerifyFailure::ConstraintPoisoned { .. }, + b @ VerifyFailure::ConstraintPoisoned { .. }, + ) => a == b, + _ => false, + }); + Err(errors) + } + } + /// Panics if the circuit being checked by this `MockProver` is not satisfied. /// /// Any verification failures will be pretty-printed to stderr before the function @@ -877,7 +1296,7 @@ impl MockProver { #[cfg(test)] mod tests { - use pasta_curves::Fp; + use halo2curves::pasta::Fp; use super::{FailureLocation, MockProver, VerifyFailure}; use crate::{ @@ -984,7 +1403,7 @@ mod tests { let q = meta.complex_selector(); let table = meta.lookup_table_column(); - meta.lookup(|cells| { + meta.lookup("lookup", |cells| { let a = cells.query_advice(a, Rotation::cur()); let q = cells.query_selector(q); @@ -1081,6 +1500,7 @@ mod tests { assert_eq!( prover.verify(), Err(vec![VerifyFailure::Lookup { + name: "lookup", lookup_index: 0, location: FailureLocation::InRegion { region: (2, "Faulty synthesis").into(), diff --git a/halo2_proofs/src/dev/failure.rs b/halo2_proofs/src/dev/failure.rs index e4d53ca723..e98d964696 100644 --- a/halo2_proofs/src/dev/failure.rs +++ b/halo2_proofs/src/dev/failure.rs @@ -3,7 +3,7 @@ use std::fmt; use std::iter; use group::ff::Field; -use pasta_curves::arithmetic::FieldExt; +use halo2curves::FieldExt; use super::{ metadata, @@ -145,6 +145,8 @@ 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, /// 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`. @@ -207,9 +209,16 @@ impl fmt::Display for VerifyFailure { ) } Self::Lookup { + name, lookup_index, location, - } => write!(f, "Lookup {} is not satisfied {}", lookup_index, location), + } => { + write!( + f, + "Lookup {}(index: {}) is not satisfied {}", + name, lookup_index, location + ) + } Self::Permutation { column, location } => { write!( f, @@ -369,6 +378,7 @@ fn render_constraint_not_satisfied( /// ``` fn render_lookup( prover: &MockProver, + name: &str, lookup_index: usize, location: &FailureLocation, ) { @@ -435,7 +445,7 @@ fn render_lookup( eprintln!(")"); eprintln!(); - eprintln!(" Lookup inputs:"); + eprintln!(" Lookup '{}' inputs:", name); for (i, input) in lookup.input_expressions.iter().enumerate() { // Fetch the cell values (since we don't store them in VerifyFailure::Lookup). let cell_values = input.evaluate( @@ -491,7 +501,7 @@ fn render_lookup( eprintln!(" ^"); emitter::render_cell_layout(" | ", location, &columns, &layout, |_, rotation| { if rotation == 0 { - eprint!(" <--{{ Lookup inputs queried here"); + eprint!(" <--{{ Lookup '{}' inputs queried here", name); } }); @@ -530,9 +540,10 @@ impl VerifyFailure { render_constraint_not_satisfied(&prover.cs.gates, constraint, location, cell_values) } Self::Lookup { + name, lookup_index, location, - } => render_lookup(prover, *lookup_index, location), + } => render_lookup(prover, name, *lookup_index, location), _ => eprintln!("{}", self), } } diff --git a/halo2_proofs/src/dev/gates.rs b/halo2_proofs/src/dev/gates.rs index fc624e96ba..b988b72346 100644 --- a/halo2_proofs/src/dev/gates.rs +++ b/halo2_proofs/src/dev/gates.rs @@ -35,7 +35,7 @@ struct Gate { /// plonk::{Circuit, ConstraintSystem, Error}, /// poly::Rotation, /// }; -/// use pasta_curves::pallas; +/// use halo2curves::pasta::pallas; /// /// #[derive(Copy, Clone)] /// struct MyConfig {} diff --git a/halo2_proofs/src/dev/util.rs b/halo2_proofs/src/dev/util.rs index f8c752879b..7088c7e304 100644 --- a/halo2_proofs/src/dev/util.rs +++ b/halo2_proofs/src/dev/util.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use group::ff::Field; -use pasta_curves::arithmetic::FieldExt; +use halo2curves::FieldExt; use super::{metadata, CellValue, Value}; use crate::{ diff --git a/halo2_proofs/src/helpers.rs b/halo2_proofs/src/helpers.rs index 2158a3691d..297fd7b9ca 100644 --- a/halo2_proofs/src/helpers.rs +++ b/halo2_proofs/src/helpers.rs @@ -1,6 +1,6 @@ use std::io; -use pasta_curves::arithmetic::CurveAffine; +use halo2curves::CurveAffine; pub(crate) trait CurveRead: CurveAffine { /// Reads a compressed element from the buffer and attempts to parse it diff --git a/halo2_proofs/src/lib.rs b/halo2_proofs/src/lib.rs index 81f7a8ab2c..c84e482675 100644 --- a/halo2_proofs/src/lib.rs +++ b/halo2_proofs/src/lib.rs @@ -23,10 +23,11 @@ #![deny(unsafe_code)] // Remove this once we update pasta_curves #![allow(unused_imports)] +#![allow(clippy::derive_partial_eq_without_eq)] pub mod arithmetic; pub mod circuit; -pub use pasta_curves as pasta; +pub use halo2curves; mod multicore; pub mod plonk; pub mod poly; diff --git a/halo2_proofs/src/plonk.rs b/halo2_proofs/src/plonk.rs index 5a34e38e02..f9a6587af6 100644 --- a/halo2_proofs/src/plonk.rs +++ b/halo2_proofs/src/plonk.rs @@ -19,6 +19,7 @@ use crate::transcript::{ChallengeScalar, EncodedChallenge, Transcript}; mod assigned; mod circuit; mod error; +mod evaluation; mod keygen; mod lookup; pub(crate) mod permutation; @@ -34,6 +35,7 @@ pub use keygen::*; pub use prover::*; pub use verifier::*; +use evaluation::Evaluator; use std::io; /// This is a verifying key which allows for the verification of proofs for a @@ -108,6 +110,21 @@ impl VerifyingKey { cs: self.cs.pinned(), } } + + /// Returns commitments of fixed polynomials + pub fn fixed_commitments(&self) -> &Vec { + &self.fixed_commitments + } + + /// Returns `VerifyingKey` of permutation + pub fn permutation(&self) -> &permutation::VerifyingKey { + &self.permutation + } + + /// Returns `ConstraintSystem` + pub fn cs(&self) -> &ConstraintSystem { + &self.cs + } } /// Minimal representation of a verification key that can be used to identify @@ -128,12 +145,13 @@ pub struct PinnedVerificationKey<'a, C: CurveAffine> { pub struct ProvingKey { vk: VerifyingKey, l0: Polynomial, - l_blind: Polynomial, l_last: Polynomial, + l_active_row: Polynomial, fixed_values: Vec>, fixed_polys: Vec>, fixed_cosets: Vec>, permutation: permutation::ProvingKey, + ev: Evaluator, } impl ProvingKey { diff --git a/halo2_proofs/src/plonk/assigned.rs b/halo2_proofs/src/plonk/assigned.rs index 09105eee38..7524291e4c 100644 --- a/halo2_proofs/src/plonk/assigned.rs +++ b/halo2_proofs/src/plonk/assigned.rs @@ -367,7 +367,7 @@ impl Assigned { #[cfg(test)] mod tests { - use pasta_curves::Fp; + use halo2curves::pasta::Fp; use super::Assigned; // We use (numerator, denominator) in the comments below to denote a rational. @@ -451,7 +451,7 @@ mod proptests { }; use group::ff::Field; - use pasta_curves::{arithmetic::FieldExt, Fp}; + use halo2curves::{pasta::Fp, FieldExt}; use proptest::{collection::vec, prelude::*, sample::select}; use super::Assigned; diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index bd98b99e5c..6dbd4a5455 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -33,7 +33,8 @@ impl Column { Column { index, column_type } } - pub(crate) fn index(&self) -> usize { + /// Index of this column. + pub fn index(&self) -> usize { self.index } @@ -206,7 +207,7 @@ impl TryFrom> for Column { /// Selectors can be used to conditionally enable (portions of) gates: /// ``` /// use halo2_proofs::poly::Rotation; -/// # use halo2_proofs::pasta::Fp; +/// # use halo2curves::pasta::Fp; /// # use halo2_proofs::plonk::ConstraintSystem; /// /// # let mut meta = ConstraintSystem::::default(); @@ -280,6 +281,18 @@ pub struct FixedQuery { pub(crate) rotation: Rotation, } +impl FixedQuery { + /// Column index + pub fn column_index(&self) -> usize { + self.column_index + } + + /// Rotation of this query + pub fn rotation(&self) -> Rotation { + self.rotation + } +} + /// Query of advice column at a certain relative location #[derive(Copy, Clone, Debug)] pub struct AdviceQuery { @@ -291,6 +304,18 @@ pub struct AdviceQuery { pub(crate) rotation: Rotation, } +impl AdviceQuery { + /// Column index + pub fn column_index(&self) -> usize { + self.column_index + } + + /// Rotation of this query + pub fn rotation(&self) -> Rotation { + self.rotation + } +} + /// Query of instance column at a certain relative location #[derive(Copy, Clone, Debug)] pub struct InstanceQuery { @@ -302,6 +327,18 @@ pub struct InstanceQuery { pub(crate) rotation: Rotation, } +impl InstanceQuery { + /// Column index + pub fn column_index(&self) -> usize { + self.column_index + } + + /// Rotation of this query + pub fn rotation(&self) -> Rotation { + self.rotation + } +} + /// A fixed column of a lookup table. /// /// A lookup table can be loaded into this column via [`Layouter::assign_table`]. Columns @@ -605,14 +642,163 @@ impl Expression { } } + /// Evaluate the polynomial lazily using the provided closures to perform the + /// operations. + pub fn evaluate_lazy( + &self, + constant: &impl Fn(F) -> T, + selector_column: &impl Fn(Selector) -> T, + fixed_column: &impl Fn(FixedQuery) -> T, + advice_column: &impl Fn(AdviceQuery) -> T, + instance_column: &impl Fn(InstanceQuery) -> T, + negated: &impl Fn(T) -> T, + sum: &impl Fn(T, T) -> T, + product: &impl Fn(T, T) -> T, + scaled: &impl Fn(T, F) -> T, + zero: &T, + ) -> T { + match self { + Expression::Constant(scalar) => constant(*scalar), + Expression::Selector(selector) => selector_column(*selector), + Expression::Fixed(query) => fixed_column(*query), + Expression::Advice(query) => advice_column(*query), + Expression::Instance(query) => instance_column(*query), + Expression::Negated(a) => { + let a = a.evaluate_lazy( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + negated, + sum, + product, + scaled, + zero, + ); + negated(a) + } + Expression::Sum(a, b) => { + let a = a.evaluate_lazy( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + negated, + sum, + product, + scaled, + zero, + ); + let b = b.evaluate_lazy( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + negated, + sum, + product, + scaled, + zero, + ); + sum(a, b) + } + Expression::Product(a, b) => { + let (a, b) = if a.complexity() <= b.complexity() { + (a, b) + } else { + (b, a) + }; + let a = a.evaluate_lazy( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + negated, + sum, + product, + scaled, + zero, + ); + + if a == *zero { + a + } else { + let b = b.evaluate_lazy( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + negated, + sum, + product, + scaled, + zero, + ); + product(a, b) + } + } + Expression::Scaled(a, f) => { + let a = a.evaluate_lazy( + constant, + selector_column, + fixed_column, + advice_column, + instance_column, + negated, + sum, + product, + scaled, + zero, + ); + scaled(a, *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 { + match self { + Expression::Constant(scalar) => format!("{:?}", scalar), + Expression::Selector(selector) => format!("selector[{}]", selector.0), + Expression::Fixed(query) => { + format!("fixed[{}][{}]", query.column_index, query.rotation.0) + } + Expression::Advice(query) => { + format!("advice[{}][{}]", query.column_index, query.rotation.0) + } + Expression::Instance(query) => { + format!("instance[{}][{}]", query.column_index, query.rotation.0) + } + Expression::Negated(a) => { + format!("(-{})", a.identifier()) + } + Expression::Sum(a, b) => { + format!("({}+{})", a.identifier(), b.identifier()) + } + Expression::Product(a, b) => { + format!("({}*{})", a.identifier(), b.identifier()) + } + Expression::Scaled(a, f) => { + format!("{}*{:?}", a.identifier(), f) + } + } + } + /// Compute the degree of this polynomial pub fn degree(&self) -> usize { match self { Expression::Constant(_) => 0, Expression::Selector(_) => 1, - Expression::Fixed { .. } => 1, - Expression::Advice { .. } => 1, - Expression::Instance { .. } => 1, + Expression::Fixed(_) => 1, + Expression::Advice(_) => 1, + Expression::Instance(_) => 1, Expression::Negated(poly) => poly.degree(), Expression::Sum(a, b) => max(a.degree(), b.degree()), Expression::Product(a, b) => a.degree() + b.degree(), @@ -620,6 +806,21 @@ impl Expression { } } + /// Approximate the computational complexity of this expression. + pub fn complexity(&self) -> usize { + match self { + Expression::Constant(_) => 0, + Expression::Selector(_) => 1, + Expression::Fixed(_) => 1, + Expression::Advice(_) => 1, + Expression::Instance(_) => 1, + Expression::Negated(poly) => poly.complexity() + 5, + Expression::Sum(a, b) => a.complexity() + b.complexity() + 15, + Expression::Product(a, b) => a.complexity() + b.complexity() + 30, + Expression::Scaled(poly, _) => poly.complexity() + 30, + } + } + /// Square this expression. pub fn square(self) -> Self { self.clone() * self @@ -766,7 +967,7 @@ pub(crate) struct PointIndex(pub usize); /// A "virtual cell" is a PLONK cell that has been queried at a particular relative offset /// within a custom gate. #[derive(Clone, Debug)] -pub(crate) struct VirtualCell { +pub struct VirtualCell { pub(crate) column: Column, pub(crate) rotation: Rotation, } @@ -810,7 +1011,8 @@ impl From> for Vec> { /// A set of polynomial constraints with a common selector. /// /// ``` -/// use halo2_proofs::{pasta::Fp, plonk::{Constraints, Expression}, poly::Rotation}; +/// use halo2_proofs::{plonk::{Constraints, Expression}, poly::Rotation}; +/// use halo2curves::pasta::Fp; /// # use halo2_proofs::plonk::ConstraintSystem; /// /// # let mut meta = ConstraintSystem::::default(); @@ -889,8 +1091,9 @@ impl>, Iter: IntoIterator> IntoIterato } } +/// Gate #[derive(Clone, Debug)] -pub(crate) struct Gate { +pub struct Gate { name: &'static str, constraint_names: Vec<&'static str>, polys: Vec>, @@ -909,7 +1112,8 @@ impl Gate { self.constraint_names[constraint_index] } - pub(crate) fn polynomials(&self) -> &[Expression] { + /// Returns constraints of this gate + pub fn polynomials(&self) -> &[Expression] { &self.polys } @@ -1054,6 +1258,7 @@ impl ConstraintSystem { /// they need to match. pub fn lookup( &mut self, + name: &'static str, table_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression, TableColumn)>, ) -> usize { let mut cells = VirtualCells::new(self); @@ -1072,7 +1277,26 @@ impl ConstraintSystem { let index = self.lookups.len(); - self.lookups.push(lookup::Argument::new(table_map)); + self.lookups.push(lookup::Argument::new(name, table_map)); + + index + } + + /// Add a lookup argument for some input expressions and table expressions. + /// + /// `table_map` returns a map between input expressions and the table expressions + /// they need to match. + pub fn lookup_any( + &mut self, + name: &'static str, + 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 index = self.lookups.len(); + + self.lookups.push(lookup::Argument::new(name, table_map)); index } @@ -1463,6 +1687,56 @@ impl ConstraintSystem { // and the interstitial values.) + 1 // for at least one row } + + /// Returns number of fixed columns + pub fn num_fixed_columns(&self) -> usize { + self.num_fixed_columns + } + + /// Returns number of advice columns + pub fn num_advice_columns(&self) -> usize { + self.num_advice_columns + } + + /// Returns number of instance columns + pub fn num_instance_columns(&self) -> usize { + self.num_instance_columns + } + + /// Returns gates + pub fn gates(&self) -> &Vec> { + &self.gates + } + + /// Returns advice queries + pub fn advice_queries(&self) -> &Vec<(Column, Rotation)> { + &self.advice_queries + } + + /// Returns instance queries + pub fn instance_queries(&self) -> &Vec<(Column, Rotation)> { + &self.instance_queries + } + + /// Returns fixed queries + pub fn fixed_queries(&self) -> &Vec<(Column, Rotation)> { + &self.fixed_queries + } + + /// Returns permutation argument + pub fn permutation(&self) -> &permutation::Argument { + &self.permutation + } + + /// Returns lookup arguments + pub fn lookups(&self) -> &Vec> { + &self.lookups + } + + /// Returns constants + pub fn constants(&self) -> &Vec> { + &self.constants + } } /// Exposes the "virtual cells" that can be queried while creating a custom gate or lookup diff --git a/halo2_proofs/src/plonk/circuit/compress_selectors.rs b/halo2_proofs/src/plonk/circuit/compress_selectors.rs index c3a1485d2a..c141d12d9b 100644 --- a/halo2_proofs/src/plonk/circuit/compress_selectors.rs +++ b/halo2_proofs/src/plonk/circuit/compress_selectors.rs @@ -70,33 +70,30 @@ where // All provided selectors of degree 0 are assumed to be either concrete // selectors or do not appear in a gate. Let's address these first. - selectors = selectors - .into_iter() - .filter(|selector| { - if selector.max_degree == 0 { - // This is a complex selector, or a selector that does not appear in any - // gate constraint. - let expression = allocate_fixed_column(); - - let combination_assignment = selector - .activations - .iter() - .map(|b| if *b { F::one() } else { F::zero() }) - .collect::>(); - let combination_index = combination_assignments.len(); - combination_assignments.push(combination_assignment); - selector_assignments.push(SelectorAssignment { - selector: selector.selector, - combination_index, - expression, - }); + selectors.retain(|selector| { + if selector.max_degree == 0 { + // This is a complex selector, or a selector that does not appear in any + // gate constraint. + let expression = allocate_fixed_column(); + + let combination_assignment = selector + .activations + .iter() + .map(|b| if *b { F::one() } else { F::zero() }) + .collect::>(); + let combination_index = combination_assignments.len(); + combination_assignments.push(combination_assignment); + selector_assignments.push(SelectorAssignment { + selector: selector.selector, + combination_index, + expression, + }); - false - } else { - true - } - }) - .collect(); + false + } else { + true + } + }); // All of the remaining `selectors` are simple. Let's try to combine them. // First, we compute the exclusion matrix that has (j, k) = true if selector @@ -233,7 +230,7 @@ where mod tests { use super::*; use crate::{plonk::FixedQuery, poly::Rotation}; - use pasta_curves::Fp; + use halo2curves::pasta::Fp; use proptest::collection::{vec, SizeRange}; use proptest::prelude::*; diff --git a/halo2_proofs/src/plonk/evaluation.rs b/halo2_proofs/src/plonk/evaluation.rs new file mode 100644 index 0000000000..06c13fe9ba --- /dev/null +++ b/halo2_proofs/src/plonk/evaluation.rs @@ -0,0 +1,771 @@ +use crate::multicore; +use crate::plonk::lookup::prover::Committed; +use crate::plonk::permutation::Argument; +use crate::plonk::{lookup, permutation, Any, ProvingKey}; +use crate::poly::Basis; +use crate::{ + arithmetic::{eval_polynomial, parallelize, CurveAffine, FieldExt}, + poly::{ + commitment::Params, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, + Polynomial, ProverQuery, Rotation, + }, + transcript::{EncodedChallenge, TranscriptWrite}, +}; +use group::prime::PrimeCurve; +use group::{ + ff::{BatchInvert, Field}, + Curve, +}; +use std::any::TypeId; +use std::convert::TryInto; +use std::num::ParseIntError; +use std::slice; +use std::{ + collections::BTreeMap, + iter, + ops::{Index, Mul, MulAssign}, +}; + +use super::{ConstraintSystem, Expression}; + +/// Return the index in the polynomial of size `isize` after rotation `rot`. +fn get_rotation_idx(idx: usize, rot: i32, rot_scale: i32, isize: i32) -> usize { + (((idx as i32) + (rot * rot_scale)).rem_euclid(isize)) as usize +} + +/// Value used in a calculation +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)] +pub enum ValueSource { + /// This is a constant value + Constant(usize), + /// This is an intermediate value + Intermediate(usize), + /// This is a fixed column + Fixed(usize, usize), + /// This is an advice (witness) column + Advice(usize, usize), + /// This is an instance (external) column + Instance(usize, usize), + /// beta + Beta(), + /// gamma + Gamma(), + /// theta + Theta(), + /// y + Y(), + /// Previous value + PreviousValue(), +} + +impl Default for ValueSource { + fn default() -> Self { + ValueSource::Constant(0) + } +} + +impl ValueSource { + /// Get the value for this source + pub fn get( + &self, + rotations: &[usize], + constants: &[F], + intermediates: &[F], + fixed_values: &[Polynomial], + advice_values: &[Polynomial], + instance_values: &[Polynomial], + beta: &F, + gamma: &F, + theta: &F, + y: &F, + previous_value: &F, + ) -> F { + match self { + ValueSource::Constant(idx) => constants[*idx], + ValueSource::Intermediate(idx) => intermediates[*idx], + ValueSource::Fixed(column_index, rotation) => { + fixed_values[*column_index][rotations[*rotation]] + } + ValueSource::Advice(column_index, rotation) => { + advice_values[*column_index][rotations[*rotation]] + } + ValueSource::Instance(column_index, rotation) => { + instance_values[*column_index][rotations[*rotation]] + } + ValueSource::Beta() => *beta, + ValueSource::Gamma() => *gamma, + ValueSource::Theta() => *theta, + ValueSource::Y() => *y, + ValueSource::PreviousValue() => *previous_value, + } + } +} + +/// Calculation +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Calculation { + /// This is an addition + Add(ValueSource, ValueSource), + /// This is a subtraction + Sub(ValueSource, ValueSource), + /// This is a product + Mul(ValueSource, ValueSource), + /// This is a square + Square(ValueSource), + /// This is a double + Double(ValueSource), + /// This is a negation + Negate(ValueSource), + /// This is Horner's rule: `val = a; val = val * c + b[]` + Horner(ValueSource, Vec, ValueSource), + /// This is a simple assignment + Store(ValueSource), +} + +impl Calculation { + /// Get the resulting value of this calculation + pub fn evaluate( + &self, + rotations: &[usize], + constants: &[F], + intermediates: &[F], + fixed_values: &[Polynomial], + advice_values: &[Polynomial], + instance_values: &[Polynomial], + beta: &F, + gamma: &F, + theta: &F, + y: &F, + previous_value: &F, + ) -> F { + let get_value = |value: &ValueSource| { + value.get( + rotations, + constants, + intermediates, + fixed_values, + advice_values, + instance_values, + beta, + gamma, + theta, + y, + previous_value, + ) + }; + match self { + Calculation::Add(a, b) => get_value(a) + get_value(b), + Calculation::Sub(a, b) => get_value(a) - get_value(b), + Calculation::Mul(a, b) => get_value(a) * get_value(b), + Calculation::Square(v) => get_value(v).square(), + Calculation::Double(v) => get_value(v).double(), + Calculation::Negate(v) => -get_value(v), + Calculation::Horner(start_value, parts, factor) => { + let factor = get_value(factor); + let mut value = get_value(start_value); + for part in parts.iter() { + value = value * factor + get_value(part); + } + value + } + Calculation::Store(v) => get_value(v), + } + } +} + +/// Evaluator +#[derive(Clone, Default, Debug)] +pub struct Evaluator { + /// Custom gates evalution + pub custom_gates: GraphEvaluator, + /// Lookups evalution + pub lookups: Vec>, +} + +/// GraphEvaluator +#[derive(Clone, Debug)] +pub struct GraphEvaluator { + /// Constants + pub constants: Vec, + /// Rotations + pub rotations: Vec, + /// Calculations + pub calculations: Vec, + /// Number of intermediates + pub num_intermediates: usize, +} + +/// EvaluationData +#[derive(Default, Debug)] +pub struct EvaluationData { + /// Intermediates + pub intermediates: Vec, + /// Rotations + pub rotations: Vec, +} + +/// CaluclationInfo +#[derive(Clone, Debug)] +pub struct CalculationInfo { + /// Calculation + pub calculation: Calculation, + /// Target + pub target: usize, +} + +impl Evaluator { + /// Creates a new evaluation structure + pub fn new(cs: &ConstraintSystem) -> Self { + let mut ev = Evaluator::default(); + + // Custom gates + let mut parts = Vec::new(); + for gate in cs.gates.iter() { + parts.extend( + gate.polynomials() + .iter() + .map(|poly| ev.custom_gates.add_expression(poly)), + ); + } + ev.custom_gates.add_calculation(Calculation::Horner( + ValueSource::PreviousValue(), + parts, + ValueSource::Y(), + )); + + // Lookups + for lookup in cs.lookups.iter() { + let mut graph = GraphEvaluator::default(); + + let mut evaluate_lc = |expressions: &Vec>| { + let parts = expressions + .iter() + .map(|expr| graph.add_expression(expr)) + .collect(); + graph.add_calculation(Calculation::Horner( + ValueSource::Constant(0), + parts, + ValueSource::Theta(), + )) + }; + + // Input coset + let compressed_input_coset = evaluate_lc(&lookup.input_expressions); + // table coset + let compressed_table_coset = evaluate_lc(&lookup.table_expressions); + // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) + let right_gamma = graph.add_calculation(Calculation::Add( + compressed_table_coset, + ValueSource::Gamma(), + )); + let lc = graph.add_calculation(Calculation::Add( + compressed_input_coset, + ValueSource::Beta(), + )); + graph.add_calculation(Calculation::Mul(lc, right_gamma)); + + ev.lookups.push(graph); + } + + ev + } + + /// Evaluate h poly + pub(in crate::plonk) fn evaluate_h( + &self, + pk: &ProvingKey, + advice_polys: &[&[Polynomial]], + instance_polys: &[&[Polynomial]], + y: C::ScalarExt, + beta: C::ScalarExt, + gamma: C::ScalarExt, + theta: C::ScalarExt, + lookups: &[Vec>], + permutations: &[permutation::prover::Committed], + ) -> Polynomial { + let domain = &pk.vk.domain; + let size = domain.extended_len(); + let rot_scale = 1 << (domain.extended_k() - domain.k()); + let fixed = &pk.fixed_cosets[..]; + let extended_omega = domain.get_extended_omega(); + let isize = size as i32; + let one = C::ScalarExt::one(); + let l0 = &pk.l0; + let l_last = &pk.l_last; + let l_active_row = &pk.l_active_row; + let p = &pk.vk.cs.permutation; + + // Calculate the advice and instance cosets + let advice: Vec>> = advice_polys + .iter() + .map(|advice_polys| { + advice_polys + .iter() + .map(|poly| domain.coeff_to_extended(poly.clone())) + .collect() + }) + .collect(); + let instance: Vec>> = instance_polys + .iter() + .map(|instance_polys| { + instance_polys + .iter() + .map(|poly| domain.coeff_to_extended(poly.clone())) + .collect() + }) + .collect(); + + let mut values = domain.empty_extended(); + + // Core expression evaluations + let num_threads = multicore::current_num_threads(); + for (((advice, instance), lookups), permutation) in advice + .iter() + .zip(instance.iter()) + .zip(lookups.iter()) + .zip(permutations.iter()) + { + // Custom gates + multicore::scope(|scope| { + let chunk_size = (size + num_threads - 1) / num_threads; + for (thread_idx, values) in values.chunks_mut(chunk_size).enumerate() { + let start = thread_idx * chunk_size; + scope.spawn(move |_| { + let mut eval_data = self.custom_gates.instance(); + for (i, value) in values.iter_mut().enumerate() { + let idx = start + i; + *value = self.custom_gates.evaluate( + &mut eval_data, + fixed, + advice, + instance, + &beta, + &gamma, + &theta, + &y, + value, + idx, + rot_scale, + isize, + ); + } + }); + } + }); + + // Permutations + let sets = &permutation.sets; + if !sets.is_empty() { + let blinding_factors = pk.vk.cs.blinding_factors(); + let last_rotation = Rotation(-((blinding_factors + 1) as i32)); + let chunk_len = pk.vk.cs.degree() - 2; + let delta_start = beta * &C::Scalar::ZETA; + + let first_set = sets.first().unwrap(); + let last_set = sets.last().unwrap(); + + // Permutation constraints + parallelize(&mut values, |values, start| { + let mut beta_term = extended_omega.pow_vartime(&[start as u64, 0, 0, 0]); + for (i, value) in values.iter_mut().enumerate() { + let idx = start + i; + let r_next = get_rotation_idx(idx, 1, rot_scale, isize); + let r_last = get_rotation_idx(idx, last_rotation.0, rot_scale, isize); + + // Enforce only for the first set. + // l_0(X) * (1 - z_0(X)) = 0 + *value = *value * y + + ((one - first_set.permutation_product_coset[idx]) * l0[idx]); + // Enforce only for the last set. + // l_last(X) * (z_l(X)^2 - z_l(X)) = 0 + *value = *value * y + + ((last_set.permutation_product_coset[idx] + * last_set.permutation_product_coset[idx] + - last_set.permutation_product_coset[idx]) + * l_last[idx]); + // Except for the first set, enforce. + // l_0(X) * (z_i(X) - z_{i-1}(\omega^(last) X)) = 0 + for (set_idx, set) in sets.iter().enumerate() { + if set_idx != 0 { + *value = *value * y + + ((set.permutation_product_coset[idx] + - permutation.sets[set_idx - 1].permutation_product_coset + [r_last]) + * l0[idx]); + } + } + // And for all the sets we enforce: + // (1 - (l_last(X) + l_blind(X))) * ( + // z_i(\omega X) \prod_j (p(X) + \beta s_j(X) + \gamma) + // - z_i(X) \prod_j (p(X) + \delta^j \beta X + \gamma) + // ) + let mut current_delta = delta_start * beta_term; + for ((set, columns), cosets) in sets + .iter() + .zip(p.columns.chunks(chunk_len)) + .zip(pk.permutation.cosets.chunks(chunk_len)) + { + let mut left = set.permutation_product_coset[r_next]; + for (values, permutation) in columns + .iter() + .map(|&column| match column.column_type() { + Any::Advice => &advice[column.index()], + Any::Fixed => &fixed[column.index()], + Any::Instance => &instance[column.index()], + }) + .zip(cosets.iter()) + { + left *= values[idx] + beta * permutation[idx] + gamma; + } + + 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::Fixed => &fixed[column.index()], + Any::Instance => &instance[column.index()], + }) { + right *= values[idx] + current_delta + gamma; + current_delta *= &C::Scalar::DELTA; + } + + *value = *value * y + ((left - right) * l_active_row[idx]); + } + beta_term *= &extended_omega; + } + }); + } + + // Lookups + for (n, lookup) in lookups.iter().enumerate() { + // Polynomials required for this lookup. + // Calculated here so these only have to be kept in memory for the short time + // they are actually needed. + let product_coset = pk.vk.domain.coeff_to_extended(lookup.product_poly.clone()); + let permuted_input_coset = pk + .vk + .domain + .coeff_to_extended(lookup.permuted_input_poly.clone()); + let permuted_table_coset = pk + .vk + .domain + .coeff_to_extended(lookup.permuted_table_poly.clone()); + + // Lookup constraints + parallelize(&mut values, |values, start| { + let lookup_evaluator = &self.lookups[n]; + let mut eval_data = lookup_evaluator.instance(); + for (i, value) in values.iter_mut().enumerate() { + let idx = start + i; + + let table_value = lookup_evaluator.evaluate( + &mut eval_data, + fixed, + advice, + instance, + &beta, + &gamma, + &theta, + &y, + &C::ScalarExt::zero(), + idx, + rot_scale, + isize, + ); + + let r_next = get_rotation_idx(idx, 1, rot_scale, isize); + let r_prev = get_rotation_idx(idx, -1, rot_scale, isize); + + let a_minus_s = permuted_input_coset[idx] - permuted_table_coset[idx]; + // l_0(X) * (1 - z(X)) = 0 + *value = *value * y + ((one - product_coset[idx]) * l0[idx]); + // l_last(X) * (z(X)^2 - z(X)) = 0 + *value = *value * y + + ((product_coset[idx] * product_coset[idx] - product_coset[idx]) + * l_last[idx]); + // (1 - (l_last(X) + l_blind(X))) * ( + // 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) + // ) = 0 + *value = *value * y + + ((product_coset[r_next] + * (permuted_input_coset[idx] + beta) + * (permuted_table_coset[idx] + gamma) + - product_coset[idx] * table_value) + * l_active_row[idx]); + // Check that the first values in the permuted input expression and permuted + // fixed expression are the same. + // l_0(X) * (a'(X) - s'(X)) = 0 + *value = *value * y + (a_minus_s * l0[idx]); + // Check that each value in the permuted lookup input expression is either + // equal to the value above it, or the value at the same index in the + // permuted table expression. + // (1 - (l_last + l_blind)) * (a′(X) − s′(X))⋅(a′(X) − a′(\omega^{-1} X)) = 0 + *value = *value * y + + (a_minus_s + * (permuted_input_coset[idx] - permuted_input_coset[r_prev]) + * l_active_row[idx]); + } + }); + } + } + values + } +} + +impl Default for GraphEvaluator { + fn default() -> Self { + Self { + // Fixed positions to allow easy access + constants: vec![ + C::ScalarExt::zero(), + C::ScalarExt::one(), + C::ScalarExt::from(2u64), + ], + rotations: Vec::new(), + calculations: Vec::new(), + num_intermediates: 0, + } + } +} + +impl GraphEvaluator { + /// Adds a rotation + fn add_rotation(&mut self, rotation: &Rotation) -> usize { + let position = self.rotations.iter().position(|&c| c == rotation.0); + match position { + Some(pos) => pos, + None => { + self.rotations.push(rotation.0); + self.rotations.len() - 1 + } + } + } + + /// Adds a constant + fn add_constant(&mut self, constant: &C::ScalarExt) -> ValueSource { + let position = self.constants.iter().position(|&c| c == *constant); + ValueSource::Constant(match position { + Some(pos) => pos, + None => { + self.constants.push(*constant); + self.constants.len() - 1 + } + }) + } + + /// Adds a calculation. + /// Currently does the simplest thing possible: just stores the + /// resulting value so the result can be reused when that calculation + /// is done multiple times. + fn add_calculation(&mut self, calculation: Calculation) -> ValueSource { + let existing_calculation = self + .calculations + .iter() + .find(|c| c.calculation == calculation); + match existing_calculation { + Some(existing_calculation) => ValueSource::Intermediate(existing_calculation.target), + None => { + let target = self.num_intermediates; + self.calculations.push(CalculationInfo { + calculation, + target, + }); + self.num_intermediates += 1; + ValueSource::Intermediate(target) + } + } + } + + /// Generates an optimized evaluation for the expression + fn add_expression(&mut self, expr: &Expression) -> ValueSource { + match expr { + Expression::Constant(scalar) => self.add_constant(scalar), + Expression::Selector(_selector) => unreachable!(), + Expression::Fixed(query) => { + let rot_idx = self.add_rotation(&query.rotation); + self.add_calculation(Calculation::Store(ValueSource::Fixed( + query.column_index, + rot_idx, + ))) + } + Expression::Advice(query) => { + let rot_idx = self.add_rotation(&query.rotation); + self.add_calculation(Calculation::Store(ValueSource::Advice( + query.column_index, + rot_idx, + ))) + } + Expression::Instance(query) => { + let rot_idx = self.add_rotation(&query.rotation); + self.add_calculation(Calculation::Store(ValueSource::Instance( + query.column_index, + rot_idx, + ))) + } + Expression::Negated(a) => match **a { + Expression::Constant(scalar) => self.add_constant(&-scalar), + _ => { + let result_a = self.add_expression(a); + match result_a { + ValueSource::Constant(0) => result_a, + _ => self.add_calculation(Calculation::Negate(result_a)), + } + } + }, + Expression::Sum(a, b) => { + // Undo subtraction stored as a + (-b) in expressions + match &**b { + Expression::Negated(b_int) => { + let result_a = self.add_expression(a); + let result_b = self.add_expression(b_int); + if result_a == ValueSource::Constant(0) { + self.add_calculation(Calculation::Negate(result_b)) + } else if result_b == ValueSource::Constant(0) { + result_a + } else { + self.add_calculation(Calculation::Sub(result_a, result_b)) + } + } + _ => { + let result_a = self.add_expression(a); + let result_b = self.add_expression(b); + if result_a == ValueSource::Constant(0) { + result_b + } else if result_b == ValueSource::Constant(0) { + result_a + } else if result_a <= result_b { + self.add_calculation(Calculation::Add(result_a, result_b)) + } else { + self.add_calculation(Calculation::Add(result_b, result_a)) + } + } + } + } + Expression::Product(a, b) => { + let result_a = self.add_expression(a); + let result_b = self.add_expression(b); + if result_a == ValueSource::Constant(0) || result_b == ValueSource::Constant(0) { + ValueSource::Constant(0) + } else if result_a == ValueSource::Constant(1) { + result_b + } else if result_b == ValueSource::Constant(1) { + result_a + } else if result_a == ValueSource::Constant(2) { + self.add_calculation(Calculation::Double(result_b)) + } else if result_b == ValueSource::Constant(2) { + self.add_calculation(Calculation::Double(result_a)) + } else if result_a == result_b { + self.add_calculation(Calculation::Square(result_a)) + } else if result_a <= result_b { + self.add_calculation(Calculation::Mul(result_a, result_b)) + } else { + self.add_calculation(Calculation::Mul(result_b, result_a)) + } + } + Expression::Scaled(a, f) => { + if *f == C::ScalarExt::zero() { + ValueSource::Constant(0) + } else if *f == C::ScalarExt::one() { + self.add_expression(a) + } else { + let cst = self.add_constant(f); + let result_a = self.add_expression(a); + self.add_calculation(Calculation::Mul(result_a, cst)) + } + } + } + } + + /// Creates a new evaluation structure + pub fn instance(&self) -> EvaluationData { + EvaluationData { + intermediates: vec![C::ScalarExt::zero(); self.num_intermediates], + rotations: vec![0usize; self.rotations.len()], + } + } + + pub fn evaluate( + &self, + data: &mut EvaluationData, + fixed: &[Polynomial], + advice: &[Polynomial], + instance: &[Polynomial], + beta: &C::ScalarExt, + gamma: &C::ScalarExt, + theta: &C::ScalarExt, + y: &C::ScalarExt, + previous_value: &C::ScalarExt, + idx: usize, + rot_scale: i32, + isize: i32, + ) -> C::ScalarExt { + // All rotation index values + for (rot_idx, rot) in self.rotations.iter().enumerate() { + data.rotations[rot_idx] = get_rotation_idx(idx, *rot, rot_scale, isize); + } + + // All calculations, with cached intermediate results + for calc in self.calculations.iter() { + data.intermediates[calc.target] = calc.calculation.evaluate( + &data.rotations, + &self.constants, + &data.intermediates, + fixed, + advice, + instance, + beta, + gamma, + theta, + y, + previous_value, + ); + } + + // Return the result of the last calculation (if any) + if let Some(calc) = self.calculations.last() { + data.intermediates[calc.target] + } else { + C::ScalarExt::zero() + } + } +} + +/// Simple evaluation of an expression +pub fn evaluate( + expression: &Expression, + size: usize, + rot_scale: i32, + fixed: &[Polynomial], + advice: &[Polynomial], + instance: &[Polynomial], +) -> Vec { + 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() { + let idx = start + i; + *value = expression.evaluate( + &|scalar| scalar, + &|_| panic!("virtual selectors are removed during optimization"), + &|query| { + fixed[query.column_index] + [get_rotation_idx(idx, query.rotation.0, rot_scale, isize)] + }, + &|query| { + advice[query.column_index] + [get_rotation_idx(idx, query.rotation.0, rot_scale, isize)] + }, + &|query| { + instance[query.column_index] + [get_rotation_idx(idx, query.rotation.0, rot_scale, isize)] + }, + &|a| -a, + &|a, b| a + &b, + &|a, b| a * b, + &|a, scalar| a * scalar, + ); + } + }); + values +} diff --git a/halo2_proofs/src/plonk/keygen.rs b/halo2_proofs/src/plonk/keygen.rs index baab69d868..3480af2a2f 100644 --- a/halo2_proofs/src/plonk/keygen.rs +++ b/halo2_proofs/src/plonk/keygen.rs @@ -10,20 +10,21 @@ use super::{ Advice, Any, Assignment, Circuit, Column, ConstraintSystem, Fixed, FloorPlanner, Instance, Selector, }, - permutation, Assigned, Error, LagrangeCoeff, Polynomial, ProvingKey, VerifyingKey, + evaluation::Evaluator, + permutation, Assigned, Error, Expression, LagrangeCoeff, Polynomial, ProvingKey, VerifyingKey, }; use crate::{ - arithmetic::CurveAffine, + arithmetic::{parallelize, CurveAffine}, circuit::Value, poly::{ batch_invert_assigned, - commitment::{Blind, Params}, + commitment::{Blind, Params, MSM}, EvaluationDomain, }, }; pub(crate) fn create_domain( - params: &Params, + k: u32, ) -> ( EvaluationDomain, ConstraintSystem, @@ -38,7 +39,7 @@ where let degree = cs.degree(); - let domain = EvaluationDomain::new(degree as u32, params.k); + let domain = EvaluationDomain::new(degree as u32, k); (domain, cs, config) } @@ -186,26 +187,27 @@ impl Assignment for Assembly { } /// Generate a `VerifyingKey` from an instance of `Circuit`. -pub fn keygen_vk( - params: &Params, +pub fn keygen_vk<'params, C, P, ConcreteCircuit>( + params: &P, circuit: &ConcreteCircuit, ) -> Result, Error> where C: CurveAffine, + P: Params<'params, C>, ConcreteCircuit: Circuit, { - let (domain, cs, config) = create_domain::(params); + let (domain, cs, config) = create_domain::(params.k()); - if (params.n as usize) < cs.minimum_rows() { - return Err(Error::not_enough_rows_available(params.k)); + if (params.n() as usize) < cs.minimum_rows() { + return Err(Error::not_enough_rows_available(params.k())); } let mut assembly: Assembly = Assembly { - k: params.k, + k: params.k(), fixed: vec![domain.empty_lagrange_assigned(); cs.num_fixed_columns], - permutation: permutation::keygen::Assembly::new(params.n as usize, &cs.permutation), - selectors: vec![vec![false; params.n as usize]; cs.num_selectors], - usable_rows: 0..params.n as usize - (cs.blinding_factors() + 1), + permutation: permutation::keygen::Assembly::new(params.n() as usize, &cs.permutation), + selectors: vec![vec![false; params.n() as usize]; cs.num_selectors], + usable_rows: 0..params.n() as usize - (cs.blinding_factors() + 1), _marker: std::marker::PhantomData, }; @@ -243,13 +245,14 @@ where } /// Generate a `ProvingKey` from a `VerifyingKey` and an instance of `Circuit`. -pub fn keygen_pk( - params: &Params, +pub fn keygen_pk<'params, C, P, ConcreteCircuit>( + params: &P, vk: VerifyingKey, circuit: &ConcreteCircuit, ) -> Result, Error> where C: CurveAffine, + P: Params<'params, C>, ConcreteCircuit: Circuit, { let mut cs = ConstraintSystem::default(); @@ -257,16 +260,16 @@ where let cs = cs; - if (params.n as usize) < cs.minimum_rows() { - return Err(Error::not_enough_rows_available(params.k)); + if (params.n() as usize) < cs.minimum_rows() { + return Err(Error::not_enough_rows_available(params.k())); } let mut assembly: Assembly = Assembly { - k: params.k, + k: params.k(), fixed: vec![vk.domain.empty_lagrange_assigned(); cs.num_fixed_columns], - permutation: permutation::keygen::Assembly::new(params.n as usize, &cs.permutation), - selectors: vec![vec![false; params.n as usize]; cs.num_selectors], - usable_rows: 0..params.n as usize - (cs.blinding_factors() + 1), + permutation: permutation::keygen::Assembly::new(params.n() as usize, &cs.permutation), + selectors: vec![vec![false; params.n() as usize]; cs.num_selectors], + usable_rows: 0..params.n() as usize - (cs.blinding_factors() + 1), _marker: std::marker::PhantomData, }; @@ -319,18 +322,32 @@ 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 mut l_active_row = vk.domain.empty_extended(); + parallelize(&mut l_active_row, |values, start| { + for (i, value) in values.iter_mut().enumerate() { + let idx = i + start; + *value = one - (l_last[idx] + l_blind[idx]); + } + }); + + // Compute the optimized evaluation data structure + let ev = Evaluator::new(&vk.cs); + Ok(ProvingKey { vk, l0, - l_blind, l_last, + l_active_row, fixed_values: fixed, fixed_polys, fixed_cosets, permutation: permutation_pk, + ev, }) } diff --git a/halo2_proofs/src/plonk/lookup.rs b/halo2_proofs/src/plonk/lookup.rs index ea312eed31..68cda75d37 100644 --- a/halo2_proofs/src/plonk/lookup.rs +++ b/halo2_proofs/src/plonk/lookup.rs @@ -1,22 +1,34 @@ use super::circuit::Expression; use ff::Field; +use std::fmt::{self, Debug}; pub(crate) mod prover; pub(crate) mod verifier; -#[derive(Clone, Debug)] -pub(crate) struct Argument { - pub input_expressions: Vec>, - pub table_expressions: Vec>, +#[derive(Clone)] +pub struct Argument { + pub(crate) name: &'static str, + pub(crate) input_expressions: Vec>, + pub(crate) table_expressions: Vec>, +} + +impl Debug for Argument { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Argument") + .field("input_expressions", &self.input_expressions) + .field("table_expressions", &self.table_expressions) + .finish() + } } impl Argument { /// Constructs a new lookup argument. /// /// `table_map` is a sequence of `(input, table)` tuples. - pub fn new(table_map: Vec<(Expression, Expression)>) -> Self { + pub fn new(name: &'static str, table_map: Vec<(Expression, Expression)>) -> Self { let (input_expressions, table_expressions) = table_map.into_iter().unzip(); Argument { + name, input_expressions, table_expressions, } @@ -69,4 +81,14 @@ impl Argument { 2 + input_degree + table_degree, ) } + + /// Returns input of this argument + pub fn input_expressions(&self) -> &Vec> { + &self.input_expressions + } + + /// Returns table of this argument + pub fn table_expressions(&self) -> &Vec> { + &self.table_expressions + } } diff --git a/halo2_proofs/src/plonk/lookup/prover.rs b/halo2_proofs/src/plonk/lookup/prover.rs index 4aee721f01..4b0ba9a614 100644 --- a/halo2_proofs/src/plonk/lookup/prover.rs +++ b/halo2_proofs/src/plonk/lookup/prover.rs @@ -3,13 +3,13 @@ use super::super::{ ProvingKey, }; use super::Argument; +use crate::plonk::evaluation::evaluate; use crate::{ arithmetic::{eval_polynomial, parallelize, CurveAffine, FieldExt}, poly::{ - self, commitment::{Blind, Params}, - multiopen::ProverQuery, - Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation, + Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, ProverQuery, + Rotation, }, transcript::{EncodedChallenge, TranscriptWrite}, }; @@ -18,6 +18,7 @@ use group::{ Curve, }; use rand_core::RngCore; +use std::{any::TypeId, convert::TryInto, num::ParseIntError, ops::Index}; use std::{ collections::BTreeMap, iter, @@ -25,40 +26,29 @@ use std::{ }; #[derive(Debug)] -pub(in crate::plonk) struct Permuted { - unpermuted_input_expressions: Vec>, - unpermuted_input_cosets: Vec>, +pub(in crate::plonk) struct Permuted { + compressed_input_expression: Polynomial, permuted_input_expression: Polynomial, permuted_input_poly: Polynomial, - permuted_input_coset: poly::AstLeaf, permuted_input_blind: Blind, - unpermuted_table_expressions: Vec>, - unpermuted_table_cosets: Vec>, + compressed_table_expression: Polynomial, permuted_table_expression: Polynomial, permuted_table_poly: Polynomial, - permuted_table_coset: poly::AstLeaf, permuted_table_blind: Blind, } #[derive(Debug)] -pub(in crate::plonk) struct Committed { - permuted: Permuted, - product_poly: Polynomial, - product_coset: poly::AstLeaf, - product_blind: Blind, -} - -pub(in crate::plonk) struct Constructed { - permuted_input_poly: Polynomial, +pub(in crate::plonk) struct Committed { + pub(in crate::plonk) permuted_input_poly: Polynomial, permuted_input_blind: Blind, - permuted_table_poly: Polynomial, + pub(in crate::plonk) permuted_table_poly: Polynomial, permuted_table_blind: Blind, - product_poly: Polynomial, + pub(in crate::plonk) product_poly: Polynomial, product_blind: Blind, } pub(in crate::plonk) struct Evaluated { - constructed: Constructed, + constructed: Committed, } impl Argument { @@ -73,120 +63,56 @@ impl Argument { /// The Permuted struct is used to update the Lookup, and is then returned. pub(in crate::plonk) fn commit_permuted< 'a, + 'params: 'a, C, + P: Params<'params, C>, E: EncodedChallenge, - Ev: Copy + Send + Sync, - Ec: Copy + Send + Sync, R: RngCore, T: TranscriptWrite, >( &self, pk: &ProvingKey, - params: &Params, + params: &P, domain: &EvaluationDomain, - value_evaluator: &poly::Evaluator, - coset_evaluator: &mut poly::Evaluator, theta: ChallengeTheta, - advice_values: &'a [poly::AstLeaf], - fixed_values: &'a [poly::AstLeaf], - instance_values: &'a [poly::AstLeaf], - advice_cosets: &'a [poly::AstLeaf], - fixed_cosets: &'a [poly::AstLeaf], - instance_cosets: &'a [poly::AstLeaf], + advice_values: &'a [Polynomial], + fixed_values: &'a [Polynomial], + instance_values: &'a [Polynomial], mut rng: R, transcript: &mut T, - ) -> Result, Error> + ) -> Result, Error> where C: CurveAffine, C::Curve: Mul + MulAssign, { // Closure to get values of expressions and compress them let compress_expressions = |expressions: &[Expression]| { - // Values of input expressions involved in the lookup - let unpermuted_expressions: Vec<_> = expressions - .iter() - .map(|expression| { - expression.evaluate( - &|scalar| poly::Ast::ConstantTerm(scalar), - &|_| panic!("virtual selectors are removed during optimization"), - &|query| { - fixed_values[query.column_index] - .with_rotation(query.rotation) - .into() - }, - &|query| { - advice_values[query.column_index] - .with_rotation(query.rotation) - .into() - }, - &|query| { - instance_values[query.column_index] - .with_rotation(query.rotation) - .into() - }, - &|a| -a, - &|a, b| a + b, - &|a, b| a * b, - &|a, scalar| a * scalar, - ) - }) - .collect(); - - let unpermuted_cosets: Vec<_> = expressions + let compressed_expression = expressions .iter() .map(|expression| { - expression.evaluate( - &|scalar| poly::Ast::ConstantTerm(scalar), - &|_| panic!("virtual selectors are removed during optimization"), - &|query| { - fixed_cosets[query.column_index] - .with_rotation(query.rotation) - .into() - }, - &|query| { - advice_cosets[query.column_index] - .with_rotation(query.rotation) - .into() - }, - &|query| { - instance_cosets[query.column_index] - .with_rotation(query.rotation) - .into() - }, - &|a| -a, - &|a, b| a + b, - &|a, b| a * b, - &|a, scalar| a * scalar, - ) + pk.vk.domain.lagrange_from_vec(evaluate( + expression, + params.n() as usize, + 1, + fixed_values, + advice_values, + instance_values, + )) }) - .collect(); - - // Compressed version of expressions - let compressed_expression = unpermuted_expressions.iter().fold( - poly::Ast::ConstantTerm(C::Scalar::zero()), - |acc, expression| &(acc * *theta) + expression, - ); - - ( - unpermuted_expressions - .iter() - .map(|ast| value_evaluator.evaluate(ast, domain)) - .collect(), - unpermuted_cosets, - value_evaluator.evaluate(&compressed_expression, domain), - ) + .fold(domain.empty_lagrange(), |acc, expression| { + acc * *theta + &expression + }); + compressed_expression }; // Get values of input expressions involved in the lookup and compress them - let (unpermuted_input_expressions, unpermuted_input_cosets, compressed_input_expression) = - compress_expressions(&self.input_expressions); + let compressed_input_expression = compress_expressions(&self.input_expressions); // Get values of table expressions involved in the lookup and compress them - let (unpermuted_table_expressions, unpermuted_table_cosets, compressed_table_expression) = - compress_expressions(&self.table_expressions); + let compressed_table_expression = compress_expressions(&self.table_expressions); // Permute compressed (InputExpression, TableExpression) pair - let (permuted_input_expression, permuted_table_expression) = permute_expression_pair::( + let (permuted_input_expression, permuted_table_expression) = permute_expression_pair( pk, params, domain, @@ -217,49 +143,40 @@ impl Argument { // Hash permuted table commitment transcript.write_point(permuted_table_commitment)?; - let permuted_input_coset = coset_evaluator - .register_poly(pk.vk.domain.coeff_to_extended(permuted_input_poly.clone())); - let permuted_table_coset = coset_evaluator - .register_poly(pk.vk.domain.coeff_to_extended(permuted_table_poly.clone())); - Ok(Permuted { - unpermuted_input_expressions, - unpermuted_input_cosets, + compressed_input_expression, permuted_input_expression, permuted_input_poly, - permuted_input_coset, permuted_input_blind, - unpermuted_table_expressions, - unpermuted_table_cosets, + compressed_table_expression, permuted_table_expression, permuted_table_poly, - permuted_table_coset, permuted_table_blind, }) } } -impl Permuted { +impl Permuted { /// Given a Lookup with input expressions, table expressions, and the permuted /// input expression and permuted table expression, this method constructs the /// grand product polynomial over the lookup. The grand product polynomial /// is used to populate the Product struct. The Product struct is /// added to the Lookup and finally returned by the method. pub(in crate::plonk) fn commit_product< + 'params, + P: Params<'params, C>, E: EncodedChallenge, R: RngCore, T: TranscriptWrite, >( self, pk: &ProvingKey, - params: &Params, - theta: ChallengeTheta, + params: &P, beta: ChallengeBeta, gamma: ChallengeGamma, - evaluator: &mut poly::Evaluator, mut rng: R, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> { let blinding_factors = pk.vk.cs.blinding_factors(); // Goal is to compute the products of fractions // @@ -272,7 +189,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 @@ -295,22 +212,8 @@ impl Permuted { for (i, product) in product.iter_mut().enumerate() { let i = i + start; - // Compress unpermuted input expressions - let mut input_term = C::Scalar::zero(); - for unpermuted_input_expression in self.unpermuted_input_expressions.iter() { - input_term *= &*theta; - input_term += &unpermuted_input_expression[i]; - } - - // Compress unpermuted table expressions - let mut table_term = C::Scalar::zero(); - for unpermuted_table_expression in self.unpermuted_table_expressions.iter() { - table_term *= &*theta; - table_term += &unpermuted_table_expression[i]; - } - - *product *= &(input_term + &*beta); - *product *= &(table_term + &*gamma); + *product *= &(self.compressed_input_expression[i] + &*beta); + *product *= &(self.compressed_table_expression[i] + &*gamma); } }); @@ -337,11 +240,11 @@ impl Permuted { }) // Take all rows including the "last" row which should // be a boolean (and ideally 1, else soundness is broken) - .take(params.n as usize - blinding_factors) + .take(params.n() as usize - blinding_factors) // Chain random blinding factors. .chain((0..blinding_factors).map(|_| C::Scalar::random(&mut rng))) .collect::>(); - assert_eq!(z.len(), params.n as usize); + assert_eq!(z.len(), params.n() as usize); let z = pk.vk.domain.lagrange_from_vec(z); #[cfg(feature = "sanity-checks")] @@ -349,7 +252,7 @@ impl Permuted { // It can be used for debugging purposes. { // While in Lagrange basis, check that product is correctly constructed - let u = (params.n as usize) - (blinding_factors + 1); + let u = (params.n() as usize) - (blinding_factors + 1); // l_0(X) * (1 - z(X)) = 0 assert_eq!(z[0], C::Scalar::one()); @@ -366,15 +269,8 @@ impl Permuted { left *= &(*gamma + permuted_table_value); let mut right = z[i]; - let mut input_term = self - .unpermuted_input_expressions - .iter() - .fold(C::Scalar::zero(), |acc, input| acc * &*theta + &input[i]); - - let mut table_term = self - .unpermuted_table_expressions - .iter() - .fold(C::Scalar::zero(), |acc, table| acc * &*theta + &table[i]); + let mut input_term = self.compressed_input_expression[i]; + let mut table_term = self.compressed_table_expression[i]; input_term += &(*beta); table_term += &(*gamma); @@ -392,113 +288,22 @@ impl Permuted { let product_blind = Blind(C::Scalar::random(rng)); let product_commitment = params.commit_lagrange(&z, product_blind).to_affine(); let z = pk.vk.domain.lagrange_to_coeff(z); - let product_coset = evaluator.register_poly(pk.vk.domain.coeff_to_extended(z.clone())); // Hash product commitment transcript.write_point(product_commitment)?; - Ok(Committed:: { - permuted: self, + Ok(Committed:: { + permuted_input_poly: self.permuted_input_poly, + permuted_input_blind: self.permuted_input_blind, + permuted_table_poly: self.permuted_table_poly, + permuted_table_blind: self.permuted_table_blind, product_poly: z, - product_coset, product_blind, }) } } -impl<'a, C: CurveAffine, Ev: Copy + Send + Sync + 'a> Committed { - /// Given a Lookup with input expressions, table expressions, permuted input - /// expression, permuted table expression, and grand product polynomial, this - /// method constructs constraints that must hold between these values. - /// This method returns the constraints as a vector of ASTs for polynomials in - /// the extended evaluation domain. - pub(in crate::plonk) fn construct( - self, - theta: ChallengeTheta, - beta: ChallengeBeta, - gamma: ChallengeGamma, - l0: poly::AstLeaf, - l_blind: poly::AstLeaf, - l_last: poly::AstLeaf, - ) -> ( - Constructed, - impl Iterator> + 'a, - ) { - let permuted = self.permuted; - - let active_rows = poly::Ast::one() - (poly::Ast::from(l_last) + l_blind); - let beta = poly::Ast::ConstantTerm(*beta); - let gamma = poly::Ast::ConstantTerm(*gamma); - - let expressions = iter::empty() - // l_0(X) * (1 - z(X)) = 0 - .chain(Some((poly::Ast::one() - self.product_coset) * l0)) - // l_last(X) * (z(X)^2 - z(X)) = 0 - .chain(Some( - (poly::Ast::from(self.product_coset) * self.product_coset - self.product_coset) - * l_last, - )) - // (1 - (l_last(X) + l_blind(X))) * ( - // 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) - // ) = 0 - .chain({ - // z(\omega X) (a'(X) + \beta) (s'(X) + \gamma) - let left: poly::Ast<_, _, _> = poly::Ast::<_, C::Scalar, _>::from( - self.product_coset.with_rotation(Rotation::next()), - ) * (poly::Ast::from(permuted.permuted_input_coset) - + beta.clone()) - * (poly::Ast::from(permuted.permuted_table_coset) + gamma.clone()); - - // z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta) (\theta^{m-1} s_0(X) + ... + s_{m-1}(X) + \gamma) - let compress_cosets = |cosets: &[poly::Ast<_, _, _>]| { - cosets.iter().fold( - poly::Ast::<_, _, ExtendedLagrangeCoeff>::ConstantTerm(C::Scalar::zero()), - |acc, eval| acc * poly::Ast::ConstantTerm(*theta) + eval.clone(), - ) - }; - let right: poly::Ast<_, _, _> = poly::Ast::from(self.product_coset) - * (compress_cosets(&permuted.unpermuted_input_cosets) + beta) - * (compress_cosets(&permuted.unpermuted_table_cosets) + gamma); - - Some((left - right) * active_rows.clone()) - }) - // Check that the first values in the permuted input expression and permuted - // fixed expression are the same. - // l_0(X) * (a'(X) - s'(X)) = 0 - .chain(Some( - (poly::Ast::from(permuted.permuted_input_coset) - permuted.permuted_table_coset) - * l0, - )) - // Check that each value in the permuted lookup input expression is either - // equal to the value above it, or the value at the same index in the - // permuted table expression. - // (1 - (l_last + l_blind)) * (a′(X) − s′(X))⋅(a′(X) − a′(\omega^{-1} X)) = 0 - .chain(Some( - (poly::Ast::<_, C::Scalar, _>::from(permuted.permuted_input_coset) - - permuted.permuted_table_coset) - * (poly::Ast::from(permuted.permuted_input_coset) - - permuted - .permuted_input_coset - .with_rotation(Rotation::prev())) - * active_rows, - )); - - ( - Constructed { - permuted_input_poly: permuted.permuted_input_poly, - permuted_input_blind: permuted.permuted_input_blind, - permuted_table_poly: permuted.permuted_table_poly, - permuted_table_blind: permuted.permuted_table_blind, - product_poly: self.product_poly, - product_blind: self.product_blind, - }, - expressions, - ) - } -} - -impl Constructed { +impl Committed { pub(in crate::plonk) fn evaluate, T: TranscriptWrite>( self, pk: &ProvingKey, @@ -581,16 +386,16 @@ type ExpressionPair = (Polynomial, Polynomial( +fn permute_expression_pair<'params, C: CurveAffine, P: Params<'params, C>, R: RngCore>( pk: &ProvingKey, - params: &Params, + params: &P, domain: &EvaluationDomain, mut rng: R, input_expression: &Polynomial, table_expression: &Polynomial, ) -> Result, Error> { let blinding_factors = pk.vk.cs.blinding_factors(); - let usable_rows = params.n as usize - (blinding_factors + 1); + let usable_rows = params.n() as usize - (blinding_factors + 1); let mut permuted_input_expression: Vec = input_expression.to_vec(); permuted_input_expression.truncate(usable_rows); @@ -643,8 +448,8 @@ fn permute_expression_pair( permuted_input_expression .extend((0..(blinding_factors + 1)).map(|_| C::Scalar::random(&mut rng))); permuted_table_coeffs.extend((0..(blinding_factors + 1)).map(|_| C::Scalar::random(&mut rng))); - assert_eq!(permuted_input_expression.len(), params.n as usize); - assert_eq!(permuted_table_coeffs.len(), params.n as usize); + assert_eq!(permuted_input_expression.len(), params.n() as usize); + assert_eq!(permuted_table_coeffs.len(), params.n() as usize); #[cfg(feature = "sanity-checks")] { diff --git a/halo2_proofs/src/plonk/lookup/verifier.rs b/halo2_proofs/src/plonk/lookup/verifier.rs index f7225c5f21..07522e478d 100644 --- a/halo2_proofs/src/plonk/lookup/verifier.rs +++ b/halo2_proofs/src/plonk/lookup/verifier.rs @@ -7,7 +7,7 @@ use super::Argument; use crate::{ arithmetic::{CurveAffine, FieldExt}, plonk::{Error, VerifyingKey}, - poly::{multiopen::VerifierQuery, Rotation}, + poly::{commitment::MSM, Rotation, VerifierQuery}, transcript::{EncodedChallenge, TranscriptRead}, }; use ff::Field; @@ -165,11 +165,11 @@ impl Evaluated { )) } - pub(in crate::plonk) fn queries<'r, 'params: 'r>( + pub(in crate::plonk) fn queries<'r, M: MSM + 'r>( &'r self, vk: &'r VerifyingKey, x: ChallengeX, - ) -> impl Iterator> + Clone { + ) -> impl Iterator> + Clone { let x_inv = vk.domain.rotate_omega(*x, Rotation::prev()); let x_next = vk.domain.rotate_omega(*x, Rotation::next()); diff --git a/halo2_proofs/src/plonk/permutation.rs b/halo2_proofs/src/plonk/permutation.rs index 58745f566e..26a4d805d6 100644 --- a/halo2_proofs/src/plonk/permutation.rs +++ b/halo2_proofs/src/plonk/permutation.rs @@ -13,9 +13,9 @@ use std::io; /// A permutation argument. #[derive(Debug, Clone)] -pub(crate) struct Argument { +pub struct Argument { /// A sequence of columns involved in the argument. - columns: Vec>, + pub(super) columns: Vec>, } impl Argument { @@ -67,17 +67,24 @@ impl Argument { } } - pub(crate) fn get_columns(&self) -> Vec> { + pub fn get_columns(&self) -> Vec> { self.columns.clone() } } /// The verifying key for a single permutation argument. #[derive(Clone, Debug)] -pub(crate) struct VerifyingKey { +pub struct VerifyingKey { commitments: Vec, } +impl VerifyingKey { + /// Returns commitments of sigma polynomials + pub fn commitments(&self) -> &Vec { + &self.commitments + } +} + /// The proving key for a single permutation argument. #[derive(Clone, Debug)] pub(crate) struct ProvingKey { diff --git a/halo2_proofs/src/plonk/permutation/keygen.rs b/halo2_proofs/src/plonk/permutation/keygen.rs index 6c77b5abd6..cdb8cc02f9 100644 --- a/halo2_proofs/src/plonk/permutation/keygen.rs +++ b/halo2_proofs/src/plonk/permutation/keygen.rs @@ -6,7 +6,7 @@ use crate::{ arithmetic::{CurveAffine, FieldExt}, plonk::{Any, Column, Error}, poly::{ - commitment::{Blind, Params}, + commitment::{Blind, CommitmentScheme, Params}, EvaluationDomain, }, }; @@ -97,17 +97,17 @@ impl Assembly { Ok(()) } - pub(crate) fn build_vk( + pub(crate) fn build_vk<'params, C: CurveAffine, P: Params<'params, C>>( self, - params: &Params, + params: &P, 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 omega_powers = Vec::with_capacity(params.n() as usize); { let mut cur = C::Scalar::one(); - for _ in 0..params.n { + for _ in 0..params.n() { omega_powers.push(cur); cur *= &domain.get_omega(); } @@ -150,17 +150,17 @@ impl Assembly { VerifyingKey { commitments } } - pub(crate) fn build_pk( + pub(crate) fn build_pk<'params, C: CurveAffine, P: Params<'params, C>>( self, - params: &Params, + 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 omega_powers = Vec::with_capacity(params.n() as usize); { let mut cur = C::Scalar::one(); - for _ in 0..params.n { + for _ in 0..params.n() { omega_powers.push(cur); cur *= &domain.get_omega(); } diff --git a/halo2_proofs/src/plonk/permutation/prover.rs b/halo2_proofs/src/plonk/permutation/prover.rs index e88b4d7049..8a9c5bbef7 100644 --- a/halo2_proofs/src/plonk/permutation/prover.rs +++ b/halo2_proofs/src/plonk/permutation/prover.rs @@ -13,20 +13,19 @@ use crate::{ poly::{ self, commitment::{Blind, Params}, - multiopen::ProverQuery, - Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation, + Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, ProverQuery, Rotation, }, transcript::{EncodedChallenge, TranscriptWrite}, }; -pub struct CommittedSet { - permutation_product_poly: Polynomial, - permutation_product_coset: poly::AstLeaf, +pub(crate) struct CommittedSet { + pub(crate) permutation_product_poly: Polynomial, + pub(crate) permutation_product_coset: Polynomial, permutation_product_blind: Blind, } -pub(crate) struct Committed { - sets: Vec>, +pub(crate) struct Committed { + pub(crate) sets: Vec>, } pub struct ConstructedSet { @@ -44,14 +43,15 @@ pub(crate) struct Evaluated { impl Argument { pub(in crate::plonk) fn commit< + 'params, C: CurveAffine, + P: Params<'params, C>, E: EncodedChallenge, - Ev: Copy + Send + Sync, R: RngCore, T: TranscriptWrite, >( &self, - params: &Params, + params: &P, pk: &plonk::ProvingKey, pkey: &ProvingKey, advice: &[Polynomial], @@ -59,10 +59,9 @@ impl Argument { instance: &[Polynomial], beta: ChallengeBeta, gamma: ChallengeGamma, - evaluator: &mut poly::Evaluator, mut rng: R, transcript: &mut T, - ) -> Result, Error> { + ) -> Result, Error> { let domain = &pk.vk.domain; // How many columns can be included in a single permutation polynomial? @@ -94,7 +93,7 @@ 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()) { @@ -152,7 +151,7 @@ impl Argument { // Compute the evaluations of the permutation product polynomial // over our domain, starting with z[0] = 1 let mut z = vec![last_z]; - for row in 1..(params.n as usize) { + for row in 1..(params.n() as usize) { let mut tmp = z[row - 1]; tmp *= &modified_values[row - 1]; @@ -160,11 +159,11 @@ impl Argument { } let mut z = domain.lagrange_from_vec(z); // Set blinding factors - for z in &mut z[params.n as usize - blinding_factors..] { + for z in &mut z[params.n() as usize - blinding_factors..] { *z = C::Scalar::random(&mut rng); } // Set new last_z - last_z = z[params.n as usize - (blinding_factors + 1)]; + last_z = z[params.n() as usize - (blinding_factors + 1)]; let blind = Blind(C::Scalar::random(&mut rng)); @@ -173,8 +172,7 @@ impl Argument { let z = domain.lagrange_to_coeff(z); let permutation_product_poly = z.clone(); - let permutation_product_coset = - evaluator.register_poly(domain.coeff_to_extended(z.clone())); + let permutation_product_coset = domain.coeff_to_extended(z.clone()); let permutation_product_commitment = permutation_product_commitment_projective.to_affine(); @@ -193,29 +191,9 @@ impl Argument { } } -impl Committed { - pub(in crate::plonk) fn construct<'a>( - self, - pk: &'a plonk::ProvingKey, - p: &'a Argument, - advice_cosets: &'a [poly::AstLeaf], - fixed_cosets: &'a [poly::AstLeaf], - instance_cosets: &'a [poly::AstLeaf], - permutation_cosets: &'a [poly::AstLeaf], - l0: poly::AstLeaf, - l_blind: poly::AstLeaf, - l_last: poly::AstLeaf, - beta: ChallengeBeta, - gamma: ChallengeGamma, - ) -> ( - Constructed, - impl Iterator> + 'a, - ) { - let chunk_len = pk.vk.cs_degree - 2; - let blinding_factors = pk.vk.cs.blinding_factors(); - let last_rotation = Rotation(-((blinding_factors + 1) as i32)); - - let constructed = Constructed { +impl Committed { + pub(in crate::plonk) fn construct(self) -> Constructed { + Constructed { sets: self .sets .iter() @@ -224,89 +202,7 @@ impl Committed { permutation_product_blind: set.permutation_product_blind, }) .collect(), - }; - - let expressions = iter::empty() - // Enforce only for the first set. - // l_0(X) * (1 - z_0(X)) = 0 - .chain( - self.sets - .first() - .map(|first_set| (poly::Ast::one() - first_set.permutation_product_coset) * l0), - ) - // Enforce only for the last set. - // l_last(X) * (z_l(X)^2 - z_l(X)) = 0 - .chain(self.sets.last().map(|last_set| { - ((poly::Ast::from(last_set.permutation_product_coset) - * last_set.permutation_product_coset) - - last_set.permutation_product_coset) - * l_last - })) - // Except for the first set, enforce. - // l_0(X) * (z_i(X) - z_{i-1}(\omega^(last) X)) = 0 - .chain( - self.sets - .iter() - .skip(1) - .zip(self.sets.iter()) - .map(|(set, last_set)| { - (poly::Ast::from(set.permutation_product_coset) - - last_set - .permutation_product_coset - .with_rotation(last_rotation)) - * l0 - }) - .collect::>(), - ) - // And for all the sets we enforce: - // (1 - (l_last(X) + l_blind(X))) * ( - // z_i(\omega X) \prod_j (p(X) + \beta s_j(X) + \gamma) - // - z_i(X) \prod_j (p(X) + \delta^j \beta X + \gamma) - // ) - .chain( - self.sets - .into_iter() - .zip(p.columns.chunks(chunk_len)) - .zip(permutation_cosets.chunks(chunk_len)) - .enumerate() - .map(move |(chunk_index, ((set, columns), cosets))| { - let mut left = poly::Ast::<_, C::Scalar, _>::from( - set.permutation_product_coset - .with_rotation(Rotation::next()), - ); - for (values, permutation) in columns - .iter() - .map(|&column| match column.column_type() { - Any::Advice => &advice_cosets[column.index()], - Any::Fixed => &fixed_cosets[column.index()], - Any::Instance => &instance_cosets[column.index()], - }) - .zip(cosets.iter()) - { - left *= poly::Ast::<_, C::Scalar, _>::from(*values) - + (poly::Ast::ConstantTerm(*beta) * poly::Ast::from(*permutation)) - + poly::Ast::ConstantTerm(*gamma); - } - - let mut right = poly::Ast::from(set.permutation_product_coset); - let mut current_delta = *beta - * &(C::Scalar::DELTA.pow_vartime(&[(chunk_index * chunk_len) as u64])); - for values in columns.iter().map(|&column| match column.column_type() { - Any::Advice => &advice_cosets[column.index()], - Any::Fixed => &fixed_cosets[column.index()], - Any::Instance => &instance_cosets[column.index()], - }) { - right *= poly::Ast::from(*values) - + poly::Ast::LinearTerm(current_delta) - + poly::Ast::ConstantTerm(*gamma); - current_delta *= &C::Scalar::DELTA; - } - - (left - right) * (poly::Ast::one() - (poly::Ast::from(l_last) + l_blind)) - }), - ); - - (constructed, expressions) + } } } diff --git a/halo2_proofs/src/plonk/permutation/verifier.rs b/halo2_proofs/src/plonk/permutation/verifier.rs index df63fc2ab2..2ac18a6758 100644 --- a/halo2_proofs/src/plonk/permutation/verifier.rs +++ b/halo2_proofs/src/plonk/permutation/verifier.rs @@ -6,7 +6,7 @@ use super::{Argument, VerifyingKey}; use crate::{ arithmetic::{CurveAffine, FieldExt}, plonk::{self, Error}, - poly::{multiopen::VerifierQuery, Rotation}, + poly::{commitment::MSM, Rotation, VerifierQuery}, transcript::{EncodedChallenge, TranscriptRead}, }; @@ -199,11 +199,11 @@ impl Evaluated { ) } - pub(in crate::plonk) fn queries<'r, 'params: 'r>( + pub(in crate::plonk) fn queries<'r, M: MSM + 'r>( &'r self, vk: &'r plonk::VerifyingKey, x: ChallengeX, - ) -> impl Iterator> + Clone { + ) -> impl Iterator> + Clone { let blinding_factors = vk.cs.blinding_factors(); let x_next = vk.domain.rotate_omega(*x, Rotation::next()); let x_last = vk @@ -238,11 +238,11 @@ impl Evaluated { } impl CommonEvaluated { - pub(in crate::plonk) fn queries<'r, 'params: 'r>( + pub(in crate::plonk) fn queries<'r, M: MSM + 'r>( &'r self, vkey: &'r VerifyingKey, x: ChallengeX, - ) -> impl Iterator> + Clone { + ) -> impl Iterator> + Clone { // Open permutation commitments for each permutation argument at x vkey.commitments .iter() diff --git a/halo2_proofs/src/plonk/prover.rs b/halo2_proofs/src/plonk/prover.rs index 7fd03e7ce1..48d89de654 100644 --- a/halo2_proofs/src/plonk/prover.rs +++ b/halo2_proofs/src/plonk/prover.rs @@ -1,8 +1,12 @@ use ff::Field; use group::Curve; +use halo2curves::CurveExt; use rand_core::RngCore; -use std::iter; +use std::env::var; use std::ops::RangeTo; +use std::sync::atomic::AtomicUsize; +use std::time::Instant; +use std::{iter, sync::atomic::Ordering}; use super::{ circuit::{ @@ -10,7 +14,7 @@ use super::{ Selector, }, lookup, permutation, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, - ChallengeY, Error, ProvingKey, + ChallengeY, Error, Expression, ProvingKey, }; use crate::{ arithmetic::{eval_polynomial, CurveAffine, FieldExt}, @@ -18,31 +22,33 @@ use crate::{ plonk::Assigned, poly::{ self, - commitment::{Blind, Params}, - multiopen::{self, ProverQuery}, - Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, + commitment::{Blind, CommitmentScheme, Params, Prover}, + Basis, Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, ProverQuery, }, }; use crate::{ poly::batch_invert_assigned, transcript::{EncodedChallenge, TranscriptWrite}, }; +use group::prime::PrimeCurveAffine; /// This creates a proof for the provided `circuit` when given the public /// parameters `params` and the proving key [`ProvingKey`] that was /// generated previously for the same circuit. The provided `instances` /// are zero-padded internally. pub fn create_proof< - C: CurveAffine, - E: EncodedChallenge, + 'params, + Scheme: CommitmentScheme, + P: Prover<'params, Scheme>, + E: EncodedChallenge, R: RngCore, - T: TranscriptWrite, - ConcreteCircuit: Circuit, + T: TranscriptWrite, + ConcreteCircuit: Circuit, >( - params: &Params, - pk: &ProvingKey, + params: &'params Scheme::ParamsProver, + pk: &ProvingKey, circuits: &[ConcreteCircuit], - instances: &[&[&[C::Scalar]]], + instances: &[&[&[Scheme::Scalar]]], mut rng: R, transcript: &mut T, ) -> Result<(), Error> { @@ -66,17 +72,16 @@ pub fn create_proof< struct InstanceSingle { pub instance_values: Vec>, pub instance_polys: Vec>, - pub instance_cosets: Vec>, } - let instance: Vec> = instances + let instance: Vec> = instances .iter() - .map(|instance| -> Result, Error> { + .map(|instance| -> Result, Error> { let instance_values = instance .iter() .map(|values| { let mut poly = domain.empty_lagrange(); - assert_eq!(poly.len(), params.n as usize); + assert_eq!(poly.len(), params.n() as usize); if values.len() > (poly.len() - (meta.blinding_factors() + 1)) { return Err(Error::InstanceTooLarge); } @@ -91,8 +96,11 @@ pub fn create_proof< .map(|poly| params.commit_lagrange(poly, Blind::default())) .collect(); let mut instance_commitments = - vec![C::identity(); instance_commitments_projective.len()]; - C::Curve::batch_normalize(&instance_commitments_projective, &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); @@ -108,320 +116,222 @@ pub fn create_proof< }) .collect(); - let instance_cosets: Vec<_> = instance_polys - .iter() - .map(|poly| domain.coeff_to_extended(poly.clone())) - .collect(); - Ok(InstanceSingle { instance_values, instance_polys, - instance_cosets, }) }) .collect::, _>>()?; - struct AdviceSingle { - pub advice_values: Vec>, - pub advice_polys: Vec>, - pub advice_cosets: Vec>, + struct AdviceSingle { + pub advice_polys: Vec>, pub advice_blinds: Vec>, } - let advice: Vec> = circuits + 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, - } - - 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 exit_region(&mut self) { - // Do nothing; we don't care about regions in this context. + .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, } - fn enable_selector( - &mut self, - _: A, - _: &Selector, - _: usize, - ) -> Result<(), Error> - where - A: FnOnce() -> AR, - AR: Into, - { - // We only care about advice columns here - - Ok(()) - } + 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 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 exit_region(&mut self) { + // Do nothing; we don't care about regions in this context. } - self.instances - .get(column.index()) - .and_then(|column| column.get(row)) - .map(|v| Value::known(*v)) - .ok_or(Error::BoundsFailure) - } + 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 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 query_instance( + &self, + column: Column, + row: usize, + ) -> Result, Error> { + if !self.usable_rows.contains(&row) { + return Err(Error::not_enough_rows_available(self.k)); + } + + self.instances + .get(column.index()) + .and_then(|column| column.get(row)) + .map(|v| Value::known(*v)) + .ok_or(Error::BoundsFailure) } - *self - .advice - .get_mut(column.index()) - .and_then(|v| v.get_mut(row)) - .ok_or(Error::BoundsFailure)? = to().into_field().assign()?; + 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)); + } + + *self + .advice + .get_mut(column.index()) + .and_then(|v| v.get_mut(row)) + .ok_or(Error::BoundsFailure)? = to().into_field().assign()?; + + Ok(()) + } - Ok(()) - } + 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_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 copy( + &mut self, + _: Column, + _: usize, + _: Column, + _: usize, + ) -> Result<(), Error> { + // We only care about advice columns here - fn copy( - &mut self, - _: Column, - _: usize, - _: Column, - _: usize, - ) -> Result<(), Error> { - // We only care about advice columns here + Ok(()) + } - Ok(()) - } + fn fill_from_row( + &mut self, + _: Column, + _: usize, + _: Value>, + ) -> Result<(), Error> { + Ok(()) + } - fn fill_from_row( - &mut self, - _: Column, - _: usize, - _: Value>, - ) -> Result<(), Error> { - 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 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. + } } - fn pop_namespace(&mut self, _: Option) { - // Do nothing; we don't care about namespaces in this context. + let unusable_rows_start = params.n() as usize - (meta.blinding_factors() + 1); + + let mut witness = WitnessCollection { + k: params.k(), + advice: vec![domain.empty_lagrange_assigned(); meta.num_advice_columns], + instances, + // 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 + // permutation argument. + usable_rows: ..unusable_rows_start, + _marker: std::marker::PhantomData, + }; + + // Synthesize the circuit to obtain the witness and other information. + ConcreteCircuit::FloorPlanner::synthesize( + &mut witness, + circuit, + config.clone(), + meta.constants.clone(), + )?; + + let mut advice = batch_invert_assigned(witness.advice); + + // Add blinding factors to advice columns + for advice in &mut advice { + for cell in &mut advice[unusable_rows_start..] { + *cell = Scheme::Scalar::random(&mut rng); + } } - } - let unusable_rows_start = params.n as usize - (meta.blinding_factors() + 1); - - let mut witness = WitnessCollection { - k: params.k, - advice: vec![domain.empty_lagrange_assigned(); meta.num_advice_columns], - instances, - // 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 - // permutation argument. - usable_rows: ..unusable_rows_start, - _marker: std::marker::PhantomData, - }; - - // Synthesize the circuit to obtain the witness and other information. - ConcreteCircuit::FloorPlanner::synthesize( - &mut witness, - circuit, - config.clone(), - meta.constants.clone(), - )?; - - let mut advice = batch_invert_assigned(witness.advice); - - // Add blinding factors to advice columns - for advice in &mut advice { - for cell in &mut advice[unusable_rows_start..] { - *cell = C::Scalar::random(&mut rng); + // Compute commitments to advice column polynomials + let advice_blinds: Vec<_> = advice + .iter() + .map(|_| Blind(Scheme::Scalar::random(&mut rng))) + .collect(); + let advice_commitments_projective: Vec<_> = advice + .iter() + .zip(advice_blinds.iter()) + .map(|(poly, blind)| params.commit_lagrange(poly, *blind)) + .collect(); + let mut advice_commitments = + vec![Scheme::Curve::identity(); advice_commitments_projective.len()]; + ::CurveExt::batch_normalize( + &advice_commitments_projective, + &mut advice_commitments, + ); + let advice_commitments = advice_commitments; + drop(advice_commitments_projective); + + for commitment in &advice_commitments { + transcript.write_point(*commitment)?; } - } - // Compute commitments to advice column polynomials - let advice_blinds: Vec<_> = advice - .iter() - .map(|_| Blind(C::Scalar::random(&mut rng))) - .collect(); - let advice_commitments_projective: Vec<_> = advice - .iter() - .zip(advice_blinds.iter()) - .map(|(poly, blind)| params.commit_lagrange(poly, *blind)) - .collect(); - let mut advice_commitments = vec![C::identity(); advice_commitments_projective.len()]; - C::Curve::batch_normalize(&advice_commitments_projective, &mut advice_commitments); - let advice_commitments = advice_commitments; - drop(advice_commitments_projective); - - for commitment in &advice_commitments { - transcript.write_point(*commitment)?; - } - - let advice_polys: Vec<_> = advice - .clone() - .into_iter() - .map(|poly| domain.lagrange_to_coeff(poly)) - .collect(); - - let advice_cosets: Vec<_> = advice_polys - .iter() - .map(|poly| domain.coeff_to_extended(poly.clone())) - .collect(); - - Ok(AdviceSingle { - advice_values: advice, - advice_polys, - advice_cosets, - advice_blinds, - }) - }) + Ok(AdviceSingle { + advice_polys: advice, + advice_blinds, + }) + }, + ) .collect::, _>>()?; - // Create polynomial evaluator context for values. - let mut value_evaluator = poly::new_evaluator(|| {}); - - // Register fixed values with the polynomial evaluator. - let fixed_values: Vec<_> = pk - .fixed_values - .iter() - .map(|poly| value_evaluator.register_poly(poly.clone())) - .collect(); - - // Register advice values with the polynomial evaluator. - let advice_values: Vec<_> = advice - .iter() - .map(|advice| { - advice - .advice_values - .iter() - .map(|poly| value_evaluator.register_poly(poly.clone())) - .collect::>() - }) - .collect(); - - // Register instance values with the polynomial evaluator. - let instance_values: Vec<_> = instance - .iter() - .map(|instance| { - instance - .instance_values - .iter() - .map(|poly| value_evaluator.register_poly(poly.clone())) - .collect::>() - }) - .collect(); - - // Create polynomial evaluator context for cosets. - let mut coset_evaluator = poly::new_evaluator(|| {}); - - // Register fixed cosets with the polynomial evaluator. - let fixed_cosets: Vec<_> = pk - .fixed_cosets - .iter() - .map(|poly| coset_evaluator.register_poly(poly.clone())) - .collect(); - - // Register advice cosets with the polynomial evaluator. - let advice_cosets: Vec<_> = advice - .iter() - .map(|advice| { - advice - .advice_cosets - .iter() - .map(|poly| coset_evaluator.register_poly(poly.clone())) - .collect::>() - }) - .collect(); - - // Register instance cosets with the polynomial evaluator. - let instance_cosets: Vec<_> = instance - .iter() - .map(|instance| { - instance - .instance_cosets - .iter() - .map(|poly| coset_evaluator.register_poly(poly.clone())) - .collect::>() - }) - .collect(); - - // Register permutation cosets with the polynomial evaluator. - let permutation_cosets: Vec<_> = pk - .permutation - .cosets - .iter() - .map(|poly| coset_evaluator.register_poly(poly.clone())) - .collect(); - - // Register boundary polynomials used in the lookup and permutation arguments. - let l0 = coset_evaluator.register_poly(pk.l0.clone()); - let l_blind = coset_evaluator.register_poly(pk.l_blind.clone()); - let l_last = coset_evaluator.register_poly(pk.l_last.clone()); - // Sample theta challenge for keeping lookup columns linearly independent let theta: ChallengeTheta<_> = transcript.squeeze_challenge_scalar(); - let lookups: Vec>> = instance_values + let lookups: Vec>> = instance .iter() - .zip(instance_cosets.iter()) - .zip(advice_values.iter()) - .zip(advice_cosets.iter()) - .map(|(((instance_values, instance_cosets), advice_values), advice_cosets)| -> Result, Error> { + .zip(advice.iter()) + .map(|(instance, advice)| -> Result, Error> { // Construct and commit to permuted values for each lookup pk.vk .cs @@ -432,15 +342,10 @@ pub fn create_proof< pk, params, domain, - &value_evaluator, - &mut coset_evaluator, theta, - advice_values, - &fixed_values, - instance_values, - advice_cosets, - &fixed_cosets, - instance_cosets, + &advice.advice_polys, + &pk.fixed_values, + &instance.instance_values, &mut rng, transcript, ) @@ -456,7 +361,7 @@ pub fn create_proof< let gamma: ChallengeGamma<_> = transcript.squeeze_challenge_scalar(); // Commit to permutations. - let permutations: Vec> = instance + let permutations: Vec> = instance .iter() .zip(advice.iter()) .map(|(instance, advice)| { @@ -464,36 +369,24 @@ pub fn create_proof< params, pk, &pk.permutation, - &advice.advice_values, + &advice.advice_polys, &pk.fixed_values, &instance.instance_values, beta, gamma, - &mut coset_evaluator, &mut rng, transcript, ) }) .collect::, _>>()?; - let lookups: Vec>> = lookups + let lookups: Vec>> = lookups .into_iter() .map(|lookups| -> Result, _> { // Construct and commit to products for each lookup lookups .into_iter() - .map(|lookup| { - lookup.commit_product( - pk, - params, - theta, - beta, - gamma, - &mut coset_evaluator, - &mut rng, - transcript, - ) - }) + .map(|lookup| lookup.commit_product(pk, params, beta, gamma, &mut rng, transcript)) .collect::, _>>() }) .collect::, _>>()?; @@ -504,96 +397,49 @@ pub fn create_proof< // Obtain challenge for keeping all separate gates linearly independent let y: ChallengeY<_> = transcript.squeeze_challenge_scalar(); - // Evaluate the h(X) polynomial's constraint system expressions for the permutation constraints. - let (permutations, permutation_expressions): (Vec<_>, Vec<_>) = permutations + // Calculate the advice polys + let advice: Vec> = advice .into_iter() - .zip(advice_cosets.iter()) - .zip(instance_cosets.iter()) - .map(|((permutation, advice), instance)| { - permutation.construct( - pk, - &pk.vk.cs.permutation, - advice, - &fixed_cosets, - instance, - &permutation_cosets, - l0, - l_blind, - l_last, - beta, - gamma, - ) - }) - .unzip(); - - let (lookups, lookup_expressions): (Vec>, Vec>) = lookups - .into_iter() - .map(|lookups| { - // Evaluate the h(X) polynomial's constraint system expressions for the lookup constraints, if any. - lookups - .into_iter() - .map(|p| p.construct(theta, beta, gamma, l0, l_blind, l_last)) - .unzip() - }) - .unzip(); - - let expressions = advice_cosets - .iter() - .zip(instance_cosets.iter()) - .zip(permutation_expressions.into_iter()) - .zip(lookup_expressions.into_iter()) - .flat_map( - |(((advice_cosets, instance_cosets), permutation_expressions), lookup_expressions)| { - let fixed_cosets = &fixed_cosets; - iter::empty() - // Custom constraints - .chain(meta.gates.iter().flat_map(move |gate| { - gate.polynomials().iter().map(move |expr| { - expr.evaluate( - &poly::Ast::ConstantTerm, - &|_| panic!("virtual selectors are removed during optimization"), - &|query| { - fixed_cosets[query.column_index] - .with_rotation(query.rotation) - .into() - }, - &|query| { - advice_cosets[query.column_index] - .with_rotation(query.rotation) - .into() - }, - &|query| { - instance_cosets[query.column_index] - .with_rotation(query.rotation) - .into() - }, - &|a| -a, - &|a, b| a + b, - &|a, b| a * b, - &|a, scalar| a * scalar, - ) - }) - })) - // Permutation constraints, if any. - .chain(permutation_expressions.into_iter()) - // Lookup constraints, if any. - .chain(lookup_expressions.into_iter().flatten()) + .map( + |AdviceSingle { + advice_polys, + advice_blinds, + }| { + AdviceSingle { + advice_polys: advice_polys + .into_iter() + .map(|poly| domain.lagrange_to_coeff(poly)) + .collect::>(), + advice_blinds, + } }, - ); + ) + .collect(); + + // Evaluate the h(X) polynomial + let h_poly = pk.ev.evaluate_h( + pk, + &advice + .iter() + .map(|a| a.advice_polys.as_slice()) + .collect::>(), + &instance + .iter() + .map(|i| i.instance_polys.as_slice()) + .collect::>(), + *y, + *beta, + *gamma, + *theta, + &lookups, + &permutations, + ); // Construct the vanishing argument's h(X) commitments - let vanishing = vanishing.construct( - params, - domain, - coset_evaluator, - expressions, - y, - &mut rng, - transcript, - )?; + let vanishing = vanishing.construct(params, domain, h_poly, &mut rng, transcript)?; let x: ChallengeX<_> = transcript.squeeze_challenge_scalar(); - let xn = x.pow(&[params.n as u64, 0, 0, 0]); + 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() { @@ -655,13 +501,13 @@ pub fn create_proof< pk.permutation.evaluate(x, transcript)?; // Evaluate the permutations, if any, at omega^i x. - let permutations: Vec> = permutations + let permutations: Vec> = permutations .into_iter() - .map(|permutation| -> Result<_, _> { permutation.evaluate(pk, x, transcript) }) + .map(|permutation| -> Result<_, _> { permutation.construct().evaluate(pk, x, transcript) }) .collect::, _>>()?; // Evaluate the lookups, if any, at omega^i x. - let lookups: Vec>> = lookups + let lookups: Vec>> = lookups .into_iter() .map(|lookups| -> Result, _> { lookups @@ -718,5 +564,8 @@ pub fn create_proof< // We query the h(X) polynomial at x .chain(vanishing.open(x)); - multiopen::create_proof(params, rng, transcript, instances).map_err(|_| Error::Opening) + let prover = P::new(params); + prover + .create_proof(rng, transcript, instances) + .map_err(|_| Error::ConstraintSystemFailure) } diff --git a/halo2_proofs/src/plonk/vanishing/prover.rs b/halo2_proofs/src/plonk/vanishing/prover.rs index c794070be3..cc52273b59 100644 --- a/halo2_proofs/src/plonk/vanishing/prover.rs +++ b/halo2_proofs/src/plonk/vanishing/prover.rs @@ -10,9 +10,8 @@ use crate::{ plonk::{ChallengeX, ChallengeY, Error}, poly::{ self, - commitment::{Blind, Params}, - multiopen::ProverQuery, - Coeff, EvaluationDomain, ExtendedLagrangeCoeff, Polynomial, + commitment::{Blind, ParamsProver}, + Coeff, EvaluationDomain, ExtendedLagrangeCoeff, Polynomial, ProverQuery, }, transcript::{EncodedChallenge, TranscriptWrite}, }; @@ -35,8 +34,14 @@ pub(in crate::plonk) struct Evaluated { } impl Argument { - pub(in crate::plonk) fn commit, R: RngCore, T: TranscriptWrite>( - params: &Params, + pub(in crate::plonk) fn commit< + 'params, + P: ParamsProver<'params, C>, + E: EncodedChallenge, + R: RngCore, + T: TranscriptWrite, + >( + params: &P, domain: &EvaluationDomain, mut rng: R, transcript: &mut T, @@ -62,24 +67,19 @@ impl Argument { impl Committed { pub(in crate::plonk) fn construct< + 'params, + P: ParamsProver<'params, C>, E: EncodedChallenge, - Ev: Copy + Send + Sync, R: RngCore, T: TranscriptWrite, >( self, - params: &Params, + params: &P, domain: &EvaluationDomain, - evaluator: poly::Evaluator, - expressions: impl Iterator>, - y: ChallengeY, + h_poly: Polynomial, mut rng: R, transcript: &mut T, ) -> Result, Error> { - // Evaluate the h(X) polynomial's constraint system expressions for the constraints provided - let h_poly = poly::Ast::distribute_powers(expressions, *y); // Fold the gates together with the y challenge - let h_poly = evaluator.evaluate(&h_poly, domain); // Evaluate the h(X) polynomial - // Divide by t(X) = X^{params.n} - 1. let h_poly = domain.divide_by_vanishing_poly(h_poly); @@ -88,7 +88,7 @@ impl Committed { // Split h(X) up into pieces let h_pieces = h_poly - .chunks_exact(params.n as usize) + .chunks_exact(params.n() as usize) .map(|v| domain.coeff_from_vec(v.to_vec())) .collect::>(); drop(h_poly); diff --git a/halo2_proofs/src/plonk/vanishing/verifier.rs b/halo2_proofs/src/plonk/vanishing/verifier.rs index c2e3700de1..3570dee6c1 100644 --- a/halo2_proofs/src/plonk/vanishing/verifier.rs +++ b/halo2_proofs/src/plonk/vanishing/verifier.rs @@ -7,7 +7,7 @@ use crate::{ plonk::{Error, VerifyingKey}, poly::{ commitment::{Params, MSM}, - multiopen::VerifierQuery, + VerifierQuery, }, transcript::{read_n_points, EncodedChallenge, TranscriptRead}, }; @@ -30,8 +30,8 @@ pub struct PartiallyEvaluated { random_eval: C::Scalar, } -pub struct Evaluated<'params, C: CurveAffine> { - h_commitment: MSM<'params, C>, +pub struct Evaluated> { + h_commitment: M, random_poly_commitment: C, expected_h_eval: C::Scalar, random_eval: C::Scalar, @@ -87,13 +87,13 @@ impl Constructed { } impl PartiallyEvaluated { - pub(in crate::plonk) fn verify( + pub(in crate::plonk) fn verify<'params, P: Params<'params, C>>( self, - params: &Params, + params: &'params P, expressions: impl Iterator, y: ChallengeY, xn: C::Scalar, - ) -> Evaluated { + ) -> 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()); @@ -103,7 +103,9 @@ impl PartiallyEvaluated { .rev() .fold(params.empty_msm(), |mut acc, commitment| { acc.scale(xn); - acc.append_term(C::Scalar::one(), *commitment); + let commitment: C::CurveExt = (*commitment).into(); + acc.append_term(C::Scalar::one(), commitment); + acc }); @@ -116,14 +118,11 @@ impl PartiallyEvaluated { } } -impl<'params, C: CurveAffine> Evaluated<'params, C> { - pub(in crate::plonk) fn queries<'r>( - &'r self, +impl> Evaluated { + pub(in crate::plonk) fn queries( + &self, x: ChallengeX, - ) -> impl Iterator> + Clone - where - 'params: 'r, - { + ) -> impl Iterator> + Clone { iter::empty() .chain(Some(VerifierQuery::new_msm( &self.h_commitment, diff --git a/halo2_proofs/src/plonk/verifier.rs b/halo2_proofs/src/plonk/verifier.rs index 4769a8908b..db6366deaf 100644 --- a/halo2_proofs/src/plonk/verifier.rs +++ b/halo2_proofs/src/plonk/verifier.rs @@ -8,9 +8,11 @@ use super::{ VerifyingKey, }; use crate::arithmetic::{CurveAffine, FieldExt}; +use crate::poly::commitment::{CommitmentScheme, Verifier}; +use crate::poly::VerificationStrategy; use crate::poly::{ - commitment::{Blind, Guard, Params, MSM}, - multiopen::{self, VerifierQuery}, + commitment::{Blind, Params, MSM}, + Guard, VerifierQuery, }; use crate::transcript::{read_n_points, read_n_scalars, EncodedChallenge, TranscriptRead}; @@ -19,65 +21,23 @@ mod batch; #[cfg(feature = "batch")] pub use batch::BatchVerifier; -/// Trait representing a strategy for verifying Halo 2 proofs. -pub trait VerificationStrategy<'params, C: CurveAffine> { - /// The output type of this verification strategy after processing a proof. - type Output; - - /// Obtains an MSM from the verifier strategy and yields back the strategy's - /// output. - fn process>( - self, - f: impl FnOnce(MSM<'params, C>) -> Result, Error>, - ) -> Result; -} - -/// A verifier that checks a single proof at a time. -#[derive(Debug)] -pub struct SingleVerifier<'params, C: CurveAffine> { - msm: MSM<'params, C>, -} - -impl<'params, C: CurveAffine> SingleVerifier<'params, C> { - /// Constructs a new single proof verifier. - pub fn new(params: &'params Params) -> Self { - SingleVerifier { - msm: MSM::new(params), - } - } -} - -impl<'params, C: CurveAffine> VerificationStrategy<'params, C> for SingleVerifier<'params, C> { - type Output = (); - - fn process>( - self, - f: impl FnOnce(MSM<'params, C>) -> Result, Error>, - ) -> Result { - let guard = f(self.msm)?; - let msm = guard.use_challenges(); - if msm.eval() { - Ok(()) - } else { - Err(Error::ConstraintSystemFailure) - } - } -} +use crate::poly::commitment::ParamsVerifier; /// Returns a boolean indicating whether or not the proof is valid pub fn verify_proof< 'params, - C: CurveAffine, - E: EncodedChallenge, - T: TranscriptRead, - V: VerificationStrategy<'params, C>, + Scheme: CommitmentScheme, + V: Verifier<'params, Scheme>, + E: EncodedChallenge, + T: TranscriptRead, + Strategy: VerificationStrategy<'params, Scheme, V>, >( - params: &'params Params, - vk: &VerifyingKey, - strategy: V, - instances: &[&[&[C::Scalar]]], + params: &'params Scheme::ParamsVerifier, + vk: &VerifyingKey, + strategy: Strategy, + instances: &[&[&[Scheme::Scalar]]], transcript: &mut T, -) -> Result { +) -> Result { // Check that instances matches the expected number of instance columns for instances in instances.iter() { if instances.len() != vk.cs.num_instance_columns { @@ -91,11 +51,11 @@ pub fn verify_proof< instance .iter() .map(|instance| { - if instance.len() > params.n as usize - (vk.cs.blinding_factors() + 1) { + 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, C::Scalar::zero()); + 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()) @@ -204,7 +164,7 @@ pub fn verify_proof< // commitments open to the correct values. let vanishing = { // x^n - let xn = x.pow(&[params.n as u64, 0, 0, 0]); + let xn = x.pow(&[params.n() as u64, 0, 0, 0]); let blinding_factors = vk.cs.blinding_factors(); let l_evals = vk @@ -212,9 +172,9 @@ pub fn verify_proof< .l_i_range(*x, xn, (-((blinding_factors + 1) as i32))..=0); assert_eq!(l_evals.len(), 2 + blinding_factors); let l_last = l_evals[0]; - let l_blind: C::Scalar = l_evals[1..(1 + blinding_factors)] + let l_blind: Scheme::Scalar = l_evals[1..(1 + blinding_factors)] .iter() - .fold(C::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) @@ -342,7 +302,11 @@ pub fn verify_proof< // We are now convinced the circuit is satisfied so long as the // polynomial commitments open to the correct values. + + let verifier = V::new(params); strategy.process(|msm| { - multiopen::verify_proof(params, transcript, queries, msm).map_err(|_| Error::Opening) + verifier + .verify_proof(transcript, queries, msm) + .map_err(|_| Error::Opening) }) } diff --git a/halo2_proofs/src/plonk/verifier/batch.rs b/halo2_proofs/src/plonk/verifier/batch.rs index b0375ad245..f07ba4141f 100644 --- a/halo2_proofs/src/plonk/verifier/batch.rs +++ b/halo2_proofs/src/plonk/verifier/batch.rs @@ -1,7 +1,7 @@ use std::{io, marker::PhantomData}; use group::ff::Field; -use pasta_curves::arithmetic::CurveAffine; +use halo2curves::CurveAffine; use rand_core::{OsRng, RngCore}; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; @@ -9,8 +9,16 @@ use super::{verify_proof, VerificationStrategy}; use crate::{ multicore, plonk::{Error, VerifyingKey}, - poly::commitment::{Guard, Params, MSM}, - transcript::{Blake2bRead, EncodedChallenge, TranscriptRead}, + poly::{ + commitment::{Params, MSM}, + ipa::{ + commitment::{IPACommitmentScheme, ParamsVerifierIPA}, + msm::MSMIPA, + multiopen::VerifierIPA, + strategy::GuardIPA, + }, + }, + transcript::{Blake2bRead, TranscriptReadBuffer}, }; /// A proof verification strategy that returns the proof's MSM. @@ -18,32 +26,37 @@ use crate::{ /// `BatchVerifier` handles the accumulation of the MSMs for the batched proofs. #[derive(Debug)] struct BatchStrategy<'params, C: CurveAffine> { - msm: MSM<'params, C>, + msm: MSMIPA<'params, C>, } -impl<'params, C: CurveAffine> BatchStrategy<'params, C> { - fn new(params: &'params Params) -> Self { +impl<'params, C: CurveAffine> + VerificationStrategy<'params, IPACommitmentScheme, VerifierIPA<'params, C>> + for BatchStrategy<'params, C> +{ + type Output = MSMIPA<'params, C>; + + fn new(params: &'params ParamsVerifierIPA) -> Self { BatchStrategy { - msm: MSM::new(params), + msm: MSMIPA::new(params), } } -} - -impl<'params, C: CurveAffine> VerificationStrategy<'params, C> for BatchStrategy<'params, C> { - type Output = MSM<'params, C>; - fn process>( + fn process( self, - f: impl FnOnce(MSM<'params, C>) -> Result, Error>, + f: impl FnOnce(MSMIPA<'params, C>) -> Result, Error>, ) -> Result { let guard = f(self.msm)?; Ok(guard.use_challenges()) } + + fn finalize(self) -> bool { + unreachable!() + } } #[derive(Debug)] struct BatchItem { - instances: Vec>>, + instances: Vec>>, proof: Vec, } @@ -73,11 +86,11 @@ impl BatchVerifier { /// This uses [`OsRng`] internally instead of taking an `R: RngCore` argument, because /// the internal parallelization requires access to a RNG that is guaranteed to not /// clone its internal state when shared between threads. - pub fn finalize(self, params: &Params, vk: &VerifyingKey) -> bool { + pub fn finalize(self, params: &ParamsVerifierIPA, vk: &VerifyingKey) -> bool { fn accumulate_msm<'params, C: CurveAffine>( - mut acc: MSM<'params, C>, - msm: MSM<'params, C>, - ) -> MSM<'params, C> { + mut acc: MSMIPA<'params, C>, + msm: MSMIPA<'params, C>, + ) -> MSMIPA<'params, C> { // Scale the MSM by a random factor to ensure that if the existing MSM has // `is_zero() == false` then this argument won't be able to interfere with it // to make it true, with high probability. @@ -113,7 +126,7 @@ impl BatchVerifier { .try_reduce(|| params.empty_msm(), |a, b| Ok(accumulate_msm(a, b))); match final_msm { - Ok(msm) => msm.eval(), + Ok(msm) => msm.check(), Err(_) => false, } } diff --git a/halo2_proofs/src/poly.rs b/halo2_proofs/src/poly.rs index d95fda66f6..ecf66a6e0b 100644 --- a/halo2_proofs/src/poly.rs +++ b/halo2_proofs/src/poly.rs @@ -6,18 +6,29 @@ use crate::arithmetic::parallelize; use crate::plonk::Assigned; use group::ff::{BatchInvert, Field}; -use pasta_curves::arithmetic::FieldExt; +use halo2curves::FieldExt; use std::fmt::Debug; use std::marker::PhantomData; -use std::ops::{Add, Deref, DerefMut, Index, IndexMut, Mul, RangeFrom, RangeFull}; +use std::ops::{Add, Deref, DerefMut, Index, IndexMut, Mul, RangeFrom, RangeFull, Sub}; +/// Generic commitment scheme structures pub mod commitment; mod domain; -mod evaluator; -pub mod multiopen; +mod query; +mod strategy; + +/// Inner product argument commitment scheme +pub mod ipa; + +/// KZG commitment scheme +pub mod kzg; + +#[cfg(test)] +mod multiopen_test; pub use domain::*; -pub use evaluator::*; +pub use query::{ProverQuery, VerifierQuery}; +pub use strategy::{Guard, VerificationStrategy}; /// This is an error that could occur during proving or circuit synthesis. // TODO: these errors need to be cleaned up @@ -195,6 +206,20 @@ impl<'a, F: Field, B: Basis> Add<&'a Polynomial> for Polynomial { } } +impl<'a, F: Field, B: Basis> Sub<&'a Polynomial> for Polynomial { + type Output = Polynomial; + + fn sub(mut self, rhs: &'a Polynomial) -> Polynomial { + parallelize(&mut self.values, |lhs, start| { + for (lhs, rhs) in lhs.iter_mut().zip(rhs.values[start..].iter()) { + *lhs -= *rhs; + } + }); + + self + } +} + impl Polynomial { /// Rotates the values in a Lagrange basis polynomial by `Rotation` pub fn rotate(&self, rotation: Rotation) -> Polynomial { @@ -215,6 +240,16 @@ impl Mul for Polynomial { type Output = Polynomial; fn mul(mut self, rhs: F) -> Polynomial { + if rhs == F::zero() { + return Polynomial { + values: vec![F::zero(); self.len()], + _marker: PhantomData, + }; + } + if rhs == F::one() { + return self; + } + parallelize(&mut self.values, |lhs, _| { for lhs in lhs.iter_mut() { *lhs *= rhs; @@ -225,6 +260,16 @@ impl Mul for Polynomial { } } +impl<'a, F: Field, B: Basis> Sub for &'a Polynomial { + type Output = Polynomial; + + fn sub(self, rhs: F) -> Polynomial { + let mut res = self.clone(); + res.values[0] -= rhs; + res + } +} + /// Describes the relative rotation of a vector. Negative numbers represent /// reverse (leftmost) rotations and positive numbers represent forward (rightmost) /// rotations. Zero represents no rotation. diff --git a/halo2_proofs/src/poly/commitment.rs b/halo2_proofs/src/poly/commitment.rs index f14413be6e..ef336d595f 100644 --- a/halo2_proofs/src/poly/commitment.rs +++ b/halo2_proofs/src/poly/commitment.rs @@ -1,205 +1,185 @@ -//! This module contains an implementation of the polynomial commitment scheme -//! described in the [Halo][halo] paper. -//! -//! [halo]: https://eprint.iacr.org/2019/1021 - -use super::{Coeff, LagrangeCoeff, Polynomial}; -use crate::arithmetic::{ - best_fft, best_multiexp, parallelize, CurveAffine, CurveExt, FieldExt, Group, +use super::{ + query::{ProverQuery, VerifierQuery}, + strategy::Guard, + Coeff, LagrangeCoeff, Polynomial, }; -use crate::helpers::CurveRead; - -use ff::{Field, PrimeField}; -use group::{prime::PrimeCurveAffine, Curve, Group as _}; -use std::ops::{Add, AddAssign, Mul, MulAssign}; - -mod msm; -mod prover; -mod verifier; - -pub use msm::MSM; -pub use prover::create_proof; -pub use verifier::{verify_proof, Accumulator, Guard}; - -use std::io; - -/// These are the public parameters for the polynomial commitment scheme. -#[derive(Clone, Debug)] -pub struct Params { - pub(crate) k: u32, - pub(crate) n: u64, - pub(crate) g: Vec, - pub(crate) g_lagrange: Vec, - pub(crate) w: C, - pub(crate) u: C, +use crate::poly::Error; +use crate::transcript::{EncodedChallenge, TranscriptRead, TranscriptWrite}; +use ff::Field; +use group::Curve; +use halo2curves::{CurveAffine, CurveExt, FieldExt}; +use rand_core::RngCore; +use std::{ + fmt::Debug, + io::{self, Read, Write}, + ops::{Add, AddAssign, Mul, MulAssign}, +}; + +/// Defines components of a commitment scheme. +pub trait CommitmentScheme { + /// Application field of this commitment scheme + type Scalar: FieldExt + halo2curves::Group; + + /// Elliptic curve used to commit the application and witnesses + type Curve: CurveAffine; + + /// Constant prover parameters + type ParamsProver: for<'params> ParamsProver< + 'params, + Self::Curve, + ParamsVerifier = Self::ParamsVerifier, + >; + + /// Constant verifier parameters + type ParamsVerifier: for<'params> ParamsVerifier<'params, Self::Curve>; + + /// Wrapper for parameter generator + fn new_params(k: u32) -> Self::ParamsProver; + + /// Wrapper for parameter reader + fn read_params(reader: &mut R) -> io::Result; } -impl Params { - /// Initializes parameters for the curve, given a random oracle to draw - /// points from. - pub fn new(k: u32) -> Self { - // This is usually a limitation on the curve, but we also want 32-bit - // architectures to be supported. - assert!(k < 32); - - // In src/arithmetic/fields.rs we ensure that usize is at least 32 bits. - - let n: u64 = 1 << k; - - let g_projective = { - let mut g = Vec::with_capacity(n as usize); - g.resize(n as usize, C::Curve::identity()); - - parallelize(&mut g, move |g, start| { - let hasher = C::CurveExt::hash_to_curve("Halo2-Parameters"); - - for (i, g) in g.iter_mut().enumerate() { - let i = (i + start) as u32; - - let mut message = [0u8; 5]; - message[1..5].copy_from_slice(&i.to_le_bytes()); - - *g = hasher(&message); - } - }); - - g - }; - - let g = { - let mut g = vec![C::identity(); n as usize]; - parallelize(&mut g, |g, starts| { - C::Curve::batch_normalize(&g_projective[starts..(starts + g.len())], g); - }); - g - }; - - // Let's evaluate all of the Lagrange basis polynomials - // using an inverse FFT. - let mut alpha_inv = <::Curve as Group>::Scalar::ROOT_OF_UNITY_INV; - for _ in k..C::Scalar::S { - alpha_inv = alpha_inv.square(); - } - let mut g_lagrange_projective = g_projective; - best_fft(&mut g_lagrange_projective, alpha_inv, k); - let minv = C::Scalar::TWO_INV.pow_vartime(&[k as u64, 0, 0, 0]); - parallelize(&mut g_lagrange_projective, |g, _| { - for g in g.iter_mut() { - *g *= minv; - } - }); - - let g_lagrange = { - let mut g_lagrange = vec![C::identity(); n as usize]; - parallelize(&mut g_lagrange, |g_lagrange, starts| { - C::Curve::batch_normalize( - &g_lagrange_projective[starts..(starts + g_lagrange.len())], - g_lagrange, - ); - }); - drop(g_lagrange_projective); - g_lagrange - }; - - let hasher = C::CurveExt::hash_to_curve("Halo2-Parameters"); - let w = hasher(&[1]).to_affine(); - let u = hasher(&[2]).to_affine(); - - Params { - k, - n, - g, - g_lagrange, - w, - u, - } - } +/// Parameters for circuit sysnthesis and prover parameters. +pub trait Params<'params, C: CurveAffine>: Sized + Clone { + /// Multi scalar multiplication engine + type MSM: MSM + 'params; - /// This computes a commitment to a polynomial described by the provided - /// slice of coefficients. The commitment will be blinded by the blinding - /// factor `r`. - pub fn commit(&self, poly: &Polynomial, r: Blind) -> C::Curve { - let mut tmp_scalars = Vec::with_capacity(poly.len() + 1); - let mut tmp_bases = Vec::with_capacity(poly.len() + 1); + /// Logaritmic size of the circuit + fn k(&self) -> u32; - tmp_scalars.extend(poly.iter()); - tmp_scalars.push(r.0); + /// Size of the circuit + fn n(&self) -> u64; - tmp_bases.extend(self.g.iter()); - tmp_bases.push(self.w); + /// Downsize `Params` with smaller `k`. + fn downsize(&mut self, k: u32); - best_multiexp::(&tmp_scalars, &tmp_bases) - } + /// Generates an empty multiscalar multiplication struct using the + /// appropriate params. + fn empty_msm(&'params self) -> Self::MSM; /// This commits to a polynomial using its evaluations over the $2^k$ size /// evaluation domain. The commitment will be blinded by the blinding factor /// `r`. - pub fn commit_lagrange( + fn commit_lagrange( &self, - poly: &Polynomial, - r: Blind, - ) -> C::Curve { - let mut tmp_scalars = Vec::with_capacity(poly.len() + 1); - let mut tmp_bases = Vec::with_capacity(poly.len() + 1); + poly: &Polynomial, + r: Blind, + ) -> C::CurveExt; - tmp_scalars.extend(poly.iter()); - tmp_scalars.push(r.0); + /// Writes params to a buffer. + fn write(&self, writer: &mut W) -> io::Result<()>; - tmp_bases.extend(self.g_lagrange.iter()); - tmp_bases.push(self.w); + /// Reads params from a buffer. + fn read(reader: &mut R) -> io::Result; +} - best_multiexp::(&tmp_scalars, &tmp_bases) - } +/// Parameters for circuit sysnthesis and prover parameters. +pub trait ParamsProver<'params, C: CurveAffine>: Params<'params, C> { + /// Constant verifier parameters. + type ParamsVerifier: ParamsVerifier<'params, C>; - /// Generates an empty multiscalar multiplication struct using the - /// appropriate params. - pub fn empty_msm(&self) -> MSM { - MSM::new(self) - } + /// Returns new instance of parameters + fn new(k: u32) -> Self; + + /// This computes a commitment to a polynomial described by the provided + /// slice of coefficients. The commitment may be blinded by the blinding + /// factor `r`. + fn commit(&self, poly: &Polynomial, r: Blind) + -> C::CurveExt; /// Getter for g generators - pub fn get_g(&self) -> Vec { - self.g.clone() - } + fn get_g(&self) -> &[C]; - /// Writes params to a buffer. - pub fn write(&self, writer: &mut W) -> io::Result<()> { - writer.write_all(&self.k.to_le_bytes())?; - for g_element in &self.g { - writer.write_all(g_element.to_bytes().as_ref())?; - } - for g_lagrange_element in &self.g_lagrange { - writer.write_all(g_lagrange_element.to_bytes().as_ref())?; - } - writer.write_all(self.w.to_bytes().as_ref())?; - writer.write_all(self.u.to_bytes().as_ref())?; - - Ok(()) - } + /// Returns verification parameters. + fn verifier_params(&'params self) -> &'params Self::ParamsVerifier; +} - /// Reads params from a buffer. - pub fn read(reader: &mut R) -> io::Result { - let mut k = [0u8; 4]; - reader.read_exact(&mut k[..])?; - let k = u32::from_le_bytes(k); - - let n: u64 = 1 << k; - - let g: Vec<_> = (0..n).map(|_| C::read(reader)).collect::>()?; - let g_lagrange: Vec<_> = (0..n).map(|_| C::read(reader)).collect::>()?; - - let w = C::read(reader)?; - let u = C::read(reader)?; - - Ok(Params { - k, - n, - g, - g_lagrange, - w, - u, - }) - } +/// Verifier specific functionality with circuit constaints +pub trait ParamsVerifier<'params, C: CurveAffine>: Params<'params, C> {} + +/// Multi scalar multiplication engine +pub trait MSM: Clone + Debug { + /// Add arbitrary term (the scalar and the point) + fn append_term(&mut self, scalar: C::Scalar, point: C::CurveExt); + + /// Add another multiexp into this one + fn add_msm(&mut self, other: &Self) + where + Self: Sized; + + /// Scale all scalars in the MSM by some scaling factor + fn scale(&mut self, factor: C::Scalar); + + /// Perform multiexp and check that it results in zero + fn check(&self) -> bool; + + /// Perform multiexp and return the result + fn eval(&self) -> C::CurveExt; + + /// Return base points + fn bases(&self) -> Vec; + + /// Scalars + fn scalars(&self) -> Vec; +} + +/// Common multi-open prover interface for various commitment schemes +pub trait Prover<'params, Scheme: CommitmentScheme> { + /// Creates new prover instance + fn new(params: &'params Scheme::ParamsProver) -> Self; + + /// Create a multi-opening proof + fn create_proof< + 'com, + E: EncodedChallenge, + T: TranscriptWrite, + R, + I, + >( + &self, + rng: R, + transcript: &mut T, + queries: I, + ) -> io::Result<()> + where + I: IntoIterator> + Clone, + R: RngCore; +} + +/// Common multi-open verifier interface for various commitment schemes +pub trait Verifier<'params, Scheme: CommitmentScheme> { + /// Unfinalized verification result. This is returned in verification + /// to allow developer to compress or combined verification results + type Guard: Guard; + + /// Accumulator fot comressed verification + type MSMAccumulator; + + /// Creates new verifier instance + fn new(params: &'params Scheme::ParamsVerifier) -> Self; + + /// Process the proof and returns unfinished result named `Guard` + fn verify_proof< + 'com, + E: EncodedChallenge, + T: TranscriptRead, + I, + >( + &self, + transcript: &mut T, + queries: I, + msm: Self::MSMAccumulator, + ) -> Result + where + 'params: 'com, + I: IntoIterator< + Item = VerifierQuery< + 'com, + Scheme::Curve, + >::MSM, + >, + > + Clone; } /// Wrapper type around a blinding factor. @@ -212,6 +192,13 @@ impl Default for Blind { } } +impl Blind { + /// Given `rng` creates new blinding scalar + pub fn new(rng: &mut R) -> Self { + Blind(F::random(rng)) + } +} + impl Add for Blind { type Output = Self; @@ -251,126 +238,3 @@ impl MulAssign for Blind { self.0 *= rhs; } } - -#[test] -fn test_commit_lagrange_epaffine() { - const K: u32 = 6; - - use rand_core::OsRng; - - use crate::pasta::{EpAffine, Fq}; - let params = Params::::new(K); - let domain = super::EvaluationDomain::new(1, K); - - let mut a = domain.empty_lagrange(); - - for (i, a) in a.iter_mut().enumerate() { - *a = Fq::from(i as u64); - } - - let b = domain.lagrange_to_coeff(a.clone()); - - let alpha = Blind(Fq::random(OsRng)); - - assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha)); -} - -#[test] -fn test_commit_lagrange_eqaffine() { - const K: u32 = 6; - - use rand_core::OsRng; - - use crate::pasta::{EqAffine, Fp}; - let params = Params::::new(K); - let domain = super::EvaluationDomain::new(1, K); - - let mut a = domain.empty_lagrange(); - - for (i, a) in a.iter_mut().enumerate() { - *a = Fp::from(i as u64); - } - - let b = domain.lagrange_to_coeff(a.clone()); - - let alpha = Blind(Fp::random(OsRng)); - - assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha)); -} - -#[test] -fn test_opening_proof() { - const K: u32 = 6; - - use ff::Field; - use rand_core::OsRng; - - use super::{ - commitment::{Blind, Params}, - EvaluationDomain, - }; - use crate::arithmetic::{eval_polynomial, FieldExt}; - use crate::pasta::{EpAffine, Fq}; - use crate::transcript::{ - Blake2bRead, Blake2bWrite, Challenge255, Transcript, TranscriptRead, TranscriptWrite, - }; - - let rng = OsRng; - - let params = Params::::new(K); - let mut params_buffer = vec![]; - params.write(&mut params_buffer).unwrap(); - let params: Params = Params::read::<_>(&mut ¶ms_buffer[..]).unwrap(); - - let domain = EvaluationDomain::new(1, K); - - let mut px = domain.empty_coeff(); - - for (i, a) in px.iter_mut().enumerate() { - *a = Fq::from(i as u64); - } - - let blind = Blind(Fq::random(rng)); - - let p = params.commit(&px, blind).to_affine(); - - let mut transcript = Blake2bWrite::, EpAffine, Challenge255>::init(vec![]); - transcript.write_point(p).unwrap(); - let x = transcript.squeeze_challenge_scalar::<()>(); - // Evaluate the polynomial - let v = eval_polynomial(&px, *x); - transcript.write_scalar(v).unwrap(); - - let (proof, ch_prover) = { - create_proof(¶ms, rng, &mut transcript, &px, blind, *x).unwrap(); - let ch_prover = transcript.squeeze_challenge(); - (transcript.finalize(), ch_prover) - }; - - // Verify the opening proof - let mut transcript = Blake2bRead::<&[u8], EpAffine, Challenge255>::init(&proof[..]); - let p_prime = transcript.read_point().unwrap(); - assert_eq!(p, p_prime); - let x_prime = transcript.squeeze_challenge_scalar::<()>(); - assert_eq!(*x, *x_prime); - let v_prime = transcript.read_scalar().unwrap(); - assert_eq!(v, v_prime); - - let mut commitment_msm = params.empty_msm(); - commitment_msm.append_term(Field::one(), p); - let guard = verify_proof(¶ms, commitment_msm, &mut transcript, *x, v).unwrap(); - let ch_verifier = transcript.squeeze_challenge(); - assert_eq!(*ch_prover, *ch_verifier); - - // Test guard behavior prior to checking another proof - { - // Test use_challenges() - let msm_challenges = guard.clone().use_challenges(); - assert!(msm_challenges.eval()); - - // Test use_g() - let g = guard.compute_g(); - let (msm_g, _accumulator) = guard.clone().use_g(g); - assert!(msm_g.eval()); - } -} diff --git a/halo2_proofs/src/poly/commitment/verifier.rs b/halo2_proofs/src/poly/commitment/verifier.rs deleted file mode 100644 index e1c813dd3a..0000000000 --- a/halo2_proofs/src/poly/commitment/verifier.rs +++ /dev/null @@ -1,168 +0,0 @@ -use group::{ - ff::{BatchInvert, Field}, - Curve, -}; - -use super::super::Error; -use super::{Params, MSM}; -use crate::transcript::{EncodedChallenge, TranscriptRead}; - -use crate::arithmetic::{best_multiexp, CurveAffine}; - -/// A guard returned by the verifier -#[derive(Debug, Clone)] -pub struct Guard<'a, C: CurveAffine, E: EncodedChallenge> { - msm: MSM<'a, C>, - neg_c: C::Scalar, - u: Vec, - u_packed: Vec, -} - -/// An accumulator instance consisting of an evaluation claim and a proof. -#[derive(Debug, Clone)] -pub struct Accumulator> { - /// The claimed output of the linear-time polycommit opening protocol - pub g: C, - - /// A vector of challenges u_0, ..., u_{k - 1} sampled by the verifier, to - /// be used in computing G'_0. - pub u_packed: Vec, -} - -impl<'a, C: CurveAffine, E: EncodedChallenge> Guard<'a, C, E> { - /// Lets caller supply the challenges and obtain an MSM with updated - /// scalars and points. - pub fn use_challenges(mut self) -> MSM<'a, C> { - let s = compute_s(&self.u, self.neg_c); - self.msm.add_to_g_scalars(&s); - - self.msm - } - - /// Lets caller supply the purported G point and simply appends - /// [-c] G to return an updated MSM. - pub fn use_g(mut self, g: C) -> (MSM<'a, C>, Accumulator) { - self.msm.append_term(self.neg_c, g); - - let accumulator = Accumulator { - g, - u_packed: self.u_packed, - }; - - (self.msm, accumulator) - } - - /// Computes G = ⟨s, params.g⟩ - pub fn compute_g(&self) -> C { - let s = compute_s(&self.u, C::Scalar::one()); - - best_multiexp(&s, &self.msm.params.g).to_affine() - } -} - -/// Checks to see if the proof represented within `transcript` is valid, and a -/// point `x` that the polynomial commitment `P` opens purportedly to the value -/// `v`. The provided `msm` should evaluate to the commitment `P` being opened. -pub fn verify_proof<'a, C: CurveAffine, E: EncodedChallenge, T: TranscriptRead>( - params: &'a Params, - mut msm: MSM<'a, C>, - transcript: &mut T, - x: C::Scalar, - v: C::Scalar, -) -> Result, Error> { - let k = params.k as usize; - - // P' = P - [v] G_0 + [ξ] S - msm.add_constant_term(-v); // add [-v] G_0 - let s_poly_commitment = transcript.read_point().map_err(|_| Error::OpeningError)?; - let xi = *transcript.squeeze_challenge_scalar::<()>(); - msm.append_term(xi, s_poly_commitment); - - let z = *transcript.squeeze_challenge_scalar::<()>(); - - let mut rounds = vec![]; - for _ in 0..k { - // Read L and R from the proof and write them to the transcript - let l = transcript.read_point().map_err(|_| Error::OpeningError)?; - let r = transcript.read_point().map_err(|_| Error::OpeningError)?; - - let u_j_packed = transcript.squeeze_challenge(); - let u_j = *u_j_packed.as_challenge_scalar::<()>(); - - rounds.push((l, r, u_j, /* to be inverted */ u_j, u_j_packed)); - } - - rounds - .iter_mut() - .map(|&mut (_, _, _, ref mut u_j, _)| u_j) - .batch_invert(); - - // This is the left-hand side of the verifier equation. - // P' + \sum([u_j^{-1}] L_j) + \sum([u_j] R_j) - let mut u = Vec::with_capacity(k); - let mut u_packed: Vec = Vec::with_capacity(k); - for (l, r, u_j, u_j_inv, u_j_packed) in rounds { - msm.append_term(u_j_inv, l); - msm.append_term(u_j, r); - - u.push(u_j); - u_packed.push(u_j_packed); - } - - // Our goal is to check that the left hand side of the verifier - // equation - // P' + \sum([u_j^{-1}] L_j) + \sum([u_j] R_j) - // equals (given b = \mathbf{b}_0, and the prover's values c, f), - // the right-hand side - // = [c] (G'_0 + [b * z] U) + [f] W - // Subtracting the right-hand side from both sides we get - // P' + \sum([u_j^{-1}] L_j) + \sum([u_j] R_j) - // + [-c] G'_0 + [-cbz] U + [-f] W - // = 0 - - let c = transcript.read_scalar().map_err(|_| Error::SamplingError)?; - let neg_c = -c; - let f = transcript.read_scalar().map_err(|_| Error::SamplingError)?; - let b = compute_b(x, &u); - - msm.add_to_u_scalar(neg_c * &b * &z); - msm.add_to_w_scalar(-f); - - let guard = Guard { - msm, - neg_c, - u, - u_packed, - }; - - Ok(guard) -} - -/// 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 cur = x; - for u_j in u.iter().rev() { - tmp *= F::one() + &(*u_j * &cur); - cur *= cur; - } - tmp -} - -/// 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()]; - v[0] = init; - - for (len, u_j) in u.iter().rev().enumerate().map(|(i, u_j)| (1 << i, u_j)) { - let (left, right) = v.split_at_mut(len); - let right = &mut right[0..len]; - right.copy_from_slice(left); - for v in right { - *v *= u_j; - } - } - - v -} diff --git a/halo2_proofs/src/poly/domain.rs b/halo2_proofs/src/poly/domain.rs index 708d72ee1e..a753cb859d 100644 --- a/halo2_proofs/src/poly/domain.rs +++ b/halo2_proofs/src/poly/domain.rs @@ -360,6 +360,16 @@ impl EvaluationDomain { }); } + /// Get the size of the domain + pub fn k(&self) -> u32 { + self.k + } + + /// Get the size of the extended domain + pub fn extended_k(&self) -> u32 { + self.extended_k + } + /// Get the size of the extended domain pub fn extended_len(&self) -> usize { 1 << self.extended_k @@ -480,7 +490,7 @@ fn test_rotate() { use rand_core::OsRng; use crate::arithmetic::eval_polynomial; - use crate::pasta::pallas::Scalar; + use halo2curves::pasta::pallas::Scalar; let domain = EvaluationDomain::::new(1, 3); let rng = OsRng; @@ -521,7 +531,7 @@ fn test_l_i() { use rand_core::OsRng; use crate::arithmetic::{eval_polynomial, lagrange_interpolate}; - use crate::pasta::pallas::Scalar; + use halo2curves::pasta::pallas::Scalar; let domain = EvaluationDomain::::new(1, 3); let mut l = vec![]; diff --git a/halo2_proofs/src/poly/evaluator.rs b/halo2_proofs/src/poly/evaluator.rs index cda6aac963..d1ba853c47 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 pasta_curves::arithmetic::FieldExt; +use halo2curves::FieldExt; use super::{ Basis, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation, @@ -650,7 +650,7 @@ impl BasisOps for ExtendedLagrangeCoeff { mod tests { use std::iter; - use pasta_curves::pallas; + use halo2curves::pasta::pallas; use super::{get_chunk_params, new_evaluator, Ast, BasisOps, Evaluator}; use crate::{ diff --git a/halo2_proofs/src/poly/ipa/commitment.rs b/halo2_proofs/src/poly/ipa/commitment.rs new file mode 100644 index 0000000000..9060e8315c --- /dev/null +++ b/halo2_proofs/src/poly/ipa/commitment.rs @@ -0,0 +1,384 @@ +//! This module contains an implementation of the polynomial commitment scheme +//! described in the [Halo][halo] paper. +//! +//! [halo]: https://eprint.iacr.org/2019/1021 + +use crate::arithmetic::{ + best_fft, best_multiexp, g_to_lagrange, parallelize, CurveAffine, CurveExt, FieldExt, Group, +}; +use crate::helpers::CurveRead; +use crate::poly::commitment::{Blind, CommitmentScheme, Params, ParamsProver, ParamsVerifier, MSM}; +use crate::poly::ipa::msm::MSMIPA; +use crate::poly::{Coeff, LagrangeCoeff, Polynomial}; + +use ff::{Field, PrimeField}; +use group::{prime::PrimeCurveAffine, Curve, Group as _}; +use std::marker::PhantomData; +use std::ops::{Add, AddAssign, Mul, MulAssign}; + +mod prover; +mod verifier; + +pub use prover::create_proof; +pub use verifier::verify_proof; + +use std::io; + +/// Public parameters for IPA commitment scheme +#[derive(Debug, Clone)] +pub struct ParamsIPA { + pub(crate) k: u32, + pub(crate) n: u64, + pub(crate) g: Vec, + pub(crate) g_lagrange: Vec, + pub(crate) w: C, + pub(crate) u: C, +} + +/// Concrete IPA commitment scheme +#[derive(Debug)] +pub struct IPACommitmentScheme { + _marker: PhantomData, +} + +impl CommitmentScheme for IPACommitmentScheme { + type Scalar = C::ScalarExt; + type Curve = C; + + type ParamsProver = ParamsIPA; + type ParamsVerifier = ParamsVerifierIPA; + + fn new_params(k: u32) -> Self::ParamsProver { + ParamsIPA::new(k) + } + + fn read_params(reader: &mut R) -> io::Result { + ParamsIPA::read(reader) + } +} + +/// Verifier parameters +pub type ParamsVerifierIPA = ParamsIPA; + +impl<'params, C: CurveAffine> ParamsVerifier<'params, C> for ParamsIPA {} + +impl<'params, C: CurveAffine> Params<'params, C> for ParamsIPA { + type MSM = MSMIPA<'params, C>; + + fn k(&self) -> u32 { + self.k + } + + fn n(&self) -> u64 { + self.n + } + + fn downsize(&mut self, k: u32) { + assert!(k <= self.k); + + self.k = k; + self.n = 1 << k; + self.g.truncate(self.n as usize); + self.g_lagrange = g_to_lagrange(self.g.iter().map(|g| g.to_curve()).collect(), k); + } + + fn empty_msm(&'params self) -> MSMIPA { + MSMIPA::new(self) + } + + /// This commits to a polynomial using its evaluations over the $2^k$ size + /// evaluation domain. The commitment will be blinded by the blinding factor + /// `r`. + fn commit_lagrange( + &self, + poly: &Polynomial, + r: Blind, + ) -> C::Curve { + let mut tmp_scalars = Vec::with_capacity(poly.len() + 1); + let mut tmp_bases = Vec::with_capacity(poly.len() + 1); + + tmp_scalars.extend(poly.iter()); + tmp_scalars.push(r.0); + + tmp_bases.extend(self.g_lagrange.iter()); + tmp_bases.push(self.w); + + best_multiexp::(&tmp_scalars, &tmp_bases) + } + + /// Writes params to a buffer. + fn write(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&self.k.to_le_bytes())?; + for g_element in &self.g { + writer.write_all(g_element.to_bytes().as_ref())?; + } + for g_lagrange_element in &self.g_lagrange { + writer.write_all(g_lagrange_element.to_bytes().as_ref())?; + } + writer.write_all(self.w.to_bytes().as_ref())?; + writer.write_all(self.u.to_bytes().as_ref())?; + + Ok(()) + } + + /// Reads params from a buffer. + fn read(reader: &mut R) -> io::Result { + let mut k = [0u8; 4]; + reader.read_exact(&mut k[..])?; + let k = u32::from_le_bytes(k); + + let n: u64 = 1 << k; + + let g: Vec<_> = (0..n).map(|_| C::read(reader)).collect::>()?; + let g_lagrange: Vec<_> = (0..n).map(|_| C::read(reader)).collect::>()?; + + let w = C::read(reader)?; + let u = C::read(reader)?; + + Ok(Self { + k, + n, + g, + g_lagrange, + w, + u, + }) + } +} + +impl<'params, C: CurveAffine> ParamsProver<'params, C> for ParamsIPA { + type ParamsVerifier = ParamsVerifierIPA; + + fn verifier_params(&'params self) -> &'params Self::ParamsVerifier { + self + } + + /// Initializes parameters for the curve, given a random oracle to draw + /// points from. + fn new(k: u32) -> Self { + // This is usually a limitation on the curve, but we also want 32-bit + // architectures to be supported. + assert!(k < 32); + + // In src/arithmetic/fields.rs we ensure that usize is at least 32 bits. + + let n: u64 = 1 << k; + + let g_projective = { + let mut g = Vec::with_capacity(n as usize); + g.resize(n as usize, C::Curve::identity()); + + parallelize(&mut g, move |g, start| { + let hasher = C::CurveExt::hash_to_curve("Halo2-Parameters"); + + for (i, g) in g.iter_mut().enumerate() { + let i = (i + start) as u32; + + let mut message = [0u8; 5]; + message[1..5].copy_from_slice(&i.to_le_bytes()); + + *g = hasher(&message); + } + }); + + g + }; + + let g = { + let mut g = vec![C::identity(); n as usize]; + parallelize(&mut g, |g, starts| { + C::Curve::batch_normalize(&g_projective[starts..(starts + g.len())], g); + }); + g + }; + + // Let's evaluate all of the Lagrange basis polynomials + // using an inverse FFT. + let g_lagrange = g_to_lagrange(g_projective, k); + + let hasher = C::CurveExt::hash_to_curve("Halo2-Parameters"); + let w = hasher(&[1]).to_affine(); + let u = hasher(&[2]).to_affine(); + + ParamsIPA { + k, + n, + g, + g_lagrange, + w, + u, + } + } + + /// This computes a commitment to a polynomial described by the provided + /// slice of coefficients. The commitment will be blinded by the blinding + /// factor `r`. + fn commit(&self, poly: &Polynomial, r: Blind) -> C::Curve { + let mut tmp_scalars = Vec::with_capacity(poly.len() + 1); + let mut tmp_bases = Vec::with_capacity(poly.len() + 1); + + tmp_scalars.extend(poly.iter()); + tmp_scalars.push(r.0); + + tmp_bases.extend(self.g.iter()); + tmp_bases.push(self.w); + + best_multiexp::(&tmp_scalars, &tmp_bases) + } + + fn get_g(&self) -> &[C] { + &self.g + } +} + +#[cfg(test)] +mod test { + + use crate::arithmetic::{ + best_fft, best_multiexp, parallelize, CurveAffine, CurveExt, FieldExt, Group, + }; + use crate::helpers::CurveRead; + use crate::poly::commitment::ParamsProver; + use crate::poly::commitment::{Blind, CommitmentScheme, Params, MSM}; + use crate::poly::ipa::commitment::{create_proof, verify_proof, ParamsIPA}; + use crate::poly::ipa::msm::MSMIPA; + use crate::poly::{Coeff, LagrangeCoeff, Polynomial}; + + use ff::{Field, PrimeField}; + use group::{prime::PrimeCurveAffine, Curve, Group as _}; + use std::marker::PhantomData; + use std::ops::{Add, AddAssign, Mul, MulAssign}; + + use std::io; + + #[test] + fn test_commit_lagrange_epaffine() { + const K: u32 = 6; + + use rand_core::OsRng; + + use crate::poly::EvaluationDomain; + use halo2curves::pasta::{EpAffine, Fq}; + + let params = ParamsIPA::::new(K); + let domain = EvaluationDomain::new(1, K); + + let mut a = domain.empty_lagrange(); + + for (i, a) in a.iter_mut().enumerate() { + *a = Fq::from(i as u64); + } + + let b = domain.lagrange_to_coeff(a.clone()); + + let alpha = Blind(Fq::random(OsRng)); + + assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha)); + } + + #[test] + fn test_commit_lagrange_eqaffine() { + const K: u32 = 6; + + use rand_core::OsRng; + + use crate::poly::EvaluationDomain; + use halo2curves::pasta::{EqAffine, Fp}; + + let params: ParamsIPA = ParamsIPA::::new(K); + let domain = EvaluationDomain::new(1, K); + + let mut a = domain.empty_lagrange(); + + for (i, a) in a.iter_mut().enumerate() { + *a = Fp::from(i as u64); + } + + let b = domain.lagrange_to_coeff(a.clone()); + + let alpha = Blind(Fp::random(OsRng)); + + assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha)); + } + + #[test] + fn test_opening_proof() { + const K: u32 = 6; + + use ff::Field; + use rand_core::OsRng; + + use super::super::commitment::{Blind, Params}; + use crate::arithmetic::{eval_polynomial, FieldExt}; + use crate::halo2curves::pasta::{EpAffine, Fq}; + use crate::poly::EvaluationDomain; + use crate::transcript::{ + Blake2bRead, Blake2bWrite, Challenge255, Transcript, TranscriptRead, TranscriptWrite, + }; + + use crate::transcript::TranscriptReadBuffer; + use crate::transcript::TranscriptWriterBuffer; + + let rng = OsRng; + + let params = ParamsIPA::::new(K); + let mut params_buffer = vec![]; + as Params<_>>::write(¶ms, &mut params_buffer).unwrap(); + let params: ParamsIPA = Params::read::<_>(&mut ¶ms_buffer[..]).unwrap(); + + let domain = EvaluationDomain::new(1, K); + + let mut px = domain.empty_coeff(); + + for (i, a) in px.iter_mut().enumerate() { + *a = Fq::from(i as u64); + } + + let blind = Blind(Fq::random(rng)); + + let p = params.commit(&px, blind).to_affine(); + + let mut transcript = + Blake2bWrite::, EpAffine, Challenge255>::init(vec![]); + transcript.write_point(p).unwrap(); + let x = transcript.squeeze_challenge_scalar::<()>(); + // Evaluate the polynomial + let v = eval_polynomial(&px, *x); + transcript.write_scalar(v).unwrap(); + + let (proof, ch_prover) = { + create_proof(¶ms, rng, &mut transcript, &px, blind, *x).unwrap(); + let ch_prover = transcript.squeeze_challenge(); + (transcript.finalize(), ch_prover) + }; + + // Verify the opening proof + let mut transcript = + Blake2bRead::<&[u8], EpAffine, Challenge255>::init(&proof[..]); + let p_prime = transcript.read_point().unwrap(); + assert_eq!(p, p_prime); + let x_prime = transcript.squeeze_challenge_scalar::<()>(); + assert_eq!(*x, *x_prime); + let v_prime = transcript.read_scalar().unwrap(); + assert_eq!(v, v_prime); + + let mut commitment_msm = MSMIPA::new(¶ms); + commitment_msm.append_term(Field::one(), p.into()); + + let guard = verify_proof(¶ms, commitment_msm, &mut transcript, *x, v).unwrap(); + let ch_verifier = transcript.squeeze_challenge(); + assert_eq!(*ch_prover, *ch_verifier); + + // Test guard behavior prior to checking another proof + { + // Test use_challenges() + let msm_challenges = guard.clone().use_challenges(); + assert!(msm_challenges.check()); + + // Test use_g() + let g = guard.compute_g(); + let (msm_g, _accumulator) = guard.clone().use_g(g); + assert!(msm_g.check()); + } + } +} diff --git a/halo2_proofs/src/poly/commitment/prover.rs b/halo2_proofs/src/poly/ipa/commitment/prover.rs similarity index 97% rename from halo2_proofs/src/poly/commitment/prover.rs rename to halo2_proofs/src/poly/ipa/commitment/prover.rs index f58973c146..3b22b31b36 100644 --- a/halo2_proofs/src/poly/commitment/prover.rs +++ b/halo2_proofs/src/poly/ipa/commitment/prover.rs @@ -1,15 +1,17 @@ use ff::Field; use rand_core::RngCore; -use super::super::{Coeff, Polynomial}; -use super::{Blind, Params}; +use super::{Params, ParamsIPA}; use crate::arithmetic::{ best_multiexp, compute_inner_product, eval_polynomial, parallelize, CurveAffine, FieldExt, }; + +use crate::poly::commitment::ParamsProver; +use crate::poly::{commitment::Blind, Coeff, Polynomial}; use crate::transcript::{EncodedChallenge, TranscriptWrite}; use group::Curve; -use std::io; +use std::io::{self, Write}; /// Create a polynomial commitment opening proof for the polynomial defined /// by the coefficients `px`, the blinding factor `blind` used for the @@ -30,7 +32,7 @@ pub fn create_proof< R: RngCore, T: TranscriptWrite, >( - params: &Params, + params: &ParamsIPA, mut rng: R, transcript: &mut T, p_poly: &Polynomial, diff --git a/halo2_proofs/src/poly/ipa/commitment/verifier.rs b/halo2_proofs/src/poly/ipa/commitment/verifier.rs new file mode 100644 index 0000000000..b3b30e0b4b --- /dev/null +++ b/halo2_proofs/src/poly/ipa/commitment/verifier.rs @@ -0,0 +1,106 @@ +use std::io::Read; + +use group::{ + ff::{BatchInvert, Field}, + Curve, +}; + +use super::ParamsIPA; +use crate::poly::ipa::commitment::{IPACommitmentScheme, ParamsVerifierIPA}; +use crate::{ + arithmetic::{best_multiexp, CurveAffine}, + poly::ipa::strategy::GuardIPA, +}; +use crate::{ + poly::{commitment::MSM, ipa::msm::MSMIPA, strategy::Guard, Error}, + transcript::{EncodedChallenge, TranscriptRead}, +}; + +/// Checks to see if the proof represented within `transcript` is valid, and a +/// point `x` that the polynomial commitment `P` opens purportedly to the value +/// `v`. The provided `msm` should evaluate to the commitment `P` being opened. +pub fn verify_proof<'params, C: CurveAffine, E: EncodedChallenge, T: TranscriptRead>( + params: &'params ParamsIPA, + mut msm: MSMIPA<'params, C>, + transcript: &mut T, + x: C::Scalar, + v: C::Scalar, +) -> Result, Error> { + let k = params.k as usize; + + // P' = P - [v] G_0 + [ξ] S + msm.add_constant_term(-v); // add [-v] G_0 + let s_poly_commitment = transcript.read_point().map_err(|_| Error::OpeningError)?; + let xi = *transcript.squeeze_challenge_scalar::<()>(); + msm.append_term(xi, s_poly_commitment.into()); + + let z = *transcript.squeeze_challenge_scalar::<()>(); + + let mut rounds = vec![]; + for _ in 0..k { + // Read L and R from the proof and write them to the transcript + let l = transcript.read_point().map_err(|_| Error::OpeningError)?; + let r = transcript.read_point().map_err(|_| Error::OpeningError)?; + + let u_j_packed = transcript.squeeze_challenge(); + let u_j = *u_j_packed.as_challenge_scalar::<()>(); + + rounds.push((l, r, u_j, /* to be inverted */ u_j, u_j_packed)); + } + + rounds + .iter_mut() + .map(|&mut (_, _, _, ref mut u_j, _)| u_j) + .batch_invert(); + + // This is the left-hand side of the verifier equation. + // P' + \sum([u_j^{-1}] L_j) + \sum([u_j] R_j) + let mut u = Vec::with_capacity(k); + let mut u_packed: Vec = Vec::with_capacity(k); + for (l, r, u_j, u_j_inv, u_j_packed) in rounds { + msm.append_term(u_j_inv, l.into()); + msm.append_term(u_j, r.into()); + + u.push(u_j); + u_packed.push(u_j_packed.get_scalar()); + } + + // Our goal is to check that the left hand side of the verifier + // equation + // P' + \sum([u_j^{-1}] L_j) + \sum([u_j] R_j) + // equals (given b = \mathbf{b}_0, and the prover's values c, f), + // the right-hand side + // = [c] (G'_0 + [b * z] U) + [f] W + // Subtracting the right-hand side from both sides we get + // P' + \sum([u_j^{-1}] L_j) + \sum([u_j] R_j) + // + [-c] G'_0 + [-cbz] U + [-f] W + // = 0 + + let c = transcript.read_scalar().map_err(|_| Error::SamplingError)?; + let neg_c = -c; + let f = transcript.read_scalar().map_err(|_| Error::SamplingError)?; + let b = compute_b(x, &u); + + msm.add_to_u_scalar(neg_c * &b * &z); + msm.add_to_w_scalar(-f); + + let guard = GuardIPA { + msm, + neg_c, + u, + u_packed, + }; + + Ok(guard) +} + +/// 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 cur = x; + for u_j in u.iter().rev() { + tmp *= F::one() + &(*u_j * &cur); + cur *= cur; + } + tmp +} diff --git a/halo2_proofs/src/poly/ipa/mod.rs b/halo2_proofs/src/poly/ipa/mod.rs new file mode 100644 index 0000000000..3600e2f051 --- /dev/null +++ b/halo2_proofs/src/poly/ipa/mod.rs @@ -0,0 +1,7 @@ +pub mod commitment; +/// Multiscalar multiplication engines +pub mod msm; +/// IPA multi-open scheme +pub mod multiopen; +/// Strategies used with KZG scheme +pub mod strategy; diff --git a/halo2_proofs/src/poly/commitment/msm.rs b/halo2_proofs/src/poly/ipa/msm.rs similarity index 65% rename from halo2_proofs/src/poly/commitment/msm.rs rename to halo2_proofs/src/poly/ipa/msm.rs index 8140ab3d62..63f994b46e 100644 --- a/halo2_proofs/src/poly/commitment/msm.rs +++ b/halo2_proofs/src/poly/ipa/msm.rs @@ -1,14 +1,17 @@ -use super::Params; -use crate::arithmetic::{best_multiexp, CurveAffine}; +use super::commitment::{IPACommitmentScheme, ParamsIPA}; +use crate::arithmetic::{best_multiexp, parallelize, CurveAffine}; +use crate::poly::{ + commitment::{CommitmentScheme, Params, MSM}, + ipa::commitment::ParamsVerifierIPA, +}; use ff::Field; use group::Group; - use std::collections::BTreeMap; /// A multiscalar multiplication in the polynomial commitment scheme #[derive(Debug, Clone)] -pub struct MSM<'a, C: CurveAffine> { - pub(crate) params: &'a Params, +pub struct MSMIPA<'params, C: CurveAffine> { + pub(crate) params: &'params ParamsVerifierIPA, g_scalars: Option>, w_scalar: Option, u_scalar: Option, @@ -16,20 +19,21 @@ pub struct MSM<'a, C: CurveAffine> { other: BTreeMap, } -impl<'a, C: CurveAffine> MSM<'a, C> { - /// Create a new, empty MSM using the provided parameters. - pub fn new(params: &'a Params) -> Self { +impl<'a, C: CurveAffine> MSMIPA<'a, C> { + /// Given verifier parameters Creates an empty multi scalar engine + pub fn new(params: &'a ParamsVerifierIPA) -> Self { let g_scalars = None; let w_scalar = None; let u_scalar = None; let other = BTreeMap::new(); - MSM { - params, + Self { g_scalars, w_scalar, u_scalar, other, + + params, } } @@ -61,10 +65,13 @@ impl<'a, C: CurveAffine> MSM<'a, C> { self.add_to_u_scalar(*u_scalar); } } +} - /// Add arbitrary term (the scalar and the point) - pub fn append_term(&mut self, scalar: C::Scalar, point: C) { +impl<'a, C: CurveAffine> MSM for MSMIPA<'a, C> { + fn append_term(&mut self, scalar: C::Scalar, point: C::Curve) { if !bool::from(point.is_identity()) { + use group::Curve; + let point = point.to_affine(); let xy = point.coordinates().unwrap(); let x = *xy.x(); let y = *xy.y(); @@ -83,42 +90,36 @@ impl<'a, C: CurveAffine> MSM<'a, C> { } } - /// Add a value to the first entry of `g_scalars`. - pub fn add_constant_term(&mut self, constant: C::Scalar) { - 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]; - g_scalars[0] += &constant; - self.g_scalars = Some(g_scalars); + /// Add another multiexp into this one + fn add_msm(&mut self, other: &Self) { + for (x, (scalar, y)) in other.other.iter() { + self.other + .entry(*x) + .and_modify(|(our_scalar, our_y)| { + if our_y == y { + *our_scalar += *scalar; + } else { + assert!(*our_y == -*y); + *our_scalar -= *scalar; + } + }) + .or_insert((*scalar, *y)); } - } - /// Add a vector of scalars to `g_scalars`. This function will panic if the - /// caller provides a slice of scalars that is not of length `params.n`. - pub fn add_to_g_scalars(&mut self, scalars: &[C::Scalar]) { - assert_eq!(scalars.len(), self.params.n as usize); - if let Some(g_scalars) = &mut self.g_scalars { - for (g_scalar, scalar) in g_scalars.iter_mut().zip(scalars.iter()) { - *g_scalar += scalar; - } - } else { - self.g_scalars = Some(scalars.to_vec()); + if let Some(g_scalars) = &other.g_scalars { + self.add_to_g_scalars(g_scalars); } - } - /// Add to `w_scalar` - pub fn add_to_w_scalar(&mut self, scalar: C::Scalar) { - self.w_scalar = self.w_scalar.map_or(Some(scalar), |a| Some(a + &scalar)); - } + if let Some(w_scalar) = &other.w_scalar { + self.add_to_w_scalar(*w_scalar); + } - /// Add to `u_scalar` - pub fn add_to_u_scalar(&mut self, scalar: C::Scalar) { - self.u_scalar = self.u_scalar.map_or(Some(scalar), |a| Some(a + &scalar)); + if let Some(u_scalar) = &other.u_scalar { + self.add_to_u_scalar(*u_scalar); + } } - /// Scale all scalars in the MSM by some scaling factor - pub fn scale(&mut self, factor: C::Scalar) { + fn scale(&mut self, factor: C::Scalar) { if let Some(g_scalars) = &mut self.g_scalars { for g_scalar in g_scalars { *g_scalar *= &factor; @@ -133,8 +134,11 @@ impl<'a, C: CurveAffine> MSM<'a, C> { self.u_scalar = self.u_scalar.map(|a| a * &factor); } - /// Perform multiexp and check that it results in zero - pub fn eval(self) -> bool { + fn check(&self) -> bool { + bool::from(self.eval().is_identity()) + } + + fn eval(&self) -> C::Curve { let len = self.g_scalars.as_ref().map(|v| v.len()).unwrap_or(0) + self.w_scalar.map(|_| 1).unwrap_or(0) + self.u_scalar.map(|_| 1).unwrap_or(0) @@ -166,55 +170,109 @@ impl<'a, C: CurveAffine> MSM<'a, C> { assert_eq!(scalars.len(), len); - bool::from(best_multiexp(&scalars, &bases).is_identity()) + best_multiexp(&scalars, &bases) + } + + fn bases(&self) -> Vec { + self.other + .iter() + .map(|(x, (_, y))| C::from_xy(*x, *y).unwrap().into()) + .collect() + } + + fn scalars(&self) -> Vec { + self.other.values().map(|(scalar, _)| *scalar).collect() + } +} + +impl<'a, C: CurveAffine> MSMIPA<'a, C> { + /// Add a value to the first entry of `g_scalars`. + pub fn add_constant_term(&mut self, constant: C::Scalar) { + 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]; + g_scalars[0] += &constant; + self.g_scalars = Some(g_scalars); + } + } + + /// Add a vector of scalars to `g_scalars`. This function will panic if the + /// caller provides a slice of scalars that is not of length `params.n`. + pub fn add_to_g_scalars(&mut self, scalars: &[C::Scalar]) { + assert_eq!(scalars.len(), self.params.n as usize); + if let Some(g_scalars) = &mut self.g_scalars { + for (g_scalar, scalar) in g_scalars.iter_mut().zip(scalars.iter()) { + *g_scalar += scalar; + } + } else { + self.g_scalars = Some(scalars.to_vec()); + } + } + /// Add to `w_scalar` + pub fn add_to_w_scalar(&mut self, scalar: C::Scalar) { + self.w_scalar = self.w_scalar.map_or(Some(scalar), |a| Some(a + &scalar)); + } + + /// Add to `u_scalar` + pub fn add_to_u_scalar(&mut self, scalar: C::Scalar) { + self.u_scalar = self.u_scalar.map_or(Some(scalar), |a| Some(a + &scalar)); } } #[cfg(test)] mod tests { - use crate::poly::commitment::{Params, MSM}; + use super::ParamsIPA; + use crate::poly::commitment::ParamsProver; + use crate::poly::{ + commitment::{Params, MSM}, + ipa::msm::MSMIPA, + }; use group::Curve; - use pasta_curves::{arithmetic::CurveAffine, EpAffine, Fp, Fq}; + use halo2curves::{ + pasta::{Ep, EpAffine, Fp, Fq}, + CurveAffine, + }; #[test] fn msm_arithmetic() { - let base = EpAffine::from_xy(-Fp::one(), Fp::from(2)).unwrap(); - let base_viol = (base + base).to_affine(); + let base: Ep = EpAffine::from_xy(-Fp::one(), Fp::from(2)).unwrap().into(); + let base_viol = base + base; - let params = Params::new(4); - let mut a: MSM = MSM::new(¶ms); + let params = ParamsIPA::new(4); + let mut a: MSMIPA = MSMIPA::new(¶ms); a.append_term(Fq::one(), base); // a = [1] P - assert!(!a.clone().eval()); + assert!(!a.clone().check()); a.append_term(Fq::one(), base); // a = [1+1] P - assert!(!a.clone().eval()); + assert!(!a.clone().check()); a.append_term(-Fq::one(), base_viol); // a = [1+1] P + [-1] 2P - assert!(a.clone().eval()); + assert!(a.clone().check()); let b = a.clone(); // Append a point that is the negation of an existing one. a.append_term(Fq::from(4), -base); // a = [1+1-4] P + [-1] 2P - assert!(!a.clone().eval()); + assert!(!a.clone().check()); a.append_term(Fq::from(2), base_viol); // a = [1+1-4] P + [-1+2] 2P - assert!(a.clone().eval()); + assert!(a.clone().check()); // Add two MSMs with common bases. a.scale(Fq::from(3)); a.add_msm(&b); // a = [3*(1+1)+(1+1-4)] P + [3*(-1)+(-1+2)] 2P - assert!(a.clone().eval()); + assert!(a.clone().check()); - let mut c: MSM = MSM::new(¶ms); + let mut c: MSMIPA = MSMIPA::new(¶ms); c.append_term(Fq::from(2), base); c.append_term(Fq::one(), -base_viol); // c = [2] P + [1] (-2P) - assert!(c.clone().eval()); + assert!(c.clone().check()); // Add two MSMs with bases that differ only in sign. a.add_msm(&c); - assert!(a.eval()); + assert!(a.check()); } } diff --git a/halo2_proofs/src/poly/ipa/multiopen.rs b/halo2_proofs/src/poly/ipa/multiopen.rs new file mode 100644 index 0000000000..b724139a8f --- /dev/null +++ b/halo2_proofs/src/poly/ipa/multiopen.rs @@ -0,0 +1,176 @@ +//! This module contains an optimisation of the polynomial commitment opening +//! scheme described in the [Halo][halo] paper. +//! +//! [halo]: https://eprint.iacr.org/2019/1021 + +use std::collections::{BTreeMap, BTreeSet}; + +use super::*; +use crate::{ + arithmetic::{CurveAffine, FieldExt}, + poly::query::Query, + transcript::ChallengeScalar, +}; + +mod prover; +mod verifier; + +pub use prover::ProverIPA; +pub use verifier::VerifierIPA; + +#[derive(Clone, Copy, Debug)] +struct X1 {} +/// Challenge for compressing openings at the same point sets together. +type ChallengeX1 = ChallengeScalar; + +#[derive(Clone, Copy, Debug)] +struct X2 {} +/// Challenge for keeping the multi-point quotient polynomial terms linearly independent. +type ChallengeX2 = ChallengeScalar; + +#[derive(Clone, Copy, Debug)] +struct X3 {} +/// Challenge point at which the commitments are opened. +type ChallengeX3 = ChallengeScalar; + +#[derive(Clone, Copy, Debug)] +struct X4 {} +/// Challenge for collapsing the openings of the various remaining polynomials at x_3 +/// together. +type ChallengeX4 = ChallengeScalar; + +#[derive(Debug)] +struct CommitmentData { + pub(crate) commitment: T, + pub(crate) set_index: usize, + pub(crate) point_indices: Vec, + pub(crate) evals: Vec, +} + +impl CommitmentData { + fn new(commitment: T) -> Self { + CommitmentData { + commitment, + set_index: 0, + point_indices: vec![], + evals: vec![], + } + } +} + +type IntermediateSets = ( + Vec>::Eval, >::Commitment>>, + Vec>, +); + +fn construct_intermediate_sets>(queries: I) -> IntermediateSets +where + I: IntoIterator + Clone, +{ + // Construct sets of unique commitments and corresponding information about + // their queries. + let mut commitment_map: Vec> = vec![]; + + // Also construct mapping from a unique point to a point_index. This defines + // an ordering on the points. + let mut point_index_map = BTreeMap::new(); + + // Iterate over all of the queries, computing the ordering of the points + // while also creating new commitment data. + for query in queries.clone() { + let num_points = point_index_map.len(); + let point_idx = point_index_map + .entry(query.get_point()) + .or_insert(num_points); + + if let Some(pos) = commitment_map + .iter() + .position(|comm| comm.commitment == query.get_commitment()) + { + commitment_map[pos].point_indices.push(*point_idx); + } else { + let mut tmp = CommitmentData::new(query.get_commitment()); + tmp.point_indices.push(*point_idx); + commitment_map.push(tmp); + } + } + + // Also construct inverse mapping from point_index to the point + let mut inverse_point_index_map = BTreeMap::new(); + for (&point, &point_index) in point_index_map.iter() { + inverse_point_index_map.insert(point_index, point); + } + + // Construct map of unique ordered point_idx_sets to their set_idx + let mut point_idx_sets = BTreeMap::new(); + // Also construct mapping from commitment to point_idx_set + let mut commitment_set_map = Vec::new(); + + for commitment_data in commitment_map.iter() { + let mut point_index_set = BTreeSet::new(); + // Note that point_index_set is ordered, unlike point_indices + for &point_index in commitment_data.point_indices.iter() { + point_index_set.insert(point_index); + } + + // Push point_index_set to CommitmentData for the relevant commitment + commitment_set_map.push((commitment_data.commitment, point_index_set.clone())); + + let num_sets = point_idx_sets.len(); + point_idx_sets.entry(point_index_set).or_insert(num_sets); + } + + // Initialise empty evals vec for each unique commitment + for commitment_data in commitment_map.iter_mut() { + let len = commitment_data.point_indices.len(); + commitment_data.evals = vec![Q::Eval::default(); len]; + } + + // Populate set_index, evals and points for each commitment using point_idx_sets + for query in queries { + // The index of the point at which the commitment is queried + let point_index = point_index_map.get(&query.get_point()).unwrap(); + + // The point_index_set at which the commitment was queried + let mut point_index_set = BTreeSet::new(); + for (commitment, point_idx_set) in commitment_set_map.iter() { + if query.get_commitment() == *commitment { + point_index_set = point_idx_set.clone(); + } + } + assert!(!point_index_set.is_empty()); + + // The set_index of the point_index_set + let set_index = point_idx_sets.get(&point_index_set).unwrap(); + for commitment_data in commitment_map.iter_mut() { + if query.get_commitment() == commitment_data.commitment { + commitment_data.set_index = *set_index; + } + } + let point_index_set: Vec = point_index_set.iter().cloned().collect(); + + // The offset of the point_index in the point_index_set + let point_index_in_set = point_index_set + .iter() + .position(|i| i == point_index) + .unwrap(); + + for commitment_data in commitment_map.iter_mut() { + if query.get_commitment() == commitment_data.commitment { + // Insert the eval using the ordering of the point_index_set + commitment_data.evals[point_index_in_set] = query.get_eval(); + } + } + } + + // Get actual points in each point set + let mut point_sets: Vec> = vec![Vec::new(); point_idx_sets.len()]; + for (point_idx_set, &set_idx) in point_idx_sets.iter() { + for &point_idx in point_idx_set.iter() { + let point = inverse_point_index_map.get(&point_idx).unwrap(); + point_sets[set_idx].push(*point); + } + } + + (commitment_map, point_sets) +} diff --git a/halo2_proofs/src/poly/ipa/multiopen/prover.rs b/halo2_proofs/src/poly/ipa/multiopen/prover.rs new file mode 100644 index 0000000000..e01b9d17d4 --- /dev/null +++ b/halo2_proofs/src/poly/ipa/multiopen/prover.rs @@ -0,0 +1,122 @@ +use super::{ + construct_intermediate_sets, ChallengeX1, ChallengeX2, ChallengeX3, ChallengeX4, Query, +}; +use crate::arithmetic::{eval_polynomial, kate_division, CurveAffine, FieldExt}; +use crate::poly::commitment::ParamsProver; +use crate::poly::commitment::{Blind, Params, Prover}; +use crate::poly::ipa::commitment::{self, IPACommitmentScheme, ParamsIPA}; +use crate::poly::query::ProverQuery; +use crate::poly::{Coeff, Polynomial}; +use crate::transcript::{EncodedChallenge, TranscriptWrite}; + +use ff::Field; +use group::Curve; +use rand_core::RngCore; +use std::io; +use std::marker::PhantomData; + +/// IPA multi-open prover +#[derive(Debug)] +pub struct ProverIPA<'params, C: CurveAffine> { + pub(crate) params: &'params ParamsIPA, +} + +impl<'params, C: CurveAffine> Prover<'params, IPACommitmentScheme> for ProverIPA<'params, C> { + fn new(params: &'params ParamsIPA) -> Self { + Self { params } + } + + /// Create a multi-opening proof + fn create_proof<'com, Z: EncodedChallenge, T: TranscriptWrite, R, I>( + &self, + mut rng: R, + transcript: &mut T, + queries: I, + ) -> io::Result<()> + where + I: IntoIterator> + Clone, + R: RngCore, + { + let x_1: ChallengeX1<_> = transcript.squeeze_challenge_scalar(); + let x_2: ChallengeX2<_> = transcript.squeeze_challenge_scalar(); + + let (poly_map, point_sets) = construct_intermediate_sets(queries); + + // 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 accumulate = |set_idx: usize, + new_poly: &Polynomial, + blind: Blind| { + if let Some(poly) = &q_polys[set_idx] { + q_polys[set_idx] = Some(poly.clone() * *x_1 + new_poly); + } else { + q_polys[set_idx] = Some(new_poly.clone()); + } + q_blinds[set_idx] *= *x_1; + q_blinds[set_idx] += blind; + }; + + for commitment_data in poly_map.into_iter() { + accumulate( + commitment_data.set_index, // set_idx, + commitment_data.commitment.poly, // poly, + commitment_data.commitment.blind, // blind, + ); + } + } + + let q_prime_poly = point_sets + .iter() + .zip(q_polys.iter()) + .fold(None, |q_prime_poly, (points, poly)| { + let mut poly = points + .iter() + .fold(poly.clone().unwrap().values, |poly, point| { + kate_division(&poly, *point) + }); + poly.resize(self.params.n as usize, C::Scalar::zero()); + let poly = Polynomial { + values: poly, + _marker: PhantomData, + }; + + if q_prime_poly.is_none() { + Some(poly) + } else { + q_prime_poly.map(|q_prime_poly| q_prime_poly * *x_2 + &poly) + } + }) + .unwrap(); + + let q_prime_blind = Blind(C::Scalar::random(&mut rng)); + let q_prime_commitment = self.params.commit(&q_prime_poly, q_prime_blind).to_affine(); + + transcript.write_point(q_prime_commitment)?; + + let x_3: ChallengeX3<_> = transcript.squeeze_challenge_scalar(); + + // Prover sends u_i for all i, which correspond to the evaluation + // of each Q polynomial commitment at x_3. + for q_i_poly in &q_polys { + transcript.write_scalar(eval_polynomial(q_i_poly.as_ref().unwrap(), *x_3))?; + } + + let x_4: ChallengeX4<_> = transcript.squeeze_challenge_scalar(); + + let (p_poly, p_poly_blind) = q_polys.into_iter().zip(q_blinds.into_iter()).fold( + (q_prime_poly, q_prime_blind), + |(q_prime_poly, q_prime_blind), (poly, blind)| { + ( + q_prime_poly * *x_4 + &poly.unwrap(), + Blind((q_prime_blind.0 * &(*x_4)) + &blind.0), + ) + }, + ); + + commitment::create_proof(self.params, rng, transcript, &p_poly, p_poly_blind, *x_3) + } +} diff --git a/halo2_proofs/src/poly/ipa/multiopen/verifier.rs b/halo2_proofs/src/poly/ipa/multiopen/verifier.rs new file mode 100644 index 0000000000..1c840b6de7 --- /dev/null +++ b/halo2_proofs/src/poly/ipa/multiopen/verifier.rs @@ -0,0 +1,146 @@ +use std::fmt::Debug; +use std::io::Read; +use std::marker::PhantomData; + +use ff::Field; +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::poly::commitment::{Params, Verifier, MSM}; +use crate::poly::ipa::commitment::{IPACommitmentScheme, ParamsIPA, ParamsVerifierIPA}; +use crate::poly::ipa::msm::MSMIPA; +use crate::poly::ipa::strategy::GuardIPA; +use crate::poly::query::{CommitmentReference, VerifierQuery}; +use crate::poly::strategy::VerificationStrategy; +use crate::poly::Error; +use crate::transcript::{EncodedChallenge, TranscriptRead}; + +/// IPA multi-open verifier +#[derive(Debug)] +pub struct VerifierIPA<'params, C: CurveAffine> { + params: &'params ParamsIPA, +} + +impl<'params, C: CurveAffine> Verifier<'params, IPACommitmentScheme> + for VerifierIPA<'params, C> +{ + type Guard = GuardIPA<'params, C>; + type MSMAccumulator = MSMIPA<'params, C>; + + fn new(params: &'params ParamsVerifierIPA) -> Self { + Self { params } + } + + fn verify_proof<'com, E: EncodedChallenge, T: TranscriptRead, I>( + &self, + transcript: &mut T, + queries: I, + mut msm: MSMIPA<'params, C>, + ) -> Result + where + 'params: 'com, + I: IntoIterator>> + Clone, + { + // Sample x_1 for compressing openings at the same point sets together + let x_1: ChallengeX1<_> = transcript.squeeze_challenge_scalar(); + + // Sample a challenge x_2 for keeping the multi-point quotient + // polynomial terms linearly independent. + let x_2: ChallengeX2<_> = transcript.squeeze_challenge_scalar(); + + let (commitment_map, point_sets) = construct_intermediate_sets(queries); + + // Compress the commitments and expected evaluations at x together. + // using the challenge x_1 + let mut q_commitments: Vec<_> = vec![self.params.empty_msm(); point_sets.len()]; + + // A vec of vecs of evals. The outer vec corresponds to the point set, + // 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()]); + } + { + let mut accumulate = |set_idx: usize, + new_commitment: CommitmentReference>, + evals: Vec| { + q_commitments[set_idx].scale(*x_1); + match new_commitment { + CommitmentReference::Commitment(c) => { + q_commitments[set_idx].append_term(C::Scalar::one(), (*c).into()); + } + CommitmentReference::MSM(msm) => { + q_commitments[set_idx].add_msm(msm); + } + } + for (eval, set_eval) in evals.iter().zip(q_eval_sets[set_idx].iter_mut()) { + *set_eval *= &(*x_1); + *set_eval += eval; + } + }; + + // Each commitment corresponds to evaluations at a set of points. + // For each set, we collapse each commitment's evals pointwise. + for commitment_data in commitment_map.into_iter() { + accumulate( + commitment_data.set_index, // set_idx, + commitment_data.commitment, // commitment, + commitment_data.evals, // evals + ); + } + } + + // Obtain the commitment to the multi-point quotient polynomial f(X). + let q_prime_commitment = transcript.read_point().map_err(|_| Error::SamplingError)?; + + // Sample a challenge x_3 for checking that f(X) was committed to + // correctly. + let x_3: ChallengeX3<_> = transcript.squeeze_challenge_scalar(); + + // u is a vector containing the evaluations of the Q polynomial + // commitments at x_3 + let mut u = Vec::with_capacity(q_eval_sets.len()); + for _ in 0..q_eval_sets.len() { + u.push(transcript.read_scalar().map_err(|_| Error::SamplingError)?); + } + + // We can compute the expected msm_eval at x_3 using the u provided + // by the prover and from x_2 + let msm_eval = point_sets + .iter() + .zip(q_eval_sets.iter()) + .zip(u.iter()) + .fold( + 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); + let eval = points.iter().fold(*proof_eval - &r_eval, |eval, point| { + eval * &(*x_3 - point).invert().unwrap() + }); + msm_eval * &(*x_2) + &eval + }, + ); + + // Sample a challenge x_4 that we will use to collapse the openings of + // the various remaining polynomials at x_3 together. + 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()); + let (msm, v) = q_commitments.into_iter().zip(u.iter()).fold( + (msm, msm_eval), + |(mut msm, msm_eval), (q_commitment, q_eval)| { + msm.scale(*x_4); + msm.add_msm(&q_commitment); + (msm, msm_eval * &(*x_4) + q_eval) + }, + ); + + // Verify the opening proof + super::commitment::verify_proof(self.params, msm, transcript, *x_3, v) + } +} diff --git a/halo2_proofs/src/poly/ipa/strategy.rs b/halo2_proofs/src/poly/ipa/strategy.rs new file mode 100644 index 0000000000..6f3b4b7228 --- /dev/null +++ b/halo2_proofs/src/poly/ipa/strategy.rs @@ -0,0 +1,176 @@ +use std::marker::PhantomData; + +use super::commitment::{IPACommitmentScheme, ParamsIPA, ParamsVerifierIPA}; +use super::msm::MSMIPA; +use super::multiopen::VerifierIPA; +use crate::poly::commitment::CommitmentScheme; +use crate::transcript::TranscriptRead; +use crate::{ + arithmetic::best_multiexp, + plonk::Error, + poly::{ + commitment::MSM, + strategy::{Guard, VerificationStrategy}, + }, + transcript::EncodedChallenge, +}; +use ff::Field; +use group::Curve; +use halo2curves::CurveAffine; +use rand_core::{OsRng, RngCore}; + +/// Wrapper for verification accumulator +#[derive(Debug, Clone)] +pub struct GuardIPA<'params, C: CurveAffine> { + pub(crate) msm: MSMIPA<'params, C>, + pub(crate) neg_c: C::Scalar, + pub(crate) u: Vec, + pub(crate) u_packed: Vec, +} + +/// An accumulator instance consisting of an evaluation claim and a proof. +#[derive(Debug, Clone)] +pub struct Accumulator { + /// The claimed output of the linear-time polycommit opening protocol + pub g: C, + + /// A vector of challenges u_0, ..., u_{k - 1} sampled by the verifier, to + /// be used in computing G'_0. + pub u_packed: Vec, +} + +/// Define accumulator type as `MSMIPA` +impl<'params, C: CurveAffine> Guard> for GuardIPA<'params, C> { + type MSMAccumulator = MSMIPA<'params, C>; +} + +/// IPA specific operations +impl<'params, C: CurveAffine> GuardIPA<'params, C> { + /// Lets caller supply the challenges and obtain an MSM with updated + /// scalars and points. + pub fn use_challenges(mut self) -> MSMIPA<'params, C> { + let s = compute_s(&self.u, self.neg_c); + self.msm.add_to_g_scalars(&s); + + self.msm + } + + /// Lets caller supply the purported G point and simply appends + /// [-c] G to return an updated MSM. + pub fn use_g(mut self, g: C) -> (MSMIPA<'params, C>, Accumulator) { + self.msm.append_term(self.neg_c, g.into()); + + let accumulator = Accumulator { + g, + u_packed: self.u_packed, + }; + + (self.msm, accumulator) + } + + /// Computes G = ⟨s, params.g⟩ + pub fn compute_g(&self) -> C { + let s = compute_s(&self.u, C::Scalar::one()); + + best_multiexp(&s, &self.msm.params.g).to_affine() + } +} + +/// A verifier that checks multiple proofs in a batch. +#[derive(Debug)] +pub struct AccumulatorStrategy<'params, C: CurveAffine> { + msm: MSMIPA<'params, C>, +} + +impl<'params, C: CurveAffine> + VerificationStrategy<'params, IPACommitmentScheme, VerifierIPA<'params, C>> + for AccumulatorStrategy<'params, C> +{ + type Output = Self; + + fn new(params: &'params ParamsIPA) -> Self { + AccumulatorStrategy { + msm: MSMIPA::new(params), + } + } + + fn process( + mut self, + f: impl FnOnce(MSMIPA<'params, C>) -> Result, Error>, + ) -> Result { + self.msm.scale(C::Scalar::random(OsRng)); + let guard = f(self.msm)?; + + Ok(Self { + msm: guard.use_challenges(), + }) + } + + /// Finalizes the batch and checks its validity. + /// + /// Returns `false` if *some* proof was invalid. If the caller needs to identify + /// specific failing proofs, it must re-process the proofs separately. + #[must_use] + fn finalize(self) -> bool { + self.msm.check() + } +} + +/// A verifier that checks single proof +#[derive(Debug)] +pub struct SingleStrategy<'params, C: CurveAffine> { + msm: MSMIPA<'params, C>, +} + +impl<'params, C: CurveAffine> + VerificationStrategy<'params, IPACommitmentScheme, VerifierIPA<'params, C>> + for SingleStrategy<'params, C> +{ + type Output = (); + + fn new(params: &'params ParamsIPA) -> Self { + SingleStrategy { + msm: MSMIPA::new(params), + } + } + + fn process( + self, + f: impl FnOnce(MSMIPA<'params, C>) -> Result, Error>, + ) -> Result { + let guard = f(self.msm)?; + let msm = guard.use_challenges(); + if msm.check() { + Ok(()) + } else { + Err(Error::ConstraintSystemFailure) + } + } + + /// Finalizes the batch and checks its validity. + /// + /// Returns `false` if *some* proof was invalid. If the caller needs to identify + /// specific failing proofs, it must re-process the proofs separately. + #[must_use] + fn finalize(self) -> bool { + unreachable!() + } +} + +/// 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()]; + v[0] = init; + + for (len, u_j) in u.iter().rev().enumerate().map(|(i, u_j)| (1 << i, u_j)) { + let (left, right) = v.split_at_mut(len); + let right = &mut right[0..len]; + right.copy_from_slice(left); + for v in right { + *v *= u_j; + } + } + + v +} diff --git a/halo2_proofs/src/poly/kzg/commitment.rs b/halo2_proofs/src/poly/kzg/commitment.rs new file mode 100644 index 0000000000..3e8cce6d09 --- /dev/null +++ b/halo2_proofs/src/poly/kzg/commitment.rs @@ -0,0 +1,353 @@ +use crate::arithmetic::{ + best_fft, best_multiexp, g_to_lagrange, parallelize, CurveAffine, CurveExt, FieldExt, Group, +}; +use crate::helpers::CurveRead; +use crate::poly::commitment::{Blind, CommitmentScheme, Params, ParamsProver, ParamsVerifier, MSM}; +use crate::poly::{Coeff, LagrangeCoeff, Polynomial}; + +use ff::{Field, PrimeField}; +use group::{prime::PrimeCurveAffine, Curve, Group as _}; +use halo2curves::pairing::Engine; +use rand_core::{OsRng, RngCore}; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::ops::{Add, AddAssign, Mul, MulAssign}; + +use std::io; + +use super::msm::MSMKZG; + +/// These are the public parameters for the polynomial commitment scheme. +#[derive(Debug, Clone)] +pub struct ParamsKZG { + pub(crate) k: u32, + pub(crate) n: u64, + pub(crate) g: Vec, + pub(crate) g_lagrange: Vec, + pub(crate) g2: E::G2Affine, + pub(crate) s_g2: E::G2Affine, +} + +/// Umbrella commitment scheme construction for all KZG variants +#[derive(Debug)] +pub struct KZGCommitmentScheme { + _marker: PhantomData, +} + +impl CommitmentScheme for KZGCommitmentScheme { + type Scalar = E::Scalar; + type Curve = E::G1Affine; + + type ParamsProver = ParamsKZG; + type ParamsVerifier = ParamsVerifierKZG; + + fn new_params(k: u32) -> Self::ParamsProver { + ParamsKZG::new(k) + } + + fn read_params(reader: &mut R) -> io::Result { + ParamsKZG::read(reader) + } +} + +impl ParamsKZG { + /// 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 { + // Largest root of unity exponent of the Engine is `2^E::Scalar::S`, so we can + // only support FFTs of polynomials below degree `2^E::Scalar::S`. + assert!(k <= E::Scalar::S); + let n: u64 = 1 << k; + + // Calculate g = [G1, [s] G1, [s^2] G1, ..., [s^(n-1)] G1] in parallel. + let g1 = E::G1Affine::generator(); + let s = ::random(rng); + + let mut g_projective = vec![E::G1::group_zero(); n as usize]; + parallelize(&mut g_projective, |g, start| { + let mut current_g: E::G1 = g1.into(); + current_g *= s.pow_vartime(&[start as u64]); + for g in g.iter_mut() { + *g = current_g; + current_g *= s; + } + }); + + let g = { + let mut g = vec![E::G1Affine::identity(); n as usize]; + parallelize(&mut g, |g, starts| { + E::G1::batch_normalize(&g_projective[starts..(starts + g.len())], g); + }); + g + }; + + let mut g_lagrange_projective = vec![E::G1::group_zero(); 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<::generator(); + let s_g2 = (g2 * s).into(); + + Self { + k, + n, + g, + g_lagrange, + g2, + s_g2, + } + } + + /// Returns gernerator on G2 + pub fn g2(&self) -> E::G2Affine { + self.g2 + } + + /// Returns first power of secret on G2 + pub fn s_g2(&self) -> E::G2Affine { + self.s_g2 + } +} + +// TODO: see the issue at https://github.com/appliedzkp/halo2/issues/45 +// So we probably need much smaller verifier key. However for new bases in g1 should be in verifier keys. +/// KZG multi-open verification parameters +pub type ParamsVerifierKZG = ParamsKZG; + +impl<'params, E: Engine + Debug> Params<'params, E::G1Affine> for ParamsKZG { + type MSM = MSMKZG; + + fn k(&self) -> u32 { + self.k + } + + fn n(&self) -> u64 { + self.n + } + + fn downsize(&mut self, k: u32) { + assert!(k <= self.k); + + self.k = k; + self.n = 1 << k; + + self.g.truncate(self.n as usize); + self.g_lagrange = g_to_lagrange(self.g.iter().map(|g| g.to_curve()).collect(), k); + } + + fn empty_msm(&'params self) -> MSMKZG { + MSMKZG::new() + } + + fn commit_lagrange( + &self, + poly: &Polynomial, + _: Blind, + ) -> E::G1 { + let mut scalars = Vec::with_capacity(poly.len()); + scalars.extend(poly.iter()); + let bases = &self.g_lagrange; + let size = scalars.len(); + assert!(bases.len() >= size); + best_multiexp(&scalars, &bases[0..size]) + } + + /// 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(()) + } + + /// 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, + }) + } +} + +impl<'params, E: Engine + Debug> ParamsVerifier<'params, E::G1Affine> for ParamsKZG {} + +impl<'params, E: Engine + Debug> ParamsProver<'params, E::G1Affine> for ParamsKZG { + type ParamsVerifier = ParamsVerifierKZG; + + fn verifier_params(&'params self) -> &'params Self::ParamsVerifier { + self + } + + fn new(k: u32) -> Self { + Self::setup(k, OsRng) + } + + fn commit(&self, poly: &Polynomial, _: Blind) -> E::G1 { + let mut scalars = Vec::with_capacity(poly.len()); + scalars.extend(poly.iter()); + let bases = &self.g; + let size = scalars.len(); + assert!(bases.len() >= size); + best_multiexp(&scalars, &bases[0..size]) + } + + fn get_g(&self) -> &[E::G1Affine] { + &self.g + } +} + +#[cfg(test)] +mod test { + + use crate::arithmetic::{ + best_fft, best_multiexp, parallelize, CurveAffine, CurveExt, FieldExt, Group, + }; + use crate::helpers::CurveRead; + use crate::poly::commitment::ParamsProver; + use crate::poly::commitment::{Blind, CommitmentScheme, Params, MSM}; + use crate::poly::kzg::commitment::{ParamsKZG, ParamsVerifierKZG}; + use crate::poly::kzg::msm::MSMKZG; + use crate::poly::kzg::multiopen::ProverSHPLONK; + use crate::poly::{Coeff, LagrangeCoeff, Polynomial}; + + use ff::{Field, PrimeField}; + use group::{prime::PrimeCurveAffine, Curve, Group as _}; + use halo2curves::bn256::G1Affine; + use std::marker::PhantomData; + use std::ops::{Add, AddAssign, Mul, MulAssign}; + + use std::io; + + #[test] + fn test_commit_lagrange() { + const K: u32 = 6; + + use rand_core::OsRng; + + use crate::poly::EvaluationDomain; + use halo2curves::bn256::{Bn256, Fr}; + + let params = ParamsKZG::::new(K); + let domain = EvaluationDomain::new(1, K); + + let mut a = domain.empty_lagrange(); + + for (i, a) in a.iter_mut().enumerate() { + *a = Fr::from(i as u64); + } + + let b = domain.lagrange_to_coeff(a.clone()); + + let alpha = Blind(Fr::random(OsRng)); + + assert_eq!(params.commit(&b, alpha), params.commit_lagrange(&a, alpha)); + } + + #[test] + fn test_parameter_serialisation_roundtrip() { + const K: u32 = 4; + + use ff::Field; + use rand_core::OsRng; + + use super::super::commitment::{Blind, Params}; + use crate::arithmetic::{eval_polynomial, FieldExt}; + use crate::halo2curves::bn256::{Bn256, Fr}; + use crate::poly::EvaluationDomain; + + let params0 = ParamsKZG::::new(K); + let mut data = vec![]; + as Params<_>>::write(¶ms0, &mut data).unwrap(); + let params1: ParamsKZG = Params::read::<_>(&mut &data[..]).unwrap(); + + assert_eq!(params0.k, params1.k); + assert_eq!(params0.n, params1.n); + assert_eq!(params0.g.len(), params1.g.len()); + assert_eq!(params0.g_lagrange.len(), params1.g_lagrange.len()); + + assert_eq!(params0.g, params1.g); + assert_eq!(params0.g_lagrange, params1.g_lagrange); + assert_eq!(params0.g2, params1.g2); + assert_eq!(params0.s_g2, params1.s_g2); + } +} diff --git a/halo2_proofs/src/poly/kzg/mod.rs b/halo2_proofs/src/poly/kzg/mod.rs new file mode 100644 index 0000000000..0c99a20c34 --- /dev/null +++ b/halo2_proofs/src/poly/kzg/mod.rs @@ -0,0 +1,8 @@ +/// KZG commitment scheme +pub mod commitment; +/// Multiscalar multiplication engines +pub mod msm; +/// KZG multi-open scheme +pub mod multiopen; +/// Strategies used with KZG scheme +pub mod strategy; diff --git a/halo2_proofs/src/poly/kzg/msm.rs b/halo2_proofs/src/poly/kzg/msm.rs new file mode 100644 index 0000000000..19754146a0 --- /dev/null +++ b/halo2_proofs/src/poly/kzg/msm.rs @@ -0,0 +1,170 @@ +use std::fmt::Debug; + +use super::commitment::{KZGCommitmentScheme, ParamsKZG}; +use crate::{ + arithmetic::{best_multiexp, parallelize, CurveAffine}, + poly::commitment::MSM, +}; +use group::{Curve, Group}; +use halo2curves::pairing::{Engine, MillerLoopResult, MultiMillerLoop}; + +/// A multiscalar multiplication in the polynomial commitment scheme +#[derive(Clone, Default, Debug)] +pub struct MSMKZG { + pub(crate) scalars: Vec, + pub(crate) bases: Vec, +} + +impl MSMKZG { + /// Create an empty MSM instance + pub fn new() -> Self { + MSMKZG { + scalars: vec![], + bases: vec![], + } + } + + /// 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(); + if !self.scalars.is_empty() { + for scalar in self.scalars.iter_mut().rev() { + *scalar *= &acc; + acc *= base; + } + } + } +} + +impl MSM for MSMKZG { + fn append_term(&mut self, scalar: E::Scalar, point: E::G1) { + self.scalars.push(scalar); + self.bases.push(point); + } + + fn add_msm(&mut self, other: &Self) { + self.scalars.extend(other.scalars().iter()); + self.bases.extend(other.bases().iter()); + } + + fn scale(&mut self, factor: E::Scalar) { + if !self.scalars.is_empty() { + parallelize(&mut self.scalars, |scalars, _| { + for other_scalar in scalars { + *other_scalar *= &factor; + } + }) + } + } + + fn check(&self) -> bool { + bool::from(self.eval().is_identity()) + } + + fn eval(&self) -> E::G1 { + use group::prime::PrimeCurveAffine; + let mut bases = vec![E::G1Affine::identity(); self.scalars.len()]; + E::G1::batch_normalize(&self.bases, &mut bases); + best_multiexp(&self.scalars, &bases) + } + + fn bases(&self) -> Vec { + self.bases.clone() + } + + fn scalars(&self) -> Vec { + self.scalars.clone() + } +} + +/// A projective point collector +#[derive(Debug, Clone)] +pub(crate) struct PreMSM { + projectives_msms: Vec>, +} + +impl PreMSM { + pub(crate) fn new() -> Self { + PreMSM { + projectives_msms: vec![], + } + } + + pub(crate) fn normalize(self) -> MSMKZG { + use group::prime::PrimeCurveAffine; + + let (scalars, bases) = self + .projectives_msms + .into_iter() + .map(|msm| (msm.scalars, msm.bases)) + .unzip::<_, _, Vec<_>, Vec<_>>(); + + MSMKZG { + scalars: scalars.into_iter().flatten().collect(), + bases: bases.into_iter().flatten().collect(), + } + } + + pub(crate) fn add_msm(&mut self, other: MSMKZG) { + self.projectives_msms.push(other); + } +} + +impl<'params, E: MultiMillerLoop + Debug> From<&'params ParamsKZG> for DualMSM<'params, E> { + fn from(params: &'params ParamsKZG) -> Self { + DualMSM::new(params) + } +} + +/// Two channel MSM accumulator +#[derive(Debug, Clone)] +pub struct DualMSM<'a, E: Engine> { + pub(crate) params: &'a ParamsKZG, + pub(crate) left: MSMKZG, + pub(crate) right: MSMKZG, +} + +impl<'a, E: MultiMillerLoop + Debug> DualMSM<'a, E> { + /// Create a new two channel MSM accumulator instance + pub fn new(params: &'a ParamsKZG) -> Self { + Self { + params, + left: MSMKZG::new(), + right: MSMKZG::new(), + } + } + + /// Scale all scalars in the MSM by some scaling factor + pub fn scale(&mut self, e: E::Scalar) { + self.left.scale(e); + self.right.scale(e); + } + + /// Add another multiexp into this one + pub fn add_msm(&mut self, other: Self) { + self.left.add_msm(&other.left); + self.right.add_msm(&other.right); + } + + /// Performs final pairing check with given verifier params and two channel linear combination + pub fn check(self) -> bool { + let s_g2_prepared = E::G2Prepared::from(self.params.s_g2); + let n_g2_prepared = E::G2Prepared::from(-self.params.g2); + + let left = self.left.eval(); + let right = self.right.eval(); + + let (term_1, term_2) = ( + (&left.into(), &s_g2_prepared), + (&right.into(), &n_g2_prepared), + ); + let terms = &[term_1, term_2]; + + bool::from( + E::multi_miller_loop(&terms[..]) + .final_exponentiation() + .is_identity(), + ) + } +} diff --git a/halo2_proofs/src/poly/kzg/multiopen.rs b/halo2_proofs/src/poly/kzg/multiopen.rs new file mode 100644 index 0000000000..97b7e2b777 --- /dev/null +++ b/halo2_proofs/src/poly/kzg/multiopen.rs @@ -0,0 +1,5 @@ +mod gwc; +mod shplonk; + +pub use gwc::*; +pub use shplonk::*; diff --git a/halo2_proofs/src/poly/kzg/multiopen/gwc.rs b/halo2_proofs/src/poly/kzg/multiopen/gwc.rs new file mode 100644 index 0000000000..4869238ae7 --- /dev/null +++ b/halo2_proofs/src/poly/kzg/multiopen/gwc.rs @@ -0,0 +1,61 @@ +mod prover; +mod verifier; + +pub use prover::ProverGWC; +pub use verifier::VerifierGWC; + +use crate::{ + arithmetic::{eval_polynomial, CurveAffine, FieldExt}, + poly::{ + commitment::{Params, ParamsVerifier}, + query::Query, + Coeff, Polynomial, + }, + transcript::ChallengeScalar, +}; + +use std::{ + collections::{BTreeMap, BTreeSet}, + marker::PhantomData, +}; + +#[derive(Clone, Copy, Debug)] +struct U {} +type ChallengeU = ChallengeScalar; + +#[derive(Clone, Copy, Debug)] +struct V {} +type ChallengeV = ChallengeScalar; + +struct CommitmentData> { + queries: Vec, + point: F, + _marker: PhantomData, +} + +fn construct_intermediate_sets>(queries: I) -> Vec> +where + I: IntoIterator + Clone, +{ + let mut point_query_map: Vec<(F, Vec)> = Vec::new(); + for query in queries { + if let Some(pos) = point_query_map + .iter() + .position(|(point, _)| *point == query.get_point()) + { + let (_, queries) = &mut point_query_map[pos]; + queries.push(query); + } else { + point_query_map.push((query.get_point(), vec![query])); + } + } + + point_query_map + .into_iter() + .map(|(point, queries)| CommitmentData { + queries, + point, + _marker: PhantomData, + }) + .collect() +} diff --git a/halo2_proofs/src/poly/kzg/multiopen/gwc/prover.rs b/halo2_proofs/src/poly/kzg/multiopen/gwc/prover.rs new file mode 100644 index 0000000000..e3575b390c --- /dev/null +++ b/halo2_proofs/src/poly/kzg/multiopen/gwc/prover.rs @@ -0,0 +1,86 @@ +use super::{construct_intermediate_sets, ChallengeV, Query}; +use crate::arithmetic::{eval_polynomial, kate_division, powers, CurveAffine, FieldExt}; + +use crate::poly::commitment::ParamsProver; +use crate::poly::commitment::Prover; +use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; +use crate::poly::query::ProverQuery; +use crate::poly::Rotation; +use crate::poly::{ + commitment::{Blind, Params}, + Coeff, Polynomial, +}; +use crate::transcript::{EncodedChallenge, TranscriptWrite}; + +use ff::Field; +use group::Curve; +use halo2curves::pairing::Engine; +use rand_core::RngCore; +use std::fmt::Debug; +use std::io::{self, Write}; +use std::marker::PhantomData; + +/// Concrete KZG prover with GWC variant +#[derive(Debug)] +pub struct ProverGWC<'params, E: Engine> { + params: &'params ParamsKZG, +} + +/// Create a multi-opening proof +impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> for ProverGWC<'params, E> { + fn new(params: &'params ParamsKZG) -> Self { + Self { params } + } + + /// Create a multi-opening proof + fn create_proof< + 'com, + Ch: EncodedChallenge, + T: TranscriptWrite, + R, + I, + >( + &self, + _: R, + transcript: &mut T, + queries: I, + ) -> io::Result<()> + where + I: IntoIterator> + Clone, + R: RngCore, + { + let v: ChallengeV<_> = transcript.squeeze_challenge_scalar(); + let commitment_data = construct_intermediate_sets(queries); + + for commitment_at_a_point in commitment_data.iter() { + let z = commitment_at_a_point.point; + let (poly_batch, eval_batch) = commitment_at_a_point + .queries + .iter() + .zip(powers(*v)) + .map(|(query, power_of_v)| { + assert_eq!(query.get_point(), z); + + let poly = query.get_commitment().poly; + let eval = query.get_eval(); + + (poly.clone() * power_of_v, eval * power_of_v) + }) + .reduce(|(poly_acc, eval_acc), (poly, eval)| (poly_acc + &poly, eval_acc + eval)) + .unwrap(); + + let poly_batch = &poly_batch - eval_batch; + let witness_poly = Polynomial { + values: kate_division(&poly_batch.values, z), + _marker: PhantomData, + }; + let w = self + .params + .commit(&witness_poly, Blind::default()) + .to_affine(); + + transcript.write_point(w)?; + } + Ok(()) + } +} diff --git a/halo2_proofs/src/poly/kzg/multiopen/gwc/verifier.rs b/halo2_proofs/src/poly/kzg/multiopen/gwc/verifier.rs new file mode 100644 index 0000000000..e5218d36ef --- /dev/null +++ b/halo2_proofs/src/poly/kzg/multiopen/gwc/verifier.rs @@ -0,0 +1,125 @@ +use std::fmt::Debug; +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::poly::commitment::Verifier; +use crate::poly::commitment::MSM; +use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; +use crate::poly::kzg::msm::{DualMSM, MSMKZG}; +use crate::poly::kzg::strategy::{AccumulatorStrategy, GuardKZG, SingleStrategy}; +use crate::poly::query::Query; +use crate::poly::query::{CommitmentReference, VerifierQuery}; +use crate::poly::strategy::VerificationStrategy; +use crate::poly::{ + commitment::{Params, ParamsVerifier}, + Error, +}; +use crate::transcript::{EncodedChallenge, TranscriptRead}; + +use ff::Field; +use group::Group; +use halo2curves::pairing::{Engine, MillerLoopResult, MultiMillerLoop}; +use rand_core::OsRng; + +#[derive(Debug)] +/// Concrete KZG verifier with GWC variant +pub struct VerifierGWC<'params, E: Engine> { + params: &'params ParamsKZG, +} + +impl<'params, E: MultiMillerLoop + Debug> Verifier<'params, KZGCommitmentScheme> + for VerifierGWC<'params, E> +{ + type Guard = GuardKZG<'params, E>; + type MSMAccumulator = DualMSM<'params, E>; + + fn new(params: &'params ParamsKZG) -> Self { + Self { params } + } + + fn verify_proof< + 'com, + Ch: EncodedChallenge, + T: TranscriptRead, + I, + >( + &self, + transcript: &mut T, + queries: I, + mut msm_accumulator: DualMSM<'params, E>, + ) -> Result + where + I: IntoIterator>> + Clone, + { + let v: ChallengeV<_> = transcript.squeeze_challenge_scalar(); + + let commitment_data = construct_intermediate_sets(queries); + + let w: Vec = (0..commitment_data.len()) + .map(|_| transcript.read_point().map_err(|_| Error::SamplingError)) + .collect::, Error>>()?; + + let u: ChallengeU<_> = transcript.squeeze_challenge_scalar(); + + let mut commitment_multi = MSMKZG::::new(); + let mut eval_multi = E::Scalar::zero(); + + let mut witness = MSMKZG::::new(); + let mut witness_with_aux = MSMKZG::::new(); + + for ((commitment_at_a_point, wi), power_of_u) in + commitment_data.iter().zip(w.into_iter()).zip(powers(*u)) + { + assert!(!commitment_at_a_point.queries.is_empty()); + let z = commitment_at_a_point.point; + + let (mut commitment_batch, eval_batch) = commitment_at_a_point + .queries + .iter() + .zip(powers(*v)) + .map(|(query, power_of_v)| { + assert_eq!(query.get_point(), z); + + let commitment = match query.get_commitment() { + CommitmentReference::Commitment(c) => { + let mut msm = MSMKZG::::new(); + msm.append_term(power_of_v, (*c).into()); + msm + } + CommitmentReference::MSM(msm) => { + let mut msm = msm.clone(); + msm.scale(power_of_v); + msm + } + }; + let eval = power_of_v * query.get_eval(); + + (commitment, eval) + }) + .reduce(|(mut commitment_acc, eval_acc), (commitment, eval)| { + commitment_acc.add_msm(&commitment); + (commitment_acc, eval_acc + eval) + }) + .unwrap(); + + commitment_batch.scale(power_of_u); + commitment_multi.add_msm(&commitment_batch); + eval_multi += power_of_u * eval_batch; + + witness_with_aux.append_term(power_of_u * z, wi.into()); + witness.append_term(power_of_u, wi.into()); + } + + msm_accumulator.left.add_msm(&witness); + + msm_accumulator.right.add_msm(&witness_with_aux); + msm_accumulator.right.add_msm(&commitment_multi); + let g0: E::G1 = self.params.g[0].into(); + msm_accumulator.right.append_term(eval_multi, -g0); + + Ok(Self::Guard::new(msm_accumulator)) + } +} diff --git a/halo2_proofs/src/poly/kzg/multiopen/shplonk.rs b/halo2_proofs/src/poly/kzg/multiopen/shplonk.rs new file mode 100644 index 0000000000..125936229e --- /dev/null +++ b/halo2_proofs/src/poly/kzg/multiopen/shplonk.rs @@ -0,0 +1,272 @@ +mod prover; +mod verifier; + +pub use prover::ProverSHPLONK; +pub use verifier::VerifierSHPLONK; + +use crate::{ + arithmetic::{eval_polynomial, lagrange_interpolate, CurveAffine, FieldExt}, + poly::{query::Query, Coeff, Polynomial}, + transcript::ChallengeScalar, +}; + +use std::{ + collections::{btree_map::Entry, BTreeMap, BTreeSet}, + marker::PhantomData, +}; + +#[derive(Clone, Copy, Debug)] +struct U {} +type ChallengeU = ChallengeScalar; + +#[derive(Clone, Copy, Debug)] +struct V {} +type ChallengeV = ChallengeScalar; + +#[derive(Clone, Copy, Debug)] +struct Y {} +type ChallengeY = ChallengeScalar; + +#[derive(Debug, Clone, PartialEq)] +struct Commitment((T, Vec)); + +impl Commitment { + fn get(&self) -> T { + self.0 .0.clone() + } + + fn evals(&self) -> Vec { + self.0 .1.clone() + } +} + +#[derive(Debug, Clone, PartialEq)] +struct RotationSet { + commitments: Vec>, + points: Vec, +} + +#[derive(Debug, PartialEq)] +struct IntermediateSets> { + rotation_sets: Vec>, + super_point_set: Vec, +} + +fn construct_intermediate_sets>( + queries: I, +) -> IntermediateSets +where + I: IntoIterator + Clone, +{ + let queries = queries.into_iter().collect::>(); + + // Find evaluation of a commitment at a rotation + let get_eval = |commitment: Q::Commitment, rotation: F| -> F { + queries + .iter() + .find(|query| query.get_commitment() == commitment && query.get_point() == rotation) + .unwrap() + .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(); + + // Collect rotation sets for each commitment + // Example elements in the vector: + // (C_0, {r_5}), + // (C_1, {r_1, r_2, r_3}), + // (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 rotation = query.get_point(); + if let Some(pos) = commitment_rotation_set_map + .iter() + .position(|(commitment, _)| *commitment == query.get_commitment()) + { + let (_, rotation_set) = &mut commitment_rotation_set_map[pos]; + if !rotation_set.contains(&rotation) { + rotation_set.push(rotation); + } + } else { + commitment_rotation_set_map.push((query.get_commitment(), vec![rotation])); + }; + } + + // Flatten rotation sets and collect commitments that opens against each commitment set + // Example elements in the vector: + // {r_5}: [C_0], + // {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); + } + } else { + rotation_set_commitment_map.push((rotation_set.clone(), vec![*commitment])) + } + } + + let rotation_sets = rotation_set_commitment_map + .into_iter() + .map(|(rotations, commitments)| { + let commitments: Vec> = commitments + .iter() + .map(|commitment| { + let evals: Vec = rotations + .iter() + .map(|rotation| get_eval(*commitment, *rotation)) + .collect(); + Commitment((*commitment, evals)) + }) + .collect(); + + RotationSet { + commitments, + points: rotations + .iter() + .map(|rotation| *rotation_point_map.get(rotation).unwrap()) + .collect(), + } + }) + .collect::>>(); + + IntermediateSets { + rotation_sets, + super_point_set, + } +} + +#[cfg(test)] +mod proptests { + use proptest::{ + collection::vec, + prelude::*, + sample::{select, subsequence}, + strategy::Strategy, + }; + + use super::{construct_intermediate_sets, Commitment, IntermediateSets}; + use crate::poly::Rotation; + use halo2curves::{pasta::Fp, FieldExt}; + + use std::collections::BTreeMap; + use std::convert::TryFrom; + + #[derive(Debug, Clone)] + struct MyQuery { + point: F, + eval: F, + commitment: usize, + } + + impl super::Query for MyQuery { + type Commitment = usize; + type Eval = Fp; + + fn get_point(&self) -> Fp { + self.point + } + + fn get_eval(&self) -> Self::Eval { + self.eval + } + + fn get_commitment(&self) -> Self::Commitment { + self.commitment + } + } + + prop_compose! { + fn arb_point()( + bytes in vec(any::(), 64) + ) -> Fp { + Fp::from_bytes_wide(&<[u8; 64]>::try_from(bytes).unwrap()) + } + } + + prop_compose! { + fn arb_query(commitment: usize, point: Fp)( + eval in arb_point() + ) -> MyQuery { + MyQuery { + point, + eval, + commitment + } + } + } + + prop_compose! { + // Mapping from column index to point index. + fn arb_queries_inner(num_points: usize, num_cols: usize, num_queries: usize)( + col_indices in vec(select((0..num_cols).collect::>()), num_queries), + point_indices in vec(select((0..num_points).collect::>()), num_queries) + ) -> Vec<(usize, usize)> { + col_indices.into_iter().zip(point_indices.into_iter()).collect() + } + } + + prop_compose! { + fn compare_queries( + num_points: usize, + num_cols: usize, + num_queries: usize, + )( + points_1 in vec(arb_point(), num_points), + points_2 in vec(arb_point(), num_points), + mapping in arb_queries_inner(num_points, num_cols, num_queries) + )( + queries_1 in mapping.iter().map(|(commitment, point_idx)| arb_query(*commitment, points_1[*point_idx])).collect::>(), + queries_2 in mapping.iter().map(|(commitment, point_idx)| arb_query(*commitment, points_2[*point_idx])).collect::>(), + ) -> ( + Vec>, + Vec> + ) { + ( + queries_1, + queries_2, + ) + } + } + + proptest! { + #[test] + fn test_intermediate_sets( + (queries_1, queries_2) in compare_queries(8, 8, 16) + ) { + let IntermediateSets { rotation_sets, .. } = construct_intermediate_sets(queries_1); + let commitment_sets = rotation_sets.iter().map(|data| + data.commitments.iter().map(Commitment::get).collect::>() + ).collect::>(); + + // It shouldn't matter what the point or eval values are; we should get + // the same exact point set indices and point indices again. + let IntermediateSets { rotation_sets: new_rotation_sets, .. } = construct_intermediate_sets(queries_2); + let new_commitment_sets = new_rotation_sets.iter().map(|data| + data.commitments.iter().map(Commitment::get).collect::>() + ).collect::>(); + + assert_eq!(commitment_sets, new_commitment_sets); + } + } +} diff --git a/halo2_proofs/src/poly/kzg/multiopen/shplonk/prover.rs b/halo2_proofs/src/poly/kzg/multiopen/shplonk/prover.rs new file mode 100644 index 0000000000..e99e0b91ea --- /dev/null +++ b/halo2_proofs/src/poly/kzg/multiopen/shplonk/prover.rs @@ -0,0 +1,273 @@ +use super::{ + construct_intermediate_sets, ChallengeU, ChallengeV, ChallengeY, Commitment, Query, RotationSet, +}; +use crate::arithmetic::{ + eval_polynomial, evaluate_vanishing_polynomial, kate_division, lagrange_interpolate, + parallelize, powers, CurveAffine, FieldExt, +}; + +use crate::poly::commitment::{Blind, ParamsProver, Prover}; +use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; +use crate::poly::query::{PolynomialPointer, ProverQuery}; +use crate::poly::Rotation; +use crate::poly::{commitment::Params, Coeff, Polynomial}; +use crate::transcript::{EncodedChallenge, TranscriptWrite}; + +use ff::Field; +use group::Curve; +use halo2curves::pairing::Engine; +use rand_core::RngCore; +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 { + let poly = roots + .iter() + .fold(poly.values, |poly, point| kate_division(&poly, *point)); + + poly +} + +struct CommitmentExtension<'a, C: CurveAffine> { + commitment: Commitment>, + low_degree_equivalent: Polynomial, +} + +impl<'a, C: CurveAffine> Commitment> { + fn extend(&self, points: Vec) -> CommitmentExtension<'a, C> { + let poly = lagrange_interpolate(&points[..], &self.evals()[..]); + + let low_degree_equivalent = Polynomial { + values: poly, + _marker: PhantomData, + }; + + CommitmentExtension { + commitment: self.clone(), + low_degree_equivalent, + } + } +} + +impl<'a, C: CurveAffine> CommitmentExtension<'a, C> { + fn linearisation_contribution(&self, u: C::Scalar) -> Polynomial { + let p_x = self.commitment.get().poly; + let r_eval = eval_polynomial(&self.low_degree_equivalent.values[..], u); + p_x - r_eval + } + + fn quotient_contribution(&self) -> Polynomial { + let len = self.low_degree_equivalent.len(); + let mut p_x = self.commitment.get().poly.clone(); + parallelize(&mut p_x.values[0..len], |lhs, start| { + for (lhs, rhs) in lhs + .iter_mut() + .zip(self.low_degree_equivalent.values[start..].iter()) + { + *lhs -= *rhs; + } + }); + p_x + } +} + +struct RotationSetExtension<'a, C: CurveAffine> { + commitments: Vec>, + points: Vec, +} + +impl<'a, C: CurveAffine> RotationSet> { + fn extend(&self, commitments: Vec>) -> RotationSetExtension<'a, C> { + RotationSetExtension { + commitments, + points: self.points.clone(), + } + } +} + +/// Concrete KZG prover with SHPLONK variant +#[derive(Debug)] +pub struct ProverSHPLONK<'a, E: Engine> { + params: &'a ParamsKZG, +} + +impl<'a, E: Engine> ProverSHPLONK<'a, E> { + /// Given parameters creates new prover instance + pub fn new(params: &'a ParamsKZG) -> Self { + Self { params } + } +} + +/// Create a multi-opening proof +impl<'params, E: Engine + Debug> Prover<'params, KZGCommitmentScheme> + for ProverSHPLONK<'params, E> +{ + fn new(params: &'params ParamsKZG) -> Self { + Self { params } + } + + /// Create a multi-opening proof + fn create_proof< + 'com, + Ch: EncodedChallenge, + T: TranscriptWrite, + R, + I, + >( + &self, + _: R, + transcript: &mut T, + queries: I, + ) -> io::Result<()> + where + I: IntoIterator> + Clone, + R: RngCore, + { + // TODO: explore if it is safe to use same challenge + // for different sets that are already combined with anoter challenge + let y: ChallengeY<_> = transcript.squeeze_challenge_scalar(); + + let quotient_contribution = + |rotation_set: &RotationSetExtension| -> Polynomial { + // [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()); + + // define numerator polynomial as + // N_i_j(X) = (P_i_j(X) - R_i_j(X)) + // and combine polynomials with same evaluation point set + // N_i(X) = linear_combinination(y, N_i_j(X)) + // where y is random scalar to combine numerator polynomials + let n_x = numerators + .zip(powers(*y)) + .map(|(numerator, power_of_y)| numerator * power_of_y) + .reduce(|acc, numerator| acc + &numerator) + .unwrap(); + + let points = &rotation_set.points[..]; + + // quotient contribution of this evaluation set is + // 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()); + + Polynomial { + values: poly, + _marker: PhantomData, + } + }; + + let intermediate_sets = construct_intermediate_sets(queries); + let (rotation_sets, super_point_set) = ( + intermediate_sets.rotation_sets, + intermediate_sets.super_point_set, + ); + + let rotation_sets: Vec> = rotation_sets + .iter() + .map(|rotation_set| { + let commitments: Vec> = rotation_set + .commitments + .iter() + .map(|commitment_data| commitment_data.extend(rotation_set.points.clone())) + .collect(); + rotation_set.extend(commitments) + }) + .collect(); + + let v: ChallengeV<_> = transcript.squeeze_challenge_scalar(); + + let quotient_polynomials = rotation_sets.iter().map(quotient_contribution); + + let h_x: Polynomial = quotient_polynomials + .zip(powers(*v)) + .map(|(poly, power_of_v)| poly * power_of_v) + .reduce(|acc, poly| acc + &poly) + .unwrap(); + + let h = self.params.commit(&h_x, Blind::default()).to_affine(); + 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(); + + // calculate difference vanishing polynomial evaluation + + let z_i = evaluate_vanishing_polynomial(&diffs[..], *u); + + // inner linearisation contibutions are + // [P_i_0(X) - r_i_0, P_i_1(X) - r_i_1, ... ] where + // r_i_j = R_i_j(u) is the evaluation of low degree equivalent polynomial + // where u is random evaluation point + let inner_contributions = rotation_set + .commitments + .iter() + .map(|commitment| commitment.linearisation_contribution(*u)); + + // 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(); + + // finally scale l_x by difference vanishing polynomial evaluation z_i + (l_x * z_i, z_i) + }; + + #[allow(clippy::type_complexity)] + let (linearisation_contibutions, z_diffs): ( + Vec>, + Vec, + ) = rotation_sets + .into_iter() + .map(linearisation_contribution) + .unzip(); + + let l_x: Polynomial = linearisation_contibutions + .into_iter() + .zip(powers(*v)) + .map(|(poly, power_of_v)| poly * power_of_v) + .reduce(|acc, poly| acc + &poly) + .unwrap(); + + let l_x = l_x - &(h_x * zt_eval); + + // sanity check + { + let must_be_zero = eval_polynomial(&l_x.values[..], *u); + assert_eq!(must_be_zero, E::Scalar::zero()); + } + + let mut h_x = div_by_vanishing(l_x, &[*u]); + + // normalize coefficients by the coefficient of the first polynomial + let z_0_diff_inv = z_diffs[0].invert().unwrap(); + for h_i in h_x.iter_mut() { + h_i.mul_assign(z_0_diff_inv) + } + + let h_x = Polynomial { + values: h_x, + _marker: PhantomData, + }; + + let h = self.params.commit(&h_x, Blind::default()).to_affine(); + transcript.write_point(h)?; + + Ok(()) + } +} diff --git a/halo2_proofs/src/poly/kzg/multiopen/shplonk/verifier.rs b/halo2_proofs/src/poly/kzg/multiopen/shplonk/verifier.rs new file mode 100644 index 0000000000..5438d41481 --- /dev/null +++ b/halo2_proofs/src/poly/kzg/multiopen/shplonk/verifier.rs @@ -0,0 +1,142 @@ +use std::fmt::Debug; +use std::io::Read; + +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::poly::commitment::Verifier; +use crate::poly::commitment::MSM; +use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; +use crate::poly::kzg::msm::DualMSM; +use crate::poly::kzg::msm::{PreMSM, MSMKZG}; +use crate::poly::kzg::strategy::{AccumulatorStrategy, GuardKZG, SingleStrategy}; +use crate::poly::query::Query; +use crate::poly::query::{CommitmentReference, VerifierQuery}; +use crate::poly::strategy::VerificationStrategy; +use crate::poly::{ + commitment::{Params, ParamsVerifier}, + Error, +}; +use crate::transcript::{EncodedChallenge, TranscriptRead}; +use ff::Field; +use group::Group; +use halo2curves::pairing::{Engine, MillerLoopResult, MultiMillerLoop}; +use rand_core::OsRng; +use std::ops::MulAssign; + +/// Concrete KZG multiopen verifier with SHPLONK variant +#[derive(Debug)] +pub struct VerifierSHPLONK<'params, E: Engine> { + params: &'params ParamsKZG, +} + +impl<'params, E: MultiMillerLoop + Debug> Verifier<'params, KZGCommitmentScheme> + for VerifierSHPLONK<'params, E> +{ + type Guard = GuardKZG<'params, E>; + type MSMAccumulator = DualMSM<'params, E>; + + fn new(params: &'params ParamsKZG) -> Self { + Self { params } + } + + /// Verify a multi-opening proof + fn verify_proof< + 'com, + Ch: EncodedChallenge, + T: TranscriptRead, + I, + >( + &self, + transcript: &mut T, + queries: I, + mut msm_accumulator: DualMSM<'params, E>, + ) -> Result + where + I: IntoIterator>> + Clone, + { + let intermediate_sets = construct_intermediate_sets(queries); + let (rotation_sets, super_point_set) = ( + intermediate_sets.rotation_sets, + intermediate_sets.super_point_set, + ); + + let y: ChallengeY<_> = transcript.squeeze_challenge_scalar(); + let v: ChallengeV<_> = transcript.squeeze_challenge_scalar(); + + let h1 = transcript.read_point().map_err(|_| Error::SamplingError)?; + 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()); + for (i, (rotation_set, power_of_v)) in rotation_sets.iter().zip(powers(*v)).enumerate() { + let diffs: Vec = super_point_set + .iter() + .filter(|point| !rotation_set.points.contains(point)) + .copied() + .collect(); + let mut z_diff_i = evaluate_vanishing_polynomial(&diffs[..], *u); + + // normalize coefficients by the coefficient of the first commitment + 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(); + } else { + z_diff_i.mul_assign(z_0_diff_inverse); + } + + let (mut inner_msm, r_inner_acc) = rotation_set + .commitments + .iter() + .zip(powers(*y)) + .map(|(commitment_data, power_of_y)| { + // calculate low degree equivalent + let r_x = lagrange_interpolate( + &rotation_set.points[..], + &commitment_data.evals()[..], + ); + let r_eval = power_of_y * eval_polynomial(&r_x[..], *u); + let msm = match commitment_data.get() { + CommitmentReference::Commitment(c) => { + let mut msm = MSMKZG::::new(); + msm.append_term(power_of_y, (*c).into()); + msm + } + CommitmentReference::MSM(msm) => { + let mut msm = msm.clone(); + msm.scale(power_of_y); + msm + } + }; + (msm, r_eval) + }) + .reduce(|(mut msm_acc, r_eval_acc), (msm, r_eval)| { + msm_acc.add_msm(&msm); + (msm_acc, r_eval_acc + r_eval) + }) + .unwrap(); + + inner_msm.scale(power_of_v * z_diff_i); + outer_msm.add_msm(inner_msm); + r_outer_acc += power_of_v * r_inner_acc * z_diff_i; + } + let mut outer_msm = outer_msm.normalize(); + let g1: E::G1 = self.params.g[0].into(); + outer_msm.append_term(-r_outer_acc, g1); + 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.right.add_msm(&outer_msm); + + Ok(Self::Guard::new(msm_accumulator)) + } +} diff --git a/halo2_proofs/src/poly/kzg/strategy.rs b/halo2_proofs/src/poly/kzg/strategy.rs new file mode 100644 index 0000000000..896760067d --- /dev/null +++ b/halo2_proofs/src/poly/kzg/strategy.rs @@ -0,0 +1,147 @@ +use std::{fmt::Debug, marker::PhantomData}; + +use super::{ + commitment::{KZGCommitmentScheme, ParamsKZG}, + msm::{DualMSM, MSMKZG}, + multiopen::VerifierGWC, +}; +use crate::{ + plonk::Error, + poly::{ + commitment::{Verifier, MSM}, + ipa::msm::MSMIPA, + strategy::{Guard, VerificationStrategy}, + }, + transcript::{EncodedChallenge, TranscriptRead}, +}; +use ff::Field; +use group::Group; +use halo2curves::{ + pairing::{Engine, MillerLoopResult, MultiMillerLoop}, + CurveAffine, +}; +use rand_core::OsRng; + +/// Wrapper for linear verification accumulator +#[derive(Debug, Clone)] +pub struct GuardKZG<'params, E: MultiMillerLoop + Debug> { + pub(crate) msm_accumulator: DualMSM<'params, E>, +} + +/// Define accumulator type as `DualMSM` +impl<'params, E: MultiMillerLoop + Debug> Guard> for GuardKZG<'params, E> { + type MSMAccumulator = DualMSM<'params, E>; +} + +/// KZG specific operations +impl<'params, E: MultiMillerLoop + Debug> GuardKZG<'params, E> { + pub(crate) fn new(msm_accumulator: DualMSM<'params, E>) -> Self { + Self { msm_accumulator } + } +} + +/// A verifier that checks multiple proofs in a batch +#[derive(Clone, Debug)] +pub struct AccumulatorStrategy<'params, E: Engine> { + pub(crate) msm_accumulator: DualMSM<'params, E>, +} + +impl<'params, E: MultiMillerLoop + Debug> AccumulatorStrategy<'params, E> { + /// Constructs an empty batch verifier + pub fn new(params: &'params ParamsKZG) -> Self { + AccumulatorStrategy { + msm_accumulator: DualMSM::new(params), + } + } + + /// Constructs and initialized new batch verifier + pub fn with(msm_accumulator: DualMSM<'params, E>) -> Self { + AccumulatorStrategy { msm_accumulator } + } +} + +/// A verifier that checks a single proof +#[derive(Clone, Debug)] +pub struct SingleStrategy<'params, E: Engine> { + pub(crate) msm: DualMSM<'params, E>, +} + +impl<'params, E: MultiMillerLoop + Debug> SingleStrategy<'params, E> { + /// Constructs an empty batch verifier + pub fn new(params: &'params ParamsKZG) -> Self { + SingleStrategy { + msm: DualMSM::new(params), + } + } +} + +impl< + 'params, + E: MultiMillerLoop + Debug, + V: Verifier< + 'params, + KZGCommitmentScheme, + MSMAccumulator = DualMSM<'params, E>, + Guard = GuardKZG<'params, E>, + >, + > VerificationStrategy<'params, KZGCommitmentScheme, V> for AccumulatorStrategy<'params, E> +{ + type Output = Self; + + fn new(params: &'params ParamsKZG) -> Self { + AccumulatorStrategy::new(params) + } + + fn process( + mut self, + f: impl FnOnce(V::MSMAccumulator) -> Result, + ) -> Result { + self.msm_accumulator.scale(E::Scalar::random(OsRng)); + + // Guard is updated with new msm contributions + let guard = f(self.msm_accumulator)?; + Ok(Self { + msm_accumulator: guard.msm_accumulator, + }) + } + + fn finalize(self) -> bool { + self.msm_accumulator.check() + } +} + +impl< + 'params, + E: MultiMillerLoop + Debug, + V: Verifier< + 'params, + KZGCommitmentScheme, + MSMAccumulator = DualMSM<'params, E>, + Guard = GuardKZG<'params, E>, + >, + > VerificationStrategy<'params, KZGCommitmentScheme, V> for SingleStrategy<'params, E> +{ + type Output = (); + + fn new(params: &'params ParamsKZG) -> Self { + Self::new(params) + } + + fn process( + self, + f: impl FnOnce(V::MSMAccumulator) -> Result, + ) -> Result { + // Guard is updated with new msm contributions + let guard = f(self.msm)?; + let msm = guard.msm_accumulator; + if msm.check() { + Ok(()) + } else { + Err(Error::ConstraintSystemFailure) + } + } + + fn finalize(self) -> bool { + unreachable!(); + } +} diff --git a/halo2_proofs/src/poly/multiopen.rs b/halo2_proofs/src/poly/multiopen.rs deleted file mode 100644 index b410d2b25c..0000000000 --- a/halo2_proofs/src/poly/multiopen.rs +++ /dev/null @@ -1,503 +0,0 @@ -//! This module contains an optimisation of the polynomial commitment opening -//! scheme described in the [Halo][halo] paper. -//! -//! [halo]: https://eprint.iacr.org/2019/1021 - -use std::collections::{BTreeMap, BTreeSet}; - -use super::*; -use crate::{ - arithmetic::{CurveAffine, FieldExt}, - transcript::ChallengeScalar, -}; - -mod prover; -mod verifier; - -pub use prover::create_proof; -pub use verifier::verify_proof; - -#[derive(Clone, Copy, Debug)] -struct X1 {} -/// Challenge for compressing openings at the same point sets together. -type ChallengeX1 = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct X2 {} -/// Challenge for keeping the multi-point quotient polynomial terms linearly independent. -type ChallengeX2 = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct X3 {} -/// Challenge point at which the commitments are opened. -type ChallengeX3 = ChallengeScalar; - -#[derive(Clone, Copy, Debug)] -struct X4 {} -/// Challenge for collapsing the openings of the various remaining polynomials at x_3 -/// together. -type ChallengeX4 = ChallengeScalar; - -/// A polynomial query at a point -#[derive(Debug, Clone)] -pub struct ProverQuery<'a, C: CurveAffine> { - /// point at which polynomial is queried - pub point: C::Scalar, - /// coefficients of polynomial - pub poly: &'a Polynomial, - /// blinding factor of polynomial - pub blind: commitment::Blind, -} - -/// A polynomial query at a point -#[derive(Debug, Clone)] -pub struct VerifierQuery<'r, 'params: 'r, C: CurveAffine> { - /// point at which polynomial is queried - point: C::Scalar, - /// commitment to polynomial - commitment: CommitmentReference<'r, 'params, C>, - /// evaluation of polynomial at query point - eval: C::Scalar, -} - -impl<'r, 'params: 'r, C: CurveAffine> VerifierQuery<'r, 'params, C> { - /// Create a new verifier query based on a commitment - pub fn new_commitment(commitment: &'r C, point: C::Scalar, eval: C::Scalar) -> Self { - VerifierQuery { - point, - eval, - commitment: CommitmentReference::Commitment(commitment), - } - } - - /// Create a new verifier query based on a linear combination of commitments - pub fn new_msm( - msm: &'r commitment::MSM<'params, C>, - point: C::Scalar, - eval: C::Scalar, - ) -> Self { - VerifierQuery { - point, - eval, - commitment: CommitmentReference::MSM(msm), - } - } -} - -#[derive(Copy, Clone, Debug)] -enum CommitmentReference<'r, 'params: 'r, C: CurveAffine> { - Commitment(&'r C), - MSM(&'r commitment::MSM<'params, C>), -} - -impl<'r, 'params: 'r, C: CurveAffine> PartialEq for CommitmentReference<'r, 'params, C> { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (&CommitmentReference::Commitment(a), &CommitmentReference::Commitment(b)) => { - std::ptr::eq(a, b) - } - (&CommitmentReference::MSM(a), &CommitmentReference::MSM(b)) => std::ptr::eq(a, b), - _ => false, - } - } -} - -#[derive(Debug)] -struct CommitmentData { - commitment: T, - set_index: usize, - point_indices: Vec, - evals: Vec, -} - -impl CommitmentData { - fn new(commitment: T) -> Self { - CommitmentData { - commitment, - set_index: 0, - point_indices: vec![], - evals: vec![], - } - } -} - -trait Query: Sized { - type Commitment: PartialEq + Copy; - type Eval: Clone + Default; - - fn get_point(&self) -> F; - fn get_eval(&self) -> Self::Eval; - fn get_commitment(&self) -> Self::Commitment; -} - -type IntermediateSets = ( - Vec>::Eval, >::Commitment>>, - Vec>, -); - -fn construct_intermediate_sets>(queries: I) -> IntermediateSets -where - I: IntoIterator + Clone, -{ - // Construct sets of unique commitments and corresponding information about - // their queries. - let mut commitment_map: Vec> = vec![]; - - // Also construct mapping from a unique point to a point_index. This defines - // an ordering on the points. - let mut point_index_map = BTreeMap::new(); - - // Iterate over all of the queries, computing the ordering of the points - // while also creating new commitment data. - for query in queries.clone() { - let num_points = point_index_map.len(); - let point_idx = point_index_map - .entry(query.get_point()) - .or_insert(num_points); - - if let Some(pos) = commitment_map - .iter() - .position(|comm| comm.commitment == query.get_commitment()) - { - commitment_map[pos].point_indices.push(*point_idx); - } else { - let mut tmp = CommitmentData::new(query.get_commitment()); - tmp.point_indices.push(*point_idx); - commitment_map.push(tmp); - } - } - - // Also construct inverse mapping from point_index to the point - let mut inverse_point_index_map = BTreeMap::new(); - for (&point, &point_index) in point_index_map.iter() { - inverse_point_index_map.insert(point_index, point); - } - - // Construct map of unique ordered point_idx_sets to their set_idx - let mut point_idx_sets = BTreeMap::new(); - // Also construct mapping from commitment to point_idx_set - let mut commitment_set_map = Vec::new(); - - for commitment_data in commitment_map.iter() { - let mut point_index_set = BTreeSet::new(); - // Note that point_index_set is ordered, unlike point_indices - for &point_index in commitment_data.point_indices.iter() { - point_index_set.insert(point_index); - } - - // Push point_index_set to CommitmentData for the relevant commitment - commitment_set_map.push((commitment_data.commitment, point_index_set.clone())); - - let num_sets = point_idx_sets.len(); - point_idx_sets.entry(point_index_set).or_insert(num_sets); - } - - // Initialise empty evals vec for each unique commitment - for commitment_data in commitment_map.iter_mut() { - let len = commitment_data.point_indices.len(); - commitment_data.evals = vec![Q::Eval::default(); len]; - } - - // Populate set_index, evals and points for each commitment using point_idx_sets - for query in queries { - // The index of the point at which the commitment is queried - let point_index = point_index_map.get(&query.get_point()).unwrap(); - - // The point_index_set at which the commitment was queried - let mut point_index_set = BTreeSet::new(); - for (commitment, point_idx_set) in commitment_set_map.iter() { - if query.get_commitment() == *commitment { - point_index_set = point_idx_set.clone(); - } - } - assert!(!point_index_set.is_empty()); - - // The set_index of the point_index_set - let set_index = point_idx_sets.get(&point_index_set).unwrap(); - for commitment_data in commitment_map.iter_mut() { - if query.get_commitment() == commitment_data.commitment { - commitment_data.set_index = *set_index; - } - } - let point_index_set: Vec = point_index_set.iter().cloned().collect(); - - // The offset of the point_index in the point_index_set - let point_index_in_set = point_index_set - .iter() - .position(|i| i == point_index) - .unwrap(); - - for commitment_data in commitment_map.iter_mut() { - if query.get_commitment() == commitment_data.commitment { - // Insert the eval using the ordering of the point_index_set - commitment_data.evals[point_index_in_set] = query.get_eval(); - } - } - } - - // Get actual points in each point set - let mut point_sets: Vec> = vec![Vec::new(); point_idx_sets.len()]; - for (point_idx_set, &set_idx) in point_idx_sets.iter() { - for &point_idx in point_idx_set.iter() { - let point = inverse_point_index_map.get(&point_idx).unwrap(); - point_sets[set_idx].push(*point); - } - } - - (commitment_map, point_sets) -} - -#[test] -fn test_roundtrip() { - use group::Curve; - use rand_core::OsRng; - - use super::commitment::{Blind, Params}; - use crate::arithmetic::{eval_polynomial, FieldExt}; - use crate::pasta::{EqAffine, Fp}; - use crate::transcript::Challenge255; - - const K: u32 = 4; - - let params: Params = Params::new(K); - let domain = EvaluationDomain::new(1, K); - let rng = OsRng; - - let mut ax = domain.empty_coeff(); - for (i, a) in ax.iter_mut().enumerate() { - *a = Fp::from(10 + i as u64); - } - - let mut bx = domain.empty_coeff(); - for (i, a) in bx.iter_mut().enumerate() { - *a = Fp::from(100 + i as u64); - } - - let mut cx = domain.empty_coeff(); - for (i, a) in cx.iter_mut().enumerate() { - *a = Fp::from(100 + i as u64); - } - - let blind = Blind(Fp::random(rng)); - - let a = params.commit(&ax, blind).to_affine(); - let b = params.commit(&bx, blind).to_affine(); - let c = params.commit(&cx, blind).to_affine(); - - let x = Fp::random(rng); - let y = Fp::random(rng); - let avx = eval_polynomial(&ax, x); - let bvx = eval_polynomial(&bx, x); - let cvy = eval_polynomial(&cx, y); - - let mut transcript = crate::transcript::Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - create_proof( - ¶ms, - rng, - &mut transcript, - std::iter::empty() - .chain(Some(ProverQuery { - point: x, - poly: &ax, - blind, - })) - .chain(Some(ProverQuery { - point: x, - poly: &bx, - blind, - })) - .chain(Some(ProverQuery { - point: y, - poly: &cx, - blind, - })), - ) - .unwrap(); - let proof = transcript.finalize(); - - { - let mut proof = &proof[..]; - let mut transcript = - crate::transcript::Blake2bRead::<_, _, Challenge255<_>>::init(&mut proof); - let msm = params.empty_msm(); - - let guard = verify_proof( - ¶ms, - &mut transcript, - std::iter::empty() - .chain(Some(VerifierQuery::new_commitment(&a, x, avx))) - .chain(Some(VerifierQuery::new_commitment(&b, x, avx))) // NB: wrong! - .chain(Some(VerifierQuery::new_commitment(&c, y, cvy))), - msm, - ) - .unwrap(); - - // Should fail. - assert!(!guard.use_challenges().eval()); - } - - { - let mut proof = &proof[..]; - - let mut transcript = - crate::transcript::Blake2bRead::<_, _, Challenge255<_>>::init(&mut proof); - let msm = params.empty_msm(); - - let guard = verify_proof( - ¶ms, - &mut transcript, - std::iter::empty() - .chain(Some(VerifierQuery::new_commitment(&a, x, avx))) - .chain(Some(VerifierQuery::new_commitment(&b, x, bvx))) - .chain(Some(VerifierQuery::new_commitment(&c, y, cvy))), - msm, - ) - .unwrap(); - - // Should succeed. - assert!(guard.use_challenges().eval()); - } -} - -#[cfg(test)] -mod tests { - use super::{construct_intermediate_sets, Query}; - use crate::arithmetic::FieldExt; - use crate::pasta::Fp; - - #[derive(Clone)] - struct MyQuery { - commitment: usize, - point: F, - eval: F, - } - - impl Query for MyQuery { - type Commitment = usize; - type Eval = F; - - fn get_point(&self) -> F { - self.point.clone() - } - fn get_eval(&self) -> Self::Eval { - self.eval.clone() - } - fn get_commitment(&self) -> Self::Commitment { - self.commitment - } - } -} - -#[cfg(test)] -mod proptests { - use proptest::{ - collection::vec, - prelude::*, - sample::{select, subsequence}, - strategy::Strategy, - }; - - use super::construct_intermediate_sets; - use crate::poly::Rotation; - use pasta_curves::{arithmetic::FieldExt, Fp}; - - use std::collections::BTreeMap; - use std::convert::TryFrom; - - #[derive(Debug, Clone)] - struct MyQuery { - point: F, - eval: F, - commitment: usize, - } - - impl super::Query for MyQuery { - type Commitment = usize; - type Eval = Fp; - - fn get_point(&self) -> Fp { - self.point - } - - fn get_eval(&self) -> Self::Eval { - self.eval - } - - fn get_commitment(&self) -> Self::Commitment { - self.commitment - } - } - - prop_compose! { - fn arb_point()( - bytes in vec(any::(), 64) - ) -> Fp { - Fp::from_bytes_wide(&<[u8; 64]>::try_from(bytes).unwrap()) - } - } - - prop_compose! { - fn arb_query(commitment: usize, point: Fp)( - eval in arb_point() - ) -> MyQuery { - MyQuery { - point, - eval, - commitment - } - } - } - - prop_compose! { - // Mapping from column index to point index. - fn arb_queries_inner(num_points: usize, num_cols: usize, num_queries: usize)( - col_indices in vec(select((0..num_cols).collect::>()), num_queries), - point_indices in vec(select((0..num_points).collect::>()), num_queries) - ) -> Vec<(usize, usize)> { - col_indices.into_iter().zip(point_indices.into_iter()).collect() - } - } - - prop_compose! { - fn compare_queries( - num_points: usize, - num_cols: usize, - num_queries: usize, - )( - points_1 in vec(arb_point(), num_points), - points_2 in vec(arb_point(), num_points), - mapping in arb_queries_inner(num_points, num_cols, num_queries) - )( - queries_1 in mapping.iter().map(|(commitment, point_idx)| arb_query(*commitment, points_1[*point_idx])).collect::>(), - queries_2 in mapping.iter().map(|(commitment, point_idx)| arb_query(*commitment, points_2[*point_idx])).collect::>(), - ) -> ( - Vec>, - Vec> - ) { - ( - queries_1, - queries_2, - ) - } - } - - proptest! { - #[test] - fn test_intermediate_sets( - (queries_1, queries_2) in compare_queries(8, 8, 16) - ) { - let (commitment_data, _point_sets) = construct_intermediate_sets(queries_1); - let set_indices = commitment_data.iter().map(|data| data.set_index).collect::>(); - let point_indices = commitment_data.iter().map(|data| data.point_indices.clone()).collect::>(); - - // It shouldn't matter what the point or eval values are; we should get - // the same exact point set indices and point indices again. - let (new_commitment_data, _new_point_sets) = construct_intermediate_sets(queries_2); - let new_set_indices = new_commitment_data.iter().map(|data| data.set_index).collect::>(); - let new_point_indices = new_commitment_data.iter().map(|data| data.point_indices.clone()).collect::>(); - - assert_eq!(set_indices, new_set_indices); - assert_eq!(point_indices, new_point_indices); - } - } -} diff --git a/halo2_proofs/src/poly/multiopen/prover.rs b/halo2_proofs/src/poly/multiopen/prover.rs deleted file mode 100644 index 245bd5b0b4..0000000000 --- a/halo2_proofs/src/poly/multiopen/prover.rs +++ /dev/null @@ -1,145 +0,0 @@ -use super::super::{ - commitment::{self, Blind, Params}, - Coeff, Polynomial, -}; -use super::{ - construct_intermediate_sets, ChallengeX1, ChallengeX2, ChallengeX3, ChallengeX4, ProverQuery, - Query, -}; - -use crate::arithmetic::{eval_polynomial, kate_division, CurveAffine, FieldExt}; -use crate::transcript::{EncodedChallenge, TranscriptWrite}; - -use ff::Field; -use group::Curve; -use rand_core::RngCore; -use std::io; -use std::marker::PhantomData; - -/// Create a multi-opening proof -pub fn create_proof< - 'a, - I, - C: CurveAffine, - E: EncodedChallenge, - R: RngCore, - T: TranscriptWrite, ->( - params: &Params, - mut rng: R, - transcript: &mut T, - queries: I, -) -> io::Result<()> -where - I: IntoIterator> + Clone, -{ - let x_1: ChallengeX1<_> = transcript.squeeze_challenge_scalar(); - let x_2: ChallengeX2<_> = transcript.squeeze_challenge_scalar(); - - let (poly_map, point_sets) = construct_intermediate_sets(queries); - - // 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 accumulate = - |set_idx: usize, new_poly: &Polynomial, blind: Blind| { - if let Some(poly) = &q_polys[set_idx] { - q_polys[set_idx] = Some(poly.clone() * *x_1 + new_poly); - } else { - q_polys[set_idx] = Some(new_poly.clone()); - } - q_blinds[set_idx] *= *x_1; - q_blinds[set_idx] += blind; - }; - - for commitment_data in poly_map.into_iter() { - accumulate( - commitment_data.set_index, // set_idx, - commitment_data.commitment.poly, // poly, - commitment_data.commitment.blind, // blind, - ); - } - } - - let q_prime_poly = point_sets - .iter() - .zip(q_polys.iter()) - .fold(None, |q_prime_poly, (points, poly)| { - let mut poly = points - .iter() - .fold(poly.clone().unwrap().values, |poly, point| { - kate_division(&poly, *point) - }); - poly.resize(params.n as usize, C::Scalar::zero()); - let poly = Polynomial { - values: poly, - _marker: PhantomData, - }; - - if q_prime_poly.is_none() { - Some(poly) - } else { - q_prime_poly.map(|q_prime_poly| q_prime_poly * *x_2 + &poly) - } - }) - .unwrap(); - - let q_prime_blind = Blind(C::Scalar::random(&mut rng)); - let q_prime_commitment = params.commit(&q_prime_poly, q_prime_blind).to_affine(); - - transcript.write_point(q_prime_commitment)?; - - let x_3: ChallengeX3<_> = transcript.squeeze_challenge_scalar(); - - // Prover sends u_i for all i, which correspond to the evaluation - // of each Q polynomial commitment at x_3. - for q_i_poly in &q_polys { - transcript.write_scalar(eval_polynomial(q_i_poly.as_ref().unwrap(), *x_3))?; - } - - let x_4: ChallengeX4<_> = transcript.squeeze_challenge_scalar(); - - let (p_poly, p_poly_blind) = q_polys.into_iter().zip(q_blinds.into_iter()).fold( - (q_prime_poly, q_prime_blind), - |(q_prime_poly, q_prime_blind), (poly, blind)| { - ( - q_prime_poly * *x_4 + &poly.unwrap(), - Blind((q_prime_blind.0 * &(*x_4)) + &blind.0), - ) - }, - ); - - commitment::create_proof(params, rng, transcript, &p_poly, p_poly_blind, *x_3) -} - -#[doc(hidden)] -#[derive(Copy, Clone)] -pub struct PolynomialPointer<'a, C: CurveAffine> { - poly: &'a Polynomial, - blind: commitment::Blind, -} - -impl<'a, C: CurveAffine> PartialEq for PolynomialPointer<'a, C> { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self.poly, other.poly) - } -} - -impl<'a, C: CurveAffine> Query for ProverQuery<'a, C> { - type Commitment = PolynomialPointer<'a, C>; - type Eval = (); - - fn get_point(&self) -> C::Scalar { - self.point - } - fn get_eval(&self) {} - fn get_commitment(&self) -> Self::Commitment { - PolynomialPointer { - poly: self.poly, - blind: self.blind, - } - } -} diff --git a/halo2_proofs/src/poly/multiopen/verifier.rs b/halo2_proofs/src/poly/multiopen/verifier.rs deleted file mode 100644 index 0505f42634..0000000000 --- a/halo2_proofs/src/poly/multiopen/verifier.rs +++ /dev/null @@ -1,143 +0,0 @@ -use ff::Field; -use rand_core::RngCore; - -use super::super::{ - commitment::{Guard, Params, MSM}, - Error, -}; -use super::{ - construct_intermediate_sets, ChallengeX1, ChallengeX2, ChallengeX3, ChallengeX4, - CommitmentReference, Query, VerifierQuery, -}; -use crate::arithmetic::{eval_polynomial, lagrange_interpolate, CurveAffine, FieldExt}; -use crate::transcript::{EncodedChallenge, TranscriptRead}; - -/// Verify a multi-opening proof -pub fn verify_proof< - 'r, - 'params: 'r, - I, - C: CurveAffine, - E: EncodedChallenge, - T: TranscriptRead, ->( - params: &'params Params, - transcript: &mut T, - queries: I, - mut msm: MSM<'params, C>, -) -> Result, Error> -where - I: IntoIterator> + Clone, -{ - // Sample x_1 for compressing openings at the same point sets together - let x_1: ChallengeX1<_> = transcript.squeeze_challenge_scalar(); - - // Sample a challenge x_2 for keeping the multi-point quotient - // polynomial terms linearly independent. - let x_2: ChallengeX2<_> = transcript.squeeze_challenge_scalar(); - - let (commitment_map, point_sets) = construct_intermediate_sets(queries); - - // Compress the commitments and expected evaluations at x together. - // using the challenge x_1 - let mut q_commitments: Vec<_> = vec![params.empty_msm(); point_sets.len()]; - - // A vec of vecs of evals. The outer vec corresponds to the point set, - // 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()]); - } - { - let mut accumulate = |set_idx: usize, new_commitment, evals: Vec| { - q_commitments[set_idx].scale(*x_1); - match new_commitment { - CommitmentReference::Commitment(c) => { - q_commitments[set_idx].append_term(C::Scalar::one(), *c); - } - CommitmentReference::MSM(msm) => { - q_commitments[set_idx].add_msm(msm); - } - } - for (eval, set_eval) in evals.iter().zip(q_eval_sets[set_idx].iter_mut()) { - *set_eval *= &(*x_1); - *set_eval += eval; - } - }; - - // Each commitment corresponds to evaluations at a set of points. - // For each set, we collapse each commitment's evals pointwise. - for commitment_data in commitment_map.into_iter() { - accumulate( - commitment_data.set_index, // set_idx, - commitment_data.commitment, // commitment, - commitment_data.evals, // evals - ); - } - } - - // Obtain the commitment to the multi-point quotient polynomial f(X). - let q_prime_commitment = transcript.read_point().map_err(|_| Error::SamplingError)?; - - // Sample a challenge x_3 for checking that f(X) was committed to - // correctly. - let x_3: ChallengeX3<_> = transcript.squeeze_challenge_scalar(); - - // u is a vector containing the evaluations of the Q polynomial - // commitments at x_3 - let mut u = Vec::with_capacity(q_eval_sets.len()); - for _ in 0..q_eval_sets.len() { - u.push(transcript.read_scalar().map_err(|_| Error::SamplingError)?); - } - - // We can compute the expected msm_eval at x_3 using the u provided - // by the prover and from x_2 - let msm_eval = point_sets - .iter() - .zip(q_eval_sets.iter()) - .zip(u.iter()) - .fold( - 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); - let eval = points.iter().fold(*proof_eval - &r_eval, |eval, point| { - eval * &(*x_3 - point).invert().unwrap() - }); - msm_eval * &(*x_2) + &eval - }, - ); - - // Sample a challenge x_4 that we will use to collapse the openings of - // the various remaining polynomials at x_3 together. - 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); - let (msm, v) = q_commitments.into_iter().zip(u.iter()).fold( - (msm, msm_eval), - |(mut msm, msm_eval), (q_commitment, q_eval)| { - msm.scale(*x_4); - msm.add_msm(&q_commitment); - (msm, msm_eval * &(*x_4) + q_eval) - }, - ); - - // Verify the opening proof - super::commitment::verify_proof(params, msm, transcript, *x_3, v) -} - -impl<'a, 'b, C: CurveAffine> Query for VerifierQuery<'a, 'b, C> { - type Commitment = CommitmentReference<'a, 'b, C>; - type Eval = C::Scalar; - - fn get_point(&self) -> C::Scalar { - self.point - } - fn get_eval(&self) -> C::Scalar { - self.eval - } - fn get_commitment(&self) -> Self::Commitment { - self.commitment - } -} diff --git a/halo2_proofs/src/poly/multiopen_test.rs b/halo2_proofs/src/poly/multiopen_test.rs new file mode 100644 index 0000000000..1df8edaa03 --- /dev/null +++ b/halo2_proofs/src/poly/multiopen_test.rs @@ -0,0 +1,270 @@ +#[cfg(test)] +mod test { + use crate::arithmetic::{eval_polynomial, FieldExt}; + use crate::plonk::Error; + use crate::poly::commitment::ParamsProver; + use crate::poly::commitment::{Blind, ParamsVerifier, MSM}; + use crate::poly::query::PolynomialPointer; + use crate::poly::{ + commitment::{CommitmentScheme, Params, Prover, Verifier}, + query::{ProverQuery, VerifierQuery}, + strategy::VerificationStrategy, + EvaluationDomain, + }; + use crate::poly::{Coeff, Polynomial}; + use crate::transcript::{ + self, Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, TranscriptRead, + TranscriptReadBuffer, TranscriptWrite, TranscriptWriterBuffer, + }; + use ff::Field; + use group::{Curve, Group}; + use halo2curves::CurveAffine; + use rand_core::{OsRng, RngCore}; + use std::io::{Read, Write}; + + #[test] + fn test_roundtrip_ipa() { + 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<_>, + _, + Blake2bWrite<_, _, Challenge255<_>>, + >(¶ms); + + let verifier_params = params.verifier_params(); + + verify::< + IPACommitmentScheme, + VerifierIPA<_>, + _, + Blake2bRead<_, _, Challenge255<_>>, + AccumulatorStrategy<_>, + >(verifier_params, &proof[..], false); + + verify::< + IPACommitmentScheme, + VerifierIPA<_>, + _, + Blake2bRead<_, _, Challenge255<_>>, + AccumulatorStrategy<_>, + >(verifier_params, &proof[..], true); + } + + #[test] + fn test_roundtrip_gwc() { + use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; + use crate::poly::kzg::multiopen::{ProverGWC, VerifierGWC}; + use crate::poly::kzg::strategy::AccumulatorStrategy; + use halo2curves::bn256::{Bn256, G1Affine}; + use halo2curves::pairing::Engine; + + const K: u32 = 4; + + let params = ParamsKZG::::new(K); + + let proof = + create_proof::<_, ProverGWC<_>, _, Blake2bWrite<_, _, Challenge255<_>>>(¶ms); + + let verifier_params = params.verifier_params(); + + verify::<_, VerifierGWC<_>, _, Blake2bRead<_, _, Challenge255<_>>, AccumulatorStrategy<_>>( + verifier_params, + &proof[..], + false, + ); + + verify::< + KZGCommitmentScheme, + VerifierGWC<_>, + _, + Blake2bRead<_, _, Challenge255<_>>, + AccumulatorStrategy<_>, + >(verifier_params, &proof[..], true); + } + + #[test] + fn test_roundtrip_shplonk() { + use crate::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; + use crate::poly::kzg::multiopen::{ProverSHPLONK, VerifierSHPLONK}; + use crate::poly::kzg::strategy::AccumulatorStrategy; + use halo2curves::bn256::{Bn256, G1Affine}; + use halo2curves::pairing::Engine; + + const K: u32 = 4; + + let params = ParamsKZG::::new(K); + + let proof = create_proof::< + KZGCommitmentScheme, + ProverSHPLONK<_>, + _, + Blake2bWrite<_, _, Challenge255<_>>, + >(¶ms); + + let verifier_params = params.verifier_params(); + + verify::< + KZGCommitmentScheme, + VerifierSHPLONK<_>, + _, + Blake2bRead<_, _, Challenge255<_>>, + AccumulatorStrategy<_>, + >(verifier_params, &proof[..], false); + + verify::< + KZGCommitmentScheme, + VerifierSHPLONK<_>, + _, + Blake2bRead<_, _, Challenge255<_>>, + AccumulatorStrategy<_>, + >(verifier_params, &proof[..], true); + } + + fn verify< + 'a, + 'params, + Scheme: CommitmentScheme, + V: Verifier<'params, Scheme>, + E: EncodedChallenge, + T: TranscriptReadBuffer<&'a [u8], Scheme::Curve, E>, + Strategy: VerificationStrategy<'params, Scheme, V, Output = Strategy>, + >( + params: &'params Scheme::ParamsVerifier, + proof: &'a [u8], + should_fail: bool, + ) { + let verifier = V::new(params); + + let mut transcript = T::init(proof); + + let a = transcript.read_point().unwrap(); + let b = transcript.read_point().unwrap(); + let c = transcript.read_point().unwrap(); + + let x = transcript.squeeze_challenge(); + let y = transcript.squeeze_challenge(); + + let avx = transcript.read_scalar().unwrap(); + let bvx = transcript.read_scalar().unwrap(); + let cvy = transcript.read_scalar().unwrap(); + + let valid_queries = std::iter::empty() + .chain(Some(VerifierQuery::new_commitment(&a, x.get_scalar(), avx))) + .chain(Some(VerifierQuery::new_commitment(&b, x.get_scalar(), bvx))) + .chain(Some(VerifierQuery::new_commitment(&c, y.get_scalar(), cvy))); + + let invalid_queries = std::iter::empty() + .chain(Some(VerifierQuery::new_commitment(&a, x.get_scalar(), avx))) + .chain(Some(VerifierQuery::new_commitment(&b, x.get_scalar(), avx))) + .chain(Some(VerifierQuery::new_commitment(&c, y.get_scalar(), cvy))); + + let queries = if should_fail { + invalid_queries.clone() + } else { + valid_queries.clone() + }; + + { + let strategy = Strategy::new(params); + let strategy = strategy + .process(|msm_accumulator| { + verifier + .verify_proof(&mut transcript, queries.clone(), msm_accumulator) + .map_err(|_| Error::Opening) + }) + .unwrap(); + + assert_eq!(strategy.finalize(), !should_fail); + } + } + + fn create_proof< + 'params, + Scheme: CommitmentScheme, + P: Prover<'params, Scheme>, + E: EncodedChallenge, + T: TranscriptWriterBuffer, Scheme::Curve, E>, + >( + params: &'params Scheme::ParamsProver, + ) -> Vec { + 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, + ); + } + + let mut bx = domain.empty_coeff(); + for (i, a) in bx.iter_mut().enumerate() { + *a = <::Curve as CurveAffine>::ScalarExt::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, + ); + } + + let mut transcript = T::init(vec![]); + + let blind = Blind::new(&mut OsRng); + let a = params.commit(&ax, blind).to_affine(); + let b = params.commit(&bx, blind).to_affine(); + let c = params.commit(&cx, blind).to_affine(); + + transcript.write_point(a).unwrap(); + transcript.write_point(b).unwrap(); + transcript.write_point(c).unwrap(); + + let x = transcript.squeeze_challenge(); + let y = transcript.squeeze_challenge(); + + let avx = eval_polynomial(&ax, x.get_scalar()); + let bvx = eval_polynomial(&bx, x.get_scalar()); + let cvy = eval_polynomial(&cx, y.get_scalar()); + + transcript.write_scalar(avx).unwrap(); + transcript.write_scalar(bvx).unwrap(); + transcript.write_scalar(cvy).unwrap(); + + let queries = [ + ProverQuery { + point: x.get_scalar(), + poly: &ax, + blind, + }, + ProverQuery { + point: x.get_scalar(), + poly: &bx, + blind, + }, + ProverQuery { + point: y.get_scalar(), + poly: &cx, + blind, + }, + ] + .to_vec(); + + let prover = P::new(params); + prover + .create_proof(&mut OsRng, &mut transcript, queries) + .unwrap(); + + transcript.finalize() + } +} diff --git a/halo2_proofs/src/poly/query.rs b/halo2_proofs/src/poly/query.rs new file mode 100644 index 0000000000..f13cc25a89 --- /dev/null +++ b/halo2_proofs/src/poly/query.rs @@ -0,0 +1,137 @@ +use std::{fmt::Debug, ops::Deref}; + +use super::commitment::{Blind, CommitmentScheme, Params, MSM}; +use crate::{ + arithmetic::eval_polynomial, + poly::{commitment, Coeff, Polynomial}, +}; +use ff::Field; +use halo2curves::CurveAffine; + +pub trait Query: Sized + Clone { + type Commitment: PartialEq + Copy; + type Eval: Clone + Default + Debug; + + fn get_point(&self) -> F; + fn get_eval(&self) -> Self::Eval; + fn get_commitment(&self) -> Self::Commitment; +} + +/// A polynomial query at a point +#[derive(Debug, Clone)] +pub struct ProverQuery<'com, C: CurveAffine> { + /// point at which polynomial is queried + pub(crate) point: C::Scalar, + /// coefficients of polynomial + pub(crate) poly: &'com Polynomial, + /// blinding factor of polynomial + pub(crate) blind: Blind, +} + +#[doc(hidden)] +#[derive(Copy, Clone)] +pub struct PolynomialPointer<'com, C: CurveAffine> { + pub(crate) poly: &'com Polynomial, + pub(crate) blind: Blind, +} + +impl<'com, C: CurveAffine> PartialEq for PolynomialPointer<'com, C> { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self.poly, other.poly) + } +} + +impl<'com, C: CurveAffine> Query for ProverQuery<'com, C> { + type Commitment = PolynomialPointer<'com, C>; + type Eval = C::Scalar; + + fn get_point(&self) -> C::Scalar { + self.point + } + fn get_eval(&self) -> Self::Eval { + eval_polynomial(&self.poly[..], self.get_point()) + } + fn get_commitment(&self) -> Self::Commitment { + PolynomialPointer { + poly: self.poly, + blind: self.blind, + } + } +} + +impl<'com, C: CurveAffine, M: MSM> VerifierQuery<'com, C, M> { + /// Create a new verifier query based on a commitment + pub fn new_commitment(commitment: &'com C, point: C::Scalar, eval: C::Scalar) -> Self { + VerifierQuery { + point, + eval, + commitment: CommitmentReference::Commitment(commitment), + } + } + + /// Create a new verifier query based on a linear combination of commitments + pub fn new_msm(msm: &'com M, point: C::Scalar, eval: C::Scalar) -> VerifierQuery<'com, C, M> { + VerifierQuery { + point, + eval, + commitment: CommitmentReference::MSM(msm), + } + } +} + +/// A polynomial query at a point +#[derive(Debug)] +pub struct VerifierQuery<'com, C: CurveAffine, M: MSM> { + /// point at which polynomial is queried + pub(crate) point: C::Scalar, + /// commitment to polynomial + pub(crate) commitment: CommitmentReference<'com, C, M>, + /// evaluation of polynomial at query point + pub(crate) eval: C::Scalar, +} + +impl<'com, C: CurveAffine, M: MSM> Clone for VerifierQuery<'com, C, M> { + fn clone(&self) -> Self { + Self { + point: self.point, + commitment: self.commitment, + eval: self.eval, + } + } +} + +#[derive(Clone, Debug)] +pub enum CommitmentReference<'r, C: CurveAffine, M: MSM> { + Commitment(&'r C), + MSM(&'r M), +} + +impl<'r, C: CurveAffine, M: MSM> Copy for CommitmentReference<'r, C, M> {} + +impl<'r, C: CurveAffine, M: MSM> PartialEq for CommitmentReference<'r, C, M> { + #![allow(clippy::vtable_address_comparisons)] + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (&CommitmentReference::Commitment(a), &CommitmentReference::Commitment(b)) => { + std::ptr::eq(a, b) + } + (&CommitmentReference::MSM(a), &CommitmentReference::MSM(b)) => std::ptr::eq(a, b), + _ => false, + } + } +} + +impl<'com, C: CurveAffine, M: MSM> Query for VerifierQuery<'com, C, M> { + type Eval = C::Scalar; + type Commitment = CommitmentReference<'com, C, M>; + + fn get_point(&self) -> C::Scalar { + self.point + } + fn get_eval(&self) -> C::Scalar { + self.eval + } + fn get_commitment(&self) -> Self::Commitment { + self.commitment + } +} diff --git a/halo2_proofs/src/poly/strategy.rs b/halo2_proofs/src/poly/strategy.rs new file mode 100644 index 0000000000..36480d372f --- /dev/null +++ b/halo2_proofs/src/poly/strategy.rs @@ -0,0 +1,37 @@ +use halo2curves::CurveAffine; +use rand_core::RngCore; + +use super::commitment::{CommitmentScheme, Verifier, MSM}; +use crate::{ + plonk::Error, + transcript::{EncodedChallenge, TranscriptRead}, +}; + +/// Guards is unfinished verification result. Implement this to construct various +/// verification strategies such as aggregation and recursion. +pub trait Guard { + /// Multi scalar engine which is not evaluated yet. + type MSMAccumulator; +} + +/// Trait representing a strategy for verifying Halo 2 proofs. +pub trait VerificationStrategy<'params, Scheme: CommitmentScheme, V: Verifier<'params, Scheme>> { + /// The output type of this verification strategy after processing a proof. + type Output; + + /// Creates new verification strategy instance + fn new(params: &'params Scheme::ParamsVerifier) -> Self; + + /// Obtains an MSM from the verifier strategy and yields back the strategy's + /// output. + fn process( + self, + f: impl FnOnce(V::MSMAccumulator) -> Result, + ) -> Result; + + /// Finalizes the batch and checks its validity. + /// + /// Returns `false` if *some* proof was invalid. If the caller needs to identify + /// specific failing proofs, it must re-process the proofs separately. + fn finalize(self) -> bool; +} diff --git a/halo2_proofs/src/transcript.rs b/halo2_proofs/src/transcript.rs index 0570dd599a..5262f3c1c7 100644 --- a/halo2_proofs/src/transcript.rs +++ b/halo2_proofs/src/transcript.rs @@ -5,7 +5,7 @@ use blake2b_simd::{Params as Blake2bParams, State as Blake2bState}; use group::ff::PrimeField; use std::convert::TryInto; -use crate::arithmetic::{Coordinates, CurveAffine, FieldExt}; +use halo2curves::{Coordinates, CurveAffine, FieldExt}; use std::io::{self, Read, Write}; use std::marker::PhantomData; @@ -61,6 +61,25 @@ pub trait TranscriptWrite>: Transcript io::Result<()>; } +/// Initializes transcript at verifier side. +pub trait TranscriptReadBuffer>: + TranscriptRead +{ + /// Initialize a transcript given an input buffer. + fn init(reader: R) -> Self; +} + +/// Manages begining and finising of transcript pipeline. +pub trait TranscriptWriterBuffer>: + TranscriptWrite +{ + /// Initialize a transcript given an output buffer. + fn init(writer: W) -> Self; + + /// Conclude the interaction and return the output buffer (writer). + fn finalize(self) -> W; +} + /// We will replace BLAKE2b with an algebraic hash function in a later version. #[derive(Debug, Clone)] pub struct Blake2bRead> { @@ -69,9 +88,11 @@ pub struct Blake2bRead> { _marker: PhantomData<(C, E)>, } -impl> Blake2bRead { +impl TranscriptReadBuffer> + for Blake2bRead> +{ /// Initialize a transcript given an input buffer. - pub fn init(reader: R) -> Self { + fn init(reader: R) -> Self { Blake2bRead { state: Blake2bParams::new() .hash_length(64) @@ -152,9 +173,10 @@ pub struct Blake2bWrite> { _marker: PhantomData<(C, E)>, } -impl> Blake2bWrite { - /// Initialize a transcript given an output buffer. - pub fn init(writer: W) -> Self { +impl TranscriptWriterBuffer> + for Blake2bWrite> +{ + fn init(writer: W) -> Self { Blake2bWrite { state: Blake2bParams::new() .hash_length(64) @@ -165,8 +187,7 @@ impl> Blake2bWrite { } } - /// Conclude the interaction and return the output buffer (writer). - pub fn finalize(self) -> W { + fn finalize(self) -> W { // TODO: handle outstanding scalars? see issue #138 self.writer } diff --git a/halo2_proofs/tests/plonk_api.rs b/halo2_proofs/tests/plonk_api.rs index 7d397c8119..af63b5fb30 100644 --- a/halo2_proofs/tests/plonk_api.rs +++ b/halo2_proofs/tests/plonk_api.rs @@ -2,18 +2,22 @@ #![allow(clippy::op_ref)] use assert_matches::assert_matches; -use halo2_proofs::arithmetic::{CurveAffine, FieldExt}; +use halo2_proofs::arithmetic::{Field, FieldExt}; use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; use halo2_proofs::dev::MockProver; -use halo2_proofs::pasta::{Eq, EqAffine, Fp}; use halo2_proofs::plonk::{ - create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Assigned, BatchVerifier, Circuit, - Column, ConstraintSystem, Error, Fixed, SingleVerifier, TableColumn, VerificationStrategy, + create_proof as create_plonk_proof, keygen_pk, keygen_vk, verify_proof as verify_plonk_proof, + Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, ProvingKey, TableColumn, + VerifyingKey, }; -use halo2_proofs::poly::commitment::{Guard, MSM}; -use halo2_proofs::poly::{commitment::Params, Rotation}; -use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge}; -use rand_core::OsRng; +use halo2_proofs::poly::commitment::{CommitmentScheme, ParamsProver, Prover, Verifier}; +use halo2_proofs::poly::Rotation; +use halo2_proofs::poly::VerificationStrategy; +use halo2_proofs::transcript::{ + Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge, TranscriptReadBuffer, + TranscriptWriterBuffer, +}; +use rand_core::{OsRng, RngCore}; use std::marker::PhantomData; #[test] @@ -24,9 +28,6 @@ fn plonk_api() { #[derive(Copy, Clone, Debug)] pub struct Variable(Column, usize); - // Initialize the polynomial commitment parameters - let params: Params = Params::new(K); - #[derive(Clone)] struct PlonkConfig { a: Column, @@ -312,7 +313,7 @@ fn plonk_api() { * ] */ - meta.lookup(|meta| { + meta.lookup("lookup", |meta| { let a_ = meta.query_any(a, Rotation::cur()); vec![(a_, sl)] }); @@ -398,199 +399,231 @@ fn plonk_api() { } } - let a = Fp::from(2834758237) * Fp::ZETA; - let instance = Fp::one() + Fp::one(); - let lookup_table = vec![instance, a, a, Fp::zero()]; - - let empty_circuit: MyCircuit = MyCircuit { - a: Value::unknown(), - lookup_table: lookup_table.clone(), - }; - - let circuit: MyCircuit = MyCircuit { - a: Value::known(a), - lookup_table, - }; - - // Check that we get an error if we try to initialize the proving key with a value of - // k that is too small for the minimum required number of rows. - let much_too_small_params: Params = Params::new(1); - assert_matches!( - keygen_vk(&much_too_small_params, &empty_circuit), - Err(Error::NotEnoughRowsAvailable { - current_k, - }) if current_k == 1 - ); - - // Check that we get an error if we try to initialize the proving key with a value of - // k that is too small for the number of rows the circuit uses. - let slightly_too_small_params: Params = Params::new(K - 1); - assert_matches!( - keygen_vk(&slightly_too_small_params, &empty_circuit), - Err(Error::NotEnoughRowsAvailable { - current_k, - }) if current_k == K - 1 - ); - - // Initialize the proving key - let vk = keygen_vk(¶ms, &empty_circuit).expect("keygen_vk should not fail"); - let pk = keygen_pk(¶ms, vk, &empty_circuit).expect("keygen_pk should not fail"); - - let pubinputs = vec![instance]; - - // Check this circuit is satisfied. - let prover = match MockProver::run(K, &circuit, vec![pubinputs.clone()]) { - Ok(prover) => prover, - Err(e) => panic!("{:?}", e), - }; - assert_eq!(prover.verify(), Ok(())); - - if std::env::var_os("HALO2_PLONK_TEST_GENERATE_NEW_PROOF").is_some() { - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - // Create a proof - create_proof( - ¶ms, - &pk, + macro_rules! common { + ($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(), + ]; + (a, instance, lookup_table) + }}; + } + + macro_rules! bad_keys { + ($scheme:ident) => {{ + let (_, _, lookup_table) = common!($scheme); + let empty_circuit: MyCircuit<<$scheme as CommitmentScheme>::Scalar> = MyCircuit { + a: Value::unknown(), + lookup_table: lookup_table.clone(), + }; + + // Check that we get an error if we try to initialize the proving key with a value of + // k that is too small for the minimum required number of rows. + let much_too_small_params= <$scheme as CommitmentScheme>::ParamsProver::new(1); + assert_matches!( + keygen_vk(&much_too_small_params, &empty_circuit), + Err(Error::NotEnoughRowsAvailable { + current_k, + }) if current_k == 1 + ); + + // Check that we get an error if we try to initialize the proving key with a value of + // k that is too small for the number of rows the circuit uses. + let slightly_too_small_params = <$scheme as CommitmentScheme>::ParamsProver::new(K-1); + assert_matches!( + keygen_vk(&slightly_too_small_params, &empty_circuit), + Err(Error::NotEnoughRowsAvailable { + current_k, + }) if current_k == K - 1 + ); + }}; + } + + fn keygen( + params: &Scheme::ParamsProver, + ) -> ProvingKey { + let (_, _, lookup_table) = common!(Scheme); + let empty_circuit: MyCircuit = MyCircuit { + a: Value::unknown(), + lookup_table, + }; + + // Initialize the proving key + let vk = keygen_vk(params, &empty_circuit).expect("keygen_vk should not fail"); + + keygen_pk(params, vk, &empty_circuit).expect("keygen_pk should not fail") + } + + fn create_proof< + 'params, + Scheme: CommitmentScheme, + P: Prover<'params, Scheme>, + E: EncodedChallenge, + R: RngCore, + T: TranscriptWriterBuffer, Scheme::Curve, E>, + >( + rng: R, + params: &'params Scheme::ParamsProver, + pk: &ProvingKey, + ) -> Vec { + let (a, instance, lookup_table) = common!(Scheme); + + let circuit: MyCircuit = MyCircuit { + a: Value::known(a), + lookup_table, + }; + + let mut transcript = T::init(vec![]); + + create_plonk_proof::( + params, + pk, &[circuit.clone(), circuit.clone()], &[&[&[instance]], &[&[instance]]], - OsRng, + rng, &mut transcript, ) .expect("proof generation should not fail"); - let proof: Vec = transcript.finalize(); - std::fs::write("plonk_api_proof.bin", &proof[..]) - .expect("should succeed to write new proof"); + // Check this circuit is satisfied. + let prover = match MockProver::run(K, &circuit, vec![vec![instance]]) { + Ok(prover) => prover, + Err(e) => panic!("{:?}", e), + }; + assert_eq!(prover.verify(), Ok(())); + + transcript.finalize() } - { - // Check that a hardcoded proof is satisfied - let proof = include_bytes!("plonk_api_proof.bin"); - let strategy = SingleVerifier::new(¶ms); - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - assert!(verify_proof( - ¶ms, - pk.get_vk(), + fn verify_proof< + 'a, + 'params, + Scheme: CommitmentScheme, + V: Verifier<'params, Scheme>, + E: EncodedChallenge, + T: TranscriptReadBuffer<&'a [u8], Scheme::Curve, E>, + Strategy: VerificationStrategy<'params, Scheme, V, Output = Strategy>, + >( + params_verifier: &'params Scheme::ParamsVerifier, + vk: &VerifyingKey, + proof: &'a [u8], + ) { + let (_, instance, _) = common!(Scheme); + let pubinputs = vec![instance]; + + let mut transcript = T::init(proof); + + let strategy = Strategy::new(params_verifier); + let strategy = verify_plonk_proof( + params_verifier, + vk, strategy, &[&[&pubinputs[..]], &[&pubinputs[..]]], &mut transcript, ) - .is_ok()); + .unwrap(); + + assert!(strategy.finalize()); } - for _ in 0..10 { - let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); - // Create a proof - create_proof( - ¶ms, - &pk, - &[circuit.clone(), circuit.clone()], - &[&[&[instance]], &[&[instance]]], - OsRng, - &mut transcript, - ) - .expect("proof generation should not fail"); - let proof: Vec = transcript.finalize(); - assert_eq!( - proof.len(), - halo2_proofs::dev::CircuitCost::>::measure(K as usize, &circuit) - .proof_size(2) - .into(), - ); + fn test_plonk_api_gwc() { + use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; + use halo2_proofs::poly::kzg::multiopen::{ProverGWC, VerifierGWC}; + use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; + use halo2curves::bn256::Bn256; - // Test single-verifier strategy. - { - let strategy = SingleVerifier::new(¶ms); - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - assert!(verify_proof( - ¶ms, - pk.get_vk(), - strategy, - &[&[&pubinputs[..]], &[&pubinputs[..]]], - &mut transcript, - ) - .is_ok()); - } + type Scheme = KZGCommitmentScheme; + bad_keys!(Scheme); - // - // Test accumulation-based strategy. - // + let params = ParamsKZG::::new(K); + let rng = OsRng; - struct AccumulationVerifier<'params, C: CurveAffine> { - msm: MSM<'params, C>, - } + let pk = keygen::>(¶ms); - impl<'params, C: CurveAffine> AccumulationVerifier<'params, C> { - fn new(params: &'params Params) -> Self { - AccumulationVerifier { - msm: MSM::new(params), - } - } - } + let proof = create_proof::<_, ProverGWC<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( + rng, ¶ms, &pk, + ); - impl<'params, C: CurveAffine> VerificationStrategy<'params, C> - for AccumulationVerifier<'params, C> - { - type Output = (); - - fn process>( - self, - f: impl FnOnce(MSM<'params, C>) -> Result, Error>, - ) -> Result { - let guard = f(self.msm)?; - let g = guard.compute_g(); - let (msm, _) = guard.use_g(g); - if msm.eval() { - Ok(()) - } else { - Err(Error::ConstraintSystemFailure) - } - } - } + let verifier_params = params.verifier_params(); - { - let strategy = AccumulationVerifier::new(¶ms); - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - assert!(verify_proof( - ¶ms, - pk.get_vk(), - strategy, - &[&[&pubinputs[..]], &[&pubinputs[..]]], - &mut transcript, - ) - .is_ok()); - } + verify_proof::< + _, + VerifierGWC<_>, + _, + Blake2bRead<_, _, Challenge255<_>>, + AccumulatorStrategy<_>, + >(verifier_params, pk.get_vk(), &proof[..]); + } - // - // Test batch-verifier strategy. - // + fn test_plonk_api_shplonk() { + use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG}; + use halo2_proofs::poly::kzg::multiopen::{ProverSHPLONK, VerifierSHPLONK}; + use halo2_proofs::poly::kzg::strategy::AccumulatorStrategy; + use halo2curves::bn256::Bn256; - { - let mut batch = BatchVerifier::new(); + type Scheme = KZGCommitmentScheme; + bad_keys!(Scheme); - // First proof. - batch.add_proof( - vec![vec![pubinputs.clone()], vec![pubinputs.clone()]], - proof.clone(), - ); + let params = ParamsKZG::::new(K); + let rng = OsRng; - // "Second" proof (just the first proof again). - batch.add_proof( - vec![vec![pubinputs.clone()], vec![pubinputs.clone()]], - proof, - ); + let pk = keygen::>(¶ms); - // Check the batch. - assert!(batch.finalize(¶ms, pk.get_vk())); - } + let proof = create_proof::<_, ProverSHPLONK<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( + rng, ¶ms, &pk, + ); + + let verifier_params = params.verifier_params(); + + verify_proof::< + _, + VerifierSHPLONK<_>, + _, + Blake2bRead<_, _, Challenge255<_>>, + AccumulatorStrategy<_>, + >(verifier_params, pk.get_vk(), &proof[..]); } - // Check that the verification key has not changed unexpectedly - { - //panic!("{:#?}", pk.get_vk().pinned()); - assert_eq!( - format!("{:#?}", pk.get_vk().pinned()), - r#####"PinnedVerificationKey { + fn test_plonk_api_ipa() { + use halo2_proofs::poly::ipa::commitment::{IPACommitmentScheme, ParamsIPA}; + use halo2_proofs::poly::ipa::multiopen::{ProverIPA, VerifierIPA}; + use halo2_proofs::poly::ipa::strategy::AccumulatorStrategy; + use halo2curves::pasta::EqAffine; + + type Scheme = IPACommitmentScheme; + bad_keys!(Scheme); + + let params = ParamsIPA::::new(K); + let rng = OsRng; + + let pk = keygen::>(¶ms); + + let proof = create_proof::<_, ProverIPA<_>, _, _, Blake2bWrite<_, _, Challenge255<_>>>( + rng, ¶ms, &pk, + ); + + let verifier_params = params.verifier_params(); + + verify_proof::< + _, + VerifierIPA<_>, + _, + Blake2bRead<_, _, Challenge255<_>>, + AccumulatorStrategy<_>, + >(verifier_params, pk.get_vk(), &proof[..]); + + // Check that the verification key has not changed unexpectedly + { + //panic!("{:#?}", pk.get_vk().pinned()); + assert_eq!( + format!("{:#?}", pk.get_vk().pinned()), + r#####"PinnedVerificationKey { base_modulus: "0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001", scalar_modulus: "0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001", domain: PinnedEvaluationDomain { @@ -984,6 +1017,11 @@ fn plonk_api() { ], }, }"##### - ); + ); + } } + + test_plonk_api_ipa(); + test_plonk_api_gwc(); + test_plonk_api_shplonk(); } diff --git a/halo2_proofs/tests/plonk_api_proof.bin b/halo2_proofs/tests/plonk_api_proof.bin deleted file mode 100644 index ecc92f0d41..0000000000 Binary files a/halo2_proofs/tests/plonk_api_proof.bin and /dev/null differ diff --git a/rust-toolchain b/rust-toolchain index 43c989b553..cc99c5daee 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.56.1 +nightly-2022-07-26