From 7011e8ce0c2f7e79ab9629aa528cfb6837cdeafe Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Thu, 21 Sep 2023 14:23:27 -0700 Subject: [PATCH] [Community Edition] Prealpha release 0.1.6 (#35) * chore: update dependencies * Minor: merge v0.1.1 to develop (#21) Just cargo fixes * feat: remove use of env vars for circuit configuration (#22) * feat: remove use of env vars for circuit configuration This is a companion to https://github.com/axiom-crypto/halo2-lib/pull/92 * chore: remove rustfmt CI check PSE upstream uses different rustfmt configuration than us, so some files disagree in formatting * chore: fix dependencies * Feat/read pk buffer capacity (#24) * feat: change default `read_pk` buffer capacity to 1MB * feat: add bench for read_pk * [Update] use ff v0.13 (#28) * feat(snark-verifier): update to ff v0.13 * feat(snark-verifier): update examples * feat(snark-verifier-sdk): update to ff v0.13 * fix: conversion from BaseConfigParams to AggregationConfigParams * chore: pin poseidon rev * refactor(sdk): add `AggregationCtxBuilder` for aggregation Contains the populated builder after aggregating, without creating the `AggregationCircuit`. Doesn't need config parameters and break points. * chore: update cargo * [Feat] Universal verifier circuit (#26) * feat: add example with different vkey as private witness Same aggregation circuit, verifies different snarks with different vkeys (same standard plonk gate, but different selectors / copy constraints) * fix: save break points when generating agg circuit for first time (#23) * fix: save break points when generating agg circuit for first time * chore: add circuit files to gitignore * feat: halo2-lib universal verifier example * chore: cargo fix * feat: allow circuit size (number of rows) to be loaded as witness * chore: clippy fix * fix(n_as_witness): computation of shifts depends on `omega` `omega` which changes when `k` changes, so all shift computations need to be done as witness. Current implementation is likely not the most optimal. Instead of storing `shift` as `omega^i`, we store just `Rotation(i)`. We de-duplicate when possible using `BTreeMap` of `Rotation`. Note you must use `Rotation` instead of `F` for `BTreeMap` because the ordering of `omega^i` may change depending on `omega`. * fix: temp remove pow_var * add universal verifier range check test * chore: do not serialize domain_as_witness if none * Revert "fix: temp remove pow_var" This reverts commit 69f648e12a850a1d16235fc33acebfb5f8b2f290. * fix: halo2_lib example * test: halo2_lib with variable lookup table passes * Bump version to 0.1.3 --------- Co-authored-by: Roshan * chore: derive Default for VerifierUniversality * feat: upgrade `revm` to support lastest hardfork (#40) * Update: use `halo2-lib` v0.4.0 (#29) * feat: update snark-verifier * update: use `halo2-lib` v0.4.0 * feat: load `k` as witness and compute `n = 2^k` and `omega` from `k` (#30) * feat: load `k` as witness and compute `n = 2^k` and `omega` from `k` Removes need to make `omega` a public output in universal verifier. * fix: bit_length * Move `OptimizedPoseidonSpec` to `halo2-base` (#31) * chore: move `OptimizedPoseidonSpec` to `halo2-base` * Bump version to 0.1.5 and remove poseidon-rs dep * chore: util::hash available without loader_halo2 feature * chore: nit * [feat] change yul code into Solidity assembly (#32) feat: change yul code into Solidity assembly Just changes to wrapping yul in solidity assembly block * chore: try pragma solidity 0.8.20 with CI * chore: make `transcript_initial_state` public So we can read transcript initial state from `VerifyingKey` * test: edit range_check example to trigger selector compression * [feat] add `aggregate_snarks` function (#34) * feat: add `aggregate_snarks` function - Previously you could only create a new `builder` pre-populated with the witnesses for snark aggregation. - This is a bad design pattern if you want to make a circuit that aggregates and also does other stuff. - This function will use whatever `SinglePhaseCoreManager` and `RangeChip` you provide to prove the snark aggregation. * chore: add comment * chore: fix comment --------- Co-authored-by: Roshan Co-authored-by: Han --- .github/workflows/ci.yaml | 8 +- .gitignore | 4 + Cargo.toml | 1 + rust-toolchain | 2 +- snark-verifier-sdk/Cargo.toml | 20 +- snark-verifier-sdk/benches/read_pk.rs | 223 +++ snark-verifier-sdk/benches/standard_plonk.rs | 43 +- .../examples/StandardPlonkVerifier.sol | 1437 +++++++++++++++++ snark-verifier-sdk/examples/k_as_witness.rs | 187 +++ snark-verifier-sdk/examples/range_check.rs | 91 ++ snark-verifier-sdk/examples/standard_plonk.rs | 216 +++ .../examples/vkey_as_witness.rs | 183 +++ snark-verifier-sdk/src/evm.rs | 23 +- snark-verifier-sdk/src/halo2.rs | 36 +- snark-verifier-sdk/src/halo2/aggregation.rs | 513 +++--- snark-verifier-sdk/src/lib.rs | 25 +- snark-verifier/Cargo.toml | 20 +- .../examples/evm-verifier-with-accumulator.rs | 220 +-- snark-verifier/examples/evm-verifier.rs | 18 +- snark-verifier/examples/recursion.rs | 223 ++- snark-verifier/src/loader.rs | 30 +- snark-verifier/src/loader/evm.rs | 10 +- snark-verifier/src/loader/evm/code.rs | 68 +- snark-verifier/src/loader/evm/loader.rs | 78 +- snark-verifier/src/loader/evm/test.rs | 48 - snark-verifier/src/loader/evm/test/tui.rs | 943 ----------- snark-verifier/src/loader/evm/util.rs | 18 +- .../src/loader/evm/util/executor.rs | 912 +---------- snark-verifier/src/loader/halo2/loader.rs | 26 +- snark-verifier/src/loader/halo2/shim.rs | 87 +- snark-verifier/src/loader/native.rs | 7 +- snark-verifier/src/pcs.rs | 24 +- snark-verifier/src/pcs/ipa.rs | 2 +- snark-verifier/src/pcs/ipa/accumulation.rs | 4 +- snark-verifier/src/pcs/ipa/decider.rs | 2 +- snark-verifier/src/pcs/ipa/multiopen/bgh19.rs | 94 +- snark-verifier/src/pcs/kzg.rs | 2 +- snark-verifier/src/pcs/kzg/accumulation.rs | 4 +- snark-verifier/src/pcs/kzg/accumulator.rs | 13 +- snark-verifier/src/pcs/kzg/decider.rs | 27 +- .../src/pcs/kzg/multiopen/bdfg21.rs | 191 +-- snark-verifier/src/pcs/kzg/multiopen/gwc19.rs | 52 +- snark-verifier/src/system/halo2.rs | 19 +- snark-verifier/src/system/halo2/transcript.rs | 23 +- .../src/system/halo2/transcript/evm.rs | 4 +- .../src/system/halo2/transcript/halo2.rs | 20 +- snark-verifier/src/util/arithmetic.rs | 29 +- snark-verifier/src/util/hash.rs | 4 +- snark-verifier/src/util/hash/poseidon.rs | 369 +---- .../src/util/hash/poseidon/tests.rs | 6 +- snark-verifier/src/util/msm.rs | 2 +- snark-verifier/src/util/poly.rs | 22 +- snark-verifier/src/verifier/plonk.rs | 17 +- snark-verifier/src/verifier/plonk/proof.rs | 43 +- snark-verifier/src/verifier/plonk/protocol.rs | 106 +- 55 files changed, 3598 insertions(+), 3201 deletions(-) create mode 100644 snark-verifier-sdk/benches/read_pk.rs create mode 100644 snark-verifier-sdk/examples/StandardPlonkVerifier.sol create mode 100644 snark-verifier-sdk/examples/k_as_witness.rs create mode 100644 snark-verifier-sdk/examples/range_check.rs create mode 100644 snark-verifier-sdk/examples/standard_plonk.rs create mode 100644 snark-verifier-sdk/examples/vkey_as_witness.rs delete mode 100644 snark-verifier/src/loader/evm/test.rs delete mode 100644 snark-verifier/src/loader/evm/test/tui.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 634eed77..733bfc9c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,11 +24,14 @@ jobs: cache-on-failure: true - name: Install solc - run: (hash svm 2>/dev/null || cargo install svm-rs) && svm install 0.8.17 && solc --version + run: (hash svm 2>/dev/null || cargo install --version 0.2.23 svm-rs) && svm install 0.8.20 && solc --version - name: Run test run: cargo test --all -- --nocapture + - name: Run example + working-directory: "snark-verifier-sdk" + run: cargo run --example standard_plonk lint: name: Lint @@ -47,8 +50,5 @@ jobs: with: cache-on-failure: true - - name: Run fmt - run: cargo fmt --all -- --check - - name: Run clippy run: cargo clippy --all --all-targets -- -D warnings diff --git a/.gitignore b/.gitignore index 829691c6..ec2971fb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ /target testdata +Cargo.lock +params +agg.pk +break_points.json \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 552212b3..53c98f1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "snark-verifier", "snark-verifier-sdk", ] +resolver = "2" [profile.dev] opt-level = 3 diff --git a/rust-toolchain b/rust-toolchain index 51ab4759..6f6d7b39 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2022-10-28 \ No newline at end of file +nightly-2023-08-12 diff --git a/snark-verifier-sdk/Cargo.toml b/snark-verifier-sdk/Cargo.toml index e1237b69..dd900125 100644 --- a/snark-verifier-sdk/Cargo.toml +++ b/snark-verifier-sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "snark-verifier-sdk" -version = "0.1.1" +version = "0.1.6" edition = "2021" [dependencies] @@ -19,13 +19,10 @@ bincode = "1.3.3" ark-std = { version = "0.3.0", features = ["print-trace"], optional = true } halo2-base = { git = "https://github.com/axiom-crypto/halo2-lib.git", branch = "community-edition", default-features = false } snark-verifier = { path = "../snark-verifier", default-features = false } +getset = "0.1.2" # loader_evm -ethereum-types = { version = "=0.14.1", default-features = false, features = ["std"], optional = true } -# sha3 = { version = "0.10", optional = true } -# revm = { version = "2.3.1", optional = true } -# bytes = { version = "1.2", optional = true } -# rlp = { version = "0.5", default-features = false, features = ["std"], optional = true } +ethereum-types = { version = "0.14.1", default-features = false, features = ["std"], optional = true } # zkevm benchmarks zkevm-circuits = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits.git", rev = "f834e61", features = ["test"], optional = true } @@ -53,7 +50,7 @@ parallel = ["snark-verifier/parallel"] halo2-pse = ["snark-verifier/halo2-pse", "dep:serde_with"] halo2-axiom = ["snark-verifier/halo2-axiom"] -zkevm = ["dep:zkevm-circuits", "dep:bus-mapping", "dep:mock", "dep:eth-types"] +# zkevm = ["dep:zkevm-circuits", "dep:bus-mapping", "dep:mock", "dep:eth-types"] [[bench]] name = "standard_plonk" @@ -69,3 +66,12 @@ harness = false name = "zkevm_plus_state" required-features = ["loader_halo2", "loader_evm", "zkevm", "halo2-pse"] harness = false + +[[bench]] +name = "read_pk" +required-features = ["loader_halo2"] +harness = false + +[[example]] +name = "standard_plonk" +required-features = ["loader_halo2", "loader_evm"] diff --git a/snark-verifier-sdk/benches/read_pk.rs b/snark-verifier-sdk/benches/read_pk.rs new file mode 100644 index 00000000..4adc42fd --- /dev/null +++ b/snark-verifier-sdk/benches/read_pk.rs @@ -0,0 +1,223 @@ +use ark_std::{end_timer, start_timer}; +use criterion::Criterion; +use criterion::{criterion_group, criterion_main}; +use halo2_base::gates::circuit::CircuitBuilderStage; +use halo2_base::halo2_proofs; +use halo2_base::utils::fs::gen_srs; +use halo2_proofs::halo2curves as halo2_curves; +use halo2_proofs::{halo2curves::bn256::Bn256, poly::kzg::commitment::ParamsKZG}; +use pprof::criterion::{Output, PProfProfiler}; +use rand::rngs::OsRng; + +use snark_verifier_sdk::halo2::aggregation::{AggregationConfigParams, VerifierUniversality}; +use snark_verifier_sdk::{ + gen_pk, + halo2::{aggregation::AggregationCircuit, gen_snark_shplonk}, + Snark, +}; +use snark_verifier_sdk::{read_pk_with_capacity, SHPLONK}; +use std::path::Path; + +mod application { + use super::halo2_curves::bn256::Fr; + use super::halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance}, + poly::Rotation, + }; + use rand::RngCore; + use snark_verifier_sdk::CircuitExt; + + #[derive(Clone, Copy)] + pub struct StandardPlonkConfig { + a: Column, + b: Column, + c: Column, + q_a: Column, + q_b: Column, + q_c: Column, + q_ab: Column, + constant: Column, + #[allow(dead_code)] + instance: Column, + } + + impl StandardPlonkConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [a, b, c] = [(); 3].map(|_| meta.advice_column()); + let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); + let instance = meta.instance_column(); + + [a, b, c].map(|column| meta.enable_equality(column)); + + meta.create_gate( + "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", + |meta| { + let [a, b, c] = + [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); + let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] + .map(|column| meta.query_fixed(column, Rotation::cur())); + let instance = meta.query_instance(instance, Rotation::cur()); + Some( + q_a * a.clone() + + q_b * b.clone() + + q_c * c + + q_ab * a * b + + constant + + instance, + ) + }, + ); + + StandardPlonkConfig { a, b, c, q_a, q_b, q_c, q_ab, constant, instance } + } + } + + #[derive(Clone, Default)] + pub struct StandardPlonk(Fr); + + impl StandardPlonk { + pub fn rand(mut rng: R) -> Self { + Self(Fr::from(rng.next_u32() as u64)) + } + } + + impl CircuitExt for StandardPlonk { + fn num_instance(&self) -> Vec { + vec![1] + } + + fn instances(&self) -> Vec> { + vec![vec![self.0]] + } + } + + impl Circuit for StandardPlonk { + type Config = StandardPlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + meta.set_minimum_degree(4); + StandardPlonkConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "", + |mut region| { + #[cfg(feature = "halo2-pse")] + { + region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; + region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fr::one()))?; + region.assign_advice( + || "", + config.a, + 1, + || Value::known(-Fr::from(5u64)), + )?; + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed( + || "", + column, + 1, + || Value::known(Fr::from(idx as u64)), + )?; + } + let a = + region.assign_advice(|| "", config.a, 2, || Value::known(Fr::one()))?; + a.copy_advice(|| "", &mut region, config.b, 3)?; + a.copy_advice(|| "", &mut region, config.c, 4)?; + } + #[cfg(feature = "halo2-axiom")] + { + region.assign_advice(config.a, 0, Value::known(self.0)); + region.assign_fixed(config.q_a, 0, -Fr::one()); + region.assign_advice(config.a, 1, Value::known(-Fr::from(5u64))); + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed(column, 1, Fr::from(idx as u64)); + } + + let a = region.assign_advice(config.a, 2, Value::known(Fr::one())); + a.copy_advice(&mut region, config.b, 3); + a.copy_advice(&mut region, config.c, 4); + } + + Ok(()) + }, + ) + } + } +} + +fn gen_application_snark(params: &ParamsKZG) -> Snark { + let circuit = application::StandardPlonk::rand(OsRng); + + let pk = gen_pk(params, &circuit, None); + gen_snark_shplonk(params, &pk, circuit, None::<&str>) +} + +fn bench(c: &mut Criterion) { + let path = "./configs/example_evm_accumulator.json"; + let params_app = gen_srs(8); + + let snarks = [(); 3].map(|_| gen_application_snark(¶ms_app)); + let agg_config = AggregationConfigParams::from_path(path); + let params = gen_srs(agg_config.degree); + + let agg_circuit = AggregationCircuit::new::( + CircuitBuilderStage::Keygen, + agg_config, + ¶ms, + snarks, + VerifierUniversality::None, + ); + + std::fs::remove_file("examples/agg.pk").ok(); + let start0 = start_timer!(|| "gen vk & pk"); + gen_pk(¶ms, &agg_circuit, Some(Path::new("examples/agg.pk"))); + end_timer!(start0); + + let mut group = c.benchmark_group("read-pk"); + group.sample_size(10); + group.bench_with_input("buffer 1mb capacity", &(1024 * 1024), |b, &c| { + b.iter(|| read_pk_with_capacity::(c, "examples/agg.pk", agg_config)) + }); + group.bench_with_input("buffer 10mb capacity", &(10 * 1024 * 1024), |b, &c| { + b.iter(|| read_pk_with_capacity::(c, "examples/agg.pk", agg_config)) + }); + group.bench_with_input("buffer 100mb capacity", &(100 * 1024 * 1024), |b, &c| { + b.iter(|| read_pk_with_capacity::(c, "examples/agg.pk", agg_config)) + }); + group.bench_with_input("buffer 1gb capacity", &(1024 * 1024 * 1024), |b, &c| { + b.iter(|| read_pk_with_capacity::(c, "examples/agg.pk", agg_config)) + }); + group.finish(); + std::fs::remove_file("examples/agg.pk").unwrap(); +} + +criterion_group! { + name = benches; + config = Criterion::default().with_profiler(PProfProfiler::new(10, Output::Flamegraph(None))); + targets = bench +} +criterion_main!(benches); diff --git a/snark-verifier-sdk/benches/standard_plonk.rs b/snark-verifier-sdk/benches/standard_plonk.rs index 70d600ea..9f566999 100644 --- a/snark-verifier-sdk/benches/standard_plonk.rs +++ b/snark-verifier-sdk/benches/standard_plonk.rs @@ -1,19 +1,18 @@ +use ark_std::{end_timer, start_timer}; use criterion::{criterion_group, criterion_main}; use criterion::{BenchmarkId, Criterion}; -use halo2_base::gates::builder::CircuitBuilderStage; -use halo2_base::utils::fs::gen_srs; -use pprof::criterion::{Output, PProfProfiler}; -use rand::rngs::OsRng; -use std::path::Path; -use ark_std::{end_timer, start_timer}; +use halo2_base::gates::circuit::CircuitBuilderStage; use halo2_base::halo2_proofs; +use halo2_base::utils::fs::gen_srs; use halo2_proofs::halo2curves as halo2_curves; use halo2_proofs::{ halo2curves::bn256::Bn256, poly::{commitment::Params, kzg::commitment::ParamsKZG}, }; +use pprof::criterion::{Output, PProfProfiler}; +use rand::rngs::OsRng; use snark_verifier_sdk::evm::{evm_verify, gen_evm_proof_shplonk, gen_evm_verifier_shplonk}; -use snark_verifier_sdk::halo2::aggregation::AggregationConfigParams; +use snark_verifier_sdk::halo2::aggregation::{AggregationConfigParams, VerifierUniversality}; use snark_verifier_sdk::{ gen_pk, halo2::{aggregation::AggregationCircuit, gen_proof_shplonk, gen_snark_shplonk}, @@ -175,8 +174,8 @@ mod application { fn gen_application_snark(params: &ParamsKZG) -> Snark { let circuit = application::StandardPlonk::rand(OsRng); - let pk = gen_pk(params, &circuit, Some(Path::new("app.pk"))); - gen_snark_shplonk(params, &pk, circuit, Some(Path::new("app.snark"))) + let pk = gen_pk(params, &circuit, None); + gen_snark_shplonk(params, &pk, circuit, None::<&str>) } fn bench(c: &mut Criterion) { @@ -186,14 +185,20 @@ fn bench(c: &mut Criterion) { let snarks = [(); 3].map(|_| gen_application_snark(¶ms_app)); let agg_config = AggregationConfigParams::from_path(path); let params = gen_srs(agg_config.degree); - let lookup_bits = params.k() as usize - 1; - let agg_circuit = AggregationCircuit::keygen::(¶ms, snarks.clone()); + let agg_circuit = AggregationCircuit::new::( + CircuitBuilderStage::Keygen, + agg_config, + ¶ms, + snarks.clone(), + VerifierUniversality::None, + ); let start0 = start_timer!(|| "gen vk & pk"); - let pk = gen_pk(¶ms, &agg_circuit, Some(Path::new("agg.pk"))); + let pk = gen_pk(¶ms, &agg_circuit, None); end_timer!(start0); let break_points = agg_circuit.break_points(); + drop(agg_circuit); let mut group = c.benchmark_group("plonk-prover"); group.sample_size(10); @@ -204,11 +209,12 @@ fn bench(c: &mut Criterion) { b.iter(|| { let agg_circuit = AggregationCircuit::new::( CircuitBuilderStage::Prover, - Some(break_points.clone()), - lookup_bits, + agg_config, params, snarks.clone(), - ); + VerifierUniversality::None, + ) + .use_break_points(break_points.clone()); let instances = agg_circuit.instances(); gen_proof_shplonk(params, pk, agg_circuit, instances, None) }) @@ -221,11 +227,12 @@ fn bench(c: &mut Criterion) { // do one more time to verify let agg_circuit = AggregationCircuit::new::( CircuitBuilderStage::Prover, - Some(break_points), - lookup_bits, + agg_config, ¶ms, snarks.clone(), - ); + VerifierUniversality::None, + ) + .use_break_points(break_points); let num_instances = agg_circuit.num_instance(); let instances = agg_circuit.instances(); let proof = gen_evm_proof_shplonk(¶ms, &pk, agg_circuit, instances.clone()); diff --git a/snark-verifier-sdk/examples/StandardPlonkVerifier.sol b/snark-verifier-sdk/examples/StandardPlonkVerifier.sol new file mode 100644 index 00000000..d6e9427c --- /dev/null +++ b/snark-verifier-sdk/examples/StandardPlonkVerifier.sol @@ -0,0 +1,1437 @@ + +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +contract Halo2Verifier { + fallback(bytes calldata) external returns (bytes memory) { + assembly { + let success := true + let f_p := 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 + let f_q := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 + function validate_ec_point(x, y) -> valid { + { + let x_lt_p := lt(x, 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + let y_lt_p := lt(y, 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + valid := and(x_lt_p, y_lt_p) + } + { + let y_square := mulmod(y, y, 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + let x_square := mulmod(x, x, 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + let x_cube := mulmod(x_square, x, 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + let x_cube_plus_3 := addmod(x_cube, 3, 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + let is_affine := eq(x_cube_plus_3, y_square) + valid := and(valid, is_affine) + } + } + mstore(0x20, mod(calldataload(0x0), f_q)) +mstore(0x40, mod(calldataload(0x20), f_q)) +mstore(0x60, mod(calldataload(0x40), f_q)) +mstore(0x80, mod(calldataload(0x60), f_q)) +mstore(0xa0, mod(calldataload(0x80), f_q)) +mstore(0xc0, mod(calldataload(0xa0), f_q)) +mstore(0xe0, mod(calldataload(0xc0), f_q)) +mstore(0x100, mod(calldataload(0xe0), f_q)) +mstore(0x120, mod(calldataload(0x100), f_q)) +mstore(0x140, mod(calldataload(0x120), f_q)) +mstore(0x160, mod(calldataload(0x140), f_q)) +mstore(0x180, mod(calldataload(0x160), f_q)) +mstore(0x0, 17740244582459666476042487670110999380715355991411842331101569887719361442506) + + { + let x := calldataload(0x180) + mstore(0x1a0, x) + let y := calldataload(0x1a0) + mstore(0x1c0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x1c0) + mstore(0x1e0, x) + let y := calldataload(0x1e0) + mstore(0x200, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x200) + mstore(0x220, x) + let y := calldataload(0x220) + mstore(0x240, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x260, keccak256(0x0, 608)) +{ + let hash := mload(0x260) + mstore(0x280, mod(hash, f_q)) + mstore(0x2a0, hash) + } + + { + let x := calldataload(0x240) + mstore(0x2c0, x) + let y := calldataload(0x260) + mstore(0x2e0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x280) + mstore(0x300, x) + let y := calldataload(0x2a0) + mstore(0x320, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x340, keccak256(0x2a0, 160)) +{ + let hash := mload(0x340) + mstore(0x360, mod(hash, f_q)) + mstore(0x380, hash) + } +mstore8(928, 1) +mstore(0x3a0, keccak256(0x380, 33)) +{ + let hash := mload(0x3a0) + mstore(0x3c0, mod(hash, f_q)) + mstore(0x3e0, hash) + } + + { + let x := calldataload(0x2c0) + mstore(0x400, x) + let y := calldataload(0x2e0) + mstore(0x420, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x300) + mstore(0x440, x) + let y := calldataload(0x320) + mstore(0x460, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x340) + mstore(0x480, x) + let y := calldataload(0x360) + mstore(0x4a0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x380) + mstore(0x4c0, x) + let y := calldataload(0x3a0) + mstore(0x4e0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x3c0) + mstore(0x500, x) + let y := calldataload(0x3e0) + mstore(0x520, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x540, keccak256(0x3e0, 352)) +{ + let hash := mload(0x540) + mstore(0x560, mod(hash, f_q)) + mstore(0x580, hash) + } + + { + let x := calldataload(0x400) + mstore(0x5a0, x) + let y := calldataload(0x420) + mstore(0x5c0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x440) + mstore(0x5e0, x) + let y := calldataload(0x460) + mstore(0x600, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x480) + mstore(0x620, x) + let y := calldataload(0x4a0) + mstore(0x640, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x660, keccak256(0x580, 224)) +{ + let hash := mload(0x660) + mstore(0x680, mod(hash, f_q)) + mstore(0x6a0, hash) + } +mstore(0x6c0, mod(calldataload(0x4c0), f_q)) +mstore(0x6e0, mod(calldataload(0x4e0), f_q)) +mstore(0x700, mod(calldataload(0x500), f_q)) +mstore(0x720, mod(calldataload(0x520), f_q)) +mstore(0x740, mod(calldataload(0x540), f_q)) +mstore(0x760, mod(calldataload(0x560), f_q)) +mstore(0x780, mod(calldataload(0x580), f_q)) +mstore(0x7a0, mod(calldataload(0x5a0), f_q)) +mstore(0x7c0, mod(calldataload(0x5c0), f_q)) +mstore(0x7e0, mod(calldataload(0x5e0), f_q)) +mstore(0x800, mod(calldataload(0x600), f_q)) +mstore(0x820, mod(calldataload(0x620), f_q)) +mstore(0x840, mod(calldataload(0x640), f_q)) +mstore(0x860, mod(calldataload(0x660), f_q)) +mstore(0x880, mod(calldataload(0x680), f_q)) +mstore(0x8a0, mod(calldataload(0x6a0), f_q)) +mstore(0x8c0, mod(calldataload(0x6c0), f_q)) +mstore(0x8e0, mod(calldataload(0x6e0), f_q)) +mstore(0x900, mod(calldataload(0x700), f_q)) +mstore(0x920, mod(calldataload(0x720), f_q)) +mstore(0x940, mod(calldataload(0x740), f_q)) +mstore(0x960, mod(calldataload(0x760), f_q)) +mstore(0x980, mod(calldataload(0x780), f_q)) +mstore(0x9a0, mod(calldataload(0x7a0), f_q)) +mstore(0x9c0, mod(calldataload(0x7c0), f_q)) +mstore(0x9e0, mod(calldataload(0x7e0), f_q)) +mstore(0xa00, mod(calldataload(0x800), f_q)) +mstore(0xa20, mod(calldataload(0x820), f_q)) +mstore(0xa40, mod(calldataload(0x840), f_q)) +mstore(0xa60, mod(calldataload(0x860), f_q)) +mstore(0xa80, mod(calldataload(0x880), f_q)) +mstore(0xaa0, mod(calldataload(0x8a0), f_q)) +mstore(0xac0, keccak256(0x6a0, 1056)) +{ + let hash := mload(0xac0) + mstore(0xae0, mod(hash, f_q)) + mstore(0xb00, hash) + } +mstore8(2848, 1) +mstore(0xb20, keccak256(0xb00, 33)) +{ + let hash := mload(0xb20) + mstore(0xb40, mod(hash, f_q)) + mstore(0xb60, hash) + } + + { + let x := calldataload(0x8c0) + mstore(0xb80, x) + let y := calldataload(0x8e0) + mstore(0xba0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0xbc0, keccak256(0xb60, 96)) +{ + let hash := mload(0xbc0) + mstore(0xbe0, mod(hash, f_q)) + mstore(0xc00, hash) + } + + { + let x := calldataload(0x900) + mstore(0xc20, x) + let y := calldataload(0x920) + mstore(0xc40, y) + success := and(validate_ec_point(x, y), success) + } +{ + let x := mload(0x20) +x := add(x, shl(88, mload(0x40))) +x := add(x, shl(176, mload(0x60))) +mstore(3168, x) +let y := mload(0x80) +y := add(y, shl(88, mload(0xa0))) +y := add(y, shl(176, mload(0xc0))) +mstore(3200, y) + + success := and(validate_ec_point(x, y), success) + } +{ + let x := mload(0xe0) +x := add(x, shl(88, mload(0x100))) +x := add(x, shl(176, mload(0x120))) +mstore(3232, x) +let y := mload(0x140) +y := add(y, shl(88, mload(0x160))) +y := add(y, shl(176, mload(0x180))) +mstore(3264, y) + + success := and(validate_ec_point(x, y), success) + } +mstore(0xce0, mulmod(mload(0x680), mload(0x680), f_q)) +mstore(0xd00, mulmod(mload(0xce0), mload(0xce0), f_q)) +mstore(0xd20, mulmod(mload(0xd00), mload(0xd00), f_q)) +mstore(0xd40, mulmod(mload(0xd20), mload(0xd20), f_q)) +mstore(0xd60, mulmod(mload(0xd40), mload(0xd40), f_q)) +mstore(0xd80, mulmod(mload(0xd60), mload(0xd60), f_q)) +mstore(0xda0, mulmod(mload(0xd80), mload(0xd80), f_q)) +mstore(0xdc0, mulmod(mload(0xda0), mload(0xda0), f_q)) +mstore(0xde0, mulmod(mload(0xdc0), mload(0xdc0), f_q)) +mstore(0xe00, mulmod(mload(0xde0), mload(0xde0), f_q)) +mstore(0xe20, mulmod(mload(0xe00), mload(0xe00), f_q)) +mstore(0xe40, mulmod(mload(0xe20), mload(0xe20), f_q)) +mstore(0xe60, mulmod(mload(0xe40), mload(0xe40), f_q)) +mstore(0xe80, mulmod(mload(0xe60), mload(0xe60), f_q)) +mstore(0xea0, mulmod(mload(0xe80), mload(0xe80), f_q)) +mstore(0xec0, mulmod(mload(0xea0), mload(0xea0), f_q)) +mstore(0xee0, mulmod(mload(0xec0), mload(0xec0), f_q)) +mstore(0xf00, mulmod(mload(0xee0), mload(0xee0), f_q)) +mstore(0xf20, mulmod(mload(0xf00), mload(0xf00), f_q)) +mstore(0xf40, mulmod(mload(0xf20), mload(0xf20), f_q)) +mstore(0xf60, mulmod(mload(0xf40), mload(0xf40), f_q)) +mstore(0xf80, addmod(mload(0xf60), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q)) +mstore(0xfa0, mulmod(mload(0xf80), 21888232434711746154598842647110004286396165347431605739555851272621938401409, f_q)) +mstore(0xfc0, mulmod(mload(0xfa0), 20975929243409798062839949658616274858986091382510192949221301676705706354487, f_q)) +mstore(0xfe0, addmod(mload(0x680), 912313628429477159406456086641000229562273017905841394476902509870102141130, f_q)) +mstore(0x1000, mulmod(mload(0xfa0), 495188420091111145957709789221178673495499187437761988132837836548330853701, f_q)) +mstore(0x1020, addmod(mload(0x680), 21393054451748164076288695956036096415052865212978272355565366350027477641916, f_q)) +mstore(0x1040, mulmod(mload(0xfa0), 16064522944768515290584536219762686197737451920702130080538975732575755569557, f_q)) +mstore(0x1060, addmod(mload(0x680), 5823719927070759931661869525494588890810912479713904263159228454000052926060, f_q)) +mstore(0x1080, mulmod(mload(0xfa0), 14686510910986211321976396297238126901237973400949744736326777596334651355305, f_q)) +mstore(0x10a0, addmod(mload(0x680), 7201731960853063900270009448019148187310390999466289607371426590241157140312, f_q)) +mstore(0x10c0, mulmod(mload(0xfa0), 10939663269433627367777756708678102241564365262857670666700619874077960926249, f_q)) +mstore(0x10e0, addmod(mload(0x680), 10948579602405647854468649036579172846983999137558363676997584312497847569368, f_q)) +mstore(0x1100, mulmod(mload(0xfa0), 15402826414547299628414612080036060696555554914079673875872749760617770134879, f_q)) +mstore(0x1120, addmod(mload(0x680), 6485416457291975593831793665221214391992809486336360467825454425958038360738, f_q)) +mstore(0x1140, mulmod(mload(0xfa0), 2785514556381676080176937710880804108647911392478702105860685610379369825016, f_q)) +mstore(0x1160, addmod(mload(0x680), 19102728315457599142069468034376470979900453007937332237837518576196438670601, f_q)) +mstore(0x1180, mulmod(mload(0xfa0), 1, f_q)) +mstore(0x11a0, addmod(mload(0x680), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q)) +mstore(0x11c0, mulmod(mload(0xfa0), 1426404432721484388505361748317961535523355871255605456897797744433766488507, f_q)) +mstore(0x11e0, addmod(mload(0x680), 20461838439117790833741043996939313553025008529160428886800406442142042007110, f_q)) +mstore(0x1200, mulmod(mload(0xfa0), 19032961837237948602743626455740240236231119053033140765040043513661803148152, f_q)) +mstore(0x1220, addmod(mload(0x680), 2855281034601326619502779289517034852317245347382893578658160672914005347465, f_q)) +mstore(0x1240, mulmod(mload(0xfa0), 3766081621734395783232337525162072736827576297943013392955872170138036189193, f_q)) +mstore(0x1260, addmod(mload(0x680), 18122161250104879439014068220095202351720788102473020950742332016437772306424, f_q)) +mstore(0x1280, mulmod(mload(0xfa0), 5854133144571823792863860130267644613802765696134002830362054821530146160770, f_q)) +mstore(0x12a0, addmod(mload(0x680), 16034109727267451429382545614989630474745598704282031513336149365045662334847, f_q)) +mstore(0x12c0, mulmod(mload(0xfa0), 14557038802599140430182096396825290815503940951075961210638273254419942783582, f_q)) +mstore(0x12e0, addmod(mload(0x680), 7331204069240134792064309348431984273044423449340073133059930932155865712035, f_q)) +mstore(0x1300, mulmod(mload(0xfa0), 9697063347556872083384215826199993067635178715531258559890418744774301211662, f_q)) +mstore(0x1320, addmod(mload(0x680), 12191179524282403138862189919057282020913185684884775783807785441801507283955, f_q)) +mstore(0x1340, mulmod(mload(0xfa0), 12459868075641381822485233712013080087763946065665469821362892189399541605692, f_q)) +mstore(0x1360, addmod(mload(0x680), 9428374796197893399761172033244195000784418334750564522335311997176266889925, f_q)) +mstore(0x1380, mulmod(mload(0xfa0), 6955697244493336113861667751840378876927906302623587437721024018233754910398, f_q)) +mstore(0x13a0, addmod(mload(0x680), 14932545627345939108384737993416896211620458097792446905977180168342053585219, f_q)) +mstore(0x13c0, mulmod(mload(0xfa0), 20345677989844117909528750049476969581182118546166966482506114734614108237981, f_q)) +mstore(0x13e0, addmod(mload(0x680), 1542564881995157312717655695780305507366245854249067861192089451961700257636, f_q)) +mstore(0x1400, mulmod(mload(0xfa0), 5289443209903185443361862148540090689648485914368835830972895623576469023722, f_q)) +mstore(0x1420, addmod(mload(0x680), 16598799661936089778884543596717184398899878486047198512725308562999339471895, f_q)) +mstore(0x1440, mulmod(mload(0xfa0), 557567375339945239933617516585967620814823575807691402619711360028043331811, f_q)) +mstore(0x1460, addmod(mload(0x680), 21330675496499329982312788228671307467733540824608342941078492826547765163806, f_q)) +{ + let prod := mload(0xfe0) + + prod := mulmod(mload(0x1020), prod, f_q) + mstore(0x1480, prod) + + prod := mulmod(mload(0x1060), prod, f_q) + mstore(0x14a0, prod) + + prod := mulmod(mload(0x10a0), prod, f_q) + mstore(0x14c0, prod) + + prod := mulmod(mload(0x10e0), prod, f_q) + mstore(0x14e0, prod) + + prod := mulmod(mload(0x1120), prod, f_q) + mstore(0x1500, prod) + + prod := mulmod(mload(0x1160), prod, f_q) + mstore(0x1520, prod) + + prod := mulmod(mload(0x11a0), prod, f_q) + mstore(0x1540, prod) + + prod := mulmod(mload(0x11e0), prod, f_q) + mstore(0x1560, prod) + + prod := mulmod(mload(0x1220), prod, f_q) + mstore(0x1580, prod) + + prod := mulmod(mload(0x1260), prod, f_q) + mstore(0x15a0, prod) + + prod := mulmod(mload(0x12a0), prod, f_q) + mstore(0x15c0, prod) + + prod := mulmod(mload(0x12e0), prod, f_q) + mstore(0x15e0, prod) + + prod := mulmod(mload(0x1320), prod, f_q) + mstore(0x1600, prod) + + prod := mulmod(mload(0x1360), prod, f_q) + mstore(0x1620, prod) + + prod := mulmod(mload(0x13a0), prod, f_q) + mstore(0x1640, prod) + + prod := mulmod(mload(0x13e0), prod, f_q) + mstore(0x1660, prod) + + prod := mulmod(mload(0x1420), prod, f_q) + mstore(0x1680, prod) + + prod := mulmod(mload(0x1460), prod, f_q) + mstore(0x16a0, prod) + + prod := mulmod(mload(0xf80), prod, f_q) + mstore(0x16c0, prod) + + } +mstore(0x1700, 32) +mstore(0x1720, 32) +mstore(0x1740, 32) +mstore(0x1760, mload(0x16c0)) +mstore(0x1780, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x17a0, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x1700, 0xc0, 0x16e0, 0x20), 1), success) +{ + + let inv := mload(0x16e0) + let v + + v := mload(0xf80) + mstore(3968, mulmod(mload(0x16a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1460) + mstore(5216, mulmod(mload(0x1680), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1420) + mstore(5152, mulmod(mload(0x1660), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x13e0) + mstore(5088, mulmod(mload(0x1640), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x13a0) + mstore(5024, mulmod(mload(0x1620), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1360) + mstore(4960, mulmod(mload(0x1600), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1320) + mstore(4896, mulmod(mload(0x15e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x12e0) + mstore(4832, mulmod(mload(0x15c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x12a0) + mstore(4768, mulmod(mload(0x15a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1260) + mstore(4704, mulmod(mload(0x1580), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1220) + mstore(4640, mulmod(mload(0x1560), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x11e0) + mstore(4576, mulmod(mload(0x1540), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x11a0) + mstore(4512, mulmod(mload(0x1520), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1160) + mstore(4448, mulmod(mload(0x1500), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1120) + mstore(4384, mulmod(mload(0x14e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x10e0) + mstore(4320, mulmod(mload(0x14c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x10a0) + mstore(4256, mulmod(mload(0x14a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1060) + mstore(4192, mulmod(mload(0x1480), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1020) + mstore(4128, mulmod(mload(0xfe0), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0xfe0, inv) + + } +mstore(0x17c0, mulmod(mload(0xfc0), mload(0xfe0), f_q)) +mstore(0x17e0, mulmod(mload(0x1000), mload(0x1020), f_q)) +mstore(0x1800, mulmod(mload(0x1040), mload(0x1060), f_q)) +mstore(0x1820, mulmod(mload(0x1080), mload(0x10a0), f_q)) +mstore(0x1840, mulmod(mload(0x10c0), mload(0x10e0), f_q)) +mstore(0x1860, mulmod(mload(0x1100), mload(0x1120), f_q)) +mstore(0x1880, mulmod(mload(0x1140), mload(0x1160), f_q)) +mstore(0x18a0, mulmod(mload(0x1180), mload(0x11a0), f_q)) +mstore(0x18c0, mulmod(mload(0x11c0), mload(0x11e0), f_q)) +mstore(0x18e0, mulmod(mload(0x1200), mload(0x1220), f_q)) +mstore(0x1900, mulmod(mload(0x1240), mload(0x1260), f_q)) +mstore(0x1920, mulmod(mload(0x1280), mload(0x12a0), f_q)) +mstore(0x1940, mulmod(mload(0x12c0), mload(0x12e0), f_q)) +mstore(0x1960, mulmod(mload(0x1300), mload(0x1320), f_q)) +mstore(0x1980, mulmod(mload(0x1340), mload(0x1360), f_q)) +mstore(0x19a0, mulmod(mload(0x1380), mload(0x13a0), f_q)) +mstore(0x19c0, mulmod(mload(0x13c0), mload(0x13e0), f_q)) +mstore(0x19e0, mulmod(mload(0x1400), mload(0x1420), f_q)) +mstore(0x1a00, mulmod(mload(0x1440), mload(0x1460), f_q)) +{ + let result := mulmod(mload(0x18a0), mload(0x20), f_q) +result := addmod(mulmod(mload(0x18c0), mload(0x40), f_q), result, f_q) +result := addmod(mulmod(mload(0x18e0), mload(0x60), f_q), result, f_q) +result := addmod(mulmod(mload(0x1900), mload(0x80), f_q), result, f_q) +result := addmod(mulmod(mload(0x1920), mload(0xa0), f_q), result, f_q) +result := addmod(mulmod(mload(0x1940), mload(0xc0), f_q), result, f_q) +result := addmod(mulmod(mload(0x1960), mload(0xe0), f_q), result, f_q) +result := addmod(mulmod(mload(0x1980), mload(0x100), f_q), result, f_q) +result := addmod(mulmod(mload(0x19a0), mload(0x120), f_q), result, f_q) +result := addmod(mulmod(mload(0x19c0), mload(0x140), f_q), result, f_q) +result := addmod(mulmod(mload(0x19e0), mload(0x160), f_q), result, f_q) +result := addmod(mulmod(mload(0x1a00), mload(0x180), f_q), result, f_q) +mstore(6688, result) + } +mstore(0x1a40, mulmod(mload(0x700), mload(0x6e0), f_q)) +mstore(0x1a60, addmod(mload(0x6c0), mload(0x1a40), f_q)) +mstore(0x1a80, addmod(mload(0x1a60), sub(f_q, mload(0x720)), f_q)) +mstore(0x1aa0, mulmod(mload(0x1a80), mload(0x820), f_q)) +mstore(0x1ac0, mulmod(mload(0x560), mload(0x1aa0), f_q)) +mstore(0x1ae0, mulmod(mload(0x780), mload(0x760), f_q)) +mstore(0x1b00, addmod(mload(0x740), mload(0x1ae0), f_q)) +mstore(0x1b20, addmod(mload(0x1b00), sub(f_q, mload(0x7a0)), f_q)) +mstore(0x1b40, mulmod(mload(0x1b20), mload(0x840), f_q)) +mstore(0x1b60, addmod(mload(0x1ac0), mload(0x1b40), f_q)) +mstore(0x1b80, mulmod(mload(0x560), mload(0x1b60), f_q)) +mstore(0x1ba0, addmod(1, sub(f_q, mload(0x920)), f_q)) +mstore(0x1bc0, mulmod(mload(0x1ba0), mload(0x18a0), f_q)) +mstore(0x1be0, addmod(mload(0x1b80), mload(0x1bc0), f_q)) +mstore(0x1c00, mulmod(mload(0x560), mload(0x1be0), f_q)) +mstore(0x1c20, mulmod(mload(0x9e0), mload(0x9e0), f_q)) +mstore(0x1c40, addmod(mload(0x1c20), sub(f_q, mload(0x9e0)), f_q)) +mstore(0x1c60, mulmod(mload(0x1c40), mload(0x17c0), f_q)) +mstore(0x1c80, addmod(mload(0x1c00), mload(0x1c60), f_q)) +mstore(0x1ca0, mulmod(mload(0x560), mload(0x1c80), f_q)) +mstore(0x1cc0, addmod(mload(0x980), sub(f_q, mload(0x960)), f_q)) +mstore(0x1ce0, mulmod(mload(0x1cc0), mload(0x18a0), f_q)) +mstore(0x1d00, addmod(mload(0x1ca0), mload(0x1ce0), f_q)) +mstore(0x1d20, mulmod(mload(0x560), mload(0x1d00), f_q)) +mstore(0x1d40, addmod(mload(0x9e0), sub(f_q, mload(0x9c0)), f_q)) +mstore(0x1d60, mulmod(mload(0x1d40), mload(0x18a0), f_q)) +mstore(0x1d80, addmod(mload(0x1d20), mload(0x1d60), f_q)) +mstore(0x1da0, mulmod(mload(0x560), mload(0x1d80), f_q)) +mstore(0x1dc0, addmod(1, sub(f_q, mload(0x17c0)), f_q)) +mstore(0x1de0, addmod(mload(0x17e0), mload(0x1800), f_q)) +mstore(0x1e00, addmod(mload(0x1de0), mload(0x1820), f_q)) +mstore(0x1e20, addmod(mload(0x1e00), mload(0x1840), f_q)) +mstore(0x1e40, addmod(mload(0x1e20), mload(0x1860), f_q)) +mstore(0x1e60, addmod(mload(0x1e40), mload(0x1880), f_q)) +mstore(0x1e80, addmod(mload(0x1dc0), sub(f_q, mload(0x1e60)), f_q)) +mstore(0x1ea0, mulmod(mload(0x880), mload(0x360), f_q)) +mstore(0x1ec0, addmod(mload(0x7e0), mload(0x1ea0), f_q)) +mstore(0x1ee0, addmod(mload(0x1ec0), mload(0x3c0), f_q)) +mstore(0x1f00, mulmod(mload(0x8a0), mload(0x360), f_q)) +mstore(0x1f20, addmod(mload(0x6c0), mload(0x1f00), f_q)) +mstore(0x1f40, addmod(mload(0x1f20), mload(0x3c0), f_q)) +mstore(0x1f60, mulmod(mload(0x1f40), mload(0x1ee0), f_q)) +mstore(0x1f80, mulmod(mload(0x1f60), mload(0x940), f_q)) +mstore(0x1fa0, mulmod(1, mload(0x360), f_q)) +mstore(0x1fc0, mulmod(mload(0x680), mload(0x1fa0), f_q)) +mstore(0x1fe0, addmod(mload(0x7e0), mload(0x1fc0), f_q)) +mstore(0x2000, addmod(mload(0x1fe0), mload(0x3c0), f_q)) +mstore(0x2020, mulmod(4131629893567559867359510883348571134090853742863529169391034518566172092834, mload(0x360), f_q)) +mstore(0x2040, mulmod(mload(0x680), mload(0x2020), f_q)) +mstore(0x2060, addmod(mload(0x6c0), mload(0x2040), f_q)) +mstore(0x2080, addmod(mload(0x2060), mload(0x3c0), f_q)) +mstore(0x20a0, mulmod(mload(0x2080), mload(0x2000), f_q)) +mstore(0x20c0, mulmod(mload(0x20a0), mload(0x920), f_q)) +mstore(0x20e0, addmod(mload(0x1f80), sub(f_q, mload(0x20c0)), f_q)) +mstore(0x2100, mulmod(mload(0x20e0), mload(0x1e80), f_q)) +mstore(0x2120, addmod(mload(0x1da0), mload(0x2100), f_q)) +mstore(0x2140, mulmod(mload(0x560), mload(0x2120), f_q)) +mstore(0x2160, mulmod(mload(0x8c0), mload(0x360), f_q)) +mstore(0x2180, addmod(mload(0x740), mload(0x2160), f_q)) +mstore(0x21a0, addmod(mload(0x2180), mload(0x3c0), f_q)) +mstore(0x21c0, mulmod(mload(0x8e0), mload(0x360), f_q)) +mstore(0x21e0, addmod(mload(0x7c0), mload(0x21c0), f_q)) +mstore(0x2200, addmod(mload(0x21e0), mload(0x3c0), f_q)) +mstore(0x2220, mulmod(mload(0x2200), mload(0x21a0), f_q)) +mstore(0x2240, mulmod(mload(0x2220), mload(0x9a0), f_q)) +mstore(0x2260, mulmod(8910878055287538404433155982483128285667088683464058436815641868457422632747, mload(0x360), f_q)) +mstore(0x2280, mulmod(mload(0x680), mload(0x2260), f_q)) +mstore(0x22a0, addmod(mload(0x740), mload(0x2280), f_q)) +mstore(0x22c0, addmod(mload(0x22a0), mload(0x3c0), f_q)) +mstore(0x22e0, mulmod(11166246659983828508719468090013646171463329086121580628794302409516816350802, mload(0x360), f_q)) +mstore(0x2300, mulmod(mload(0x680), mload(0x22e0), f_q)) +mstore(0x2320, addmod(mload(0x7c0), mload(0x2300), f_q)) +mstore(0x2340, addmod(mload(0x2320), mload(0x3c0), f_q)) +mstore(0x2360, mulmod(mload(0x2340), mload(0x22c0), f_q)) +mstore(0x2380, mulmod(mload(0x2360), mload(0x980), f_q)) +mstore(0x23a0, addmod(mload(0x2240), sub(f_q, mload(0x2380)), f_q)) +mstore(0x23c0, mulmod(mload(0x23a0), mload(0x1e80), f_q)) +mstore(0x23e0, addmod(mload(0x2140), mload(0x23c0), f_q)) +mstore(0x2400, mulmod(mload(0x560), mload(0x23e0), f_q)) +mstore(0x2420, mulmod(mload(0x900), mload(0x360), f_q)) +mstore(0x2440, addmod(mload(0x1a20), mload(0x2420), f_q)) +mstore(0x2460, addmod(mload(0x2440), mload(0x3c0), f_q)) +mstore(0x2480, mulmod(mload(0x2460), mload(0xa00), f_q)) +mstore(0x24a0, mulmod(284840088355319032285349970403338060113257071685626700086398481893096618818, mload(0x360), f_q)) +mstore(0x24c0, mulmod(mload(0x680), mload(0x24a0), f_q)) +mstore(0x24e0, addmod(mload(0x1a20), mload(0x24c0), f_q)) +mstore(0x2500, addmod(mload(0x24e0), mload(0x3c0), f_q)) +mstore(0x2520, mulmod(mload(0x2500), mload(0x9e0), f_q)) +mstore(0x2540, addmod(mload(0x2480), sub(f_q, mload(0x2520)), f_q)) +mstore(0x2560, mulmod(mload(0x2540), mload(0x1e80), f_q)) +mstore(0x2580, addmod(mload(0x2400), mload(0x2560), f_q)) +mstore(0x25a0, mulmod(mload(0x560), mload(0x2580), f_q)) +mstore(0x25c0, addmod(1, sub(f_q, mload(0xa20)), f_q)) +mstore(0x25e0, mulmod(mload(0x25c0), mload(0x18a0), f_q)) +mstore(0x2600, addmod(mload(0x25a0), mload(0x25e0), f_q)) +mstore(0x2620, mulmod(mload(0x560), mload(0x2600), f_q)) +mstore(0x2640, mulmod(mload(0xa20), mload(0xa20), f_q)) +mstore(0x2660, addmod(mload(0x2640), sub(f_q, mload(0xa20)), f_q)) +mstore(0x2680, mulmod(mload(0x2660), mload(0x17c0), f_q)) +mstore(0x26a0, addmod(mload(0x2620), mload(0x2680), f_q)) +mstore(0x26c0, mulmod(mload(0x560), mload(0x26a0), f_q)) +mstore(0x26e0, addmod(mload(0xa60), mload(0x360), f_q)) +mstore(0x2700, mulmod(mload(0x26e0), mload(0xa40), f_q)) +mstore(0x2720, addmod(mload(0xaa0), mload(0x3c0), f_q)) +mstore(0x2740, mulmod(mload(0x2720), mload(0x2700), f_q)) +mstore(0x2760, addmod(mload(0x7c0), mload(0x360), f_q)) +mstore(0x2780, mulmod(mload(0x2760), mload(0xa20), f_q)) +mstore(0x27a0, addmod(mload(0x800), mload(0x3c0), f_q)) +mstore(0x27c0, mulmod(mload(0x27a0), mload(0x2780), f_q)) +mstore(0x27e0, addmod(mload(0x2740), sub(f_q, mload(0x27c0)), f_q)) +mstore(0x2800, mulmod(mload(0x27e0), mload(0x1e80), f_q)) +mstore(0x2820, addmod(mload(0x26c0), mload(0x2800), f_q)) +mstore(0x2840, mulmod(mload(0x560), mload(0x2820), f_q)) +mstore(0x2860, addmod(mload(0xa60), sub(f_q, mload(0xaa0)), f_q)) +mstore(0x2880, mulmod(mload(0x2860), mload(0x18a0), f_q)) +mstore(0x28a0, addmod(mload(0x2840), mload(0x2880), f_q)) +mstore(0x28c0, mulmod(mload(0x560), mload(0x28a0), f_q)) +mstore(0x28e0, mulmod(mload(0x2860), mload(0x1e80), f_q)) +mstore(0x2900, addmod(mload(0xa60), sub(f_q, mload(0xa80)), f_q)) +mstore(0x2920, mulmod(mload(0x2900), mload(0x28e0), f_q)) +mstore(0x2940, addmod(mload(0x28c0), mload(0x2920), f_q)) +mstore(0x2960, mulmod(mload(0xf60), mload(0xf60), f_q)) +mstore(0x2980, mulmod(mload(0x2960), mload(0xf60), f_q)) +mstore(0x29a0, mulmod(1, mload(0xf60), f_q)) +mstore(0x29c0, mulmod(1, mload(0x2960), f_q)) +mstore(0x29e0, mulmod(mload(0x2940), mload(0xf80), f_q)) +mstore(0x2a00, mulmod(mload(0xce0), mload(0x680), f_q)) +mstore(0x2a20, mulmod(mload(0x2a00), mload(0x680), f_q)) +mstore(0x2a40, mulmod(mload(0x680), 20975929243409798062839949658616274858986091382510192949221301676705706354487, f_q)) +mstore(0x2a60, addmod(mload(0xbe0), sub(f_q, mload(0x2a40)), f_q)) +mstore(0x2a80, mulmod(mload(0x680), 2785514556381676080176937710880804108647911392478702105860685610379369825016, f_q)) +mstore(0x2aa0, addmod(mload(0xbe0), sub(f_q, mload(0x2a80)), f_q)) +mstore(0x2ac0, mulmod(mload(0x680), 1, f_q)) +mstore(0x2ae0, addmod(mload(0xbe0), sub(f_q, mload(0x2ac0)), f_q)) +mstore(0x2b00, mulmod(mload(0x680), 1426404432721484388505361748317961535523355871255605456897797744433766488507, f_q)) +mstore(0x2b20, addmod(mload(0xbe0), sub(f_q, mload(0x2b00)), f_q)) +mstore(0x2b40, mulmod(mload(0x680), 19032961837237948602743626455740240236231119053033140765040043513661803148152, f_q)) +mstore(0x2b60, addmod(mload(0xbe0), sub(f_q, mload(0x2b40)), f_q)) +mstore(0x2b80, mulmod(mload(0x680), 3766081621734395783232337525162072736827576297943013392955872170138036189193, f_q)) +mstore(0x2ba0, addmod(mload(0xbe0), sub(f_q, mload(0x2b80)), f_q)) +mstore(0x2bc0, mulmod(12142985201493934370659158242092015678465417407805993602870272259656026591649, mload(0x2a00), f_q)) +mstore(0x2be0, mulmod(mload(0x2bc0), 1, f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x2bc0), f_q) +result := addmod(mulmod(mload(0x680), sub(f_q, mload(0x2be0)), f_q), result, f_q) +mstore(11264, result) + } +mstore(0x2c20, mulmod(12858672892267984631233883117647866851148059157064290846881981435700301865966, mload(0x2a00), f_q)) +mstore(0x2c40, mulmod(mload(0x2c20), 1426404432721484388505361748317961535523355871255605456897797744433766488507, f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x2c20), f_q) +result := addmod(mulmod(mload(0x680), sub(f_q, mload(0x2c40)), f_q), result, f_q) +mstore(11360, result) + } +mstore(0x2c80, mulmod(20880316823902385764034220950270964645276820671488089374347912013802613180902, mload(0x2a00), f_q)) +mstore(0x2ca0, mulmod(mload(0x2c80), 19032961837237948602743626455740240236231119053033140765040043513661803148152, f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x2c80), f_q) +result := addmod(mulmod(mload(0x680), sub(f_q, mload(0x2ca0)), f_q), result, f_q) +mstore(11456, result) + } +mstore(0x2ce0, mulmod(17575202995145968412995467587554373308969396527144859871466654432792864477050, mload(0x2a00), f_q)) +mstore(0x2d00, mulmod(mload(0x2ce0), 3766081621734395783232337525162072736827576297943013392955872170138036189193, f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x2ce0), f_q) +result := addmod(mulmod(mload(0x680), sub(f_q, mload(0x2d00)), f_q), result, f_q) +mstore(11552, result) + } +mstore(0x2d40, mulmod(1, mload(0x2ae0), f_q)) +mstore(0x2d60, mulmod(mload(0x2d40), mload(0x2b20), f_q)) +mstore(0x2d80, mulmod(mload(0x2d60), mload(0x2b60), f_q)) +mstore(0x2da0, mulmod(mload(0x2d80), mload(0x2ba0), f_q)) +{ + let result := mulmod(mload(0xbe0), 1, f_q) +result := addmod(mulmod(mload(0x680), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q), result, f_q) +mstore(11712, result) + } +mstore(0x2de0, mulmod(21869340487638379139105209872801492456082780734504032269410146788560475001942, mload(0xce0), f_q)) +mstore(0x2e00, mulmod(mload(0x2de0), 1, f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x2de0), f_q) +result := addmod(mulmod(mload(0x680), sub(f_q, mload(0x2e00)), f_q), result, f_q) +mstore(11808, result) + } +mstore(0x2e40, mulmod(16199055355995875908874098831560099797649990976433931925532505422809603664814, mload(0xce0), f_q)) +mstore(0x2e60, mulmod(mload(0x2e40), 1426404432721484388505361748317961535523355871255605456897797744433766488507, f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x2e40), f_q) +result := addmod(mulmod(mload(0x680), sub(f_q, mload(0x2e60)), f_q), result, f_q) +mstore(11904, result) + } +mstore(0x2ea0, mulmod(4865187280763976036134135047793386535665014353502380106898040096680231678680, mload(0xce0), f_q)) +mstore(0x2ec0, mulmod(mload(0x2ea0), 20975929243409798062839949658616274858986091382510192949221301676705706354487, f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x2ea0), f_q) +result := addmod(mulmod(mload(0x680), sub(f_q, mload(0x2ec0)), f_q), result, f_q) +mstore(12000, result) + } +mstore(0x2f00, mulmod(mload(0x2d60), mload(0x2a60), f_q)) +mstore(0x2f20, mulmod(20461838439117790833741043996939313553025008529160428886800406442142042007111, mload(0x680), f_q)) +mstore(0x2f40, mulmod(mload(0x2f20), 1, f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x2f20), f_q) +result := addmod(mulmod(mload(0x680), sub(f_q, mload(0x2f40)), f_q), result, f_q) +mstore(12128, result) + } +mstore(0x2f80, mulmod(1426404432721484388505361748317961535523355871255605456897797744433766488506, mload(0x680), f_q)) +mstore(0x2fa0, mulmod(mload(0x2f80), 1426404432721484388505361748317961535523355871255605456897797744433766488507, f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x2f80), f_q) +result := addmod(mulmod(mload(0x680), sub(f_q, mload(0x2fa0)), f_q), result, f_q) +mstore(12224, result) + } +mstore(0x2fe0, mulmod(19102728315457599142069468034376470979900453007937332237837518576196438670602, mload(0x680), f_q)) +mstore(0x3000, mulmod(mload(0x2fe0), 1, f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x2fe0), f_q) +result := addmod(mulmod(mload(0x680), sub(f_q, mload(0x3000)), f_q), result, f_q) +mstore(12320, result) + } +mstore(0x3040, mulmod(2785514556381676080176937710880804108647911392478702105860685610379369825015, mload(0x680), f_q)) +mstore(0x3060, mulmod(mload(0x3040), 2785514556381676080176937710880804108647911392478702105860685610379369825016, f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x3040), f_q) +result := addmod(mulmod(mload(0x680), sub(f_q, mload(0x3060)), f_q), result, f_q) +mstore(12416, result) + } +mstore(0x30a0, mulmod(mload(0x2d40), mload(0x2aa0), f_q)) +{ + let prod := mload(0x2c00) + + prod := mulmod(mload(0x2c60), prod, f_q) + mstore(0x30c0, prod) + + prod := mulmod(mload(0x2cc0), prod, f_q) + mstore(0x30e0, prod) + + prod := mulmod(mload(0x2d20), prod, f_q) + mstore(0x3100, prod) + + prod := mulmod(mload(0x2dc0), prod, f_q) + mstore(0x3120, prod) + + prod := mulmod(mload(0x2d40), prod, f_q) + mstore(0x3140, prod) + + prod := mulmod(mload(0x2e20), prod, f_q) + mstore(0x3160, prod) + + prod := mulmod(mload(0x2e80), prod, f_q) + mstore(0x3180, prod) + + prod := mulmod(mload(0x2ee0), prod, f_q) + mstore(0x31a0, prod) + + prod := mulmod(mload(0x2f00), prod, f_q) + mstore(0x31c0, prod) + + prod := mulmod(mload(0x2f60), prod, f_q) + mstore(0x31e0, prod) + + prod := mulmod(mload(0x2fc0), prod, f_q) + mstore(0x3200, prod) + + prod := mulmod(mload(0x2d60), prod, f_q) + mstore(0x3220, prod) + + prod := mulmod(mload(0x3020), prod, f_q) + mstore(0x3240, prod) + + prod := mulmod(mload(0x3080), prod, f_q) + mstore(0x3260, prod) + + prod := mulmod(mload(0x30a0), prod, f_q) + mstore(0x3280, prod) + + } +mstore(0x32c0, 32) +mstore(0x32e0, 32) +mstore(0x3300, 32) +mstore(0x3320, mload(0x3280)) +mstore(0x3340, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x3360, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x32c0, 0xc0, 0x32a0, 0x20), 1), success) +{ + + let inv := mload(0x32a0) + let v + + v := mload(0x30a0) + mstore(12448, mulmod(mload(0x3260), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3080) + mstore(12416, mulmod(mload(0x3240), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3020) + mstore(12320, mulmod(mload(0x3220), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2d60) + mstore(11616, mulmod(mload(0x3200), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2fc0) + mstore(12224, mulmod(mload(0x31e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2f60) + mstore(12128, mulmod(mload(0x31c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2f00) + mstore(12032, mulmod(mload(0x31a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2ee0) + mstore(12000, mulmod(mload(0x3180), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2e80) + mstore(11904, mulmod(mload(0x3160), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2e20) + mstore(11808, mulmod(mload(0x3140), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2d40) + mstore(11584, mulmod(mload(0x3120), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2dc0) + mstore(11712, mulmod(mload(0x3100), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2d20) + mstore(11552, mulmod(mload(0x30e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2cc0) + mstore(11456, mulmod(mload(0x30c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2c60) + mstore(11360, mulmod(mload(0x2c00), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x2c00, inv) + + } +{ + let result := mload(0x2c00) +result := addmod(mload(0x2c60), result, f_q) +result := addmod(mload(0x2cc0), result, f_q) +result := addmod(mload(0x2d20), result, f_q) +mstore(13184, result) + } +mstore(0x33a0, mulmod(mload(0x2da0), mload(0x2d40), f_q)) +{ + let result := mload(0x2dc0) +mstore(13248, result) + } +mstore(0x33e0, mulmod(mload(0x2da0), mload(0x2f00), f_q)) +{ + let result := mload(0x2e20) +result := addmod(mload(0x2e80), result, f_q) +result := addmod(mload(0x2ee0), result, f_q) +mstore(13312, result) + } +mstore(0x3420, mulmod(mload(0x2da0), mload(0x2d60), f_q)) +{ + let result := mload(0x2f60) +result := addmod(mload(0x2fc0), result, f_q) +mstore(13376, result) + } +mstore(0x3460, mulmod(mload(0x2da0), mload(0x30a0), f_q)) +{ + let result := mload(0x3020) +result := addmod(mload(0x3080), result, f_q) +mstore(13440, result) + } +{ + let prod := mload(0x3380) + + prod := mulmod(mload(0x33c0), prod, f_q) + mstore(0x34a0, prod) + + prod := mulmod(mload(0x3400), prod, f_q) + mstore(0x34c0, prod) + + prod := mulmod(mload(0x3440), prod, f_q) + mstore(0x34e0, prod) + + prod := mulmod(mload(0x3480), prod, f_q) + mstore(0x3500, prod) + + } +mstore(0x3540, 32) +mstore(0x3560, 32) +mstore(0x3580, 32) +mstore(0x35a0, mload(0x3500)) +mstore(0x35c0, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x35e0, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x3540, 0xc0, 0x3520, 0x20), 1), success) +{ + + let inv := mload(0x3520) + let v + + v := mload(0x3480) + mstore(13440, mulmod(mload(0x34e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3440) + mstore(13376, mulmod(mload(0x34c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3400) + mstore(13312, mulmod(mload(0x34a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x33c0) + mstore(13248, mulmod(mload(0x3380), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x3380, inv) + + } +mstore(0x3600, mulmod(mload(0x33a0), mload(0x33c0), f_q)) +mstore(0x3620, mulmod(mload(0x33e0), mload(0x3400), f_q)) +mstore(0x3640, mulmod(mload(0x3420), mload(0x3440), f_q)) +mstore(0x3660, mulmod(mload(0x3460), mload(0x3480), f_q)) +mstore(0x3680, mulmod(mload(0xae0), mload(0xae0), f_q)) +mstore(0x36a0, mulmod(mload(0x3680), mload(0xae0), f_q)) +mstore(0x36c0, mulmod(mload(0x36a0), mload(0xae0), f_q)) +mstore(0x36e0, mulmod(mload(0x36c0), mload(0xae0), f_q)) +mstore(0x3700, mulmod(mload(0x36e0), mload(0xae0), f_q)) +mstore(0x3720, mulmod(mload(0x3700), mload(0xae0), f_q)) +mstore(0x3740, mulmod(mload(0x3720), mload(0xae0), f_q)) +mstore(0x3760, mulmod(mload(0x3740), mload(0xae0), f_q)) +mstore(0x3780, mulmod(mload(0x3760), mload(0xae0), f_q)) +mstore(0x37a0, mulmod(mload(0x3780), mload(0xae0), f_q)) +mstore(0x37c0, mulmod(mload(0x37a0), mload(0xae0), f_q)) +mstore(0x37e0, mulmod(mload(0x37c0), mload(0xae0), f_q)) +mstore(0x3800, mulmod(mload(0xb40), mload(0xb40), f_q)) +mstore(0x3820, mulmod(mload(0x3800), mload(0xb40), f_q)) +mstore(0x3840, mulmod(mload(0x3820), mload(0xb40), f_q)) +mstore(0x3860, mulmod(mload(0x3840), mload(0xb40), f_q)) +{ + let result := mulmod(mload(0x6c0), mload(0x2c00), f_q) +result := addmod(mulmod(mload(0x6e0), mload(0x2c60), f_q), result, f_q) +result := addmod(mulmod(mload(0x700), mload(0x2cc0), f_q), result, f_q) +result := addmod(mulmod(mload(0x720), mload(0x2d20), f_q), result, f_q) +mstore(14464, result) + } +mstore(0x38a0, mulmod(mload(0x3880), mload(0x3380), f_q)) +mstore(0x38c0, mulmod(sub(f_q, mload(0x38a0)), 1, f_q)) +{ + let result := mulmod(mload(0x740), mload(0x2c00), f_q) +result := addmod(mulmod(mload(0x760), mload(0x2c60), f_q), result, f_q) +result := addmod(mulmod(mload(0x780), mload(0x2cc0), f_q), result, f_q) +result := addmod(mulmod(mload(0x7a0), mload(0x2d20), f_q), result, f_q) +mstore(14560, result) + } +mstore(0x3900, mulmod(mload(0x38e0), mload(0x3380), f_q)) +mstore(0x3920, mulmod(sub(f_q, mload(0x3900)), mload(0xae0), f_q)) +mstore(0x3940, mulmod(1, mload(0xae0), f_q)) +mstore(0x3960, addmod(mload(0x38c0), mload(0x3920), f_q)) +mstore(0x3980, mulmod(mload(0x3960), 1, f_q)) +mstore(0x39a0, mulmod(mload(0x3940), 1, f_q)) +mstore(0x39c0, mulmod(1, mload(0x33a0), f_q)) +{ + let result := mulmod(mload(0x7c0), mload(0x2dc0), f_q) +mstore(14816, result) + } +mstore(0x3a00, mulmod(mload(0x39e0), mload(0x3600), f_q)) +mstore(0x3a20, mulmod(sub(f_q, mload(0x3a00)), 1, f_q)) +mstore(0x3a40, mulmod(mload(0x39c0), 1, f_q)) +{ + let result := mulmod(mload(0xaa0), mload(0x2dc0), f_q) +mstore(14944, result) + } +mstore(0x3a80, mulmod(mload(0x3a60), mload(0x3600), f_q)) +mstore(0x3aa0, mulmod(sub(f_q, mload(0x3a80)), mload(0xae0), f_q)) +mstore(0x3ac0, mulmod(mload(0x39c0), mload(0xae0), f_q)) +mstore(0x3ae0, addmod(mload(0x3a20), mload(0x3aa0), f_q)) +{ + let result := mulmod(mload(0x7e0), mload(0x2dc0), f_q) +mstore(15104, result) + } +mstore(0x3b20, mulmod(mload(0x3b00), mload(0x3600), f_q)) +mstore(0x3b40, mulmod(sub(f_q, mload(0x3b20)), mload(0x3680), f_q)) +mstore(0x3b60, mulmod(mload(0x39c0), mload(0x3680), f_q)) +mstore(0x3b80, addmod(mload(0x3ae0), mload(0x3b40), f_q)) +{ + let result := mulmod(mload(0x800), mload(0x2dc0), f_q) +mstore(15264, result) + } +mstore(0x3bc0, mulmod(mload(0x3ba0), mload(0x3600), f_q)) +mstore(0x3be0, mulmod(sub(f_q, mload(0x3bc0)), mload(0x36a0), f_q)) +mstore(0x3c00, mulmod(mload(0x39c0), mload(0x36a0), f_q)) +mstore(0x3c20, addmod(mload(0x3b80), mload(0x3be0), f_q)) +{ + let result := mulmod(mload(0x820), mload(0x2dc0), f_q) +mstore(15424, result) + } +mstore(0x3c60, mulmod(mload(0x3c40), mload(0x3600), f_q)) +mstore(0x3c80, mulmod(sub(f_q, mload(0x3c60)), mload(0x36c0), f_q)) +mstore(0x3ca0, mulmod(mload(0x39c0), mload(0x36c0), f_q)) +mstore(0x3cc0, addmod(mload(0x3c20), mload(0x3c80), f_q)) +{ + let result := mulmod(mload(0x840), mload(0x2dc0), f_q) +mstore(15584, result) + } +mstore(0x3d00, mulmod(mload(0x3ce0), mload(0x3600), f_q)) +mstore(0x3d20, mulmod(sub(f_q, mload(0x3d00)), mload(0x36e0), f_q)) +mstore(0x3d40, mulmod(mload(0x39c0), mload(0x36e0), f_q)) +mstore(0x3d60, addmod(mload(0x3cc0), mload(0x3d20), f_q)) +{ + let result := mulmod(mload(0x880), mload(0x2dc0), f_q) +mstore(15744, result) + } +mstore(0x3da0, mulmod(mload(0x3d80), mload(0x3600), f_q)) +mstore(0x3dc0, mulmod(sub(f_q, mload(0x3da0)), mload(0x3700), f_q)) +mstore(0x3de0, mulmod(mload(0x39c0), mload(0x3700), f_q)) +mstore(0x3e00, addmod(mload(0x3d60), mload(0x3dc0), f_q)) +{ + let result := mulmod(mload(0x8a0), mload(0x2dc0), f_q) +mstore(15904, result) + } +mstore(0x3e40, mulmod(mload(0x3e20), mload(0x3600), f_q)) +mstore(0x3e60, mulmod(sub(f_q, mload(0x3e40)), mload(0x3720), f_q)) +mstore(0x3e80, mulmod(mload(0x39c0), mload(0x3720), f_q)) +mstore(0x3ea0, addmod(mload(0x3e00), mload(0x3e60), f_q)) +{ + let result := mulmod(mload(0x8c0), mload(0x2dc0), f_q) +mstore(16064, result) + } +mstore(0x3ee0, mulmod(mload(0x3ec0), mload(0x3600), f_q)) +mstore(0x3f00, mulmod(sub(f_q, mload(0x3ee0)), mload(0x3740), f_q)) +mstore(0x3f20, mulmod(mload(0x39c0), mload(0x3740), f_q)) +mstore(0x3f40, addmod(mload(0x3ea0), mload(0x3f00), f_q)) +{ + let result := mulmod(mload(0x8e0), mload(0x2dc0), f_q) +mstore(16224, result) + } +mstore(0x3f80, mulmod(mload(0x3f60), mload(0x3600), f_q)) +mstore(0x3fa0, mulmod(sub(f_q, mload(0x3f80)), mload(0x3760), f_q)) +mstore(0x3fc0, mulmod(mload(0x39c0), mload(0x3760), f_q)) +mstore(0x3fe0, addmod(mload(0x3f40), mload(0x3fa0), f_q)) +{ + let result := mulmod(mload(0x900), mload(0x2dc0), f_q) +mstore(16384, result) + } +mstore(0x4020, mulmod(mload(0x4000), mload(0x3600), f_q)) +mstore(0x4040, mulmod(sub(f_q, mload(0x4020)), mload(0x3780), f_q)) +mstore(0x4060, mulmod(mload(0x39c0), mload(0x3780), f_q)) +mstore(0x4080, addmod(mload(0x3fe0), mload(0x4040), f_q)) +mstore(0x40a0, mulmod(mload(0x29a0), mload(0x33a0), f_q)) +mstore(0x40c0, mulmod(mload(0x29c0), mload(0x33a0), f_q)) +{ + let result := mulmod(mload(0x29e0), mload(0x2dc0), f_q) +mstore(16608, result) + } +mstore(0x4100, mulmod(mload(0x40e0), mload(0x3600), f_q)) +mstore(0x4120, mulmod(sub(f_q, mload(0x4100)), mload(0x37a0), f_q)) +mstore(0x4140, mulmod(mload(0x39c0), mload(0x37a0), f_q)) +mstore(0x4160, mulmod(mload(0x40a0), mload(0x37a0), f_q)) +mstore(0x4180, mulmod(mload(0x40c0), mload(0x37a0), f_q)) +mstore(0x41a0, addmod(mload(0x4080), mload(0x4120), f_q)) +{ + let result := mulmod(mload(0x860), mload(0x2dc0), f_q) +mstore(16832, result) + } +mstore(0x41e0, mulmod(mload(0x41c0), mload(0x3600), f_q)) +mstore(0x4200, mulmod(sub(f_q, mload(0x41e0)), mload(0x37c0), f_q)) +mstore(0x4220, mulmod(mload(0x39c0), mload(0x37c0), f_q)) +mstore(0x4240, addmod(mload(0x41a0), mload(0x4200), f_q)) +mstore(0x4260, mulmod(mload(0x4240), mload(0xb40), f_q)) +mstore(0x4280, mulmod(mload(0x3a40), mload(0xb40), f_q)) +mstore(0x42a0, mulmod(mload(0x3ac0), mload(0xb40), f_q)) +mstore(0x42c0, mulmod(mload(0x3b60), mload(0xb40), f_q)) +mstore(0x42e0, mulmod(mload(0x3c00), mload(0xb40), f_q)) +mstore(0x4300, mulmod(mload(0x3ca0), mload(0xb40), f_q)) +mstore(0x4320, mulmod(mload(0x3d40), mload(0xb40), f_q)) +mstore(0x4340, mulmod(mload(0x3de0), mload(0xb40), f_q)) +mstore(0x4360, mulmod(mload(0x3e80), mload(0xb40), f_q)) +mstore(0x4380, mulmod(mload(0x3f20), mload(0xb40), f_q)) +mstore(0x43a0, mulmod(mload(0x3fc0), mload(0xb40), f_q)) +mstore(0x43c0, mulmod(mload(0x4060), mload(0xb40), f_q)) +mstore(0x43e0, mulmod(mload(0x4140), mload(0xb40), f_q)) +mstore(0x4400, mulmod(mload(0x4160), mload(0xb40), f_q)) +mstore(0x4420, mulmod(mload(0x4180), mload(0xb40), f_q)) +mstore(0x4440, mulmod(mload(0x4220), mload(0xb40), f_q)) +mstore(0x4460, addmod(mload(0x3980), mload(0x4260), f_q)) +mstore(0x4480, mulmod(1, mload(0x33e0), f_q)) +{ + let result := mulmod(mload(0x920), mload(0x2e20), f_q) +result := addmod(mulmod(mload(0x940), mload(0x2e80), f_q), result, f_q) +result := addmod(mulmod(mload(0x960), mload(0x2ee0), f_q), result, f_q) +mstore(17568, result) + } +mstore(0x44c0, mulmod(mload(0x44a0), mload(0x3620), f_q)) +mstore(0x44e0, mulmod(sub(f_q, mload(0x44c0)), 1, f_q)) +mstore(0x4500, mulmod(mload(0x4480), 1, f_q)) +{ + let result := mulmod(mload(0x980), mload(0x2e20), f_q) +result := addmod(mulmod(mload(0x9a0), mload(0x2e80), f_q), result, f_q) +result := addmod(mulmod(mload(0x9c0), mload(0x2ee0), f_q), result, f_q) +mstore(17696, result) + } +mstore(0x4540, mulmod(mload(0x4520), mload(0x3620), f_q)) +mstore(0x4560, mulmod(sub(f_q, mload(0x4540)), mload(0xae0), f_q)) +mstore(0x4580, mulmod(mload(0x4480), mload(0xae0), f_q)) +mstore(0x45a0, addmod(mload(0x44e0), mload(0x4560), f_q)) +mstore(0x45c0, mulmod(mload(0x45a0), mload(0x3800), f_q)) +mstore(0x45e0, mulmod(mload(0x4500), mload(0x3800), f_q)) +mstore(0x4600, mulmod(mload(0x4580), mload(0x3800), f_q)) +mstore(0x4620, addmod(mload(0x4460), mload(0x45c0), f_q)) +mstore(0x4640, mulmod(1, mload(0x3420), f_q)) +{ + let result := mulmod(mload(0x9e0), mload(0x2f60), f_q) +result := addmod(mulmod(mload(0xa00), mload(0x2fc0), f_q), result, f_q) +mstore(18016, result) + } +mstore(0x4680, mulmod(mload(0x4660), mload(0x3640), f_q)) +mstore(0x46a0, mulmod(sub(f_q, mload(0x4680)), 1, f_q)) +mstore(0x46c0, mulmod(mload(0x4640), 1, f_q)) +{ + let result := mulmod(mload(0xa20), mload(0x2f60), f_q) +result := addmod(mulmod(mload(0xa40), mload(0x2fc0), f_q), result, f_q) +mstore(18144, result) + } +mstore(0x4700, mulmod(mload(0x46e0), mload(0x3640), f_q)) +mstore(0x4720, mulmod(sub(f_q, mload(0x4700)), mload(0xae0), f_q)) +mstore(0x4740, mulmod(mload(0x4640), mload(0xae0), f_q)) +mstore(0x4760, addmod(mload(0x46a0), mload(0x4720), f_q)) +mstore(0x4780, mulmod(mload(0x4760), mload(0x3820), f_q)) +mstore(0x47a0, mulmod(mload(0x46c0), mload(0x3820), f_q)) +mstore(0x47c0, mulmod(mload(0x4740), mload(0x3820), f_q)) +mstore(0x47e0, addmod(mload(0x4620), mload(0x4780), f_q)) +mstore(0x4800, mulmod(1, mload(0x3460), f_q)) +{ + let result := mulmod(mload(0xa60), mload(0x3020), f_q) +result := addmod(mulmod(mload(0xa80), mload(0x3080), f_q), result, f_q) +mstore(18464, result) + } +mstore(0x4840, mulmod(mload(0x4820), mload(0x3660), f_q)) +mstore(0x4860, mulmod(sub(f_q, mload(0x4840)), 1, f_q)) +mstore(0x4880, mulmod(mload(0x4800), 1, f_q)) +mstore(0x48a0, mulmod(mload(0x4860), mload(0x3840), f_q)) +mstore(0x48c0, mulmod(mload(0x4880), mload(0x3840), f_q)) +mstore(0x48e0, addmod(mload(0x47e0), mload(0x48a0), f_q)) +mstore(0x4900, mulmod(1, mload(0x2da0), f_q)) +mstore(0x4920, mulmod(1, mload(0xbe0), f_q)) +mstore(0x4940, 0x0000000000000000000000000000000000000000000000000000000000000001) + mstore(0x4960, 0x0000000000000000000000000000000000000000000000000000000000000002) +mstore(0x4980, mload(0x48e0)) +success := and(eq(staticcall(gas(), 0x7, 0x4940, 0x60, 0x4940, 0x40), 1), success) +mstore(0x49a0, mload(0x4940)) + mstore(0x49c0, mload(0x4960)) +mstore(0x49e0, mload(0x1a0)) + mstore(0x4a00, mload(0x1c0)) +success := and(eq(staticcall(gas(), 0x6, 0x49a0, 0x80, 0x49a0, 0x40), 1), success) +mstore(0x4a20, mload(0x1e0)) + mstore(0x4a40, mload(0x200)) +mstore(0x4a60, mload(0x39a0)) +success := and(eq(staticcall(gas(), 0x7, 0x4a20, 0x60, 0x4a20, 0x40), 1), success) +mstore(0x4a80, mload(0x49a0)) + mstore(0x4aa0, mload(0x49c0)) +mstore(0x4ac0, mload(0x4a20)) + mstore(0x4ae0, mload(0x4a40)) +success := and(eq(staticcall(gas(), 0x6, 0x4a80, 0x80, 0x4a80, 0x40), 1), success) +mstore(0x4b00, mload(0x220)) + mstore(0x4b20, mload(0x240)) +mstore(0x4b40, mload(0x4280)) +success := and(eq(staticcall(gas(), 0x7, 0x4b00, 0x60, 0x4b00, 0x40), 1), success) +mstore(0x4b60, mload(0x4a80)) + mstore(0x4b80, mload(0x4aa0)) +mstore(0x4ba0, mload(0x4b00)) + mstore(0x4bc0, mload(0x4b20)) +success := and(eq(staticcall(gas(), 0x6, 0x4b60, 0x80, 0x4b60, 0x40), 1), success) +mstore(0x4be0, mload(0x300)) + mstore(0x4c00, mload(0x320)) +mstore(0x4c20, mload(0x42a0)) +success := and(eq(staticcall(gas(), 0x7, 0x4be0, 0x60, 0x4be0, 0x40), 1), success) +mstore(0x4c40, mload(0x4b60)) + mstore(0x4c60, mload(0x4b80)) +mstore(0x4c80, mload(0x4be0)) + mstore(0x4ca0, mload(0x4c00)) +success := and(eq(staticcall(gas(), 0x6, 0x4c40, 0x80, 0x4c40, 0x40), 1), success) +mstore(0x4cc0, 0x0a56bb24bdbd10b2ae380a0f6d2926afe21e55ef423d664a7093bdc47d7a970d) + mstore(0x4ce0, 0x3046067f3fbc66fb8322b30a454654489ad23370423a63caeab071e10143c65d) +mstore(0x4d00, mload(0x42c0)) +success := and(eq(staticcall(gas(), 0x7, 0x4cc0, 0x60, 0x4cc0, 0x40), 1), success) +mstore(0x4d20, mload(0x4c40)) + mstore(0x4d40, mload(0x4c60)) +mstore(0x4d60, mload(0x4cc0)) + mstore(0x4d80, mload(0x4ce0)) +success := and(eq(staticcall(gas(), 0x6, 0x4d20, 0x80, 0x4d20, 0x40), 1), success) +mstore(0x4da0, 0x165f42d12623508ec09f1a14ffe3841e636362840bcfe45fd4f0847a50968d91) + mstore(0x4dc0, 0x19664a50abfa10f51e0eb96bf39b959cd2ebd0ed52c60bc0d4e8535272a13ed9) +mstore(0x4de0, mload(0x42e0)) +success := and(eq(staticcall(gas(), 0x7, 0x4da0, 0x60, 0x4da0, 0x40), 1), success) +mstore(0x4e00, mload(0x4d20)) + mstore(0x4e20, mload(0x4d40)) +mstore(0x4e40, mload(0x4da0)) + mstore(0x4e60, mload(0x4dc0)) +success := and(eq(staticcall(gas(), 0x6, 0x4e00, 0x80, 0x4e00, 0x40), 1), success) +mstore(0x4e80, 0x0c7dc4e4516515f0050b10e6891a60f39ab7645bbf2134080574b9ae26a3fee2) + mstore(0x4ea0, 0x2ee3484a156715514bcc56f95e316bb23031b7460f679ebb427a7f8ad73aead2) +mstore(0x4ec0, mload(0x4300)) +success := and(eq(staticcall(gas(), 0x7, 0x4e80, 0x60, 0x4e80, 0x40), 1), success) +mstore(0x4ee0, mload(0x4e00)) + mstore(0x4f00, mload(0x4e20)) +mstore(0x4f20, mload(0x4e80)) + mstore(0x4f40, mload(0x4ea0)) +success := and(eq(staticcall(gas(), 0x6, 0x4ee0, 0x80, 0x4ee0, 0x40), 1), success) +mstore(0x4f60, 0x2fb1bc5dc1a8a163ac673f110c7ced2581955a8d47770fced15a66d8199a5c91) + mstore(0x4f80, 0x18f7802cf51055b658637bf93f53fcc4dbe4218c03cb54c9d3e74534e1b9cb8f) +mstore(0x4fa0, mload(0x4320)) +success := and(eq(staticcall(gas(), 0x7, 0x4f60, 0x60, 0x4f60, 0x40), 1), success) +mstore(0x4fc0, mload(0x4ee0)) + mstore(0x4fe0, mload(0x4f00)) +mstore(0x5000, mload(0x4f60)) + mstore(0x5020, mload(0x4f80)) +success := and(eq(staticcall(gas(), 0x6, 0x4fc0, 0x80, 0x4fc0, 0x40), 1), success) +mstore(0x5040, 0x2051ae2329a00ab4684564c9db909204379c6404686b9bb004253a0e87937c1a) + mstore(0x5060, 0x2228d27991956786c7b7ed829027a4268a8f57d342e507683aaaea7046a1721b) +mstore(0x5080, mload(0x4340)) +success := and(eq(staticcall(gas(), 0x7, 0x5040, 0x60, 0x5040, 0x40), 1), success) +mstore(0x50a0, mload(0x4fc0)) + mstore(0x50c0, mload(0x4fe0)) +mstore(0x50e0, mload(0x5040)) + mstore(0x5100, mload(0x5060)) +success := and(eq(staticcall(gas(), 0x6, 0x50a0, 0x80, 0x50a0, 0x40), 1), success) +mstore(0x5120, 0x144e8b5cd066532d7a019f3061d407aeb922b3a37125d39cee71a336088a8d57) + mstore(0x5140, 0x0465b8271620ef2988dbb05e0e32f58c14ee4aaec69263d36175afc35b129dc8) +mstore(0x5160, mload(0x4360)) +success := and(eq(staticcall(gas(), 0x7, 0x5120, 0x60, 0x5120, 0x40), 1), success) +mstore(0x5180, mload(0x50a0)) + mstore(0x51a0, mload(0x50c0)) +mstore(0x51c0, mload(0x5120)) + mstore(0x51e0, mload(0x5140)) +success := and(eq(staticcall(gas(), 0x6, 0x5180, 0x80, 0x5180, 0x40), 1), success) +mstore(0x5200, 0x2c5cb6e96533664432584b4373fc01286cb8aa2c8a7f05eac98fc5bc689e65bf) + mstore(0x5220, 0x2c6a00e02442db4548c0583842d7c460dfaba5a8ae3d86ca57b0d7c82d7090c8) +mstore(0x5240, mload(0x4380)) +success := and(eq(staticcall(gas(), 0x7, 0x5200, 0x60, 0x5200, 0x40), 1), success) +mstore(0x5260, mload(0x5180)) + mstore(0x5280, mload(0x51a0)) +mstore(0x52a0, mload(0x5200)) + mstore(0x52c0, mload(0x5220)) +success := and(eq(staticcall(gas(), 0x6, 0x5260, 0x80, 0x5260, 0x40), 1), success) +mstore(0x52e0, 0x2b737ba20cacc46db1394cb36a1cb92566946b943314b7037d038fbb83f233a8) + mstore(0x5300, 0x04dc42ddd6eb4c1c43a1175bc70c5b5c92577f968055708b4beeb22cea5cff99) +mstore(0x5320, mload(0x43a0)) +success := and(eq(staticcall(gas(), 0x7, 0x52e0, 0x60, 0x52e0, 0x40), 1), success) +mstore(0x5340, mload(0x5260)) + mstore(0x5360, mload(0x5280)) +mstore(0x5380, mload(0x52e0)) + mstore(0x53a0, mload(0x5300)) +success := and(eq(staticcall(gas(), 0x6, 0x5340, 0x80, 0x5340, 0x40), 1), success) +mstore(0x53c0, 0x0ff2ba345398269313967c25483b19e5e2de27f76a3a4357408c35dc42ddfec3) + mstore(0x53e0, 0x063d5c171abc02e7d09324a4f1bb6493d0584767678901f452ebc7f285037015) +mstore(0x5400, mload(0x43c0)) +success := and(eq(staticcall(gas(), 0x7, 0x53c0, 0x60, 0x53c0, 0x40), 1), success) +mstore(0x5420, mload(0x5340)) + mstore(0x5440, mload(0x5360)) +mstore(0x5460, mload(0x53c0)) + mstore(0x5480, mload(0x53e0)) +success := and(eq(staticcall(gas(), 0x6, 0x5420, 0x80, 0x5420, 0x40), 1), success) +mstore(0x54a0, mload(0x5a0)) + mstore(0x54c0, mload(0x5c0)) +mstore(0x54e0, mload(0x43e0)) +success := and(eq(staticcall(gas(), 0x7, 0x54a0, 0x60, 0x54a0, 0x40), 1), success) +mstore(0x5500, mload(0x5420)) + mstore(0x5520, mload(0x5440)) +mstore(0x5540, mload(0x54a0)) + mstore(0x5560, mload(0x54c0)) +success := and(eq(staticcall(gas(), 0x6, 0x5500, 0x80, 0x5500, 0x40), 1), success) +mstore(0x5580, mload(0x5e0)) + mstore(0x55a0, mload(0x600)) +mstore(0x55c0, mload(0x4400)) +success := and(eq(staticcall(gas(), 0x7, 0x5580, 0x60, 0x5580, 0x40), 1), success) +mstore(0x55e0, mload(0x5500)) + mstore(0x5600, mload(0x5520)) +mstore(0x5620, mload(0x5580)) + mstore(0x5640, mload(0x55a0)) +success := and(eq(staticcall(gas(), 0x6, 0x55e0, 0x80, 0x55e0, 0x40), 1), success) +mstore(0x5660, mload(0x620)) + mstore(0x5680, mload(0x640)) +mstore(0x56a0, mload(0x4420)) +success := and(eq(staticcall(gas(), 0x7, 0x5660, 0x60, 0x5660, 0x40), 1), success) +mstore(0x56c0, mload(0x55e0)) + mstore(0x56e0, mload(0x5600)) +mstore(0x5700, mload(0x5660)) + mstore(0x5720, mload(0x5680)) +success := and(eq(staticcall(gas(), 0x6, 0x56c0, 0x80, 0x56c0, 0x40), 1), success) +mstore(0x5740, mload(0x500)) + mstore(0x5760, mload(0x520)) +mstore(0x5780, mload(0x4440)) +success := and(eq(staticcall(gas(), 0x7, 0x5740, 0x60, 0x5740, 0x40), 1), success) +mstore(0x57a0, mload(0x56c0)) + mstore(0x57c0, mload(0x56e0)) +mstore(0x57e0, mload(0x5740)) + mstore(0x5800, mload(0x5760)) +success := and(eq(staticcall(gas(), 0x6, 0x57a0, 0x80, 0x57a0, 0x40), 1), success) +mstore(0x5820, mload(0x400)) + mstore(0x5840, mload(0x420)) +mstore(0x5860, mload(0x45e0)) +success := and(eq(staticcall(gas(), 0x7, 0x5820, 0x60, 0x5820, 0x40), 1), success) +mstore(0x5880, mload(0x57a0)) + mstore(0x58a0, mload(0x57c0)) +mstore(0x58c0, mload(0x5820)) + mstore(0x58e0, mload(0x5840)) +success := and(eq(staticcall(gas(), 0x6, 0x5880, 0x80, 0x5880, 0x40), 1), success) +mstore(0x5900, mload(0x440)) + mstore(0x5920, mload(0x460)) +mstore(0x5940, mload(0x4600)) +success := and(eq(staticcall(gas(), 0x7, 0x5900, 0x60, 0x5900, 0x40), 1), success) +mstore(0x5960, mload(0x5880)) + mstore(0x5980, mload(0x58a0)) +mstore(0x59a0, mload(0x5900)) + mstore(0x59c0, mload(0x5920)) +success := and(eq(staticcall(gas(), 0x6, 0x5960, 0x80, 0x5960, 0x40), 1), success) +mstore(0x59e0, mload(0x480)) + mstore(0x5a00, mload(0x4a0)) +mstore(0x5a20, mload(0x47a0)) +success := and(eq(staticcall(gas(), 0x7, 0x59e0, 0x60, 0x59e0, 0x40), 1), success) +mstore(0x5a40, mload(0x5960)) + mstore(0x5a60, mload(0x5980)) +mstore(0x5a80, mload(0x59e0)) + mstore(0x5aa0, mload(0x5a00)) +success := and(eq(staticcall(gas(), 0x6, 0x5a40, 0x80, 0x5a40, 0x40), 1), success) +mstore(0x5ac0, mload(0x4c0)) + mstore(0x5ae0, mload(0x4e0)) +mstore(0x5b00, mload(0x47c0)) +success := and(eq(staticcall(gas(), 0x7, 0x5ac0, 0x60, 0x5ac0, 0x40), 1), success) +mstore(0x5b20, mload(0x5a40)) + mstore(0x5b40, mload(0x5a60)) +mstore(0x5b60, mload(0x5ac0)) + mstore(0x5b80, mload(0x5ae0)) +success := and(eq(staticcall(gas(), 0x6, 0x5b20, 0x80, 0x5b20, 0x40), 1), success) +mstore(0x5ba0, mload(0x2c0)) + mstore(0x5bc0, mload(0x2e0)) +mstore(0x5be0, mload(0x48c0)) +success := and(eq(staticcall(gas(), 0x7, 0x5ba0, 0x60, 0x5ba0, 0x40), 1), success) +mstore(0x5c00, mload(0x5b20)) + mstore(0x5c20, mload(0x5b40)) +mstore(0x5c40, mload(0x5ba0)) + mstore(0x5c60, mload(0x5bc0)) +success := and(eq(staticcall(gas(), 0x6, 0x5c00, 0x80, 0x5c00, 0x40), 1), success) +mstore(0x5c80, mload(0xb80)) + mstore(0x5ca0, mload(0xba0)) +mstore(0x5cc0, sub(f_q, mload(0x4900))) +success := and(eq(staticcall(gas(), 0x7, 0x5c80, 0x60, 0x5c80, 0x40), 1), success) +mstore(0x5ce0, mload(0x5c00)) + mstore(0x5d00, mload(0x5c20)) +mstore(0x5d20, mload(0x5c80)) + mstore(0x5d40, mload(0x5ca0)) +success := and(eq(staticcall(gas(), 0x6, 0x5ce0, 0x80, 0x5ce0, 0x40), 1), success) +mstore(0x5d60, mload(0xc20)) + mstore(0x5d80, mload(0xc40)) +mstore(0x5da0, mload(0x4920)) +success := and(eq(staticcall(gas(), 0x7, 0x5d60, 0x60, 0x5d60, 0x40), 1), success) +mstore(0x5dc0, mload(0x5ce0)) + mstore(0x5de0, mload(0x5d00)) +mstore(0x5e00, mload(0x5d60)) + mstore(0x5e20, mload(0x5d80)) +success := and(eq(staticcall(gas(), 0x6, 0x5dc0, 0x80, 0x5dc0, 0x40), 1), success) +mstore(0x5e40, mload(0x5dc0)) + mstore(0x5e60, mload(0x5de0)) +mstore(0x5e80, mload(0xc20)) + mstore(0x5ea0, mload(0xc40)) +mstore(0x5ec0, mload(0xc60)) + mstore(0x5ee0, mload(0xc80)) +mstore(0x5f00, mload(0xca0)) + mstore(0x5f20, mload(0xcc0)) +mstore(0x5f40, keccak256(0x5e40, 256)) +mstore(24416, mod(mload(24384), f_q)) +mstore(0x5f80, mulmod(mload(0x5f60), mload(0x5f60), f_q)) +mstore(0x5fa0, mulmod(1, mload(0x5f60), f_q)) +mstore(0x5fc0, mload(0x5ec0)) + mstore(0x5fe0, mload(0x5ee0)) +mstore(0x6000, mload(0x5fa0)) +success := and(eq(staticcall(gas(), 0x7, 0x5fc0, 0x60, 0x5fc0, 0x40), 1), success) +mstore(0x6020, mload(0x5e40)) + mstore(0x6040, mload(0x5e60)) +mstore(0x6060, mload(0x5fc0)) + mstore(0x6080, mload(0x5fe0)) +success := and(eq(staticcall(gas(), 0x6, 0x6020, 0x80, 0x6020, 0x40), 1), success) +mstore(0x60a0, mload(0x5f00)) + mstore(0x60c0, mload(0x5f20)) +mstore(0x60e0, mload(0x5fa0)) +success := and(eq(staticcall(gas(), 0x7, 0x60a0, 0x60, 0x60a0, 0x40), 1), success) +mstore(0x6100, mload(0x5e80)) + mstore(0x6120, mload(0x5ea0)) +mstore(0x6140, mload(0x60a0)) + mstore(0x6160, mload(0x60c0)) +success := and(eq(staticcall(gas(), 0x6, 0x6100, 0x80, 0x6100, 0x40), 1), success) +mstore(0x6180, mload(0x6020)) + mstore(0x61a0, mload(0x6040)) +mstore(0x61c0, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) + mstore(0x61e0, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) + mstore(0x6200, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) + mstore(0x6220, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) +mstore(0x6240, mload(0x6100)) + mstore(0x6260, mload(0x6120)) +mstore(0x6280, 0x138d5863615c12d3bd7d3fd007776d281a337f9d7f6dce23532100bb4bb5839d) + mstore(0x62a0, 0x0a3bb881671ee4e9238366e87f6598f0de356372ed3dc870766ec8ac005211e4) + mstore(0x62c0, 0x19c9d7d9c6e7ad2d9a0d5847ebdd2687c668939a202553ded2760d3eb8dbf559) + mstore(0x62e0, 0x198adb441818c42721c88c532ed13a5da1ebb78b85574d0b7326d8e6f4c1e25a) +success := and(eq(staticcall(gas(), 0x8, 0x6180, 0x180, 0x6180, 0x20), 1), success) +success := and(eq(mload(0x6180), 1), success) + + // Revert if anything fails + if iszero(success) { revert(0, 0) } + + // Return empty bytes on success + return(0, 0) + + } + } +} + \ No newline at end of file diff --git a/snark-verifier-sdk/examples/k_as_witness.rs b/snark-verifier-sdk/examples/k_as_witness.rs new file mode 100644 index 00000000..3561447f --- /dev/null +++ b/snark-verifier-sdk/examples/k_as_witness.rs @@ -0,0 +1,187 @@ +use halo2_base::gates::circuit::CircuitBuilderStage; +use halo2_base::halo2_proofs; +use halo2_base::halo2_proofs::arithmetic::Field; +use halo2_base::halo2_proofs::halo2curves::bn256::Fr; +use halo2_base::halo2_proofs::poly::commitment::Params; +use halo2_base::utils::fs::gen_srs; +use halo2_proofs::halo2curves as halo2_curves; + +use rand::rngs::StdRng; +use rand::SeedableRng; +use snark_verifier_sdk::halo2::aggregation::{AggregationConfigParams, VerifierUniversality}; +use snark_verifier_sdk::SHPLONK; +use snark_verifier_sdk::{ + gen_pk, + halo2::{aggregation::AggregationCircuit, gen_snark_shplonk}, + Snark, +}; + +mod application { + use super::halo2_curves::bn256::Fr; + use super::halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance}, + poly::Rotation, + }; + + use snark_verifier_sdk::CircuitExt; + + #[derive(Clone, Copy)] + pub struct StandardPlonkConfig { + a: Column, + b: Column, + c: Column, + q_a: Column, + q_b: Column, + q_c: Column, + q_ab: Column, + constant: Column, + #[allow(dead_code)] + instance: Column, + } + + impl StandardPlonkConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [a, b, c] = [(); 3].map(|_| meta.advice_column()); + let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); + let instance = meta.instance_column(); + + [a, b, c].map(|column| meta.enable_equality(column)); + + meta.create_gate( + "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", + |meta| { + let [a, b, c] = + [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); + let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] + .map(|column| meta.query_fixed(column, Rotation::cur())); + let instance = meta.query_instance(instance, Rotation::cur()); + Some( + q_a * a.clone() + + q_b * b.clone() + + q_c * c + + q_ab * a * b + + constant + + instance, + ) + }, + ); + + StandardPlonkConfig { a, b, c, q_a, q_b, q_c, q_ab, constant, instance } + } + } + + #[derive(Clone)] + pub struct StandardPlonk(pub Fr, pub usize); + + impl CircuitExt for StandardPlonk { + fn num_instance(&self) -> Vec { + vec![1] + } + + fn instances(&self) -> Vec> { + vec![vec![self.0]] + } + } + + impl Circuit for StandardPlonk { + type Config = StandardPlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self(Fr::zero(), self.1) + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + meta.set_minimum_degree(4); + StandardPlonkConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "", + |mut region| { + region.assign_advice(config.a, 0, Value::known(self.0)); + region.assign_fixed(config.q_a, 0, -Fr::one()); + region.assign_advice(config.a, 1, Value::known(-Fr::from(5u64))); + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed(column, 1, Fr::from(idx as u64)); + } + let a = region.assign_advice(config.a, 2, Value::known(Fr::one())); + a.copy_advice(&mut region, config.b, 3); + a.copy_advice(&mut region, config.c, 4); + + // assuming <= 10 blinding factors + // fill in most of circuit with a computation + let n = self.1; + for offset in 5..n - 10 { + region.assign_advice(config.a, offset, Value::known(-Fr::from(5u64))); + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed(column, offset, Fr::from(idx as u64)); + } + } + + Ok(()) + }, + ) + } + } +} + +fn gen_application_snark(k: u32) -> Snark { + let rng = StdRng::seed_from_u64(0); + let params = gen_srs(k); + let circuit = application::StandardPlonk(Fr::random(rng), params.n() as usize); + + let pk = gen_pk(¶ms, &circuit, None); + gen_snark_shplonk(¶ms, &pk, circuit, None::<&str>) +} + +fn main() { + let dummy_snark = gen_application_snark(8); + + let k = 15u32; + let params = gen_srs(k); + let lookup_bits = k as usize - 1; + let mut agg_circuit = AggregationCircuit::new::( + CircuitBuilderStage::Keygen, + AggregationConfigParams { degree: k, lookup_bits, ..Default::default() }, + ¶ms, + vec![dummy_snark], + VerifierUniversality::Full, + ); + let agg_config = agg_circuit.calculate_params(Some(10)); + + let pk = gen_pk(¶ms, &agg_circuit, None); + let break_points = agg_circuit.break_points(); + + let snarks = [8, 12, 15, 20].map(|k| (k, gen_application_snark(k))); + for (k, snark) in snarks { + let agg_circuit = AggregationCircuit::new::( + CircuitBuilderStage::Prover, + agg_config, + ¶ms, + vec![snark], + VerifierUniversality::Full, + ) + .use_break_points(break_points.clone()); + let _snark = gen_snark_shplonk(¶ms, &pk, agg_circuit, None::<&str>); + println!("snark with k = {k} success"); + } +} diff --git a/snark-verifier-sdk/examples/range_check.rs b/snark-verifier-sdk/examples/range_check.rs new file mode 100644 index 00000000..3d75c2cd --- /dev/null +++ b/snark-verifier-sdk/examples/range_check.rs @@ -0,0 +1,91 @@ +use ark_std::{end_timer, start_timer}; +use halo2_base::gates::circuit::builder::BaseCircuitBuilder; +use halo2_base::gates::circuit::{BaseCircuitParams, CircuitBuilderStage}; +use halo2_base::gates::{GateInstructions, RangeInstructions}; +use halo2_base::halo2_proofs::halo2curves::bn256::Fr; +use halo2_base::utils::fs::gen_srs; + +use itertools::Itertools; +use snark_verifier_sdk::halo2::aggregation::{AggregationConfigParams, VerifierUniversality}; +use snark_verifier_sdk::SHPLONK; +use snark_verifier_sdk::{ + gen_pk, + halo2::{aggregation::AggregationCircuit, gen_snark_shplonk}, + Snark, +}; + +fn generate_circuit(k: u32, fill: bool) -> Snark { + let lookup_bits = k as usize - 1; + let circuit_params = BaseCircuitParams { + k: k as usize, + num_advice_per_phase: vec![10], + num_lookup_advice_per_phase: vec![5], + num_fixed: 1, + lookup_bits: Some(lookup_bits), + num_instance_columns: 1, + }; + let mut builder = BaseCircuitBuilder::new(false).use_params(circuit_params); + let range = builder.range_chip(); + + let ctx = builder.main(0); + + let x = ctx.load_witness(Fr::from(14)); + if fill { + for _ in 0..2 << k { + range.gate().add(ctx, x, x); + } + } + + let params = gen_srs(k); + // do not call calculate_params, we want to use fixed params + let pk = gen_pk(¶ms, &builder, None); + // std::fs::remove_file(Path::new("examples/app.pk")).ok(); + // let _pk = gen_pk(¶ms, &builder, Some(Path::new("examples/app.pk"))); + // let pk = read_pk::>( + // Path::new("examples/app.pk"), + // builder.config_params.clone(), + // ) + // .unwrap(); + // std::fs::remove_file(Path::new("examples/app.pk")).ok(); + // builder now has break_point set + gen_snark_shplonk(¶ms, &pk, builder, None::<&str>) +} + +fn main() { + let dummy_snark = generate_circuit(9, false); + + let k = 16u32; + let lookup_bits = k as usize - 1; + let params = gen_srs(k); + let mut agg_circuit = AggregationCircuit::new::( + CircuitBuilderStage::Keygen, + AggregationConfigParams { degree: k, lookup_bits, ..Default::default() }, + ¶ms, + vec![dummy_snark], + VerifierUniversality::Full, + ); + let agg_config = agg_circuit.calculate_params(Some(10)); + + let start0 = start_timer!(|| "gen vk & pk"); + let pk = gen_pk(¶ms, &agg_circuit, None); + // std::fs::remove_file(Path::new("examples/agg.pk")).ok(); + // let _pk = gen_pk(¶ms, &agg_circuit, Some(Path::new("examples/agg.pk"))); + end_timer!(start0); + // let pk = read_pk::(Path::new("examples/agg.pk"), agg_config).unwrap(); + // std::fs::remove_file(Path::new("examples/agg.pk")).ok(); + let break_points = agg_circuit.break_points(); + + let snarks = (10..16).map(|k| generate_circuit(k, true)).collect_vec(); + for (i, snark) in snarks.into_iter().enumerate() { + let agg_circuit = AggregationCircuit::new::( + CircuitBuilderStage::Prover, + agg_config, + ¶ms, + vec![snark], + VerifierUniversality::Full, + ) + .use_break_points(break_points.clone()); + let _snark = gen_snark_shplonk(¶ms, &pk, agg_circuit, None::<&str>); + println!("snark {i} success"); + } +} diff --git a/snark-verifier-sdk/examples/standard_plonk.rs b/snark-verifier-sdk/examples/standard_plonk.rs new file mode 100644 index 00000000..7ad9c971 --- /dev/null +++ b/snark-verifier-sdk/examples/standard_plonk.rs @@ -0,0 +1,216 @@ +use std::path::Path; + +use halo2_base::gates::circuit::CircuitBuilderStage; +use halo2_base::halo2_proofs; +use halo2_base::utils::fs::gen_srs; +use halo2_proofs::halo2curves as halo2_curves; +use halo2_proofs::{halo2curves::bn256::Bn256, poly::kzg::commitment::ParamsKZG}; +use rand::rngs::OsRng; +use snark_verifier_sdk::evm::{evm_verify, gen_evm_proof_shplonk, gen_evm_verifier_shplonk}; +use snark_verifier_sdk::halo2::aggregation::{AggregationConfigParams, VerifierUniversality}; +use snark_verifier_sdk::{ + gen_pk, + halo2::{aggregation::AggregationCircuit, gen_snark_shplonk}, + Snark, +}; +use snark_verifier_sdk::{CircuitExt, SHPLONK}; + +mod application { + use super::halo2_curves::bn256::Fr; + use super::halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance}, + poly::Rotation, + }; + use rand::RngCore; + use snark_verifier_sdk::CircuitExt; + + #[derive(Clone, Copy)] + pub struct StandardPlonkConfig { + a: Column, + b: Column, + c: Column, + q_a: Column, + q_b: Column, + q_c: Column, + q_ab: Column, + constant: Column, + #[allow(dead_code)] + instance: Column, + } + + impl StandardPlonkConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [a, b, c] = [(); 3].map(|_| meta.advice_column()); + let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); + let instance = meta.instance_column(); + + [a, b, c].map(|column| meta.enable_equality(column)); + + meta.create_gate( + "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", + |meta| { + let [a, b, c] = + [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); + let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] + .map(|column| meta.query_fixed(column, Rotation::cur())); + let instance = meta.query_instance(instance, Rotation::cur()); + Some( + q_a * a.clone() + + q_b * b.clone() + + q_c * c + + q_ab * a * b + + constant + + instance, + ) + }, + ); + + StandardPlonkConfig { a, b, c, q_a, q_b, q_c, q_ab, constant, instance } + } + } + + #[derive(Clone, Default)] + pub struct StandardPlonk(Fr); + + impl StandardPlonk { + pub fn rand(mut rng: R) -> Self { + Self(Fr::from(rng.next_u32() as u64)) + } + } + + impl CircuitExt for StandardPlonk { + fn num_instance(&self) -> Vec { + vec![1] + } + + fn instances(&self) -> Vec> { + vec![vec![self.0]] + } + } + + impl Circuit for StandardPlonk { + type Config = StandardPlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + meta.set_minimum_degree(4); + StandardPlonkConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "", + |mut region| { + #[cfg(feature = "halo2-pse")] + { + region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; + region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fr::one()))?; + region.assign_advice( + || "", + config.a, + 1, + || Value::known(-Fr::from(5u64)), + )?; + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed( + || "", + column, + 1, + || Value::known(Fr::from(idx as u64)), + )?; + } + let a = + region.assign_advice(|| "", config.a, 2, || Value::known(Fr::one()))?; + a.copy_advice(|| "", &mut region, config.b, 3)?; + a.copy_advice(|| "", &mut region, config.c, 4)?; + } + #[cfg(feature = "halo2-axiom")] + { + region.assign_advice(config.a, 0, Value::known(self.0)); + region.assign_fixed(config.q_a, 0, -Fr::one()); + region.assign_advice(config.a, 1, Value::known(-Fr::from(5u64))); + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed(column, 1, Fr::from(idx as u64)); + } + + let a = region.assign_advice(config.a, 2, Value::known(Fr::one())); + a.copy_advice(&mut region, config.b, 3); + a.copy_advice(&mut region, config.c, 4); + } + + Ok(()) + }, + ) + } + } +} + +fn gen_application_snark(params: &ParamsKZG) -> Snark { + let circuit = application::StandardPlonk::rand(OsRng); + + let pk = gen_pk(params, &circuit, None); + gen_snark_shplonk(params, &pk, circuit, None::<&str>) +} + +fn main() { + let params_app = gen_srs(8); + + let k = 21u32; + let lookup_bits = k as usize - 1; + let params = gen_srs(k); + let snarks = [(); 1].map(|_| gen_application_snark(¶ms_app)); + + let mut agg_circuit = AggregationCircuit::new::( + CircuitBuilderStage::Keygen, + AggregationConfigParams { degree: k, lookup_bits, ..Default::default() }, + ¶ms, + snarks.clone(), + VerifierUniversality::Full, + ); + let agg_config = agg_circuit.calculate_params(Some(10)); + + let pk = gen_pk(¶ms, &agg_circuit, None); + let break_points = agg_circuit.break_points(); + drop(agg_circuit); + + let agg_circuit = AggregationCircuit::new::( + CircuitBuilderStage::Prover, + agg_config, + ¶ms, + snarks.clone(), + VerifierUniversality::Full, + ) + .use_break_points(break_points); + let num_instances = agg_circuit.num_instance(); + let instances = agg_circuit.instances(); + let proof = gen_evm_proof_shplonk(¶ms, &pk, agg_circuit, instances.clone()); + + let deployment_code = gen_evm_verifier_shplonk::( + ¶ms, + pk.get_vk(), + num_instances, + Some(Path::new("examples/StandardPlonkVerifier.sol")), + ); + evm_verify(deployment_code, instances, proof); +} diff --git a/snark-verifier-sdk/examples/vkey_as_witness.rs b/snark-verifier-sdk/examples/vkey_as_witness.rs new file mode 100644 index 00000000..ca066d80 --- /dev/null +++ b/snark-verifier-sdk/examples/vkey_as_witness.rs @@ -0,0 +1,183 @@ +use application::ComputeFlag; + +use halo2_base::gates::circuit::CircuitBuilderStage; +use halo2_base::halo2_proofs; +use halo2_base::halo2_proofs::arithmetic::Field; +use halo2_base::halo2_proofs::halo2curves::bn256::Fr; +use halo2_base::utils::fs::gen_srs; +use halo2_proofs::halo2curves as halo2_curves; + +use halo2_proofs::{halo2curves::bn256::Bn256, poly::kzg::commitment::ParamsKZG}; +use rand::rngs::OsRng; +use snark_verifier_sdk::halo2::aggregation::{AggregationConfigParams, VerifierUniversality}; +use snark_verifier_sdk::SHPLONK; +use snark_verifier_sdk::{ + gen_pk, + halo2::{aggregation::AggregationCircuit, gen_snark_shplonk}, + Snark, +}; + +mod application { + use super::halo2_curves::bn256::Fr; + use super::halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance}, + poly::Rotation, + }; + + use snark_verifier_sdk::CircuitExt; + + #[derive(Clone, Copy)] + pub struct StandardPlonkConfig { + a: Column, + b: Column, + c: Column, + q_a: Column, + q_b: Column, + q_c: Column, + q_ab: Column, + constant: Column, + #[allow(dead_code)] + instance: Column, + } + + impl StandardPlonkConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [a, b, c] = [(); 3].map(|_| meta.advice_column()); + let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); + let instance = meta.instance_column(); + + [a, b, c].map(|column| meta.enable_equality(column)); + + meta.create_gate( + "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", + |meta| { + let [a, b, c] = + [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); + let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] + .map(|column| meta.query_fixed(column, Rotation::cur())); + let instance = meta.query_instance(instance, Rotation::cur()); + Some( + q_a * a.clone() + + q_b * b.clone() + + q_c * c + + q_ab * a * b + + constant + + instance, + ) + }, + ); + + StandardPlonkConfig { a, b, c, q_a, q_b, q_c, q_ab, constant, instance } + } + } + + #[derive(Clone, Copy, PartialEq, Eq)] + pub enum ComputeFlag { + All, + SkipFixed, + SkipCopy, + } + + #[derive(Clone)] + pub struct StandardPlonk(pub Fr, pub ComputeFlag); + + impl CircuitExt for StandardPlonk { + fn num_instance(&self) -> Vec { + vec![1] + } + + fn instances(&self) -> Vec> { + vec![vec![self.0]] + } + } + + impl Circuit for StandardPlonk { + type Config = StandardPlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self(Fr::zero(), self.1) + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + meta.set_minimum_degree(4); + StandardPlonkConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "", + |mut region| { + region.assign_advice(config.a, 0, Value::known(self.0)); + region.assign_fixed(config.q_a, 0, -Fr::one()); + region.assign_advice(config.a, 1, Value::known(-Fr::from(5u64))); + if self.1 != ComputeFlag::SkipFixed { + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed(column, 1, Fr::from(idx as u64)); + } + } + let a = region.assign_advice(config.a, 2, Value::known(Fr::one())); + if self.1 != ComputeFlag::SkipCopy { + a.copy_advice(&mut region, config.b, 3); + a.copy_advice(&mut region, config.c, 4); + } + + Ok(()) + }, + ) + } + } +} + +fn gen_application_snark(params: &ParamsKZG, flag: ComputeFlag) -> Snark { + let circuit = application::StandardPlonk(Fr::random(OsRng), flag); + + let pk = gen_pk(params, &circuit, None); + gen_snark_shplonk(params, &pk, circuit, None::<&str>) +} + +fn main() { + let params_app = gen_srs(8); + let dummy_snark = gen_application_snark(¶ms_app, ComputeFlag::All); + + let k = 15u32; + let params = gen_srs(k); + let lookup_bits = k as usize - 1; + let mut agg_circuit = AggregationCircuit::new::( + CircuitBuilderStage::Keygen, + AggregationConfigParams { degree: k, lookup_bits, ..Default::default() }, + ¶ms, + vec![dummy_snark], + VerifierUniversality::PreprocessedAsWitness, + ); + let agg_config = agg_circuit.calculate_params(Some(10)); + + let pk = gen_pk(¶ms, &agg_circuit, None); + let break_points = agg_circuit.break_points(); + + let snarks = [ComputeFlag::All, ComputeFlag::SkipFixed, ComputeFlag::SkipCopy] + .map(|flag| gen_application_snark(¶ms_app, flag)); + for (i, snark) in snarks.into_iter().enumerate() { + let agg_circuit = AggregationCircuit::new::( + CircuitBuilderStage::Prover, + agg_config, + ¶ms, + vec![snark], + VerifierUniversality::PreprocessedAsWitness, + ) + .use_break_points(break_points.clone()); + let _snark = gen_snark_shplonk(¶ms, &pk, agg_circuit, None::<&str>); + println!("snark {i} success"); + } +} diff --git a/snark-verifier-sdk/src/evm.rs b/snark-verifier-sdk/src/evm.rs index f423d327..d3b745fb 100644 --- a/snark-verifier-sdk/src/evm.rs +++ b/snark-verifier-sdk/src/evm.rs @@ -3,7 +3,6 @@ use crate::{GWC, SHPLONK}; use super::{CircuitExt, PlonkVerifier}; #[cfg(feature = "display")] use ark_std::{end_timer, start_timer}; -use ethereum_types::Address; use halo2_base::halo2_proofs::{ halo2curves::bn256::{Bn256, Fq, Fr, G1Affine}, plonk::{create_proof, verify_proof, Circuit, ProvingKey, VerifyingKey}, @@ -23,7 +22,7 @@ use itertools::Itertools; use rand::{rngs::StdRng, SeedableRng}; pub use snark_verifier::loader::evm::encode_calldata; use snark_verifier::{ - loader::evm::{compile_yul, EvmLoader, ExecutorBuilder}, + loader::evm::{compile_solidity, deploy_and_call, EvmLoader}, pcs::{ kzg::{KzgAccumulator, KzgAsVerifyingKey, KzgDecidingKey, KzgSuccinctVerifyingKey}, AccumulationDecider, AccumulationScheme, PolynomialCommitmentScheme, @@ -148,11 +147,11 @@ where PlonkVerifier::::read_proof(&dk, &protocol, &instances, &mut transcript).unwrap(); PlonkVerifier::::verify(&dk, &protocol, &instances, &proof).unwrap(); - let yul_code = loader.yul_code(); - let byte_code = compile_yul(&yul_code); + let sol_code = loader.solidity_code(); + let byte_code = compile_solidity(&sol_code); if let Some(path) = path { path.parent().and_then(|dir| fs::create_dir_all(dir).ok()).unwrap(); - fs::write(path, yul_code).unwrap(); + fs::write(path, sol_code).unwrap(); } byte_code } @@ -177,18 +176,8 @@ pub fn gen_evm_verifier_shplonk>( pub fn evm_verify(deployment_code: Vec, instances: Vec>, proof: Vec) { let calldata = encode_calldata(&instances, &proof); - let success = { - let mut evm = ExecutorBuilder::default().with_gas_limit(u64::MAX.into()).build(); - - let caller = Address::from_low_u64_be(0xfe); - let verifier = evm.deploy(caller, deployment_code.into(), 0.into()).address.unwrap(); - let result = evm.call_raw(caller, verifier, calldata.into(), 0.into()); - - dbg!(result.gas_used); - - !result.reverted - }; - assert!(success); + let gas_cost = deploy_and_call(deployment_code, calldata).unwrap(); + dbg!(gas_cost); } pub fn write_calldata(instances: &[Vec], proof: &[u8], path: &Path) -> io::Result { diff --git a/snark-verifier-sdk/src/halo2.rs b/snark-verifier-sdk/src/halo2.rs index b0710230..aa5f8810 100644 --- a/snark-verifier-sdk/src/halo2.rs +++ b/snark-verifier-sdk/src/halo2.rs @@ -2,6 +2,7 @@ use super::{read_instances, write_instances, CircuitExt, PlonkSuccinctVerifier, #[cfg(feature = "display")] use ark_std::{end_timer, start_timer}; use halo2_base::halo2_proofs; +pub use halo2_base::poseidon::hasher::spec::OptimizedPoseidonSpec; use halo2_proofs::{ circuit::Layouter, halo2curves::{ @@ -26,7 +27,6 @@ use halo2_proofs::{ use itertools::Itertools; use lazy_static::lazy_static; use rand::{rngs::StdRng, SeedableRng}; -pub use snark_verifier::util::hash::OptimizedPoseidonSpec; use snark_verifier::{ cost::CostEstimation, loader::native::NativeLoader, @@ -35,6 +35,7 @@ use snark_verifier::{ AccumulationScheme, PolynomialCommitmentScheme, Query, }, system::halo2::{compile, Config}, + util::arithmetic::Rotation, util::transcript::TranscriptWrite, verifier::plonk::PlonkProof, }; @@ -122,20 +123,25 @@ where end_timer!(proof_time); // validate proof before caching - assert!({ - let mut transcript_read = - PoseidonTranscript::::from_spec(&proof[..], POSEIDON_SPEC.clone()); - VerificationStrategy::<_, V>::finalize( - verify_proof::<_, V, _, _, _>( - params.verifier_params(), - pk.get_vk(), - AccumulatorStrategy::new(params.verifier_params()), - &[instances.as_slice()], - &mut transcript_read, + assert!( + { + let mut transcript_read = PoseidonTranscript::::from_spec( + &proof[..], + POSEIDON_SPEC.clone(), + ); + VerificationStrategy::<_, V>::finalize( + verify_proof::<_, V, _, _, _>( + params.verifier_params(), + pk.get_vk(), + AccumulatorStrategy::new(params.verifier_params()), + &[instances.as_slice()], + &mut transcript_read, + ) + .unwrap(), ) - .unwrap(), - ) - }); + }, + "SNARK proof failed to verify" + ); if let Some((instance_path, proof_path)) = path { write_instances(&instances, instance_path); @@ -286,7 +292,7 @@ where NativeLoader, Accumulator = KzgAccumulator, VerifyingKey = KzgAsVerifyingKey, - > + CostEstimation>>, + > + CostEstimation>>, { struct CsProxy(PhantomData<(F, C)>); diff --git a/snark-verifier-sdk/src/halo2/aggregation.rs b/snark-verifier-sdk/src/halo2/aggregation.rs index b5dc148d..9488e2a9 100644 --- a/snark-verifier-sdk/src/halo2/aggregation.rs +++ b/snark-verifier-sdk/src/halo2/aggregation.rs @@ -1,22 +1,19 @@ use super::PlonkSuccinctVerifier; use crate::{BITS, LIMBS}; +use getset::Getters; use halo2_base::{ gates::{ - builder::{ - CircuitBuilderStage, FlexGateConfigParams, GateThreadBuilder, - MultiPhaseThreadBreakPoints, RangeCircuitBuilder, RangeWithInstanceCircuitBuilder, - RangeWithInstanceConfig, + circuit::{ + builder::BaseCircuitBuilder, BaseCircuitParams, BaseConfig, CircuitBuilderStage, }, + flex_gate::{threads::SinglePhaseCoreManager, MultiPhaseThreadBreakPoints}, RangeChip, }, halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, halo2curves::bn256::{Bn256, Fr, G1Affine}, plonk::{self, Circuit, ConstraintSystem, Selector}, - poly::{ - commitment::{Params, ParamsProver}, - kzg::commitment::ParamsKZG, - }, + poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG}, }, utils::ScalarField, AssignedValue, @@ -38,12 +35,7 @@ use snark_verifier::{ }, verifier::SnarkVerifier, }; -use std::{ - env::{set_var, var}, - fs::File, - path::Path, - rc::Rc, -}; +use std::{fs::File, mem, path::Path, rc::Rc}; use super::{CircuitExt, PoseidonTranscript, Snark, POSEIDON_SPEC}; @@ -51,6 +43,44 @@ pub type Svk = KzgSuccinctVerifyingKey; pub type BaseFieldEccChip<'chip> = halo2_ecc::ecc::BaseFieldEccChip<'chip, G1Affine>; pub type Halo2Loader<'chip> = loader::halo2::Halo2Loader>; +#[derive(Clone, Debug)] +pub struct PreprocessedAndDomainAsWitness { + // this is basically the vkey + pub preprocessed: Vec>, + pub k: AssignedValue, +} + +#[derive(Clone, Debug)] +pub struct SnarkAggregationWitness<'a> { + pub previous_instances: Vec>>, + pub accumulator: KzgAccumulator>>, + /// This returns the assigned `preprocessed` and `transcript_initial_state` values as a vector of assigned values, one for each aggregated snark. + /// These can then be exposed as public instances. + pub preprocessed: Vec, +} + +/// Different possible stages of universality the aggregation circuit can support +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] +pub enum VerifierUniversality { + /// Default: verifier is specific to a single circuit + #[default] + None, + /// Preprocessed digest (commitments to fixed columns) is loaded as witness + PreprocessedAsWitness, + /// Preprocessed as witness and log_2(number of rows in the circuit) = k loaded as witness + Full, +} + +impl VerifierUniversality { + pub fn preprocessed_as_witness(&self) -> bool { + self != &VerifierUniversality::None + } + + pub fn k_as_witness(&self) -> bool { + self == &VerifierUniversality::Full + } +} + #[allow(clippy::type_complexity)] /// Core function used in `synthesize` to aggregate multiple `snarks`. /// @@ -58,6 +88,9 @@ pub type Halo2Loader<'chip> = loader::halo2::Halo2Loader( @@ -65,7 +98,8 @@ pub fn aggregate<'a, AS>( loader: &Rc>, snarks: &[Snark], as_proof: &[u8], -) -> (Vec>>, KzgAccumulator>>) + universality: VerifierUniversality, +) -> SnarkAggregationWitness<'a> where AS: PolynomialCommitmentScheme< G1Affine, @@ -90,6 +124,7 @@ where }; let mut previous_instances = Vec::with_capacity(snarks.len()); + let mut preprocessed_witnesses = Vec::with_capacity(snarks.len()); // to avoid re-loading the spec each time, we create one transcript and clear the stream let mut transcript = PoseidonTranscript::>, &[u8]>::from_spec( loader, @@ -97,10 +132,41 @@ where POSEIDON_SPEC.clone(), ); + let preprocessed_as_witness = universality.preprocessed_as_witness(); let mut accumulators = snarks .iter() - .flat_map(|snark| { - let protocol = snark.protocol.loaded(loader); + .flat_map(|snark: &Snark| { + let protocol = if preprocessed_as_witness { + // always load `domain.n` as witness if vkey is witness + snark.protocol.loaded_preprocessed_as_witness(loader, universality.k_as_witness()) + } else { + snark.protocol.loaded(loader) + }; + let preprocessed = protocol + .preprocessed + .iter() + .flat_map(|preprocessed| { + let assigned = preprocessed.assigned(); + [assigned.x(), assigned.y()] + .into_iter() + .flat_map(|coordinate| coordinate.limbs().to_vec()) + .collect_vec() + }) + .chain( + protocol.transcript_initial_state.clone().map(|scalar| scalar.into_assigned()), + ) + .collect_vec(); + // Store `k` as witness. If `k` was fixed, assign it as a constant. + let k = protocol + .domain_as_witness + .as_ref() + .map(|domain| domain.k.clone().into_assigned()) + .unwrap_or_else(|| { + loader.ctx_mut().main().load_constant(Fr::from(protocol.domain.k as u64)) + }); + let preprocessed_and_k = PreprocessedAndDomainAsWitness { preprocessed, k }; + preprocessed_witnesses.push(preprocessed_and_k); + let instances = assign_instances(&snark.instances); // read the transcript and perform Fiat-Shamir @@ -138,12 +204,16 @@ where accumulators.pop().unwrap() }; - (previous_instances, accumulator) + SnarkAggregationWitness { + previous_instances, + accumulator, + preprocessed: preprocessed_witnesses, + } } /// Same as `FlexGateConfigParams` except we assume a single Phase and default 'Vertical' strategy. /// Also adds `lookup_bits` field. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Default, Debug, Serialize, Deserialize)] pub struct AggregationConfigParams { pub degree: u32, pub num_advice: usize, @@ -159,14 +229,67 @@ impl AggregationConfigParams { } } -#[derive(Clone, Debug)] +impl From for BaseCircuitParams { + fn from(params: AggregationConfigParams) -> Self { + BaseCircuitParams { + k: params.degree as usize, + num_advice_per_phase: vec![params.num_advice], + num_lookup_advice_per_phase: vec![params.num_lookup_advice], + num_fixed: params.num_fixed, + lookup_bits: Some(params.lookup_bits), + num_instance_columns: 1, + } + } +} + +impl TryFrom<&BaseCircuitParams> for AggregationConfigParams { + type Error = &'static str; + + fn try_from(params: &BaseCircuitParams) -> Result { + if params.num_advice_per_phase.iter().skip(1).any(|&n| n != 0) { + return Err("AggregationConfigParams only supports 1 phase"); + } + if params.num_lookup_advice_per_phase.iter().skip(1).any(|&n| n != 0) { + return Err("AggregationConfigParams only supports 1 phase"); + } + if params.lookup_bits.is_none() { + return Err("AggregationConfigParams requires lookup_bits"); + } + if params.num_instance_columns != 1 { + return Err("AggregationConfigParams only supports 1 instance column"); + } + Ok(Self { + degree: params.k as u32, + num_advice: params.num_advice_per_phase[0], + num_lookup_advice: params.num_lookup_advice_per_phase[0], + num_fixed: params.num_fixed, + lookup_bits: params.lookup_bits.unwrap(), + }) + } +} + +impl TryFrom for AggregationConfigParams { + type Error = &'static str; + + fn try_from(value: BaseCircuitParams) -> Result { + Self::try_from(&value) + } +} + +#[derive(Clone, Debug, Getters)] pub struct AggregationCircuit { - pub inner: RangeWithInstanceCircuitBuilder, + /// Circuit builder consisting of virtual region managers + pub builder: BaseCircuitBuilder, // the public instances from previous snarks that were aggregated, now collected as PRIVATE assigned values // the user can optionally append these to `inner.assigned_instances` to expose them - pub previous_instances: Vec>>, - // accumulation scheme proof, private input - pub as_proof: Vec, // not sure this needs to be stored, keeping for now + #[getset(get = "pub")] + previous_instances: Vec>>, + /// This returns the assigned `preprocessed_digest` (vkey), optional `transcript_initial_state`, `domain.n` (optional), and `omega` (optional) values as a vector of assigned values, one for each aggregated snark. + /// These can then be exposed as public instances. + #[getset(get = "pub")] + preprocessed: Vec, + // accumulation scheme proof, no longer used + // pub as_proof: Vec, } // trait just so we can have a generic that is either SHPLONK or GWC @@ -192,157 +315,153 @@ pub trait Halo2KzgAccumulationScheme<'a> = PolynomialCommitmentScheme< VerifyingKey = KzgAsVerifyingKey, > + AccumulationSchemeProver>; -impl AggregationCircuit { - /// Given snarks, this creates a circuit and runs the `GateThreadBuilder` to verify all the snarks. - /// By default, the returned circuit has public instances equal to the limbs of the pair of elliptic curve points, referred to as the `accumulator`, that need to be verified in a final pairing check. - /// - /// The user can optionally modify the circuit after calling this function to add more instances to `assigned_instances` to expose. - /// - /// Warning: will fail silently if `snarks` were created using a different multi-open scheme than `AS` - /// where `AS` can be either [`crate::SHPLONK`] or [`crate::GWC`] (for original PLONK multi-open scheme) - pub fn new( - stage: CircuitBuilderStage, - break_points: Option, - lookup_bits: usize, - params: &ParamsKZG, - snarks: impl IntoIterator, - ) -> Self - where - AS: for<'a> Halo2KzgAccumulationScheme<'a>, - { - let svk: Svk = params.get_g()[0].into(); - let snarks = snarks.into_iter().collect_vec(); +/// **Private** witnesses that form the output of [aggregate_snarks]. +/// Same as [SnarkAggregationWitness] except that we flatten `accumulator` into a vector of field elements. +#[derive(Clone, Debug)] +pub struct SnarkAggregationOutput { + pub previous_instances: Vec>>, + pub accumulator: Vec>, + /// This returns the assigned `preprocessed` and `transcript_initial_state` values as a vector of assigned values, one for each aggregated snark. + /// These can then be exposed as public instances. + pub preprocessed: Vec, +} - let mut transcript_read = - PoseidonTranscript::::from_spec(&[], POSEIDON_SPEC.clone()); - // TODO: the snarks can probably store these accumulators - let accumulators = snarks - .iter() - .flat_map(|snark| { - transcript_read.new_stream(snark.proof()); - let proof = PlonkSuccinctVerifier::::read_proof( - &svk, - &snark.protocol, - &snark.instances, - &mut transcript_read, - ) - .unwrap(); - PlonkSuccinctVerifier::::verify(&svk, &snark.protocol, &snark.instances, &proof) - .unwrap() - }) - .collect_vec(); +/// Given snarks, this populates the circuit builder with the virtual cells and constraints necessary to verify all the snarks. +/// +/// ## Notes +/// - This function does _not_ expose any public instances. +/// - `svk` is the generator of the KZG trusted setup, usually gotten via `params.get_g()[0]` +/// (avoids having to pass `params` into function just to get generator) +/// +/// ## Universality +/// - If `universality` is not `None`, then the verifying keys of each snark in `snarks` is loaded as a witness in the circuit. +/// - Moreover, if `universality` is `Full`, then the number of rows `n` of each snark in `snarks` is also loaded as a witness. In this case the generator `omega` of the order `n` multiplicative subgroup of `F` is also loaded as a witness. +/// - By default, these witnesses are _private_ and returned in `self.preprocessed_digests +/// - The user can optionally modify the circuit after calling this function to add more instances to `assigned_instances` to expose. +/// +/// ## Warning +/// Will fail silently if `snarks` were created using a different multi-open scheme than `AS` +/// where `AS` can be either [`crate::SHPLONK`] or [`crate::GWC`] (for original PLONK multi-open scheme) +/// +/// ## Assumptions +/// - `pool` and `range` reference the same `SharedCopyConstraintManager`. +pub fn aggregate_snarks( + pool: &mut SinglePhaseCoreManager, + range: &RangeChip, + svk: Svk, // gotten by params.get_g()[0].into() + snarks: impl IntoIterator, + universality: VerifierUniversality, +) -> SnarkAggregationOutput +where + AS: for<'a> Halo2KzgAccumulationScheme<'a>, +{ + let snarks = snarks.into_iter().collect_vec(); - let (_accumulator, as_proof) = { - let mut transcript_write = PoseidonTranscript::>::from_spec( - vec![], - POSEIDON_SPEC.clone(), - ); - let rng = StdRng::from_entropy(); - let accumulator = - AS::create_proof(&Default::default(), &accumulators, &mut transcript_write, rng) - .unwrap(); - (accumulator, transcript_write.finalize()) - }; - - // create thread builder and run aggregation witness gen - let builder = match stage { - CircuitBuilderStage::Mock => GateThreadBuilder::mock(), - CircuitBuilderStage::Prover => GateThreadBuilder::prover(), - CircuitBuilderStage::Keygen => GateThreadBuilder::keygen(), - }; - // create halo2loader - let range = RangeChip::::default(lookup_bits); - let fp_chip = FpChip::::new(&range, BITS, LIMBS); - let ecc_chip = BaseFieldEccChip::new(&fp_chip); - let loader = Halo2Loader::new(ecc_chip, builder); - - let (previous_instances, accumulator) = - aggregate::(&svk, &loader, &snarks, as_proof.as_slice()); - let lhs = accumulator.lhs.assigned(); - let rhs = accumulator.rhs.assigned(); - let assigned_instances = lhs - .x() - .limbs() - .iter() - .chain(lhs.y().limbs().iter()) - .chain(rhs.x().limbs().iter()) - .chain(rhs.y().limbs().iter()) - .copied() - .collect_vec(); - - #[cfg(debug_assertions)] - { - let KzgAccumulator { lhs, rhs } = _accumulator; - let instances = - [lhs.x, lhs.y, rhs.x, rhs.y].map(fe_to_limbs::<_, Fr, LIMBS, BITS>).concat(); - for (lhs, rhs) in instances.iter().zip(assigned_instances.iter()) { - assert_eq!(lhs, rhs.value()); - } - } + let mut transcript_read = + PoseidonTranscript::::from_spec(&[], POSEIDON_SPEC.clone()); + // TODO: the snarks can probably store these accumulators + let accumulators = snarks + .iter() + .flat_map(|snark| { + transcript_read.new_stream(snark.proof()); + let proof = PlonkSuccinctVerifier::::read_proof( + &svk, + &snark.protocol, + &snark.instances, + &mut transcript_read, + ) + .unwrap(); + PlonkSuccinctVerifier::::verify(&svk, &snark.protocol, &snark.instances, &proof) + .unwrap() + }) + .collect_vec(); - let builder = loader.take_ctx(); - let circuit = match stage { - CircuitBuilderStage::Mock => RangeCircuitBuilder::mock(builder), - CircuitBuilderStage::Keygen => RangeCircuitBuilder::keygen(builder), - CircuitBuilderStage::Prover => { - RangeCircuitBuilder::prover(builder, break_points.unwrap()) - } - }; - let inner = RangeWithInstanceCircuitBuilder::new(circuit, assigned_instances); - Self { inner, previous_instances, as_proof } - } + let (_accumulator, as_proof) = { + let mut transcript_write = + PoseidonTranscript::>::from_spec(vec![], POSEIDON_SPEC.clone()); + let rng = StdRng::from_entropy(); + let accumulator = + AS::create_proof(&Default::default(), &accumulators, &mut transcript_write, rng) + .unwrap(); + (accumulator, transcript_write.finalize()) + }; - pub fn public( - stage: CircuitBuilderStage, - break_points: Option, - lookup_bits: usize, - params: &ParamsKZG, - snarks: impl IntoIterator, - has_prev_accumulator: bool, - ) -> Self - where - AS: for<'a> Halo2KzgAccumulationScheme<'a>, - { - let mut private = Self::new::(stage, break_points, lookup_bits, params, snarks); - private.expose_previous_instances(has_prev_accumulator); - private - } + // create halo2loader + let fp_chip = FpChip::::new(range, BITS, LIMBS); + let ecc_chip = BaseFieldEccChip::new(&fp_chip); + // `pool` needs to be owned by loader. + // We put it back later (below), so it should have same effect as just mutating `pool`. + let tmp_pool = mem::take(pool); + // range_chip has shared reference to LookupAnyManager, with shared CopyConstraintManager + // pool has shared reference to CopyConstraintManager + let loader = Halo2Loader::new(ecc_chip, tmp_pool); + + // run witness and copy constraint generation + let SnarkAggregationWitness { previous_instances, accumulator, preprocessed } = + aggregate::(&svk, &loader, &snarks, as_proof.as_slice(), universality); + let lhs = accumulator.lhs.assigned(); + let rhs = accumulator.rhs.assigned(); + let accumulator = lhs + .x() + .limbs() + .iter() + .chain(lhs.y().limbs().iter()) + .chain(rhs.x().limbs().iter()) + .chain(rhs.y().limbs().iter()) + .copied() + .collect_vec(); - // this function is for convenience - /// `params` should be the universal trusted setup to be used for the aggregation circuit, not the one used to generate the previous snarks, although we assume both use the same generator g[0] - pub fn keygen(params: &ParamsKZG, snarks: impl IntoIterator) -> Self - where - AS: for<'a> Halo2KzgAccumulationScheme<'a>, + #[cfg(debug_assertions)] { - let lookup_bits = params.k() as usize - 1; // almost always we just use the max lookup bits possible, which is k - 1 because of blinding factors - let circuit = - Self::new::(CircuitBuilderStage::Keygen, None, lookup_bits, params, snarks); - circuit.config(params.k(), Some(10)); - set_var("LOOKUP_BITS", lookup_bits.to_string()); - circuit + let KzgAccumulator { lhs, rhs } = _accumulator; + let instances = + [lhs.x, lhs.y, rhs.x, rhs.y].map(fe_to_limbs::<_, Fr, LIMBS, BITS>).concat(); + for (lhs, rhs) in instances.iter().zip(accumulator.iter()) { + assert_eq!(lhs, rhs.value()); + } } + // put back `pool` into `builder` + *pool = loader.take_ctx(); + SnarkAggregationOutput { previous_instances, accumulator, preprocessed } +} - // this function is for convenience - pub fn prover( +impl AggregationCircuit { + /// Given snarks, this creates `BaseCircuitBuilder` and populates the circuit builder with the virtual cells and constraints necessary to verify all the snarks. + /// + /// By default, the returned circuit has public instances equal to the limbs of the pair of elliptic curve points, referred to as the `accumulator`, that need to be verified in a final pairing check. + /// + /// # Universality + /// - If `universality` is not `None`, then the verifying keys of each snark in `snarks` is loaded as a witness in the circuit. + /// - Moreover, if `universality` is `Full`, then the number of rows `n` of each snark in `snarks` is also loaded as a witness. In this case the generator `omega` of the order `n` multiplicative subgroup of `F` is also loaded as a witness. + /// - By default, these witnesses are _private_ and returned in `self.preprocessed_digests + /// - The user can optionally modify the circuit after calling this function to add more instances to `assigned_instances` to expose. + /// + /// # Warning + /// Will fail silently if `snarks` were created using a different multi-open scheme than `AS` + /// where `AS` can be either [`crate::SHPLONK`] or [`crate::GWC`] (for original PLONK multi-open scheme) + pub fn new( + stage: CircuitBuilderStage, + config_params: AggregationConfigParams, params: &ParamsKZG, snarks: impl IntoIterator, - break_points: MultiPhaseThreadBreakPoints, + universality: VerifierUniversality, ) -> Self where AS: for<'a> Halo2KzgAccumulationScheme<'a>, { - let lookup_bits: usize = var("LOOKUP_BITS").expect("LOOKUP_BITS not set").parse().unwrap(); - let circuit = Self::new::( - CircuitBuilderStage::Prover, - Some(break_points), - lookup_bits, - params, - snarks, + let svk: Svk = params.get_g()[0].into(); + let mut builder = BaseCircuitBuilder::from_stage(stage).use_params(config_params.into()); + let range = builder.range_chip(); + let SnarkAggregationOutput { previous_instances, accumulator, preprocessed } = + aggregate_snarks::(builder.pool(0), &range, svk, snarks, universality); + assert_eq!( + builder.assigned_instances.len(), + 1, + "AggregationCircuit must have exactly 1 instance column" ); - let minimum_rows = var("MINIMUM_ROWS").map(|s| s.parse().unwrap_or(10)).unwrap_or(10); - circuit.config(params.k(), Some(minimum_rows)); - set_var("LOOKUP_BITS", lookup_bits.to_string()); - circuit + // expose accumulator as public instances + builder.assigned_instances[0] = accumulator; + Self { builder, previous_instances, preprocessed } } /// Re-expose the previous public instances of aggregated snarks again. @@ -351,55 +470,87 @@ impl AggregationCircuit { pub fn expose_previous_instances(&mut self, has_prev_accumulator: bool) { let start = (has_prev_accumulator as usize) * 4 * LIMBS; for prev in self.previous_instances.iter() { - self.inner.assigned_instances.extend_from_slice(&prev[start..]); + self.builder.assigned_instances[0].extend_from_slice(&prev[start..]); } } - pub fn as_proof(&self) -> &[u8] { - &self.as_proof[..] + /// The log_2 size of the lookup table + pub fn lookup_bits(&self) -> usize { + self.builder.config_params.lookup_bits.unwrap() + } + + /// Set config params + pub fn set_params(&mut self, params: AggregationConfigParams) { + self.builder.set_params(params.into()); } - pub fn config(&self, k: u32, minimum_rows: Option) -> FlexGateConfigParams { - self.inner.config(k, minimum_rows) + /// Returns new with config params + pub fn use_params(mut self, params: AggregationConfigParams) -> Self { + self.set_params(params); + self } + /// The break points of the circuit. pub fn break_points(&self) -> MultiPhaseThreadBreakPoints { - self.inner.break_points() + self.builder.break_points() } - pub fn instance_count(&self) -> usize { - self.inner.instance_count() + /// Sets the break points of the circuit. + pub fn set_break_points(&mut self, break_points: MultiPhaseThreadBreakPoints) { + self.builder.set_break_points(break_points); } - pub fn instance(&self) -> Vec { - self.inner.instance() + /// Returns new with break points + pub fn use_break_points(mut self, break_points: MultiPhaseThreadBreakPoints) -> Self { + self.set_break_points(break_points); + self + } + + /// Auto-configure the circuit and change the circuit's internal configuration parameters. + pub fn calculate_params(&mut self, minimum_rows: Option) -> AggregationConfigParams { + self.builder.calculate_params(minimum_rows).try_into().unwrap() } } -impl CircuitExt for RangeWithInstanceCircuitBuilder { +impl CircuitExt for BaseCircuitBuilder { fn num_instance(&self) -> Vec { - vec![self.instance_count()] + self.assigned_instances.iter().map(|instances| instances.len()).collect() } fn instances(&self) -> Vec> { - vec![self.instance()] + self.assigned_instances + .iter() + .map(|instances| instances.iter().map(|v| *v.value()).collect()) + .collect() } fn selectors(config: &Self::Config) -> Vec { - config.range.gate.basic_gates[0].iter().map(|gate| gate.q_enable).collect() + config.gate().basic_gates[0].iter().map(|gate| gate.q_enable).collect() } } impl Circuit for AggregationCircuit { - type Config = RangeWithInstanceConfig; + type Config = BaseConfig; type FloorPlanner = SimpleFloorPlanner; + type Params = AggregationConfigParams; + + fn params(&self) -> Self::Params { + (&self.builder.config_params).try_into().unwrap() + } fn without_witnesses(&self) -> Self { unimplemented!() } - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - RangeWithInstanceCircuitBuilder::configure(meta) + fn configure_with_params( + meta: &mut ConstraintSystem, + params: Self::Params, + ) -> Self::Config { + BaseCircuitBuilder::configure_with_params(meta, params.into()) + } + + fn configure(_: &mut ConstraintSystem) -> Self::Config { + unreachable!() } fn synthesize( @@ -407,17 +558,17 @@ impl Circuit for AggregationCircuit { config: Self::Config, layouter: impl Layouter, ) -> Result<(), plonk::Error> { - self.inner.synthesize(config, layouter) + self.builder.synthesize(config, layouter) } } impl CircuitExt for AggregationCircuit { fn num_instance(&self) -> Vec { - self.inner.num_instance() + self.builder.num_instance() } fn instances(&self) -> Vec> { - self.inner.instances() + self.builder.instances() } fn accumulator_indices() -> Option> { @@ -425,7 +576,7 @@ impl CircuitExt for AggregationCircuit { } fn selectors(config: &Self::Config) -> Vec { - RangeWithInstanceCircuitBuilder::selectors(config) + BaseCircuitBuilder::selectors(config) } } diff --git a/snark-verifier-sdk/src/lib.rs b/snark-verifier-sdk/src/lib.rs index 9a5833d6..40401a4c 100644 --- a/snark-verifier-sdk/src/lib.rs +++ b/snark-verifier-sdk/src/lib.rs @@ -33,6 +33,8 @@ pub mod halo2; pub const LIMBS: usize = 3; pub const BITS: usize = 88; +const BUFFER_SIZE: usize = 1024 * 1024; // 1MB + /// AS stands for accumulation scheme. /// AS can be either `Kzg` (the original PLONK KZG multi-open) or `Kzg` (SHPLONK) pub type PlonkVerifier = verifier::plonk::PlonkVerifier>; @@ -76,19 +78,28 @@ pub trait CircuitExt: Circuit { } } -pub fn read_pk>(path: &Path) -> io::Result> { - let f = File::open(path)?; +pub fn read_pk>(path: &Path, params: C::Params) -> io::Result> { + read_pk_with_capacity::(BUFFER_SIZE, path, params) +} + +pub fn read_pk_with_capacity>( + capacity: usize, + path: impl AsRef, + params: C::Params, +) -> io::Result> { + let f = File::open(path.as_ref())?; #[cfg(feature = "display")] - let read_time = start_timer!(|| format!("Reading pkey from {path:?}")); + let read_time = start_timer!(|| format!("Reading pkey from {:?}", path.as_ref())); // BufReader is indeed MUCH faster than Read - let mut bufreader = BufReader::new(f); + let mut bufreader = BufReader::with_capacity(capacity, f); // But it's even faster to load the whole file into memory first and then process, // HOWEVER this requires twice as much memory to initialize // let initial_buffer_size = f.metadata().map(|m| m.len() as usize + 1).unwrap_or(0); // let mut bufreader = Vec::with_capacity(initial_buffer_size); // f.read_to_end(&mut bufreader)?; - let pk = ProvingKey::read::<_, C>(&mut bufreader, SerdeFormat::RawBytesUnchecked).unwrap(); + let pk = + ProvingKey::read::<_, C>(&mut bufreader, SerdeFormat::RawBytesUnchecked, params).unwrap(); #[cfg(feature = "display")] end_timer!(read_time); @@ -103,7 +114,7 @@ pub fn gen_pk>( path: Option<&Path>, ) -> ProvingKey { if let Some(path) = path { - if let Ok(pk) = read_pk::(path) { + if let Ok(pk) = read_pk::(path, circuit.params()) { return pk; } } @@ -121,7 +132,7 @@ pub fn gen_pk>( let write_time = start_timer!(|| format!("Writing pkey to {path:?}")); path.parent().and_then(|dir| fs::create_dir_all(dir).ok()).unwrap(); - let mut f = BufWriter::new(File::create(path).unwrap()); + let mut f = BufWriter::with_capacity(BUFFER_SIZE, File::create(path).unwrap()); pk.write(&mut f, SerdeFormat::RawBytesUnchecked).unwrap(); #[cfg(feature = "display")] diff --git a/snark-verifier/Cargo.toml b/snark-verifier/Cargo.toml index ce05247b..72894ebf 100644 --- a/snark-verifier/Cargo.toml +++ b/snark-verifier/Cargo.toml @@ -1,32 +1,28 @@ [package] name = "snark-verifier" -version = "0.1.1" +version = "0.1.6" edition = "2021" [dependencies] -itertools = "0.10.5" -lazy_static = "1.4.0" -num-bigint = "0.4.3" +itertools = "0.11" +lazy_static = "1.4" +num-bigint = "0.4" num-integer = "0.1.45" num-traits = "0.2.15" hex = "0.4" rand = "0.8" serde = { version = "1.0", features = ["derive"] } +pairing = { version = "0.23" } # Use halo2-base as non-optional dependency because it re-exports halo2_proofs, halo2curves, and poseidon, using different repos based on feature flag "halo2-axiom" or "halo2-pse" halo2-base = { git = "https://github.com/axiom-crypto/halo2-lib.git", branch = "community-edition", default-features = false } -# This is Scroll's audited poseidon circuit. We only use it for the Native Poseidon spec. We do not use the halo2 circuit at all (and it wouldn't even work because the halo2_proofs tag is not compatbile). -poseidon-circuit = { git = "https://github.com/scroll-tech/poseidon-circuit.git", rev = "50015b7" } # parallel -rayon = { version = "1.7.0", optional = true } +rayon = { version = "1.7", optional = true } # loader_evm sha3 = { version = "0.10", optional = true } -bytes = { version = "1.4.0", default-features = false, optional = true } -primitive-types = { version = "0.12.1", default-features = false, features = ["std"], optional = true } -rlp = { version = "0.5.2", default-features = false, features = ["std"], optional = true } -revm = { version = "= 2.3.1", optional = true } +revm = { version = "3.3.0", optional = true } # loader_halo2 halo2-ecc = { git = "https://github.com/axiom-crypto/halo2-lib.git", branch = "community-edition", default-features = false, optional = true } @@ -44,7 +40,7 @@ tui = { version = "0.19", default-features = false, features = ["crossterm"] } [features] default = ["loader_evm", "loader_halo2", "halo2-axiom", "display"] display = ["halo2-base/display", "halo2-ecc?/display"] -loader_evm = ["dep:primitive-types", "dep:sha3", "dep:revm", "dep:bytes", "dep:rlp"] +loader_evm = ["dep:sha3", "dep:revm"] loader_halo2 = ["halo2-ecc"] parallel = ["dep:rayon"] # EXACTLY one of halo2-pse / halo2-axiom should always be turned on; not sure how to enforce this with Cargo diff --git a/snark-verifier/examples/evm-verifier-with-accumulator.rs b/snark-verifier/examples/evm-verifier-with-accumulator.rs index dd537880..304b7395 100644 --- a/snark-verifier/examples/evm-verifier-with-accumulator.rs +++ b/snark-verifier/examples/evm-verifier-with-accumulator.rs @@ -1,5 +1,9 @@ use aggregation::{AggregationCircuit, AggregationConfigParams}; -use halo2_base::{gates::builder::CircuitBuilderStage, halo2_proofs, utils::fs::gen_srs}; +use halo2_base::{ + gates::circuit::{BaseCircuitParams, CircuitBuilderStage}, + halo2_proofs, + utils::fs::gen_srs, +}; use halo2_proofs::{ dev::MockProver, halo2curves::bn256::{Bn256, Fq, Fr, G1Affine}, @@ -19,14 +23,14 @@ use itertools::Itertools; use rand::rngs::OsRng; use snark_verifier::{ loader::{ - evm::{self, encode_calldata, Address, EvmLoader, ExecutorBuilder}, + evm::{self, deploy_and_call, encode_calldata, EvmLoader}, native::NativeLoader, }, pcs::kzg::{Gwc19, KzgAs, LimbsEncoding}, system::halo2::{compile, transcript::evm::EvmTranscript, Config}, verifier::{self, SnarkVerifier}, }; -use std::{env::set_var, fs::File, io::Cursor, rc::Rc}; +use std::{fs::File, io::Cursor, rc::Rc}; const LIMBS: usize = 3; const BITS: usize = 88; @@ -198,22 +202,11 @@ mod application { mod aggregation { use crate::PlonkSuccinctVerifier; - use super::halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner}, - plonk::{self, Circuit, Column, Instance}, - }; use super::{As, BITS, LIMBS}; use super::{Fr, G1Affine}; - use halo2_base::{ - gates::{ - builder::{ - assign_threads_in, CircuitBuilderStage, FlexGateConfigParams, GateThreadBuilder, - MultiPhaseThreadBreakPoints, RangeCircuitBuilder, - }, - range::RangeConfig, - RangeChip, - }, - AssignedValue, SKIP_FIRST_PASS, + use halo2_base::gates::{ + circuit::{builder::BaseCircuitBuilder, BaseCircuitParams, CircuitBuilderStage}, + flex_gate::MultiPhaseThreadBreakPoints, }; use halo2_ecc::bn254::FpChip; use itertools::Itertools; @@ -228,7 +221,7 @@ mod aggregation { util::arithmetic::fe_to_limbs, verifier::{plonk::PlonkProtocol, SnarkVerifier}, }; - use std::{collections::HashMap, rc::Rc}; + use std::{mem, rc::Rc}; const T: usize = 3; const RATE: usize = 2; @@ -300,7 +293,7 @@ mod aggregation { As::verify(&Default::default(), &accumulators, &proof).unwrap() } - #[derive(serde::Serialize, serde::Deserialize)] + #[derive(serde::Serialize, serde::Deserialize, Default)] pub struct AggregationConfigParams { pub degree: u32, pub num_advice: usize, @@ -309,24 +302,17 @@ mod aggregation { pub lookup_bits: usize, } - #[derive(Clone)] - pub struct AggregationConfig { - pub range: RangeConfig, - pub instance: Column, - } - #[derive(Clone, Debug)] pub struct AggregationCircuit { - pub circuit: RangeCircuitBuilder, + pub inner: BaseCircuitBuilder, pub as_proof: Vec, - pub assigned_instances: Vec>, } impl AggregationCircuit { pub fn new( stage: CircuitBuilderStage, + circuit_params: BaseCircuitParams, break_points: Option, - lookup_bits: usize, params_g0: G1Affine, snarks: impl IntoIterator, ) -> Self { @@ -361,18 +347,15 @@ mod aggregation { (accumulator, transcript.finalize()) }; - // create thread builder and run aggregation witness gen - let builder = match stage { - CircuitBuilderStage::Mock => GateThreadBuilder::mock(), - CircuitBuilderStage::Prover => GateThreadBuilder::prover(), - CircuitBuilderStage::Keygen => GateThreadBuilder::keygen(), - }; + let mut builder = BaseCircuitBuilder::from_stage(stage).use_params(circuit_params); // create halo2loader - let range = RangeChip::::default(lookup_bits); + let range = builder.range_chip(); let fp_chip = FpChip::::new(&range, BITS, LIMBS); let ecc_chip = BaseFieldEccChip::new(&fp_chip); - let loader = Halo2Loader::new(ecc_chip, builder); + let pool = mem::take(builder.pool(0)); + let loader = Halo2Loader::new(ecc_chip, pool); + // witness generation let KzgAccumulator { lhs, rhs } = aggregate(&svk, &loader, &snarks, as_proof.as_slice()); let lhs = lhs.assigned(); @@ -397,23 +380,12 @@ mod aggregation { } } - let builder = loader.take_ctx(); - let circuit = match stage { - CircuitBuilderStage::Mock => RangeCircuitBuilder::mock(builder), - CircuitBuilderStage::Keygen => RangeCircuitBuilder::keygen(builder), - CircuitBuilderStage::Prover => { - RangeCircuitBuilder::prover(builder, break_points.unwrap()) - } - }; - Self { circuit, as_proof, assigned_instances } - } - - pub fn config(&self, k: u32, minimum_rows: Option) -> FlexGateConfigParams { - self.circuit.0.builder.borrow().config(k as usize, minimum_rows) - } - - pub fn break_points(&self) -> MultiPhaseThreadBreakPoints { - self.circuit.0.break_points.borrow().clone() + *builder.pool(0) = loader.take_ctx(); + builder.assigned_instances[0] = assigned_instances; + if let Some(break_points) = break_points { + builder.set_break_points(break_points); + } + Self { inner: builder, as_proof } } pub fn num_instance() -> Vec { @@ -422,108 +394,17 @@ mod aggregation { } pub fn instances(&self) -> Vec> { - vec![self.assigned_instances.iter().map(|v| *v.value()).collect_vec()] + self.inner + .assigned_instances + .iter() + .map(|v| v.iter().map(|v| *v.value()).collect_vec()) + .collect() } pub fn accumulator_indices() -> Vec<(usize, usize)> { (0..4 * LIMBS).map(|idx| (0, idx)).collect() } } - - impl Circuit for AggregationCircuit { - type Config = AggregationConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - unimplemented!() - } - - fn configure(meta: &mut plonk::ConstraintSystem) -> Self::Config { - let range = RangeCircuitBuilder::configure(meta); - let instance = meta.instance_column(); - meta.enable_equality(instance); - AggregationConfig { range, instance } - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), plonk::Error> { - // copied from RangeCircuitBuilder::synthesize but with extra logic to expose public instances - let range = config.range; - let circuit = &self.circuit.0; - range.load_lookup_table(&mut layouter).expect("load lookup table should not fail"); - - // we later `take` the builder, so we need to save this value - let witness_gen_only = circuit.builder.borrow().witness_gen_only(); - let mut assigned_advices = HashMap::new(); - - let mut first_pass = SKIP_FIRST_PASS; - layouter - .assign_region( - || "AggregationCircuit", - |mut region| { - if first_pass { - first_pass = false; - return Ok(()); - } - // only support FirstPhase in this Builder because getting challenge value requires more specialized witness generation during synthesize - if !witness_gen_only { - // clone the builder so we can re-use the circuit for both vk and pk gen - let builder = circuit.builder.borrow(); - let assignments = builder.assign_all( - &range.gate, - &range.lookup_advice, - &range.q_lookup, - &mut region, - Default::default(), - ); - *circuit.break_points.borrow_mut() = assignments.break_points; - assigned_advices = assignments.assigned_advices; - } else { - #[cfg(feature = "display")] - let start0 = std::time::Instant::now(); - let builder = circuit.builder.take(); - let break_points = circuit.break_points.take(); - for (phase, (threads, break_points)) in builder - .threads - .into_iter() - .zip(break_points.into_iter()) - .enumerate() - .take(1) - { - assign_threads_in( - phase, - threads, - &range.gate, - &range.lookup_advice[phase], - &mut region, - break_points, - ); - } - #[cfg(feature = "display")] - println!("assign threads in {:?}", start0.elapsed()); - } - Ok(()) - }, - ) - .unwrap(); - - if !witness_gen_only { - // expose public instances - let mut layouter = layouter.namespace(|| "expose"); - for (i, instance) in self.assigned_instances.iter().enumerate() { - let cell = instance.cell.unwrap(); - let (cell, _) = assigned_advices - .get(&(cell.context_id, cell.offset)) - .expect("instance not assigned"); - layouter.constrain_instance(*cell, config.instance, i); - } - } - Ok(()) - } - } } fn gen_pk>(params: &ParamsKZG, circuit: &C) -> ProvingKey { @@ -620,23 +501,13 @@ fn gen_aggregation_evm_verifier( let proof = PlonkVerifier::read_proof(&vk, &protocol, &instances, &mut transcript).unwrap(); PlonkVerifier::verify(&vk, &protocol, &instances, &proof).unwrap(); - evm::compile_yul(&loader.yul_code()) + evm::compile_solidity(&loader.solidity_code()) } fn evm_verify(deployment_code: Vec, instances: Vec>, proof: Vec) { let calldata = encode_calldata(&instances, &proof); - let success = { - let mut evm = ExecutorBuilder::default().with_gas_limit(u64::MAX.into()).build(); - - let caller = Address::from_low_u64_be(0xfe); - let verifier = evm.deploy(caller, deployment_code.into(), 0.into()).address.unwrap(); - let result = evm.call_raw(caller, verifier, calldata.into(), 0.into()); - - dbg!(result.gas_used); - - !result.reverted - }; - assert!(success); + let gas_cost = deploy_and_call(deployment_code, calldata).unwrap(); + dbg!(gas_cost); } fn main() { @@ -649,25 +520,32 @@ fn main() { File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), ) .unwrap(); - let agg_circuit = AggregationCircuit::new( + let mut circuit_params = BaseCircuitParams { + k: agg_config.degree as usize, + num_advice_per_phase: vec![agg_config.num_advice], + num_lookup_advice_per_phase: vec![agg_config.num_lookup_advice], + num_fixed: agg_config.num_fixed, + lookup_bits: Some(agg_config.lookup_bits), + num_instance_columns: 1, + }; + let mut agg_circuit = AggregationCircuit::new( CircuitBuilderStage::Mock, + circuit_params, None, - agg_config.lookup_bits, params_app.get_g()[0], snarks.clone(), ); - agg_circuit.config(agg_config.degree, Some(6)); - set_var("LOOKUP_BITS", agg_config.lookup_bits.to_string()); + circuit_params = agg_circuit.inner.calculate_params(Some(9)); #[cfg(debug_assertions)] { - MockProver::run(agg_config.degree, &agg_circuit, agg_circuit.instances()) + MockProver::run(agg_config.degree, &agg_circuit.inner, agg_circuit.instances()) .unwrap() .assert_satisfied(); println!("mock prover passed"); } let params = gen_srs(agg_config.degree); - let pk = gen_pk(¶ms, &agg_circuit); + let pk = gen_pk(¶ms, &agg_circuit.inner); let deployment_code = gen_aggregation_evm_verifier( ¶ms, pk.get_vk(), @@ -675,13 +553,13 @@ fn main() { aggregation::AggregationCircuit::accumulator_indices(), ); - let break_points = agg_circuit.break_points(); + let break_points = agg_circuit.inner.break_points(); drop(agg_circuit); let agg_circuit = AggregationCircuit::new( CircuitBuilderStage::Prover, + circuit_params, Some(break_points), - agg_config.lookup_bits, params_app.get_g()[0], snarks, ); @@ -689,7 +567,7 @@ fn main() { let proof = gen_proof::<_, _, EvmTranscript, EvmTranscript>( ¶ms, &pk, - agg_circuit, + agg_circuit.inner, instances.clone(), ); evm_verify(deployment_code, instances, proof); diff --git a/snark-verifier/examples/evm-verifier.rs b/snark-verifier/examples/evm-verifier.rs index 5b2aa802..d541528a 100644 --- a/snark-verifier/examples/evm-verifier.rs +++ b/snark-verifier/examples/evm-verifier.rs @@ -21,7 +21,7 @@ use halo2_proofs::{ use itertools::Itertools; use rand::{rngs::OsRng, RngCore}; use snark_verifier::{ - loader::evm::{self, encode_calldata, Address, EvmLoader, ExecutorBuilder}, + loader::evm::{self, deploy_and_call, encode_calldata, EvmLoader}, pcs::kzg::{Gwc19, KzgAs}, system::halo2::{compile, transcript::evm::EvmTranscript, Config}, verifier::{self, SnarkVerifier}, @@ -237,23 +237,13 @@ fn gen_evm_verifier( let proof = PlonkVerifier::read_proof(&vk, &protocol, &instances, &mut transcript).unwrap(); PlonkVerifier::verify(&vk, &protocol, &instances, &proof).unwrap(); - evm::compile_yul(&loader.yul_code()) + evm::compile_solidity(&loader.solidity_code()) } fn evm_verify(deployment_code: Vec, instances: Vec>, proof: Vec) { let calldata = encode_calldata(&instances, &proof); - let success = { - let mut evm = ExecutorBuilder::default().with_gas_limit(u64::MAX.into()).build(); - - let caller = Address::from_low_u64_be(0xfe); - let verifier = evm.deploy(caller, deployment_code.into(), 0.into()).address.unwrap(); - let result = evm.call_raw(caller, verifier, calldata.into(), 0.into()); - - dbg!(result.gas_used); - - !result.reverted - }; - assert!(success); + let gas_cost = deploy_and_call(deployment_code, calldata).unwrap(); + dbg!(gas_cost); } fn main() { diff --git a/snark-verifier/examples/recursion.rs b/snark-verifier/examples/recursion.rs index 5829e1b7..da34cb82 100644 --- a/snark-verifier/examples/recursion.rs +++ b/snark-verifier/examples/recursion.rs @@ -2,20 +2,18 @@ use ark_std::{end_timer, start_timer}; use common::*; -use halo2_base::gates::flex_gate::GateStrategy; use halo2_base::utils::fs::gen_srs; -use halo2_base::{gates::builder::FlexGateConfigParams, halo2_proofs}; +use halo2_base::{gates::circuit::BaseCircuitParams, halo2_proofs}; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, dev::MockProver, halo2curves::{ bn256::{Bn256, Fr, G1Affine}, group::ff::Field, - FieldExt, }, plonk::{ - self, create_proof, keygen_pk, keygen_vk, Circuit, ConstraintSystem, Error, ProvingKey, - Selector, VerifyingKey, + create_proof, keygen_pk, keygen_vk, Circuit, ConstraintSystem, Error, ProvingKey, Selector, + VerifyingKey, }, poly::{ commitment::ParamsProver, @@ -46,7 +44,7 @@ use snark_verifier::{ SnarkVerifier, }, }; -use std::{env::set_var, fs, iter, marker::PhantomData, rc::Rc}; +use std::{fs, iter, marker::PhantomData, rc::Rc}; use crate::recursion::AggregationConfigParams; @@ -191,19 +189,38 @@ mod common { pub fn gen_dummy_snark>( params: &ParamsKZG, vk: Option<&VerifyingKey>, - ) -> Snark { - struct CsProxy(PhantomData<(F, C)>); + config_params: ConcreteCircuit::Params, + ) -> Snark + where + ConcreteCircuit::Params: Clone, + { + struct CsProxy>(C::Params, PhantomData<(F, C)>); - impl> Circuit for CsProxy { + impl> Circuit for CsProxy + where + C::Params: Clone, + { type Config = C::Config; type FloorPlanner = C::FloorPlanner; + type Params = C::Params; + + fn params(&self) -> Self::Params { + self.0.clone() + } fn without_witnesses(&self) -> Self { - CsProxy(PhantomData) + CsProxy(self.0.clone(), PhantomData) } - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - C::configure(meta) + fn configure_with_params( + meta: &mut ConstraintSystem, + params: Self::Params, + ) -> Self::Config { + C::configure_with_params(meta, params) + } + + fn configure(_: &mut ConstraintSystem) -> Self::Config { + unreachable!() } fn synthesize( @@ -226,9 +243,9 @@ mod common { } } - let dummy_vk = vk - .is_none() - .then(|| keygen_vk(params, &CsProxy::(PhantomData)).unwrap()); + let dummy_vk = vk.is_none().then(|| { + keygen_vk(params, &CsProxy::(config_params, PhantomData)).unwrap() + }); let protocol = compile( params, vk.or(dummy_vk.as_ref()).unwrap(), @@ -323,18 +340,16 @@ mod application { } mod recursion { - use std::{collections::HashMap, env::var}; + use std::mem; use halo2_base::{ gates::{ - builder::{GateThreadBuilder, RangeCircuitBuilder}, - range::RangeConfig, - GateInstructions, RangeChip, RangeInstructions, + circuit::{builder::BaseCircuitBuilder, BaseCircuitParams, BaseConfig}, + GateInstructions, RangeInstructions, }, AssignedValue, }; use halo2_ecc::{bn254::FpChip, ecc::EcPoint}; - use halo2_proofs::plonk::{Column, Instance}; use snark_verifier::loader::halo2::{EccInstructions, IntegerInstructions}; use super::*; @@ -358,7 +373,7 @@ mod recursion { ) -> (Vec>>, Vec>>>) { let protocol = if let Some(preprocessed_digest) = preprocessed_digest { let preprocessed_digest = loader.scalar_from_assigned(preprocessed_digest); - let protocol = snark.protocol.loaded_preprocessed_as_witness(loader); + let protocol = snark.protocol.loaded_preprocessed_as_witness(loader, false); let inputs = protocol .preprocessed .iter() @@ -411,9 +426,9 @@ mod recursion { .zip([rhs.lhs.assigned(), rhs.rhs.assigned()].iter()) .map(|(lhs, rhs)| { loader.ecc_chip().select( - loader.ctx_mut().main(0), - EcPoint::clone(&lhs), - EcPoint::clone(&rhs), + loader.ctx_mut().main(), + EcPoint::clone(lhs), + EcPoint::clone(rhs), *condition, ) }) @@ -446,24 +461,18 @@ mod recursion { pub lookup_bits: usize, } - #[derive(Clone)] - pub struct RecursionConfig { - pub range: RangeConfig, - pub instance: Column, - } - #[derive(Clone)] pub struct RecursionCircuit { svk: Svk, default_accumulator: KzgAccumulator, app: Snark, previous: Snark, + #[allow(dead_code)] round: usize, instances: Vec, as_proof: Vec, - inner: RangeCircuitBuilder, - assigned_instances: Vec>, + inner: BaseCircuitBuilder, } impl RecursionCircuit { @@ -479,6 +488,7 @@ mod recursion { initial_state: Fr, state: Fr, round: usize, + config_params: BaseCircuitParams, ) -> Self { let svk = params.get_g()[0].into(); let default_accumulator = KzgAccumulator::new(params.get_g()[1], params.get_g()[0]); @@ -533,42 +543,30 @@ mod recursion { .chain([preprocessed_digest, initial_state, state, Fr::from(round as u64)]) .collect(); - let builder = GateThreadBuilder::mock(); - let inner = RangeCircuitBuilder::mock(builder); - let mut circuit = Self { - svk, - default_accumulator, - app: app, - previous: previous, - round, - instances, - as_proof, - inner, - assigned_instances: vec![], - }; + let inner = BaseCircuitBuilder::new(false).use_params(config_params); + let mut circuit = + Self { svk, default_accumulator, app, previous, round, instances, as_proof, inner }; circuit.build(); circuit } fn build(&mut self) { - let lookup_bits = var("LOOKUP_BITS").unwrap().parse().unwrap(); - let range = RangeChip::::default(lookup_bits); + let range = self.inner.range_chip(); let main_gate = range.gate(); - let mut builder = GateThreadBuilder::mock(); - let ctx = &mut builder; + let pool = self.inner.pool(0); let [preprocessed_digest, initial_state, state, round] = [ self.instances[Self::PREPROCESSED_DIGEST_ROW], self.instances[Self::INITIAL_STATE_ROW], self.instances[Self::STATE_ROW], self.instances[Self::ROUND_ROW], ] - .map(|instance| main_gate.assign_integer(ctx, instance)); - let first_round = main_gate.is_zero(ctx.main(0), round); - let not_first_round = main_gate.not(ctx.main(0), first_round); + .map(|instance| main_gate.assign_integer(pool, instance)); + let first_round = main_gate.is_zero(pool.main(), round); + let not_first_round = main_gate.not(pool.main(), first_round); let fp_chip = FpChip::::new(&range, BITS, LIMBS); let ecc_chip = BaseFieldEccChip::new(&fp_chip); - let loader = Halo2Loader::new(ecc_chip, builder); + let loader = Halo2Loader::new(ecc_chip, mem::take(self.inner.pool(0))); let (mut app_instances, app_accumulators) = succinct_verify(&self.svk, &loader, &self.app, None); let (mut previous_instances, previous_accumulators) = @@ -599,8 +597,8 @@ mod recursion { let app_instances = app_instances.pop().unwrap(); let previous_instances = previous_instances.pop().unwrap(); - let mut builder = loader.take_ctx(); - let ctx = builder.main(0); + let mut pool = loader.take_ctx(); + let ctx = pool.main(); for (lhs, rhs) in [ // Propagate preprocessed_digest ( @@ -629,9 +627,9 @@ mod recursion { ] { ctx.constrain_equal(lhs, rhs); } - *self.inner.0.builder.borrow_mut() = builder; + *self.inner.pool(0) = pool; - self.assigned_instances.extend( + self.inner.assigned_instances[0].extend( [lhs.x(), lhs.y(), rhs.x(), rhs.y()] .into_iter() .flat_map(|coordinate| coordinate.limbs()) @@ -640,8 +638,12 @@ mod recursion { ); } - fn initial_snark(params: &ParamsKZG, vk: Option<&VerifyingKey>) -> Snark { - let mut snark = gen_dummy_snark::(params, vk); + fn initial_snark( + params: &ParamsKZG, + vk: Option<&VerifyingKey>, + config_params: BaseCircuitParams, + ) -> Snark { + let mut snark = gen_dummy_snark::(params, vk, config_params); let g = params.get_g(); snark.instances = vec![[g[1].x, g[1].y, g[0].x, g[0].y] .into_iter() @@ -670,67 +672,35 @@ mod recursion { } impl Circuit for RecursionCircuit { - type Config = RecursionConfig; + type Config = BaseConfig; type FloorPlanner = SimpleFloorPlanner; + type Params = BaseCircuitParams; + + fn params(&self) -> Self::Params { + self.inner.params() + } fn without_witnesses(&self) -> Self { unimplemented!() } - fn configure(meta: &mut plonk::ConstraintSystem) -> Self::Config { - let range = RangeCircuitBuilder::configure(meta); - let instance = meta.instance_column(); - meta.enable_equality(instance); - RecursionConfig { range, instance } + fn configure_with_params( + meta: &mut ConstraintSystem, + params: Self::Params, + ) -> Self::Config { + BaseCircuitBuilder::configure_with_params(meta, params) + } + + fn configure(_: &mut ConstraintSystem) -> Self::Config { + unreachable!() } fn synthesize( &self, config: Self::Config, - mut layouter: impl Layouter, + layouter: impl Layouter, ) -> Result<(), Error> { - let range = config.range; - range.load_lookup_table(&mut layouter).expect("load lookup table should not fail"); - let circuit = &self.inner.0; - - let mut assigned_advices = HashMap::new(); - // POC so will only do mock prover and not real prover - let mut first_pass = halo2_base::SKIP_FIRST_PASS; // assume using simple floor planner - layouter - .assign_region( - || "Recursion Circuit", - |mut region| { - if first_pass { - first_pass = false; - return Ok(()); - } - // clone the builder so we can re-use the circuit for both vk and pk gen - let builder = circuit.builder.borrow(); - let assignments = builder.assign_all( - &range.gate, - &range.lookup_advice, - &range.q_lookup, - &mut region, - Default::default(), - ); - *circuit.break_points.borrow_mut() = assignments.break_points; - assigned_advices = assignments.assigned_advices; - Ok(()) - }, - ) - .unwrap(); - - // expose public instances - let mut layouter = layouter.namespace(|| "expose"); - for (i, instance) in self.assigned_instances.iter().enumerate() { - let cell = instance.cell.unwrap(); - let (cell, _) = assigned_advices - .get(&(cell.context_id, cell.offset)) - .expect("instance not assigned"); - layouter.constrain_instance(*cell, config.instance, i); - } - - Ok(()) + self.inner.synthesize(config, layouter) } } @@ -749,7 +719,7 @@ mod recursion { } fn selectors(config: &Self::Config) -> Vec { - config.range.gate.basic_gates[0].iter().map(|gate| gate.q_enable).collect() + config.gate().basic_gates[0].iter().map(|gate| gate.q_enable).collect() } } @@ -757,14 +727,20 @@ mod recursion { recursion_params: &ParamsKZG, app_params: &ParamsKZG, app_vk: &VerifyingKey, - ) -> ProvingKey { + recursion_config: BaseCircuitParams, + app_config: ConcreteCircuit::Params, + ) -> ProvingKey + where + ConcreteCircuit::Params: Clone, + { let recursion = RecursionCircuit::new( recursion_params, - gen_dummy_snark::(app_params, Some(app_vk)), - RecursionCircuit::initial_snark(recursion_params, None), + gen_dummy_snark::(app_params, Some(app_vk), app_config), + RecursionCircuit::initial_snark(recursion_params, None, recursion_config.clone()), Fr::zero(), Fr::zero(), 0, + recursion_config, ); // we cannot auto-configure the circuit because dummy_snark must know the configuration beforehand // uncomment the following line only in development to test and print out the optimal configuration ahead of time @@ -779,11 +755,15 @@ mod recursion { recursion_pk: &ProvingKey, initial_state: Fr, inputs: Vec, + config_params: BaseCircuitParams, ) -> (Fr, Snark) { let mut state = initial_state; let mut app = ConcreteCircuit::new(state); - let mut previous = - RecursionCircuit::initial_snark(recursion_params, Some(recursion_pk.get_vk())); + let mut previous = RecursionCircuit::initial_snark( + recursion_params, + Some(recursion_pk.get_vk()), + config_params.clone(), + ); for (round, input) in inputs.into_iter().enumerate() { state = app.state_transition(input); println!("Generate app snark"); @@ -795,6 +775,7 @@ mod recursion { initial_state, state, round, + config_params.clone(), ); println!("Generate recursion snark"); previous = gen_snark(recursion_params, recursion_pk, recursion); @@ -805,20 +786,19 @@ mod recursion { } fn main() { - let app_params = gen_srs(3); + let app_params = gen_srs(5); let recursion_config: AggregationConfigParams = serde_json::from_reader(fs::File::open("configs/example_recursion.json").unwrap()).unwrap(); let k = recursion_config.degree; let recursion_params = gen_srs(k); - let flex_gate_config = FlexGateConfigParams { - strategy: GateStrategy::Vertical, + let config_params = BaseCircuitParams { k: k as usize, num_advice_per_phase: vec![recursion_config.num_advice], num_lookup_advice_per_phase: vec![recursion_config.num_lookup_advice], num_fixed: recursion_config.num_fixed, + lookup_bits: Some(recursion_config.lookup_bits), + num_instance_columns: 1, }; - set_var("FLEX_GATE_CONFIG_PARAMS", serde_json::to_string(&flex_gate_config).unwrap()); - set_var("LOOKUP_BITS", recursion_config.lookup_bits.to_string()); let app_pk = gen_pk(&app_params, &application::Square::default()); @@ -827,6 +807,8 @@ fn main() { &recursion_params, &app_params, app_pk.get_vk(), + config_params.clone(), + (), ); end_timer!(pk_time); @@ -839,9 +821,10 @@ fn main() { &recursion_pk, Fr::from(2u64), vec![(); num_round], + config_params.clone(), ); end_timer!(pf_time); - assert_eq!(final_state, Fr::from(2u64).pow(&[1 << num_round, 0, 0, 0])); + assert_eq!(final_state, Fr::from(2u64).pow([1 << num_round])); { let dk = diff --git a/snark-verifier/src/loader.rs b/snark-verifier/src/loader.rs index 77a8f54b..26450e1a 100644 --- a/snark-verifier/src/loader.rs +++ b/snark-verifier/src/loader.rs @@ -67,6 +67,12 @@ pub trait LoadedScalar: Clone + Debug + PartialEq + FieldOps { acc } + /// Returns power to exponent, where exponent is also [`LoadedScalar`]. + /// If `Loader` is for Halo2, then `exp` must have at most `exp_max_bits` bits (otherwise constraints will fail). + /// + /// Currently **unimplemented** for EvmLoader + fn pow_var(&self, exp: &Self, exp_max_bits: usize) -> Self; + /// Returns powers up to exponent `n-1`. fn powers(&self, n: usize) -> Vec { iter::once(self.loader().load_one()) @@ -122,12 +128,12 @@ pub trait ScalarLoader { /// Load `zero` as constant. fn load_zero(&self) -> Self::LoadedScalar { - self.load_const(&F::zero()) + self.load_const(&F::ZERO) } /// Load `one` as constant. fn load_one(&self) -> Self::LoadedScalar { - self.load_const(&F::one()) + self.load_const(&F::ONE) } /// Assert lhs and rhs field elements are equal. @@ -145,13 +151,13 @@ pub trait ScalarLoader { let loader = values.first().unwrap().1.loader(); iter::empty() - .chain(if constant == F::zero() { + .chain(if constant == F::ZERO { None } else { Some(Cow::Owned(loader.load_const(&constant))) }) .chain(values.iter().map(|&(coeff, value)| { - if coeff == F::one() { + if coeff == F::ONE { Cow::Borrowed(value) } else { Cow::Owned(loader.load_const(&coeff) * value) @@ -174,9 +180,9 @@ pub trait ScalarLoader { let loader = values.first().unwrap().1.loader(); iter::empty() - .chain(if constant == F::zero() { None } else { Some(loader.load_const(&constant)) }) + .chain(if constant == F::ZERO { None } else { Some(loader.load_const(&constant)) }) .chain(values.iter().map(|&(coeff, lhs, rhs)| { - if coeff == F::one() { + if coeff == F::ONE { lhs.clone() * rhs } else { loader.load_const(&coeff) * lhs * rhs @@ -188,20 +194,20 @@ pub trait ScalarLoader { /// Sum field elements with coefficients. fn sum_with_coeff(&self, values: &[(F, &Self::LoadedScalar)]) -> Self::LoadedScalar { - self.sum_with_coeff_and_const(values, F::zero()) + self.sum_with_coeff_and_const(values, F::ZERO) } /// Sum field elements and constant. fn sum_with_const(&self, values: &[&Self::LoadedScalar], constant: F) -> Self::LoadedScalar { self.sum_with_coeff_and_const( - &values.iter().map(|&value| (F::one(), value)).collect_vec(), + &values.iter().map(|&value| (F::ONE, value)).collect_vec(), constant, ) } /// Sum field elements. fn sum(&self, values: &[&Self::LoadedScalar]) -> Self::LoadedScalar { - self.sum_with_const(values, F::zero()) + self.sum_with_const(values, F::ZERO) } /// Sum product of field elements with coefficients. @@ -209,7 +215,7 @@ pub trait ScalarLoader { &self, values: &[(F, &Self::LoadedScalar, &Self::LoadedScalar)], ) -> Self::LoadedScalar { - self.sum_products_with_coeff_and_const(values, F::zero()) + self.sum_products_with_coeff_and_const(values, F::ZERO) } /// Sum product of field elements and constant. @@ -219,7 +225,7 @@ pub trait ScalarLoader { constant: F, ) -> Self::LoadedScalar { self.sum_products_with_coeff_and_const( - &values.iter().map(|&(lhs, rhs)| (F::one(), lhs, rhs)).collect_vec(), + &values.iter().map(|&(lhs, rhs)| (F::ONE, lhs, rhs)).collect_vec(), constant, ) } @@ -229,7 +235,7 @@ pub trait ScalarLoader { &self, values: &[(&Self::LoadedScalar, &Self::LoadedScalar)], ) -> Self::LoadedScalar { - self.sum_products_with_const(values, F::zero()) + self.sum_products_with_const(values, F::ZERO) } /// Product of field elements. diff --git a/snark-verifier/src/loader/evm.rs b/snark-verifier/src/loader/evm.rs index e942b4a3..19b145bb 100644 --- a/snark-verifier/src/loader/evm.rs +++ b/snark-verifier/src/loader/evm.rs @@ -4,14 +4,8 @@ mod code; pub(crate) mod loader; pub(crate) mod util; -#[cfg(test)] -mod test; - pub use loader::{EcPoint, EvmLoader, Scalar}; pub use util::{ - compile_yul, encode_calldata, estimate_gas, fe_to_u256, modulus, u256_to_fe, Address, - ExecutorBuilder, H256, U256, U512, + compile_solidity, deploy_and_call, encode_calldata, estimate_gas, fe_to_u256, modulus, + u256_to_fe, Address, B256, U256, U512, }; - -#[cfg(test)] -pub use test::execute; diff --git a/snark-verifier/src/loader/evm/code.rs b/snark-verifier/src/loader/evm/code.rs index 2fec71d2..193b66e9 100644 --- a/snark-verifier/src/loader/evm/code.rs +++ b/snark-verifier/src/loader/evm/code.rs @@ -6,57 +6,49 @@ pub enum Precompiled { } #[derive(Clone, Debug)] -pub struct YulCode { +pub struct SolidityAssemblyCode { // runtime code area runtime: String, } -impl YulCode { +impl SolidityAssemblyCode { pub fn new() -> Self { - YulCode { - runtime: String::new(), - } + Self { runtime: String::new() } } pub fn code(&self, base_modulus: String, scalar_modulus: String) -> String { format!( " - object \"plonk_verifier\" {{ - code {{ - function allocate(size) -> ptr {{ - ptr := mload(0x40) - if eq(ptr, 0) {{ ptr := 0x60 }} - mstore(0x40, add(ptr, size)) +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.19 <0.8.21; + +contract Halo2Verifier {{ + fallback(bytes calldata) external returns (bytes memory) {{ + assembly {{ + let success := true + let f_p := {base_modulus} + let f_q := {scalar_modulus} + function validate_ec_point(x, y) -> valid {{ + {{ + let x_lt_p := lt(x, {base_modulus}) + let y_lt_p := lt(y, {base_modulus}) + valid := and(x_lt_p, y_lt_p) }} - let size := datasize(\"Runtime\") - let offset := allocate(size) - datacopy(offset, dataoffset(\"Runtime\"), size) - return(offset, size) - }} - object \"Runtime\" {{ - code {{ - let success:bool := true - let f_p := {base_modulus} - let f_q := {scalar_modulus} - function validate_ec_point(x, y) -> valid:bool {{ - {{ - let x_lt_p:bool := lt(x, {base_modulus}) - let y_lt_p:bool := lt(y, {base_modulus}) - valid := and(x_lt_p, y_lt_p) - }} - {{ - let y_square := mulmod(y, y, {base_modulus}) - let x_square := mulmod(x, x, {base_modulus}) - let x_cube := mulmod(x_square, x, {base_modulus}) - let x_cube_plus_3 := addmod(x_cube, 3, {base_modulus}) - let is_affine:bool := eq(x_cube_plus_3, y_square) - valid := and(valid, is_affine) - }} - }} - {} + {{ + let y_square := mulmod(y, y, {base_modulus}) + let x_square := mulmod(x, x, {base_modulus}) + let x_cube := mulmod(x_square, x, {base_modulus}) + let x_cube_plus_3 := addmod(x_cube, 3, {base_modulus}) + let is_affine := eq(x_cube_plus_3, y_square) + valid := and(valid, is_affine) }} }} - }}", + {} + }} + }} +}} + ", self.runtime ) } diff --git a/snark-verifier/src/loader/evm/loader.rs b/snark-verifier/src/loader/evm/loader.rs index 98ca5ca4..3bf49baf 100644 --- a/snark-verifier/src/loader/evm/loader.rs +++ b/snark-verifier/src/loader/evm/loader.rs @@ -1,7 +1,7 @@ use crate::{ loader::{ evm::{ - code::{Precompiled, YulCode}, + code::{Precompiled, SolidityAssemblyCode}, fe_to_u256, modulus, u256_to_fe, U256, U512, }, EcPointLoader, LoadedEcPoint, LoadedScalar, Loader, ScalarLoader, @@ -52,17 +52,13 @@ impl Value { pub struct EvmLoader { base_modulus: U256, scalar_modulus: U256, - code: RefCell, + code: RefCell, ptr: RefCell, cache: RefCell>, - #[cfg(test)] - gas_metering_ids: RefCell>, } fn hex_encode_u256(value: &U256) -> String { - let mut bytes = [0; 32]; - value.to_big_endian(&mut bytes); - format!("0x{}", hex::encode(bytes)) + format!("0x{}", hex::encode(value.to_be_bytes::<32>())) } impl EvmLoader { @@ -74,7 +70,7 @@ impl EvmLoader { { let base_modulus = modulus::(); let scalar_modulus = modulus::(); - let code = YulCode::new(); + let code = SolidityAssemblyCode::new(); Rc::new(Self { base_modulus, @@ -82,15 +78,17 @@ impl EvmLoader { code: RefCell::new(code), ptr: Default::default(), cache: Default::default(), - #[cfg(test)] - gas_metering_ids: RefCell::new(Vec::new()), }) } - /// Returns generated yul code. - pub fn yul_code(self: &Rc) -> String { + /// Returns generated Solidity code. This is "Solidity" code that is wrapped in an assembly block. + /// In other words, it's basically just assembly (equivalently, Yul). + pub fn solidity_code(self: &Rc) -> String { let code = " - if not(success) { revert(0, 0) } + // Revert if anything fails + if iszero(success) { revert(0, 0) } + + // Return empty bytes on success return(0, 0)" .to_string(); self.code.borrow_mut().runtime_append(code); @@ -110,7 +108,7 @@ impl EvmLoader { *self.ptr.borrow() } - pub(crate) fn code_mut(&self) -> impl DerefMut + '_ { + pub(crate) fn code_mut(&self) -> impl DerefMut + '_ { self.code.borrow_mut() } @@ -308,11 +306,11 @@ impl EvmLoader { fn invert(self: &Rc, scalar: &Scalar) -> Scalar { let rd_ptr = self.allocate(0x20); let [cd_ptr, ..] = [ - &self.scalar(Value::Constant(0x20.into())), - &self.scalar(Value::Constant(0x20.into())), - &self.scalar(Value::Constant(0x20.into())), + &self.scalar(Value::Constant(U256::from(0x20))), + &self.scalar(Value::Constant(U256::from(0x20))), + &self.scalar(Value::Constant(U256::from(0x20))), scalar, - &self.scalar(Value::Constant(self.scalar_modulus - 2)), + &self.scalar(Value::Constant(self.scalar_modulus - U256::from(2))), &self.scalar(Value::Constant(self.scalar_modulus)), ] .map(|value| self.dup_scalar(value).ptr()); @@ -383,8 +381,8 @@ impl EvmLoader { fn add(self: &Rc, lhs: &Scalar, rhs: &Scalar) -> Scalar { if let (Value::Constant(lhs), Value::Constant(rhs)) = (&lhs.value, &rhs.value) { - let out = (U512::from(lhs) + U512::from(rhs)) % U512::from(self.scalar_modulus); - return self.scalar(Value::Constant(out.try_into().unwrap())); + let out = (U512::from(*lhs) + U512::from(*rhs)) % U512::from(self.scalar_modulus); + return self.scalar(Value::Constant(U256::from(out))); } self.scalar(Value::Sum(Box::new(lhs.value.clone()), Box::new(rhs.value.clone()))) @@ -403,8 +401,8 @@ impl EvmLoader { fn mul(self: &Rc, lhs: &Scalar, rhs: &Scalar) -> Scalar { if let (Value::Constant(lhs), Value::Constant(rhs)) = (&lhs.value, &rhs.value) { - let out = (U512::from(lhs) * U512::from(rhs)) % U512::from(self.scalar_modulus); - return self.scalar(Value::Constant(out.try_into().unwrap())); + let out = (U512::from(*lhs) * U512::from(*rhs)) % U512::from(self.scalar_modulus); + return self.scalar(Value::Constant(U256::from(out))); } self.scalar(Value::Product(Box::new(lhs.value.clone()), Box::new(rhs.value.clone()))) @@ -421,22 +419,16 @@ impl EvmLoader { #[cfg(test)] impl EvmLoader { - fn start_gas_metering(self: &Rc, identifier: &str) { - self.gas_metering_ids.borrow_mut().push(identifier.to_string()); - let code = format!("let {identifier} := gas()"); - self.code.borrow_mut().runtime_append(code); + fn start_gas_metering(self: &Rc, _: &str) { + // unimplemented } fn end_gas_metering(self: &Rc) { - let code = - format!("log1(0, 0, sub({}, gas()))", self.gas_metering_ids.borrow().last().unwrap()); - self.code.borrow_mut().runtime_append(code); + // unimplemented } - pub fn print_gas_metering(self: &Rc, costs: Vec) { - for (identifier, cost) in self.gas_metering_ids.borrow().iter().zip(costs) { - println!("{identifier}: {cost}"); - } + pub fn print_gas_metering(self: &Rc, _: Vec) { + // unimplemented } } @@ -632,6 +624,10 @@ impl> LoadedScalar for Scalar { fn loader(&self) -> &Self::Loader { &self.loader } + + fn pow_var(&self, _exp: &Self, _exp_max_bits: usize) -> Self { + todo!() + } } impl EcPointLoader for Rc @@ -644,7 +640,7 @@ where fn ec_point_load_const(&self, value: &C) -> EcPoint { let coordinates = value.coordinates().unwrap(); let [x, y] = [coordinates.x(), coordinates.y()] - .map(|coordinate| U256::from_little_endian(coordinate.to_repr().as_ref())); + .map(|coordinate| U256::try_from_le_slice(coordinate.to_repr().as_ref()).unwrap()); self.ec_point(Value::Constant((x, y))) } @@ -659,7 +655,7 @@ where .iter() .cloned() .map(|(scalar, ec_point)| match scalar.value { - Value::Constant(constant) if U256::one() == constant => ec_point.clone(), + Value::Constant(constant) if U256::from(1) == constant => ec_point.clone(), _ => ec_point.loader.ec_point_scalar_mul(ec_point, scalar), }) .reduce(|acc, ec_point| acc.loader.ec_point_add(&acc, &ec_point)) @@ -684,8 +680,8 @@ impl> ScalarLoader for Rc { } let push_addend = |(coeff, value): &(F, &Scalar)| { - assert_ne!(*coeff, F::zero()); - match (*coeff == F::one(), &value.value) { + assert_ne!(*coeff, F::ZERO); + match (*coeff == F::ONE, &value.value) { (true, _) => self.push(value), (false, Value::Constant(value)) => self.push( &self.scalar(Value::Constant(fe_to_u256(*coeff * u256_to_fe::(*value)))), @@ -699,7 +695,7 @@ impl> ScalarLoader for Rc { }; let mut values = values.iter(); - let initial_value = if constant == F::zero() { + let initial_value = if constant == F::ZERO { push_addend(values.next().unwrap()) } else { self.push(&self.scalar(Value::Constant(fe_to_u256(constant)))) @@ -733,8 +729,8 @@ impl> ScalarLoader for Rc { } let push_addend = |(coeff, lhs, rhs): &(F, &Scalar, &Scalar)| { - assert_ne!(*coeff, F::zero()); - match (*coeff == F::one(), &lhs.value, &rhs.value) { + assert_ne!(*coeff, F::ZERO); + match (*coeff == F::ONE, &lhs.value, &rhs.value) { (_, Value::Constant(lhs), Value::Constant(rhs)) => { self.push(&self.scalar(Value::Constant(fe_to_u256( *coeff * u256_to_fe::(*lhs) * u256_to_fe::(*rhs), @@ -764,7 +760,7 @@ impl> ScalarLoader for Rc { }; let mut values = values.iter(); - let initial_value = if constant == F::zero() { + let initial_value = if constant == F::ZERO { push_addend(values.next().unwrap()) } else { self.push(&self.scalar(Value::Constant(fe_to_u256(constant)))) diff --git a/snark-verifier/src/loader/evm/test.rs b/snark-verifier/src/loader/evm/test.rs deleted file mode 100644 index e3467408..00000000 --- a/snark-verifier/src/loader/evm/test.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::{ - loader::evm::{test::tui::Tui, Address, ExecutorBuilder, U256}, - util::Itertools, -}; -use std::env::var_os; - -mod tui; - -fn debug() -> bool { - matches!( - var_os("DEBUG"), - Some(value) if value.to_str() == Some("1") - ) -} - -pub fn execute(deployment_code: Vec, calldata: Vec) -> (bool, u64, Vec) { - assert!( - deployment_code.len() <= 0x6000, - "Contract size {} exceeds the limit 24576", - deployment_code.len() - ); - - let debug = debug(); - let caller = Address::from_low_u64_be(0xfe); - - let mut evm = ExecutorBuilder::default() - .with_gas_limit(u64::MAX.into()) - .set_debugger(debug) - .build(); - - let contract = evm - .deploy(caller, deployment_code.into(), 0.into()) - .address - .unwrap(); - let result = evm.call_raw(caller, contract, calldata.into(), 0.into()); - - let costs = result - .logs - .into_iter() - .map(|log| U256::from_big_endian(log.topics[0].as_bytes()).as_u64()) - .collect_vec(); - - if debug { - Tui::new(result.debug.unwrap().flatten(0), 0).start(); - } - - (!result.reverted, result.gas_used, costs) -} diff --git a/snark-verifier/src/loader/evm/test/tui.rs b/snark-verifier/src/loader/evm/test/tui.rs deleted file mode 100644 index 328082c7..00000000 --- a/snark-verifier/src/loader/evm/test/tui.rs +++ /dev/null @@ -1,943 +0,0 @@ -//! Copied and modified from https://github.com/foundry-rs/foundry/blob/master/ui/src/lib.rs - -use crate::loader::evm::{ - util::executor::{CallKind, DebugStep}, - Address, -}; -use crossterm::{ - event::{ - self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, - MouseEvent, MouseEventKind, - }, - execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, -}; -use revm::opcode; -use std::{ - cmp::{max, min}, - io, - sync::mpsc, - thread, - time::{Duration, Instant}, -}; -use tui::{ - backend::{Backend, CrosstermBackend}, - layout::{Alignment, Constraint, Direction, Layout, Rect}, - style::{Color, Modifier, Style}, - terminal::Frame, - text::{Span, Spans, Text}, - widgets::{Block, Borders, Paragraph, Wrap}, - Terminal, -}; - -pub struct Tui { - debug_arena: Vec<(Address, Vec, CallKind)>, - terminal: Terminal>, - key_buffer: String, - current_step: usize, -} - -impl Tui { - pub fn new(debug_arena: Vec<(Address, Vec, CallKind)>, current_step: usize) -> Self { - enable_raw_mode().unwrap(); - let mut stdout = io::stdout(); - execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap(); - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend).unwrap(); - terminal.hide_cursor().unwrap(); - Tui { - debug_arena, - terminal, - key_buffer: String::new(), - current_step, - } - } - - pub fn start(mut self) { - std::panic::set_hook(Box::new(|e| { - disable_raw_mode().expect("Unable to disable raw mode"); - execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture) - .expect("unable to execute disable mouse capture"); - println!("{e}"); - })); - let tick_rate = Duration::from_millis(60); - - let (tx, rx) = mpsc::channel(); - thread::spawn(move || { - let mut last_tick = Instant::now(); - loop { - if event::poll(tick_rate - last_tick.elapsed()).unwrap() { - let event = event::read().unwrap(); - if let Event::Key(key) = event { - if tx.send(Interrupt::KeyPressed(key)).is_err() { - return; - } - } else if let Event::Mouse(mouse) = event { - if tx.send(Interrupt::MouseEvent(mouse)).is_err() { - return; - } - } - } - if last_tick.elapsed() > tick_rate { - if tx.send(Interrupt::IntervalElapsed).is_err() { - return; - } - last_tick = Instant::now(); - } - } - }); - - self.terminal.clear().unwrap(); - let mut draw_memory: DrawMemory = DrawMemory::default(); - - let debug_call = &self.debug_arena; - let mut opcode_list: Vec = debug_call[0] - .1 - .iter() - .map(|step| step.pretty_opcode()) - .collect(); - let mut last_index = 0; - - let mut stack_labels = false; - let mut mem_utf = false; - loop { - if last_index != draw_memory.inner_call_index { - opcode_list = debug_call[draw_memory.inner_call_index] - .1 - .iter() - .map(|step| step.pretty_opcode()) - .collect(); - last_index = draw_memory.inner_call_index; - } - match rx.recv().unwrap() { - Interrupt::KeyPressed(event) => match event.code { - KeyCode::Char('q') => { - disable_raw_mode().unwrap(); - execute!( - self.terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - ) - .unwrap(); - return; - } - KeyCode::Char('j') | KeyCode::Down => { - for _ in 0..Tui::buffer_as_number(&self.key_buffer, 1) { - if event.modifiers.contains(KeyModifiers::CONTROL) { - let max_mem = (debug_call[draw_memory.inner_call_index].1 - [self.current_step] - .memory - .len() - / 32) - .saturating_sub(1); - let step = if event.modifiers.contains(KeyModifiers::ALT) { - 20 - } else { - 1 - }; - if draw_memory.current_mem_startline + step < max_mem { - draw_memory.current_mem_startline += step; - } - } else if self.current_step < opcode_list.len() - 1 { - self.current_step += 1; - } else if draw_memory.inner_call_index < debug_call.len() - 1 { - draw_memory.inner_call_index += 1; - self.current_step = 0; - } - } - self.key_buffer.clear(); - } - KeyCode::Char('J') => { - for _ in 0..Tui::buffer_as_number(&self.key_buffer, 1) { - let max_stack = debug_call[draw_memory.inner_call_index].1 - [self.current_step] - .stack - .len() - .saturating_sub(1); - if draw_memory.current_stack_startline < max_stack { - draw_memory.current_stack_startline += 1; - } - } - self.key_buffer.clear(); - } - KeyCode::Char('k') | KeyCode::Up => { - for _ in 0..Tui::buffer_as_number(&self.key_buffer, 1) { - if event.modifiers.contains(KeyModifiers::CONTROL) { - draw_memory.current_mem_startline = - draw_memory.current_mem_startline.saturating_sub(1); - } else if self.current_step > 0 { - self.current_step -= 1; - } else if draw_memory.inner_call_index > 0 { - draw_memory.inner_call_index -= 1; - self.current_step = - debug_call[draw_memory.inner_call_index].1.len() - 1; - } - } - self.key_buffer.clear(); - } - KeyCode::Char('K') => { - for _ in 0..Tui::buffer_as_number(&self.key_buffer, 1) { - draw_memory.current_stack_startline = - draw_memory.current_stack_startline.saturating_sub(1); - } - self.key_buffer.clear(); - } - KeyCode::Char('g') => { - draw_memory.inner_call_index = 0; - self.current_step = 0; - self.key_buffer.clear(); - } - KeyCode::Char('G') => { - draw_memory.inner_call_index = debug_call.len() - 1; - self.current_step = debug_call[draw_memory.inner_call_index].1.len() - 1; - self.key_buffer.clear(); - } - KeyCode::Char('c') => { - draw_memory.inner_call_index = - draw_memory.inner_call_index.saturating_sub(1); - self.current_step = debug_call[draw_memory.inner_call_index].1.len() - 1; - self.key_buffer.clear(); - } - KeyCode::Char('C') => { - if debug_call.len() > draw_memory.inner_call_index + 1 { - draw_memory.inner_call_index += 1; - self.current_step = 0; - } - self.key_buffer.clear(); - } - KeyCode::Char('s') => { - for _ in 0..Tui::buffer_as_number(&self.key_buffer, 1) { - let remaining_ops = &opcode_list[self.current_step..]; - self.current_step += remaining_ops - .iter() - .enumerate() - .find_map(|(i, op)| { - if i < remaining_ops.len() - 1 { - match ( - op.contains("JUMP") && op != "JUMPDEST", - &*remaining_ops[i + 1], - ) { - (true, "JUMPDEST") => Some(i + 1), - _ => None, - } - } else { - None - } - }) - .unwrap_or(opcode_list.len() - 1); - if self.current_step > opcode_list.len() { - self.current_step = opcode_list.len() - 1 - }; - } - self.key_buffer.clear(); - } - KeyCode::Char('a') => { - for _ in 0..Tui::buffer_as_number(&self.key_buffer, 1) { - let prev_ops = &opcode_list[..self.current_step]; - self.current_step = prev_ops - .iter() - .enumerate() - .rev() - .find_map(|(i, op)| { - if i > 0 { - match ( - prev_ops[i - 1].contains("JUMP") - && prev_ops[i - 1] != "JUMPDEST", - &**op, - ) { - (true, "JUMPDEST") => Some(i - 1), - _ => None, - } - } else { - None - } - }) - .unwrap_or_default(); - } - self.key_buffer.clear(); - } - KeyCode::Char('t') => { - stack_labels = !stack_labels; - } - KeyCode::Char('m') => { - mem_utf = !mem_utf; - } - KeyCode::Char(other) => match other { - '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => { - self.key_buffer.push(other); - } - _ => { - self.key_buffer.clear(); - } - }, - _ => { - self.key_buffer.clear(); - } - }, - Interrupt::MouseEvent(event) => match event.kind { - MouseEventKind::ScrollUp => { - if self.current_step > 0 { - self.current_step -= 1; - } else if draw_memory.inner_call_index > 0 { - draw_memory.inner_call_index -= 1; - draw_memory.current_mem_startline = 0; - draw_memory.current_stack_startline = 0; - self.current_step = - debug_call[draw_memory.inner_call_index].1.len() - 1; - } - } - MouseEventKind::ScrollDown => { - if self.current_step < opcode_list.len() - 1 { - self.current_step += 1; - } else if draw_memory.inner_call_index < debug_call.len() - 1 { - draw_memory.inner_call_index += 1; - draw_memory.current_mem_startline = 0; - draw_memory.current_stack_startline = 0; - self.current_step = 0; - } - } - _ => {} - }, - Interrupt::IntervalElapsed => {} - } - let current_step = self.current_step; - self.terminal - .draw(|f| { - Tui::draw_layout( - f, - debug_call[draw_memory.inner_call_index].0, - &debug_call[draw_memory.inner_call_index].1[..], - &opcode_list, - current_step, - &mut draw_memory, - stack_labels, - mem_utf, - ) - }) - .unwrap(); - } - } - - fn buffer_as_number(buffer: &str, default_value: usize) -> usize { - if let Ok(num) = buffer.parse() { - if num >= 1 { - num - } else { - default_value - } - } else { - default_value - } - } - - fn draw_layout( - f: &mut Frame, - address: Address, - debug_steps: &[DebugStep], - opcode_list: &[String], - current_step: usize, - draw_memory: &mut DrawMemory, - stack_labels: bool, - mem_utf: bool, - ) { - let total_size = f.size(); - if total_size.width < 225 { - Tui::vertical_layout( - f, - address, - debug_steps, - opcode_list, - current_step, - draw_memory, - stack_labels, - mem_utf, - ); - } else { - Tui::square_layout( - f, - address, - debug_steps, - opcode_list, - current_step, - draw_memory, - stack_labels, - mem_utf, - ); - } - } - - fn vertical_layout( - f: &mut Frame, - address: Address, - debug_steps: &[DebugStep], - opcode_list: &[String], - current_step: usize, - draw_memory: &mut DrawMemory, - stack_labels: bool, - mem_utf: bool, - ) { - let total_size = f.size(); - if let [app, footer] = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Ratio(98, 100), Constraint::Ratio(2, 100)].as_ref()) - .split(total_size)[..] - { - if let [op_pane, stack_pane, memory_pane] = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Ratio(1, 3), - Constraint::Ratio(1, 3), - Constraint::Ratio(1, 3), - ] - .as_ref(), - ) - .split(app)[..] - { - Tui::draw_footer(f, footer); - Tui::draw_op_list( - f, - address, - debug_steps, - opcode_list, - current_step, - draw_memory, - op_pane, - ); - Tui::draw_stack( - f, - debug_steps, - current_step, - stack_pane, - stack_labels, - draw_memory, - ); - Tui::draw_memory( - f, - debug_steps, - current_step, - memory_pane, - mem_utf, - draw_memory, - ); - } else { - panic!("unable to create vertical panes") - } - } else { - panic!("unable to create footer / app") - } - } - - fn square_layout( - f: &mut Frame, - address: Address, - debug_steps: &[DebugStep], - opcode_list: &[String], - current_step: usize, - draw_memory: &mut DrawMemory, - stack_labels: bool, - mem_utf: bool, - ) { - let total_size = f.size(); - - if let [app, footer] = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Ratio(98, 100), Constraint::Ratio(2, 100)].as_ref()) - .split(total_size)[..] - { - if let [left_pane, right_pane] = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)].as_ref()) - .split(app)[..] - { - if let [stack_pane, memory_pane] = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Ratio(2, 5), Constraint::Ratio(3, 5)].as_ref()) - .split(right_pane)[..] - { - Tui::draw_footer(f, footer); - Tui::draw_op_list( - f, - address, - debug_steps, - opcode_list, - current_step, - draw_memory, - left_pane, - ); - Tui::draw_stack( - f, - debug_steps, - current_step, - stack_pane, - stack_labels, - draw_memory, - ); - Tui::draw_memory( - f, - debug_steps, - current_step, - memory_pane, - mem_utf, - draw_memory, - ); - } else { - panic!("Couldn't generate horizontal split layout 1:2."); - } - } else { - panic!("Couldn't generate vertical split layout 1:2."); - } - } else { - panic!("Couldn't generate application & footer") - } - } - - fn draw_footer(f: &mut Frame, area: Rect) { - let block_controls = Block::default(); - - let text_output = Text::from(Span::styled( - "[q]: quit | [k/j]: prev/next op | [a/s]: prev/next jump | [c/C]: prev/next call | [g/G]: start/end | [t]: toggle stack labels | [m]: toggle memory decoding | [shift + j/k]: scroll stack | [ctrl + j/k]: scroll memory", - Style::default().add_modifier(Modifier::DIM) - )); - let paragraph = Paragraph::new(text_output) - .block(block_controls) - .alignment(Alignment::Center) - .wrap(Wrap { trim: false }); - f.render_widget(paragraph, area); - } - - fn draw_op_list( - f: &mut Frame, - address: Address, - debug_steps: &[DebugStep], - opcode_list: &[String], - current_step: usize, - draw_memory: &mut DrawMemory, - area: Rect, - ) { - let block_source_code = Block::default() - .title(format!( - "Address: {:?} | PC: {} | Gas used in call: {}", - address, - if let Some(step) = debug_steps.get(current_step) { - step.pc.to_string() - } else { - "END".to_string() - }, - debug_steps[current_step].total_gas_used, - )) - .borders(Borders::ALL); - let mut text_output: Vec = Vec::new(); - - let display_start; - - let height = area.height as i32; - let extra_top_lines = height / 2; - let prev_start = draw_memory.current_startline; - let abs_min_start = 0; - let abs_max_start = (opcode_list.len() as i32 - 1) - (height / 2); - let mut min_start = max( - current_step as i32 - height + extra_top_lines, - abs_min_start, - ) as usize; - - let mut max_start = max( - min(current_step as i32 - extra_top_lines, abs_max_start), - abs_min_start, - ) as usize; - - if min_start > max_start { - std::mem::swap(&mut min_start, &mut max_start); - } - - if prev_start < min_start { - display_start = min_start; - } else if prev_start > max_start { - display_start = max_start; - } else { - display_start = prev_start; - } - draw_memory.current_startline = display_start; - - let max_pc_len = debug_steps - .iter() - .fold(0, |max_val, val| val.pc.max(max_val)) - .to_string() - .len(); - - let mut add_new_line = |line_number| { - let bg_color = if line_number == current_step { - Color::DarkGray - } else { - Color::Reset - }; - - let line_number_format = if line_number == current_step { - let step: &DebugStep = &debug_steps[line_number]; - format!("{:0>max_pc_len$x}|▶", step.pc) - } else if line_number < debug_steps.len() { - let step: &DebugStep = &debug_steps[line_number]; - format!("{:0>max_pc_len$x}| ", step.pc) - } else { - "END CALL".to_string() - }; - - if let Some(op) = opcode_list.get(line_number) { - text_output.push(Spans::from(Span::styled( - format!("{line_number_format}{op}"), - Style::default().fg(Color::White).bg(bg_color), - ))); - } else { - text_output.push(Spans::from(Span::styled( - line_number_format, - Style::default().fg(Color::White).bg(bg_color), - ))); - } - }; - for number in display_start..opcode_list.len() { - add_new_line(number); - } - add_new_line(opcode_list.len()); - let paragraph = Paragraph::new(text_output) - .block(block_source_code) - .wrap(Wrap { trim: true }); - f.render_widget(paragraph, area); - } - - fn draw_stack( - f: &mut Frame, - debug_steps: &[DebugStep], - current_step: usize, - area: Rect, - stack_labels: bool, - draw_memory: &mut DrawMemory, - ) { - let stack = &debug_steps[current_step].stack; - let stack_space = Block::default() - .title(format!("Stack: {}", stack.len())) - .borders(Borders::ALL); - let min_len = usize::max(format!("{}", stack.len()).len(), 2); - - let indices_affected = stack_indices_affected(debug_steps[current_step].instruction.0); - - let text: Vec = stack - .iter() - .rev() - .enumerate() - .skip(draw_memory.current_stack_startline) - .map(|(i, stack_item)| { - let affected = indices_affected - .iter() - .find(|(affected_index, _name)| *affected_index == i); - - let mut words: Vec = (0..32) - .into_iter() - .rev() - .map(|i| stack_item.byte(i)) - .map(|byte| { - Span::styled( - format!("{byte:02x} "), - if affected.is_some() { - Style::default().fg(Color::Cyan) - } else if byte == 0 { - Style::default().add_modifier(Modifier::DIM) - } else { - Style::default().fg(Color::White) - }, - ) - }) - .collect(); - - if stack_labels { - if let Some((_, name)) = affected { - words.push(Span::raw(format!("| {name}"))); - } else { - words.push(Span::raw("| ".to_string())); - } - } - - let mut spans = vec![Span::styled( - format!("{i:0min_len$}| "), - Style::default().fg(Color::White), - )]; - spans.extend(words); - spans.push(Span::raw("\n")); - - Spans::from(spans) - }) - .collect(); - - let paragraph = Paragraph::new(text) - .block(stack_space) - .wrap(Wrap { trim: true }); - f.render_widget(paragraph, area); - } - - fn draw_memory( - f: &mut Frame, - debug_steps: &[DebugStep], - current_step: usize, - area: Rect, - mem_utf8: bool, - draw_mem: &mut DrawMemory, - ) { - let memory = &debug_steps[current_step].memory; - let stack_space = Block::default() - .title(format!( - "Memory (max expansion: {} bytes)", - memory.effective_len() - )) - .borders(Borders::ALL); - let memory = memory.data(); - let max_i = memory.len() / 32; - let min_len = format!("{:x}", max_i * 32).len(); - - let mut word = None; - let mut color = None; - let stack_len = debug_steps[current_step].stack.len(); - if stack_len > 0 { - let w = debug_steps[current_step].stack[stack_len - 1]; - match debug_steps[current_step].instruction.0 { - opcode::MLOAD => { - word = Some(w.as_usize() / 32); - color = Some(Color::Cyan); - } - opcode::MSTORE => { - word = Some(w.as_usize() / 32); - color = Some(Color::Red); - } - _ => {} - } - } - - if current_step > 0 { - let prev_step = current_step - 1; - let stack_len = debug_steps[prev_step].stack.len(); - if debug_steps[prev_step].instruction.0 == opcode::MSTORE { - let prev_top = debug_steps[prev_step].stack[stack_len - 1]; - word = Some(prev_top.as_usize() / 32); - color = Some(Color::Green); - } - } - - let text: Vec = memory - .chunks(32) - .enumerate() - .skip(draw_mem.current_mem_startline) - .map(|(i, mem_word)| { - let words: Vec = mem_word - .iter() - .map(|byte| { - Span::styled( - format!("{byte:02x} "), - if let (Some(w), Some(color)) = (word, color) { - if i == w { - Style::default().fg(color) - } else if *byte == 0 { - Style::default().add_modifier(Modifier::DIM) - } else { - Style::default().fg(Color::White) - } - } else if *byte == 0 { - Style::default().add_modifier(Modifier::DIM) - } else { - Style::default().fg(Color::White) - }, - ) - }) - .collect(); - - let mut spans = vec![Span::styled( - format!("{:0min_len$x}| ", i * 32), - Style::default().fg(Color::White), - )]; - spans.extend(words); - - if mem_utf8 { - let chars: Vec = mem_word - .chunks(4) - .map(|utf| { - if let Ok(utf_str) = std::str::from_utf8(utf) { - Span::raw(utf_str.replace(char::from(0), ".")) - } else { - Span::raw(".") - } - }) - .collect(); - spans.push(Span::raw("|")); - spans.extend(chars); - } - - spans.push(Span::raw("\n")); - - Spans::from(spans) - }) - .collect(); - let paragraph = Paragraph::new(text) - .block(stack_space) - .wrap(Wrap { trim: true }); - f.render_widget(paragraph, area); - } -} - -enum Interrupt { - KeyPressed(KeyEvent), - MouseEvent(MouseEvent), - IntervalElapsed, -} - -struct DrawMemory { - pub current_startline: usize, - pub inner_call_index: usize, - pub current_mem_startline: usize, - pub current_stack_startline: usize, -} - -impl DrawMemory { - fn default() -> Self { - DrawMemory { - current_startline: 0, - inner_call_index: 0, - current_mem_startline: 0, - current_stack_startline: 0, - } - } -} - -fn stack_indices_affected(op: u8) -> Vec<(usize, &'static str)> { - match op { - 0x01 => vec![(0, "a"), (1, "b")], - 0x02 => vec![(0, "a"), (1, "b")], - 0x03 => vec![(0, "a"), (1, "b")], - 0x04 => vec![(0, "a"), (1, "b")], - 0x05 => vec![(0, "a"), (1, "b")], - 0x06 => vec![(0, "a"), (1, "b")], - 0x07 => vec![(0, "a"), (1, "b")], - 0x08 => vec![(0, "a"), (1, "b"), (2, "mod")], - 0x09 => vec![(0, "a"), (1, "b"), (2, "mod")], - 0x0a => vec![(0, "base"), (1, "exp")], - 0x0b => vec![(0, "i"), (1, "a")], - 0x10 => vec![(0, "a"), (1, "b")], - 0x11 => vec![(0, "a"), (1, "b")], - 0x12 => vec![(0, "a"), (1, "b")], - 0x13 => vec![(0, "a"), (1, "b")], - 0x14 => vec![(0, "a"), (1, "b")], - 0x15 => vec![(0, "a")], - 0x16 => vec![(0, "a"), (1, "b")], - 0x17 => vec![(0, "a"), (1, "b")], - 0x18 => vec![(0, "a"), (1, "b")], - 0x19 => vec![(0, "a")], - 0x1a => vec![(0, "i"), (1, "a")], - 0x1b => vec![(0, "shift"), (1, "a")], - 0x1c => vec![(0, "shift"), (1, "a")], - 0x1d => vec![(0, "shift"), (1, "a")], - 0x20 => vec![(0, "offset"), (1, "length")], - 0x31 => vec![(0, "address")], - 0x35 => vec![(0, "offset")], - 0x37 => vec![(0, "dst"), (1, "src"), (2, "length")], - 0x39 => vec![(0, "dst"), (1, "src"), (2, "length")], - 0x3b => vec![(0, "address")], - 0x3c => vec![(0, "address"), (1, "dst"), (2, "src"), (3, "length")], - 0x3e => vec![(0, "dst"), (1, "src"), (2, "length")], - 0x3f => vec![(0, "address")], - 0x40 => vec![(0, "number")], - 0x50 => vec![(0, "a")], - 0x51 => vec![(0, "offset")], - 0x52 => vec![(0, "offset"), (1, "a")], - 0x53 => vec![(0, "offset"), (1, "a")], - 0x54 => vec![(0, "key")], - 0x55 => vec![(0, "key"), (1, "a")], - 0x56 => vec![(0, "dst")], - 0x57 => vec![(0, "dst"), (1, "cond")], - 0x80 => vec![(0, "a")], - 0x81 => vec![(1, "a")], - 0x82 => vec![(2, "a")], - 0x83 => vec![(3, "a")], - 0x84 => vec![(4, "a")], - 0x85 => vec![(5, "a")], - 0x86 => vec![(6, "a")], - 0x87 => vec![(7, "a")], - 0x88 => vec![(8, "a")], - 0x89 => vec![(9, "a")], - 0x8a => vec![(10, "a")], - 0x8b => vec![(11, "a")], - 0x8c => vec![(12, "a")], - 0x8d => vec![(13, "a")], - 0x8e => vec![(14, "a")], - 0x8f => vec![(15, "a")], - 0x90 => vec![(0, "a"), (1, "a")], - 0x91 => vec![(0, "a"), (2, "a")], - 0x92 => vec![(0, "a"), (3, "a")], - 0x93 => vec![(0, "a"), (4, "a")], - 0x94 => vec![(0, "a"), (5, "a")], - 0x95 => vec![(0, "a"), (6, "a")], - 0x96 => vec![(0, "a"), (7, "a")], - 0x97 => vec![(0, "a"), (8, "a")], - 0x98 => vec![(0, "a"), (9, "a")], - 0x99 => vec![(0, "a"), (10, "a")], - 0x9a => vec![(0, "a"), (11, "a")], - 0x9b => vec![(0, "a"), (12, "a")], - 0x9c => vec![(0, "a"), (13, "a")], - 0x9d => vec![(0, "a"), (14, "a")], - 0x9e => vec![(0, "a"), (15, "a")], - 0x9f => vec![(0, "a"), (16, "a")], - 0xa0 => vec![(0, "offset"), (1, "length")], - 0xa1 => vec![(0, "offset"), (1, "length"), (2, "topic")], - 0xa2 => vec![(0, "offset"), (1, "length"), (2, "topic1"), (3, "topic2")], - 0xa3 => vec![ - (0, "offset"), - (1, "length"), - (2, "topic1"), - (3, "topic2"), - (4, "topic3"), - ], - 0xa4 => vec![ - (0, "offset"), - (1, "length"), - (2, "topic1"), - (3, "topic2"), - (4, "topic3"), - (5, "topic4"), - ], - 0xf0 => vec![(0, "value"), (1, "offset"), (2, "length")], - 0xf1 => vec![ - (0, "gas"), - (1, "address"), - (2, "value"), - (3, "cd_offset"), - (4, "cd_length"), - (5, "rd_offset"), - (6, "rd_length"), - ], - 0xf2 => vec![ - (0, "gas"), - (1, "address"), - (2, "value"), - (3, "cd_offset"), - (4, "cd_length"), - (5, "rd_offset"), - (6, "rd_length"), - ], - 0xf3 => vec![(0, "offset"), (1, "length")], - 0xf4 => vec![ - (0, "gas"), - (1, "address"), - (2, "cd_offset"), - (3, "cd_length"), - (4, "rd_offset"), - (5, "rd_length"), - ], - 0xf5 => vec![(0, "value"), (1, "offset"), (2, "length"), (3, "salt")], - 0xfa => vec![ - (0, "gas"), - (1, "address"), - (2, "cd_offset"), - (3, "cd_length"), - (4, "rd_offset"), - (5, "rd_length"), - ], - 0xfd => vec![(0, "offset"), (1, "length")], - 0xff => vec![(0, "address")], - _ => vec![], - } -} diff --git a/snark-verifier/src/loader/evm/util.rs b/snark-verifier/src/loader/evm/util.rs index a84df4c3..031e85b9 100644 --- a/snark-verifier/src/loader/evm/util.rs +++ b/snark-verifier/src/loader/evm/util.rs @@ -8,12 +8,11 @@ use std::{ process::{Command, Stdio}, }; -pub use primitive_types::{H160 as Address, H256, U256, U512}; +pub use executor::deploy_and_call; +pub use revm::primitives::ruint::aliases::{B160 as Address, B256, U256, U512}; pub(crate) mod executor; -pub use executor::ExecutorBuilder; - /// Memory chunk in EVM. #[derive(Debug)] pub struct MemoryChunk { @@ -55,7 +54,7 @@ pub fn fe_to_u256(f: F) -> U256 where F: PrimeField, { - U256::from_little_endian(f.to_repr().as_ref()) + U256::from_le_bytes(f.to_repr()) } /// Convert a [`U256`] into a [`PrimeField`]. @@ -64,9 +63,7 @@ where F: PrimeField, { let value = value % modulus::(); - let mut repr = F::Repr::default(); - value.to_little_endian(repr.as_mut()); - F::from_repr(repr).unwrap() + F::from_repr(value.to_le_bytes::<32>()).unwrap() } /// Returns modulus of [`PrimeField`] as [`U256`]. @@ -74,7 +71,7 @@ pub fn modulus() -> U256 where F: PrimeField, { - U256::from_little_endian((-F::one()).to_repr().as_ref()) + 1 + U256::from_le_bytes((-F::ONE).to_repr()) + U256::from(1) } /// Encode instances and proof into calldata. @@ -104,13 +101,12 @@ pub fn estimate_gas(cost: Cost) -> usize { intrinsic_cost + calldata_cost + ec_operation_cost } -/// Compile given yul `code` into deployment bytecode. -pub fn compile_yul(code: &str) -> Vec { +/// Compile given Solidity `code` into deployment bytecode. +pub fn compile_solidity(code: &str) -> Vec { let mut cmd = Command::new("solc") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .arg("--bin") - .arg("--yul") .arg("-") .spawn() .unwrap(); diff --git a/snark-verifier/src/loader/evm/util/executor.rs b/snark-verifier/src/loader/evm/util/executor.rs index a7697a0e..e2c5bb2c 100644 --- a/snark-verifier/src/loader/evm/util/executor.rs +++ b/snark-verifier/src/loader/evm/util/executor.rs @@ -1,875 +1,59 @@ -//! Copied and modified from -//! - -use crate::loader::evm::{Address, H256, U256}; -use bytes::Bytes; use revm::{ - evm_inner, opcode, spec_opcode_gas, Account, BlockEnv, CallInputs, CallScheme, CreateInputs, - CreateScheme, Database, DatabaseCommit, EVMData, Env, ExecutionResult, Gas, GasInspector, - InMemoryDB, Inspector, Interpreter, Memory, OpCode, Return, TransactOut, TransactTo, TxEnv, + primitives::{CreateScheme, ExecutionResult, Output, TransactTo, TxEnv}, + InMemoryDB, EVM, }; -use sha3::{Digest, Keccak256}; -use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc}; -macro_rules! return_ok { - () => { - Return::Continue | Return::Stop | Return::Return | Return::SelfDestruct +/// Deploy contract and then call with calldata. +/// Returns gas_used of call to deployed contract if both transactions are successful. +pub fn deploy_and_call(deployment_code: Vec, calldata: Vec) -> Result { + let mut evm = EVM { + env: Default::default(), + db: Some(InMemoryDB::default()), }; -} - -fn keccak256(data: impl AsRef<[u8]>) -> [u8; 32] { - Keccak256::digest(data.as_ref()).into() -} - -fn get_contract_address(sender: impl Into
, nonce: impl Into) -> Address { - let mut stream = rlp::RlpStream::new(); - stream.begin_list(2); - stream.append(&sender.into()); - stream.append(&nonce.into()); - - let hash = keccak256(&stream.out()); - - let mut bytes = [0u8; 20]; - bytes.copy_from_slice(&hash[12..]); - Address::from(bytes) -} - -fn get_create2_address( - from: impl Into
, - salt: [u8; 32], - init_code: impl Into, -) -> Address { - get_create2_address_from_hash(from, salt, keccak256(init_code.into().as_ref()).to_vec()) -} - -fn get_create2_address_from_hash( - from: impl Into
, - salt: [u8; 32], - init_code_hash: impl Into, -) -> Address { - let bytes = [ - &[0xff], - from.into().as_bytes(), - salt.as_slice(), - init_code_hash.into().as_ref(), - ] - .concat(); - - let hash = keccak256(bytes); - - let mut bytes = [0u8; 20]; - bytes.copy_from_slice(&hash[12..]); - Address::from(bytes) -} - -fn get_create_address(call: &CreateInputs, nonce: u64) -> Address { - match call.scheme { - CreateScheme::Create => get_contract_address(call.caller, nonce), - CreateScheme::Create2 { salt } => { - let mut buffer: [u8; 4 * 8] = [0; 4 * 8]; - salt.to_big_endian(&mut buffer); - get_create2_address(call.caller, buffer, call.init_code.clone()) - } - } -} - -#[derive(Clone, Debug, Default)] -pub struct Log { - pub address: Address, - pub topics: Vec, - pub data: Bytes, -} - -#[derive(Clone, Debug, Default)] -struct LogCollector { - logs: Vec, -} - -impl Inspector for LogCollector { - fn log(&mut self, _: &mut EVMData<'_, DB>, address: &Address, topics: &[H256], data: &Bytes) { - self.logs.push(Log { - address: *address, - topics: topics.to_vec(), - data: data.clone(), - }); - } - - fn call( - &mut self, - _: &mut EVMData<'_, DB>, - call: &mut CallInputs, - _: bool, - ) -> (Return, Gas, Bytes) { - (Return::Continue, Gas::new(call.gas_limit), Bytes::new()) - } -} - -#[derive(Clone, Debug, Copy)] -pub enum CallKind { - Call, - StaticCall, - CallCode, - DelegateCall, - Create, - Create2, -} - -impl Default for CallKind { - fn default() -> Self { - CallKind::Call - } -} - -impl From for CallKind { - fn from(scheme: CallScheme) -> Self { - match scheme { - CallScheme::Call => CallKind::Call, - CallScheme::StaticCall => CallKind::StaticCall, - CallScheme::CallCode => CallKind::CallCode, - CallScheme::DelegateCall => CallKind::DelegateCall, - } - } -} - -impl From for CallKind { - fn from(create: CreateScheme) -> Self { - match create { - CreateScheme::Create => CallKind::Create, - CreateScheme::Create2 { .. } => CallKind::Create2, - } - } -} - -#[derive(Clone, Debug, Default)] -pub struct DebugArena { - pub arena: Vec, -} - -impl DebugArena { - fn push_node(&mut self, mut new_node: DebugNode) -> usize { - fn recursively_push( - arena: &mut Vec, - entry: usize, - mut new_node: DebugNode, - ) -> usize { - match new_node.depth { - _ if arena[entry].depth == new_node.depth - 1 => { - let id = arena.len(); - new_node.location = arena[entry].children.len(); - new_node.parent = Some(entry); - arena[entry].children.push(id); - arena.push(new_node); - id - } - _ => { - let child = *arena[entry].children.last().unwrap(); - recursively_push(arena, child, new_node) - } - } - } - - if self.arena.is_empty() { - self.arena.push(new_node); - 0 - } else if new_node.depth == 0 { - let id = self.arena.len(); - new_node.location = self.arena[0].children.len(); - new_node.parent = Some(0); - self.arena[0].children.push(id); - self.arena.push(new_node); - id - } else { - recursively_push(&mut self.arena, 0, new_node) - } - } - - #[cfg(test)] - pub fn flatten(&self, entry: usize) -> Vec<(Address, Vec, CallKind)> { - let node = &self.arena[entry]; - - let mut flattened = vec![]; - if !node.steps.is_empty() { - flattened.push((node.address, node.steps.clone(), node.kind)); - } - flattened.extend(node.children.iter().flat_map(|child| self.flatten(*child))); - - flattened - } -} - -#[derive(Clone, Debug, Default)] -pub struct DebugNode { - pub parent: Option, - pub children: Vec, - pub location: usize, - pub address: Address, - pub kind: CallKind, - pub depth: usize, - pub steps: Vec, -} - -#[derive(Clone, Debug)] -pub struct DebugStep { - pub stack: Vec, - pub memory: Memory, - pub instruction: Instruction, - pub push_bytes: Option>, - pub pc: usize, - pub total_gas_used: u64, -} - -impl Default for DebugStep { - fn default() -> Self { - Self { - stack: vec![], - memory: Memory::new(), - instruction: Instruction(revm::opcode::INVALID), - push_bytes: None, - pc: 0, - total_gas_used: 0, - } - } -} - -impl DebugStep { - #[cfg(test)] - pub fn pretty_opcode(&self) -> String { - if let Some(push_bytes) = &self.push_bytes { - format!("{}(0x{})", self.instruction, hex::encode(push_bytes)) - } else { - self.instruction.to_string() - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct Instruction(pub u8); - -impl From for Instruction { - fn from(op: u8) -> Instruction { - Instruction(op) - } -} - -impl Display for Instruction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - OpCode::try_from_u8(self.0).map_or_else( - || format!("UNDEFINED(0x{:02x})", self.0), - |opcode| opcode.as_str().to_string(), - ) - ) - } -} - -#[derive(Clone, Debug)] -struct Debugger { - arena: DebugArena, - head: usize, - context: Address, - gas_inspector: Rc>, -} - -impl Debugger { - fn new(gas_inspector: Rc>) -> Self { - Self { - arena: Default::default(), - head: Default::default(), - context: Default::default(), - gas_inspector, - } - } - - fn enter(&mut self, depth: usize, address: Address, kind: CallKind) { - self.context = address; - self.head = self.arena.push_node(DebugNode { - depth, - address, - kind, - ..Default::default() - }); - } - - fn exit(&mut self) { - if let Some(parent_id) = self.arena.arena[self.head].parent { - let DebugNode { - depth, - address, - kind, - .. - } = self.arena.arena[parent_id]; - self.context = address; - self.head = self.arena.push_node(DebugNode { - depth, - address, - kind, - ..Default::default() - }); - } - } -} - -impl Inspector for Debugger { - fn step( - &mut self, - interpreter: &mut Interpreter, - data: &mut EVMData<'_, DB>, - _is_static: bool, - ) -> Return { - let pc = interpreter.program_counter(); - let op = interpreter.contract.bytecode.bytecode()[pc]; - - let opcode_infos = spec_opcode_gas(data.env.cfg.spec_id); - let opcode_info = &opcode_infos[op as usize]; - - let push_size = if opcode_info.is_push() { - (op - opcode::PUSH1 + 1) as usize - } else { - 0 - }; - let push_bytes = match push_size { - 0 => None, - n => { - let start = pc + 1; - let end = start + n; - Some(interpreter.contract.bytecode.bytecode()[start..end].to_vec()) - } - }; - - let spent = interpreter.gas.limit() - self.gas_inspector.borrow().gas_remaining(); - let total_gas_used = spent - (interpreter.gas.refunded() as u64).min(spent / 5); - - self.arena.arena[self.head].steps.push(DebugStep { - pc, - stack: interpreter.stack().data().clone(), - memory: interpreter.memory.clone(), - instruction: Instruction(op), - push_bytes, - total_gas_used, - }); - - Return::Continue - } - - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - _: bool, - ) -> (Return, Gas, Bytes) { - self.enter( - data.journaled_state.depth() as usize, - call.context.code_address, - call.context.scheme.into(), - ); - - (Return::Continue, Gas::new(call.gas_limit), Bytes::new()) - } - - fn call_end( - &mut self, - _: &mut EVMData<'_, DB>, - _: &CallInputs, - gas: Gas, - status: Return, - retdata: Bytes, - _: bool, - ) -> (Return, Gas, Bytes) { - self.exit(); - (status, gas, retdata) - } - - fn create( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - ) -> (Return, Option
, Gas, Bytes) { - let nonce = data.journaled_state.account(call.caller).info.nonce; - self.enter( - data.journaled_state.depth() as usize, - get_create_address(call, nonce), - CallKind::Create, - ); - - ( - Return::Continue, - None, - Gas::new(call.gas_limit), - Bytes::new(), - ) - } - - fn create_end( - &mut self, - _: &mut EVMData<'_, DB>, - _: &CreateInputs, - status: Return, - address: Option
, - gas: Gas, - retdata: Bytes, - ) -> (Return, Option
, Gas, Bytes) { - self.exit(); - - (status, address, gas, retdata) - } -} - -macro_rules! call_inspectors { - ($id:ident, [ $($inspector:expr),+ ], $call:block) => { - $({ - if let Some($id) = $inspector { - $call; - } - })+ - } -} - -struct InspectorData { - logs: Vec, - debug: Option, -} - -#[derive(Default)] -struct InspectorStack { - gas: Option>>, - logs: Option, - debugger: Option, -} - -impl InspectorStack { - fn collect_inspector_states(self) -> InspectorData { - InspectorData { - logs: self.logs.map(|logs| logs.logs).unwrap_or_default(), - debug: self.debugger.map(|debugger| debugger.arena), - } - } -} - -impl Inspector for InspectorStack { - fn initialize_interp( - &mut self, - interpreter: &mut Interpreter, - data: &mut EVMData<'_, DB>, - is_static: bool, - ) -> Return { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.logs, - &mut self.debugger - ], - { - let status = inspector.initialize_interp(interpreter, data, is_static); - - if status != Return::Continue { - return status; - } - } - ); - - Return::Continue - } - - fn step( - &mut self, - interpreter: &mut Interpreter, - data: &mut EVMData<'_, DB>, - is_static: bool, - ) -> Return { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.logs, - &mut self.debugger - ], - { - let status = inspector.step(interpreter, data, is_static); - - if status != Return::Continue { - return status; - } - } - ); - - Return::Continue - } - - fn log( - &mut self, - evm_data: &mut EVMData<'_, DB>, - address: &Address, - topics: &[H256], - data: &Bytes, - ) { - call_inspectors!(inspector, [&mut self.logs], { - inspector.log(evm_data, address, topics, data); - }); - } - - fn step_end( - &mut self, - interpreter: &mut Interpreter, - data: &mut EVMData<'_, DB>, - is_static: bool, - status: Return, - ) -> Return { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.logs, - &mut self.debugger - ], - { - let status = inspector.step_end(interpreter, data, is_static, status); - - if status != Return::Continue { - return status; - } - } - ); - - Return::Continue - } - - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - is_static: bool, - ) -> (Return, Gas, Bytes) { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.logs, - &mut self.debugger - ], - { - let (status, gas, retdata) = inspector.call(data, call, is_static); - - if status != Return::Continue { - return (status, gas, retdata); - } - } - ); - - (Return::Continue, Gas::new(call.gas_limit), Bytes::new()) - } - - fn call_end( - &mut self, - data: &mut EVMData<'_, DB>, - call: &CallInputs, - remaining_gas: Gas, - status: Return, - retdata: Bytes, - is_static: bool, - ) -> (Return, Gas, Bytes) { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.logs, - &mut self.debugger - ], - { - let (new_status, new_gas, new_retdata) = inspector.call_end( - data, - call, - remaining_gas, - status, - retdata.clone(), - is_static, - ); - - if new_status != status || (new_status == Return::Revert && new_retdata != retdata) - { - return (new_status, new_gas, new_retdata); - } - } - ); - - (status, remaining_gas, retdata) - } - - fn create( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - ) -> (Return, Option
, Gas, Bytes) { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.logs, - &mut self.debugger - ], - { - let (status, addr, gas, retdata) = inspector.create(data, call); - - if status != Return::Continue { - return (status, addr, gas, retdata); - } - } - ); - - ( - Return::Continue, - None, - Gas::new(call.gas_limit), - Bytes::new(), - ) - } - - fn create_end( - &mut self, - data: &mut EVMData<'_, DB>, - call: &CreateInputs, - status: Return, - address: Option
, - remaining_gas: Gas, - retdata: Bytes, - ) -> (Return, Option
, Gas, Bytes) { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.logs, - &mut self.debugger - ], - { - let (new_status, new_address, new_gas, new_retdata) = inspector.create_end( - data, - call, - status, - address, - remaining_gas, - retdata.clone(), - ); - - if new_status != status { - return (new_status, new_address, new_gas, new_retdata); - } - } - ); - - (status, address, remaining_gas, retdata) - } - - fn selfdestruct(&mut self) { - call_inspectors!(inspector, [&mut self.logs, &mut self.debugger], { - Inspector::::selfdestruct(inspector); - }); - } -} - -/// Call result. -#[derive(Debug)] -pub struct RawCallResult { - /// Exit reason - pub exit_reason: Return, - /// If the call is reverted or not. - pub reverted: bool, - /// Returndata - pub result: Bytes, - /// Gas used - pub gas_used: u64, - /// Gas refunded - pub gas_refunded: u64, - /// Logs emitted during the call - pub logs: Vec, - /// Debug information if any - pub debug: Option, - /// State changes if any - pub state_changeset: Option>, - /// Environment - pub env: Env, - /// Output - pub out: TransactOut, -} - -#[derive(Clone, Debug)] -pub struct DeployResult { - pub exit_reason: Return, - pub reverted: bool, - pub address: Option
, - pub gas_used: u64, - pub gas_refunded: u64, - pub logs: Vec, - pub debug: Option, - pub env: Env, -} - -/// Executor builder. -#[derive(Debug, Default)] -pub struct ExecutorBuilder { - debugger: bool, - gas_limit: Option, -} - -impl ExecutorBuilder { - /// Set `debugger`. - pub fn set_debugger(mut self, enable: bool) -> Self { - self.debugger = enable; - self - } - - /// Set `gas_limit`. - pub fn with_gas_limit(mut self, gas_limit: U256) -> Self { - self.gas_limit = Some(gas_limit); - self - } - - /// Initialize an `Executor`. - pub fn build(self) -> Executor { - Executor::new(self.debugger, self.gas_limit.unwrap_or(U256::MAX)) - } -} - -#[derive(Clone, Debug)] -pub struct Executor { - db: InMemoryDB, - debugger: bool, - gas_limit: U256, -} - -impl Executor { - fn new(debugger: bool, gas_limit: U256) -> Self { - Executor { - db: InMemoryDB::default(), - debugger, - gas_limit, - } - } - - pub fn db_mut(&mut self) -> &mut InMemoryDB { - &mut self.db - } - - pub fn deploy(&mut self, from: Address, code: Bytes, value: U256) -> DeployResult { - let env = self.build_test_env(from, TransactTo::Create(CreateScheme::Create), code, value); - let result = self.call_raw_with_env(env); - self.commit(&result); - - let RawCallResult { - exit_reason, - out, - gas_used, - gas_refunded, - logs, - debug, - env, - .. - } = result; - - let address = match (exit_reason, out) { - (return_ok!(), TransactOut::Create(_, Some(address))) => Some(address), - _ => None, - }; - - DeployResult { - exit_reason, - reverted: !matches!(exit_reason, return_ok!()), - address, - gas_used, - gas_refunded, - logs, - debug, - env, - } - } - - pub fn call_raw( - &self, - from: Address, - to: Address, - calldata: Bytes, - value: U256, - ) -> RawCallResult { - let env = self.build_test_env(from, TransactTo::Call(to), calldata, value); - self.call_raw_with_env(env) - } + evm.env.tx = TxEnv { + gas_limit: u64::MAX, + transact_to: TransactTo::Create(CreateScheme::Create), + data: deployment_code.into(), + ..Default::default() + }; - fn call_raw_with_env(&self, mut env: Env) -> RawCallResult { - let mut inspector = self.inspector(); - let result = - evm_inner::<_, true>(&mut env, &mut self.db.clone(), &mut inspector).transact(); - let (exec_result, state_changeset) = result; - let ExecutionResult { - exit_reason, - gas_refunded, - gas_used, - out, + let result = evm.transact_commit().unwrap(); + let contract = match result { + ExecutionResult::Success { + output: Output::Create(_, Some(contract)), .. - } = exec_result; - - let result = match out { - TransactOut::Call(ref data) => data.to_owned(), - _ => Bytes::default(), - }; - let InspectorData { logs, debug } = inspector.collect_inspector_states(); - - RawCallResult { - exit_reason, - reverted: !matches!(exit_reason, return_ok!()), - result, - gas_used, - gas_refunded, - logs: logs.to_vec(), - debug, - state_changeset: Some(state_changeset.into_iter().collect()), - env, - out, - } - } - - fn commit(&mut self, result: &RawCallResult) { - if let Some(state_changeset) = result.state_changeset.as_ref() { - self.db - .commit(state_changeset.clone().into_iter().collect()); - } - } + } => contract, + ExecutionResult::Revert { gas_used, output } => { + return Err(format!( + "Contract deployment transaction reverts with gas_used {gas_used} and output {:#x}", + output + )) + } + ExecutionResult::Halt { reason, gas_used } => return Err(format!( + "Contract deployment transaction halts unexpectedly with gas_used {gas_used} and reason {:?}", + reason + )), + _ => unreachable!(), + }; - fn inspector(&self) -> InspectorStack { - let mut stack = InspectorStack { - logs: Some(LogCollector::default()), - ..Default::default() - }; - if self.debugger { - let gas_inspector = Rc::new(RefCell::new(GasInspector::default())); - stack.gas = Some(gas_inspector.clone()); - stack.debugger = Some(Debugger::new(gas_inspector)); - } - stack - } + evm.env.tx = TxEnv { + gas_limit: u64::MAX, + transact_to: TransactTo::Call(contract), + data: calldata.into(), + ..Default::default() + }; - fn build_test_env( - &self, - caller: Address, - transact_to: TransactTo, - data: Bytes, - value: U256, - ) -> Env { - Env { - block: BlockEnv { - gas_limit: self.gas_limit, - ..BlockEnv::default() - }, - tx: TxEnv { - caller, - transact_to, - data, - value, - gas_limit: self.gas_limit.as_u64(), - ..TxEnv::default() - }, - ..Env::default() - } + let result = evm.transact_commit().unwrap(); + match result { + ExecutionResult::Success { gas_used, .. } => Ok(gas_used), + ExecutionResult::Revert { gas_used, output } => Err(format!( + "Contract call transaction reverts with gas_used {gas_used} and output {:#x}", + output + )), + ExecutionResult::Halt { reason, gas_used } => Err(format!( + "Contract call transaction halts unexpectedly with gas_used {gas_used} and reason {:?}", + reason + )), } } diff --git a/snark-verifier/src/loader/halo2/loader.rs b/snark-verifier/src/loader/halo2/loader.rs index 31be9841..f8e1da7d 100644 --- a/snark-verifier/src/loader/halo2/loader.rs +++ b/snark-verifier/src/loader/halo2/loader.rs @@ -136,15 +136,15 @@ impl> Halo2Loader { | (Value::Constant(constant), Value::Assigned(assigned)) => { Value::Assigned(self.scalar_chip().sum_with_coeff_and_const( &mut self.ctx_mut(), - &[(C::Scalar::one(), assigned)], + &[(C::Scalar::ONE, assigned)], *constant, )) } (Value::Assigned(lhs), Value::Assigned(rhs)) => { Value::Assigned(self.scalar_chip().sum_with_coeff_and_const( &mut self.ctx_mut(), - &[(C::Scalar::one(), lhs), (C::Scalar::one(), rhs)], - C::Scalar::zero(), + &[(C::Scalar::ONE, lhs), (C::Scalar::ONE, rhs)], + C::Scalar::ZERO, )) } }; @@ -161,14 +161,14 @@ impl> Halo2Loader { (Value::Constant(constant), Value::Assigned(assigned)) => { Value::Assigned(self.scalar_chip().sum_with_coeff_and_const( &mut self.ctx_mut(), - &[(-C::Scalar::one(), assigned)], + &[(-C::Scalar::ONE, assigned)], *constant, )) } (Value::Assigned(assigned), Value::Constant(constant)) => { Value::Assigned(self.scalar_chip().sum_with_coeff_and_const( &mut self.ctx_mut(), - &[(C::Scalar::one(), assigned)], + &[(C::Scalar::ONE, assigned)], -*constant, )) } @@ -191,14 +191,14 @@ impl> Halo2Loader { Value::Assigned(self.scalar_chip().sum_with_coeff_and_const( &mut self.ctx_mut(), &[(*constant, assigned)], - C::Scalar::zero(), + C::Scalar::ZERO, )) } (Value::Assigned(lhs), Value::Assigned(rhs)) => { Value::Assigned(self.scalar_chip().sum_products_with_coeff_and_const( &mut self.ctx_mut(), - &[(C::Scalar::one(), lhs, rhs)], - C::Scalar::zero(), + &[(C::Scalar::ONE, lhs, rhs)], + C::Scalar::ZERO, )) } }; @@ -306,6 +306,14 @@ impl> LoadedScalar for Sc fn loader(&self) -> &Self::Loader { &self.loader } + + fn pow_var(&self, exp: &Self, max_bits: usize) -> Self { + let loader = self.loader(); + let base = self.clone().into_assigned(); + let exp = exp.clone().into_assigned(); + let res = loader.scalar_chip().pow_var(&mut loader.ctx_mut(), &base, &exp, max_bits); + loader.scalar_from_assigned(res) + } } impl> Debug for Scalar { @@ -557,7 +565,7 @@ impl> EcPointLoader for Rc + if scalar.eq(&C::Scalar::ONE) => { variable_base_non_scaled.push(base); } diff --git a/snark-verifier/src/loader/halo2/shim.rs b/snark-verifier/src/loader/halo2/shim.rs index 790c9e22..80d5eae2 100644 --- a/snark-verifier/src/loader/halo2/shim.rs +++ b/snark-verifier/src/loader/halo2/shim.rs @@ -1,8 +1,8 @@ -use crate::util::arithmetic::{CurveAffine, FieldExt}; +use crate::util::arithmetic::{CurveAffine, PrimeField}; use std::{fmt::Debug, ops::Deref}; /// Instructions to handle field element operations. -pub trait IntegerInstructions: Clone + Debug { +pub trait IntegerInstructions: Clone + Debug { /// Context (either enhanced `region` or some kind of builder). type Context: Debug; /// Assigned cell. @@ -24,8 +24,8 @@ pub trait IntegerInstructions: Clone + Debug { fn sum_with_coeff_and_const( &self, ctx: &mut Self::Context, - values: &[(F::Scalar, impl Deref)], - constant: F::Scalar, + values: &[(F, impl Deref)], + constant: F, ) -> Self::AssignedInteger; /// Sum product of integers with coefficients and constant. @@ -33,11 +33,11 @@ pub trait IntegerInstructions: Clone + Debug { &self, ctx: &mut Self::Context, values: &[( - F::Scalar, + F, impl Deref, impl Deref, )], - constant: F::Scalar, + constant: F, ) -> Self::AssignedInteger; /// Returns `lhs - rhs`. @@ -65,6 +65,15 @@ pub trait IntegerInstructions: Clone + Debug { lhs: &Self::AssignedInteger, rhs: &Self::AssignedInteger, ); + + /// Returns `base^exponent` and constrains that `exponent` has at most `max_bits` bits. + fn pow_var( + &self, + ctx: &mut Self::Context, + base: &Self::AssignedInteger, + exponent: &Self::AssignedInteger, + max_bits: usize, + ) -> Self::AssignedInteger; } /// Instructions to handle elliptic curve point operations. @@ -132,68 +141,70 @@ mod halo2_lib { use crate::halo2_proofs::halo2curves::CurveAffineExt; use crate::{ loader::halo2::{EccInstructions, IntegerInstructions}, - util::arithmetic::CurveAffine, + util::arithmetic::{CurveAffine, PrimeField}, }; + use halo2_base::gates::flex_gate::threads::SinglePhaseCoreManager; use halo2_base::{ self, - gates::{builder::GateThreadBuilder, GateChip, GateInstructions, RangeInstructions}, + gates::{GateChip, GateInstructions, RangeInstructions}, + utils::BigPrimeField, AssignedValue, QuantumCell::{Constant, Existing}, }; use halo2_ecc::bigint::ProperCrtUint; use halo2_ecc::{ ecc::{BaseFieldEccChip, EcPoint}, - fields::{FieldChip, PrimeField}, + fields::FieldChip, }; use std::ops::Deref; type AssignedInteger = ProperCrtUint<::ScalarExt>; type AssignedEcPoint = EcPoint<::ScalarExt, AssignedInteger>; - impl IntegerInstructions for GateChip { - type Context = GateThreadBuilder; + impl IntegerInstructions for GateChip { + type Context = SinglePhaseCoreManager; type AssignedCell = AssignedValue; type AssignedInteger = AssignedValue; fn assign_integer(&self, ctx: &mut Self::Context, integer: F) -> Self::AssignedInteger { - ctx.main(0).load_witness(integer) + ctx.main().load_witness(integer) } fn assign_constant(&self, ctx: &mut Self::Context, integer: F) -> Self::AssignedInteger { - ctx.main(0).load_constant(integer) + ctx.main().load_constant(integer) } fn sum_with_coeff_and_const( &self, ctx: &mut Self::Context, - values: &[(F::Scalar, impl Deref)], + values: &[(F, impl Deref)], constant: F, ) -> Self::AssignedInteger { let mut a = Vec::with_capacity(values.len() + 1); let mut b = Vec::with_capacity(values.len() + 1); - if constant != F::zero() { + if constant != F::ZERO { a.push(Constant(constant)); - b.push(Constant(F::one())); + b.push(Constant(F::ONE)); } a.extend(values.iter().map(|(_, a)| Existing(*a.deref()))); b.extend(values.iter().map(|(c, _)| Constant(*c))); - self.inner_product(ctx.main(0), a, b) + self.inner_product(ctx.main(), a, b) } fn sum_products_with_coeff_and_const( &self, ctx: &mut Self::Context, values: &[( - F::Scalar, + F, impl Deref, impl Deref, )], constant: F, ) -> Self::AssignedInteger { match values.len() { - 0 => ctx.main(0).load_constant(constant), + 0 => ctx.main().load_constant(constant), _ => self.sum_products_with_coeff_and_var( - ctx.main(0), + ctx.main(), values.iter().map(|(c, a, b)| (*c, Existing(*a.deref()), Existing(*b.deref()))), Constant(constant), ), @@ -206,11 +217,11 @@ mod halo2_lib { a: &Self::AssignedInteger, b: &Self::AssignedInteger, ) -> Self::AssignedInteger { - GateInstructions::sub(self, ctx.main(0), Existing(*a), Existing(*b)) + GateInstructions::sub(self, ctx.main(), Existing(*a), Existing(*b)) } fn neg(&self, ctx: &mut Self::Context, a: &Self::AssignedInteger) -> Self::AssignedInteger { - GateInstructions::neg(self, ctx.main(0), Existing(*a)) + GateInstructions::neg(self, ctx.main(), Existing(*a)) } fn invert( @@ -219,9 +230,9 @@ mod halo2_lib { a: &Self::AssignedInteger, ) -> Self::AssignedInteger { // make sure scalar != 0 - let is_zero = self.is_zero(ctx.main(0), *a); - self.assert_is_const(ctx.main(0), &is_zero, &F::zero()); - GateInstructions::div_unsafe(self, ctx.main(0), Constant(F::one()), Existing(*a)) + let is_zero = self.is_zero(ctx.main(), *a); + self.assert_is_const(ctx.main(), &is_zero, &F::ZERO); + GateInstructions::div_unsafe(self, ctx.main(), Constant(F::ONE), Existing(*a)) } fn assert_equal( @@ -230,16 +241,26 @@ mod halo2_lib { a: &Self::AssignedInteger, b: &Self::AssignedInteger, ) { - ctx.main(0).constrain_equal(a, b); + ctx.main().constrain_equal(a, b); + } + + fn pow_var( + &self, + ctx: &mut Self::Context, + base: &Self::AssignedInteger, + exponent: &Self::AssignedInteger, + max_bits: usize, + ) -> Self::AssignedInteger { + GateInstructions::pow_var(self, ctx.main(), *base, *exponent, max_bits) } } impl<'chip, C: CurveAffineExt> EccInstructions for BaseFieldEccChip<'chip, C> where - C::ScalarExt: PrimeField, - C::Base: PrimeField, + C::ScalarExt: BigPrimeField, + C::Base: BigPrimeField, { - type Context = GateThreadBuilder; + type Context = SinglePhaseCoreManager; type ScalarChip = GateChip; type AssignedCell = AssignedValue; type AssignedScalar = AssignedValue; @@ -250,11 +271,11 @@ mod halo2_lib { } fn assign_constant(&self, ctx: &mut Self::Context, point: C) -> Self::AssignedEcPoint { - self.assign_constant_point(ctx.main(0), point) + self.assign_constant_point(ctx.main(), point) } fn assign_point(&self, ctx: &mut Self::Context, point: C) -> Self::AssignedEcPoint { - self.assign_point(ctx.main(0), point) + self.assign_point(ctx.main(), point) } fn sum_with_const( @@ -270,7 +291,7 @@ mod halo2_lib { Some(constant) }; self.sum::( - ctx.main(0), + ctx.main(), constant.into_iter().chain(values.iter().map(|v| v.deref().clone())), ) } @@ -326,7 +347,7 @@ mod halo2_lib { a: &Self::AssignedEcPoint, b: &Self::AssignedEcPoint, ) { - self.assert_equal(ctx.main(0), a.clone(), b.clone()); + self.assert_equal(ctx.main(), a.clone(), b.clone()); } } } diff --git a/snark-verifier/src/loader/native.rs b/snark-verifier/src/loader/native.rs index 783aaa89..a9aa86ff 100644 --- a/snark-verifier/src/loader/native.rs +++ b/snark-verifier/src/loader/native.rs @@ -2,7 +2,7 @@ use crate::{ loader::{EcPointLoader, LoadedEcPoint, LoadedScalar, Loader, ScalarLoader}, - util::arithmetic::{Curve, CurveAffine, FieldOps, PrimeField}, + util::arithmetic::{fe_to_big, Curve, CurveAffine, FieldOps, PrimeField}, Error, }; use lazy_static::lazy_static; @@ -38,6 +38,11 @@ impl LoadedScalar for F { fn loader(&self) -> &NativeLoader { &LOADER } + + fn pow_var(&self, exp: &Self, _: usize) -> Self { + let exp = fe_to_big(*exp).to_u64_digits(); + self.pow_vartime(exp) + } } impl EcPointLoader for NativeLoader { diff --git a/snark-verifier/src/pcs.rs b/snark-verifier/src/pcs.rs index 65b1325b..1ca9eedc 100644 --- a/snark-verifier/src/pcs.rs +++ b/snark-verifier/src/pcs.rs @@ -3,7 +3,7 @@ use crate::{ loader::{native::NativeLoader, Loader}, util::{ - arithmetic::{CurveAffine, PrimeField}, + arithmetic::{CurveAffine, Rotation}, msm::Msm, transcript::{TranscriptRead, TranscriptWrite}, }, @@ -18,24 +18,26 @@ pub mod kzg; /// Query to an oracle. /// It assumes all queries are based on the same point, but with some `shift`. #[derive(Clone, Debug)] -pub struct Query { +pub struct Query { /// Index of polynomial to query pub poly: usize, /// Shift of the query point. - pub shift: F, + pub shift: S, + /// Shift loaded as either constant or witness. It is user's job to ensure this is correctly constrained to have value equal to `shift` + pub loaded_shift: T, /// Evaluation read from transcript. pub eval: T, } -impl Query { +impl Query { /// Initialize [`Query`] without evaluation. - pub fn new(poly: usize, shift: F) -> Self { - Self { poly, shift, eval: () } + pub fn new(poly: usize, shift: S) -> Self { + Self { poly, shift, loaded_shift: (), eval: () } } - /// Returns [`Query`] with evaluation. - pub fn with_evaluation(self, eval: T) -> Query { - Query { poly: self.poly, shift: self.shift, eval } + /// Returns [`Query`] with evaluation and optionally the shift are loaded as. + pub fn with_evaluation(self, loaded_shift: T, eval: T) -> Query { + Query { poly: self.poly, shift: self.shift, loaded_shift, eval } } } @@ -55,7 +57,7 @@ where /// Read [`PolynomialCommitmentScheme::Proof`] from transcript. fn read_proof( vk: &Self::VerifyingKey, - queries: &[Query], + queries: &[Query], transcript: &mut T, ) -> Result where @@ -66,7 +68,7 @@ where vk: &Self::VerifyingKey, commitments: &[Msm], point: &L::LoadedScalar, - queries: &[Query], + queries: &[Query], proof: &Self::Proof, ) -> Result; } diff --git a/snark-verifier/src/pcs/ipa.rs b/snark-verifier/src/pcs/ipa.rs index 6358e15d..288745d7 100644 --- a/snark-verifier/src/pcs/ipa.rs +++ b/snark-verifier/src/pcs/ipa.rs @@ -379,7 +379,7 @@ fn h_eval>(xi: &[T], z: &T) -> T { fn h_coeffs(xi: &[F], scalar: F) -> Vec { assert!(!xi.is_empty()); - let mut coeffs = vec![F::zero(); 1 << xi.len()]; + let mut coeffs = vec![F::ZERO; 1 << xi.len()]; coeffs[0] = scalar; for (len, xi) in xi.iter().rev().enumerate().map(|(i, xi)| (1 << i, xi)) { diff --git a/snark-verifier/src/pcs/ipa/accumulation.rs b/snark-verifier/src/pcs/ipa/accumulation.rs index 56d61aa7..51434541 100644 --- a/snark-verifier/src/pcs/ipa/accumulation.rs +++ b/snark-verifier/src/pcs/ipa/accumulation.rs @@ -186,13 +186,13 @@ where let (u, h) = instances .iter() - .map(|IpaAccumulator { u, xi }| (*u, h_coeffs(xi, C::Scalar::one()))) + .map(|IpaAccumulator { u, xi }| (*u, h_coeffs(xi, C::Scalar::ONE))) .chain(a_b_u.map(|(a, b, u)| { ( u, iter::empty() .chain([b, a]) - .chain(iter::repeat_with(C::Scalar::zero).take(pk.domain.n - 2)) + .chain(iter::repeat(C::Scalar::ZERO).take(pk.domain.n - 2)) .collect(), ) })) diff --git a/snark-verifier/src/pcs/ipa/decider.rs b/snark-verifier/src/pcs/ipa/decider.rs index 5235a857..6fd7026b 100644 --- a/snark-verifier/src/pcs/ipa/decider.rs +++ b/snark-verifier/src/pcs/ipa/decider.rs @@ -48,7 +48,7 @@ mod native { dk: &Self::DecidingKey, IpaAccumulator { u, xi }: IpaAccumulator, ) -> Result<(), Error> { - let h = h_coeffs(&xi, C::Scalar::one()); + let h = h_coeffs(&xi, C::Scalar::ONE); (u == multi_scalar_multiplication(&h, &dk.g).to_affine()) .then_some(()) .ok_or_else(|| Error::AssertionFailure("U == commit(G, h)".to_string())) diff --git a/snark-verifier/src/pcs/ipa/multiopen/bgh19.rs b/snark-verifier/src/pcs/ipa/multiopen/bgh19.rs index cae77a5f..e2cb87ab 100644 --- a/snark-verifier/src/pcs/ipa/multiopen/bgh19.rs +++ b/snark-verifier/src/pcs/ipa/multiopen/bgh19.rs @@ -5,7 +5,7 @@ use crate::{ PolynomialCommitmentScheme, Query, }, util::{ - arithmetic::{CurveAffine, FieldExt, Fraction}, + arithmetic::{CurveAffine, Fraction, PrimeField, Rotation}, msm::Msm, transcript::TranscriptRead, Itertools, @@ -35,7 +35,7 @@ where fn read_proof( svk: &Self::VerifyingKey, - queries: &[Query], + queries: &[Query], transcript: &mut T, ) -> Result where @@ -48,7 +48,7 @@ where svk: &Self::VerifyingKey, commitments: &[Msm], x: &L::LoadedScalar, - queries: &[Query], + queries: &[Query], proof: &Self::Proof, ) -> Result { let loader = x.loader(); @@ -119,7 +119,7 @@ where { fn read>( svk: &IpaSuccinctVerifyingKey, - queries: &[Query], + queries: &[Query], transcript: &mut T, ) -> Result { // Multiopen @@ -157,28 +157,33 @@ where } } -fn query_sets(queries: &[Query]) -> Vec> +fn query_sets(queries: &[Query]) -> Vec> where - F: FieldExt, + S: PartialEq + Ord + Copy, T: Clone, { let poly_shifts = - queries.iter().fold(Vec::<(usize, Vec, Vec<&T>)>::new(), |mut poly_shifts, query| { + queries.iter().fold(Vec::<(usize, Vec<_>, Vec<&T>)>::new(), |mut poly_shifts, query| { if let Some(pos) = poly_shifts.iter().position(|(poly, _, _)| *poly == query.poly) { let (_, shifts, evals) = &mut poly_shifts[pos]; - if !shifts.contains(&query.shift) { - shifts.push(query.shift); + if !shifts.iter().map(|(shift, _)| shift).contains(&query.shift) { + shifts.push((query.shift, query.loaded_shift.clone())); evals.push(&query.eval); } } else { - poly_shifts.push((query.poly, vec![query.shift], vec![&query.eval])); + poly_shifts.push(( + query.poly, + vec![(query.shift, query.loaded_shift.clone())], + vec![&query.eval], + )); } poly_shifts }); - poly_shifts.into_iter().fold(Vec::>::new(), |mut sets, (poly, shifts, evals)| { + poly_shifts.into_iter().fold(Vec::>::new(), |mut sets, (poly, shifts, evals)| { if let Some(pos) = sets.iter().position(|set| { - BTreeSet::from_iter(set.shifts.iter()) == BTreeSet::from_iter(shifts.iter()) + BTreeSet::from_iter(set.shifts.iter().map(|(shift, _)| shift)) + == BTreeSet::from_iter(shifts.iter().map(|(shift, _)| shift)) }) { let set = &mut sets[pos]; if !set.polys.contains(&poly) { @@ -187,7 +192,7 @@ where set.shifts .iter() .map(|lhs| { - let idx = shifts.iter().position(|rhs| lhs == rhs).unwrap(); + let idx = shifts.iter().position(|rhs| lhs.0 == rhs.0).unwrap(); evals[idx] }) .collect(), @@ -201,18 +206,23 @@ where }) } -fn query_set_coeffs(sets: &[QuerySet], x: &T, x_3: &T) -> Vec> +fn query_set_coeffs( + sets: &[QuerySet], + x: &T, + x_3: &T, +) -> Vec> where - F: FieldExt, + F: PrimeField + Ord, T: LoadedScalar, { - let loader = x.loader(); - let superset = sets.iter().flat_map(|set| set.shifts.clone()).sorted().dedup(); + let superset = BTreeMap::from_iter(sets.iter().flat_map(|set| set.shifts.clone())); let size = sets.iter().map(|set| set.shifts.len()).chain(Some(2)).max().unwrap(); let powers_of_x = x.powers(size); let x_3_minus_x_shift_i = BTreeMap::from_iter( - superset.map(|shift| (shift, x_3.clone() - x.clone() * loader.load_const(&shift))), + superset + .into_iter() + .map(|(shift, loaded_shift)| (shift, x_3.clone() - x.clone() * loaded_shift)), ); let mut coeffs = sets @@ -228,23 +238,22 @@ where } #[derive(Clone, Debug)] -struct QuerySet<'a, F, T> { - shifts: Vec, +struct QuerySet<'a, S, T> { + shifts: Vec<(S, T)>, polys: Vec, evals: Vec>, } -impl<'a, F, T> QuerySet<'a, F, T> -where - F: FieldExt, - T: LoadedScalar, -{ +impl<'a, S, T> QuerySet<'a, S, T> { fn msm>( &self, commitments: &[Msm<'a, C, L>], q_eval: &T, powers_of_x_1: &[T], - ) -> Msm { + ) -> Msm + where + T: LoadedScalar, + { self.polys .iter() .rev() @@ -254,7 +263,15 @@ where - Msm::constant(q_eval.clone()) } - fn f_eval(&self, coeff: &QuerySetCoeff, q_eval: &T, powers_of_x_1: &[T]) -> T { + fn f_eval( + &self, + coeff: &QuerySetCoeff, + q_eval: &T, + powers_of_x_1: &[T], + ) -> T + where + T: LoadedScalar, + { let loader = q_eval.loader(); let r_eval = { let r_evals = self @@ -288,10 +305,15 @@ struct QuerySetCoeff { impl QuerySetCoeff where - F: FieldExt, + F: PrimeField + Ord, T: LoadedScalar, { - fn new(shifts: &[F], powers_of_x: &[T], x_3: &T, x_3_minus_x_shift_i: &BTreeMap) -> Self { + fn new( + shifts: &[(Rotation, T)], + powers_of_x: &[T], + x_3: &T, + x_3_minus_x_shift_i: &BTreeMap, + ) -> Self { let loader = x_3.loader(); let normalized_ell_primes = shifts .iter() @@ -301,9 +323,9 @@ where .iter() .enumerate() .filter(|&(i, _)| i != j) - .map(|(_, shift_i)| (*shift_j - shift_i)) + .map(|(_, shift_i)| (shift_j.1.clone() - &shift_i.1)) .reduce(|acc, value| acc * value) - .unwrap_or_else(|| F::one()) + .unwrap_or_else(|| loader.load_const(&F::ONE)) }) .collect_vec(); @@ -313,17 +335,15 @@ where let barycentric_weights = shifts .iter() .zip(normalized_ell_primes.iter()) - .map(|(shift, normalized_ell_prime)| { - loader.sum_products_with_coeff(&[ - (*normalized_ell_prime, x_pow_k_minus_one, x_3), - (-(*normalized_ell_prime * shift), x_pow_k_minus_one, x), - ]) + .map(|((_, loaded_shift), normalized_ell_prime)| { + let tmp = normalized_ell_prime.clone() * x_pow_k_minus_one; + loader.sum_products(&[(&tmp, x_3), (&-(tmp.clone() * loaded_shift), x)]) }) .map(Fraction::one_over) .collect_vec(); let f_eval_coeff = Fraction::one_over(loader.product( - &shifts.iter().map(|shift| x_3_minus_x_shift_i.get(shift).unwrap()).collect_vec(), + &shifts.iter().map(|(shift, _)| x_3_minus_x_shift_i.get(shift).unwrap()).collect_vec(), )); Self { diff --git a/snark-verifier/src/pcs/kzg.rs b/snark-verifier/src/pcs/kzg.rs index 8f416ee3..387a108c 100644 --- a/snark-verifier/src/pcs/kzg.rs +++ b/snark-verifier/src/pcs/kzg.rs @@ -18,7 +18,7 @@ pub use accumulator::LimbsEncodingInstructions; /// KZG succinct verifying key. #[derive(Clone, Copy, Debug)] -pub struct KzgSuccinctVerifyingKey { +pub struct KzgSuccinctVerifyingKey { /// Generator. pub g: C, } diff --git a/snark-verifier/src/pcs/kzg/accumulation.rs b/snark-verifier/src/pcs/kzg/accumulation.rs index 1f901568..d71e366e 100644 --- a/snark-verifier/src/pcs/kzg/accumulation.rs +++ b/snark-verifier/src/pcs/kzg/accumulation.rs @@ -19,6 +19,7 @@ pub struct KzgAs(PhantomData<(M, MOS)>); impl AccumulationScheme for KzgAs where M: MultiMillerLoop, + M::G1Affine: CurveAffine, L: Loader, MOS: Clone + Debug, { @@ -139,6 +140,7 @@ where impl AccumulationSchemeProver for KzgAs where M: MultiMillerLoop, + M::G1Affine: CurveAffine, MOS: Clone + Debug, { type ProvingKey = KzgAsProvingKey; @@ -163,7 +165,7 @@ where let blind = pk .zk() .then(|| { - let s = M::Scalar::random(rng); + let s = M::Fr::random(rng); let (g, s_g) = pk.0.unwrap(); let lhs = (s_g * s).to_affine(); let rhs = (g * s).to_affine(); diff --git a/snark-verifier/src/pcs/kzg/accumulator.rs b/snark-verifier/src/pcs/kzg/accumulator.rs index 82d1454b..37cb493f 100644 --- a/snark-verifier/src/pcs/kzg/accumulator.rs +++ b/snark-verifier/src/pcs/kzg/accumulator.rs @@ -59,7 +59,6 @@ mod native { let [lhs_x, lhs_y, rhs_x, rhs_y]: [_; 4] = limbs .chunks(LIMBS) - .into_iter() .map(|limbs| { fe_from_limbs::<_, _, LIMBS, BITS>( limbs.iter().map(|limb| **limb).collect_vec().try_into().unwrap(), @@ -109,7 +108,6 @@ mod evm { let [lhs_x, lhs_y, rhs_x, rhs_y]: [[_; LIMBS]; 4] = limbs .chunks(LIMBS) - .into_iter() .map(|limbs| limbs.to_vec().try_into().unwrap()) .collect_vec() .try_into() @@ -204,14 +202,15 @@ mod halo2 { mod halo2_lib { use super::*; use halo2_base::halo2_proofs::halo2curves::CurveAffineExt; - use halo2_ecc::{ecc::BaseFieldEccChip, fields::PrimeField}; + use halo2_base::utils::BigPrimeField; + use halo2_ecc::ecc::BaseFieldEccChip; impl<'chip, C, const LIMBS: usize, const BITS: usize> LimbsEncodingInstructions for BaseFieldEccChip<'chip, C> where C: CurveAffineExt, - C::ScalarExt: PrimeField, - C::Base: PrimeField, + C::ScalarExt: BigPrimeField, + C::Base: BigPrimeField, { fn assign_ec_point_from_limbs( &self, @@ -221,7 +220,7 @@ mod halo2 { assert_eq!(limbs.len(), 2 * LIMBS); let ec_point = self.assign_point::( - ctx.main(0), + ctx.main(), ec_point_from_limbs::<_, LIMBS, BITS>( &limbs.iter().map(|limb| limb.value()).collect_vec(), ), @@ -231,7 +230,7 @@ mod halo2 { .iter() .zip_eq(iter::empty().chain(ec_point.x().limbs()).chain(ec_point.y().limbs())) { - ctx.main(0).constrain_equal(src, dst); + ctx.main().constrain_equal(src, dst); } ec_point diff --git a/snark-verifier/src/pcs/kzg/decider.rs b/snark-verifier/src/pcs/kzg/decider.rs index 59f1afbf..d55e0a57 100644 --- a/snark-verifier/src/pcs/kzg/decider.rs +++ b/snark-verifier/src/pcs/kzg/decider.rs @@ -1,4 +1,7 @@ -use crate::{pcs::kzg::KzgSuccinctVerifyingKey, util::arithmetic::MultiMillerLoop}; +use crate::{ + pcs::kzg::KzgSuccinctVerifyingKey, + util::arithmetic::{CurveAffine, MultiMillerLoop}, +}; use std::marker::PhantomData; /// KZG deciding key. @@ -23,7 +26,10 @@ impl KzgDecidingKey { } } -impl From<(M::G1Affine, M::G2Affine, M::G2Affine)> for KzgDecidingKey { +impl From<(M::G1Affine, M::G2Affine, M::G2Affine)> for KzgDecidingKey +where + M::G1Affine: CurveAffine, +{ fn from((g1, g2, s_g2): (M::G1Affine, M::G2Affine, M::G2Affine)) -> KzgDecidingKey { KzgDecidingKey::new(g1, g2, s_g2) } @@ -43,7 +49,7 @@ mod native { AccumulationDecider, }, util::{ - arithmetic::{Group, MillerLoopResult, MultiMillerLoop}, + arithmetic::{CurveAffine, Group, MillerLoopResult, MultiMillerLoop}, Itertools, }, Error, @@ -53,6 +59,7 @@ mod native { impl AccumulationDecider for KzgAs where M: MultiMillerLoop, + M::G1Affine: CurveAffine, MOS: Clone + Debug, { type DecidingKey = KzgDecidingKey; @@ -103,7 +110,9 @@ mod evm { impl AccumulationDecider> for KzgAs where M: MultiMillerLoop, - M::Scalar: PrimeField, + M::G1Affine: CurveAffine, + M::G2Affine: CurveAffine, + M::Fr: PrimeField, MOS: Clone + Debug, { type DecidingKey = KzgDecidingKey; @@ -118,10 +127,10 @@ mod evm { let x = coordinates.x().to_repr(); let y = coordinates.y().to_repr(); ( - U256::from_little_endian(&x.as_ref()[32..]), - U256::from_little_endian(&x.as_ref()[..32]), - U256::from_little_endian(&y.as_ref()[32..]), - U256::from_little_endian(&y.as_ref()[..32]), + U256::try_from_le_slice(&x.as_ref()[32..]).unwrap(), + U256::try_from_le_slice(&x.as_ref()[..32]).unwrap(), + U256::try_from_le_slice(&y.as_ref()[32..]).unwrap(), + U256::try_from_le_slice(&y.as_ref()[..32]).unwrap(), ) }); loader.pairing(&lhs, g2, &rhs, minus_s_g2); @@ -152,7 +161,7 @@ mod evm { loader.code_mut().runtime_append(code); let challenge = loader.scalar(Value::Memory(challenge_ptr)); - let powers_of_challenge = LoadedScalar::::powers(&challenge, lhs.len()); + let powers_of_challenge = LoadedScalar::::powers(&challenge, lhs.len()); let [lhs, rhs] = [lhs, rhs].map(|msms| { msms.iter() .zip(powers_of_challenge.iter()) diff --git a/snark-verifier/src/pcs/kzg/multiopen/bdfg21.rs b/snark-verifier/src/pcs/kzg/multiopen/bdfg21.rs index 3a448056..e13e09cc 100644 --- a/snark-verifier/src/pcs/kzg/multiopen/bdfg21.rs +++ b/snark-verifier/src/pcs/kzg/multiopen/bdfg21.rs @@ -6,7 +6,7 @@ use crate::{ PolynomialCommitmentScheme, Query, }, util::{ - arithmetic::{CurveAffine, FieldExt, Fraction, MultiMillerLoop}, + arithmetic::{CurveAffine, Fraction, MultiMillerLoop, PrimeField, Rotation}, msm::Msm, transcript::TranscriptRead, Itertools, @@ -27,6 +27,8 @@ pub struct Bdfg21; impl PolynomialCommitmentScheme for KzgAs where M: MultiMillerLoop, + M::G1Affine: CurveAffine, + M::Fr: Ord, L: Loader, { type VerifyingKey = KzgSuccinctVerifyingKey; @@ -35,7 +37,7 @@ where fn read_proof( _: &KzgSuccinctVerifyingKey, - _: &[Query], + _: &[Query], transcript: &mut T, ) -> Result, Error> where @@ -48,22 +50,21 @@ where svk: &KzgSuccinctVerifyingKey, commitments: &[Msm], z: &L::LoadedScalar, - queries: &[Query], + queries: &[Query], proof: &Bdfg21Proof, ) -> Result { let sets = query_sets(queries); let f = { let coeffs = query_set_coeffs(&sets, z, &proof.z_prime); - let powers_of_mu = proof - .mu - .powers(sets.iter().map(|set| set.polys.len()).max().unwrap()); + let powers_of_mu = + proof.mu.powers(sets.iter().map(|set| set.polys.len()).max().unwrap()); let msms = sets .iter() .zip(coeffs.iter()) .map(|(set, coeff)| set.msm(coeff, commitments, &powers_of_mu)); - msms.zip(proof.gamma.powers(sets.len()).into_iter()) + msms.zip(proof.gamma.powers(sets.len())) .map(|(msm, power_of_gamma)| msm * &power_of_gamma) .sum::>() - Msm::base(&proof.w) * &coeffs[0].z_s @@ -72,10 +73,7 @@ where let rhs = Msm::base(&proof.w_prime); let lhs = f + rhs.clone() * &proof.z_prime; - Ok(KzgAccumulator::new( - lhs.evaluate(Some(svk.g)), - rhs.evaluate(Some(svk.g)), - )) + Ok(KzgAccumulator::new(lhs.evaluate(Some(svk.g)), rhs.evaluate(Some(svk.g)))) } } @@ -104,94 +102,71 @@ where let w = transcript.read_ec_point()?; let z_prime = transcript.squeeze_challenge(); let w_prime = transcript.read_ec_point()?; - Ok(Bdfg21Proof { - mu, - gamma, - w, - z_prime, - w_prime, - }) + Ok(Bdfg21Proof { mu, gamma, w, z_prime, w_prime }) } } -fn query_sets(queries: &[Query]) -> Vec> { - let poly_shifts = queries.iter().fold( - Vec::<(usize, Vec, Vec<&T>)>::new(), - |mut poly_shifts, query| { - if let Some(pos) = poly_shifts - .iter() - .position(|(poly, _, _)| *poly == query.poly) - { +fn query_sets(queries: &[Query]) -> Vec> { + let poly_shifts = + queries.iter().fold(Vec::<(usize, Vec<_>, Vec<&T>)>::new(), |mut poly_shifts, query| { + if let Some(pos) = poly_shifts.iter().position(|(poly, _, _)| *poly == query.poly) { let (_, shifts, evals) = &mut poly_shifts[pos]; - if !shifts.contains(&query.shift) { - shifts.push(query.shift); + if !shifts.iter().map(|(shift, _)| shift).contains(&query.shift) { + shifts.push((query.shift, query.loaded_shift.clone())); evals.push(&query.eval); } } else { - poly_shifts.push((query.poly, vec![query.shift], vec![&query.eval])); + poly_shifts.push(( + query.poly, + vec![(query.shift, query.loaded_shift.clone())], + vec![&query.eval], + )); } poly_shifts - }, - ); - - poly_shifts.into_iter().fold( - Vec::>::new(), - |mut sets, (poly, shifts, evals)| { - if let Some(pos) = sets.iter().position(|set| { - BTreeSet::from_iter(set.shifts.iter()) == BTreeSet::from_iter(shifts.iter()) - }) { - let set = &mut sets[pos]; - if !set.polys.contains(&poly) { - set.polys.push(poly); - set.evals.push( - set.shifts - .iter() - .map(|lhs| { - let idx = shifts.iter().position(|rhs| lhs == rhs).unwrap(); - evals[idx] - }) - .collect(), - ); - } - } else { - let set = QuerySet { - shifts, - polys: vec![poly], - evals: vec![evals], - }; - sets.push(set); + }); + + poly_shifts.into_iter().fold(Vec::>::new(), |mut sets, (poly, shifts, evals)| { + if let Some(pos) = sets.iter().position(|set| { + BTreeSet::from_iter(set.shifts.iter().map(|(shift, _)| shift)) + == BTreeSet::from_iter(shifts.iter().map(|(shift, _)| shift)) + }) { + let set = &mut sets[pos]; + if !set.polys.contains(&poly) { + set.polys.push(poly); + set.evals.push( + set.shifts + .iter() + .map(|lhs| { + let idx = shifts.iter().position(|rhs| lhs.0 == rhs.0).unwrap(); + evals[idx] + }) + .collect(), + ); } - sets - }, - ) + } else { + let set = QuerySet { shifts, polys: vec![poly], evals: vec![evals] }; + sets.push(set); + } + sets + }) } -fn query_set_coeffs<'a, F: FieldExt, T: LoadedScalar>( - sets: &[QuerySet<'a, F, T>], +fn query_set_coeffs>( + sets: &[QuerySet], z: &T, z_prime: &T, ) -> Vec> { - let loader = z.loader(); + // map of shift => loaded_shift, removing duplicate `shift` values + // shift is the rotation, not omega^rotation, to ensure BTreeMap does not depend on omega (otherwise ordering can change) + let superset = BTreeMap::from_iter(sets.iter().flat_map(|set| set.shifts.clone())); - let superset = sets - .iter() - .flat_map(|set| set.shifts.clone()) - .sorted() - .dedup(); - - let size = sets - .iter() - .map(|set| set.shifts.len()) - .chain(Some(2)) - .max() - .unwrap(); + let size = sets.iter().map(|set| set.shifts.len()).chain(Some(2)).max().unwrap(); let powers_of_z = z.powers(size); - let z_prime_minus_z_shift_i = BTreeMap::from_iter(superset.map(|shift| { - ( - shift, - z_prime.clone() - z.clone() * loader.load_const(&shift), - ) - })); + let z_prime_minus_z_shift_i = BTreeMap::from_iter( + superset + .into_iter() + .map(|(shift, loaded_shift)| (shift, z_prime.clone() - z.clone() * loaded_shift)), + ); let mut z_s_1 = None; let mut coeffs = sets @@ -219,19 +194,22 @@ fn query_set_coeffs<'a, F: FieldExt, T: LoadedScalar>( } #[derive(Clone, Debug)] -struct QuerySet<'a, F, T> { - shifts: Vec, +struct QuerySet<'a, S, T> { + shifts: Vec<(S, T)>, // vec of (shift, loaded_shift) polys: Vec, evals: Vec>, } -impl<'a, F: FieldExt, T: LoadedScalar> QuerySet<'a, F, T> { +impl<'a, S, T> QuerySet<'a, S, T> { fn msm>( &self, - coeff: &QuerySetCoeff, + coeff: &QuerySetCoeff, commitments: &[Msm<'a, C, L>], powers_of_mu: &[T], - ) -> Msm { + ) -> Msm + where + T: LoadedScalar, + { self.polys .iter() .zip(self.evals.iter()) @@ -270,14 +248,14 @@ struct QuerySetCoeff { impl QuerySetCoeff where - F: FieldExt, + F: PrimeField + Ord, T: LoadedScalar, { fn new( - shifts: &[F], + shifts: &[(Rotation, T)], powers_of_z: &[T], z_prime: &T, - z_prime_minus_z_shift_i: &BTreeMap, + z_prime_minus_z_shift_i: &BTreeMap, z_s_1: &Option, ) -> Self { let loader = z_prime.loader(); @@ -290,9 +268,9 @@ where .iter() .enumerate() .filter(|&(i, _)| i != j) - .map(|(_, shift_i)| (*shift_j - shift_i)) + .map(|(_, shift_i)| (shift_j.1.clone() - &shift_i.1)) .reduce(|acc, value| acc * value) - .unwrap_or_else(|| F::one()) + .unwrap_or_else(|| loader.load_const(&F::ONE)) }) .collect_vec(); @@ -302,11 +280,9 @@ where let barycentric_weights = shifts .iter() .zip(normalized_ell_primes.iter()) - .map(|(shift, normalized_ell_prime)| { - loader.sum_products_with_coeff(&[ - (*normalized_ell_prime, z_pow_k_minus_one, z_prime), - (-(*normalized_ell_prime * shift), z_pow_k_minus_one, z), - ]) + .map(|((_, loaded_shift), normalized_ell_prime)| { + let tmp = normalized_ell_prime.clone() * z_pow_k_minus_one; + loader.sum_products(&[(&tmp, z_prime), (&-(tmp.clone() * loaded_shift), z)]) }) .map(Fraction::one_over) .collect_vec(); @@ -314,7 +290,7 @@ where let z_s = loader.product( &shifts .iter() - .map(|shift| z_prime_minus_z_shift_i.get(shift).unwrap()) + .map(|(shift, _)| z_prime_minus_z_shift_i.get(shift).unwrap()) .collect_vec(), ); let z_s_1_over_z_s = z_s_1.clone().map(|z_s_1| Fraction::new(z_s_1, z_s.clone())); @@ -344,13 +320,8 @@ where .iter_mut() .chain(self.commitment_coeff.as_mut()) .for_each(Fraction::evaluate); - let barycentric_weights_sum = loader.sum( - &self - .eval_coeffs - .iter() - .map(Fraction::evaluated) - .collect_vec(), - ); + let barycentric_weights_sum = + loader.sum(&self.eval_coeffs.iter().map(Fraction::evaluated).collect_vec()); self.r_eval_coeff = Some(match self.commitment_coeff.as_ref() { Some(coeff) => Fraction::new(coeff.evaluated().clone(), barycentric_weights_sum), None => Fraction::one_over(barycentric_weights_sum), @@ -370,13 +341,9 @@ impl CostEstimation for KzgAs where M: MultiMillerLoop, { - type Input = Vec>; + type Input = Vec>; - fn estimate_cost(_: &Vec>) -> Cost { - Cost { - num_commitment: 2, - num_msm: 2, - ..Default::default() - } + fn estimate_cost(_: &Vec>) -> Cost { + Cost { num_commitment: 2, num_msm: 2, ..Default::default() } } } diff --git a/snark-verifier/src/pcs/kzg/multiopen/gwc19.rs b/snark-verifier/src/pcs/kzg/multiopen/gwc19.rs index e5741163..e8114d09 100644 --- a/snark-verifier/src/pcs/kzg/multiopen/gwc19.rs +++ b/snark-verifier/src/pcs/kzg/multiopen/gwc19.rs @@ -6,7 +6,7 @@ use crate::{ PolynomialCommitmentScheme, Query, }, util::{ - arithmetic::{CurveAffine, MultiMillerLoop, PrimeField}, + arithmetic::{CurveAffine, MultiMillerLoop, Rotation}, msm::Msm, transcript::TranscriptRead, Itertools, @@ -23,6 +23,7 @@ pub struct Gwc19; impl PolynomialCommitmentScheme for KzgAs where M: MultiMillerLoop, + M::G1Affine: CurveAffine, L: Loader, { type VerifyingKey = KzgSuccinctVerifyingKey; @@ -31,7 +32,7 @@ where fn read_proof( _: &Self::VerifyingKey, - queries: &[Query], + queries: &[Query], transcript: &mut T, ) -> Result where @@ -44,22 +45,23 @@ where svk: &Self::VerifyingKey, commitments: &[Msm], z: &L::LoadedScalar, - queries: &[Query], + queries: &[Query], proof: &Self::Proof, ) -> Result { let sets = query_sets(queries); let powers_of_u = &proof.u.powers(sets.len()); let f = { - let powers_of_v = proof - .v - .powers(sets.iter().map(|set| set.polys.len()).max().unwrap()); + let powers_of_v = proof.v.powers(sets.iter().map(|set| set.polys.len()).max().unwrap()); sets.iter() .map(|set| set.msm(commitments, &powers_of_v)) .zip(powers_of_u.iter()) .map(|(msm, power_of_u)| msm * power_of_u) .sum::>() }; - let z_omegas = sets.iter().map(|set| z.loader().load_const(&set.shift) * z); + let z_omegas = sets.iter().map(|set| { + let loaded_shift = set.loaded_shift.clone(); + loaded_shift * z + }); let rhs = proof .ws @@ -67,11 +69,7 @@ where .zip(powers_of_u.iter()) .map(|(w, power_of_u)| Msm::base(w) * power_of_u) .collect_vec(); - let lhs = f + rhs - .iter() - .zip(z_omegas) - .map(|(uw, z_omega)| uw.clone() * &z_omega) - .sum(); + let lhs = f + rhs.iter().zip(z_omegas).map(|(uw, z_omega)| uw.clone() * &z_omega).sum(); Ok(KzgAccumulator::new( lhs.evaluate(Some(svk.g)), @@ -97,7 +95,7 @@ where C: CurveAffine, L: Loader, { - fn read(queries: &[Query], transcript: &mut T) -> Result + fn read(queries: &[Query], transcript: &mut T) -> Result where T: TranscriptRead, { @@ -108,22 +106,25 @@ where } } -struct QuerySet<'a, F, T> { - shift: F, +struct QuerySet<'a, S, T> { + shift: S, + loaded_shift: T, polys: Vec, evals: Vec<&'a T>, } -impl<'a, F, T> QuerySet<'a, F, T> +impl<'a, S, T> QuerySet<'a, S, T> where - F: PrimeField, T: Clone, { fn msm>( &self, commitments: &[Msm<'a, C, L>], powers_of_v: &[L::LoadedScalar], - ) -> Msm { + ) -> Msm + where + T: LoadedScalar, + { self.polys .iter() .zip(self.evals.iter().cloned()) @@ -137,9 +138,9 @@ where } } -fn query_sets(queries: &[Query]) -> Vec> +fn query_sets(queries: &[Query]) -> Vec> where - F: PrimeField, + S: PartialEq + Copy, T: Clone + PartialEq, { queries.iter().fold(Vec::new(), |mut sets, query| { @@ -149,6 +150,7 @@ where } else { sets.push(QuerySet { shift: query.shift, + loaded_shift: query.loaded_shift.clone(), polys: vec![query.poly], evals: vec![&query.eval], }); @@ -161,14 +163,10 @@ impl CostEstimation for KzgAs where M: MultiMillerLoop, { - type Input = Vec>; + type Input = Vec>; - fn estimate_cost(queries: &Vec>) -> Cost { + fn estimate_cost(queries: &Vec>) -> Cost { let num_w = query_sets(queries).len(); - Cost { - num_commitment: num_w, - num_msm: num_w, - ..Default::default() - } + Cost { num_commitment: num_w, num_msm: num_w, ..Default::default() } } } diff --git a/snark-verifier/src/system/halo2.rs b/snark-verifier/src/system/halo2.rs index 98f4488c..d62ab26f 100644 --- a/snark-verifier/src/system/halo2.rs +++ b/snark-verifier/src/system/halo2.rs @@ -7,7 +7,7 @@ use crate::halo2_proofs::{ }; use crate::{ util::{ - arithmetic::{root_of_unity, CurveAffine, Domain, FieldExt, Rotation}, + arithmetic::{root_of_unity, CurveAffine, Domain, PrimeField, Rotation}, Itertools, }, verifier::plonk::protocol::{ @@ -141,6 +141,7 @@ pub fn compile<'a, C: CurveAffine, P: Params<'a, C>>( PlonkProtocol { domain, + domain_as_witness: None, preprocessed, num_instance: polynomials.num_instance(), num_witness: polynomials.num_witness(), @@ -161,7 +162,7 @@ impl From for Rotation { } } -struct Polynomials<'a, F: FieldExt> { +struct Polynomials<'a, F: PrimeField> { cs: &'a ConstraintSystem, zk: bool, query_instance: bool, @@ -179,7 +180,7 @@ struct Polynomials<'a, F: FieldExt> { num_lookup_z: usize, } -impl<'a, F: FieldExt> Polynomials<'a, F> { +impl<'a, F: PrimeField> Polynomials<'a, F> { fn new( cs: &'a ConstraintSystem, zk: bool, @@ -474,7 +475,7 @@ impl<'a, F: FieldExt> Polynomials<'a, F> { } fn l_active(&self) -> Expression { - Expression::Constant(F::one()) - self.l_last() - self.l_blind() + Expression::Constant(F::ONE) - self.l_last() - self.l_blind() } fn system_challenge_offset(&self) -> usize { @@ -499,7 +500,7 @@ impl<'a, F: FieldExt> Polynomials<'a, F> { } fn permutation_constraints(&'a self, t: usize) -> impl IntoIterator> + 'a { - let one = &Expression::Constant(F::one()); + let one = &Expression::Constant(F::ONE); let l_0 = &Expression::::CommonPolynomial(CommonPolynomial::Lagrange(0)); let l_last = &self.l_last(); let l_active = &self.l_active(); @@ -591,7 +592,7 @@ impl<'a, F: FieldExt> Polynomials<'a, F> { } fn lookup_constraints(&'a self, t: usize) -> impl IntoIterator> + 'a { - let one = &Expression::Constant(F::one()); + let one = &Expression::Constant(F::ONE); let l_0 = &Expression::::CommonPolynomial(CommonPolynomial::Lagrange(0)); let l_last = &self.l_last(); let l_active = &self.l_active(); @@ -698,7 +699,7 @@ impl EncodedChallenge for MockChallenge { } #[derive(Default)] -struct MockTranscript(F); +struct MockTranscript(F); impl Transcript for MockTranscript { fn squeeze_challenge(&mut self) -> MockChallenge { @@ -715,7 +716,9 @@ impl Transcript for MockTranscript } } -fn transcript_initial_state(vk: &VerifyingKey) -> C::Scalar { +/// Returns the transcript initial state of the [VerifyingKey]. +/// Roundabout way to do it because [VerifyingKey] doesn't expose the field. +pub fn transcript_initial_state(vk: &VerifyingKey) -> C::Scalar { let mut transcript = MockTranscript::default(); vk.hash_into(&mut transcript).unwrap(); transcript.0 diff --git a/snark-verifier/src/system/halo2/transcript.rs b/snark-verifier/src/system/halo2/transcript.rs index 9cfd6b89..10da3a22 100644 --- a/snark-verifier/src/system/halo2/transcript.rs +++ b/snark-verifier/src/system/halo2/transcript.rs @@ -1,7 +1,6 @@ //! Transcripts implemented with both `halo2_proofs::transcript` and //! `crate::util::transcript`. use crate::halo2_proofs; -use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite, Challenge255}; use crate::{ loader::native::{self, NativeLoader}, util::{ @@ -10,6 +9,8 @@ use crate::{ }, Error, }; +use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite, Challenge255}; +use pairing::group::ff::FromUniformBytes; use std::io::{Read, Write}; #[cfg(feature = "loader_evm")] @@ -18,7 +19,10 @@ pub mod evm; #[cfg(feature = "loader_halo2")] pub mod halo2; -impl Transcript for Blake2bRead> { +impl Transcript for Blake2bRead> +where + C::Scalar: FromUniformBytes<64>, +{ fn loader(&self) -> &NativeLoader { &native::LOADER } @@ -38,8 +42,9 @@ impl Transcript for Blake2bRead TranscriptRead - for Blake2bRead> +impl TranscriptRead for Blake2bRead> +where + C::Scalar: FromUniformBytes<64>, { fn read_scalar(&mut self) -> Result { halo2_proofs::transcript::TranscriptRead::read_scalar(self) @@ -52,7 +57,10 @@ impl TranscriptRead } } -impl Transcript for Blake2bWrite> { +impl Transcript for Blake2bWrite> +where + C::Scalar: FromUniformBytes<64>, +{ fn loader(&self) -> &NativeLoader { &native::LOADER } @@ -72,7 +80,10 @@ impl Transcript for Blake2bWrite TranscriptWrite for Blake2bWrite, C, Challenge255> { +impl TranscriptWrite for Blake2bWrite, C, Challenge255> +where + C::Scalar: FromUniformBytes<64>, +{ fn write_scalar(&mut self, scalar: C::Scalar) -> Result<(), Error> { halo2_proofs::transcript::TranscriptWrite::write_scalar(self, scalar) .map_err(|err| Error::Transcript(err.kind(), err.to_string())) diff --git a/snark-verifier/src/system/halo2/transcript/evm.rs b/snark-verifier/src/system/halo2/transcript/evm.rs index c71c9e79..da2c09c2 100644 --- a/snark-verifier/src/system/halo2/transcript/evm.rs +++ b/snark-verifier/src/system/halo2/transcript/evm.rs @@ -178,7 +178,7 @@ where .collect_vec(); let hash: [u8; 32] = Keccak256::digest(data).into(); self.buf = hash.to_vec(); - u256_to_fe(U256::from_big_endian(hash.as_slice())) + u256_to_fe(U256::from_be_bytes(hash)) } fn common_ec_point(&mut self, ec_point: &C) -> Result<(), Error> { @@ -274,7 +274,7 @@ where type Input = [u8; 32]; fn new(challenge_input: &[u8; 32]) -> Self { - ChallengeEvm(u256_to_fe(U256::from_big_endian(challenge_input))) + ChallengeEvm(u256_to_fe(U256::from_be_bytes(*challenge_input))) } fn get_scalar(&self) -> C::Scalar { diff --git a/snark-verifier/src/system/halo2/transcript/halo2.rs b/snark-verifier/src/system/halo2/transcript/halo2.rs index 86b1929c..8a0ce6d4 100644 --- a/snark-verifier/src/system/halo2/transcript/halo2.rs +++ b/snark-verifier/src/system/halo2/transcript/halo2.rs @@ -1,6 +1,7 @@ //! Transcript for verifier in [`halo2_proofs`] circuit. use crate::halo2_proofs; +use crate::util::arithmetic::FieldExt; use crate::{ loader::{ halo2::{EcPoint, EccInstructions, Halo2Loader, Scalar}, @@ -64,7 +65,10 @@ where { /// Initialize [`PoseidonTranscript`] given readable or writeable stream for /// verifying or proving with [`NativeLoader`]. - pub fn new(loader: &Rc>, stream: R) -> Self { + pub fn new(loader: &Rc>, stream: R) -> Self + where + C::Scalar: FieldExt, + { let buf = Poseidon::new::(loader); Self { loader: loader.clone(), stream, buf } } @@ -165,7 +169,10 @@ impl(stream: S) -> Self { + pub fn new(stream: S) -> Self + where + C::Scalar: FieldExt, + { Self { loader: NativeLoader, stream, @@ -375,6 +382,7 @@ impl where C: CurveAffine, + C::Scalar: FieldExt, R: Read, { fn init(reader: R) -> Self { @@ -409,6 +417,7 @@ impl where C: CurveAffine, + C::Scalar: FieldExt, W: Write, { fn init(writer: W) -> Self { @@ -423,12 +432,13 @@ where mod halo2_lib { use crate::halo2_curves::CurveAffineExt; use crate::system::halo2::transcript::halo2::NativeEncoding; - use halo2_ecc::{ecc::BaseFieldEccChip, fields::PrimeField}; + use halo2_base::utils::BigPrimeField; + use halo2_ecc::ecc::BaseFieldEccChip; impl<'chip, C: CurveAffineExt> NativeEncoding for BaseFieldEccChip<'chip, C> where - C::Scalar: PrimeField, - C::Base: PrimeField, + C::Scalar: BigPrimeField, + C::Base: BigPrimeField, { fn encode( &self, diff --git a/snark-verifier/src/util/arithmetic.rs b/snark-verifier/src/util/arithmetic.rs index 97962e32..c34daef8 100644 --- a/snark-verifier/src/util/arithmetic.rs +++ b/snark-verifier/src/util/arithmetic.rs @@ -4,15 +4,15 @@ use crate::halo2_curves; use crate::util::Itertools; pub use halo2_curves::{ group::{ - ff::{BatchInvert, Field, PrimeField}, + ff::{BatchInvert, Field, FromUniformBytes, PrimeField}, prime::PrimeCurveAffine, Curve, Group, GroupEncoding, }, - pairing::MillerLoopResult, - Coordinates, CurveAffine, CurveExt, FieldExt, + Coordinates, CurveAffine, CurveExt, }; use num_bigint::BigUint; use num_traits::One; +pub use pairing::MillerLoopResult; use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, @@ -22,9 +22,14 @@ use std::{ }; /// [`halo2_curves::pairing::MultiMillerLoop`] with [`std::fmt::Debug`]. -pub trait MultiMillerLoop: halo2_curves::pairing::MultiMillerLoop + Debug {} +pub trait MultiMillerLoop: pairing::MultiMillerLoop + Debug {} -impl MultiMillerLoop for M {} +impl MultiMillerLoop for M {} + +/// Trait for fields that can implement Poseidon hash +pub trait FieldExt: PrimeField + FromUniformBytes<64> + Ord {} + +impl + Ord> FieldExt for F {} /// Operations that could be done with field elements. pub trait FieldOps: @@ -54,7 +59,7 @@ pub fn batch_invert_and_mul(values: &mut [F], coeff: &F) { } let products = values .iter() - .scan(F::one(), |acc, value| { + .scan(F::ONE, |acc, value| { *acc *= value; Some(*acc) }) @@ -65,7 +70,7 @@ pub fn batch_invert_and_mul(values: &mut [F], coeff: &F) { * coeff; for (value, product) in - values.iter_mut().rev().zip(products.into_iter().rev().skip(1).chain(Some(F::one()))) + values.iter_mut().rev().zip(products.into_iter().rev().skip(1).chain(Some(F::ONE))) { let mut inv = all_product_inv * product; mem::swap(value, &mut inv); @@ -75,7 +80,7 @@ pub fn batch_invert_and_mul(values: &mut [F], coeff: &F) { /// Batch invert [`PrimeField`] elements. pub fn batch_invert(values: &mut [F]) { - batch_invert_and_mul(values, &F::one()) + batch_invert_and_mul(values, &F::ONE) } /// Root of unity of 2^k-sized multiplicative subgroup of [`PrimeField`] by @@ -88,7 +93,7 @@ pub fn batch_invert(values: &mut [F]) { pub fn root_of_unity(k: usize) -> F { assert!(k <= F::S as usize); - iter::successors(Some(F::root_of_unity()), |acc| Some(acc.square())) + iter::successors(Some(F::ROOT_OF_UNITY), |acc| Some(acc.square())) .take(F::S as usize - k + 1) .last() .unwrap() @@ -151,7 +156,7 @@ impl Domain { match rotation.0.cmp(&0) { Ordering::Equal => scalar, Ordering::Greater => scalar * self.gen.pow_vartime([rotation.0 as u64]), - Ordering::Less => scalar * self.gen_inv.pow_vartime([(-(rotation.0 as i64)) as u64]), + Ordering::Less => scalar * self.gen_inv.pow_vartime([(-rotation.0) as u64]), } } } @@ -230,7 +235,7 @@ impl Fraction { /// Modulus of a [`PrimeField`] pub fn modulus() -> BigUint { - fe_to_big(-F::one()) + 1usize + fe_to_big(-F::ONE) + 1usize } /// Convert a [`BigUint`] into a [`PrimeField`] . @@ -286,7 +291,7 @@ pub fn fe_to_limbs(scalar: F) -> impl Iterator { - iter::successors(Some(F::one()), move |power| Some(scalar * power)) + iter::successors(Some(F::ONE), move |power| Some(scalar * power)) } /// Compute inner product of 2 slice of [`Field`]. diff --git a/snark-verifier/src/util/hash.rs b/snark-verifier/src/util/hash.rs index a8fe168c..a14eec23 100644 --- a/snark-verifier/src/util/hash.rs +++ b/snark-verifier/src/util/hash.rs @@ -1,10 +1,10 @@ //! Hash algorithms. -#[cfg(feature = "loader_halo2")] mod poseidon; +pub use crate::util::hash::poseidon::Poseidon; #[cfg(feature = "loader_halo2")] -pub use crate::util::hash::poseidon::{OptimizedPoseidonSpec, Poseidon}; +pub(crate) use halo2_base::poseidon::hasher::spec::OptimizedPoseidonSpec; #[cfg(feature = "loader_evm")] pub use sha3::{Digest, Keccak256}; diff --git a/snark-verifier/src/util/hash/poseidon.rs b/snark-verifier/src/util/hash/poseidon.rs index 1ff06ab9..740f4c72 100644 --- a/snark-verifier/src/util/hash/poseidon.rs +++ b/snark-verifier/src/util/hash/poseidon.rs @@ -1,339 +1,35 @@ -#![allow(clippy::needless_range_loop)] // for clarity of matrix operations +//! Trait based implementation of Poseidon permutation + +use halo2_base::poseidon::hasher::{mds::SparseMDSMatrix, spec::OptimizedPoseidonSpec}; + use crate::{ loader::{LoadedScalar, ScalarLoader}, - util::{arithmetic::FieldExt, Itertools}, + util::{ + arithmetic::{FieldExt, PrimeField}, + Itertools, + }, }; -use poseidon_circuit::poseidon::primitives::Spec as PoseidonSpec; // trait use std::{iter, marker::PhantomData, mem}; #[cfg(test)] mod tests; -// struct so we can use PoseidonSpec trait to generate round constants and MDS matrix -#[derive(Debug)] -pub struct Poseidon128Pow5Gen< - F: FieldExt, - const T: usize, - const RATE: usize, - const R_F: usize, - const R_P: usize, - const SECURE_MDS: usize, -> { - _marker: PhantomData, -} - -impl< - F: FieldExt, - const T: usize, - const RATE: usize, - const R_F: usize, - const R_P: usize, - const SECURE_MDS: usize, - > PoseidonSpec for Poseidon128Pow5Gen -{ - fn full_rounds() -> usize { - R_F - } - - fn partial_rounds() -> usize { - R_P - } - - fn sbox(val: F) -> F { - val.pow_vartime([5]) - } - - // see "Avoiding insecure matrices" in Section 2.3 of https://eprint.iacr.org/2019/458.pdf - // most Specs used in practice have SECURE_MDS = 0 - fn secure_mds() -> usize { - SECURE_MDS - } -} - -// We use the optimized Poseidon implementation described in Supplementary Material Section B of https://eprint.iacr.org/2019/458.pdf -// This involves some further computation of optimized constants and sparse MDS matrices beyond what the Scroll PoseidonSpec generates -// The implementation below is adapted from https://github.com/privacy-scaling-explorations/poseidon - -/// `OptimizedPoseidonSpec` holds construction parameters as well as constants that are used in -/// permutation step. -#[derive(Debug, Clone)] -pub struct OptimizedPoseidonSpec { - pub(crate) r_f: usize, - pub(crate) mds_matrices: MDSMatrices, - pub(crate) constants: OptimizedConstants, -} - -/// `OptimizedConstants` has round constants that are added each round. While -/// full rounds has T sized constants there is a single constant for each -/// partial round -#[derive(Debug, Clone)] -pub struct OptimizedConstants { - pub(crate) start: Vec<[F; T]>, - pub(crate) partial: Vec, - pub(crate) end: Vec<[F; T]>, -} - -/// The type used to hold the MDS matrix -pub(crate) type Mds = [[F; T]; T]; - -/// `MDSMatrices` holds the MDS matrix as well as transition matrix which is -/// also called `pre_sparse_mds` and sparse matrices that enables us to reduce -/// number of multiplications in apply MDS step -#[derive(Debug, Clone)] -pub struct MDSMatrices { - pub(crate) mds: MDSMatrix, - pub(crate) pre_sparse_mds: MDSMatrix, - pub(crate) sparse_matrices: Vec>, -} - -/// `SparseMDSMatrix` are in `[row], [hat | identity]` form and used in linear -/// layer of partial rounds instead of the original MDS -#[derive(Debug, Clone)] -pub struct SparseMDSMatrix { - pub(crate) row: [F; T], - pub(crate) col_hat: [F; RATE], -} - -/// `MDSMatrix` is applied to `State` to achive linear layer of Poseidon -#[derive(Clone, Debug)] -pub struct MDSMatrix(pub(crate) Mds); - -impl MDSMatrix { - pub(crate) fn mul_vector(&self, v: &[F; T]) -> [F; T] { - let mut res = [F::zero(); T]; - for i in 0..T { - for j in 0..T { - res[i] += self.0[i][j] * v[j]; - } - } - res - } - - fn identity() -> Mds { - let mut mds = [[F::zero(); T]; T]; - for i in 0..T { - mds[i][i] = F::one(); - } - mds - } - - /// Multiplies two MDS matrices. Used in sparse matrix calculations - fn mul(&self, other: &Self) -> Self { - let mut res = [[F::zero(); T]; T]; - for i in 0..T { - for j in 0..T { - for k in 0..T { - res[i][j] += self.0[i][k] * other.0[k][j]; - } - } - } - Self(res) - } - - fn transpose(&self) -> Self { - let mut res = [[F::zero(); T]; T]; - for i in 0..T { - for j in 0..T { - res[i][j] = self.0[j][i]; - } - } - Self(res) - } - - fn determinant(m: [[F; N]; N]) -> F { - let mut res = F::one(); - let mut m = m; - for i in 0..N { - let mut pivot = i; - while m[pivot][i] == F::zero() { - pivot += 1; - assert!(pivot < N, "matrix is not invertible"); - } - if pivot != i { - res = -res; - m.swap(pivot, i); - } - res *= m[i][i]; - let inv = m[i][i].invert().unwrap(); - for j in i + 1..N { - let factor = m[j][i] * inv; - for k in i + 1..N { - m[j][k] -= m[i][k] * factor; - } - } - } - res - } - - /// See Section B in Supplementary Material https://eprint.iacr.org/2019/458.pdf - /// Factorises an MDS matrix `M` into `M'` and `M''` where `M = M' * M''`. - /// Resulted `M''` matrices are the sparse ones while `M'` will contribute - /// to the accumulator of the process - fn factorise(&self) -> (Self, SparseMDSMatrix) { - assert_eq!(RATE + 1, T); - // Given `(t-1 * t-1)` MDS matrix called `hat` constructs the `t * t` matrix in - // form `[[1 | 0], [0 | m]]`, ie `hat` is the right bottom sub-matrix - let prime = |hat: Mds| -> Self { - let mut prime = Self::identity(); - for (prime_row, hat_row) in prime.iter_mut().skip(1).zip(hat.iter()) { - for (el_prime, el_hat) in prime_row.iter_mut().skip(1).zip(hat_row.iter()) { - *el_prime = *el_hat; - } - } - Self(prime) - }; - - // Given `(t-1)` sized `w_hat` vector constructs the matrix in form - // `[[m_0_0 | m_0_i], [w_hat | identity]]` - let prime_prime = |w_hat: [F; RATE]| -> Mds { - let mut prime_prime = Self::identity(); - prime_prime[0] = self.0[0]; - for (row, w) in prime_prime.iter_mut().skip(1).zip(w_hat.iter()) { - row[0] = *w - } - prime_prime - }; - - let w = self.0.iter().skip(1).map(|row| row[0]).collect::>(); - // m_hat is the `(t-1 * t-1)` right bottom sub-matrix of m := self.0 - let mut m_hat = [[F::zero(); RATE]; RATE]; - for i in 0..RATE { - for j in 0..RATE { - m_hat[i][j] = self.0[i + 1][j + 1]; - } - } - // w_hat = m_hat^{-1} * w, where m_hat^{-1} is matrix inverse and * is matrix mult - // we avoid computing m_hat^{-1} explicitly by using Cramer's rule: https://en.wikipedia.org/wiki/Cramer%27s_rule - let mut w_hat = [F::zero(); RATE]; - let det = Self::determinant(m_hat); - let det_inv = Option::::from(det.invert()).expect("matrix is not invertible"); - for j in 0..RATE { - let mut m_hat_j = m_hat; - for i in 0..RATE { - m_hat_j[i][j] = w[i]; - } - w_hat[j] = Self::determinant(m_hat_j) * det_inv; - } - let m_prime = prime(m_hat); - let m_prime_prime = prime_prime(w_hat); - // row = first row of m_prime_prime.transpose() = first column of m_prime_prime - let row: [F; T] = - m_prime_prime.iter().map(|row| row[0]).collect::>().try_into().unwrap(); - // col_hat = first column of m_prime_prime.transpose() without first element = first row of m_prime_prime without first element - let col_hat: [F; RATE] = m_prime_prime[0][1..].try_into().unwrap(); - (m_prime, SparseMDSMatrix { row, col_hat }) - } -} - -impl OptimizedPoseidonSpec { - /// Generate new spec with specific number of full and partial rounds. `SECURE_MDS` is usually 0, but may need to be specified because insecure matrices may sometimes be generated - pub fn new() -> Self { - let (round_constants, mds, mds_inv) = - Poseidon128Pow5Gen::::constants(); - let mds = MDSMatrix(mds); - let inverse_mds = MDSMatrix(mds_inv); - - let constants = - Self::calculate_optimized_constants(R_F, R_P, round_constants, &inverse_mds); - let (sparse_matrices, pre_sparse_mds) = Self::calculate_sparse_matrices(R_P, &mds); - - Self { - r_f: R_F, - constants, - mds_matrices: MDSMatrices { mds, sparse_matrices, pre_sparse_mds }, - } - } - - fn calculate_optimized_constants( - r_f: usize, - r_p: usize, - constants: Vec<[F; T]>, - inverse_mds: &MDSMatrix, - ) -> OptimizedConstants { - let (number_of_rounds, r_f_half) = (r_f + r_p, r_f / 2); - assert_eq!(constants.len(), number_of_rounds); - - // Calculate optimized constants for first half of the full rounds - let mut constants_start: Vec<[F; T]> = vec![[F::zero(); T]; r_f_half]; - constants_start[0] = constants[0]; - for (optimized, constants) in - constants_start.iter_mut().skip(1).zip(constants.iter().skip(1)) - { - *optimized = inverse_mds.mul_vector(constants); - } - - // Calculate constants for partial rounds - let mut acc = constants[r_f_half + r_p]; - let mut constants_partial = vec![F::zero(); r_p]; - for (optimized, constants) in constants_partial - .iter_mut() - .rev() - .zip(constants.iter().skip(r_f_half).rev().skip(r_f_half)) - { - let mut tmp = inverse_mds.mul_vector(&acc); - *optimized = tmp[0]; - - tmp[0] = F::zero(); - for ((acc, tmp), constant) in acc.iter_mut().zip(tmp.into_iter()).zip(constants.iter()) - { - *acc = tmp + constant - } - } - constants_start.push(inverse_mds.mul_vector(&acc)); - - // Calculate optimized constants for ending half of the full rounds - let mut constants_end: Vec<[F; T]> = vec![[F::zero(); T]; r_f_half - 1]; - for (optimized, constants) in - constants_end.iter_mut().zip(constants.iter().skip(r_f_half + r_p + 1)) - { - *optimized = inverse_mds.mul_vector(constants); - } - - OptimizedConstants { - start: constants_start, - partial: constants_partial, - end: constants_end, - } - } - - fn calculate_sparse_matrices( - r_p: usize, - mds: &MDSMatrix, - ) -> (Vec>, MDSMatrix) { - let mds = mds.transpose(); - let mut acc = mds.clone(); - let mut sparse_matrices = (0..r_p) - .map(|_| { - let (m_prime, m_prime_prime) = acc.factorise(); - acc = mds.mul(&m_prime); - m_prime_prime - }) - .collect::>>(); - - sparse_matrices.reverse(); - (sparse_matrices, acc.transpose()) - } -} - -// ================ END OF CONSTRUCTION OF POSEIDON SPEC ==================== - -// now we get to actual trait based implementation of Poseidon permutation // this works for any loader, where the two loaders used are NativeLoader (native rust) and Halo2Loader (ZK circuit) #[derive(Clone, Debug)] -struct State { +struct State { inner: [L; T], _marker: PhantomData, } // the transcript hash implementation is the one suggested in the original paper https://eprint.iacr.org/2019/458.pdf // another reference implementation is https://github.com/privacy-scaling-explorations/halo2wrong/tree/master/transcript/src -impl, const T: usize, const RATE: usize> State { +impl, const T: usize, const RATE: usize> State { fn new(inner: [L; T]) -> Self { Self { inner, _marker: PhantomData } } fn default(loader: &L::Loader) -> Self { - let mut default_state = [F::zero(); T]; + let mut default_state = [F::ZERO; T]; // from Section 4.2 of https://eprint.iacr.org/2019/458.pdf // • Variable-Input-Length Hashing. The capacity value is 2^64 + (o−1) where o the output length. // for our transcript use cases, o = 1 @@ -376,8 +72,8 @@ impl, const T: usize, const RATE: usize> State, const T: usize, const RATE: usize> State) { self.inner = iter::once( self.loader() - .sum_with_coeff(&mds.row.iter().cloned().zip(self.inner.iter()).collect_vec()), + .sum_with_coeff(&mds.row().iter().cloned().zip(self.inner.iter()).collect_vec()), ) - .chain(mds.col_hat.iter().zip(self.inner.iter().skip(1)).map(|(coeff, state)| { - self.loader().sum_with_coeff(&[(*coeff, &self.inner[0]), (F::one(), state)]) + .chain(mds.col_hat().iter().zip(self.inner.iter().skip(1)).map(|(coeff, state)| { + self.loader().sum_with_coeff(&[(*coeff, &self.inner[0]), (F::ONE, state)]) })) .collect_vec() .try_into() @@ -411,19 +107,22 @@ impl, const T: usize, const RATE: usize> State { +pub struct Poseidon { spec: OptimizedPoseidonSpec, default_state: State, state: State, buf: Vec, } -impl, const T: usize, const RATE: usize> Poseidon { +impl, const T: usize, const RATE: usize> Poseidon { /// Initialize a poseidon hasher. /// Generates a new spec with specific number of full and partial rounds. `SECURE_MDS` is usually 0, but may need to be specified because insecure matrices may sometimes be generated pub fn new( loader: &L::Loader, - ) -> Self { + ) -> Self + where + F: FieldExt, + { let default_state = State::default(loader); Self { spec: OptimizedPoseidonSpec::new::(), @@ -467,35 +166,35 @@ impl, const T: usize, const RATE: usize> Poseido } fn permutation(&mut self, inputs: &[L]) { - let r_f = self.spec.r_f / 2; - let mds = self.spec.mds_matrices.mds.0; - let pre_sparse_mds = self.spec.mds_matrices.pre_sparse_mds.0; - let sparse_matrices = &self.spec.mds_matrices.sparse_matrices; + let r_f = self.spec.r_f() / 2; + let mds = self.spec.mds_matrices().mds().as_ref(); + let pre_sparse_mds = self.spec.mds_matrices().pre_sparse_mds().as_ref(); + let sparse_matrices = &self.spec.mds_matrices().sparse_matrices(); // First half of the full rounds - let constants = &self.spec.constants.start; + let constants = self.spec.constants().start(); self.state.absorb_with_pre_constants(inputs, &constants[0]); for constants in constants.iter().skip(1).take(r_f - 1) { self.state.sbox_full(constants); - self.state.apply_mds(&mds); + self.state.apply_mds(mds); } self.state.sbox_full(constants.last().unwrap()); - self.state.apply_mds(&pre_sparse_mds); + self.state.apply_mds(pre_sparse_mds); // Partial rounds - let constants = &self.spec.constants.partial; + let constants = self.spec.constants().partial(); for (constant, sparse_mds) in constants.iter().zip(sparse_matrices.iter()) { self.state.sbox_part(constant); self.state.apply_sparse_mds(sparse_mds); } // Second half of the full rounds - let constants = &self.spec.constants.end; + let constants = self.spec.constants().end(); for constants in constants.iter() { self.state.sbox_full(constants); - self.state.apply_mds(&mds); + self.state.apply_mds(mds); } - self.state.sbox_full(&[F::zero(); T]); - self.state.apply_mds(&mds); + self.state.sbox_full(&[F::ZERO; T]); + self.state.apply_mds(mds); } } diff --git a/snark-verifier/src/util/hash/poseidon/tests.rs b/snark-verifier/src/util/hash/poseidon/tests.rs index cf4712bc..507b1d7c 100644 --- a/snark-verifier/src/util/hash/poseidon/tests.rs +++ b/snark-verifier/src/util/hash/poseidon/tests.rs @@ -24,7 +24,7 @@ fn test_mds() { "11597556804922396090267472882856054602429588299176362916247939723151043581408", ], ]; - for (row1, row2) in mds.iter().zip_eq(spec.mds_matrices.mds.0.iter()) { + for (row1, row2) in mds.iter().zip_eq(spec.mds_matrices().mds().as_ref().iter()) { for (e1, e2) in row1.iter().zip_eq(row2.iter()) { assert_eq!(Fr::from_str_vartime(e1).unwrap(), *e2); } @@ -47,7 +47,7 @@ fn test_poseidon_against_test_vectors() { hasher.state = State::new(state.try_into().unwrap()); hasher.permutation(&[(); RATE].map(|_| Fr::zero())); // avoid padding let state_0 = hasher.state.inner; - let expected = vec![ + let expected = [ "7853200120776062878684798364095072458815029376092732009249414926327459813530", "7142104613055408817911962100316808866448378443474503659992478482890339429929", "6549537674122432311777789598043107870002137484850126429160507761192163713804", @@ -71,7 +71,7 @@ fn test_poseidon_against_test_vectors() { hasher.state = State::new(state.try_into().unwrap()); hasher.permutation(&[(); RATE].map(|_| Fr::zero())); let state_0 = hasher.state.inner; - let expected = vec![ + let expected = [ "18821383157269793795438455681495246036402687001665670618754263018637548127333", "7817711165059374331357136443537800893307845083525445872661165200086166013245", "16733335996448830230979566039396561240864200624113062088822991822580465420551", diff --git a/snark-verifier/src/util/msm.rs b/snark-verifier/src/util/msm.rs index 8d18cdf8..fef53e59 100644 --- a/snark-verifier/src/util/msm.rs +++ b/snark-verifier/src/util/msm.rs @@ -71,7 +71,7 @@ where let gen = gen.map(|gen| self.bases.first().unwrap().loader().ec_point_load_const(&gen)); let pairs = iter::empty() .chain(self.constant.as_ref().map(|constant| (constant, gen.as_ref().unwrap()))) - .chain(self.scalars.iter().zip(self.bases.into_iter())) + .chain(self.scalars.iter().zip(self.bases)) .collect_vec(); L::multi_scalar_multiplication(&pairs) } diff --git a/snark-verifier/src/util/poly.rs b/snark-verifier/src/util/poly.rs index 17a065f9..86271af4 100644 --- a/snark-verifier/src/util/poly.rs +++ b/snark-verifier/src/util/poly.rs @@ -55,7 +55,7 @@ impl Polynomial { /// Returns evaluation at given `x`. pub fn evaluate(&self, x: F) -> F { let evaluate_serial = - |coeffs: &[F]| coeffs.iter().rev().fold(F::zero(), |acc, coeff| acc * x + coeff); + |coeffs: &[F]| coeffs.iter().rev().fold(F::ZERO, |acc, coeff| acc * x + coeff); #[cfg(feature = "parallel")] { @@ -68,17 +68,15 @@ impl Polynomial { } let chunk_size = Integer::div_ceil(&self.len(), &num_threads); - let mut results = vec![F::zero(); num_threads]; + let mut results = vec![F::ZERO; num_threads]; parallelize_iter( - results.iter_mut().zip(self.0.chunks(chunk_size)).zip(powers(x.pow_vartime(&[ - chunk_size as u64, - 0, - 0, - 0, - ]))), + results + .iter_mut() + .zip(self.0.chunks(chunk_size)) + .zip(powers(x.pow_vartime([chunk_size as u64]))), |((result, coeffs), scalar)| *result = evaluate_serial(coeffs) * scalar, ); - results.iter().fold(F::zero(), |acc, result| acc + result) + results.iter().fold(F::ZERO, |acc, result| acc + result) } #[cfg(not(feature = "parallel"))] evaluate_serial(&self.0) @@ -133,10 +131,10 @@ impl Mul for Polynomial { type Output = Polynomial; fn mul(mut self, rhs: F) -> Polynomial { - if rhs == F::zero() { - return Polynomial::new(vec![F::zero(); self.len()]); + if rhs == F::ZERO { + return Polynomial::new(vec![F::ZERO; self.len()]); } - if rhs == F::one() { + if rhs == F::ONE { return self; } parallelize(&mut self.0, |(lhs, _)| { diff --git a/snark-verifier/src/verifier/plonk.rs b/snark-verifier/src/verifier/plonk.rs index d5937ab8..9c910400 100644 --- a/snark-verifier/src/verifier/plonk.rs +++ b/snark-verifier/src/verifier/plonk.rs @@ -15,7 +15,10 @@ use crate::{ AccumulationDecider, AccumulationScheme, AccumulatorEncoding, PolynomialCommitmentScheme, Query, }, - util::{arithmetic::CurveAffine, transcript::TranscriptRead}, + util::{ + arithmetic::{CurveAffine, Rotation}, + transcript::TranscriptRead, + }, verifier::{plonk::protocol::CommonPolynomialEvaluation, SnarkVerifier}, Error, }; @@ -62,8 +65,12 @@ where proof: &Self::Proof, ) -> Result { let common_poly_eval = { - let mut common_poly_eval = - CommonPolynomialEvaluation::new(&protocol.domain, protocol.langranges(), &proof.z); + let mut common_poly_eval = CommonPolynomialEvaluation::new( + &protocol.domain, + protocol.langranges(), + &proof.z, + &protocol.domain_as_witness, + ); L::batch_invert(common_poly_eval.denoms()); common_poly_eval.evaluate(); @@ -140,7 +147,7 @@ where L: Loader, AS: AccumulationScheme + PolynomialCommitmentScheme - + CostEstimation>>, + + CostEstimation>>, { type Input = PlonkProtocol; @@ -168,7 +175,7 @@ where L: Loader, AS: AccumulationScheme + PolynomialCommitmentScheme - + CostEstimation>>, + + CostEstimation>>, { type Input = PlonkProtocol; diff --git a/snark-verifier/src/verifier/plonk/proof.rs b/snark-verifier/src/verifier/plonk/proof.rs index 7adba7ac..459027ba 100644 --- a/snark-verifier/src/verifier/plonk/proof.rs +++ b/snark-verifier/src/verifier/plonk/proof.rs @@ -13,7 +13,10 @@ use crate::{ }, Error, }; -use std::{collections::HashMap, iter}; +use std::{ + collections::{BTreeMap, HashMap}, + iter, +}; /// Proof of PLONK with [`PolynomialCommitmentScheme`] that has /// [`AccumulationScheme`]. @@ -153,26 +156,42 @@ where } /// Empty queries - pub fn empty_queries(protocol: &PlonkProtocol) -> Vec> { - protocol - .queries - .iter() - .map(|query| { - let shift = protocol.domain.rotate_scalar(C::Scalar::one(), query.rotation); - pcs::Query::new(query.poly, shift) - }) - .collect() + pub fn empty_queries(protocol: &PlonkProtocol) -> Vec> { + // `preprocessed` should always be non-empty, unless the circuit has no constraints or constants + protocol.queries.iter().map(|query| pcs::Query::new(query.poly, query.rotation)).collect() } pub(super) fn queries( &self, protocol: &PlonkProtocol, mut evaluations: HashMap, - ) -> Vec> { + ) -> Vec> { + if protocol.queries.is_empty() { + return vec![]; + } + let loader = evaluations[&protocol.queries[0]].loader(); + let rotations = + protocol.queries.iter().map(|query| query.rotation).sorted().dedup().collect_vec(); + let loaded_shifts = if let Some(domain) = protocol.domain_as_witness.as_ref() { + // the `rotation`s are still constants, it is only generator `omega` that might be witness + BTreeMap::from_iter( + rotations.into_iter().map(|rotation| (rotation, domain.rotate_one(rotation))), + ) + } else { + BTreeMap::from_iter(rotations.into_iter().map(|rotation| { + ( + rotation, + loader.load_const(&protocol.domain.rotate_scalar(C::Scalar::ONE, rotation)), + ) + })) + }; Self::empty_queries(protocol) .into_iter() .zip(protocol.queries.iter().map(|query| evaluations.remove(query).unwrap())) - .map(|(query, eval)| query.with_evaluation(eval)) + .map(|(query, eval)| { + let shift = loaded_shifts[&query.shift].clone(); + query.with_evaluation(shift, eval) + }) .collect() } diff --git a/snark-verifier/src/verifier/plonk/protocol.rs b/snark-verifier/src/verifier/plonk/protocol.rs index a3a84346..9260e572 100644 --- a/snark-verifier/src/verifier/plonk/protocol.rs +++ b/snark-verifier/src/verifier/plonk/protocol.rs @@ -1,7 +1,7 @@ use crate::{ loader::{native::NativeLoader, LoadedScalar, Loader}, util::{ - arithmetic::{CurveAffine, Domain, Field, Fraction, Rotation}, + arithmetic::{CurveAffine, Domain, Field, Fraction, PrimeField, Rotation}, Itertools, }, }; @@ -9,13 +9,46 @@ use num_integer::Integer; use num_traits::One; use serde::{Deserialize, Serialize}; use std::{ - cmp::max, + cmp::{max, Ordering}, collections::{BTreeMap, BTreeSet}, fmt::Debug, iter::{self, Sum}, ops::{Add, Mul, Neg, Sub}, }; +/// Domain parameters to be optionally loaded as witnesses +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DomainAsWitness +where + C: CurveAffine, + L: Loader, +{ + /// 2k is the number of rows in the domain + pub k: L::LoadedScalar, + /// n = 2k is the number of rows in the domain + pub n: L::LoadedScalar, + /// Generator of the domain + pub gen: L::LoadedScalar, + /// Inverse generator of the domain + pub gen_inv: L::LoadedScalar, +} + +impl DomainAsWitness +where + C: CurveAffine, + L: Loader, +{ + /// Rotate `F::one()` to given `rotation`. + pub fn rotate_one(&self, rotation: Rotation) -> L::LoadedScalar { + let loader = self.gen.loader(); + match rotation.0.cmp(&0) { + Ordering::Equal => loader.load_one(), + Ordering::Greater => self.gen.pow_const(rotation.0 as u64), + Ordering::Less => self.gen_inv.pow_const(-rotation.0 as u64), + } + } +} + /// Protocol specifying configuration of a PLONK. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PlonkProtocol @@ -29,6 +62,14 @@ where ))] /// Working domain. pub domain: Domain, + + #[serde(bound( + serialize = "L::LoadedScalar: Serialize", + deserialize = "L::LoadedScalar: Deserialize<'de>" + ))] + /// Optional: load `domain.n` and `domain.gen` as a witness + pub domain_as_witness: Option>, + #[serde(bound( serialize = "L::LoadedEcPoint: Serialize", deserialize = "L::LoadedEcPoint: Deserialize<'de>" @@ -115,6 +156,7 @@ where .map(|transcript_initial_state| loader.load_const(transcript_initial_state)); PlonkProtocol { domain: self.domain.clone(), + domain_as_witness: None, preprocessed, num_instance: self.num_instance.clone(), num_witness: self.num_witness.clone(), @@ -133,12 +175,18 @@ where #[cfg(feature = "loader_halo2")] mod halo2 { use crate::{ - loader::halo2::{EccInstructions, Halo2Loader}, + loader::{ + halo2::{EccInstructions, Halo2Loader}, + LoadedScalar, ScalarLoader, + }, util::arithmetic::CurveAffine, verifier::plonk::PlonkProtocol, }; + use halo2_base::utils::bit_length; use std::rc::Rc; + use super::{DomainAsWitness, PrimeField}; + impl PlonkProtocol where C: CurveAffine, @@ -149,7 +197,23 @@ mod halo2 { pub fn loaded_preprocessed_as_witness>( &self, loader: &Rc>, + load_k_as_witness: bool, ) -> PlonkProtocol>> { + let domain_as_witness = load_k_as_witness.then(|| { + let k = loader.assign_scalar(C::Scalar::from(self.domain.k as u64)); + // n = 2^k + let two = loader.load_const(&C::Scalar::from(2)); + let n = two.pow_var(&k, bit_length(C::Scalar::S as u64) + 1); + // gen = omega = ROOT_OF_UNITY ^ {2^{S - k}}, where ROOT_OF_UNITY is primitive 2^S root of unity + // this makes omega a 2^k root of unity + let root_of_unity = loader.load_const(&C::Scalar::ROOT_OF_UNITY); + let s = loader.load_const(&C::Scalar::from(C::Scalar::S as u64)); + let exp = two.pow_var(&(s - &k), bit_length(C::Scalar::S as u64)); // if S - k < 0, constraint on max bits will fail + let gen = root_of_unity.pow_var(&exp, C::Scalar::S as usize); // 2^{S - k} < 2^S for k > 0 + let gen_inv = gen.invert().expect("subgroup generation is invertible"); + DomainAsWitness { k, n, gen, gen_inv } + }); + let preprocessed = self .preprocessed .iter() @@ -161,6 +225,7 @@ mod halo2 { .map(|transcript_initial_state| loader.assign_scalar(*transcript_initial_state)); PlonkProtocol { domain: self.domain.clone(), + domain_as_witness, preprocessed, num_instance: self.num_instance.clone(), num_witness: self.num_witness.clone(), @@ -201,26 +266,39 @@ where C: CurveAffine, L: Loader, { + // if `n_as_witness` is Some, then we assume `n_as_witness` has value equal to `domain.n` (i.e., number of rows in the circuit) + // and is loaded as a witness instead of a constant. + // The generator of `domain` also depends on `n`. pub fn new( domain: &Domain, - langranges: impl IntoIterator, + lagranges: impl IntoIterator, z: &L::LoadedScalar, + domain_as_witness: &Option>, ) -> Self { let loader = z.loader(); - let zn = z.pow_const(domain.n as u64); - let langranges = langranges.into_iter().sorted().dedup().collect_vec(); - + let lagranges = lagranges.into_iter().sorted().dedup().collect_vec(); let one = loader.load_one(); + + let (zn, n_inv, omegas) = if let Some(domain) = domain_as_witness.as_ref() { + let zn = z.pow_var(&domain.n, C::Scalar::S as usize + 1); + let n_inv = domain.n.invert().expect("n is not zero"); + let omegas = lagranges.iter().map(|&i| domain.rotate_one(Rotation(i))).collect_vec(); + (zn, n_inv, omegas) + } else { + let zn = z.pow_const(domain.n as u64); + let n_inv = loader.load_const(&domain.n_inv); + let omegas = lagranges + .iter() + .map(|&i| loader.load_const(&domain.rotate_scalar(C::Scalar::ONE, Rotation(i)))) + .collect_vec(); + (zn, n_inv, omegas) + }; + let zn_minus_one = zn.clone() - &one; let zn_minus_one_inv = Fraction::one_over(zn_minus_one.clone()); - let n_inv = loader.load_const(&domain.n_inv); let numer = zn_minus_one.clone() * &n_inv; - let omegas = langranges - .iter() - .map(|&i| loader.load_const(&domain.rotate_scalar(C::Scalar::one(), Rotation(i)))) - .collect_vec(); let lagrange_evals = omegas .iter() .map(|omega| Fraction::new(numer.clone() * omega, z.clone() - omega)) @@ -231,7 +309,7 @@ where zn_minus_one, zn_minus_one_inv, identity: z.clone(), - lagrange: langranges.into_iter().zip(lagrange_evals).collect(), + lagrange: lagranges.into_iter().zip(lagrange_evals).collect(), } } @@ -478,7 +556,7 @@ impl Sum for Expression { impl One for Expression { fn one() -> Self { - Expression::Constant(F::one()) + Expression::Constant(F::ONE) } }