diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..ee9196fd6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +name: Tests + +on: + push: + branches: ["main", "community-edition"] + pull_request: + branches: ["main", "community-edition"] + +env: + CARGO_TERM_COLOR: always + INFURA_ID: ${{ vars.INFURA_ID }} + JSON_RPC_URL: ${{ vars.JSON_RPC_URL }} + +jobs: + build: + runs-on: ubuntu-latest-m + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run axiom-eth tests MockProver + run: | + export INFURA_ID=${{ vars.INFURA_ID }} + export JSON_RPC_URL=${{ vars.JSON_RPC_URL }} + cd axiom-eth + cargo test -- test_mock_account_queries_empty + cargo test -- test_mock_account_queries_genesis + cargo test -- test_mock_account_queries_simple + cargo test -- test_genesis_block + cargo test -- test_mock_block_queries_random + cargo test -- test_mock_row_consistency_nouns + cargo test -- test_default_storage_query + cargo test -- test_mock_storage_queries_empty + cargo test -- test_mock_storage_queries_mapping + cargo test -- test_mock_storage_queries_slot0 + cargo test -- test_mock_storage_queries_uni_v3 + cargo test -- test_multi_goerli_header_mock + cargo test -- test_one_mainnet_header_before_london_mock + cargo test -- test_one_mainnet_header_mock + cargo test -- test_one_mainnet_header_withdrawals_mock + cargo test -- test_keccak + cargo test -- test_mock_mpt_inclusion_fixed + cargo test -- test_mpt_empty_root + cargo test -- test_mpt_noninclusion_branch_fixed + cargo test -- test_mpt_noninclusion_extension_fixed + cargo test -- test_mock_rlc + cargo test -- test_mock_rlp_array + cargo test -- test_mock_rlp_field + cargo test -- test_mock_rlp_literal + cargo test -- test_mock_rlp_long_field + cargo test -- test_mock_rlp_long_long_field + cargo test -- test_mock_rlp_short_field + cargo test -- test_mock_single_eip1186 diff --git a/.gitignore b/.gitignore index b73176140..659830831 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,17 @@ -**/target +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock + +# These are backup files generated by rustfmt **/*.rs.bk -*~ \ No newline at end of file +======= +/target + +*.png + +/halo2_ecc/src/bn254/data/ +/halo2_ecc/src/secp256k1/data/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..e69de29bb diff --git a/Cargo.toml b/Cargo.toml index 6bced82ea..783c4f726 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,71 +1,39 @@ -[package] -name = "axiom-eth" -version = "0.1.1" -edition = "2021" -autobins = false - -[[bin]] -name = "header_chain" -required-features = ["aggregation", "clap"] - -[[bin]] -name = "storage_proof" -required-features = ["aggregation", "clap", "evm"] - -[dependencies] -itertools = "0.10" -lazy_static = "1.4.0" -serde = { version = "1.0", default-features = false, features = ["derive"] } -serde_json = { version = "1.0", default-features = false } -rayon = "1.7" - -# misc -log = "0.4" -env_logger = "0.10" -ark-std = { version = "0.3.0", features = ["print-trace"], optional = true } -clap = { version = "4.0.13", features = ["derive"], optional = true } -clap-num = { version = "1.0.2", optional = true } -bincode = { version = "1.3.3", optional = true } -base64 = { version = "0.21", optional = true } -serde_with = { version = "2.2", optional = true } - -# halo2 -ff = "0.12" -halo2-base = { git = "https://github.com/axiom-crypto/halo2-lib.git", branch = "community-edition", default-features = false } -zkevm-keccak = { git = "https://github.com/axiom-crypto/halo2-lib.git", branch = "community-edition", default-features = false } - -# crypto -rlp = "0.5.2" -ethers-core = { version = "=2.0.6" } # used by halo2-mpt already -rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } -rand = "0.8" -rand_chacha = "0.3.1" - -# aggregation -snark-verifier = { git = "https://github.com/axiom-crypto/snark-verifier.git", branch = "community-edition", default-features = false, features = ["loader_halo2"], optional = true } -snark-verifier-sdk = { git = "https://github.com/axiom-crypto/snark-verifier.git", branch = "community-edition", default-features = false, features = ["loader_halo2"], optional = true } - -# generating circuit inputs from blockchain -ethers-providers = { version = "2.0.2", optional = true } -tokio = { version = "1.26", default-features = false, features = ["rt", "rt-multi-thread"], optional = true } - -[dev-dependencies] -hex = "0.4.3" -ark-std = { version = "0.3.0", features = ["print-trace"] } -log = "0.4" -test-log = "0.2.11" -test-case = "3.1.0" - -[features] -default = ["halo2-axiom", "jemallocator", "display", "aggregation", "clap", "evm"] -aggregation = ["dep:snark-verifier", "snark-verifier-sdk", "providers"] -evm = ["snark-verifier-sdk?/loader_evm", "aggregation"] -providers = ["dep:ethers-providers", "dep:tokio", "dep:bincode", "dep:base64", "dep:serde_with"] -display = ["zkevm-keccak/display", "snark-verifier-sdk?/display", "dep:ark-std"] -clap = ["dep:clap", "dep:clap-num"] -# 'production' feature turns off circuit auto-configuration and forces trusted setup SRS to be read (and not generated) -production = [] -# EXACTLY one of halo2-pse / halo2-axiom should always be turned on -halo2-pse = ["zkevm-keccak/halo2-pse", "snark-verifier-sdk?/halo2-pse"] -halo2-axiom = ["zkevm-keccak/halo2-axiom", "snark-verifier-sdk?/halo2-axiom"] -jemallocator = ["halo2-base/jemallocator"] +[workspace] +members = [ + "axiom-eth", + "any_circuit_derive" +] + +[profile.dev] +opt-level = 3 +debug = 1 # change to 0 or 2 for more or less debug info +overflow-checks = true +incremental = true + +# Local "release" mode, more optimized than dev but faster to compile than release +[profile.local] +inherits = "dev" +opt-level = 3 +# Set this to 1 or 2 to get more useful backtraces +debug = 1 +debug-assertions = true +panic = 'unwind' +# better recompile times +incremental = true +lto = "thin" +codegen-units = 16 + +[profile.release] +opt-level = 3 +debug = false +debug-assertions = false +lto = "fat" +# `codegen-units = 1` can lead to WORSE performance - always bench to find best profile for your machine! +# codegen-units = 1 +panic = "abort" +incremental = false + +# For performance profiling +[profile.flamegraph] +inherits = "release" +debug = true diff --git a/README.md b/README.md index e69de29bb..b347a4afb 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,3 @@ +# axiom-eth + +This repository builds on [halo2-lib](https://github.com/axiom-crypto/halo2-lib/releases/tag/v0.3.0) and [snark-verifier](https://github.com/axiom-crypto/snark-verifier/tree/v0.1.1) to create a library of zero-knowledge proof circuits that [Axiom](https://axiom.xyz) uses to read data from the Ethereum virtual machine (EVM). diff --git a/any_circuit_derive/Cargo.toml b/any_circuit_derive/Cargo.toml new file mode 100644 index 000000000..bde6e6b06 --- /dev/null +++ b/any_circuit_derive/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "any_circuit_derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = "2.0.15" +quote = "1.0.26" +proc-macro2 = "1.0.56" diff --git a/any_circuit_derive/src/lib.rs b/any_circuit_derive/src/lib.rs new file mode 100644 index 000000000..7d53d4643 --- /dev/null +++ b/any_circuit_derive/src/lib.rs @@ -0,0 +1,100 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput}; + +#[proc_macro_derive(AnyCircuit)] +pub fn any_circuit_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + let generics = &input.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let variants = match input.data { + Data::Enum(data_enum) => data_enum.variants, + _ => panic!("AnyCircuit can only be derived for enums"), + }; + + let read_or_create_pk_arms = variants.iter().map(|variant| { + let ident = &variant.ident; + quote! { + Self::#ident(pre_circuit) => pre_circuit.read_or_create_pk(params, pk_path, pinning_path, read_only) + } + }); + + let gen_snark_shplonk_arms = variants.iter().map(|variant| { + let ident = &variant.ident; + quote! { + Self::#ident(pre_circuit) => pre_circuit.gen_snark_shplonk(params, pk, pinning_path, path) + } + }); + + let gen_evm_verifier_shplonk_arms = variants.iter().map(|variant| { + let ident = &variant.ident; + quote! { + Self::#ident(pre_circuit) => pre_circuit.gen_evm_verifier_shplonk(params, pk, yul_path) + } + }); + + let gen_calldata_arms = variants.iter().map(|variant| { + let ident = &variant.ident; + quote! { + Self::#ident(pre_circuit) => pre_circuit.gen_calldata(params, pk, pinning_path, path, deployment_code) + } + }); + + let expanded = quote! { + impl #impl_generics AnyCircuit for #name #ty_generics #where_clause { + fn read_or_create_pk( + self, + params: &ParamsKZG, + pk_path: impl AsRef, + pinning_path: impl AsRef, + read_only: bool, + ) -> ProvingKey { + match self { + #(#read_or_create_pk_arms,)* + } + } + + fn gen_snark_shplonk( + self, + params: &ParamsKZG, + pk: &ProvingKey, + pinning_path: impl AsRef, + path: Option>, + ) -> Snark { + match self { + #(#gen_snark_shplonk_arms,)* + } + } + + fn gen_evm_verifier_shplonk( + self, + params: &ParamsKZG, + pk: &ProvingKey, + yul_path: impl AsRef, + ) -> Vec { + match self { + #(#gen_evm_verifier_shplonk_arms,)* + } + } + + fn gen_calldata( + self, + params: &ParamsKZG, + pk: &ProvingKey, + pinning_path: impl AsRef, + path: impl AsRef, + deployment_code: Option>, + ) -> String { + match self { + #(#gen_calldata_arms,)* + } + } + } + }; + + TokenStream::from(expanded) +} diff --git a/axiom-eth/.env.example b/axiom-eth/.env.example new file mode 100644 index 000000000..ae955fa8d --- /dev/null +++ b/axiom-eth/.env.example @@ -0,0 +1,2 @@ +# need to export for tests to work +export JSON_RPC_URL= diff --git a/axiom-eth/.gitignore b/axiom-eth/.gitignore new file mode 100644 index 000000000..b73176140 --- /dev/null +++ b/axiom-eth/.gitignore @@ -0,0 +1,4 @@ +**/target +Cargo.lock +**/*.rs.bk +*~ \ No newline at end of file diff --git a/axiom-eth/Cargo.toml b/axiom-eth/Cargo.toml new file mode 100644 index 000000000..244789721 --- /dev/null +++ b/axiom-eth/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "axiom-eth" +version = "0.2.1" +edition = "2021" + +[[bin]] +name = "header_chain" +required-features = ["aggregation", "clap"] + +[dependencies] +itertools = "0.10" +lazy_static = "1.4.0" +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0", default-features = false } +rayon = "1.7" + +# misc +log = "0.4" +env_logger = "0.10" +ark-std = { version = "0.3.0", features = ["print-trace"], optional = true } +clap = { version = "4.0.13", features = ["derive"], optional = true } +clap-num = { version = "1.0.2", optional = true } +bincode = { version = "1.3.3", optional = true } +base64 = { version = "0.21", optional = true } +serde_with = { version = "2.2", optional = true } + +# halo2 +ff = "0.12" +halo2-base = { git = "https://github.com/axiom-crypto/halo2-lib.git", tag = "v0.3.0", default-features = false } +zkevm-keccak = { git = "https://github.com/axiom-crypto/halo2-lib.git", tag = "v0.3.0", default-features = false } +# macro +any_circuit_derive = { path = "../any_circuit_derive", optional = true } + +# crypto +rlp = "=0.5.2" +ethers-core = { version = "=2.0.6" } +rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } +rand = "0.8" +rand_chacha = "0.3.1" + +# aggregation +snark-verifier = { git = "https://github.com/axiom-crypto/snark-verifier.git", tag = "v0.1.1", default-features = false, features = ["loader_halo2"], optional = true } +snark-verifier-sdk = { git = "https://github.com/axiom-crypto/snark-verifier.git", tag = "v0.1.1", default-features = false, features = ["loader_halo2"], optional = true } + +# generating circuit inputs from blockchain +ethers-providers = { version = "=2.0.6", optional = true } +tokio = { version = "=1.28", default-features = false, features = ["rt", "rt-multi-thread"], optional = true } +futures = { version = "=0.3", optional = true } + +[dev-dependencies] +hex = "0.4.3" +ark-std = { version = "0.3.0", features = ["print-trace"] } +log = "0.4" +test-log = "0.2.11" +test-case = "3.1.0" + +[features] +default = ["halo2-axiom", "jemallocator", "display", "aggregation", "clap", "evm"] +aggregation = ["dep:snark-verifier", "snark-verifier-sdk", "providers", "dep:any_circuit_derive"] +evm = ["snark-verifier-sdk?/loader_evm", "aggregation"] +providers = ["dep:ethers-providers", "dep:tokio", "dep:bincode", "dep:base64", "dep:serde_with", "dep:futures"] +display = ["zkevm-keccak/display", "snark-verifier-sdk?/display", "dep:ark-std"] +clap = ["dep:clap", "dep:clap-num"] +# 'production' feature turns off circuit auto-configuration and forces trusted setup SRS to be read (and not generated) +production = [] +# EXACTLY one of halo2-pse / halo2-axiom should always be turned on +halo2-pse = ["zkevm-keccak/halo2-pse", "snark-verifier-sdk?/halo2-pse"] +halo2-axiom = ["zkevm-keccak/halo2-axiom", "snark-verifier-sdk?/halo2-axiom"] +jemallocator = ["halo2-base/jemallocator"] diff --git a/axiom-eth/LICENSE b/axiom-eth/LICENSE new file mode 100644 index 000000000..69ec60b53 --- /dev/null +++ b/axiom-eth/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Axiom + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/axiom-eth/README.md b/axiom-eth/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/configs/bench/keccak.json b/axiom-eth/configs/bench/keccak.json similarity index 100% rename from configs/bench/keccak.json rename to axiom-eth/configs/bench/keccak.json diff --git a/configs/bench/mpt.json b/axiom-eth/configs/bench/mpt.json similarity index 100% rename from configs/bench/mpt.json rename to axiom-eth/configs/bench/mpt.json diff --git a/configs/bench/storage.json b/axiom-eth/configs/bench/storage.json similarity index 100% rename from configs/bench/storage.json rename to axiom-eth/configs/bench/storage.json diff --git a/configs/bench/storage_evm.json b/axiom-eth/configs/bench/storage_evm.json similarity index 100% rename from configs/bench/storage_evm.json rename to axiom-eth/configs/bench/storage_evm.json diff --git a/configs/headers/goerli_3.json b/axiom-eth/configs/headers/goerli_3.json similarity index 98% rename from configs/headers/goerli_3.json rename to axiom-eth/configs/headers/goerli_3.json index d17f8c783..93b9cde6c 100644 --- a/configs/headers/goerli_3.json +++ b/axiom-eth/configs/headers/goerli_3.json @@ -20,26 +20,26 @@ "break_points": { "gate": [ [ - 32706, - 32707, - 32708, 32707, + 32706, 32707, + 32706, 32707, 32708, 32706, 32708, 32708, - 32706, - 32707, 32708, - 32707, + 32708, 32706, 32708, 32707, 32708, 32708, 32708, + 32706, + 32707, + 32707, 32708, 32708 ], diff --git a/configs/headers/goerli_4_3.json b/axiom-eth/configs/headers/goerli_4_3.json similarity index 100% rename from configs/headers/goerli_4_3.json rename to axiom-eth/configs/headers/goerli_4_3.json diff --git a/configs/headers/goerli_4_3_final.json b/axiom-eth/configs/headers/goerli_4_3_final.json similarity index 100% rename from configs/headers/goerli_4_3_final.json rename to axiom-eth/configs/headers/goerli_4_3_final.json diff --git a/configs/headers/goerli_4_3_for_evm_0.json b/axiom-eth/configs/headers/goerli_4_3_for_evm_0.json similarity index 100% rename from configs/headers/goerli_4_3_for_evm_0.json rename to axiom-eth/configs/headers/goerli_4_3_for_evm_0.json diff --git a/axiom-eth/configs/headers/mainnet_10_7.json b/axiom-eth/configs/headers/mainnet_10_7.json new file mode 100644 index 000000000..db678022b --- /dev/null +++ b/axiom-eth/configs/headers/mainnet_10_7.json @@ -0,0 +1,29 @@ +{ + "params": { + "degree": 20, + "num_advice": 15, + "num_lookup_advice": 2, + "num_fixed": 1, + "lookup_bits": 19 + }, + "break_points": [ + [ + 1048566, + 1048565, + 1048564, + 1048564, + 1048564, + 1048566, + 1048566, + 1048565, + 1048566, + 1048566, + 1048566, + 1048565, + 1048565, + 1048565 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/configs/headers/mainnet_10_7_final.json b/axiom-eth/configs/headers/mainnet_10_7_final.json similarity index 86% rename from configs/headers/mainnet_10_7_final.json rename to axiom-eth/configs/headers/mainnet_10_7_final.json index 45b71a084..0fe477120 100644 --- a/configs/headers/mainnet_10_7_final.json +++ b/axiom-eth/configs/headers/mainnet_10_7_final.json @@ -3,12 +3,12 @@ "degree": 19, "num_rlc_columns": 1, "num_range_advice": [ - 25, + 30, 0, 0 ], "num_lookup_advice": [ - 3, + 4, 0, 0 ], @@ -21,29 +21,34 @@ "gate": [ [ 524178, - 524178, + 524177, 524178, 524176, 524177, + 524177, 524176, 524176, - 524178, 524177, - 524176, 524177, + 524176, + 524178, 524178, 524177, 524178, 524178, 524178, - 524177, 524178, 524176, 524177, - 524176, + 524178, 524177, 524177, - 524177 + 524178, + 524177, + 524178, + 524176, + 524178, + 524178 ], [], [] diff --git a/axiom-eth/configs/headers/mainnet_10_7_for_evm_0.json b/axiom-eth/configs/headers/mainnet_10_7_for_evm_0.json new file mode 100644 index 000000000..45d965b2b --- /dev/null +++ b/axiom-eth/configs/headers/mainnet_10_7_for_evm_0.json @@ -0,0 +1,21 @@ +{ + "params": { + "degree": 22, + "num_advice": 7, + "num_lookup_advice": 1, + "num_fixed": 1, + "lookup_bits": 20 + }, + "break_points": [ + [ + 4194292, + 4194294, + 4194294, + 4194294, + 4194292, + 4194292 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/configs/headers/mainnet_10_7_for_evm_1.json b/axiom-eth/configs/headers/mainnet_10_7_for_evm_1.json similarity index 100% rename from configs/headers/mainnet_10_7_for_evm_1.json rename to axiom-eth/configs/headers/mainnet_10_7_for_evm_1.json diff --git a/configs/headers/mainnet_10_7_historical_0.json b/axiom-eth/configs/headers/mainnet_10_7_historical_0.json similarity index 100% rename from configs/headers/mainnet_10_7_historical_0.json rename to axiom-eth/configs/headers/mainnet_10_7_historical_0.json diff --git a/axiom-eth/configs/headers/mainnet_11_7.json b/axiom-eth/configs/headers/mainnet_11_7.json new file mode 100644 index 000000000..d3af70174 --- /dev/null +++ b/axiom-eth/configs/headers/mainnet_11_7.json @@ -0,0 +1,33 @@ +{ + "params": { + "degree": 20, + "num_advice": 19, + "num_lookup_advice": 3, + "num_fixed": 1, + "lookup_bits": 19 + }, + "break_points": [ + [ + 1048565, + 1048565, + 1048564, + 1048565, + 1048564, + 1048564, + 1048565, + 1048565, + 1048566, + 1048565, + 1048566, + 1048564, + 1048565, + 1048565, + 1048565, + 1048564, + 1048564, + 1048566 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/configs/headers/mainnet_10_7_for_evm_0.json b/axiom-eth/configs/headers/mainnet_12_7.json similarity index 91% rename from configs/headers/mainnet_10_7_for_evm_0.json rename to axiom-eth/configs/headers/mainnet_12_7.json index 74858c8cd..f45235fd6 100644 --- a/configs/headers/mainnet_10_7_for_evm_0.json +++ b/axiom-eth/configs/headers/mainnet_12_7.json @@ -8,17 +8,17 @@ }, "break_points": [ [ - 2097140, 2097141, 2097140, + 2097142, + 2097142, 2097141, + 2097140, 2097142, 2097140, 2097140, 2097141, - 2097142, - 2097140, - 2097142 + 2097140 ], [], [] diff --git a/axiom-eth/configs/headers/mainnet_13_7.json b/axiom-eth/configs/headers/mainnet_13_7.json new file mode 100644 index 000000000..213a02e9a --- /dev/null +++ b/axiom-eth/configs/headers/mainnet_13_7.json @@ -0,0 +1,31 @@ +{ + "params": { + "degree": 20, + "num_advice": 17, + "num_lookup_advice": 2, + "num_fixed": 1, + "lookup_bits": 19 + }, + "break_points": [ + [ + 1048566, + 1048566, + 1048565, + 1048565, + 1048564, + 1048566, + 1048564, + 1048565, + 1048566, + 1048565, + 1048564, + 1048564, + 1048564, + 1048565, + 1048566, + 1048566 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/axiom-eth/configs/headers/mainnet_14_7.json b/axiom-eth/configs/headers/mainnet_14_7.json new file mode 100644 index 000000000..981bb2f5f --- /dev/null +++ b/axiom-eth/configs/headers/mainnet_14_7.json @@ -0,0 +1,35 @@ +{ + "params": { + "degree": 20, + "num_advice": 21, + "num_lookup_advice": 3, + "num_fixed": 1, + "lookup_bits": 19 + }, + "break_points": [ + [ + 1048566, + 1048566, + 1048566, + 1048566, + 1048565, + 1048566, + 1048564, + 1048565, + 1048566, + 1048565, + 1048564, + 1048565, + 1048565, + 1048565, + 1048566, + 1048564, + 1048566, + 1048565, + 1048564, + 1048566 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/configs/headers/mainnet_9_7.json b/axiom-eth/configs/headers/mainnet_15_7.json similarity index 61% rename from configs/headers/mainnet_9_7.json rename to axiom-eth/configs/headers/mainnet_15_7.json index bb40eb761..0616207fc 100644 --- a/configs/headers/mainnet_9_7.json +++ b/axiom-eth/configs/headers/mainnet_15_7.json @@ -1,21 +1,25 @@ { "params": { "degree": 21, - "num_advice": 9, - "num_lookup_advice": 1, + "num_advice": 13, + "num_lookup_advice": 2, "num_fixed": 1, "lookup_bits": 20 }, "break_points": [ [ - 2097141, - 2097141, 2097142, - 2097141, 2097142, + 2097142, + 2097140, 2097140, 2097142, - 2097140 + 2097141, + 2097142, + 2097142, + 2097141, + 2097142, + 2097142 ], [], [] diff --git a/axiom-eth/configs/headers/mainnet_16_7.json b/axiom-eth/configs/headers/mainnet_16_7.json new file mode 100644 index 000000000..a62b893b8 --- /dev/null +++ b/axiom-eth/configs/headers/mainnet_16_7.json @@ -0,0 +1,33 @@ +{ + "params": { + "degree": 20, + "num_advice": 19, + "num_lookup_advice": 2, + "num_fixed": 1, + "lookup_bits": 19 + }, + "break_points": [ + [ + 1048564, + 1048565, + 1048566, + 1048564, + 1048566, + 1048564, + 1048565, + 1048565, + 1048564, + 1048564, + 1048564, + 1048565, + 1048566, + 1048565, + 1048566, + 1048565, + 1048566, + 1048566 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/configs/headers/mainnet_17_7.json b/axiom-eth/configs/headers/mainnet_17_7.json similarity index 100% rename from configs/headers/mainnet_17_7.json rename to axiom-eth/configs/headers/mainnet_17_7.json diff --git a/axiom-eth/configs/headers/mainnet_17_7_final.json b/axiom-eth/configs/headers/mainnet_17_7_final.json new file mode 100644 index 000000000..c5a8f6497 --- /dev/null +++ b/axiom-eth/configs/headers/mainnet_17_7_final.json @@ -0,0 +1,35 @@ +{ + "params": { + "degree": 22, + "num_rlc_columns": 1, + "num_range_advice": [ + 7, + 0, + 0 + ], + "num_lookup_advice": [ + 1, + 0, + 0 + ], + "num_fixed": 1, + "unusable_rows": 109, + "keccak_rows_per_round": 50, + "lookup_bits": 21 + }, + "break_points": { + "gate": [ + [ + 4194194, + 4194192, + 4194194, + 4194194, + 4194192, + 4194192 + ], + [], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-eth/configs/headers/mainnet_17_7_for_evm_0.json b/axiom-eth/configs/headers/mainnet_17_7_for_evm_0.json new file mode 100644 index 000000000..96bdd7023 --- /dev/null +++ b/axiom-eth/configs/headers/mainnet_17_7_for_evm_0.json @@ -0,0 +1,18 @@ +{ + "params": { + "degree": 22, + "num_advice": 4, + "num_lookup_advice": 1, + "num_fixed": 1, + "lookup_bits": 21 + }, + "break_points": [ + [ + 4194293, + 4194292, + 4194293 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/axiom-eth/configs/headers/mainnet_17_7_for_evm_1.json b/axiom-eth/configs/headers/mainnet_17_7_for_evm_1.json new file mode 100644 index 000000000..b3e69eea2 --- /dev/null +++ b/axiom-eth/configs/headers/mainnet_17_7_for_evm_1.json @@ -0,0 +1,14 @@ +{ + "params": { + "degree": 23, + "num_advice": 1, + "num_lookup_advice": 1, + "num_fixed": 1, + "lookup_bits": 22 + }, + "break_points": [ + [], + [], + [] + ] +} \ No newline at end of file diff --git a/configs/headers/mainnet_18_7_final.json b/axiom-eth/configs/headers/mainnet_18_7_final.json similarity index 100% rename from configs/headers/mainnet_18_7_final.json rename to axiom-eth/configs/headers/mainnet_18_7_final.json diff --git a/configs/headers/mainnet_7.json b/axiom-eth/configs/headers/mainnet_7.json similarity index 88% rename from configs/headers/mainnet_7.json rename to axiom-eth/configs/headers/mainnet_7.json index f61695deb..d2e043d4a 100644 --- a/configs/headers/mainnet_7.json +++ b/axiom-eth/configs/headers/mainnet_7.json @@ -20,27 +20,27 @@ "break_points": { "gate": [ [ - 524224, 524222, - 524224, 524223, - 524224, - 524222, - 524222, 524223, - 524224, + 524223, + 524223, + 524223, 524224, 524223, 524222, + 524223, + 524223, + 524223, 524224, + 524223, 524224, 524223, 524224, 524224, - 524223, - 524223, + 524222, 524224, - 524222 + 524224 ], [ 524224, diff --git a/configs/headers/mainnet_8_7.json b/axiom-eth/configs/headers/mainnet_8_7.json similarity index 80% rename from configs/headers/mainnet_8_7.json rename to axiom-eth/configs/headers/mainnet_8_7.json index 6cb5a820a..26662ebee 100644 --- a/configs/headers/mainnet_8_7.json +++ b/axiom-eth/configs/headers/mainnet_8_7.json @@ -1,27 +1,28 @@ { "params": { "degree": 22, - "num_advice": 15, + "num_advice": 16, "num_lookup_advice": 2, "num_fixed": 1, "lookup_bits": 21 }, "break_points": [ [ - 4194292, - 4194292, - 4194292, - 4194293, 4194294, 4194293, - 4194294, 4194292, 4194294, + 4194293, 4194292, + 4194293, + 4194293, 4194294, + 4194292, 4194294, + 4194293, 4194294, - 4194293 + 4194292, + 4194294 ], [], [] diff --git a/axiom-eth/configs/headers/mainnet_9_7.json b/axiom-eth/configs/headers/mainnet_9_7.json new file mode 100644 index 000000000..9bef5275a --- /dev/null +++ b/axiom-eth/configs/headers/mainnet_9_7.json @@ -0,0 +1,24 @@ +{ + "params": { + "degree": 21, + "num_advice": 10, + "num_lookup_advice": 2, + "num_fixed": 1, + "lookup_bits": 20 + }, + "break_points": [ + [ + 2097142, + 2097142, + 2097140, + 2097142, + 2097142, + 2097142, + 2097142, + 2097140, + 2097141 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/configs/storage/mainnet_10.json b/axiom-eth/configs/storage/mainnet_10.json similarity index 100% rename from configs/storage/mainnet_10.json rename to axiom-eth/configs/storage/mainnet_10.json diff --git a/configs/storage/mainnet_10_evm.json b/axiom-eth/configs/storage/mainnet_10_evm.json similarity index 100% rename from configs/storage/mainnet_10_evm.json rename to axiom-eth/configs/storage/mainnet_10_evm.json diff --git a/axiom-eth/configs/tests/account_query.json b/axiom-eth/configs/tests/account_query.json new file mode 100644 index 000000000..d7ad86cb8 --- /dev/null +++ b/axiom-eth/configs/tests/account_query.json @@ -0,0 +1,9 @@ +{ + "degree": 18, + "num_rlc_columns": 3, + "num_range_advice": [40, 23], + "num_lookup_advice": [1, 1], + "num_fixed": 1, + "unusable_rows": 69, + "keccak_rows_per_round": 10 +} diff --git a/axiom-eth/configs/tests/batch_query/account_3.json b/axiom-eth/configs/tests/batch_query/account_3.json new file mode 100644 index 000000000..14d906406 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/account_3.json @@ -0,0 +1,48 @@ +{ + "params": { + "degree": 19, + "num_rlc_columns": 1, + "num_range_advice": [ + 13, + 7, + 0 + ], + "num_lookup_advice": [ + 1, + 1, + 0 + ], + "num_fixed": 1, + "unusable_rows": 87, + "keccak_rows_per_round": 34, + "lookup_bits": 8 + }, + "break_points": { + "gate": [ + [ + 524178, + 524177, + 524178, + 524178, + 524176, + 524176, + 524177, + 524176, + 524177, + 524178, + 524178, + 524177 + ], + [ + 524176, + 524178, + 524176, + 524178, + 524178, + 524177 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/account_3_6_0.json b/axiom-eth/configs/tests/batch_query/account_3_6_0.json new file mode 100644 index 000000000..5250cfe2c --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/account_3_6_0.json @@ -0,0 +1,22 @@ +{ + "params": { + "degree": 22, + "num_advice": 8, + "num_lookup_advice": 1, + "num_fixed": 1, + "lookup_bits": 21 + }, + "break_points": [ + [ + 4194292, + 4194293, + 4194294, + 4194294, + 4194294, + 4194293, + 4194294 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/account_3_6_1.json b/axiom-eth/configs/tests/batch_query/account_3_6_1.json new file mode 100644 index 000000000..d93a7d9b5 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/account_3_6_1.json @@ -0,0 +1,18 @@ +{ + "params": { + "degree": 23, + "num_advice": 4, + "num_lookup_advice": 1, + "num_fixed": 1, + "lookup_bits": 22 + }, + "break_points": [ + [ + 8388598, + 8388597, + 8388598 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/account_3_6_2.json b/axiom-eth/configs/tests/batch_query/account_3_6_2.json new file mode 100644 index 000000000..20b4ebb50 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/account_3_6_2.json @@ -0,0 +1,21 @@ +{ + "params": { + "degree": 23, + "num_advice": 7, + "num_lookup_advice": 1, + "num_fixed": 1, + "lookup_bits": 22 + }, + "break_points": [ + [ + 8388598, + 8388598, + 8388597, + 8388598, + 8388597, + 8388597 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/account_4.json b/axiom-eth/configs/tests/batch_query/account_4.json new file mode 100644 index 000000000..6dea15fe7 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/account_4.json @@ -0,0 +1,48 @@ +{ + "params": { + "degree": 20, + "num_rlc_columns": 1, + "num_range_advice": [ + 13, + 7, + 0 + ], + "num_lookup_advice": [ + 1, + 1, + 0 + ], + "num_fixed": 1, + "unusable_rows": 87, + "keccak_rows_per_round": 34, + "lookup_bits": 8 + }, + "break_points": { + "gate": [ + [ + 1048465, + 1048464, + 1048466, + 1048466, + 1048465, + 1048465, + 1048465, + 1048465, + 1048464, + 1048465, + 1048466, + 1048466 + ], + [ + 1048465, + 1048464, + 1048464, + 1048466, + 1048466, + 1048466 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/account_5.json b/axiom-eth/configs/tests/batch_query/account_5.json new file mode 100644 index 000000000..00f447b62 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/account_5.json @@ -0,0 +1,48 @@ +{ + "params": { + "degree": 21, + "num_rlc_columns": 1, + "num_range_advice": [ + 13, + 7, + 0 + ], + "num_lookup_advice": [ + 1, + 1, + 0 + ], + "num_fixed": 1, + "unusable_rows": 59, + "keccak_rows_per_round": 34, + "lookup_bits": 8 + }, + "break_points": { + "gate": [ + [ + 2097040, + 2097042, + 2097041, + 2097040, + 2097040, + 2097041, + 2097042, + 2097042, + 2097042, + 2097041, + 2097040, + 2097042 + ], + [ + 2097040, + 2097041, + 2097042, + 2097042, + 2097042, + 2097040 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/final_0.json b/axiom-eth/configs/tests/batch_query/final_0.json new file mode 100644 index 000000000..fc3c50ea7 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/final_0.json @@ -0,0 +1,125 @@ +{ + "params": { + "degree": 19, + "num_rlc_columns": 1, + "num_range_advice": [ + 97, + 0, + 0 + ], + "num_lookup_advice": [ + 12, + 0, + 0 + ], + "num_fixed": 2, + "unusable_rows": 109, + "keccak_rows_per_round": 50, + "lookup_bits": 18 + }, + "break_points": { + "gate": [ + [ + 524176, + 524177, + 524178, + 524178, + 524177, + 524176, + 524178, + 524178, + 524178, + 524178, + 524178, + 524176, + 524176, + 524178, + 524178, + 524178, + 524177, + 524178, + 524176, + 524177, + 524177, + 524177, + 524176, + 524177, + 524176, + 524178, + 524176, + 524178, + 524176, + 524177, + 524176, + 524177, + 524176, + 524176, + 524177, + 524176, + 524177, + 524177, + 524176, + 524176, + 524177, + 524178, + 524176, + 524178, + 524176, + 524178, + 524178, + 524176, + 524178, + 524176, + 524178, + 524178, + 524177, + 524177, + 524176, + 524178, + 524177, + 524178, + 524176, + 524178, + 524177, + 524178, + 524177, + 524176, + 524176, + 524178, + 524176, + 524176, + 524178, + 524177, + 524176, + 524176, + 524177, + 524178, + 524177, + 524176, + 524177, + 524177, + 524178, + 524176, + 524177, + 524176, + 524176, + 524176, + 524178, + 524178, + 524178, + 524177, + 524177, + 524177, + 524177, + 524176, + 524178, + 524176, + 524176, + 524176 + ], + [], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/final_1.json b/axiom-eth/configs/tests/batch_query/final_1.json new file mode 100644 index 000000000..f7c8e6aa6 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/final_1.json @@ -0,0 +1,42 @@ +{ + "params": { + "degree": 21, + "num_advice": 28, + "num_lookup_advice": 3, + "num_fixed": 1, + "lookup_bits": 20 + }, + "break_points": [ + [ + 2097141, + 2097142, + 2097140, + 2097142, + 2097140, + 2097142, + 2097140, + 2097142, + 2097141, + 2097142, + 2097141, + 2097140, + 2097142, + 2097140, + 2097141, + 2097141, + 2097140, + 2097141, + 2097142, + 2097142, + 2097142, + 2097142, + 2097141, + 2097141, + 2097142, + 2097140, + 2097140 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/final_2.json b/axiom-eth/configs/tests/batch_query/final_2.json new file mode 100644 index 000000000..017f94d2e --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/final_2.json @@ -0,0 +1,18 @@ +{ + "params": { + "degree": 22, + "num_advice": 4, + "num_lookup_advice": 1, + "num_fixed": 1, + "lookup_bits": 21 + }, + "break_points": [ + [ + 4194294, + 4194293, + 4194294 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/mainnet_block_5.json b/axiom-eth/configs/tests/batch_query/mainnet_block_5.json new file mode 100644 index 000000000..c7f2152ab --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/mainnet_block_5.json @@ -0,0 +1,47 @@ +{ + "params": { + "degree": 19, + "num_rlc_columns": 1, + "num_range_advice": [ + 17, + 2, + 0 + ], + "num_lookup_advice": [ + 1, + 1, + 0 + ], + "num_fixed": 1, + "unusable_rows": 63, + "keccak_rows_per_round": 17, + "lookup_bits": 8 + }, + "break_points": { + "gate": [ + [ + 524212, + 524212, + 524211, + 524211, + 524211, + 524210, + 524212, + 524212, + 524210, + 524210, + 524212, + 524212, + 524210, + 524210, + 524212, + 524210 + ], + [ + 524212 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/mainnet_block_5_1.json b/axiom-eth/configs/tests/batch_query/mainnet_block_5_1.json new file mode 100644 index 000000000..0286bb360 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/mainnet_block_5_1.json @@ -0,0 +1,47 @@ +{ + "params": { + "degree": 21, + "num_advice": 33, + "num_lookup_advice": 4, + "num_fixed": 1, + "lookup_bits": 20 + }, + "break_points": [ + [ + 2097141, + 2097140, + 2097142, + 2097141, + 2097142, + 2097142, + 2097140, + 2097140, + 2097141, + 2097140, + 2097142, + 2097142, + 2097142, + 2097142, + 2097142, + 2097142, + 2097141, + 2097141, + 2097140, + 2097142, + 2097142, + 2097142, + 2097141, + 2097140, + 2097142, + 2097142, + 2097142, + 2097142, + 2097142, + 2097140, + 2097140, + 2097141 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/mainnet_block_6.json b/axiom-eth/configs/tests/batch_query/mainnet_block_6.json new file mode 100644 index 000000000..92af3e1d0 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/mainnet_block_6.json @@ -0,0 +1,47 @@ +{ + "params": { + "degree": 20, + "num_rlc_columns": 1, + "num_range_advice": [ + 17, + 2, + 0 + ], + "num_lookup_advice": [ + 1, + 1, + 0 + ], + "num_fixed": 1, + "unusable_rows": 63, + "keccak_rows_per_round": 17, + "lookup_bits": 8 + }, + "break_points": { + "gate": [ + [ + 1048499, + 1048499, + 1048498, + 1048498, + 1048498, + 1048498, + 1048500, + 1048498, + 1048500, + 1048498, + 1048499, + 1048499, + 1048499, + 1048500, + 1048500, + 1048500 + ], + [ + 1048500 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/row_6.json b/axiom-eth/configs/tests/batch_query/row_6.json new file mode 100644 index 000000000..a6a2aac14 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/row_6.json @@ -0,0 +1,16 @@ +{ + "params": { + "degree": 22, + "num_advice": 2, + "num_lookup_advice": 0, + "num_fixed": 1, + "lookup_bits": 0 + }, + "break_points": [ + [ + 4194294 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/schema.account.json b/axiom-eth/configs/tests/batch_query/schema.account.json new file mode 100644 index 000000000..d29666a52 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/schema.account.json @@ -0,0 +1,5 @@ +{ + "start_arity": 4, + "total_arity": 7, + "level": 0 +} diff --git a/axiom-eth/configs/tests/batch_query/schema.block.json b/axiom-eth/configs/tests/batch_query/schema.block.json new file mode 100644 index 000000000..b69c2e728 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/schema.block.json @@ -0,0 +1,4 @@ +{ + "network": "Mainnet", + "arities": [5,1] +} diff --git a/axiom-eth/configs/tests/batch_query/schema.final.json b/axiom-eth/configs/tests/batch_query/schema.final.json new file mode 100644 index 000000000..c9aa4dbf7 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/schema.final.json @@ -0,0 +1,20 @@ +{ + "round": 2, + "network": "Mainnet", + "block_arities": [6], + "account_schema": { + "start_arity": 3, + "total_arity": 6, + "level": 0 + }, + "storage_schema": { + "start_arity": 3, + "total_arity": 6, + "level": 0 + }, + "row_schema": { + "start_arity": 6, + "total_arity": 6, + "level": 0 + } +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/schema.row.json b/axiom-eth/configs/tests/batch_query/schema.row.json new file mode 100644 index 000000000..e8ce97cda --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/schema.row.json @@ -0,0 +1,4 @@ +{ + "arities": [7], + "merklelize": [true] +} diff --git a/axiom-eth/configs/tests/batch_query/schema.storage.json b/axiom-eth/configs/tests/batch_query/schema.storage.json new file mode 100644 index 000000000..d29666a52 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/schema.storage.json @@ -0,0 +1,5 @@ +{ + "start_arity": 4, + "total_arity": 7, + "level": 0 +} diff --git a/axiom-eth/configs/tests/batch_query/storage_3.json b/axiom-eth/configs/tests/batch_query/storage_3.json new file mode 100644 index 000000000..dd2ce077f --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/storage_3.json @@ -0,0 +1,45 @@ +{ + "params": { + "degree": 19, + "num_rlc_columns": 1, + "num_range_advice": [ + 11, + 6, + 0 + ], + "num_lookup_advice": [ + 1, + 1, + 0 + ], + "num_fixed": 1, + "unusable_rows": 63, + "keccak_rows_per_round": 38, + "lookup_bits": 8 + }, + "break_points": { + "gate": [ + [ + 524178, + 524178, + 524178, + 524176, + 524178, + 524178, + 524176, + 524178, + 524176, + 524176 + ], + [ + 524178, + 524178, + 524176, + 524178, + 524178 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/storage_3_6_0.json b/axiom-eth/configs/tests/batch_query/storage_3_6_0.json new file mode 100644 index 000000000..c53ca8408 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/storage_3_6_0.json @@ -0,0 +1,22 @@ +{ + "params": { + "degree": 22, + "num_advice": 8, + "num_lookup_advice": 1, + "num_fixed": 1, + "lookup_bits": 21 + }, + "break_points": [ + [ + 4194294, + 4194294, + 4194294, + 4194292, + 4194292, + 4194293, + 4194293 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/storage_3_6_1.json b/axiom-eth/configs/tests/batch_query/storage_3_6_1.json new file mode 100644 index 000000000..79b22b9a6 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/storage_3_6_1.json @@ -0,0 +1,18 @@ +{ + "params": { + "degree": 23, + "num_advice": 4, + "num_lookup_advice": 1, + "num_fixed": 1, + "lookup_bits": 22 + }, + "break_points": [ + [ + 8388596, + 8388596, + 8388597 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/storage_3_6_2.json b/axiom-eth/configs/tests/batch_query/storage_3_6_2.json new file mode 100644 index 000000000..8973f290e --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/storage_3_6_2.json @@ -0,0 +1,21 @@ +{ + "params": { + "degree": 23, + "num_advice": 7, + "num_lookup_advice": 1, + "num_fixed": 1, + "lookup_bits": 22 + }, + "break_points": [ + [ + 8388596, + 8388597, + 8388596, + 8388598, + 8388598, + 8388597 + ], + [], + [] + ] +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/storage_4.json b/axiom-eth/configs/tests/batch_query/storage_4.json new file mode 100644 index 000000000..62adeddf9 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/storage_4.json @@ -0,0 +1,45 @@ +{ + "params": { + "degree": 20, + "num_rlc_columns": 1, + "num_range_advice": [ + 11, + 6, + 0 + ], + "num_lookup_advice": [ + 1, + 1, + 0 + ], + "num_fixed": 1, + "unusable_rows": 63, + "keccak_rows_per_round": 38, + "lookup_bits": 8 + }, + "break_points": { + "gate": [ + [ + 1048466, + 1048464, + 1048466, + 1048464, + 1048464, + 1048466, + 1048465, + 1048465, + 1048464, + 1048464 + ], + [ + 1048466, + 1048464, + 1048466, + 1048466, + 1048464 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/batch_query/storage_5.json b/axiom-eth/configs/tests/batch_query/storage_5.json new file mode 100644 index 000000000..c1bedfca2 --- /dev/null +++ b/axiom-eth/configs/tests/batch_query/storage_5.json @@ -0,0 +1,45 @@ +{ + "params": { + "degree": 21, + "num_rlc_columns": 1, + "num_range_advice": [ + 11, + 6, + 0 + ], + "num_lookup_advice": [ + 1, + 1, + 0 + ], + "num_fixed": 1, + "unusable_rows": 63, + "keccak_rows_per_round": 38, + "lookup_bits": 8 + }, + "break_points": { + "gate": [ + [ + 2097042, + 2097042, + 2097040, + 2097042, + 2097042, + 2097041, + 2097040, + 2097040, + 2097042, + 2097040 + ], + [ + 2097042, + 2097040, + 2097042, + 2097042, + 2097041 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-eth/configs/tests/block_query.json b/axiom-eth/configs/tests/block_query.json new file mode 100644 index 000000000..e19e5a1bd --- /dev/null +++ b/axiom-eth/configs/tests/block_query.json @@ -0,0 +1,9 @@ +{ + "degree": 19, + "num_rlc_columns": 3, + "num_range_advice": [40, 23], + "num_lookup_advice": [1, 1], + "num_fixed": 1, + "unusable_rows": 69, + "keccak_rows_per_round": 10 +} diff --git a/configs/tests/mpt.json b/axiom-eth/configs/tests/mpt.json similarity index 100% rename from configs/tests/mpt.json rename to axiom-eth/configs/tests/mpt.json diff --git a/axiom-eth/configs/tests/multi_block.json b/axiom-eth/configs/tests/multi_block.json new file mode 100644 index 000000000..2144bace6 --- /dev/null +++ b/axiom-eth/configs/tests/multi_block.json @@ -0,0 +1 @@ +{"params":{"degree":14,"num_rlc_columns":3,"num_range_advice":[46,16,0],"num_lookup_advice":[1,1,0],"num_fixed":1,"unusable_rows":61,"keccak_rows_per_round":12,"lookup_bits":8},"break_points":{"gate":[[16322,16321,16320,16322,16320,16322,16322,16321,16320,16322,16320,16322,16322,16322,16322,16320,16322,16321,16321,16322,16322,16321,16321,16322,16322,16322,16320,16320,16321,16322,16320,16322,16322,16320,16320,16322,16322,16322,16321,16320,16322,16322,16322,16321,16322],[16322,16320,16322,16320,16320,16320,16322,16321,16322,16322,16322,16322,16321,16321,16320],[]],"rlc":[16322,16322]}} \ No newline at end of file diff --git a/axiom-eth/configs/tests/one_block.json b/axiom-eth/configs/tests/one_block.json new file mode 100644 index 000000000..6de53cd7b --- /dev/null +++ b/axiom-eth/configs/tests/one_block.json @@ -0,0 +1,23 @@ +{ + "params": { + "degree": 12, + "num_rlc_columns": 1, + "num_range_advice": [20, 8, 0], + "num_lookup_advice": [1, 1, 0], + "num_fixed": 1, + "unusable_rows": 109, + "keccak_rows_per_round": 27, + "lookup_bits": 8 + }, + "break_points": { + "gate": [ + [ + 4003, 4002, 4004, 4003, 4002, 4004, 4003, 4003, 4003, 4003, 4002, 4004, + 4004, 4003, 4004, 4002, 4004, 4004, 4004 + ], + [4004, 4004, 4004, 4004, 4003, 4004], + [] + ], + "rlc": [] + } +} diff --git a/configs/tests/storage.json b/axiom-eth/configs/tests/storage.json similarity index 91% rename from configs/tests/storage.json rename to axiom-eth/configs/tests/storage.json index b39a488e5..d7ad86cb8 100644 --- a/configs/tests/storage.json +++ b/axiom-eth/configs/tests/storage.json @@ -1,5 +1,5 @@ { - "degree": 15, + "degree": 18, "num_rlc_columns": 3, "num_range_advice": [40, 23], "num_lookup_advice": [1, 1], diff --git a/configs/tests/storage_evm.json b/axiom-eth/configs/tests/storage_evm.json similarity index 100% rename from configs/tests/storage_evm.json rename to axiom-eth/configs/tests/storage_evm.json diff --git a/axiom-eth/configs/tests/storage_query.json b/axiom-eth/configs/tests/storage_query.json new file mode 100644 index 000000000..d7ad86cb8 --- /dev/null +++ b/axiom-eth/configs/tests/storage_query.json @@ -0,0 +1,9 @@ +{ + "degree": 18, + "num_rlc_columns": 3, + "num_range_advice": [40, 23], + "num_lookup_advice": [1, 1], + "num_fixed": 1, + "unusable_rows": 69, + "keccak_rows_per_round": 10 +} diff --git a/data/headers/default_blocks_goerli.json b/axiom-eth/data/headers/default_blocks_goerli.json similarity index 100% rename from data/headers/default_blocks_goerli.json rename to axiom-eth/data/headers/default_blocks_goerli.json diff --git a/data/headers/default_hashes_goerli.json b/axiom-eth/data/headers/default_hashes_goerli.json similarity index 100% rename from data/headers/default_hashes_goerli.json rename to axiom-eth/data/headers/default_hashes_goerli.json diff --git a/data/headers/mainnet_10_7_for_evm_1.yul b/axiom-eth/data/headers/mainnet_10_7_for_evm_1.yul similarity index 97% rename from data/headers/mainnet_10_7_for_evm_1.yul rename to axiom-eth/data/headers/mainnet_10_7_for_evm_1.yul index 81a3bf79f..32802ed25 100644 --- a/data/headers/mainnet_10_7_for_evm_1.yul +++ b/axiom-eth/data/headers/mainnet_10_7_for_evm_1.yul @@ -70,7 +70,7 @@ mstore(0x480, mod(calldataload(0x460), f_q)) mstore(0x4a0, mod(calldataload(0x480), f_q)) mstore(0x4c0, mod(calldataload(0x4a0), f_q)) mstore(0x4e0, mod(calldataload(0x4c0), f_q)) -mstore(0x0, 9318401369509048103473335146630514059113343267469070248308933105380762293116) +mstore(0x0, 8412653266354028249917373514688863030371584715513288348683675775000288975891) { let x := calldataload(0x4e0) @@ -1321,8 +1321,8 @@ mstore(0x4de0, mload(0x4d00)) mstore(0x4e20, mload(0x4d80)) mstore(0x4e40, mload(0x4da0)) success := and(eq(staticcall(gas(), 0x6, 0x4de0, 0x80, 0x4de0, 0x40), 1), success) -mstore(0x4e60, 0x1ef6c7c3d21360d073c5576ef014b06a0c3076bb12074e4051af2d6d163e0ad2) - mstore(0x4e80, 0x080acba1450a16e51f0c5221d0a074c045eceecf114d49cafb8c83bc2ec8dad4) +mstore(0x4e60, 0x1d13ede7ff4c42ae75f3e6d92da2827cbef2d3aef89ed2ccc9a4f765f6360188) + mstore(0x4e80, 0x030f7e60f598b79db0bae27268cbd88a7ae4df973c35453b5a6ccfdad9ff11ff) mstore(0x4ea0, mload(0x4820)) success := and(eq(staticcall(gas(), 0x7, 0x4e60, 0x60, 0x4e60, 0x40), 1), success) mstore(0x4ec0, mload(0x4de0)) @@ -1330,8 +1330,8 @@ mstore(0x4ec0, mload(0x4de0)) mstore(0x4f00, mload(0x4e60)) mstore(0x4f20, mload(0x4e80)) success := and(eq(staticcall(gas(), 0x6, 0x4ec0, 0x80, 0x4ec0, 0x40), 1), success) -mstore(0x4f40, 0x04749c650b669e569915d421c5b6894ab2bc7e94a566f4f1ce469edfeda54484) - mstore(0x4f60, 0x158eef5710be9af3a9575efa76619f1e76485682f07b75ee364046f276e3c093) +mstore(0x4f40, 0x14f9967686882b298d055eecfbeadcdaccdc11bb403d2595d9e009ea2fd4914b) + mstore(0x4f60, 0x0f951ee95117ad514363247e2d918cbe010dc8a088ed831da215dd1667b233b4) mstore(0x4f80, mload(0x4840)) success := and(eq(staticcall(gas(), 0x7, 0x4f40, 0x60, 0x4f40, 0x40), 1), success) mstore(0x4fa0, mload(0x4ec0)) @@ -1339,8 +1339,8 @@ mstore(0x4fa0, mload(0x4ec0)) mstore(0x4fe0, mload(0x4f40)) mstore(0x5000, mload(0x4f60)) success := and(eq(staticcall(gas(), 0x6, 0x4fa0, 0x80, 0x4fa0, 0x40), 1), success) -mstore(0x5020, 0x051ec50af23618d6667c98a3bed2d00f0379b114094ff8d73415e158f572150c) - mstore(0x5040, 0x120ed49de1bfc0526035921e76bc543e5b08d4254f3a0416b89bc2b4c4a7ef69) +mstore(0x5020, 0x24511bb528e2e9ac50ab0ed0af2698996c42f025757a6c6725cd8678bc986afc) + mstore(0x5040, 0x0bbb921d6dd604b30b9ad2b3a21a9ff666f0f31ce0397280b36c8b3e7fcb69e6) mstore(0x5060, mload(0x4860)) success := and(eq(staticcall(gas(), 0x7, 0x5020, 0x60, 0x5020, 0x40), 1), success) mstore(0x5080, mload(0x4fa0)) @@ -1348,8 +1348,8 @@ mstore(0x5080, mload(0x4fa0)) mstore(0x50c0, mload(0x5020)) mstore(0x50e0, mload(0x5040)) success := and(eq(staticcall(gas(), 0x6, 0x5080, 0x80, 0x5080, 0x40), 1), success) -mstore(0x5100, 0x1c13ab24f967c0018d745f637e5086bad8904dd3df5c6458ea8b29055201faf2) - mstore(0x5120, 0x2448c0fbcbe5c7bd6a0093720f90abd5d721f6ebd92833c095d905abdebab089) +mstore(0x5100, 0x2af66054cae62f0c8f9fccd1dc80e91e29e05c42638a0064ccd23ddb907b41c1) + mstore(0x5120, 0x00a744ccce7ca0fa72198c504cb9135bdc4c4644dcef20be2a4fcc8da824f90b) mstore(0x5140, mload(0x4880)) success := and(eq(staticcall(gas(), 0x7, 0x5100, 0x60, 0x5100, 0x40), 1), success) mstore(0x5160, mload(0x5080)) @@ -1357,8 +1357,8 @@ mstore(0x5160, mload(0x5080)) mstore(0x51a0, mload(0x5100)) mstore(0x51c0, mload(0x5120)) success := and(eq(staticcall(gas(), 0x6, 0x5160, 0x80, 0x5160, 0x40), 1), success) -mstore(0x51e0, 0x03681c75579fdd8737c9b7e60b343b29768c13503d897b86808af74760831c89) - mstore(0x5200, 0x256e78420fe6bc2823b6369ab4f93bc6b97ce216ab74f04d8d06385c959e1d42) +mstore(0x51e0, 0x2e852506bb76850bd60c8e13c186f78e105123f87c3a80ed0b5566c56e310a17) + mstore(0x5200, 0x2607ef20e29fab6b8703414cde488052a2cefdd2e4d81f0b40105f1e703e699a) mstore(0x5220, mload(0x48a0)) success := and(eq(staticcall(gas(), 0x7, 0x51e0, 0x60, 0x51e0, 0x40), 1), success) mstore(0x5240, mload(0x5160)) @@ -1366,8 +1366,8 @@ mstore(0x5240, mload(0x5160)) mstore(0x5280, mload(0x51e0)) mstore(0x52a0, mload(0x5200)) success := and(eq(staticcall(gas(), 0x6, 0x5240, 0x80, 0x5240, 0x40), 1), success) -mstore(0x52c0, 0x2487698e362f6698aa1dcd05b9a8ec91171e0790764ab9317bad922c64024c1e) - mstore(0x52e0, 0x1b172df74ad642bcea681e3bbbc3f3dda20e8e127afc3f784e656fe30c321822) +mstore(0x52c0, 0x059381fafcf28a916d81002477897dfd1772363fadcb0143f9151930297bac65) + mstore(0x52e0, 0x03b347be31d37236965be868ba48852c029e5124a774d2483df72990f54b3cbc) mstore(0x5300, mload(0x48c0)) success := and(eq(staticcall(gas(), 0x7, 0x52c0, 0x60, 0x52c0, 0x40), 1), success) mstore(0x5320, mload(0x5240)) @@ -1375,8 +1375,8 @@ mstore(0x5320, mload(0x5240)) mstore(0x5360, mload(0x52c0)) mstore(0x5380, mload(0x52e0)) success := and(eq(staticcall(gas(), 0x6, 0x5320, 0x80, 0x5320, 0x40), 1), success) -mstore(0x53a0, 0x190a894dc7a3749d6dcaea4e3e80dd313c0fb0d8ea7364e5c6933e28a4b768aa) - mstore(0x53c0, 0x246bddc8b26f64e960add8925d0991de76b4eba13cec604683390e3a594e2836) +mstore(0x53a0, 0x21200ab05a3645e0025a5971ae43c96947bbaa2fa02cbe3ce17d095904bd98c4) + mstore(0x53c0, 0x2fe6f17111569c4137890e976237d5edb8e060b52a29566a0218ebf048655bb1) mstore(0x53e0, mload(0x48e0)) success := and(eq(staticcall(gas(), 0x7, 0x53a0, 0x60, 0x53a0, 0x40), 1), success) mstore(0x5400, mload(0x5320)) @@ -1485,10 +1485,10 @@ mstore(0x5e20, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c mstore(0x5e80, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) mstore(0x5ea0, mload(0x5d60)) mstore(0x5ec0, mload(0x5d80)) -mstore(0x5ee0, 0x186282957db913abd99f91db59fe69922e95040603ef44c0bd7aa3adeef8f5ac) - mstore(0x5f00, 0x17944351223333f260ddc3b4af45191b856689eda9eab5cbcddbbe570ce860d2) - mstore(0x5f20, 0x06d971ff4a7467c3ec596ed6efc674572e32fd6f52b721f97e35b0b3d3546753) - mstore(0x5f40, 0x06ecdb9f9567f59ed2eee36e1e1d58797fd13cc97fafc2910f5e8a12f202fa9a) +mstore(0x5ee0, 0x138d5863615c12d3bd7d3fd007776d281a337f9d7f6dce23532100bb4bb5839d) + mstore(0x5f00, 0x0a3bb881671ee4e9238366e87f6598f0de356372ed3dc870766ec8ac005211e4) + mstore(0x5f20, 0x19c9d7d9c6e7ad2d9a0d5847ebdd2687c668939a202553ded2760d3eb8dbf559) + mstore(0x5f40, 0x198adb441818c42721c88c532ed13a5da1ebb78b85574d0b7326d8e6f4c1e25a) success := and(eq(staticcall(gas(), 0x8, 0x5de0, 0x180, 0x5de0, 0x20), 1), success) success := and(eq(mload(0x5de0), 1), success) diff --git a/axiom-eth/data/headers/mainnet_17_7_for_evm_1.yul b/axiom-eth/data/headers/mainnet_17_7_for_evm_1.yul new file mode 100644 index 000000000..ddd6ceb55 --- /dev/null +++ b/axiom-eth/data/headers/mainnet_17_7_for_evm_1.yul @@ -0,0 +1,1668 @@ + + object "plonk_verifier" { + code { + function allocate(size) -> ptr { + ptr := mload(0x40) + if eq(ptr, 0) { ptr := 0x60 } + mstore(0x40, add(ptr, size)) + } + 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 := 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 + let f_q := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 + function validate_ec_point(x, y) -> valid:bool { + { + let x_lt_p:bool := lt(x, 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + let y_lt_p:bool := 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:bool := 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(0x1a0, mod(calldataload(0x180), f_q)) +mstore(0x1c0, mod(calldataload(0x1a0), f_q)) +mstore(0x1e0, mod(calldataload(0x1c0), f_q)) +mstore(0x200, mod(calldataload(0x1e0), f_q)) +mstore(0x220, mod(calldataload(0x200), f_q)) +mstore(0x240, mod(calldataload(0x220), f_q)) +mstore(0x260, mod(calldataload(0x240), f_q)) +mstore(0x280, mod(calldataload(0x260), f_q)) +mstore(0x2a0, mod(calldataload(0x280), f_q)) +mstore(0x2c0, mod(calldataload(0x2a0), f_q)) +mstore(0x2e0, mod(calldataload(0x2c0), f_q)) +mstore(0x300, mod(calldataload(0x2e0), f_q)) +mstore(0x320, mod(calldataload(0x300), f_q)) +mstore(0x340, mod(calldataload(0x320), f_q)) +mstore(0x360, mod(calldataload(0x340), f_q)) +mstore(0x380, mod(calldataload(0x360), f_q)) +mstore(0x3a0, mod(calldataload(0x380), f_q)) +mstore(0x3c0, mod(calldataload(0x3a0), f_q)) +mstore(0x3e0, mod(calldataload(0x3c0), f_q)) +mstore(0x400, mod(calldataload(0x3e0), f_q)) +mstore(0x420, mod(calldataload(0x400), f_q)) +mstore(0x440, mod(calldataload(0x420), f_q)) +mstore(0x460, mod(calldataload(0x440), f_q)) +mstore(0x480, mod(calldataload(0x460), f_q)) +mstore(0x4a0, mod(calldataload(0x480), f_q)) +mstore(0x4c0, mod(calldataload(0x4a0), f_q)) +mstore(0x4e0, mod(calldataload(0x4c0), f_q)) +mstore(0x500, mod(calldataload(0x4e0), f_q)) +mstore(0x520, mod(calldataload(0x500), f_q)) +mstore(0x540, mod(calldataload(0x520), f_q)) +mstore(0x560, mod(calldataload(0x540), f_q)) +mstore(0x580, mod(calldataload(0x560), f_q)) +mstore(0x5a0, mod(calldataload(0x580), f_q)) +mstore(0x5c0, mod(calldataload(0x5a0), f_q)) +mstore(0x5e0, mod(calldataload(0x5c0), f_q)) +mstore(0x600, mod(calldataload(0x5e0), f_q)) +mstore(0x620, mod(calldataload(0x600), f_q)) +mstore(0x640, mod(calldataload(0x620), f_q)) +mstore(0x660, mod(calldataload(0x640), f_q)) +mstore(0x680, mod(calldataload(0x660), f_q)) +mstore(0x6a0, mod(calldataload(0x680), f_q)) +mstore(0x0, 20726407482471391512090360235977739314152844021314353719148771804318778614389) + + { + let x := calldataload(0x6a0) + mstore(0x6c0, x) + let y := calldataload(0x6c0) + mstore(0x6e0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x700, keccak256(0x0, 1792)) +{ + let hash := mload(0x700) + mstore(0x720, mod(hash, f_q)) + mstore(0x740, hash) + } + + { + let x := calldataload(0x6e0) + mstore(0x760, x) + let y := calldataload(0x700) + mstore(0x780, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x720) + mstore(0x7a0, x) + let y := calldataload(0x740) + mstore(0x7c0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x7e0, keccak256(0x740, 160)) +{ + let hash := mload(0x7e0) + mstore(0x800, mod(hash, f_q)) + mstore(0x820, hash) + } +mstore8(2112, 1) +mstore(0x840, keccak256(0x820, 33)) +{ + let hash := mload(0x840) + mstore(0x860, mod(hash, f_q)) + mstore(0x880, hash) + } + + { + let x := calldataload(0x760) + mstore(0x8a0, x) + let y := calldataload(0x780) + mstore(0x8c0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x7a0) + mstore(0x8e0, x) + let y := calldataload(0x7c0) + mstore(0x900, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x7e0) + mstore(0x920, x) + let y := calldataload(0x800) + mstore(0x940, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x960, keccak256(0x880, 224)) +{ + let hash := mload(0x960) + mstore(0x980, mod(hash, f_q)) + mstore(0x9a0, hash) + } + + { + let x := calldataload(0x820) + mstore(0x9c0, x) + let y := calldataload(0x840) + mstore(0x9e0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x860) + mstore(0xa00, x) + let y := calldataload(0x880) + mstore(0xa20, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x8a0) + mstore(0xa40, x) + let y := calldataload(0x8c0) + mstore(0xa60, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x8e0) + mstore(0xa80, x) + let y := calldataload(0x900) + mstore(0xaa0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0xac0, keccak256(0x9a0, 288)) +{ + let hash := mload(0xac0) + mstore(0xae0, mod(hash, f_q)) + mstore(0xb00, hash) + } +mstore(0xb20, mod(calldataload(0x920), f_q)) +mstore(0xb40, mod(calldataload(0x940), f_q)) +mstore(0xb60, mod(calldataload(0x960), f_q)) +mstore(0xb80, mod(calldataload(0x980), f_q)) +mstore(0xba0, mod(calldataload(0x9a0), f_q)) +mstore(0xbc0, mod(calldataload(0x9c0), f_q)) +mstore(0xbe0, mod(calldataload(0x9e0), f_q)) +mstore(0xc00, mod(calldataload(0xa00), f_q)) +mstore(0xc20, mod(calldataload(0xa20), f_q)) +mstore(0xc40, mod(calldataload(0xa40), f_q)) +mstore(0xc60, mod(calldataload(0xa60), f_q)) +mstore(0xc80, mod(calldataload(0xa80), f_q)) +mstore(0xca0, mod(calldataload(0xaa0), f_q)) +mstore(0xcc0, mod(calldataload(0xac0), f_q)) +mstore(0xce0, mod(calldataload(0xae0), f_q)) +mstore(0xd00, mod(calldataload(0xb00), f_q)) +mstore(0xd20, mod(calldataload(0xb20), f_q)) +mstore(0xd40, mod(calldataload(0xb40), f_q)) +mstore(0xd60, mod(calldataload(0xb60), f_q)) +mstore(0xd80, keccak256(0xb00, 640)) +{ + let hash := mload(0xd80) + mstore(0xda0, mod(hash, f_q)) + mstore(0xdc0, hash) + } +mstore8(3552, 1) +mstore(0xde0, keccak256(0xdc0, 33)) +{ + let hash := mload(0xde0) + mstore(0xe00, mod(hash, f_q)) + mstore(0xe20, hash) + } + + { + let x := calldataload(0xb80) + mstore(0xe40, x) + let y := calldataload(0xba0) + mstore(0xe60, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0xe80, keccak256(0xe20, 96)) +{ + let hash := mload(0xe80) + mstore(0xea0, mod(hash, f_q)) + mstore(0xec0, hash) + } + + { + let x := calldataload(0xbc0) + mstore(0xee0, x) + let y := calldataload(0xbe0) + mstore(0xf00, 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(3872, x) +let y := mload(0x80) +y := add(y, shl(88, mload(0xa0))) +y := add(y, shl(176, mload(0xc0))) +mstore(3904, 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(3936, x) +let y := mload(0x140) +y := add(y, shl(88, mload(0x160))) +y := add(y, shl(176, mload(0x180))) +mstore(3968, y) + + success := and(validate_ec_point(x, y), success) + } +mstore(0xfa0, mulmod(mload(0xae0), mload(0xae0), f_q)) +mstore(0xfc0, mulmod(mload(0xfa0), mload(0xfa0), f_q)) +mstore(0xfe0, mulmod(mload(0xfc0), mload(0xfc0), f_q)) +mstore(0x1000, mulmod(mload(0xfe0), mload(0xfe0), f_q)) +mstore(0x1020, mulmod(mload(0x1000), mload(0x1000), f_q)) +mstore(0x1040, mulmod(mload(0x1020), mload(0x1020), f_q)) +mstore(0x1060, mulmod(mload(0x1040), mload(0x1040), f_q)) +mstore(0x1080, mulmod(mload(0x1060), mload(0x1060), f_q)) +mstore(0x10a0, mulmod(mload(0x1080), mload(0x1080), f_q)) +mstore(0x10c0, mulmod(mload(0x10a0), mload(0x10a0), f_q)) +mstore(0x10e0, mulmod(mload(0x10c0), mload(0x10c0), f_q)) +mstore(0x1100, mulmod(mload(0x10e0), mload(0x10e0), f_q)) +mstore(0x1120, mulmod(mload(0x1100), mload(0x1100), f_q)) +mstore(0x1140, mulmod(mload(0x1120), mload(0x1120), f_q)) +mstore(0x1160, mulmod(mload(0x1140), mload(0x1140), f_q)) +mstore(0x1180, mulmod(mload(0x1160), mload(0x1160), f_q)) +mstore(0x11a0, mulmod(mload(0x1180), mload(0x1180), f_q)) +mstore(0x11c0, mulmod(mload(0x11a0), mload(0x11a0), f_q)) +mstore(0x11e0, mulmod(mload(0x11c0), mload(0x11c0), f_q)) +mstore(0x1200, mulmod(mload(0x11e0), mload(0x11e0), f_q)) +mstore(0x1220, mulmod(mload(0x1200), mload(0x1200), f_q)) +mstore(0x1240, mulmod(mload(0x1220), mload(0x1220), f_q)) +mstore(0x1260, mulmod(mload(0x1240), mload(0x1240), f_q)) +mstore(0x1280, addmod(mload(0x1260), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q)) +mstore(0x12a0, mulmod(mload(0x1280), 21888240262557392955334514970720457388010314637169927192662615958087340972065, f_q)) +mstore(0x12c0, mulmod(mload(0x12a0), 4506835738822104338668100540817374747935106310012997856968187171738630203507, f_q)) +mstore(0x12e0, addmod(mload(0xae0), 17381407133017170883578305204439900340613258090403036486730017014837178292110, f_q)) +mstore(0x1300, mulmod(mload(0x12a0), 21710372849001950800533397158415938114909991150039389063546734567764856596059, f_q)) +mstore(0x1320, addmod(mload(0xae0), 177870022837324421713008586841336973638373250376645280151469618810951899558, f_q)) +mstore(0x1340, mulmod(mload(0x12a0), 1887003188133998471169152042388914354640772748308168868301418279904560637395, f_q)) +mstore(0x1360, addmod(mload(0xae0), 20001239683705276751077253702868360733907591652107865475396785906671247858222, f_q)) +mstore(0x1380, mulmod(mload(0x12a0), 2785514556381676080176937710880804108647911392478702105860685610379369825016, f_q)) +mstore(0x13a0, addmod(mload(0xae0), 19102728315457599142069468034376470979900453007937332237837518576196438670601, f_q)) +mstore(0x13c0, mulmod(mload(0x12a0), 14655294445420895451632927078981340937842238432098198055057679026789553137428, f_q)) +mstore(0x13e0, addmod(mload(0xae0), 7232948426418379770613478666275934150706125968317836288640525159786255358189, f_q)) +mstore(0x1400, mulmod(mload(0x12a0), 8734126352828345679573237859165904705806588461301144420590422589042130041188, f_q)) +mstore(0x1420, addmod(mload(0xae0), 13154116519010929542673167886091370382741775939114889923107781597533678454429, f_q)) +mstore(0x1440, mulmod(mload(0x12a0), 9741553891420464328295280489650144566903017206473301385034033384879943874347, f_q)) +mstore(0x1460, addmod(mload(0xae0), 12146688980418810893951125255607130521645347193942732958664170801695864621270, f_q)) +mstore(0x1480, mulmod(mload(0x12a0), 1, f_q)) +mstore(0x14a0, addmod(mload(0xae0), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q)) +mstore(0x14c0, mulmod(mload(0x12a0), 8374374965308410102411073611984011876711565317741801500439755773472076597347, f_q)) +mstore(0x14e0, addmod(mload(0xae0), 13513867906530865119835332133273263211836799082674232843258448413103731898270, f_q)) +mstore(0x1500, mulmod(mload(0x12a0), 11211301017135681023579411905410872569206244553457844956874280139879520583390, f_q)) +mstore(0x1520, addmod(mload(0xae0), 10676941854703594198666993839846402519342119846958189386823924046696287912227, f_q)) +mstore(0x1540, mulmod(mload(0x12a0), 3615478808282855240548287271348143516886772452944084747768312988864436725401, f_q)) +mstore(0x1560, addmod(mload(0xae0), 18272764063556419981698118473909131571661591947471949595929891197711371770216, f_q)) +mstore(0x1580, mulmod(mload(0x12a0), 1426404432721484388505361748317961535523355871255605456897797744433766488507, f_q)) +mstore(0x15a0, addmod(mload(0xae0), 20461838439117790833741043996939313553025008529160428886800406442142042007110, f_q)) +mstore(0x15c0, mulmod(mload(0x12a0), 216092043779272773661818549620449970334216366264741118684015851799902419467, f_q)) +mstore(0x15e0, addmod(mload(0xae0), 21672150828060002448584587195636825118214148034151293225014188334775906076150, f_q)) +mstore(0x1600, mulmod(mload(0x12a0), 12619617507853212586156872920672483948819476989779550311307282715684870266992, f_q)) +mstore(0x1620, addmod(mload(0xae0), 9268625363986062636089532824584791139728887410636484032390921470890938228625, f_q)) +mstore(0x1640, mulmod(mload(0x12a0), 18610195890048912503953886742825279624920778288956610528523679659246523534888, f_q)) +mstore(0x1660, addmod(mload(0xae0), 3278046981790362718292519002431995463627586111459423815174524527329284960729, f_q)) +mstore(0x1680, mulmod(mload(0x12a0), 19032961837237948602743626455740240236231119053033140765040043513661803148152, f_q)) +mstore(0x16a0, addmod(mload(0xae0), 2855281034601326619502779289517034852317245347382893578658160672914005347465, f_q)) +mstore(0x16c0, mulmod(mload(0x12a0), 14875928112196239563830800280253496262679717528621719058794366823499719730250, f_q)) +mstore(0x16e0, addmod(mload(0xae0), 7012314759643035658415605465003778825868646871794315284903837363076088765367, f_q)) +mstore(0x1700, mulmod(mload(0x12a0), 915149353520972163646494413843788069594022902357002628455555785223409501882, f_q)) +mstore(0x1720, addmod(mload(0xae0), 20973093518318303058599911331413487018954341498059031715242648401352398993735, f_q)) +mstore(0x1740, mulmod(mload(0x12a0), 5522161504810533295870699551020523636289972223872138525048055197429246400245, f_q)) +mstore(0x1760, addmod(mload(0xae0), 16366081367028741926375706194236751452258392176543895818650148989146562095372, f_q)) +mstore(0x1780, mulmod(mload(0x12a0), 3766081621734395783232337525162072736827576297943013392955872170138036189193, f_q)) +mstore(0x17a0, addmod(mload(0xae0), 18122161250104879439014068220095202351720788102473020950742332016437772306424, f_q)) +mstore(0x17c0, mulmod(mload(0x12a0), 9100833993744738801214480881117348002768153232283708533639316963648253510584, f_q)) +mstore(0x17e0, addmod(mload(0xae0), 12787408878094536421031924864139927085780211168132325810058887222927554985033, f_q)) +mstore(0x1800, mulmod(mload(0x12a0), 4245441013247250116003069945606352967193023389718465410501109428393342802981, f_q)) +mstore(0x1820, addmod(mload(0xae0), 17642801858592025106243335799650922121355341010697568933197094758182465692636, f_q)) +mstore(0x1840, mulmod(mload(0x12a0), 6132660129994545119218258312491950835441607143741804980633129304664017206141, f_q)) +mstore(0x1860, addmod(mload(0xae0), 15755582741844730103028147432765324253106757256674229363065074881911791289476, f_q)) +mstore(0x1880, mulmod(mload(0x12a0), 5854133144571823792863860130267644613802765696134002830362054821530146160770, f_q)) +mstore(0x18a0, addmod(mload(0xae0), 16034109727267451429382545614989630474745598704282031513336149365045662334847, f_q)) +mstore(0x18c0, mulmod(mload(0x12a0), 515148244606945972463850631189471072103916690263705052318085725998468254533, f_q)) +mstore(0x18e0, addmod(mload(0xae0), 21373094627232329249782555114067804016444447710152329291380118460577340241084, f_q)) +mstore(0x1900, mulmod(mload(0x12a0), 5980488956150442207659150513163747165544364597008566989111579977672498964212, f_q)) +mstore(0x1920, addmod(mload(0xae0), 15907753915688833014587255232093527923003999803407467354586624208903309531405, f_q)) +mstore(0x1940, mulmod(mload(0x12a0), 5223738580615264174925218065001555728265216895679471490312087802465486318994, f_q)) +mstore(0x1960, addmod(mload(0xae0), 16664504291224011047321187680255719360283147504736562853386116384110322176623, f_q)) +mstore(0x1980, mulmod(mload(0x12a0), 14557038802599140430182096396825290815503940951075961210638273254419942783582, f_q)) +mstore(0x19a0, addmod(mload(0xae0), 7331204069240134792064309348431984273044423449340073133059930932155865712035, f_q)) +mstore(0x19c0, mulmod(mload(0x12a0), 16976236069879939850923145256911338076234942200101755618884183331004076579046, f_q)) +mstore(0x19e0, addmod(mload(0xae0), 4912006801959335371323260488345937012313422200314278724814020855571731916571, f_q)) +mstore(0x1a00, mulmod(mload(0x12a0), 13553911191894110065493137367144919847521088405945523452288398666974237857208, f_q)) +mstore(0x1a20, addmod(mload(0xae0), 8334331679945165156753268378112355241027275994470510891409805519601570638409, f_q)) +mstore(0x1a40, mulmod(mload(0x12a0), 12222687719926148270818604386979005738180875192307070468454582955273533101023, f_q)) +mstore(0x1a60, addmod(mload(0xae0), 9665555151913126951427801358278269350367489208108963875243621231302275394594, f_q)) +mstore(0x1a80, mulmod(mload(0x12a0), 9697063347556872083384215826199993067635178715531258559890418744774301211662, f_q)) +mstore(0x1aa0, addmod(mload(0xae0), 12191179524282403138862189919057282020913185684884775783807785441801507283955, f_q)) +mstore(0x1ac0, mulmod(mload(0x12a0), 13783318220968413117070077848579881425001701814458176881760898225529300547844, f_q)) +mstore(0x1ae0, addmod(mload(0xae0), 8104924650870862105176327896677393663546662585957857461937305961046507947773, f_q)) +mstore(0x1b00, mulmod(mload(0x12a0), 10807735674816066981985242612061336605021639643453679977988966079770672437131, f_q)) +mstore(0x1b20, addmod(mload(0xae0), 11080507197023208240261163133195938483526724756962354365709238106805136058486, f_q)) +mstore(0x1b40, mulmod(mload(0x12a0), 15487660954688013862248478071816391715224351867581977083810729441220383572585, f_q)) +mstore(0x1b60, addmod(mload(0xae0), 6400581917151261359997927673440883373324012532834057259887474745355424923032, f_q)) +mstore(0x1b80, mulmod(mload(0x12a0), 12459868075641381822485233712013080087763946065665469821362892189399541605692, f_q)) +mstore(0x1ba0, addmod(mload(0xae0), 9428374796197893399761172033244195000784418334750564522335311997176266889925, f_q)) +mstore(0x1bc0, mulmod(mload(0x12a0), 12562571400845953139885120066983392294851269266041089223701347829190217414825, f_q)) +mstore(0x1be0, addmod(mload(0xae0), 9325671470993322082361285678273882793697095134374945119996856357385591080792, f_q)) +mstore(0x1c00, mulmod(mload(0x12a0), 16038300751658239075779628684257016433412502747804121525056508685985277092575, f_q)) +mstore(0x1c20, addmod(mload(0xae0), 5849942120181036146466777061000258655135861652611912818641695500590531403042, f_q)) +mstore(0x1c40, mulmod(mload(0x12a0), 17665522928519859765452767154433594409738037332395989540221744312194874941704, f_q)) +mstore(0x1c60, addmod(mload(0xae0), 4222719943319415456793638590823680678810327068020044803476459874380933553913, f_q)) +mstore(0x1c80, mulmod(mload(0x12a0), 6955697244493336113861667751840378876927906302623587437721024018233754910398, f_q)) +mstore(0x1ca0, addmod(mload(0xae0), 14932545627345939108384737993416896211620458097792446905977180168342053585219, f_q)) +mstore(0x1cc0, mulmod(mload(0x12a0), 1918679275621049296283934091410967415474987212511681231948800935495808101054, f_q)) +mstore(0x1ce0, addmod(mload(0xae0), 19969563596218225925962471653846307673073377187904353111749403251080000394563, f_q)) +mstore(0x1d00, mulmod(mload(0x12a0), 13498745591877810872211159461644682954739332524336278910448604883789771736885, f_q)) +mstore(0x1d20, addmod(mload(0xae0), 8389497279961464350035246283612592133809031876079755433249599302786036758732, f_q)) +mstore(0x1d40, mulmod(mload(0x12a0), 6604851689411953560355663038203889299997924520355363678860500374111951937637, f_q)) +mstore(0x1d60, addmod(mload(0xae0), 15283391182427321661890742707053385788550439880060670664837703812463856557980, f_q)) +mstore(0x1d80, mulmod(mload(0x12a0), 20345677989844117909528750049476969581182118546166966482506114734614108237981, f_q)) +mstore(0x1da0, addmod(mload(0xae0), 1542564881995157312717655695780305507366245854249067861192089451961700257636, f_q)) +mstore(0x1dc0, mulmod(mload(0x12a0), 11244009323710436498447061620026171700033960328162115124806024297270121927878, f_q)) +mstore(0x1de0, addmod(mload(0xae0), 10644233548128838723799344125231103388514404072253919218892179889305686567739, f_q)) +mstore(0x1e00, mulmod(mload(0x12a0), 790608022292213379425324383664216541739009722347092850716054055768832299157, f_q)) +mstore(0x1e20, addmod(mload(0xae0), 21097634849547061842821081361593058546809354678068941492982150130806976196460, f_q)) +mstore(0x1e40, mulmod(mload(0x12a0), 13894403229372218245111098554468346933152618215322268934207074514797092422856, f_q)) +mstore(0x1e60, addmod(mload(0xae0), 7993839642467056977135307190788928155395746185093765409491129671778716072761, f_q)) +mstore(0x1e80, mulmod(mload(0x12a0), 5289443209903185443361862148540090689648485914368835830972895623576469023722, f_q)) +mstore(0x1ea0, addmod(mload(0xae0), 16598799661936089778884543596717184398899878486047198512725308562999339471895, f_q)) +mstore(0x1ec0, mulmod(mload(0x12a0), 19715528266218439644661892824912275086257866064695767122686506494361332681035, f_q)) +mstore(0x1ee0, addmod(mload(0xae0), 2172714605620835577584512920345000002290498335720267221011697692214475814582, f_q)) +mstore(0x1f00, mulmod(mload(0x12a0), 15161189183906287273290738379431332336600234154579306802151507052820126345529, f_q)) +mstore(0x1f20, addmod(mload(0xae0), 6727053687932987948955667365825942751948130245836727541546697133755682150088, f_q)) +mstore(0x1f40, mulmod(mload(0x12a0), 12456424076401232823832128238027368612265814450984711658287606686035629293382, f_q)) +mstore(0x1f60, addmod(mload(0xae0), 9431818795438042398414277507229906476282549949431322685410597500540179202235, f_q)) +mstore(0x1f80, mulmod(mload(0x12a0), 557567375339945239933617516585967620814823575807691402619711360028043331811, f_q)) +mstore(0x1fa0, addmod(mload(0xae0), 21330675496499329982312788228671307467733540824608342941078492826547765163806, f_q)) +mstore(0x1fc0, mulmod(mload(0x12a0), 3675353143102618619098608207619541954347747556257261634661810167705798540391, f_q)) +mstore(0x1fe0, addmod(mload(0xae0), 18212889728736656603147797537637733134200616844158772709036394018870009955226, f_q)) +mstore(0x2000, mulmod(mload(0x12a0), 16611719114775828483319365659907682366622074960672212059891361227499450055959, f_q)) +mstore(0x2020, addmod(mload(0xae0), 5276523757063446738927040085349592721926289439743822283806842959076358439658, f_q)) +mstore(0x2040, mulmod(mload(0x12a0), 16386136101309958540926610099404767784529741901845901994660986029617143477017, f_q)) +mstore(0x2060, addmod(mload(0xae0), 5502106770529316681319795645852507304018622498570132349037218156958665018600, f_q)) +mstore(0x2080, mulmod(mload(0x12a0), 4509404676247677387317362072810231899718070082381452255950861037254608304934, f_q)) +mstore(0x20a0, addmod(mload(0xae0), 17378838195591597834929043672447043188830294318034582087747343149321200190683, f_q)) +mstore(0x20c0, mulmod(mload(0x12a0), 16810138474166795540944740696920121481076613636731046381068745586671284628566, f_q)) +mstore(0x20e0, addmod(mload(0xae0), 5078104397672479681301665048337153607471750763684987962629458599904523867051, f_q)) +mstore(0x2100, mulmod(mload(0x12a0), 6866457077948847028333856457654941632900463970069876241424363695212127143359, f_q)) +mstore(0x2120, addmod(mload(0xae0), 15021785793890428193912549287602333455647900430346158102273840491363681352258, f_q)) +mstore(0x2140, mulmod(mload(0x12a0), 15050098906272869114113753879341673724544293065073132019915594147673843274264, f_q)) +mstore(0x2160, addmod(mload(0xae0), 6838143965566406108132651865915601364004071335342902323782610038901965221353, f_q)) +mstore(0x2180, mulmod(mload(0x12a0), 20169013865622130318472103510465966222180994822334426398191891983290742724178, f_q)) +mstore(0x21a0, addmod(mload(0xae0), 1719229006217144903774302234791308866367369578081607945506312203285065771439, f_q)) +{ + let prod := mload(0x12e0) + + prod := mulmod(mload(0x1320), prod, f_q) + mstore(0x21c0, prod) + + prod := mulmod(mload(0x1360), prod, f_q) + mstore(0x21e0, prod) + + prod := mulmod(mload(0x13a0), prod, f_q) + mstore(0x2200, prod) + + prod := mulmod(mload(0x13e0), prod, f_q) + mstore(0x2220, prod) + + prod := mulmod(mload(0x1420), prod, f_q) + mstore(0x2240, prod) + + prod := mulmod(mload(0x1460), prod, f_q) + mstore(0x2260, prod) + + prod := mulmod(mload(0x14a0), prod, f_q) + mstore(0x2280, prod) + + prod := mulmod(mload(0x14e0), prod, f_q) + mstore(0x22a0, prod) + + prod := mulmod(mload(0x1520), prod, f_q) + mstore(0x22c0, prod) + + prod := mulmod(mload(0x1560), prod, f_q) + mstore(0x22e0, prod) + + prod := mulmod(mload(0x15a0), prod, f_q) + mstore(0x2300, prod) + + prod := mulmod(mload(0x15e0), prod, f_q) + mstore(0x2320, prod) + + prod := mulmod(mload(0x1620), prod, f_q) + mstore(0x2340, prod) + + prod := mulmod(mload(0x1660), prod, f_q) + mstore(0x2360, prod) + + prod := mulmod(mload(0x16a0), prod, f_q) + mstore(0x2380, prod) + + prod := mulmod(mload(0x16e0), prod, f_q) + mstore(0x23a0, prod) + + prod := mulmod(mload(0x1720), prod, f_q) + mstore(0x23c0, prod) + + prod := mulmod(mload(0x1760), prod, f_q) + mstore(0x23e0, prod) + + prod := mulmod(mload(0x17a0), prod, f_q) + mstore(0x2400, prod) + + prod := mulmod(mload(0x17e0), prod, f_q) + mstore(0x2420, prod) + + prod := mulmod(mload(0x1820), prod, f_q) + mstore(0x2440, prod) + + prod := mulmod(mload(0x1860), prod, f_q) + mstore(0x2460, prod) + + prod := mulmod(mload(0x18a0), prod, f_q) + mstore(0x2480, prod) + + prod := mulmod(mload(0x18e0), prod, f_q) + mstore(0x24a0, prod) + + prod := mulmod(mload(0x1920), prod, f_q) + mstore(0x24c0, prod) + + prod := mulmod(mload(0x1960), prod, f_q) + mstore(0x24e0, prod) + + prod := mulmod(mload(0x19a0), prod, f_q) + mstore(0x2500, prod) + + prod := mulmod(mload(0x19e0), prod, f_q) + mstore(0x2520, prod) + + prod := mulmod(mload(0x1a20), prod, f_q) + mstore(0x2540, prod) + + prod := mulmod(mload(0x1a60), prod, f_q) + mstore(0x2560, prod) + + prod := mulmod(mload(0x1aa0), prod, f_q) + mstore(0x2580, prod) + + prod := mulmod(mload(0x1ae0), prod, f_q) + mstore(0x25a0, prod) + + prod := mulmod(mload(0x1b20), prod, f_q) + mstore(0x25c0, prod) + + prod := mulmod(mload(0x1b60), prod, f_q) + mstore(0x25e0, prod) + + prod := mulmod(mload(0x1ba0), prod, f_q) + mstore(0x2600, prod) + + prod := mulmod(mload(0x1be0), prod, f_q) + mstore(0x2620, prod) + + prod := mulmod(mload(0x1c20), prod, f_q) + mstore(0x2640, prod) + + prod := mulmod(mload(0x1c60), prod, f_q) + mstore(0x2660, prod) + + prod := mulmod(mload(0x1ca0), prod, f_q) + mstore(0x2680, prod) + + prod := mulmod(mload(0x1ce0), prod, f_q) + mstore(0x26a0, prod) + + prod := mulmod(mload(0x1d20), prod, f_q) + mstore(0x26c0, prod) + + prod := mulmod(mload(0x1d60), prod, f_q) + mstore(0x26e0, prod) + + prod := mulmod(mload(0x1da0), prod, f_q) + mstore(0x2700, prod) + + prod := mulmod(mload(0x1de0), prod, f_q) + mstore(0x2720, prod) + + prod := mulmod(mload(0x1e20), prod, f_q) + mstore(0x2740, prod) + + prod := mulmod(mload(0x1e60), prod, f_q) + mstore(0x2760, prod) + + prod := mulmod(mload(0x1ea0), prod, f_q) + mstore(0x2780, prod) + + prod := mulmod(mload(0x1ee0), prod, f_q) + mstore(0x27a0, prod) + + prod := mulmod(mload(0x1f20), prod, f_q) + mstore(0x27c0, prod) + + prod := mulmod(mload(0x1f60), prod, f_q) + mstore(0x27e0, prod) + + prod := mulmod(mload(0x1fa0), prod, f_q) + mstore(0x2800, prod) + + prod := mulmod(mload(0x1fe0), prod, f_q) + mstore(0x2820, prod) + + prod := mulmod(mload(0x2020), prod, f_q) + mstore(0x2840, prod) + + prod := mulmod(mload(0x2060), prod, f_q) + mstore(0x2860, prod) + + prod := mulmod(mload(0x20a0), prod, f_q) + mstore(0x2880, prod) + + prod := mulmod(mload(0x20e0), prod, f_q) + mstore(0x28a0, prod) + + prod := mulmod(mload(0x2120), prod, f_q) + mstore(0x28c0, prod) + + prod := mulmod(mload(0x2160), prod, f_q) + mstore(0x28e0, prod) + + prod := mulmod(mload(0x21a0), prod, f_q) + mstore(0x2900, prod) + + prod := mulmod(mload(0x1280), prod, f_q) + mstore(0x2920, prod) + + } +mstore(0x2960, 32) +mstore(0x2980, 32) +mstore(0x29a0, 32) +mstore(0x29c0, mload(0x2920)) +mstore(0x29e0, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x2a00, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x2960, 0xc0, 0x2940, 0x20), 1), success) +{ + + let inv := mload(0x2940) + let v + + v := mload(0x1280) + mstore(4736, mulmod(mload(0x2900), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x21a0) + mstore(8608, mulmod(mload(0x28e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2160) + mstore(8544, mulmod(mload(0x28c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2120) + mstore(8480, mulmod(mload(0x28a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x20e0) + mstore(8416, mulmod(mload(0x2880), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x20a0) + mstore(8352, mulmod(mload(0x2860), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2060) + mstore(8288, mulmod(mload(0x2840), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2020) + mstore(8224, mulmod(mload(0x2820), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1fe0) + mstore(8160, mulmod(mload(0x2800), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1fa0) + mstore(8096, mulmod(mload(0x27e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1f60) + mstore(8032, mulmod(mload(0x27c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1f20) + mstore(7968, mulmod(mload(0x27a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1ee0) + mstore(7904, mulmod(mload(0x2780), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1ea0) + mstore(7840, mulmod(mload(0x2760), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1e60) + mstore(7776, mulmod(mload(0x2740), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1e20) + mstore(7712, mulmod(mload(0x2720), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1de0) + mstore(7648, mulmod(mload(0x2700), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1da0) + mstore(7584, mulmod(mload(0x26e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1d60) + mstore(7520, mulmod(mload(0x26c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1d20) + mstore(7456, mulmod(mload(0x26a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1ce0) + mstore(7392, mulmod(mload(0x2680), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1ca0) + mstore(7328, mulmod(mload(0x2660), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1c60) + mstore(7264, mulmod(mload(0x2640), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1c20) + mstore(7200, mulmod(mload(0x2620), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1be0) + mstore(7136, mulmod(mload(0x2600), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1ba0) + mstore(7072, mulmod(mload(0x25e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1b60) + mstore(7008, mulmod(mload(0x25c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1b20) + mstore(6944, mulmod(mload(0x25a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1ae0) + mstore(6880, mulmod(mload(0x2580), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1aa0) + mstore(6816, mulmod(mload(0x2560), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1a60) + mstore(6752, mulmod(mload(0x2540), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1a20) + mstore(6688, mulmod(mload(0x2520), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x19e0) + mstore(6624, mulmod(mload(0x2500), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x19a0) + mstore(6560, mulmod(mload(0x24e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1960) + mstore(6496, mulmod(mload(0x24c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1920) + mstore(6432, mulmod(mload(0x24a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x18e0) + mstore(6368, mulmod(mload(0x2480), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x18a0) + mstore(6304, mulmod(mload(0x2460), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1860) + mstore(6240, mulmod(mload(0x2440), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1820) + mstore(6176, mulmod(mload(0x2420), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x17e0) + mstore(6112, mulmod(mload(0x2400), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x17a0) + mstore(6048, mulmod(mload(0x23e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1760) + mstore(5984, mulmod(mload(0x23c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1720) + mstore(5920, mulmod(mload(0x23a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x16e0) + mstore(5856, mulmod(mload(0x2380), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x16a0) + mstore(5792, mulmod(mload(0x2360), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1660) + mstore(5728, mulmod(mload(0x2340), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1620) + mstore(5664, mulmod(mload(0x2320), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x15e0) + mstore(5600, mulmod(mload(0x2300), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x15a0) + mstore(5536, mulmod(mload(0x22e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1560) + mstore(5472, mulmod(mload(0x22c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1520) + mstore(5408, mulmod(mload(0x22a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x14e0) + mstore(5344, mulmod(mload(0x2280), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x14a0) + mstore(5280, mulmod(mload(0x2260), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1460) + mstore(5216, mulmod(mload(0x2240), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1420) + mstore(5152, mulmod(mload(0x2220), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x13e0) + mstore(5088, mulmod(mload(0x2200), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x13a0) + mstore(5024, mulmod(mload(0x21e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1360) + mstore(4960, mulmod(mload(0x21c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1320) + mstore(4896, mulmod(mload(0x12e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x12e0, inv) + + } +mstore(0x2a20, mulmod(mload(0x12c0), mload(0x12e0), f_q)) +mstore(0x2a40, mulmod(mload(0x1300), mload(0x1320), f_q)) +mstore(0x2a60, mulmod(mload(0x1340), mload(0x1360), f_q)) +mstore(0x2a80, mulmod(mload(0x1380), mload(0x13a0), f_q)) +mstore(0x2aa0, mulmod(mload(0x13c0), mload(0x13e0), f_q)) +mstore(0x2ac0, mulmod(mload(0x1400), mload(0x1420), f_q)) +mstore(0x2ae0, mulmod(mload(0x1440), mload(0x1460), f_q)) +mstore(0x2b00, mulmod(mload(0x1480), mload(0x14a0), f_q)) +mstore(0x2b20, mulmod(mload(0x14c0), mload(0x14e0), f_q)) +mstore(0x2b40, mulmod(mload(0x1500), mload(0x1520), f_q)) +mstore(0x2b60, mulmod(mload(0x1540), mload(0x1560), f_q)) +mstore(0x2b80, mulmod(mload(0x1580), mload(0x15a0), f_q)) +mstore(0x2ba0, mulmod(mload(0x15c0), mload(0x15e0), f_q)) +mstore(0x2bc0, mulmod(mload(0x1600), mload(0x1620), f_q)) +mstore(0x2be0, mulmod(mload(0x1640), mload(0x1660), f_q)) +mstore(0x2c00, mulmod(mload(0x1680), mload(0x16a0), f_q)) +mstore(0x2c20, mulmod(mload(0x16c0), mload(0x16e0), f_q)) +mstore(0x2c40, mulmod(mload(0x1700), mload(0x1720), f_q)) +mstore(0x2c60, mulmod(mload(0x1740), mload(0x1760), f_q)) +mstore(0x2c80, mulmod(mload(0x1780), mload(0x17a0), f_q)) +mstore(0x2ca0, mulmod(mload(0x17c0), mload(0x17e0), f_q)) +mstore(0x2cc0, mulmod(mload(0x1800), mload(0x1820), f_q)) +mstore(0x2ce0, mulmod(mload(0x1840), mload(0x1860), f_q)) +mstore(0x2d00, mulmod(mload(0x1880), mload(0x18a0), f_q)) +mstore(0x2d20, mulmod(mload(0x18c0), mload(0x18e0), f_q)) +mstore(0x2d40, mulmod(mload(0x1900), mload(0x1920), f_q)) +mstore(0x2d60, mulmod(mload(0x1940), mload(0x1960), f_q)) +mstore(0x2d80, mulmod(mload(0x1980), mload(0x19a0), f_q)) +mstore(0x2da0, mulmod(mload(0x19c0), mload(0x19e0), f_q)) +mstore(0x2dc0, mulmod(mload(0x1a00), mload(0x1a20), f_q)) +mstore(0x2de0, mulmod(mload(0x1a40), mload(0x1a60), f_q)) +mstore(0x2e00, mulmod(mload(0x1a80), mload(0x1aa0), f_q)) +mstore(0x2e20, mulmod(mload(0x1ac0), mload(0x1ae0), f_q)) +mstore(0x2e40, mulmod(mload(0x1b00), mload(0x1b20), f_q)) +mstore(0x2e60, mulmod(mload(0x1b40), mload(0x1b60), f_q)) +mstore(0x2e80, mulmod(mload(0x1b80), mload(0x1ba0), f_q)) +mstore(0x2ea0, mulmod(mload(0x1bc0), mload(0x1be0), f_q)) +mstore(0x2ec0, mulmod(mload(0x1c00), mload(0x1c20), f_q)) +mstore(0x2ee0, mulmod(mload(0x1c40), mload(0x1c60), f_q)) +mstore(0x2f00, mulmod(mload(0x1c80), mload(0x1ca0), f_q)) +mstore(0x2f20, mulmod(mload(0x1cc0), mload(0x1ce0), f_q)) +mstore(0x2f40, mulmod(mload(0x1d00), mload(0x1d20), f_q)) +mstore(0x2f60, mulmod(mload(0x1d40), mload(0x1d60), f_q)) +mstore(0x2f80, mulmod(mload(0x1d80), mload(0x1da0), f_q)) +mstore(0x2fa0, mulmod(mload(0x1dc0), mload(0x1de0), f_q)) +mstore(0x2fc0, mulmod(mload(0x1e00), mload(0x1e20), f_q)) +mstore(0x2fe0, mulmod(mload(0x1e40), mload(0x1e60), f_q)) +mstore(0x3000, mulmod(mload(0x1e80), mload(0x1ea0), f_q)) +mstore(0x3020, mulmod(mload(0x1ec0), mload(0x1ee0), f_q)) +mstore(0x3040, mulmod(mload(0x1f00), mload(0x1f20), f_q)) +mstore(0x3060, mulmod(mload(0x1f40), mload(0x1f60), f_q)) +mstore(0x3080, mulmod(mload(0x1f80), mload(0x1fa0), f_q)) +mstore(0x30a0, mulmod(mload(0x1fc0), mload(0x1fe0), f_q)) +mstore(0x30c0, mulmod(mload(0x2000), mload(0x2020), f_q)) +mstore(0x30e0, mulmod(mload(0x2040), mload(0x2060), f_q)) +mstore(0x3100, mulmod(mload(0x2080), mload(0x20a0), f_q)) +mstore(0x3120, mulmod(mload(0x20c0), mload(0x20e0), f_q)) +mstore(0x3140, mulmod(mload(0x2100), mload(0x2120), f_q)) +mstore(0x3160, mulmod(mload(0x2140), mload(0x2160), f_q)) +mstore(0x3180, mulmod(mload(0x2180), mload(0x21a0), f_q)) +{ + let result := mulmod(mload(0x2b00), mload(0x20), f_q) +result := addmod(mulmod(mload(0x2b20), mload(0x40), f_q), result, f_q) +result := addmod(mulmod(mload(0x2b40), mload(0x60), f_q), result, f_q) +result := addmod(mulmod(mload(0x2b60), mload(0x80), f_q), result, f_q) +result := addmod(mulmod(mload(0x2b80), mload(0xa0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2ba0), mload(0xc0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2bc0), mload(0xe0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2be0), mload(0x100), f_q), result, f_q) +result := addmod(mulmod(mload(0x2c00), mload(0x120), f_q), result, f_q) +result := addmod(mulmod(mload(0x2c20), mload(0x140), f_q), result, f_q) +result := addmod(mulmod(mload(0x2c40), mload(0x160), f_q), result, f_q) +result := addmod(mulmod(mload(0x2c60), mload(0x180), f_q), result, f_q) +result := addmod(mulmod(mload(0x2c80), mload(0x1a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2ca0), mload(0x1c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2cc0), mload(0x1e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2ce0), mload(0x200), f_q), result, f_q) +result := addmod(mulmod(mload(0x2d00), mload(0x220), f_q), result, f_q) +result := addmod(mulmod(mload(0x2d20), mload(0x240), f_q), result, f_q) +result := addmod(mulmod(mload(0x2d40), mload(0x260), f_q), result, f_q) +result := addmod(mulmod(mload(0x2d60), mload(0x280), f_q), result, f_q) +result := addmod(mulmod(mload(0x2d80), mload(0x2a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2da0), mload(0x2c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2dc0), mload(0x2e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2de0), mload(0x300), f_q), result, f_q) +result := addmod(mulmod(mload(0x2e00), mload(0x320), f_q), result, f_q) +result := addmod(mulmod(mload(0x2e20), mload(0x340), f_q), result, f_q) +result := addmod(mulmod(mload(0x2e40), mload(0x360), f_q), result, f_q) +result := addmod(mulmod(mload(0x2e60), mload(0x380), f_q), result, f_q) +result := addmod(mulmod(mload(0x2e80), mload(0x3a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2ea0), mload(0x3c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2ec0), mload(0x3e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2ee0), mload(0x400), f_q), result, f_q) +result := addmod(mulmod(mload(0x2f00), mload(0x420), f_q), result, f_q) +result := addmod(mulmod(mload(0x2f20), mload(0x440), f_q), result, f_q) +result := addmod(mulmod(mload(0x2f40), mload(0x460), f_q), result, f_q) +result := addmod(mulmod(mload(0x2f60), mload(0x480), f_q), result, f_q) +result := addmod(mulmod(mload(0x2f80), mload(0x4a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2fa0), mload(0x4c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2fc0), mload(0x4e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2fe0), mload(0x500), f_q), result, f_q) +result := addmod(mulmod(mload(0x3000), mload(0x520), f_q), result, f_q) +result := addmod(mulmod(mload(0x3020), mload(0x540), f_q), result, f_q) +result := addmod(mulmod(mload(0x3040), mload(0x560), f_q), result, f_q) +result := addmod(mulmod(mload(0x3060), mload(0x580), f_q), result, f_q) +result := addmod(mulmod(mload(0x3080), mload(0x5a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x30a0), mload(0x5c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x30c0), mload(0x5e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x30e0), mload(0x600), f_q), result, f_q) +result := addmod(mulmod(mload(0x3100), mload(0x620), f_q), result, f_q) +result := addmod(mulmod(mload(0x3120), mload(0x640), f_q), result, f_q) +result := addmod(mulmod(mload(0x3140), mload(0x660), f_q), result, f_q) +result := addmod(mulmod(mload(0x3160), mload(0x680), f_q), result, f_q) +result := addmod(mulmod(mload(0x3180), mload(0x6a0), f_q), result, f_q) +mstore(12704, result) + } +mstore(0x31c0, mulmod(mload(0xb60), mload(0xb40), f_q)) +mstore(0x31e0, addmod(mload(0xb20), mload(0x31c0), f_q)) +mstore(0x3200, addmod(mload(0x31e0), sub(f_q, mload(0xb80)), f_q)) +mstore(0x3220, mulmod(mload(0x3200), mload(0xc00), f_q)) +mstore(0x3240, mulmod(mload(0x980), mload(0x3220), f_q)) +mstore(0x3260, addmod(1, sub(f_q, mload(0xca0)), f_q)) +mstore(0x3280, mulmod(mload(0x3260), mload(0x2b00), f_q)) +mstore(0x32a0, addmod(mload(0x3240), mload(0x3280), f_q)) +mstore(0x32c0, mulmod(mload(0x980), mload(0x32a0), f_q)) +mstore(0x32e0, mulmod(mload(0xca0), mload(0xca0), f_q)) +mstore(0x3300, addmod(mload(0x32e0), sub(f_q, mload(0xca0)), f_q)) +mstore(0x3320, mulmod(mload(0x3300), mload(0x2a20), f_q)) +mstore(0x3340, addmod(mload(0x32c0), mload(0x3320), f_q)) +mstore(0x3360, mulmod(mload(0x980), mload(0x3340), f_q)) +mstore(0x3380, addmod(1, sub(f_q, mload(0x2a20)), f_q)) +mstore(0x33a0, addmod(mload(0x2a40), mload(0x2a60), f_q)) +mstore(0x33c0, addmod(mload(0x33a0), mload(0x2a80), f_q)) +mstore(0x33e0, addmod(mload(0x33c0), mload(0x2aa0), f_q)) +mstore(0x3400, addmod(mload(0x33e0), mload(0x2ac0), f_q)) +mstore(0x3420, addmod(mload(0x3400), mload(0x2ae0), f_q)) +mstore(0x3440, addmod(mload(0x3380), sub(f_q, mload(0x3420)), f_q)) +mstore(0x3460, mulmod(mload(0xc40), mload(0x800), f_q)) +mstore(0x3480, addmod(mload(0xba0), mload(0x3460), f_q)) +mstore(0x34a0, addmod(mload(0x3480), mload(0x860), f_q)) +mstore(0x34c0, mulmod(mload(0xc60), mload(0x800), f_q)) +mstore(0x34e0, addmod(mload(0xb20), mload(0x34c0), f_q)) +mstore(0x3500, addmod(mload(0x34e0), mload(0x860), f_q)) +mstore(0x3520, mulmod(mload(0x3500), mload(0x34a0), f_q)) +mstore(0x3540, mulmod(mload(0xc80), mload(0x800), f_q)) +mstore(0x3560, addmod(mload(0x31a0), mload(0x3540), f_q)) +mstore(0x3580, addmod(mload(0x3560), mload(0x860), f_q)) +mstore(0x35a0, mulmod(mload(0x3580), mload(0x3520), f_q)) +mstore(0x35c0, mulmod(mload(0x35a0), mload(0xcc0), f_q)) +mstore(0x35e0, mulmod(1, mload(0x800), f_q)) +mstore(0x3600, mulmod(mload(0xae0), mload(0x35e0), f_q)) +mstore(0x3620, addmod(mload(0xba0), mload(0x3600), f_q)) +mstore(0x3640, addmod(mload(0x3620), mload(0x860), f_q)) +mstore(0x3660, mulmod(4131629893567559867359510883348571134090853742863529169391034518566172092834, mload(0x800), f_q)) +mstore(0x3680, mulmod(mload(0xae0), mload(0x3660), f_q)) +mstore(0x36a0, addmod(mload(0xb20), mload(0x3680), f_q)) +mstore(0x36c0, addmod(mload(0x36a0), mload(0x860), f_q)) +mstore(0x36e0, mulmod(mload(0x36c0), mload(0x3640), f_q)) +mstore(0x3700, mulmod(8910878055287538404433155982483128285667088683464058436815641868457422632747, mload(0x800), f_q)) +mstore(0x3720, mulmod(mload(0xae0), mload(0x3700), f_q)) +mstore(0x3740, addmod(mload(0x31a0), mload(0x3720), f_q)) +mstore(0x3760, addmod(mload(0x3740), mload(0x860), f_q)) +mstore(0x3780, mulmod(mload(0x3760), mload(0x36e0), f_q)) +mstore(0x37a0, mulmod(mload(0x3780), mload(0xca0), f_q)) +mstore(0x37c0, addmod(mload(0x35c0), sub(f_q, mload(0x37a0)), f_q)) +mstore(0x37e0, mulmod(mload(0x37c0), mload(0x3440), f_q)) +mstore(0x3800, addmod(mload(0x3360), mload(0x37e0), f_q)) +mstore(0x3820, mulmod(mload(0x980), mload(0x3800), f_q)) +mstore(0x3840, addmod(1, sub(f_q, mload(0xce0)), f_q)) +mstore(0x3860, mulmod(mload(0x3840), mload(0x2b00), f_q)) +mstore(0x3880, addmod(mload(0x3820), mload(0x3860), f_q)) +mstore(0x38a0, mulmod(mload(0x980), mload(0x3880), f_q)) +mstore(0x38c0, mulmod(mload(0xce0), mload(0xce0), f_q)) +mstore(0x38e0, addmod(mload(0x38c0), sub(f_q, mload(0xce0)), f_q)) +mstore(0x3900, mulmod(mload(0x38e0), mload(0x2a20), f_q)) +mstore(0x3920, addmod(mload(0x38a0), mload(0x3900), f_q)) +mstore(0x3940, mulmod(mload(0x980), mload(0x3920), f_q)) +mstore(0x3960, addmod(mload(0xd20), mload(0x800), f_q)) +mstore(0x3980, mulmod(mload(0x3960), mload(0xd00), f_q)) +mstore(0x39a0, addmod(mload(0xd60), mload(0x860), f_q)) +mstore(0x39c0, mulmod(mload(0x39a0), mload(0x3980), f_q)) +mstore(0x39e0, mulmod(mload(0xb20), mload(0xbe0), f_q)) +mstore(0x3a00, addmod(mload(0x39e0), mload(0x800), f_q)) +mstore(0x3a20, mulmod(mload(0x3a00), mload(0xce0), f_q)) +mstore(0x3a40, addmod(mload(0xbc0), mload(0x860), f_q)) +mstore(0x3a60, mulmod(mload(0x3a40), mload(0x3a20), f_q)) +mstore(0x3a80, addmod(mload(0x39c0), sub(f_q, mload(0x3a60)), f_q)) +mstore(0x3aa0, mulmod(mload(0x3a80), mload(0x3440), f_q)) +mstore(0x3ac0, addmod(mload(0x3940), mload(0x3aa0), f_q)) +mstore(0x3ae0, mulmod(mload(0x980), mload(0x3ac0), f_q)) +mstore(0x3b00, addmod(mload(0xd20), sub(f_q, mload(0xd60)), f_q)) +mstore(0x3b20, mulmod(mload(0x3b00), mload(0x2b00), f_q)) +mstore(0x3b40, addmod(mload(0x3ae0), mload(0x3b20), f_q)) +mstore(0x3b60, mulmod(mload(0x980), mload(0x3b40), f_q)) +mstore(0x3b80, mulmod(mload(0x3b00), mload(0x3440), f_q)) +mstore(0x3ba0, addmod(mload(0xd20), sub(f_q, mload(0xd40)), f_q)) +mstore(0x3bc0, mulmod(mload(0x3ba0), mload(0x3b80), f_q)) +mstore(0x3be0, addmod(mload(0x3b60), mload(0x3bc0), f_q)) +mstore(0x3c00, mulmod(mload(0x1260), mload(0x1260), f_q)) +mstore(0x3c20, mulmod(mload(0x3c00), mload(0x1260), f_q)) +mstore(0x3c40, mulmod(mload(0x3c20), mload(0x1260), f_q)) +mstore(0x3c60, mulmod(1, mload(0x1260), f_q)) +mstore(0x3c80, mulmod(1, mload(0x3c00), f_q)) +mstore(0x3ca0, mulmod(1, mload(0x3c20), f_q)) +mstore(0x3cc0, mulmod(mload(0x3be0), mload(0x1280), f_q)) +mstore(0x3ce0, mulmod(mload(0xfa0), mload(0xae0), f_q)) +mstore(0x3d00, mulmod(mload(0x3ce0), mload(0xae0), f_q)) +mstore(0x3d20, mulmod(mload(0xae0), 1, f_q)) +mstore(0x3d40, addmod(mload(0xea0), sub(f_q, mload(0x3d20)), f_q)) +mstore(0x3d60, mulmod(mload(0xae0), 3615478808282855240548287271348143516886772452944084747768312988864436725401, f_q)) +mstore(0x3d80, addmod(mload(0xea0), sub(f_q, mload(0x3d60)), f_q)) +mstore(0x3da0, mulmod(mload(0xae0), 8374374965308410102411073611984011876711565317741801500439755773472076597347, f_q)) +mstore(0x3dc0, addmod(mload(0xea0), sub(f_q, mload(0x3da0)), f_q)) +mstore(0x3de0, mulmod(mload(0xae0), 9741553891420464328295280489650144566903017206473301385034033384879943874347, f_q)) +mstore(0x3e00, addmod(mload(0xea0), sub(f_q, mload(0x3de0)), f_q)) +mstore(0x3e20, mulmod(mload(0xae0), 11211301017135681023579411905410872569206244553457844956874280139879520583390, f_q)) +mstore(0x3e40, addmod(mload(0xea0), sub(f_q, mload(0x3e20)), f_q)) +{ + let result := mulmod(mload(0xea0), mulmod(mload(0x3ce0), 13213688729882003894512633350385593288217014177373218494356903340348818451480, f_q), f_q) +result := addmod(mulmod(mload(0xae0), mulmod(mload(0x3ce0), 8674554141957271327733772394871681800331350223042815849341300846226990044137, f_q), f_q), result, f_q) +mstore(15968, result) + } +{ + let result := mulmod(mload(0xea0), mulmod(mload(0x3ce0), 8207090019724696496350398458716998472718344609680392612601596849934418295470, f_q), f_q) +result := addmod(mulmod(mload(0xae0), mulmod(mload(0x3ce0), 7391709068497399131897422873231908718558236401035363928063603272120120747483, f_q), f_q), result, f_q) +mstore(16000, result) + } +{ + let result := mulmod(mload(0xea0), mulmod(mload(0x3ce0), 7391709068497399131897422873231908718558236401035363928063603272120120747483, f_q), f_q) +result := addmod(mulmod(mload(0xae0), mulmod(mload(0x3ce0), 1833147409647494756995474660497533717522217035849797032644829375745951548463, f_q), f_q), result, f_q) +mstore(16032, result) + } +{ + let result := mulmod(mload(0xea0), mulmod(mload(0x3ce0), 19036273796805830823244991598792794567595348772040298280440552631112242221017, f_q), f_q) +result := addmod(mulmod(mload(0xae0), mulmod(mload(0x3ce0), 21424174760842011600237027652323753233820727276907995465687706728442780288120, f_q), f_q), result, f_q) +mstore(16064, result) + } +mstore(0x3ee0, mulmod(1, mload(0x3d40), f_q)) +mstore(0x3f00, mulmod(mload(0x3ee0), mload(0x3dc0), f_q)) +mstore(0x3f20, mulmod(mload(0x3f00), mload(0x3e40), f_q)) +mstore(0x3f40, mulmod(mload(0x3f20), mload(0x3d80), f_q)) +{ + let result := mulmod(mload(0xea0), mulmod(mload(0xae0), 13513867906530865119835332133273263211836799082674232843258448413103731898271, f_q), f_q) +result := addmod(mulmod(mload(0xae0), mulmod(mload(0xae0), 8374374965308410102411073611984011876711565317741801500439755773472076597346, f_q), f_q), result, f_q) +mstore(16224, result) + } +{ + let result := mulmod(mload(0xea0), mulmod(mload(0xae0), 8374374965308410102411073611984011876711565317741801500439755773472076597346, f_q), f_q) +result := addmod(mulmod(mload(0xae0), mulmod(mload(0xae0), 19051316820012004301078067451830414396053685164699990887263679820168364509574, f_q), f_q), result, f_q) +mstore(16256, result) + } +{ + let result := mulmod(mload(0xea0), mulmod(mload(0xae0), 12146688980418810893951125255607130521645347193942732958664170801695864621271, f_q), f_q) +result := addmod(mulmod(mload(0xae0), mulmod(mload(0xae0), 9741553891420464328295280489650144566903017206473301385034033384879943874346, f_q), f_q), result, f_q) +mstore(16288, result) + } +{ + let result := mulmod(mload(0xea0), mulmod(mload(0xae0), 9741553891420464328295280489650144566903017206473301385034033384879943874346, f_q), f_q) +result := addmod(mulmod(mload(0xae0), mulmod(mload(0xae0), 1007427538592118648722042630484239861096428745172156964443610795837813833159, f_q), f_q), result, f_q) +mstore(16320, result) + } +mstore(0x3fe0, mulmod(mload(0x3ee0), mload(0x3e00), f_q)) +{ + let result := mulmod(mload(0xea0), 1, f_q) +result := addmod(mulmod(mload(0xae0), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q), result, f_q) +mstore(16384, result) + } +{ + let prod := mload(0x3e60) + + prod := mulmod(mload(0x3e80), prod, f_q) + mstore(0x4020, prod) + + prod := mulmod(mload(0x3ea0), prod, f_q) + mstore(0x4040, prod) + + prod := mulmod(mload(0x3ec0), prod, f_q) + mstore(0x4060, prod) + + prod := mulmod(mload(0x3f60), prod, f_q) + mstore(0x4080, prod) + + prod := mulmod(mload(0x3f80), prod, f_q) + mstore(0x40a0, prod) + + prod := mulmod(mload(0x3f00), prod, f_q) + mstore(0x40c0, prod) + + prod := mulmod(mload(0x3fa0), prod, f_q) + mstore(0x40e0, prod) + + prod := mulmod(mload(0x3fc0), prod, f_q) + mstore(0x4100, prod) + + prod := mulmod(mload(0x3fe0), prod, f_q) + mstore(0x4120, prod) + + prod := mulmod(mload(0x4000), prod, f_q) + mstore(0x4140, prod) + + prod := mulmod(mload(0x3ee0), prod, f_q) + mstore(0x4160, prod) + + } +mstore(0x41a0, 32) +mstore(0x41c0, 32) +mstore(0x41e0, 32) +mstore(0x4200, mload(0x4160)) +mstore(0x4220, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x4240, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x41a0, 0xc0, 0x4180, 0x20), 1), success) +{ + + let inv := mload(0x4180) + let v + + v := mload(0x3ee0) + mstore(16096, mulmod(mload(0x4140), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4000) + mstore(16384, mulmod(mload(0x4120), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3fe0) + mstore(16352, mulmod(mload(0x4100), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3fc0) + mstore(16320, mulmod(mload(0x40e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3fa0) + mstore(16288, mulmod(mload(0x40c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3f00) + mstore(16128, mulmod(mload(0x40a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3f80) + mstore(16256, mulmod(mload(0x4080), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3f60) + mstore(16224, mulmod(mload(0x4060), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3ec0) + mstore(16064, mulmod(mload(0x4040), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3ea0) + mstore(16032, mulmod(mload(0x4020), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3e80) + mstore(16000, mulmod(mload(0x3e60), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x3e60, inv) + + } +{ + let result := mload(0x3e60) +result := addmod(mload(0x3e80), result, f_q) +result := addmod(mload(0x3ea0), result, f_q) +result := addmod(mload(0x3ec0), result, f_q) +mstore(16992, result) + } +mstore(0x4280, mulmod(mload(0x3f40), mload(0x3f00), f_q)) +{ + let result := mload(0x3f60) +result := addmod(mload(0x3f80), result, f_q) +mstore(17056, result) + } +mstore(0x42c0, mulmod(mload(0x3f40), mload(0x3fe0), f_q)) +{ + let result := mload(0x3fa0) +result := addmod(mload(0x3fc0), result, f_q) +mstore(17120, result) + } +mstore(0x4300, mulmod(mload(0x3f40), mload(0x3ee0), f_q)) +{ + let result := mload(0x4000) +mstore(17184, result) + } +{ + let prod := mload(0x4260) + + prod := mulmod(mload(0x42a0), prod, f_q) + mstore(0x4340, prod) + + prod := mulmod(mload(0x42e0), prod, f_q) + mstore(0x4360, prod) + + prod := mulmod(mload(0x4320), prod, f_q) + mstore(0x4380, prod) + + } +mstore(0x43c0, 32) +mstore(0x43e0, 32) +mstore(0x4400, 32) +mstore(0x4420, mload(0x4380)) +mstore(0x4440, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x4460, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x43c0, 0xc0, 0x43a0, 0x20), 1), success) +{ + + let inv := mload(0x43a0) + let v + + v := mload(0x4320) + mstore(17184, mulmod(mload(0x4360), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x42e0) + mstore(17120, mulmod(mload(0x4340), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x42a0) + mstore(17056, mulmod(mload(0x4260), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x4260, inv) + + } +mstore(0x4480, mulmod(mload(0x4280), mload(0x42a0), f_q)) +mstore(0x44a0, mulmod(mload(0x42c0), mload(0x42e0), f_q)) +mstore(0x44c0, mulmod(mload(0x4300), mload(0x4320), f_q)) +mstore(0x44e0, mulmod(mload(0xda0), mload(0xda0), f_q)) +mstore(0x4500, mulmod(mload(0x44e0), mload(0xda0), f_q)) +mstore(0x4520, mulmod(mload(0x4500), mload(0xda0), f_q)) +mstore(0x4540, mulmod(mload(0x4520), mload(0xda0), f_q)) +mstore(0x4560, mulmod(mload(0x4540), mload(0xda0), f_q)) +mstore(0x4580, mulmod(mload(0x4560), mload(0xda0), f_q)) +mstore(0x45a0, mulmod(mload(0x4580), mload(0xda0), f_q)) +mstore(0x45c0, mulmod(mload(0x45a0), mload(0xda0), f_q)) +mstore(0x45e0, mulmod(mload(0x45c0), mload(0xda0), f_q)) +mstore(0x4600, mulmod(mload(0xe00), mload(0xe00), f_q)) +mstore(0x4620, mulmod(mload(0x4600), mload(0xe00), f_q)) +mstore(0x4640, mulmod(mload(0x4620), mload(0xe00), f_q)) +{ + let result := mulmod(mload(0xb20), mload(0x3e60), f_q) +result := addmod(mulmod(mload(0xb40), mload(0x3e80), f_q), result, f_q) +result := addmod(mulmod(mload(0xb60), mload(0x3ea0), f_q), result, f_q) +result := addmod(mulmod(mload(0xb80), mload(0x3ec0), f_q), result, f_q) +mstore(18016, result) + } +mstore(0x4680, mulmod(mload(0x4660), mload(0x4260), f_q)) +mstore(0x46a0, mulmod(sub(f_q, mload(0x4680)), 1, f_q)) +mstore(0x46c0, mulmod(mload(0x46a0), 1, f_q)) +mstore(0x46e0, mulmod(1, mload(0x4280), f_q)) +{ + let result := mulmod(mload(0xca0), mload(0x3f60), f_q) +result := addmod(mulmod(mload(0xcc0), mload(0x3f80), f_q), result, f_q) +mstore(18176, result) + } +mstore(0x4720, mulmod(mload(0x4700), mload(0x4480), f_q)) +mstore(0x4740, mulmod(sub(f_q, mload(0x4720)), 1, f_q)) +mstore(0x4760, mulmod(mload(0x46e0), 1, f_q)) +{ + let result := mulmod(mload(0xce0), mload(0x3f60), f_q) +result := addmod(mulmod(mload(0xd00), mload(0x3f80), f_q), result, f_q) +mstore(18304, result) + } +mstore(0x47a0, mulmod(mload(0x4780), mload(0x4480), f_q)) +mstore(0x47c0, mulmod(sub(f_q, mload(0x47a0)), mload(0xda0), f_q)) +mstore(0x47e0, mulmod(mload(0x46e0), mload(0xda0), f_q)) +mstore(0x4800, addmod(mload(0x4740), mload(0x47c0), f_q)) +mstore(0x4820, mulmod(mload(0x4800), mload(0xe00), f_q)) +mstore(0x4840, mulmod(mload(0x4760), mload(0xe00), f_q)) +mstore(0x4860, mulmod(mload(0x47e0), mload(0xe00), f_q)) +mstore(0x4880, addmod(mload(0x46c0), mload(0x4820), f_q)) +mstore(0x48a0, mulmod(1, mload(0x42c0), f_q)) +{ + let result := mulmod(mload(0xd20), mload(0x3fa0), f_q) +result := addmod(mulmod(mload(0xd40), mload(0x3fc0), f_q), result, f_q) +mstore(18624, result) + } +mstore(0x48e0, mulmod(mload(0x48c0), mload(0x44a0), f_q)) +mstore(0x4900, mulmod(sub(f_q, mload(0x48e0)), 1, f_q)) +mstore(0x4920, mulmod(mload(0x48a0), 1, f_q)) +mstore(0x4940, mulmod(mload(0x4900), mload(0x4600), f_q)) +mstore(0x4960, mulmod(mload(0x4920), mload(0x4600), f_q)) +mstore(0x4980, addmod(mload(0x4880), mload(0x4940), f_q)) +mstore(0x49a0, mulmod(1, mload(0x4300), f_q)) +{ + let result := mulmod(mload(0xd60), mload(0x4000), f_q) +mstore(18880, result) + } +mstore(0x49e0, mulmod(mload(0x49c0), mload(0x44c0), f_q)) +mstore(0x4a00, mulmod(sub(f_q, mload(0x49e0)), 1, f_q)) +mstore(0x4a20, mulmod(mload(0x49a0), 1, f_q)) +{ + let result := mulmod(mload(0xba0), mload(0x4000), f_q) +mstore(19008, result) + } +mstore(0x4a60, mulmod(mload(0x4a40), mload(0x44c0), f_q)) +mstore(0x4a80, mulmod(sub(f_q, mload(0x4a60)), mload(0xda0), f_q)) +mstore(0x4aa0, mulmod(mload(0x49a0), mload(0xda0), f_q)) +mstore(0x4ac0, addmod(mload(0x4a00), mload(0x4a80), f_q)) +{ + let result := mulmod(mload(0xbc0), mload(0x4000), f_q) +mstore(19168, result) + } +mstore(0x4b00, mulmod(mload(0x4ae0), mload(0x44c0), f_q)) +mstore(0x4b20, mulmod(sub(f_q, mload(0x4b00)), mload(0x44e0), f_q)) +mstore(0x4b40, mulmod(mload(0x49a0), mload(0x44e0), f_q)) +mstore(0x4b60, addmod(mload(0x4ac0), mload(0x4b20), f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x4000), f_q) +mstore(19328, result) + } +mstore(0x4ba0, mulmod(mload(0x4b80), mload(0x44c0), f_q)) +mstore(0x4bc0, mulmod(sub(f_q, mload(0x4ba0)), mload(0x4500), f_q)) +mstore(0x4be0, mulmod(mload(0x49a0), mload(0x4500), f_q)) +mstore(0x4c00, addmod(mload(0x4b60), mload(0x4bc0), f_q)) +{ + let result := mulmod(mload(0xc00), mload(0x4000), f_q) +mstore(19488, result) + } +mstore(0x4c40, mulmod(mload(0x4c20), mload(0x44c0), f_q)) +mstore(0x4c60, mulmod(sub(f_q, mload(0x4c40)), mload(0x4520), f_q)) +mstore(0x4c80, mulmod(mload(0x49a0), mload(0x4520), f_q)) +mstore(0x4ca0, addmod(mload(0x4c00), mload(0x4c60), f_q)) +{ + let result := mulmod(mload(0xc40), mload(0x4000), f_q) +mstore(19648, result) + } +mstore(0x4ce0, mulmod(mload(0x4cc0), mload(0x44c0), f_q)) +mstore(0x4d00, mulmod(sub(f_q, mload(0x4ce0)), mload(0x4540), f_q)) +mstore(0x4d20, mulmod(mload(0x49a0), mload(0x4540), f_q)) +mstore(0x4d40, addmod(mload(0x4ca0), mload(0x4d00), f_q)) +{ + let result := mulmod(mload(0xc60), mload(0x4000), f_q) +mstore(19808, result) + } +mstore(0x4d80, mulmod(mload(0x4d60), mload(0x44c0), f_q)) +mstore(0x4da0, mulmod(sub(f_q, mload(0x4d80)), mload(0x4560), f_q)) +mstore(0x4dc0, mulmod(mload(0x49a0), mload(0x4560), f_q)) +mstore(0x4de0, addmod(mload(0x4d40), mload(0x4da0), f_q)) +{ + let result := mulmod(mload(0xc80), mload(0x4000), f_q) +mstore(19968, result) + } +mstore(0x4e20, mulmod(mload(0x4e00), mload(0x44c0), f_q)) +mstore(0x4e40, mulmod(sub(f_q, mload(0x4e20)), mload(0x4580), f_q)) +mstore(0x4e60, mulmod(mload(0x49a0), mload(0x4580), f_q)) +mstore(0x4e80, addmod(mload(0x4de0), mload(0x4e40), f_q)) +mstore(0x4ea0, mulmod(mload(0x3c60), mload(0x4300), f_q)) +mstore(0x4ec0, mulmod(mload(0x3c80), mload(0x4300), f_q)) +mstore(0x4ee0, mulmod(mload(0x3ca0), mload(0x4300), f_q)) +{ + let result := mulmod(mload(0x3cc0), mload(0x4000), f_q) +mstore(20224, result) + } +mstore(0x4f20, mulmod(mload(0x4f00), mload(0x44c0), f_q)) +mstore(0x4f40, mulmod(sub(f_q, mload(0x4f20)), mload(0x45a0), f_q)) +mstore(0x4f60, mulmod(mload(0x49a0), mload(0x45a0), f_q)) +mstore(0x4f80, mulmod(mload(0x4ea0), mload(0x45a0), f_q)) +mstore(0x4fa0, mulmod(mload(0x4ec0), mload(0x45a0), f_q)) +mstore(0x4fc0, mulmod(mload(0x4ee0), mload(0x45a0), f_q)) +mstore(0x4fe0, addmod(mload(0x4e80), mload(0x4f40), f_q)) +{ + let result := mulmod(mload(0xc20), mload(0x4000), f_q) +mstore(20480, result) + } +mstore(0x5020, mulmod(mload(0x5000), mload(0x44c0), f_q)) +mstore(0x5040, mulmod(sub(f_q, mload(0x5020)), mload(0x45c0), f_q)) +mstore(0x5060, mulmod(mload(0x49a0), mload(0x45c0), f_q)) +mstore(0x5080, addmod(mload(0x4fe0), mload(0x5040), f_q)) +mstore(0x50a0, mulmod(mload(0x5080), mload(0x4620), f_q)) +mstore(0x50c0, mulmod(mload(0x4a20), mload(0x4620), f_q)) +mstore(0x50e0, mulmod(mload(0x4aa0), mload(0x4620), f_q)) +mstore(0x5100, mulmod(mload(0x4b40), mload(0x4620), f_q)) +mstore(0x5120, mulmod(mload(0x4be0), mload(0x4620), f_q)) +mstore(0x5140, mulmod(mload(0x4c80), mload(0x4620), f_q)) +mstore(0x5160, mulmod(mload(0x4d20), mload(0x4620), f_q)) +mstore(0x5180, mulmod(mload(0x4dc0), mload(0x4620), f_q)) +mstore(0x51a0, mulmod(mload(0x4e60), mload(0x4620), f_q)) +mstore(0x51c0, mulmod(mload(0x4f60), mload(0x4620), f_q)) +mstore(0x51e0, mulmod(mload(0x4f80), mload(0x4620), f_q)) +mstore(0x5200, mulmod(mload(0x4fa0), mload(0x4620), f_q)) +mstore(0x5220, mulmod(mload(0x4fc0), mload(0x4620), f_q)) +mstore(0x5240, mulmod(mload(0x5060), mload(0x4620), f_q)) +mstore(0x5260, addmod(mload(0x4980), mload(0x50a0), f_q)) +mstore(0x5280, mulmod(1, mload(0x3f40), f_q)) +mstore(0x52a0, mulmod(1, mload(0xea0), f_q)) +mstore(0x52c0, 0x0000000000000000000000000000000000000000000000000000000000000001) + mstore(0x52e0, 0x0000000000000000000000000000000000000000000000000000000000000002) +mstore(0x5300, mload(0x5260)) +success := and(eq(staticcall(gas(), 0x7, 0x52c0, 0x60, 0x52c0, 0x40), 1), success) +mstore(0x5320, mload(0x52c0)) + mstore(0x5340, mload(0x52e0)) +mstore(0x5360, mload(0x6c0)) + mstore(0x5380, mload(0x6e0)) +success := and(eq(staticcall(gas(), 0x6, 0x5320, 0x80, 0x5320, 0x40), 1), success) +mstore(0x53a0, mload(0x8a0)) + mstore(0x53c0, mload(0x8c0)) +mstore(0x53e0, mload(0x4840)) +success := and(eq(staticcall(gas(), 0x7, 0x53a0, 0x60, 0x53a0, 0x40), 1), success) +mstore(0x5400, mload(0x5320)) + mstore(0x5420, mload(0x5340)) +mstore(0x5440, mload(0x53a0)) + mstore(0x5460, mload(0x53c0)) +success := and(eq(staticcall(gas(), 0x6, 0x5400, 0x80, 0x5400, 0x40), 1), success) +mstore(0x5480, mload(0x8e0)) + mstore(0x54a0, mload(0x900)) +mstore(0x54c0, mload(0x4860)) +success := and(eq(staticcall(gas(), 0x7, 0x5480, 0x60, 0x5480, 0x40), 1), success) +mstore(0x54e0, mload(0x5400)) + mstore(0x5500, mload(0x5420)) +mstore(0x5520, mload(0x5480)) + mstore(0x5540, mload(0x54a0)) +success := and(eq(staticcall(gas(), 0x6, 0x54e0, 0x80, 0x54e0, 0x40), 1), success) +mstore(0x5560, mload(0x760)) + mstore(0x5580, mload(0x780)) +mstore(0x55a0, mload(0x4960)) +success := and(eq(staticcall(gas(), 0x7, 0x5560, 0x60, 0x5560, 0x40), 1), success) +mstore(0x55c0, mload(0x54e0)) + mstore(0x55e0, mload(0x5500)) +mstore(0x5600, mload(0x5560)) + mstore(0x5620, mload(0x5580)) +success := and(eq(staticcall(gas(), 0x6, 0x55c0, 0x80, 0x55c0, 0x40), 1), success) +mstore(0x5640, mload(0x7a0)) + mstore(0x5660, mload(0x7c0)) +mstore(0x5680, mload(0x50c0)) +success := and(eq(staticcall(gas(), 0x7, 0x5640, 0x60, 0x5640, 0x40), 1), success) +mstore(0x56a0, mload(0x55c0)) + mstore(0x56c0, mload(0x55e0)) +mstore(0x56e0, mload(0x5640)) + mstore(0x5700, mload(0x5660)) +success := and(eq(staticcall(gas(), 0x6, 0x56a0, 0x80, 0x56a0, 0x40), 1), success) +mstore(0x5720, 0x2628a2d33f3038b09f8da73da91ad1915ba8c8ef3dc1974d45a5095724cd7a7b) + mstore(0x5740, 0x17eb1dec35d4fbfee2604aea0adaa87b88b3f110dfff4281ce731b323345041d) +mstore(0x5760, mload(0x50e0)) +success := and(eq(staticcall(gas(), 0x7, 0x5720, 0x60, 0x5720, 0x40), 1), success) +mstore(0x5780, mload(0x56a0)) + mstore(0x57a0, mload(0x56c0)) +mstore(0x57c0, mload(0x5720)) + mstore(0x57e0, mload(0x5740)) +success := and(eq(staticcall(gas(), 0x6, 0x5780, 0x80, 0x5780, 0x40), 1), success) +mstore(0x5800, 0x14f9967686882b298d055eecfbeadcdaccdc11bb403d2595d9e009ea2fd4914b) + mstore(0x5820, 0x0f951ee95117ad514363247e2d918cbe010dc8a088ed831da215dd1667b233b4) +mstore(0x5840, mload(0x5100)) +success := and(eq(staticcall(gas(), 0x7, 0x5800, 0x60, 0x5800, 0x40), 1), success) +mstore(0x5860, mload(0x5780)) + mstore(0x5880, mload(0x57a0)) +mstore(0x58a0, mload(0x5800)) + mstore(0x58c0, mload(0x5820)) +success := and(eq(staticcall(gas(), 0x6, 0x5860, 0x80, 0x5860, 0x40), 1), success) +mstore(0x58e0, 0x24d50696646bd6191da549a2ee4e88244fb30cb015fe92d513d3b91aa0a20350) + mstore(0x5900, 0x10cd19fd1350ddb19ecba226a03b2b660d49ba420ac0c7817c3e4e52b65c146e) +mstore(0x5920, mload(0x5120)) +success := and(eq(staticcall(gas(), 0x7, 0x58e0, 0x60, 0x58e0, 0x40), 1), success) +mstore(0x5940, mload(0x5860)) + mstore(0x5960, mload(0x5880)) +mstore(0x5980, mload(0x58e0)) + mstore(0x59a0, mload(0x5900)) +success := and(eq(staticcall(gas(), 0x6, 0x5940, 0x80, 0x5940, 0x40), 1), success) +mstore(0x59c0, 0x0967970cf8ca52621a6f6d7499f9fff4ba207a3f1e1f110a4f3f6a7f97b1ae87) + mstore(0x59e0, 0x2b25ce8d060fe48b43e784320ba107dadbe66a2933e9f3cc0f34fd7cd7d68c2c) +mstore(0x5a00, mload(0x5140)) +success := and(eq(staticcall(gas(), 0x7, 0x59c0, 0x60, 0x59c0, 0x40), 1), success) +mstore(0x5a20, mload(0x5940)) + mstore(0x5a40, mload(0x5960)) +mstore(0x5a60, mload(0x59c0)) + mstore(0x5a80, mload(0x59e0)) +success := and(eq(staticcall(gas(), 0x6, 0x5a20, 0x80, 0x5a20, 0x40), 1), success) +mstore(0x5aa0, 0x2c72606b01e2c4e1d024f80212715216d977f8edd0c33ca78198e0d06094e61b) + mstore(0x5ac0, 0x0dc2a071dfeff0b4160413749b90206e3fb09992e44cbf3785e37adfe2bc7cc1) +mstore(0x5ae0, mload(0x5160)) +success := and(eq(staticcall(gas(), 0x7, 0x5aa0, 0x60, 0x5aa0, 0x40), 1), success) +mstore(0x5b00, mload(0x5a20)) + mstore(0x5b20, mload(0x5a40)) +mstore(0x5b40, mload(0x5aa0)) + mstore(0x5b60, mload(0x5ac0)) +success := and(eq(staticcall(gas(), 0x6, 0x5b00, 0x80, 0x5b00, 0x40), 1), success) +mstore(0x5b80, 0x2f0e188fe808bb945a2ffe3af51e982a43fc00a1632671ae35e71db3cf275491) + mstore(0x5ba0, 0x102f773a8356d921a48c78362fd1c66fd7523379750934159bb98f0411b05568) +mstore(0x5bc0, mload(0x5180)) +success := and(eq(staticcall(gas(), 0x7, 0x5b80, 0x60, 0x5b80, 0x40), 1), success) +mstore(0x5be0, mload(0x5b00)) + mstore(0x5c00, mload(0x5b20)) +mstore(0x5c20, mload(0x5b80)) + mstore(0x5c40, mload(0x5ba0)) +success := and(eq(staticcall(gas(), 0x6, 0x5be0, 0x80, 0x5be0, 0x40), 1), success) +mstore(0x5c60, 0x020c93807210e4433d8b939024e4936fe6fd5015122c8900abe2f1b4e69fb616) + mstore(0x5c80, 0x0b2b813d219baae5338d68589fdfca7bca4f7b03491ef6bc4f4316c6e555e576) +mstore(0x5ca0, mload(0x51a0)) +success := and(eq(staticcall(gas(), 0x7, 0x5c60, 0x60, 0x5c60, 0x40), 1), success) +mstore(0x5cc0, mload(0x5be0)) + mstore(0x5ce0, mload(0x5c00)) +mstore(0x5d00, mload(0x5c60)) + mstore(0x5d20, mload(0x5c80)) +success := and(eq(staticcall(gas(), 0x6, 0x5cc0, 0x80, 0x5cc0, 0x40), 1), success) +mstore(0x5d40, mload(0x9c0)) + mstore(0x5d60, mload(0x9e0)) +mstore(0x5d80, mload(0x51c0)) +success := and(eq(staticcall(gas(), 0x7, 0x5d40, 0x60, 0x5d40, 0x40), 1), success) +mstore(0x5da0, mload(0x5cc0)) + mstore(0x5dc0, mload(0x5ce0)) +mstore(0x5de0, mload(0x5d40)) + mstore(0x5e00, mload(0x5d60)) +success := and(eq(staticcall(gas(), 0x6, 0x5da0, 0x80, 0x5da0, 0x40), 1), success) +mstore(0x5e20, mload(0xa00)) + mstore(0x5e40, mload(0xa20)) +mstore(0x5e60, mload(0x51e0)) +success := and(eq(staticcall(gas(), 0x7, 0x5e20, 0x60, 0x5e20, 0x40), 1), success) +mstore(0x5e80, mload(0x5da0)) + mstore(0x5ea0, mload(0x5dc0)) +mstore(0x5ec0, mload(0x5e20)) + mstore(0x5ee0, mload(0x5e40)) +success := and(eq(staticcall(gas(), 0x6, 0x5e80, 0x80, 0x5e80, 0x40), 1), success) +mstore(0x5f00, mload(0xa40)) + mstore(0x5f20, mload(0xa60)) +mstore(0x5f40, mload(0x5200)) +success := and(eq(staticcall(gas(), 0x7, 0x5f00, 0x60, 0x5f00, 0x40), 1), success) +mstore(0x5f60, mload(0x5e80)) + mstore(0x5f80, mload(0x5ea0)) +mstore(0x5fa0, mload(0x5f00)) + mstore(0x5fc0, mload(0x5f20)) +success := and(eq(staticcall(gas(), 0x6, 0x5f60, 0x80, 0x5f60, 0x40), 1), success) +mstore(0x5fe0, mload(0xa80)) + mstore(0x6000, mload(0xaa0)) +mstore(0x6020, mload(0x5220)) +success := and(eq(staticcall(gas(), 0x7, 0x5fe0, 0x60, 0x5fe0, 0x40), 1), success) +mstore(0x6040, mload(0x5f60)) + mstore(0x6060, mload(0x5f80)) +mstore(0x6080, mload(0x5fe0)) + mstore(0x60a0, mload(0x6000)) +success := and(eq(staticcall(gas(), 0x6, 0x6040, 0x80, 0x6040, 0x40), 1), success) +mstore(0x60c0, mload(0x920)) + mstore(0x60e0, mload(0x940)) +mstore(0x6100, mload(0x5240)) +success := and(eq(staticcall(gas(), 0x7, 0x60c0, 0x60, 0x60c0, 0x40), 1), success) +mstore(0x6120, mload(0x6040)) + mstore(0x6140, mload(0x6060)) +mstore(0x6160, mload(0x60c0)) + mstore(0x6180, mload(0x60e0)) +success := and(eq(staticcall(gas(), 0x6, 0x6120, 0x80, 0x6120, 0x40), 1), success) +mstore(0x61a0, mload(0xe40)) + mstore(0x61c0, mload(0xe60)) +mstore(0x61e0, sub(f_q, mload(0x5280))) +success := and(eq(staticcall(gas(), 0x7, 0x61a0, 0x60, 0x61a0, 0x40), 1), success) +mstore(0x6200, mload(0x6120)) + mstore(0x6220, mload(0x6140)) +mstore(0x6240, mload(0x61a0)) + mstore(0x6260, mload(0x61c0)) +success := and(eq(staticcall(gas(), 0x6, 0x6200, 0x80, 0x6200, 0x40), 1), success) +mstore(0x6280, mload(0xee0)) + mstore(0x62a0, mload(0xf00)) +mstore(0x62c0, mload(0x52a0)) +success := and(eq(staticcall(gas(), 0x7, 0x6280, 0x60, 0x6280, 0x40), 1), success) +mstore(0x62e0, mload(0x6200)) + mstore(0x6300, mload(0x6220)) +mstore(0x6320, mload(0x6280)) + mstore(0x6340, mload(0x62a0)) +success := and(eq(staticcall(gas(), 0x6, 0x62e0, 0x80, 0x62e0, 0x40), 1), success) +mstore(0x6360, mload(0x62e0)) + mstore(0x6380, mload(0x6300)) +mstore(0x63a0, mload(0xee0)) + mstore(0x63c0, mload(0xf00)) +mstore(0x63e0, mload(0xf20)) + mstore(0x6400, mload(0xf40)) +mstore(0x6420, mload(0xf60)) + mstore(0x6440, mload(0xf80)) +mstore(0x6460, keccak256(0x6360, 256)) +mstore(25728, mod(mload(25696), f_q)) +mstore(0x64a0, mulmod(mload(0x6480), mload(0x6480), f_q)) +mstore(0x64c0, mulmod(1, mload(0x6480), f_q)) +mstore(0x64e0, mload(0x63e0)) + mstore(0x6500, mload(0x6400)) +mstore(0x6520, mload(0x64c0)) +success := and(eq(staticcall(gas(), 0x7, 0x64e0, 0x60, 0x64e0, 0x40), 1), success) +mstore(0x6540, mload(0x6360)) + mstore(0x6560, mload(0x6380)) +mstore(0x6580, mload(0x64e0)) + mstore(0x65a0, mload(0x6500)) +success := and(eq(staticcall(gas(), 0x6, 0x6540, 0x80, 0x6540, 0x40), 1), success) +mstore(0x65c0, mload(0x6420)) + mstore(0x65e0, mload(0x6440)) +mstore(0x6600, mload(0x64c0)) +success := and(eq(staticcall(gas(), 0x7, 0x65c0, 0x60, 0x65c0, 0x40), 1), success) +mstore(0x6620, mload(0x63a0)) + mstore(0x6640, mload(0x63c0)) +mstore(0x6660, mload(0x65c0)) + mstore(0x6680, mload(0x65e0)) +success := and(eq(staticcall(gas(), 0x6, 0x6620, 0x80, 0x6620, 0x40), 1), success) +mstore(0x66a0, mload(0x6540)) + mstore(0x66c0, mload(0x6560)) +mstore(0x66e0, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) + mstore(0x6700, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) + mstore(0x6720, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) + mstore(0x6740, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) +mstore(0x6760, mload(0x6620)) + mstore(0x6780, mload(0x6640)) +mstore(0x67a0, 0x138d5863615c12d3bd7d3fd007776d281a337f9d7f6dce23532100bb4bb5839d) + mstore(0x67c0, 0x0a3bb881671ee4e9238366e87f6598f0de356372ed3dc870766ec8ac005211e4) + mstore(0x67e0, 0x19c9d7d9c6e7ad2d9a0d5847ebdd2687c668939a202553ded2760d3eb8dbf559) + mstore(0x6800, 0x198adb441818c42721c88c532ed13a5da1ebb78b85574d0b7326d8e6f4c1e25a) +success := and(eq(staticcall(gas(), 0x8, 0x66a0, 0x180, 0x66a0, 0x20), 1), success) +success := and(eq(mload(0x66a0), 1), success) + + if not(success) { revert(0, 0) } + return(0, 0) + + } + } + } \ No newline at end of file diff --git a/axiom-eth/data/headers/shasums.txt b/axiom-eth/data/headers/shasums.txt new file mode 100644 index 000000000..f293722bd --- /dev/null +++ b/axiom-eth/data/headers/shasums.txt @@ -0,0 +1,21 @@ +// These are generated via `sha256sum` on Ubuntu. On macOS use `shasum -a 256`. + +4b454602fc23415a0e825498a1f9e451adc4cbe2aac9e7f824f6d41c6c1702d3 data/headers/mainnet_10_7.pk +41bb6cf9f4d1c4d7eb28d6329356ff5ae20638047fafe04ba05c79f1583898a9 data/headers/mainnet_10_7_final.pk +af9f558f2e5e2c903f6ee71c1f113ae97efdcd1894f1e919525dd72dd1368f9a data/headers/mainnet_10_7_for_evm_0.pk +1036cf4838e8cbf964371a310453012aad1616a8dbb81711e192cf3b158fbbaa data/headers/mainnet_10_7_for_evm_1.pk +87de3424dc6243f81f21cc8b3ca3d3a8b92bc4c1ac3daf4a2f5f8e770dac9bb2 data/headers/mainnet_11_7.pk +3021696fe800eaf1acc1dc367142f029adeca626f7b8cf765c7a53a73fd138cb data/headers/mainnet_12_7.pk +b48d30933a8afefe85f5eafe26d9a5a82a0722ad12a0314768ce744f4351ccf3 data/headers/mainnet_13_7.pk +dd2b7a81fa18ec638a288e289eb5e9453c101cecb478e8924d1bc0fad7c6095e data/headers/mainnet_14_7.pk +7b1d5957b070ca1b6e456cca61f129b856cb41ca5738684fc4b45302d8948705 data/headers/mainnet_15_7.pk +88f153a1750cfca2e8fb021e3bcc8441506c8764a58370f1afb6239b125462ae data/headers/mainnet_16_7.pk +1a6be85f70cd0c9651d61c756c9959a0951830329a1e3c78a3372bc130d23ebb data/headers/mainnet_17_7_final.pk +a2efd68fbb313d476b964f6f4ad918d3c3717e107fa668f937aa986c9143c346 data/headers/mainnet_17_7_for_evm_0.pk +fc80f3468ce0df0247077cd3d4520b4f9ba654e077f8b9de31883e365836e5dc data/headers/mainnet_17_7_for_evm_1.pk +f1fc7cb04fef1ce523affcb3e344b544bf40dce2443acc6b6914137ba6f4204b data/headers/mainnet_7.pk +648c02a64380208b2561d405c174559c95fa815cbc33e19892446f24624ae160 data/headers/mainnet_8_7.pk +f106d4bfbaa4673b21e0d906bc89dd67791dbc748ead0d4d63ec51b8eb7c4922 data/headers/mainnet_9_7.pk + +2f8c9420b1f79be6d7fe1a54b02dad2c2d492071e1082d22050a68cda4546a9b data/headers/mainnet_10_7_for_evm_1.yul +f6e0fd84055585bfce8662affe9982158d8523996881a79e5b5119723d5bbf62 data/headers/mainnet_17_7_for_evm_1.yul diff --git a/data/storage/mainnet_10_evm.yul b/axiom-eth/data/storage/mainnet_10_evm.yul similarity index 100% rename from data/storage/mainnet_10_evm.yul rename to axiom-eth/data/storage/mainnet_10_evm.yul diff --git a/data/storage/task.t.json b/axiom-eth/data/storage/task.t.json similarity index 100% rename from data/storage/task.t.json rename to axiom-eth/data/storage/task.t.json diff --git a/axiom-eth/data/tests/batch_query/final_2.yul b/axiom-eth/data/tests/batch_query/final_2.yul new file mode 100644 index 000000000..cdf4591b5 --- /dev/null +++ b/axiom-eth/data/tests/batch_query/final_2.yul @@ -0,0 +1,1790 @@ + + object "plonk_verifier" { + code { + function allocate(size) -> ptr { + ptr := mload(0x40) + if eq(ptr, 0) { ptr := 0x60 } + mstore(0x40, add(ptr, size)) + } + 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 := 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 + let f_q := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 + function validate_ec_point(x, y) -> valid:bool { + { + let x_lt_p:bool := lt(x, 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + let y_lt_p:bool := 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:bool := 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(0x1a0, mod(calldataload(0x180), f_q)) +mstore(0x1c0, mod(calldataload(0x1a0), f_q)) +mstore(0x1e0, mod(calldataload(0x1c0), f_q)) +mstore(0x200, mod(calldataload(0x1e0), f_q)) +mstore(0x220, mod(calldataload(0x200), f_q)) +mstore(0x240, mod(calldataload(0x220), f_q)) +mstore(0x260, mod(calldataload(0x240), f_q)) +mstore(0x280, mod(calldataload(0x260), f_q)) +mstore(0x2a0, mod(calldataload(0x280), f_q)) +mstore(0x2c0, mod(calldataload(0x2a0), f_q)) +mstore(0x2e0, mod(calldataload(0x2c0), f_q)) +mstore(0x300, mod(calldataload(0x2e0), f_q)) +mstore(0x320, mod(calldataload(0x300), f_q)) +mstore(0x0, 9990344841004121443508147239314748645763433289166582887839854113757608871028) + + { + let x := calldataload(0x320) + mstore(0x340, x) + let y := calldataload(0x340) + mstore(0x360, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x360) + mstore(0x380, x) + let y := calldataload(0x380) + mstore(0x3a0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x3a0) + mstore(0x3c0, x) + let y := calldataload(0x3c0) + mstore(0x3e0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x3e0) + mstore(0x400, x) + let y := calldataload(0x400) + mstore(0x420, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x420) + mstore(0x440, x) + let y := calldataload(0x440) + mstore(0x460, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x480, keccak256(0x0, 1152)) +{ + let hash := mload(0x480) + mstore(0x4a0, mod(hash, f_q)) + mstore(0x4c0, hash) + } + + { + let x := calldataload(0x460) + mstore(0x4e0, x) + let y := calldataload(0x480) + mstore(0x500, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x4a0) + mstore(0x520, x) + let y := calldataload(0x4c0) + mstore(0x540, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x560, keccak256(0x4c0, 160)) +{ + let hash := mload(0x560) + mstore(0x580, mod(hash, f_q)) + mstore(0x5a0, hash) + } +mstore8(1472, 1) +mstore(0x5c0, keccak256(0x5a0, 33)) +{ + let hash := mload(0x5c0) + mstore(0x5e0, mod(hash, f_q)) + mstore(0x600, hash) + } + + { + let x := calldataload(0x4e0) + mstore(0x620, x) + let y := calldataload(0x500) + mstore(0x640, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x520) + mstore(0x660, x) + let y := calldataload(0x540) + mstore(0x680, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x560) + mstore(0x6a0, x) + let y := calldataload(0x580) + mstore(0x6c0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x5a0) + mstore(0x6e0, x) + let y := calldataload(0x5c0) + mstore(0x700, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x5e0) + mstore(0x720, x) + let y := calldataload(0x600) + mstore(0x740, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x620) + mstore(0x760, x) + let y := calldataload(0x640) + mstore(0x780, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x7a0, keccak256(0x600, 416)) +{ + let hash := mload(0x7a0) + mstore(0x7c0, mod(hash, f_q)) + mstore(0x7e0, hash) + } + + { + let x := calldataload(0x660) + mstore(0x800, x) + let y := calldataload(0x680) + mstore(0x820, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x6a0) + mstore(0x840, x) + let y := calldataload(0x6c0) + mstore(0x860, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x6e0) + mstore(0x880, x) + let y := calldataload(0x700) + mstore(0x8a0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x8c0, keccak256(0x7e0, 224)) +{ + let hash := mload(0x8c0) + mstore(0x8e0, mod(hash, f_q)) + mstore(0x900, hash) + } +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, mod(calldataload(0x8c0), f_q)) +mstore(0xae0, mod(calldataload(0x8e0), f_q)) +mstore(0xb00, mod(calldataload(0x900), f_q)) +mstore(0xb20, mod(calldataload(0x920), f_q)) +mstore(0xb40, mod(calldataload(0x940), f_q)) +mstore(0xb60, mod(calldataload(0x960), f_q)) +mstore(0xb80, mod(calldataload(0x980), f_q)) +mstore(0xba0, mod(calldataload(0x9a0), f_q)) +mstore(0xbc0, mod(calldataload(0x9c0), f_q)) +mstore(0xbe0, mod(calldataload(0x9e0), f_q)) +mstore(0xc00, mod(calldataload(0xa00), f_q)) +mstore(0xc20, mod(calldataload(0xa20), f_q)) +mstore(0xc40, mod(calldataload(0xa40), f_q)) +mstore(0xc60, mod(calldataload(0xa60), f_q)) +mstore(0xc80, mod(calldataload(0xa80), f_q)) +mstore(0xca0, mod(calldataload(0xaa0), f_q)) +mstore(0xcc0, mod(calldataload(0xac0), f_q)) +mstore(0xce0, mod(calldataload(0xae0), f_q)) +mstore(0xd00, mod(calldataload(0xb00), f_q)) +mstore(0xd20, mod(calldataload(0xb20), f_q)) +mstore(0xd40, mod(calldataload(0xb40), f_q)) +mstore(0xd60, mod(calldataload(0xb60), f_q)) +mstore(0xd80, mod(calldataload(0xb80), f_q)) +mstore(0xda0, mod(calldataload(0xba0), f_q)) +mstore(0xdc0, mod(calldataload(0xbc0), f_q)) +mstore(0xde0, mod(calldataload(0xbe0), f_q)) +mstore(0xe00, mod(calldataload(0xc00), f_q)) +mstore(0xe20, mod(calldataload(0xc20), f_q)) +mstore(0xe40, mod(calldataload(0xc40), f_q)) +mstore(0xe60, mod(calldataload(0xc60), f_q)) +mstore(0xe80, mod(calldataload(0xc80), f_q)) +mstore(0xea0, mod(calldataload(0xca0), f_q)) +mstore(0xec0, mod(calldataload(0xcc0), f_q)) +mstore(0xee0, mod(calldataload(0xce0), f_q)) +mstore(0xf00, keccak256(0x900, 1536)) +{ + let hash := mload(0xf00) + mstore(0xf20, mod(hash, f_q)) + mstore(0xf40, hash) + } +mstore8(3936, 1) +mstore(0xf60, keccak256(0xf40, 33)) +{ + let hash := mload(0xf60) + mstore(0xf80, mod(hash, f_q)) + mstore(0xfa0, hash) + } + + { + let x := calldataload(0xd00) + mstore(0xfc0, x) + let y := calldataload(0xd20) + mstore(0xfe0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x1000, keccak256(0xfa0, 96)) +{ + let hash := mload(0x1000) + mstore(0x1020, mod(hash, f_q)) + mstore(0x1040, hash) + } + + { + let x := calldataload(0xd40) + mstore(0x1060, x) + let y := calldataload(0xd60) + mstore(0x1080, 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(4256, x) +let y := mload(0x80) +y := add(y, shl(88, mload(0xa0))) +y := add(y, shl(176, mload(0xc0))) +mstore(4288, 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(4320, x) +let y := mload(0x140) +y := add(y, shl(88, mload(0x160))) +y := add(y, shl(176, mload(0x180))) +mstore(4352, y) + + success := and(validate_ec_point(x, y), success) + } +mstore(0x1120, mulmod(mload(0x8e0), mload(0x8e0), f_q)) +mstore(0x1140, mulmod(mload(0x1120), mload(0x1120), f_q)) +mstore(0x1160, mulmod(mload(0x1140), mload(0x1140), f_q)) +mstore(0x1180, mulmod(mload(0x1160), mload(0x1160), f_q)) +mstore(0x11a0, mulmod(mload(0x1180), mload(0x1180), f_q)) +mstore(0x11c0, mulmod(mload(0x11a0), mload(0x11a0), f_q)) +mstore(0x11e0, mulmod(mload(0x11c0), mload(0x11c0), f_q)) +mstore(0x1200, mulmod(mload(0x11e0), mload(0x11e0), f_q)) +mstore(0x1220, mulmod(mload(0x1200), mload(0x1200), f_q)) +mstore(0x1240, mulmod(mload(0x1220), mload(0x1220), f_q)) +mstore(0x1260, mulmod(mload(0x1240), mload(0x1240), f_q)) +mstore(0x1280, mulmod(mload(0x1260), mload(0x1260), f_q)) +mstore(0x12a0, mulmod(mload(0x1280), mload(0x1280), f_q)) +mstore(0x12c0, mulmod(mload(0x12a0), mload(0x12a0), f_q)) +mstore(0x12e0, mulmod(mload(0x12c0), mload(0x12c0), f_q)) +mstore(0x1300, mulmod(mload(0x12e0), mload(0x12e0), f_q)) +mstore(0x1320, mulmod(mload(0x1300), mload(0x1300), f_q)) +mstore(0x1340, mulmod(mload(0x1320), mload(0x1320), f_q)) +mstore(0x1360, mulmod(mload(0x1340), mload(0x1340), f_q)) +mstore(0x1380, mulmod(mload(0x1360), mload(0x1360), f_q)) +mstore(0x13a0, mulmod(mload(0x1380), mload(0x1380), f_q)) +mstore(0x13c0, mulmod(mload(0x13a0), mload(0x13a0), f_q)) +mstore(0x13e0, addmod(mload(0x13c0), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q)) +mstore(0x1400, mulmod(mload(0x13e0), 21888237653275510688422624196183639687472264873923820041627027729598873448513, f_q)) +mstore(0x1420, mulmod(mload(0x1400), 13225785879531581993054172815365636627224369411478295502904397545373139154045, f_q)) +mstore(0x1440, addmod(mload(0x8e0), 8662456992307693229192232929891638461323994988937738840793806641202669341572, f_q)) +mstore(0x1460, mulmod(mload(0x1400), 10939663269433627367777756708678102241564365262857670666700619874077960926249, f_q)) +mstore(0x1480, addmod(mload(0x8e0), 10948579602405647854468649036579172846983999137558363676997584312497847569368, f_q)) +mstore(0x14a0, mulmod(mload(0x1400), 11016257578652593686382655500910603527869149377564754001549454008164059876499, f_q)) +mstore(0x14c0, addmod(mload(0x8e0), 10871985293186681535863750244346671560679215022851280342148750178411748619118, f_q)) +mstore(0x14e0, mulmod(mload(0x1400), 15402826414547299628414612080036060696555554914079673875872749760617770134879, f_q)) +mstore(0x1500, addmod(mload(0x8e0), 6485416457291975593831793665221214391992809486336360467825454425958038360738, f_q)) +mstore(0x1520, mulmod(mload(0x1400), 21710372849001950800533397158415938114909991150039389063546734567764856596059, f_q)) +mstore(0x1540, addmod(mload(0x8e0), 177870022837324421713008586841336973638373250376645280151469618810951899558, f_q)) +mstore(0x1560, mulmod(mload(0x1400), 2785514556381676080176937710880804108647911392478702105860685610379369825016, f_q)) +mstore(0x1580, addmod(mload(0x8e0), 19102728315457599142069468034376470979900453007937332237837518576196438670601, f_q)) +mstore(0x15a0, mulmod(mload(0x1400), 8734126352828345679573237859165904705806588461301144420590422589042130041188, f_q)) +mstore(0x15c0, addmod(mload(0x8e0), 13154116519010929542673167886091370382741775939114889923107781597533678454429, f_q)) +mstore(0x15e0, mulmod(mload(0x1400), 1, f_q)) +mstore(0x1600, addmod(mload(0x8e0), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q)) +mstore(0x1620, mulmod(mload(0x1400), 11211301017135681023579411905410872569206244553457844956874280139879520583390, f_q)) +mstore(0x1640, addmod(mload(0x8e0), 10676941854703594198666993839846402519342119846958189386823924046696287912227, f_q)) +mstore(0x1660, mulmod(mload(0x1400), 1426404432721484388505361748317961535523355871255605456897797744433766488507, f_q)) +mstore(0x1680, addmod(mload(0x8e0), 20461838439117790833741043996939313553025008529160428886800406442142042007110, f_q)) +mstore(0x16a0, mulmod(mload(0x1400), 12619617507853212586156872920672483948819476989779550311307282715684870266992, f_q)) +mstore(0x16c0, addmod(mload(0x8e0), 9268625363986062636089532824584791139728887410636484032390921470890938228625, f_q)) +mstore(0x16e0, mulmod(mload(0x1400), 19032961837237948602743626455740240236231119053033140765040043513661803148152, f_q)) +mstore(0x1700, addmod(mload(0x8e0), 2855281034601326619502779289517034852317245347382893578658160672914005347465, f_q)) +mstore(0x1720, mulmod(mload(0x1400), 915149353520972163646494413843788069594022902357002628455555785223409501882, f_q)) +mstore(0x1740, addmod(mload(0x8e0), 20973093518318303058599911331413487018954341498059031715242648401352398993735, f_q)) +mstore(0x1760, mulmod(mload(0x1400), 3766081621734395783232337525162072736827576297943013392955872170138036189193, f_q)) +mstore(0x1780, addmod(mload(0x8e0), 18122161250104879439014068220095202351720788102473020950742332016437772306424, f_q)) +mstore(0x17a0, mulmod(mload(0x1400), 4245441013247250116003069945606352967193023389718465410501109428393342802981, f_q)) +mstore(0x17c0, addmod(mload(0x8e0), 17642801858592025106243335799650922121355341010697568933197094758182465692636, f_q)) +mstore(0x17e0, mulmod(mload(0x1400), 5854133144571823792863860130267644613802765696134002830362054821530146160770, f_q)) +mstore(0x1800, addmod(mload(0x8e0), 16034109727267451429382545614989630474745598704282031513336149365045662334847, f_q)) +mstore(0x1820, mulmod(mload(0x1400), 5980488956150442207659150513163747165544364597008566989111579977672498964212, f_q)) +mstore(0x1840, addmod(mload(0x8e0), 15907753915688833014587255232093527923003999803407467354586624208903309531405, f_q)) +mstore(0x1860, mulmod(mload(0x1400), 14557038802599140430182096396825290815503940951075961210638273254419942783582, f_q)) +mstore(0x1880, addmod(mload(0x8e0), 7331204069240134792064309348431984273044423449340073133059930932155865712035, f_q)) +mstore(0x18a0, mulmod(mload(0x1400), 13553911191894110065493137367144919847521088405945523452288398666974237857208, f_q)) +mstore(0x18c0, addmod(mload(0x8e0), 8334331679945165156753268378112355241027275994470510891409805519601570638409, f_q)) +mstore(0x18e0, mulmod(mload(0x1400), 9697063347556872083384215826199993067635178715531258559890418744774301211662, f_q)) +mstore(0x1900, addmod(mload(0x8e0), 12191179524282403138862189919057282020913185684884775783807785441801507283955, f_q)) +mstore(0x1920, mulmod(mload(0x1400), 10807735674816066981985242612061336605021639643453679977988966079770672437131, f_q)) +mstore(0x1940, addmod(mload(0x8e0), 11080507197023208240261163133195938483526724756962354365709238106805136058486, f_q)) +mstore(0x1960, mulmod(mload(0x1400), 12459868075641381822485233712013080087763946065665469821362892189399541605692, f_q)) +mstore(0x1980, addmod(mload(0x8e0), 9428374796197893399761172033244195000784418334750564522335311997176266889925, f_q)) +mstore(0x19a0, mulmod(mload(0x1400), 16038300751658239075779628684257016433412502747804121525056508685985277092575, f_q)) +mstore(0x19c0, addmod(mload(0x8e0), 5849942120181036146466777061000258655135861652611912818641695500590531403042, f_q)) +mstore(0x19e0, mulmod(mload(0x1400), 6955697244493336113861667751840378876927906302623587437721024018233754910398, f_q)) +mstore(0x1a00, addmod(mload(0x8e0), 14932545627345939108384737993416896211620458097792446905977180168342053585219, f_q)) +mstore(0x1a20, mulmod(mload(0x1400), 13498745591877810872211159461644682954739332524336278910448604883789771736885, f_q)) +mstore(0x1a40, addmod(mload(0x8e0), 8389497279961464350035246283612592133809031876079755433249599302786036758732, f_q)) +mstore(0x1a60, mulmod(mload(0x1400), 20345677989844117909528750049476969581182118546166966482506114734614108237981, f_q)) +mstore(0x1a80, addmod(mload(0x8e0), 1542564881995157312717655695780305507366245854249067861192089451961700257636, f_q)) +mstore(0x1aa0, mulmod(mload(0x1400), 790608022292213379425324383664216541739009722347092850716054055768832299157, f_q)) +mstore(0x1ac0, addmod(mload(0x8e0), 21097634849547061842821081361593058546809354678068941492982150130806976196460, f_q)) +mstore(0x1ae0, mulmod(mload(0x1400), 5289443209903185443361862148540090689648485914368835830972895623576469023722, f_q)) +mstore(0x1b00, addmod(mload(0x8e0), 16598799661936089778884543596717184398899878486047198512725308562999339471895, f_q)) +mstore(0x1b20, mulmod(mload(0x1400), 15161189183906287273290738379431332336600234154579306802151507052820126345529, f_q)) +mstore(0x1b40, addmod(mload(0x8e0), 6727053687932987948955667365825942751948130245836727541546697133755682150088, f_q)) +mstore(0x1b60, mulmod(mload(0x1400), 557567375339945239933617516585967620814823575807691402619711360028043331811, f_q)) +mstore(0x1b80, addmod(mload(0x8e0), 21330675496499329982312788228671307467733540824608342941078492826547765163806, f_q)) +mstore(0x1ba0, mulmod(mload(0x1400), 16611719114775828483319365659907682366622074960672212059891361227499450055959, f_q)) +mstore(0x1bc0, addmod(mload(0x8e0), 5276523757063446738927040085349592721926289439743822283806842959076358439658, f_q)) +mstore(0x1be0, mulmod(mload(0x1400), 4509404676247677387317362072810231899718070082381452255950861037254608304934, f_q)) +mstore(0x1c00, addmod(mload(0x8e0), 17378838195591597834929043672447043188830294318034582087747343149321200190683, f_q)) +{ + let prod := mload(0x1440) + + prod := mulmod(mload(0x1480), prod, f_q) + mstore(0x1c20, prod) + + prod := mulmod(mload(0x14c0), prod, f_q) + mstore(0x1c40, prod) + + prod := mulmod(mload(0x1500), prod, f_q) + mstore(0x1c60, prod) + + prod := mulmod(mload(0x1540), prod, f_q) + mstore(0x1c80, prod) + + prod := mulmod(mload(0x1580), prod, f_q) + mstore(0x1ca0, prod) + + prod := mulmod(mload(0x15c0), prod, f_q) + mstore(0x1cc0, prod) + + prod := mulmod(mload(0x1600), prod, f_q) + mstore(0x1ce0, prod) + + prod := mulmod(mload(0x1640), prod, f_q) + mstore(0x1d00, prod) + + prod := mulmod(mload(0x1680), prod, f_q) + mstore(0x1d20, prod) + + prod := mulmod(mload(0x16c0), prod, f_q) + mstore(0x1d40, prod) + + prod := mulmod(mload(0x1700), prod, f_q) + mstore(0x1d60, prod) + + prod := mulmod(mload(0x1740), prod, f_q) + mstore(0x1d80, prod) + + prod := mulmod(mload(0x1780), prod, f_q) + mstore(0x1da0, prod) + + prod := mulmod(mload(0x17c0), prod, f_q) + mstore(0x1dc0, prod) + + prod := mulmod(mload(0x1800), prod, f_q) + mstore(0x1de0, prod) + + prod := mulmod(mload(0x1840), prod, f_q) + mstore(0x1e00, prod) + + prod := mulmod(mload(0x1880), prod, f_q) + mstore(0x1e20, prod) + + prod := mulmod(mload(0x18c0), prod, f_q) + mstore(0x1e40, prod) + + prod := mulmod(mload(0x1900), prod, f_q) + mstore(0x1e60, prod) + + prod := mulmod(mload(0x1940), prod, f_q) + mstore(0x1e80, prod) + + prod := mulmod(mload(0x1980), prod, f_q) + mstore(0x1ea0, prod) + + prod := mulmod(mload(0x19c0), prod, f_q) + mstore(0x1ec0, prod) + + prod := mulmod(mload(0x1a00), prod, f_q) + mstore(0x1ee0, prod) + + prod := mulmod(mload(0x1a40), prod, f_q) + mstore(0x1f00, prod) + + prod := mulmod(mload(0x1a80), prod, f_q) + mstore(0x1f20, prod) + + prod := mulmod(mload(0x1ac0), prod, f_q) + mstore(0x1f40, prod) + + prod := mulmod(mload(0x1b00), prod, f_q) + mstore(0x1f60, prod) + + prod := mulmod(mload(0x1b40), prod, f_q) + mstore(0x1f80, prod) + + prod := mulmod(mload(0x1b80), prod, f_q) + mstore(0x1fa0, prod) + + prod := mulmod(mload(0x1bc0), prod, f_q) + mstore(0x1fc0, prod) + + prod := mulmod(mload(0x1c00), prod, f_q) + mstore(0x1fe0, prod) + + prod := mulmod(mload(0x13e0), prod, f_q) + mstore(0x2000, prod) + + } +mstore(0x2040, 32) +mstore(0x2060, 32) +mstore(0x2080, 32) +mstore(0x20a0, mload(0x2000)) +mstore(0x20c0, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x20e0, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x2040, 0xc0, 0x2020, 0x20), 1), success) +{ + + let inv := mload(0x2020) + let v + + v := mload(0x13e0) + mstore(5088, mulmod(mload(0x1fe0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1c00) + mstore(7168, mulmod(mload(0x1fc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1bc0) + mstore(7104, mulmod(mload(0x1fa0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1b80) + mstore(7040, mulmod(mload(0x1f80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1b40) + mstore(6976, mulmod(mload(0x1f60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1b00) + mstore(6912, mulmod(mload(0x1f40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1ac0) + mstore(6848, mulmod(mload(0x1f20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1a80) + mstore(6784, mulmod(mload(0x1f00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1a40) + mstore(6720, mulmod(mload(0x1ee0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1a00) + mstore(6656, mulmod(mload(0x1ec0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x19c0) + mstore(6592, mulmod(mload(0x1ea0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1980) + mstore(6528, mulmod(mload(0x1e80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1940) + mstore(6464, mulmod(mload(0x1e60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1900) + mstore(6400, mulmod(mload(0x1e40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x18c0) + mstore(6336, mulmod(mload(0x1e20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1880) + mstore(6272, mulmod(mload(0x1e00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1840) + mstore(6208, mulmod(mload(0x1de0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1800) + mstore(6144, mulmod(mload(0x1dc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x17c0) + mstore(6080, mulmod(mload(0x1da0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1780) + mstore(6016, mulmod(mload(0x1d80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1740) + mstore(5952, mulmod(mload(0x1d60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1700) + mstore(5888, mulmod(mload(0x1d40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x16c0) + mstore(5824, mulmod(mload(0x1d20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1680) + mstore(5760, mulmod(mload(0x1d00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1640) + mstore(5696, mulmod(mload(0x1ce0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1600) + mstore(5632, mulmod(mload(0x1cc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x15c0) + mstore(5568, mulmod(mload(0x1ca0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1580) + mstore(5504, mulmod(mload(0x1c80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1540) + mstore(5440, mulmod(mload(0x1c60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1500) + mstore(5376, mulmod(mload(0x1c40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x14c0) + mstore(5312, mulmod(mload(0x1c20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1480) + mstore(5248, mulmod(mload(0x1440), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x1440, inv) + + } +mstore(0x2100, mulmod(mload(0x1420), mload(0x1440), f_q)) +mstore(0x2120, mulmod(mload(0x1460), mload(0x1480), f_q)) +mstore(0x2140, mulmod(mload(0x14a0), mload(0x14c0), f_q)) +mstore(0x2160, mulmod(mload(0x14e0), mload(0x1500), f_q)) +mstore(0x2180, mulmod(mload(0x1520), mload(0x1540), f_q)) +mstore(0x21a0, mulmod(mload(0x1560), mload(0x1580), f_q)) +mstore(0x21c0, mulmod(mload(0x15a0), mload(0x15c0), f_q)) +mstore(0x21e0, mulmod(mload(0x15e0), mload(0x1600), f_q)) +mstore(0x2200, mulmod(mload(0x1620), mload(0x1640), f_q)) +mstore(0x2220, mulmod(mload(0x1660), mload(0x1680), f_q)) +mstore(0x2240, mulmod(mload(0x16a0), mload(0x16c0), f_q)) +mstore(0x2260, mulmod(mload(0x16e0), mload(0x1700), f_q)) +mstore(0x2280, mulmod(mload(0x1720), mload(0x1740), f_q)) +mstore(0x22a0, mulmod(mload(0x1760), mload(0x1780), f_q)) +mstore(0x22c0, mulmod(mload(0x17a0), mload(0x17c0), f_q)) +mstore(0x22e0, mulmod(mload(0x17e0), mload(0x1800), f_q)) +mstore(0x2300, mulmod(mload(0x1820), mload(0x1840), f_q)) +mstore(0x2320, mulmod(mload(0x1860), mload(0x1880), f_q)) +mstore(0x2340, mulmod(mload(0x18a0), mload(0x18c0), f_q)) +mstore(0x2360, mulmod(mload(0x18e0), mload(0x1900), f_q)) +mstore(0x2380, mulmod(mload(0x1920), mload(0x1940), f_q)) +mstore(0x23a0, mulmod(mload(0x1960), mload(0x1980), f_q)) +mstore(0x23c0, mulmod(mload(0x19a0), mload(0x19c0), f_q)) +mstore(0x23e0, mulmod(mload(0x19e0), mload(0x1a00), f_q)) +mstore(0x2400, mulmod(mload(0x1a20), mload(0x1a40), f_q)) +mstore(0x2420, mulmod(mload(0x1a60), mload(0x1a80), f_q)) +mstore(0x2440, mulmod(mload(0x1aa0), mload(0x1ac0), f_q)) +mstore(0x2460, mulmod(mload(0x1ae0), mload(0x1b00), f_q)) +mstore(0x2480, mulmod(mload(0x1b20), mload(0x1b40), f_q)) +mstore(0x24a0, mulmod(mload(0x1b60), mload(0x1b80), f_q)) +mstore(0x24c0, mulmod(mload(0x1ba0), mload(0x1bc0), f_q)) +mstore(0x24e0, mulmod(mload(0x1be0), mload(0x1c00), f_q)) +{ + let result := mulmod(mload(0x21e0), mload(0x20), f_q) +result := addmod(mulmod(mload(0x2200), mload(0x40), f_q), result, f_q) +result := addmod(mulmod(mload(0x2220), mload(0x60), f_q), result, f_q) +result := addmod(mulmod(mload(0x2240), mload(0x80), f_q), result, f_q) +result := addmod(mulmod(mload(0x2260), mload(0xa0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2280), mload(0xc0), f_q), result, f_q) +result := addmod(mulmod(mload(0x22a0), mload(0xe0), f_q), result, f_q) +result := addmod(mulmod(mload(0x22c0), mload(0x100), f_q), result, f_q) +result := addmod(mulmod(mload(0x22e0), mload(0x120), f_q), result, f_q) +result := addmod(mulmod(mload(0x2300), mload(0x140), f_q), result, f_q) +result := addmod(mulmod(mload(0x2320), mload(0x160), f_q), result, f_q) +result := addmod(mulmod(mload(0x2340), mload(0x180), f_q), result, f_q) +result := addmod(mulmod(mload(0x2360), mload(0x1a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2380), mload(0x1c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x23a0), mload(0x1e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x23c0), mload(0x200), f_q), result, f_q) +result := addmod(mulmod(mload(0x23e0), mload(0x220), f_q), result, f_q) +result := addmod(mulmod(mload(0x2400), mload(0x240), f_q), result, f_q) +result := addmod(mulmod(mload(0x2420), mload(0x260), f_q), result, f_q) +result := addmod(mulmod(mload(0x2440), mload(0x280), f_q), result, f_q) +result := addmod(mulmod(mload(0x2460), mload(0x2a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2480), mload(0x2c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x24a0), mload(0x2e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x24c0), mload(0x300), f_q), result, f_q) +result := addmod(mulmod(mload(0x24e0), mload(0x320), f_q), result, f_q) +mstore(9472, result) + } +mstore(0x2520, mulmod(mload(0x960), mload(0x940), f_q)) +mstore(0x2540, addmod(mload(0x920), mload(0x2520), f_q)) +mstore(0x2560, addmod(mload(0x2540), sub(f_q, mload(0x980)), f_q)) +mstore(0x2580, mulmod(mload(0x2560), mload(0xb80), f_q)) +mstore(0x25a0, mulmod(mload(0x7c0), mload(0x2580), f_q)) +mstore(0x25c0, mulmod(mload(0x9e0), mload(0x9c0), f_q)) +mstore(0x25e0, addmod(mload(0x9a0), mload(0x25c0), f_q)) +mstore(0x2600, addmod(mload(0x25e0), sub(f_q, mload(0xa00)), f_q)) +mstore(0x2620, mulmod(mload(0x2600), mload(0xba0), f_q)) +mstore(0x2640, addmod(mload(0x25a0), mload(0x2620), f_q)) +mstore(0x2660, mulmod(mload(0x7c0), mload(0x2640), f_q)) +mstore(0x2680, mulmod(mload(0xa60), mload(0xa40), f_q)) +mstore(0x26a0, addmod(mload(0xa20), mload(0x2680), f_q)) +mstore(0x26c0, addmod(mload(0x26a0), sub(f_q, mload(0xa80)), f_q)) +mstore(0x26e0, mulmod(mload(0x26c0), mload(0xbc0), f_q)) +mstore(0x2700, addmod(mload(0x2660), mload(0x26e0), f_q)) +mstore(0x2720, mulmod(mload(0x7c0), mload(0x2700), f_q)) +mstore(0x2740, mulmod(mload(0xae0), mload(0xac0), f_q)) +mstore(0x2760, addmod(mload(0xaa0), mload(0x2740), f_q)) +mstore(0x2780, addmod(mload(0x2760), sub(f_q, mload(0xb00)), f_q)) +mstore(0x27a0, mulmod(mload(0x2780), mload(0xbe0), f_q)) +mstore(0x27c0, addmod(mload(0x2720), mload(0x27a0), f_q)) +mstore(0x27e0, mulmod(mload(0x7c0), mload(0x27c0), f_q)) +mstore(0x2800, addmod(1, sub(f_q, mload(0xd00)), f_q)) +mstore(0x2820, mulmod(mload(0x2800), mload(0x21e0), f_q)) +mstore(0x2840, addmod(mload(0x27e0), mload(0x2820), f_q)) +mstore(0x2860, mulmod(mload(0x7c0), mload(0x2840), f_q)) +mstore(0x2880, mulmod(mload(0xe20), mload(0xe20), f_q)) +mstore(0x28a0, addmod(mload(0x2880), sub(f_q, mload(0xe20)), f_q)) +mstore(0x28c0, mulmod(mload(0x28a0), mload(0x2100), f_q)) +mstore(0x28e0, addmod(mload(0x2860), mload(0x28c0), f_q)) +mstore(0x2900, mulmod(mload(0x7c0), mload(0x28e0), f_q)) +mstore(0x2920, addmod(mload(0xd60), sub(f_q, mload(0xd40)), f_q)) +mstore(0x2940, mulmod(mload(0x2920), mload(0x21e0), f_q)) +mstore(0x2960, addmod(mload(0x2900), mload(0x2940), f_q)) +mstore(0x2980, mulmod(mload(0x7c0), mload(0x2960), f_q)) +mstore(0x29a0, addmod(mload(0xdc0), sub(f_q, mload(0xda0)), f_q)) +mstore(0x29c0, mulmod(mload(0x29a0), mload(0x21e0), f_q)) +mstore(0x29e0, addmod(mload(0x2980), mload(0x29c0), f_q)) +mstore(0x2a00, mulmod(mload(0x7c0), mload(0x29e0), f_q)) +mstore(0x2a20, addmod(mload(0xe20), sub(f_q, mload(0xe00)), f_q)) +mstore(0x2a40, mulmod(mload(0x2a20), mload(0x21e0), f_q)) +mstore(0x2a60, addmod(mload(0x2a00), mload(0x2a40), f_q)) +mstore(0x2a80, mulmod(mload(0x7c0), mload(0x2a60), f_q)) +mstore(0x2aa0, addmod(1, sub(f_q, mload(0x2100)), f_q)) +mstore(0x2ac0, addmod(mload(0x2120), mload(0x2140), f_q)) +mstore(0x2ae0, addmod(mload(0x2ac0), mload(0x2160), f_q)) +mstore(0x2b00, addmod(mload(0x2ae0), mload(0x2180), f_q)) +mstore(0x2b20, addmod(mload(0x2b00), mload(0x21a0), f_q)) +mstore(0x2b40, addmod(mload(0x2b20), mload(0x21c0), f_q)) +mstore(0x2b60, addmod(mload(0x2aa0), sub(f_q, mload(0x2b40)), f_q)) +mstore(0x2b80, mulmod(mload(0xc20), mload(0x580), f_q)) +mstore(0x2ba0, addmod(mload(0xb40), mload(0x2b80), f_q)) +mstore(0x2bc0, addmod(mload(0x2ba0), mload(0x5e0), f_q)) +mstore(0x2be0, mulmod(mload(0xc40), mload(0x580), f_q)) +mstore(0x2c00, addmod(mload(0x920), mload(0x2be0), f_q)) +mstore(0x2c20, addmod(mload(0x2c00), mload(0x5e0), f_q)) +mstore(0x2c40, mulmod(mload(0x2c20), mload(0x2bc0), f_q)) +mstore(0x2c60, mulmod(mload(0x2c40), mload(0xd20), f_q)) +mstore(0x2c80, mulmod(1, mload(0x580), f_q)) +mstore(0x2ca0, mulmod(mload(0x8e0), mload(0x2c80), f_q)) +mstore(0x2cc0, addmod(mload(0xb40), mload(0x2ca0), f_q)) +mstore(0x2ce0, addmod(mload(0x2cc0), mload(0x5e0), f_q)) +mstore(0x2d00, mulmod(4131629893567559867359510883348571134090853742863529169391034518566172092834, mload(0x580), f_q)) +mstore(0x2d20, mulmod(mload(0x8e0), mload(0x2d00), f_q)) +mstore(0x2d40, addmod(mload(0x920), mload(0x2d20), f_q)) +mstore(0x2d60, addmod(mload(0x2d40), mload(0x5e0), f_q)) +mstore(0x2d80, mulmod(mload(0x2d60), mload(0x2ce0), f_q)) +mstore(0x2da0, mulmod(mload(0x2d80), mload(0xd00), f_q)) +mstore(0x2dc0, addmod(mload(0x2c60), sub(f_q, mload(0x2da0)), f_q)) +mstore(0x2de0, mulmod(mload(0x2dc0), mload(0x2b60), f_q)) +mstore(0x2e00, addmod(mload(0x2a80), mload(0x2de0), f_q)) +mstore(0x2e20, mulmod(mload(0x7c0), mload(0x2e00), f_q)) +mstore(0x2e40, mulmod(mload(0xc60), mload(0x580), f_q)) +mstore(0x2e60, addmod(mload(0x9a0), mload(0x2e40), f_q)) +mstore(0x2e80, addmod(mload(0x2e60), mload(0x5e0), f_q)) +mstore(0x2ea0, mulmod(mload(0xc80), mload(0x580), f_q)) +mstore(0x2ec0, addmod(mload(0xa20), mload(0x2ea0), f_q)) +mstore(0x2ee0, addmod(mload(0x2ec0), mload(0x5e0), f_q)) +mstore(0x2f00, mulmod(mload(0x2ee0), mload(0x2e80), f_q)) +mstore(0x2f20, mulmod(mload(0x2f00), mload(0xd80), f_q)) +mstore(0x2f40, mulmod(8910878055287538404433155982483128285667088683464058436815641868457422632747, mload(0x580), f_q)) +mstore(0x2f60, mulmod(mload(0x8e0), mload(0x2f40), f_q)) +mstore(0x2f80, addmod(mload(0x9a0), mload(0x2f60), f_q)) +mstore(0x2fa0, addmod(mload(0x2f80), mload(0x5e0), f_q)) +mstore(0x2fc0, mulmod(11166246659983828508719468090013646171463329086121580628794302409516816350802, mload(0x580), f_q)) +mstore(0x2fe0, mulmod(mload(0x8e0), mload(0x2fc0), f_q)) +mstore(0x3000, addmod(mload(0xa20), mload(0x2fe0), f_q)) +mstore(0x3020, addmod(mload(0x3000), mload(0x5e0), f_q)) +mstore(0x3040, mulmod(mload(0x3020), mload(0x2fa0), f_q)) +mstore(0x3060, mulmod(mload(0x3040), mload(0xd60), f_q)) +mstore(0x3080, addmod(mload(0x2f20), sub(f_q, mload(0x3060)), f_q)) +mstore(0x30a0, mulmod(mload(0x3080), mload(0x2b60), f_q)) +mstore(0x30c0, addmod(mload(0x2e20), mload(0x30a0), f_q)) +mstore(0x30e0, mulmod(mload(0x7c0), mload(0x30c0), f_q)) +mstore(0x3100, mulmod(mload(0xca0), mload(0x580), f_q)) +mstore(0x3120, addmod(mload(0xaa0), mload(0x3100), f_q)) +mstore(0x3140, addmod(mload(0x3120), mload(0x5e0), f_q)) +mstore(0x3160, mulmod(mload(0xcc0), mload(0x580), f_q)) +mstore(0x3180, addmod(mload(0xb20), mload(0x3160), f_q)) +mstore(0x31a0, addmod(mload(0x3180), mload(0x5e0), f_q)) +mstore(0x31c0, mulmod(mload(0x31a0), mload(0x3140), f_q)) +mstore(0x31e0, mulmod(mload(0x31c0), mload(0xde0), f_q)) +mstore(0x3200, mulmod(284840088355319032285349970403338060113257071685626700086398481893096618818, mload(0x580), f_q)) +mstore(0x3220, mulmod(mload(0x8e0), mload(0x3200), f_q)) +mstore(0x3240, addmod(mload(0xaa0), mload(0x3220), f_q)) +mstore(0x3260, addmod(mload(0x3240), mload(0x5e0), f_q)) +mstore(0x3280, mulmod(21134065618345176623193549882539580312263652408302468683943992798037078993309, mload(0x580), f_q)) +mstore(0x32a0, mulmod(mload(0x8e0), mload(0x3280), f_q)) +mstore(0x32c0, addmod(mload(0xb20), mload(0x32a0), f_q)) +mstore(0x32e0, addmod(mload(0x32c0), mload(0x5e0), f_q)) +mstore(0x3300, mulmod(mload(0x32e0), mload(0x3260), f_q)) +mstore(0x3320, mulmod(mload(0x3300), mload(0xdc0), f_q)) +mstore(0x3340, addmod(mload(0x31e0), sub(f_q, mload(0x3320)), f_q)) +mstore(0x3360, mulmod(mload(0x3340), mload(0x2b60), f_q)) +mstore(0x3380, addmod(mload(0x30e0), mload(0x3360), f_q)) +mstore(0x33a0, mulmod(mload(0x7c0), mload(0x3380), f_q)) +mstore(0x33c0, mulmod(mload(0xce0), mload(0x580), f_q)) +mstore(0x33e0, addmod(mload(0x2500), mload(0x33c0), f_q)) +mstore(0x3400, addmod(mload(0x33e0), mload(0x5e0), f_q)) +mstore(0x3420, mulmod(mload(0x3400), mload(0xe40), f_q)) +mstore(0x3440, mulmod(5625741653535312224677218588085279924365897425605943700675464992185016992283, mload(0x580), f_q)) +mstore(0x3460, mulmod(mload(0x8e0), mload(0x3440), f_q)) +mstore(0x3480, addmod(mload(0x2500), mload(0x3460), f_q)) +mstore(0x34a0, addmod(mload(0x3480), mload(0x5e0), f_q)) +mstore(0x34c0, mulmod(mload(0x34a0), mload(0xe20), f_q)) +mstore(0x34e0, addmod(mload(0x3420), sub(f_q, mload(0x34c0)), f_q)) +mstore(0x3500, mulmod(mload(0x34e0), mload(0x2b60), f_q)) +mstore(0x3520, addmod(mload(0x33a0), mload(0x3500), f_q)) +mstore(0x3540, mulmod(mload(0x7c0), mload(0x3520), f_q)) +mstore(0x3560, addmod(1, sub(f_q, mload(0xe60)), f_q)) +mstore(0x3580, mulmod(mload(0x3560), mload(0x21e0), f_q)) +mstore(0x35a0, addmod(mload(0x3540), mload(0x3580), f_q)) +mstore(0x35c0, mulmod(mload(0x7c0), mload(0x35a0), f_q)) +mstore(0x35e0, mulmod(mload(0xe60), mload(0xe60), f_q)) +mstore(0x3600, addmod(mload(0x35e0), sub(f_q, mload(0xe60)), f_q)) +mstore(0x3620, mulmod(mload(0x3600), mload(0x2100), f_q)) +mstore(0x3640, addmod(mload(0x35c0), mload(0x3620), f_q)) +mstore(0x3660, mulmod(mload(0x7c0), mload(0x3640), f_q)) +mstore(0x3680, addmod(mload(0xea0), mload(0x580), f_q)) +mstore(0x36a0, mulmod(mload(0x3680), mload(0xe80), f_q)) +mstore(0x36c0, addmod(mload(0xee0), mload(0x5e0), f_q)) +mstore(0x36e0, mulmod(mload(0x36c0), mload(0x36a0), f_q)) +mstore(0x3700, addmod(mload(0xb20), mload(0x580), f_q)) +mstore(0x3720, mulmod(mload(0x3700), mload(0xe60), f_q)) +mstore(0x3740, addmod(mload(0xb60), mload(0x5e0), f_q)) +mstore(0x3760, mulmod(mload(0x3740), mload(0x3720), f_q)) +mstore(0x3780, addmod(mload(0x36e0), sub(f_q, mload(0x3760)), f_q)) +mstore(0x37a0, mulmod(mload(0x3780), mload(0x2b60), f_q)) +mstore(0x37c0, addmod(mload(0x3660), mload(0x37a0), f_q)) +mstore(0x37e0, mulmod(mload(0x7c0), mload(0x37c0), f_q)) +mstore(0x3800, addmod(mload(0xea0), sub(f_q, mload(0xee0)), f_q)) +mstore(0x3820, mulmod(mload(0x3800), mload(0x21e0), f_q)) +mstore(0x3840, addmod(mload(0x37e0), mload(0x3820), f_q)) +mstore(0x3860, mulmod(mload(0x7c0), mload(0x3840), f_q)) +mstore(0x3880, mulmod(mload(0x3800), mload(0x2b60), f_q)) +mstore(0x38a0, addmod(mload(0xea0), sub(f_q, mload(0xec0)), f_q)) +mstore(0x38c0, mulmod(mload(0x38a0), mload(0x3880), f_q)) +mstore(0x38e0, addmod(mload(0x3860), mload(0x38c0), f_q)) +mstore(0x3900, mulmod(mload(0x13c0), mload(0x13c0), f_q)) +mstore(0x3920, mulmod(mload(0x3900), mload(0x13c0), f_q)) +mstore(0x3940, mulmod(1, mload(0x13c0), f_q)) +mstore(0x3960, mulmod(1, mload(0x3900), f_q)) +mstore(0x3980, mulmod(mload(0x38e0), mload(0x13e0), f_q)) +mstore(0x39a0, mulmod(mload(0x1120), mload(0x8e0), f_q)) +mstore(0x39c0, mulmod(mload(0x39a0), mload(0x8e0), f_q)) +mstore(0x39e0, mulmod(mload(0x8e0), 1, f_q)) +mstore(0x3a00, addmod(mload(0x1020), sub(f_q, mload(0x39e0)), f_q)) +mstore(0x3a20, mulmod(mload(0x8e0), 1426404432721484388505361748317961535523355871255605456897797744433766488507, f_q)) +mstore(0x3a40, addmod(mload(0x1020), sub(f_q, mload(0x3a20)), f_q)) +mstore(0x3a60, mulmod(mload(0x8e0), 8734126352828345679573237859165904705806588461301144420590422589042130041188, f_q)) +mstore(0x3a80, addmod(mload(0x1020), sub(f_q, mload(0x3a60)), f_q)) +mstore(0x3aa0, mulmod(mload(0x8e0), 11211301017135681023579411905410872569206244553457844956874280139879520583390, f_q)) +mstore(0x3ac0, addmod(mload(0x1020), sub(f_q, mload(0x3aa0)), f_q)) +mstore(0x3ae0, mulmod(mload(0x8e0), 12619617507853212586156872920672483948819476989779550311307282715684870266992, f_q)) +mstore(0x3b00, addmod(mload(0x1020), sub(f_q, mload(0x3ae0)), f_q)) +mstore(0x3b20, mulmod(mload(0x8e0), 13225785879531581993054172815365636627224369411478295502904397545373139154045, f_q)) +mstore(0x3b40, addmod(mload(0x1020), sub(f_q, mload(0x3b20)), f_q)) +{ + let result := mulmod(mload(0x1020), mulmod(mload(0x39a0), 3544324119167359571073009690693121464267965232733679586767649244433889388945, f_q), f_q) +result := addmod(mulmod(mload(0x8e0), mulmod(mload(0x39a0), 18343918752671915651173396054564153624280399167682354756930554942141919106672, f_q), f_q), result, f_q) +mstore(15200, result) + } +{ + let result := mulmod(mload(0x1020), mulmod(mload(0x39a0), 3860370625838117017501327045244227871206764201116468958063324100051382735289, f_q), f_q) +result := addmod(mulmod(mload(0x8e0), mulmod(mload(0x39a0), 21616901807277407275624036604424346159916096890712898844034238973395610537327, f_q), f_q), result, f_q) +mstore(15232, result) + } +{ + let result := mulmod(mload(0x1020), mulmod(mload(0x39a0), 21616901807277407275624036604424346159916096890712898844034238973395610537327, f_q), f_q) +result := addmod(mulmod(mload(0x8e0), mulmod(mload(0x39a0), 889236556954614024749610889108815341999962898269585485843658889664869519176, f_q), f_q), result, f_q) +mstore(15264, result) + } +{ + let result := mulmod(mload(0x1020), mulmod(mload(0x39a0), 3209408481237076479025468386201293941554240476766691830436732310949352383503, f_q), f_q) +result := addmod(mulmod(mload(0x8e0), mulmod(mload(0x39a0), 12080394110851700286656425387058292751221637853580771255128961096834426654570, f_q), f_q), result, f_q) +mstore(15296, result) + } +mstore(0x3be0, mulmod(1, mload(0x3a00), f_q)) +mstore(0x3c00, mulmod(mload(0x3be0), mload(0x3ac0), f_q)) +mstore(0x3c20, mulmod(mload(0x3c00), mload(0x3a40), f_q)) +mstore(0x3c40, mulmod(mload(0x3c20), mload(0x3b00), f_q)) +{ + let result := mulmod(mload(0x1020), 1, f_q) +result := addmod(mulmod(mload(0x8e0), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q), result, f_q) +mstore(15456, result) + } +{ + let result := mulmod(mload(0x1020), mulmod(mload(0x1120), 8390819244605639573390577733158868133682115698337564550620146375401109684432, f_q), f_q) +result := addmod(mulmod(mload(0x8e0), mulmod(mload(0x1120), 13497423627233635648855828012098406954866248702078469793078057811174698811185, f_q), f_q), result, f_q) +mstore(15488, result) + } +{ + let result := mulmod(mload(0x1020), mulmod(mload(0x1120), 14389468897523033212448771694851898440525479866834419679925499462425232628530, f_q), f_q) +result := addmod(mulmod(mload(0x8e0), mulmod(mload(0x1120), 10771624105926513343199793365135253961557027396599172824137553349410803667382, f_q), f_q), result, f_q) +mstore(15520, result) + } +{ + let result := mulmod(mload(0x1020), mulmod(mload(0x1120), 8021781111580269725587432039983408559403601261632071736490564397134126857583, f_q), f_q) +result := addmod(mulmod(mload(0x8e0), mulmod(mload(0x1120), 13263758384809315129424392494083758423780924407584659157289746760747196496964, f_q), f_q), result, f_q) +mstore(15552, result) + } +mstore(0x3ce0, mulmod(mload(0x3c00), mload(0x3b40), f_q)) +{ + let result := mulmod(mload(0x1020), mulmod(mload(0x8e0), 10676941854703594198666993839846402519342119846958189386823924046696287912228, f_q), f_q) +result := addmod(mulmod(mload(0x8e0), mulmod(mload(0x8e0), 11211301017135681023579411905410872569206244553457844956874280139879520583389, f_q), f_q), result, f_q) +mstore(15616, result) + } +{ + let result := mulmod(mload(0x1020), mulmod(mload(0x8e0), 11211301017135681023579411905410872569206244553457844956874280139879520583389, f_q), f_q) +result := addmod(mulmod(mload(0x8e0), mulmod(mload(0x8e0), 9784896584414196635074050157092911033682888682202239499976482395445754094883, f_q), f_q), result, f_q) +mstore(15648, result) + } +{ + let result := mulmod(mload(0x1020), mulmod(mload(0x8e0), 13154116519010929542673167886091370382741775939114889923107781597533678454430, f_q), f_q) +result := addmod(mulmod(mload(0x8e0), mulmod(mload(0x8e0), 8734126352828345679573237859165904705806588461301144420590422589042130041187, f_q), f_q), result, f_q) +mstore(15680, result) + } +{ + let result := mulmod(mload(0x1020), mulmod(mload(0x8e0), 8734126352828345679573237859165904705806588461301144420590422589042130041187, f_q), f_q) +result := addmod(mulmod(mload(0x8e0), mulmod(mload(0x8e0), 5948611796446669599396300148285100597158677068822442314729736978662760216172, f_q), f_q), result, f_q) +mstore(15712, result) + } +mstore(0x3d80, mulmod(mload(0x3be0), mload(0x3a80), f_q)) +{ + let prod := mload(0x3b60) + + prod := mulmod(mload(0x3b80), prod, f_q) + mstore(0x3da0, prod) + + prod := mulmod(mload(0x3ba0), prod, f_q) + mstore(0x3dc0, prod) + + prod := mulmod(mload(0x3bc0), prod, f_q) + mstore(0x3de0, prod) + + prod := mulmod(mload(0x3c60), prod, f_q) + mstore(0x3e00, prod) + + prod := mulmod(mload(0x3be0), prod, f_q) + mstore(0x3e20, prod) + + prod := mulmod(mload(0x3c80), prod, f_q) + mstore(0x3e40, prod) + + prod := mulmod(mload(0x3ca0), prod, f_q) + mstore(0x3e60, prod) + + prod := mulmod(mload(0x3cc0), prod, f_q) + mstore(0x3e80, prod) + + prod := mulmod(mload(0x3ce0), prod, f_q) + mstore(0x3ea0, prod) + + prod := mulmod(mload(0x3d00), prod, f_q) + mstore(0x3ec0, prod) + + prod := mulmod(mload(0x3d20), prod, f_q) + mstore(0x3ee0, prod) + + prod := mulmod(mload(0x3c00), prod, f_q) + mstore(0x3f00, prod) + + prod := mulmod(mload(0x3d40), prod, f_q) + mstore(0x3f20, prod) + + prod := mulmod(mload(0x3d60), prod, f_q) + mstore(0x3f40, prod) + + prod := mulmod(mload(0x3d80), prod, f_q) + mstore(0x3f60, prod) + + } +mstore(0x3fa0, 32) +mstore(0x3fc0, 32) +mstore(0x3fe0, 32) +mstore(0x4000, mload(0x3f60)) +mstore(0x4020, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x4040, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x3fa0, 0xc0, 0x3f80, 0x20), 1), success) +{ + + let inv := mload(0x3f80) + let v + + v := mload(0x3d80) + mstore(15744, mulmod(mload(0x3f40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3d60) + mstore(15712, mulmod(mload(0x3f20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3d40) + mstore(15680, mulmod(mload(0x3f00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3c00) + mstore(15360, mulmod(mload(0x3ee0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3d20) + mstore(15648, mulmod(mload(0x3ec0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3d00) + mstore(15616, mulmod(mload(0x3ea0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3ce0) + mstore(15584, mulmod(mload(0x3e80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3cc0) + mstore(15552, mulmod(mload(0x3e60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3ca0) + mstore(15520, mulmod(mload(0x3e40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3c80) + mstore(15488, mulmod(mload(0x3e20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3be0) + mstore(15328, mulmod(mload(0x3e00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3c60) + mstore(15456, mulmod(mload(0x3de0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3bc0) + mstore(15296, mulmod(mload(0x3dc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3ba0) + mstore(15264, mulmod(mload(0x3da0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3b80) + mstore(15232, mulmod(mload(0x3b60), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x3b60, inv) + + } +{ + let result := mload(0x3b60) +result := addmod(mload(0x3b80), result, f_q) +result := addmod(mload(0x3ba0), result, f_q) +result := addmod(mload(0x3bc0), result, f_q) +mstore(16480, result) + } +mstore(0x4080, mulmod(mload(0x3c40), mload(0x3be0), f_q)) +{ + let result := mload(0x3c60) +mstore(16544, result) + } +mstore(0x40c0, mulmod(mload(0x3c40), mload(0x3ce0), f_q)) +{ + let result := mload(0x3c80) +result := addmod(mload(0x3ca0), result, f_q) +result := addmod(mload(0x3cc0), result, f_q) +mstore(16608, result) + } +mstore(0x4100, mulmod(mload(0x3c40), mload(0x3c00), f_q)) +{ + let result := mload(0x3d00) +result := addmod(mload(0x3d20), result, f_q) +mstore(16672, result) + } +mstore(0x4140, mulmod(mload(0x3c40), mload(0x3d80), f_q)) +{ + let result := mload(0x3d40) +result := addmod(mload(0x3d60), result, f_q) +mstore(16736, result) + } +{ + let prod := mload(0x4060) + + prod := mulmod(mload(0x40a0), prod, f_q) + mstore(0x4180, prod) + + prod := mulmod(mload(0x40e0), prod, f_q) + mstore(0x41a0, prod) + + prod := mulmod(mload(0x4120), prod, f_q) + mstore(0x41c0, prod) + + prod := mulmod(mload(0x4160), prod, f_q) + mstore(0x41e0, prod) + + } +mstore(0x4220, 32) +mstore(0x4240, 32) +mstore(0x4260, 32) +mstore(0x4280, mload(0x41e0)) +mstore(0x42a0, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x42c0, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x4220, 0xc0, 0x4200, 0x20), 1), success) +{ + + let inv := mload(0x4200) + let v + + v := mload(0x4160) + mstore(16736, mulmod(mload(0x41c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4120) + mstore(16672, mulmod(mload(0x41a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x40e0) + mstore(16608, mulmod(mload(0x4180), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x40a0) + mstore(16544, mulmod(mload(0x4060), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x4060, inv) + + } +mstore(0x42e0, mulmod(mload(0x4080), mload(0x40a0), f_q)) +mstore(0x4300, mulmod(mload(0x40c0), mload(0x40e0), f_q)) +mstore(0x4320, mulmod(mload(0x4100), mload(0x4120), f_q)) +mstore(0x4340, mulmod(mload(0x4140), mload(0x4160), f_q)) +mstore(0x4360, mulmod(mload(0xf20), mload(0xf20), f_q)) +mstore(0x4380, mulmod(mload(0x4360), mload(0xf20), f_q)) +mstore(0x43a0, mulmod(mload(0x4380), mload(0xf20), f_q)) +mstore(0x43c0, mulmod(mload(0x43a0), mload(0xf20), f_q)) +mstore(0x43e0, mulmod(mload(0x43c0), mload(0xf20), f_q)) +mstore(0x4400, mulmod(mload(0x43e0), mload(0xf20), f_q)) +mstore(0x4420, mulmod(mload(0x4400), mload(0xf20), f_q)) +mstore(0x4440, mulmod(mload(0x4420), mload(0xf20), f_q)) +mstore(0x4460, mulmod(mload(0x4440), mload(0xf20), f_q)) +mstore(0x4480, mulmod(mload(0x4460), mload(0xf20), f_q)) +mstore(0x44a0, mulmod(mload(0x4480), mload(0xf20), f_q)) +mstore(0x44c0, mulmod(mload(0x44a0), mload(0xf20), f_q)) +mstore(0x44e0, mulmod(mload(0x44c0), mload(0xf20), f_q)) +mstore(0x4500, mulmod(mload(0x44e0), mload(0xf20), f_q)) +mstore(0x4520, mulmod(mload(0x4500), mload(0xf20), f_q)) +mstore(0x4540, mulmod(mload(0x4520), mload(0xf20), f_q)) +mstore(0x4560, mulmod(mload(0xf80), mload(0xf80), f_q)) +mstore(0x4580, mulmod(mload(0x4560), mload(0xf80), f_q)) +mstore(0x45a0, mulmod(mload(0x4580), mload(0xf80), f_q)) +mstore(0x45c0, mulmod(mload(0x45a0), mload(0xf80), f_q)) +{ + let result := mulmod(mload(0x920), mload(0x3b60), f_q) +result := addmod(mulmod(mload(0x940), mload(0x3b80), f_q), result, f_q) +result := addmod(mulmod(mload(0x960), mload(0x3ba0), f_q), result, f_q) +result := addmod(mulmod(mload(0x980), mload(0x3bc0), f_q), result, f_q) +mstore(17888, result) + } +mstore(0x4600, mulmod(mload(0x45e0), mload(0x4060), f_q)) +mstore(0x4620, mulmod(sub(f_q, mload(0x4600)), 1, f_q)) +{ + let result := mulmod(mload(0x9a0), mload(0x3b60), f_q) +result := addmod(mulmod(mload(0x9c0), mload(0x3b80), f_q), result, f_q) +result := addmod(mulmod(mload(0x9e0), mload(0x3ba0), f_q), result, f_q) +result := addmod(mulmod(mload(0xa00), mload(0x3bc0), f_q), result, f_q) +mstore(17984, result) + } +mstore(0x4660, mulmod(mload(0x4640), mload(0x4060), f_q)) +mstore(0x4680, mulmod(sub(f_q, mload(0x4660)), mload(0xf20), f_q)) +mstore(0x46a0, mulmod(1, mload(0xf20), f_q)) +mstore(0x46c0, addmod(mload(0x4620), mload(0x4680), f_q)) +{ + let result := mulmod(mload(0xa20), mload(0x3b60), f_q) +result := addmod(mulmod(mload(0xa40), mload(0x3b80), f_q), result, f_q) +result := addmod(mulmod(mload(0xa60), mload(0x3ba0), f_q), result, f_q) +result := addmod(mulmod(mload(0xa80), mload(0x3bc0), f_q), result, f_q) +mstore(18144, result) + } +mstore(0x4700, mulmod(mload(0x46e0), mload(0x4060), f_q)) +mstore(0x4720, mulmod(sub(f_q, mload(0x4700)), mload(0x4360), f_q)) +mstore(0x4740, mulmod(1, mload(0x4360), f_q)) +mstore(0x4760, addmod(mload(0x46c0), mload(0x4720), f_q)) +{ + let result := mulmod(mload(0xaa0), mload(0x3b60), f_q) +result := addmod(mulmod(mload(0xac0), mload(0x3b80), f_q), result, f_q) +result := addmod(mulmod(mload(0xae0), mload(0x3ba0), f_q), result, f_q) +result := addmod(mulmod(mload(0xb00), mload(0x3bc0), f_q), result, f_q) +mstore(18304, result) + } +mstore(0x47a0, mulmod(mload(0x4780), mload(0x4060), f_q)) +mstore(0x47c0, mulmod(sub(f_q, mload(0x47a0)), mload(0x4380), f_q)) +mstore(0x47e0, mulmod(1, mload(0x4380), f_q)) +mstore(0x4800, addmod(mload(0x4760), mload(0x47c0), f_q)) +mstore(0x4820, mulmod(mload(0x4800), 1, f_q)) +mstore(0x4840, mulmod(mload(0x46a0), 1, f_q)) +mstore(0x4860, mulmod(mload(0x4740), 1, f_q)) +mstore(0x4880, mulmod(mload(0x47e0), 1, f_q)) +mstore(0x48a0, mulmod(1, mload(0x4080), f_q)) +{ + let result := mulmod(mload(0xb20), mload(0x3c60), f_q) +mstore(18624, result) + } +mstore(0x48e0, mulmod(mload(0x48c0), mload(0x42e0), f_q)) +mstore(0x4900, mulmod(sub(f_q, mload(0x48e0)), 1, f_q)) +mstore(0x4920, mulmod(mload(0x48a0), 1, f_q)) +{ + let result := mulmod(mload(0xee0), mload(0x3c60), f_q) +mstore(18752, result) + } +mstore(0x4960, mulmod(mload(0x4940), mload(0x42e0), f_q)) +mstore(0x4980, mulmod(sub(f_q, mload(0x4960)), mload(0xf20), f_q)) +mstore(0x49a0, mulmod(mload(0x48a0), mload(0xf20), f_q)) +mstore(0x49c0, addmod(mload(0x4900), mload(0x4980), f_q)) +{ + let result := mulmod(mload(0xb40), mload(0x3c60), f_q) +mstore(18912, result) + } +mstore(0x4a00, mulmod(mload(0x49e0), mload(0x42e0), f_q)) +mstore(0x4a20, mulmod(sub(f_q, mload(0x4a00)), mload(0x4360), f_q)) +mstore(0x4a40, mulmod(mload(0x48a0), mload(0x4360), f_q)) +mstore(0x4a60, addmod(mload(0x49c0), mload(0x4a20), f_q)) +{ + let result := mulmod(mload(0xb60), mload(0x3c60), f_q) +mstore(19072, result) + } +mstore(0x4aa0, mulmod(mload(0x4a80), mload(0x42e0), f_q)) +mstore(0x4ac0, mulmod(sub(f_q, mload(0x4aa0)), mload(0x4380), f_q)) +mstore(0x4ae0, mulmod(mload(0x48a0), mload(0x4380), f_q)) +mstore(0x4b00, addmod(mload(0x4a60), mload(0x4ac0), f_q)) +{ + let result := mulmod(mload(0xb80), mload(0x3c60), f_q) +mstore(19232, result) + } +mstore(0x4b40, mulmod(mload(0x4b20), mload(0x42e0), f_q)) +mstore(0x4b60, mulmod(sub(f_q, mload(0x4b40)), mload(0x43a0), f_q)) +mstore(0x4b80, mulmod(mload(0x48a0), mload(0x43a0), f_q)) +mstore(0x4ba0, addmod(mload(0x4b00), mload(0x4b60), f_q)) +{ + let result := mulmod(mload(0xba0), mload(0x3c60), f_q) +mstore(19392, result) + } +mstore(0x4be0, mulmod(mload(0x4bc0), mload(0x42e0), f_q)) +mstore(0x4c00, mulmod(sub(f_q, mload(0x4be0)), mload(0x43c0), f_q)) +mstore(0x4c20, mulmod(mload(0x48a0), mload(0x43c0), f_q)) +mstore(0x4c40, addmod(mload(0x4ba0), mload(0x4c00), f_q)) +{ + let result := mulmod(mload(0xbc0), mload(0x3c60), f_q) +mstore(19552, result) + } +mstore(0x4c80, mulmod(mload(0x4c60), mload(0x42e0), f_q)) +mstore(0x4ca0, mulmod(sub(f_q, mload(0x4c80)), mload(0x43e0), f_q)) +mstore(0x4cc0, mulmod(mload(0x48a0), mload(0x43e0), f_q)) +mstore(0x4ce0, addmod(mload(0x4c40), mload(0x4ca0), f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x3c60), f_q) +mstore(19712, result) + } +mstore(0x4d20, mulmod(mload(0x4d00), mload(0x42e0), f_q)) +mstore(0x4d40, mulmod(sub(f_q, mload(0x4d20)), mload(0x4400), f_q)) +mstore(0x4d60, mulmod(mload(0x48a0), mload(0x4400), f_q)) +mstore(0x4d80, addmod(mload(0x4ce0), mload(0x4d40), f_q)) +{ + let result := mulmod(mload(0xc20), mload(0x3c60), f_q) +mstore(19872, result) + } +mstore(0x4dc0, mulmod(mload(0x4da0), mload(0x42e0), f_q)) +mstore(0x4de0, mulmod(sub(f_q, mload(0x4dc0)), mload(0x4420), f_q)) +mstore(0x4e00, mulmod(mload(0x48a0), mload(0x4420), f_q)) +mstore(0x4e20, addmod(mload(0x4d80), mload(0x4de0), f_q)) +{ + let result := mulmod(mload(0xc40), mload(0x3c60), f_q) +mstore(20032, result) + } +mstore(0x4e60, mulmod(mload(0x4e40), mload(0x42e0), f_q)) +mstore(0x4e80, mulmod(sub(f_q, mload(0x4e60)), mload(0x4440), f_q)) +mstore(0x4ea0, mulmod(mload(0x48a0), mload(0x4440), f_q)) +mstore(0x4ec0, addmod(mload(0x4e20), mload(0x4e80), f_q)) +{ + let result := mulmod(mload(0xc60), mload(0x3c60), f_q) +mstore(20192, result) + } +mstore(0x4f00, mulmod(mload(0x4ee0), mload(0x42e0), f_q)) +mstore(0x4f20, mulmod(sub(f_q, mload(0x4f00)), mload(0x4460), f_q)) +mstore(0x4f40, mulmod(mload(0x48a0), mload(0x4460), f_q)) +mstore(0x4f60, addmod(mload(0x4ec0), mload(0x4f20), f_q)) +{ + let result := mulmod(mload(0xc80), mload(0x3c60), f_q) +mstore(20352, result) + } +mstore(0x4fa0, mulmod(mload(0x4f80), mload(0x42e0), f_q)) +mstore(0x4fc0, mulmod(sub(f_q, mload(0x4fa0)), mload(0x4480), f_q)) +mstore(0x4fe0, mulmod(mload(0x48a0), mload(0x4480), f_q)) +mstore(0x5000, addmod(mload(0x4f60), mload(0x4fc0), f_q)) +{ + let result := mulmod(mload(0xca0), mload(0x3c60), f_q) +mstore(20512, result) + } +mstore(0x5040, mulmod(mload(0x5020), mload(0x42e0), f_q)) +mstore(0x5060, mulmod(sub(f_q, mload(0x5040)), mload(0x44a0), f_q)) +mstore(0x5080, mulmod(mload(0x48a0), mload(0x44a0), f_q)) +mstore(0x50a0, addmod(mload(0x5000), mload(0x5060), f_q)) +{ + let result := mulmod(mload(0xcc0), mload(0x3c60), f_q) +mstore(20672, result) + } +mstore(0x50e0, mulmod(mload(0x50c0), mload(0x42e0), f_q)) +mstore(0x5100, mulmod(sub(f_q, mload(0x50e0)), mload(0x44c0), f_q)) +mstore(0x5120, mulmod(mload(0x48a0), mload(0x44c0), f_q)) +mstore(0x5140, addmod(mload(0x50a0), mload(0x5100), f_q)) +{ + let result := mulmod(mload(0xce0), mload(0x3c60), f_q) +mstore(20832, result) + } +mstore(0x5180, mulmod(mload(0x5160), mload(0x42e0), f_q)) +mstore(0x51a0, mulmod(sub(f_q, mload(0x5180)), mload(0x44e0), f_q)) +mstore(0x51c0, mulmod(mload(0x48a0), mload(0x44e0), f_q)) +mstore(0x51e0, addmod(mload(0x5140), mload(0x51a0), f_q)) +mstore(0x5200, mulmod(mload(0x3940), mload(0x4080), f_q)) +mstore(0x5220, mulmod(mload(0x3960), mload(0x4080), f_q)) +{ + let result := mulmod(mload(0x3980), mload(0x3c60), f_q) +mstore(21056, result) + } +mstore(0x5260, mulmod(mload(0x5240), mload(0x42e0), f_q)) +mstore(0x5280, mulmod(sub(f_q, mload(0x5260)), mload(0x4500), f_q)) +mstore(0x52a0, mulmod(mload(0x48a0), mload(0x4500), f_q)) +mstore(0x52c0, mulmod(mload(0x5200), mload(0x4500), f_q)) +mstore(0x52e0, mulmod(mload(0x5220), mload(0x4500), f_q)) +mstore(0x5300, addmod(mload(0x51e0), mload(0x5280), f_q)) +{ + let result := mulmod(mload(0xc00), mload(0x3c60), f_q) +mstore(21280, result) + } +mstore(0x5340, mulmod(mload(0x5320), mload(0x42e0), f_q)) +mstore(0x5360, mulmod(sub(f_q, mload(0x5340)), mload(0x4520), f_q)) +mstore(0x5380, mulmod(mload(0x48a0), mload(0x4520), f_q)) +mstore(0x53a0, addmod(mload(0x5300), mload(0x5360), f_q)) +mstore(0x53c0, mulmod(mload(0x53a0), mload(0xf80), f_q)) +mstore(0x53e0, mulmod(mload(0x4920), mload(0xf80), f_q)) +mstore(0x5400, mulmod(mload(0x49a0), mload(0xf80), f_q)) +mstore(0x5420, mulmod(mload(0x4a40), mload(0xf80), f_q)) +mstore(0x5440, mulmod(mload(0x4ae0), mload(0xf80), f_q)) +mstore(0x5460, mulmod(mload(0x4b80), mload(0xf80), f_q)) +mstore(0x5480, mulmod(mload(0x4c20), mload(0xf80), f_q)) +mstore(0x54a0, mulmod(mload(0x4cc0), mload(0xf80), f_q)) +mstore(0x54c0, mulmod(mload(0x4d60), mload(0xf80), f_q)) +mstore(0x54e0, mulmod(mload(0x4e00), mload(0xf80), f_q)) +mstore(0x5500, mulmod(mload(0x4ea0), mload(0xf80), f_q)) +mstore(0x5520, mulmod(mload(0x4f40), mload(0xf80), f_q)) +mstore(0x5540, mulmod(mload(0x4fe0), mload(0xf80), f_q)) +mstore(0x5560, mulmod(mload(0x5080), mload(0xf80), f_q)) +mstore(0x5580, mulmod(mload(0x5120), mload(0xf80), f_q)) +mstore(0x55a0, mulmod(mload(0x51c0), mload(0xf80), f_q)) +mstore(0x55c0, mulmod(mload(0x52a0), mload(0xf80), f_q)) +mstore(0x55e0, mulmod(mload(0x52c0), mload(0xf80), f_q)) +mstore(0x5600, mulmod(mload(0x52e0), mload(0xf80), f_q)) +mstore(0x5620, mulmod(mload(0x5380), mload(0xf80), f_q)) +mstore(0x5640, addmod(mload(0x4820), mload(0x53c0), f_q)) +mstore(0x5660, mulmod(1, mload(0x40c0), f_q)) +{ + let result := mulmod(mload(0xd00), mload(0x3c80), f_q) +result := addmod(mulmod(mload(0xd20), mload(0x3ca0), f_q), result, f_q) +result := addmod(mulmod(mload(0xd40), mload(0x3cc0), f_q), result, f_q) +mstore(22144, result) + } +mstore(0x56a0, mulmod(mload(0x5680), mload(0x4300), f_q)) +mstore(0x56c0, mulmod(sub(f_q, mload(0x56a0)), 1, f_q)) +mstore(0x56e0, mulmod(mload(0x5660), 1, f_q)) +{ + let result := mulmod(mload(0xd60), mload(0x3c80), f_q) +result := addmod(mulmod(mload(0xd80), mload(0x3ca0), f_q), result, f_q) +result := addmod(mulmod(mload(0xda0), mload(0x3cc0), f_q), result, f_q) +mstore(22272, result) + } +mstore(0x5720, mulmod(mload(0x5700), mload(0x4300), f_q)) +mstore(0x5740, mulmod(sub(f_q, mload(0x5720)), mload(0xf20), f_q)) +mstore(0x5760, mulmod(mload(0x5660), mload(0xf20), f_q)) +mstore(0x5780, addmod(mload(0x56c0), mload(0x5740), f_q)) +{ + let result := mulmod(mload(0xdc0), mload(0x3c80), f_q) +result := addmod(mulmod(mload(0xde0), mload(0x3ca0), f_q), result, f_q) +result := addmod(mulmod(mload(0xe00), mload(0x3cc0), f_q), result, f_q) +mstore(22432, result) + } +mstore(0x57c0, mulmod(mload(0x57a0), mload(0x4300), f_q)) +mstore(0x57e0, mulmod(sub(f_q, mload(0x57c0)), mload(0x4360), f_q)) +mstore(0x5800, mulmod(mload(0x5660), mload(0x4360), f_q)) +mstore(0x5820, addmod(mload(0x5780), mload(0x57e0), f_q)) +mstore(0x5840, mulmod(mload(0x5820), mload(0x4560), f_q)) +mstore(0x5860, mulmod(mload(0x56e0), mload(0x4560), f_q)) +mstore(0x5880, mulmod(mload(0x5760), mload(0x4560), f_q)) +mstore(0x58a0, mulmod(mload(0x5800), mload(0x4560), f_q)) +mstore(0x58c0, addmod(mload(0x5640), mload(0x5840), f_q)) +mstore(0x58e0, mulmod(1, mload(0x4100), f_q)) +{ + let result := mulmod(mload(0xe20), mload(0x3d00), f_q) +result := addmod(mulmod(mload(0xe40), mload(0x3d20), f_q), result, f_q) +mstore(22784, result) + } +mstore(0x5920, mulmod(mload(0x5900), mload(0x4320), f_q)) +mstore(0x5940, mulmod(sub(f_q, mload(0x5920)), 1, f_q)) +mstore(0x5960, mulmod(mload(0x58e0), 1, f_q)) +{ + let result := mulmod(mload(0xe60), mload(0x3d00), f_q) +result := addmod(mulmod(mload(0xe80), mload(0x3d20), f_q), result, f_q) +mstore(22912, result) + } +mstore(0x59a0, mulmod(mload(0x5980), mload(0x4320), f_q)) +mstore(0x59c0, mulmod(sub(f_q, mload(0x59a0)), mload(0xf20), f_q)) +mstore(0x59e0, mulmod(mload(0x58e0), mload(0xf20), f_q)) +mstore(0x5a00, addmod(mload(0x5940), mload(0x59c0), f_q)) +mstore(0x5a20, mulmod(mload(0x5a00), mload(0x4580), f_q)) +mstore(0x5a40, mulmod(mload(0x5960), mload(0x4580), f_q)) +mstore(0x5a60, mulmod(mload(0x59e0), mload(0x4580), f_q)) +mstore(0x5a80, addmod(mload(0x58c0), mload(0x5a20), f_q)) +mstore(0x5aa0, mulmod(1, mload(0x4140), f_q)) +{ + let result := mulmod(mload(0xea0), mload(0x3d40), f_q) +result := addmod(mulmod(mload(0xec0), mload(0x3d60), f_q), result, f_q) +mstore(23232, result) + } +mstore(0x5ae0, mulmod(mload(0x5ac0), mload(0x4340), f_q)) +mstore(0x5b00, mulmod(sub(f_q, mload(0x5ae0)), 1, f_q)) +mstore(0x5b20, mulmod(mload(0x5aa0), 1, f_q)) +mstore(0x5b40, mulmod(mload(0x5b00), mload(0x45a0), f_q)) +mstore(0x5b60, mulmod(mload(0x5b20), mload(0x45a0), f_q)) +mstore(0x5b80, addmod(mload(0x5a80), mload(0x5b40), f_q)) +mstore(0x5ba0, mulmod(1, mload(0x3c40), f_q)) +mstore(0x5bc0, mulmod(1, mload(0x1020), f_q)) +mstore(0x5be0, 0x0000000000000000000000000000000000000000000000000000000000000001) + mstore(0x5c00, 0x0000000000000000000000000000000000000000000000000000000000000002) +mstore(0x5c20, mload(0x5b80)) +success := and(eq(staticcall(gas(), 0x7, 0x5be0, 0x60, 0x5be0, 0x40), 1), success) +mstore(0x5c40, mload(0x5be0)) + mstore(0x5c60, mload(0x5c00)) +mstore(0x5c80, mload(0x340)) + mstore(0x5ca0, mload(0x360)) +success := and(eq(staticcall(gas(), 0x6, 0x5c40, 0x80, 0x5c40, 0x40), 1), success) +mstore(0x5cc0, mload(0x380)) + mstore(0x5ce0, mload(0x3a0)) +mstore(0x5d00, mload(0x4840)) +success := and(eq(staticcall(gas(), 0x7, 0x5cc0, 0x60, 0x5cc0, 0x40), 1), success) +mstore(0x5d20, mload(0x5c40)) + mstore(0x5d40, mload(0x5c60)) +mstore(0x5d60, mload(0x5cc0)) + mstore(0x5d80, mload(0x5ce0)) +success := and(eq(staticcall(gas(), 0x6, 0x5d20, 0x80, 0x5d20, 0x40), 1), success) +mstore(0x5da0, mload(0x3c0)) + mstore(0x5dc0, mload(0x3e0)) +mstore(0x5de0, mload(0x4860)) +success := and(eq(staticcall(gas(), 0x7, 0x5da0, 0x60, 0x5da0, 0x40), 1), success) +mstore(0x5e00, mload(0x5d20)) + mstore(0x5e20, mload(0x5d40)) +mstore(0x5e40, mload(0x5da0)) + mstore(0x5e60, mload(0x5dc0)) +success := and(eq(staticcall(gas(), 0x6, 0x5e00, 0x80, 0x5e00, 0x40), 1), success) +mstore(0x5e80, mload(0x400)) + mstore(0x5ea0, mload(0x420)) +mstore(0x5ec0, mload(0x4880)) +success := and(eq(staticcall(gas(), 0x7, 0x5e80, 0x60, 0x5e80, 0x40), 1), success) +mstore(0x5ee0, mload(0x5e00)) + mstore(0x5f00, mload(0x5e20)) +mstore(0x5f20, mload(0x5e80)) + mstore(0x5f40, mload(0x5ea0)) +success := and(eq(staticcall(gas(), 0x6, 0x5ee0, 0x80, 0x5ee0, 0x40), 1), success) +mstore(0x5f60, mload(0x440)) + mstore(0x5f80, mload(0x460)) +mstore(0x5fa0, mload(0x53e0)) +success := and(eq(staticcall(gas(), 0x7, 0x5f60, 0x60, 0x5f60, 0x40), 1), success) +mstore(0x5fc0, mload(0x5ee0)) + mstore(0x5fe0, mload(0x5f00)) +mstore(0x6000, mload(0x5f60)) + mstore(0x6020, mload(0x5f80)) +success := and(eq(staticcall(gas(), 0x6, 0x5fc0, 0x80, 0x5fc0, 0x40), 1), success) +mstore(0x6040, mload(0x520)) + mstore(0x6060, mload(0x540)) +mstore(0x6080, mload(0x5400)) +success := and(eq(staticcall(gas(), 0x7, 0x6040, 0x60, 0x6040, 0x40), 1), success) +mstore(0x60a0, mload(0x5fc0)) + mstore(0x60c0, mload(0x5fe0)) +mstore(0x60e0, mload(0x6040)) + mstore(0x6100, mload(0x6060)) +success := and(eq(staticcall(gas(), 0x6, 0x60a0, 0x80, 0x60a0, 0x40), 1), success) +mstore(0x6120, 0x00cc220045e1876953ba639a05a4c1aca08e5fa15d0bba22a7289c5c4cad49c3) + mstore(0x6140, 0x18e8ca9f988d5bfa0620553c77e53ff68e072ffe45951345eb7a3d309edda0a6) +mstore(0x6160, mload(0x5420)) +success := and(eq(staticcall(gas(), 0x7, 0x6120, 0x60, 0x6120, 0x40), 1), success) +mstore(0x6180, mload(0x60a0)) + mstore(0x61a0, mload(0x60c0)) +mstore(0x61c0, mload(0x6120)) + mstore(0x61e0, mload(0x6140)) +success := and(eq(staticcall(gas(), 0x6, 0x6180, 0x80, 0x6180, 0x40), 1), success) +mstore(0x6200, 0x2192633be96c041a0f5153dbc32317753fcba8582fe35193aeaba1152c707982) + mstore(0x6220, 0x17b607ab3b8920817e7d35efd451379021722d7fd97c1900fc55158372bdd325) +mstore(0x6240, mload(0x5440)) +success := and(eq(staticcall(gas(), 0x7, 0x6200, 0x60, 0x6200, 0x40), 1), success) +mstore(0x6260, mload(0x6180)) + mstore(0x6280, mload(0x61a0)) +mstore(0x62a0, mload(0x6200)) + mstore(0x62c0, mload(0x6220)) +success := and(eq(staticcall(gas(), 0x6, 0x6260, 0x80, 0x6260, 0x40), 1), success) +mstore(0x62e0, 0x0a685d3bada38ead79faae078736b8f59099f1182da515495606a5cffe4d1141) + mstore(0x6300, 0x14d74b30a856f01c65b791b5c568186cef57a6190b8bdf8d0350565b3db24166) +mstore(0x6320, mload(0x5460)) +success := and(eq(staticcall(gas(), 0x7, 0x62e0, 0x60, 0x62e0, 0x40), 1), success) +mstore(0x6340, mload(0x6260)) + mstore(0x6360, mload(0x6280)) +mstore(0x6380, mload(0x62e0)) + mstore(0x63a0, mload(0x6300)) +success := and(eq(staticcall(gas(), 0x6, 0x6340, 0x80, 0x6340, 0x40), 1), success) +mstore(0x63c0, 0x2b24e16fa33e6e1ef5b9a38c7645ece96fd12a8083475e2012c96aefd0b6d23c) + mstore(0x63e0, 0x148f6b8b316854b74f1bb307507a288c99ec9cd7dc6d83a91a8e5bc5c687ad6f) +mstore(0x6400, mload(0x5480)) +success := and(eq(staticcall(gas(), 0x7, 0x63c0, 0x60, 0x63c0, 0x40), 1), success) +mstore(0x6420, mload(0x6340)) + mstore(0x6440, mload(0x6360)) +mstore(0x6460, mload(0x63c0)) + mstore(0x6480, mload(0x63e0)) +success := and(eq(staticcall(gas(), 0x6, 0x6420, 0x80, 0x6420, 0x40), 1), success) +mstore(0x64a0, 0x30371678a7355deb3a48da7c0254f8372aef787a72ac76865a685f08d1b47406) + mstore(0x64c0, 0x1212a2b934d56b6cba3fc5e50a88160d0ffc76625b26556d1ea9afaecc8b7228) +mstore(0x64e0, mload(0x54a0)) +success := and(eq(staticcall(gas(), 0x7, 0x64a0, 0x60, 0x64a0, 0x40), 1), success) +mstore(0x6500, mload(0x6420)) + mstore(0x6520, mload(0x6440)) +mstore(0x6540, mload(0x64a0)) + mstore(0x6560, mload(0x64c0)) +success := and(eq(staticcall(gas(), 0x6, 0x6500, 0x80, 0x6500, 0x40), 1), success) +mstore(0x6580, 0x2913e5d2e55149c28e6f6141cbe0859487749ad860a79db179bc84f008210c8d) + mstore(0x65a0, 0x244520bf1e4fa29d5d4e47c5f23d3dc1c5b0ea5342b611447757b7c0ef6c7c6c) +mstore(0x65c0, mload(0x54c0)) +success := and(eq(staticcall(gas(), 0x7, 0x6580, 0x60, 0x6580, 0x40), 1), success) +mstore(0x65e0, mload(0x6500)) + mstore(0x6600, mload(0x6520)) +mstore(0x6620, mload(0x6580)) + mstore(0x6640, mload(0x65a0)) +success := and(eq(staticcall(gas(), 0x6, 0x65e0, 0x80, 0x65e0, 0x40), 1), success) +mstore(0x6660, 0x1782258424ac6a56f6c9050c5ee8b0ee94d2835301efcf7fa2ffac8cd46fe051) + mstore(0x6680, 0x02085b2fb16ce7163e4d127dd4b84b4a2d442c733e6ee308c40f957b63e63eed) +mstore(0x66a0, mload(0x54e0)) +success := and(eq(staticcall(gas(), 0x7, 0x6660, 0x60, 0x6660, 0x40), 1), success) +mstore(0x66c0, mload(0x65e0)) + mstore(0x66e0, mload(0x6600)) +mstore(0x6700, mload(0x6660)) + mstore(0x6720, mload(0x6680)) +success := and(eq(staticcall(gas(), 0x6, 0x66c0, 0x80, 0x66c0, 0x40), 1), success) +mstore(0x6740, 0x24d0cfc629f45d52209bb4c745d84e4e8d609e0b468803261c0b63c113f90860) + mstore(0x6760, 0x1298f0f9abd6c0fabaa2544dbc66387395fccea5adc422e1223a8a8071a7807a) +mstore(0x6780, mload(0x5500)) +success := and(eq(staticcall(gas(), 0x7, 0x6740, 0x60, 0x6740, 0x40), 1), success) +mstore(0x67a0, mload(0x66c0)) + mstore(0x67c0, mload(0x66e0)) +mstore(0x67e0, mload(0x6740)) + mstore(0x6800, mload(0x6760)) +success := and(eq(staticcall(gas(), 0x6, 0x67a0, 0x80, 0x67a0, 0x40), 1), success) +mstore(0x6820, 0x1ea2a5b364fabd33bc8367d0c79b4c23ca12f1f61aa31e9de4576e49cbebc362) + mstore(0x6840, 0x1b10ef25d0ae6c1575d6540136af75091bef66e0dfd434bc98c6984f3efb0cd9) +mstore(0x6860, mload(0x5520)) +success := and(eq(staticcall(gas(), 0x7, 0x6820, 0x60, 0x6820, 0x40), 1), success) +mstore(0x6880, mload(0x67a0)) + mstore(0x68a0, mload(0x67c0)) +mstore(0x68c0, mload(0x6820)) + mstore(0x68e0, mload(0x6840)) +success := and(eq(staticcall(gas(), 0x6, 0x6880, 0x80, 0x6880, 0x40), 1), success) +mstore(0x6900, 0x04ca8b9f8cf0befb0563fccc3e9e7f0abdd48cc784ece101a3bf20024a908cf6) + mstore(0x6920, 0x2209f6bfdcf9874364a6ce608ce0fb6080ae9f25547ef2053db42f367a1cc1a2) +mstore(0x6940, mload(0x5540)) +success := and(eq(staticcall(gas(), 0x7, 0x6900, 0x60, 0x6900, 0x40), 1), success) +mstore(0x6960, mload(0x6880)) + mstore(0x6980, mload(0x68a0)) +mstore(0x69a0, mload(0x6900)) + mstore(0x69c0, mload(0x6920)) +success := and(eq(staticcall(gas(), 0x6, 0x6960, 0x80, 0x6960, 0x40), 1), success) +mstore(0x69e0, 0x190cdaa8341aa9c280a8cc45def465f282d26793c6ae248237dfe793763d6e50) + mstore(0x6a00, 0x09b1c2259dd02fa90e7f6c2c75dc08eeb478a7901a71fae9aae1a392aef36761) +mstore(0x6a20, mload(0x5560)) +success := and(eq(staticcall(gas(), 0x7, 0x69e0, 0x60, 0x69e0, 0x40), 1), success) +mstore(0x6a40, mload(0x6960)) + mstore(0x6a60, mload(0x6980)) +mstore(0x6a80, mload(0x69e0)) + mstore(0x6aa0, mload(0x6a00)) +success := and(eq(staticcall(gas(), 0x6, 0x6a40, 0x80, 0x6a40, 0x40), 1), success) +mstore(0x6ac0, 0x031fd8e6a123f1d59760f40a9fa7fa6232501973966a6d3d826b5179ccead03b) + mstore(0x6ae0, 0x2ef50aae0d481e526bd247d2b3848ee882e54cc528f0d3b1e63363f5127ed89f) +mstore(0x6b00, mload(0x5580)) +success := and(eq(staticcall(gas(), 0x7, 0x6ac0, 0x60, 0x6ac0, 0x40), 1), success) +mstore(0x6b20, mload(0x6a40)) + mstore(0x6b40, mload(0x6a60)) +mstore(0x6b60, mload(0x6ac0)) + mstore(0x6b80, mload(0x6ae0)) +success := and(eq(staticcall(gas(), 0x6, 0x6b20, 0x80, 0x6b20, 0x40), 1), success) +mstore(0x6ba0, 0x09fb3e41ba2806e59703c92a6eeac970464c40ba010449e678a0a891cc5da83c) + mstore(0x6bc0, 0x1b12bbce3975cff98a9cab482298a2b276a1422f3cd09a85a3f4c5edd310506f) +mstore(0x6be0, mload(0x55a0)) +success := and(eq(staticcall(gas(), 0x7, 0x6ba0, 0x60, 0x6ba0, 0x40), 1), success) +mstore(0x6c00, mload(0x6b20)) + mstore(0x6c20, mload(0x6b40)) +mstore(0x6c40, mload(0x6ba0)) + mstore(0x6c60, mload(0x6bc0)) +success := and(eq(staticcall(gas(), 0x6, 0x6c00, 0x80, 0x6c00, 0x40), 1), success) +mstore(0x6c80, mload(0x800)) + mstore(0x6ca0, mload(0x820)) +mstore(0x6cc0, mload(0x55c0)) +success := and(eq(staticcall(gas(), 0x7, 0x6c80, 0x60, 0x6c80, 0x40), 1), success) +mstore(0x6ce0, mload(0x6c00)) + mstore(0x6d00, mload(0x6c20)) +mstore(0x6d20, mload(0x6c80)) + mstore(0x6d40, mload(0x6ca0)) +success := and(eq(staticcall(gas(), 0x6, 0x6ce0, 0x80, 0x6ce0, 0x40), 1), success) +mstore(0x6d60, mload(0x840)) + mstore(0x6d80, mload(0x860)) +mstore(0x6da0, mload(0x55e0)) +success := and(eq(staticcall(gas(), 0x7, 0x6d60, 0x60, 0x6d60, 0x40), 1), success) +mstore(0x6dc0, mload(0x6ce0)) + mstore(0x6de0, mload(0x6d00)) +mstore(0x6e00, mload(0x6d60)) + mstore(0x6e20, mload(0x6d80)) +success := and(eq(staticcall(gas(), 0x6, 0x6dc0, 0x80, 0x6dc0, 0x40), 1), success) +mstore(0x6e40, mload(0x880)) + mstore(0x6e60, mload(0x8a0)) +mstore(0x6e80, mload(0x5600)) +success := and(eq(staticcall(gas(), 0x7, 0x6e40, 0x60, 0x6e40, 0x40), 1), success) +mstore(0x6ea0, mload(0x6dc0)) + mstore(0x6ec0, mload(0x6de0)) +mstore(0x6ee0, mload(0x6e40)) + mstore(0x6f00, mload(0x6e60)) +success := and(eq(staticcall(gas(), 0x6, 0x6ea0, 0x80, 0x6ea0, 0x40), 1), success) +mstore(0x6f20, mload(0x760)) + mstore(0x6f40, mload(0x780)) +mstore(0x6f60, mload(0x5620)) +success := and(eq(staticcall(gas(), 0x7, 0x6f20, 0x60, 0x6f20, 0x40), 1), success) +mstore(0x6f80, mload(0x6ea0)) + mstore(0x6fa0, mload(0x6ec0)) +mstore(0x6fc0, mload(0x6f20)) + mstore(0x6fe0, mload(0x6f40)) +success := and(eq(staticcall(gas(), 0x6, 0x6f80, 0x80, 0x6f80, 0x40), 1), success) +mstore(0x7000, mload(0x620)) + mstore(0x7020, mload(0x640)) +mstore(0x7040, mload(0x5860)) +success := and(eq(staticcall(gas(), 0x7, 0x7000, 0x60, 0x7000, 0x40), 1), success) +mstore(0x7060, mload(0x6f80)) + mstore(0x7080, mload(0x6fa0)) +mstore(0x70a0, mload(0x7000)) + mstore(0x70c0, mload(0x7020)) +success := and(eq(staticcall(gas(), 0x6, 0x7060, 0x80, 0x7060, 0x40), 1), success) +mstore(0x70e0, mload(0x660)) + mstore(0x7100, mload(0x680)) +mstore(0x7120, mload(0x5880)) +success := and(eq(staticcall(gas(), 0x7, 0x70e0, 0x60, 0x70e0, 0x40), 1), success) +mstore(0x7140, mload(0x7060)) + mstore(0x7160, mload(0x7080)) +mstore(0x7180, mload(0x70e0)) + mstore(0x71a0, mload(0x7100)) +success := and(eq(staticcall(gas(), 0x6, 0x7140, 0x80, 0x7140, 0x40), 1), success) +mstore(0x71c0, mload(0x6a0)) + mstore(0x71e0, mload(0x6c0)) +mstore(0x7200, mload(0x58a0)) +success := and(eq(staticcall(gas(), 0x7, 0x71c0, 0x60, 0x71c0, 0x40), 1), success) +mstore(0x7220, mload(0x7140)) + mstore(0x7240, mload(0x7160)) +mstore(0x7260, mload(0x71c0)) + mstore(0x7280, mload(0x71e0)) +success := and(eq(staticcall(gas(), 0x6, 0x7220, 0x80, 0x7220, 0x40), 1), success) +mstore(0x72a0, mload(0x6e0)) + mstore(0x72c0, mload(0x700)) +mstore(0x72e0, mload(0x5a40)) +success := and(eq(staticcall(gas(), 0x7, 0x72a0, 0x60, 0x72a0, 0x40), 1), success) +mstore(0x7300, mload(0x7220)) + mstore(0x7320, mload(0x7240)) +mstore(0x7340, mload(0x72a0)) + mstore(0x7360, mload(0x72c0)) +success := and(eq(staticcall(gas(), 0x6, 0x7300, 0x80, 0x7300, 0x40), 1), success) +mstore(0x7380, mload(0x720)) + mstore(0x73a0, mload(0x740)) +mstore(0x73c0, mload(0x5a60)) +success := and(eq(staticcall(gas(), 0x7, 0x7380, 0x60, 0x7380, 0x40), 1), success) +mstore(0x73e0, mload(0x7300)) + mstore(0x7400, mload(0x7320)) +mstore(0x7420, mload(0x7380)) + mstore(0x7440, mload(0x73a0)) +success := and(eq(staticcall(gas(), 0x6, 0x73e0, 0x80, 0x73e0, 0x40), 1), success) +mstore(0x7460, mload(0x4e0)) + mstore(0x7480, mload(0x500)) +mstore(0x74a0, mload(0x5b60)) +success := and(eq(staticcall(gas(), 0x7, 0x7460, 0x60, 0x7460, 0x40), 1), success) +mstore(0x74c0, mload(0x73e0)) + mstore(0x74e0, mload(0x7400)) +mstore(0x7500, mload(0x7460)) + mstore(0x7520, mload(0x7480)) +success := and(eq(staticcall(gas(), 0x6, 0x74c0, 0x80, 0x74c0, 0x40), 1), success) +mstore(0x7540, mload(0xfc0)) + mstore(0x7560, mload(0xfe0)) +mstore(0x7580, sub(f_q, mload(0x5ba0))) +success := and(eq(staticcall(gas(), 0x7, 0x7540, 0x60, 0x7540, 0x40), 1), success) +mstore(0x75a0, mload(0x74c0)) + mstore(0x75c0, mload(0x74e0)) +mstore(0x75e0, mload(0x7540)) + mstore(0x7600, mload(0x7560)) +success := and(eq(staticcall(gas(), 0x6, 0x75a0, 0x80, 0x75a0, 0x40), 1), success) +mstore(0x7620, mload(0x1060)) + mstore(0x7640, mload(0x1080)) +mstore(0x7660, mload(0x5bc0)) +success := and(eq(staticcall(gas(), 0x7, 0x7620, 0x60, 0x7620, 0x40), 1), success) +mstore(0x7680, mload(0x75a0)) + mstore(0x76a0, mload(0x75c0)) +mstore(0x76c0, mload(0x7620)) + mstore(0x76e0, mload(0x7640)) +success := and(eq(staticcall(gas(), 0x6, 0x7680, 0x80, 0x7680, 0x40), 1), success) +mstore(0x7700, mload(0x7680)) + mstore(0x7720, mload(0x76a0)) +mstore(0x7740, mload(0x1060)) + mstore(0x7760, mload(0x1080)) +mstore(0x7780, mload(0x10a0)) + mstore(0x77a0, mload(0x10c0)) +mstore(0x77c0, mload(0x10e0)) + mstore(0x77e0, mload(0x1100)) +mstore(0x7800, keccak256(0x7700, 256)) +mstore(30752, mod(mload(30720), f_q)) +mstore(0x7840, mulmod(mload(0x7820), mload(0x7820), f_q)) +mstore(0x7860, mulmod(1, mload(0x7820), f_q)) +mstore(0x7880, mload(0x7780)) + mstore(0x78a0, mload(0x77a0)) +mstore(0x78c0, mload(0x7860)) +success := and(eq(staticcall(gas(), 0x7, 0x7880, 0x60, 0x7880, 0x40), 1), success) +mstore(0x78e0, mload(0x7700)) + mstore(0x7900, mload(0x7720)) +mstore(0x7920, mload(0x7880)) + mstore(0x7940, mload(0x78a0)) +success := and(eq(staticcall(gas(), 0x6, 0x78e0, 0x80, 0x78e0, 0x40), 1), success) +mstore(0x7960, mload(0x77c0)) + mstore(0x7980, mload(0x77e0)) +mstore(0x79a0, mload(0x7860)) +success := and(eq(staticcall(gas(), 0x7, 0x7960, 0x60, 0x7960, 0x40), 1), success) +mstore(0x79c0, mload(0x7740)) + mstore(0x79e0, mload(0x7760)) +mstore(0x7a00, mload(0x7960)) + mstore(0x7a20, mload(0x7980)) +success := and(eq(staticcall(gas(), 0x6, 0x79c0, 0x80, 0x79c0, 0x40), 1), success) +mstore(0x7a40, mload(0x78e0)) + mstore(0x7a60, mload(0x7900)) +mstore(0x7a80, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) + mstore(0x7aa0, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) + mstore(0x7ac0, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) + mstore(0x7ae0, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) +mstore(0x7b00, mload(0x79c0)) + mstore(0x7b20, mload(0x79e0)) +mstore(0x7b40, 0x186282957db913abd99f91db59fe69922e95040603ef44c0bd7aa3adeef8f5ac) + mstore(0x7b60, 0x17944351223333f260ddc3b4af45191b856689eda9eab5cbcddbbe570ce860d2) + mstore(0x7b80, 0x06d971ff4a7467c3ec596ed6efc674572e32fd6f52b721f97e35b0b3d3546753) + mstore(0x7ba0, 0x06ecdb9f9567f59ed2eee36e1e1d58797fd13cc97fafc2910f5e8a12f202fa9a) +success := and(eq(staticcall(gas(), 0x8, 0x7a40, 0x180, 0x7a40, 0x20), 1), success) +success := and(eq(mload(0x7a40), 1), success) + + if not(success) { revert(0, 0) } + return(0, 0) + + } + } + } \ No newline at end of file diff --git a/axiom-eth/data/tests/batch_query/shasums.txt b/axiom-eth/data/tests/batch_query/shasums.txt new file mode 100644 index 000000000..5b3d7523e --- /dev/null +++ b/axiom-eth/data/tests/batch_query/shasums.txt @@ -0,0 +1,21 @@ +// These are generated via `sha256sum` on Ubuntu. On macOS use `shasum -a 256`. + +4ad2fc71e23f9a6bc8eb424cbb6d07e3b2f540599827841d4a07fd0a92609c51 data/tests/batch_query/account_3.pk +2dd100517825060bda1f1c51db6b09469b79d91f97a23ff62357105b32cef6ec data/tests/batch_query/account_3_6_0.pk +9c9b2224250ebd807042aaa40dd17527faf8295fe269e7dfa9406d648e1ef9fd data/tests/batch_query/account_3_6_1.pk +ba448e91eeb1ce05932da3f98c2c2472aadb675a12749a5c072b7a1d83d9fa34 data/tests/batch_query/account_3_6_2.pk +1bc5f620efaa8057ad249447bcf18703f5137dd0f9893e38349083d9325ceb27 data/tests/batch_query/account_4.pk +67a147d2fbfeecdf6693cc67b7dac1171463e383a867304badc80ed0af9329fd data/tests/batch_query/account_5.pk +588fa07245b64f637059bb89355cb9e3621ba6395db81233ea3e82ea1a8c9256 data/tests/batch_query/final_0.pk +0d348f7589361df4eaac888bcc026680e93d6c74c8e1263b15055580462d27b1 data/tests/batch_query/final_1.pk +cc4b26fd45b33f106ee1773f22b86becf79543d88e16bb7723fe7555e86ab03d data/tests/batch_query/final_2.pk +e2039ac72d4b50ce68f2cdc55226c2ee263bfe1a2e4cf80da0536ea92510e201 data/tests/batch_query/mainnet_block_6.pk +fd8fb7e1b5a444b667bf8070478d1d61d0025635ed9ba319a295b46fe0b6daba data/tests/batch_query/row_6.pk +07b6005328ef68726b2ca27ea702897f4793b8c8bfc7f3c4a3e8990d7cb7d317 data/tests/batch_query/storage_3.pk +c4dbd4f5de408b60aca8846c1d55384194b754b214c2bec46bad838d49d41488 data/tests/batch_query/storage_3_6_0.pk +b3e8db6bd2d4ef01a43bb836d7177a7c7e904f28581a8ab7d8a0d61ce1f86817 data/tests/batch_query/storage_3_6_1.pk +241b6b057280f109a650fe7f904ca4973c7d77577f828f9d83645e2eb0922357 data/tests/batch_query/storage_3_6_2.pk +587a0c31e98abd8bb2c3cfef33094943e6d4a0b3677b4d638181ae2e150050ac data/tests/batch_query/storage_4.pk +4e30f6cc658a0c239cdaa54b2b631b90c00805361a128721c81dca435241522b data/tests/batch_query/storage_5.pk + +c1717278590ad66f0b1247c21992d6e254f464984d793e7b938a07f10f08058a data/tests/batch_query/final_2.yul diff --git a/axiom-eth/data/tests/batch_query/test_scheduler_final_assembly_old.calldata b/axiom-eth/data/tests/batch_query/test_scheduler_final_assembly_old.calldata new file mode 100644 index 000000000..e5e1a4610 --- /dev/null +++ b/axiom-eth/data/tests/batch_query/test_scheduler_final_assembly_old.calldata @@ -0,0 +1 @@ +0000000000000000000000000000000000000000005d8f090ec618d6c149f47b00000000000000000000000000000000000000000042926fd2e06132ea09ce130000000000000000000000000000000000000000000001090f9f2443881d1aaa000000000000000000000000000000000000000000a2c599262539b4cbb16a89000000000000000000000000000000000000000000cefde7d8db8856716528aa0000000000000000000000000000000000000000000029f2a9f6d1c36efc14090000000000000000000000000000000000000000007aa62d85d140c86dcdce7f0000000000000000000000000000000000000000005dcf3a406cec4768aadb9d000000000000000000000000000000000000000000001899449bbdc5c219d01600000000000000000000000000000000000000000096f131c6445121cb532e33000000000000000000000000000000000000000000e1a10015c1cec8f3afd034000000000000000000000000000000000000000000001d6ecb38563d7beefb112a8a5879fc5648c7be24f15e69eb96c2be738222bfac9581d2c97968be6a18bb00000000000000000000000000000000322d1ad245a73637a4731299672fdf3b000000000000000000000000000000006d2608dc223f1669bfdcd306fd304ce924cc26563ed6861bf3e5bbbacda1c3a2bd92c17deab83c4787132a1e39a1ca4000000000000000000000000000000000082df7ffd0116d06a60b47987dd4f670000000000000000000000000000000008a2211b976df2d5374aff1614ab6a6441f8d632b3e4e554acee7d33597d61a7c1c91778364a23eebac9242c59919a30c0000000000000000000000000000000033637ca1c7b3c31311cef488b2f31a8300000000000000000000000000000000c3ab61cbb1e5615b62a460ec032f239800000000000000000000000000000000bfe45f2d685be849578a95bea4b8b05300000000000000000000000000000000c63329084db7b06fee20ca2ad3c6556a000000000000000000000000000000002d77d208db8c601b953c1a107b63322600000000000000000000000000000000835576967fe4dec12375b07a6c6a38cf24469d3ba4e96f077f4a4eabdcb07aaa8132c92bddf38a303e09774d9e34484e08a9dc71996c2e3b0ff0962ee0f784d5d4bcfe18141711501f607826425c7db11b2355dd297cfe3efa0bfbda7e271de8e45d22c787d444deb77a83f90674f03123bf3514861375b4b264e3dc918bfa8ad42aecc10857aa33d19322124395f7061a5a510e671ab23eb9a42db974bdbec4937e855c039f45ecd7e1747de70dbc8d2683a836c517766414c1bb97861fc207bd5736ccc982036bc88e3bceb560fe9319a189d0e207cf0974005eac6777900f4ccf4838ee35b5402552ceecdbce5b3e112aefd6616c6dbc939e5cd1b02c747a9e5ad20931d787cd13b4378460cc957a0df013dc154189804949719eeb147bb0e93fbb86390240fe095c8d17fdb682ad27c07a305c766636e2c1f933fa2c913615786422e990a8b455b79793e613835f21c281213ab7212afcc9ae04391c13d7505a92f85e4b28c8d1f30a94059c6dac0d2b74abcd8b14dca277b4badf262876944b4564a64478afc0472c2278a2e39520bca81004d1978dd7be3964aad13f205a7d529cf52d22afc2fe48823437ad111efacd29658a679a440bb63baf4cdcce66e665b18cb4fe9e9400f004bce23ba51ef81782834ae39e75956e2ad554c6f5cfa8058af6b6f5f8cacfefa7471c7dde038aaffcc0ee45a510367293a18d277f2b90518d1140ede2e9f5b8a3792e156b01aaaf53daa760c74dfd7c9cd36276e104b8647e3d71103bfdbdd4a148dea6261d815a813ce2788084034d6ca79f80980b76c3a80eec4de1ad55d04f5bff7eb72d3db12a36569137aa4588ad4cd37d4979e8d66b28a0a1389bcf45eb80d6549d2c36f99ac43029b0a0d242b135c1ea6b6f6cf24f32acf6b5187a924011c15efe1d1d27dd36b50f3a991c0f631341a50c23f90a593b9070ee5dfc97d9d782f0120b513562d42aac1ef0abbc7ce70342b8ab68497096a784a6bf00304ebc46f7bf24e7092ed6b7556da12e4ba7b1cc04424bce43de2727a5b55cee4fe6556bb1132937e480a5a414e787cbdd6d7ae5076e4c5c7d3a183229771c84df28dd24785b000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000021d4f631c4182e4820090e4c5f0e13c1dad77ad1a7ecf3bfc6b58a7a1a92239af000311bbd6e4ef04f37b22a918a54075d066aa9e5658468ac1df89edea01aa9c0cbe57d4d36c9949cf69ecfdad62ffc9b4becd38ff0e221df84b78c23d794c3f0efaab84f4efc23210ce070496f3366593065693e25526550e294e3992f84b610197b9f1c37c31d39745385744bba98e8b12542069a4ca79c8f7c33fa50dce512fee005320358e48388603812f5dc9170dea8a91080d8a43f55ac338ea4b6d5116986a539993696eaf47b0254fb0f2be5bd4409dc82c323d289031126176527725f0575b5adf59e990231246bd767da316c21a2f8f83e2e162da258963255a6a1b4101308b83995c1a277cbcd3b9010cf1e3ca2499f30b2893e218b19f2290bb1d0281edeab6ac08d396641786d0854aa1fc194aca462b6631a5c6540a8b13d7139f34c58106d048fcd14d036b57697c8996f9cb027decbd04b8be0a42a638142e109985ce48321ecbde2b3c55f0d4469145516e2d938dc7fc7e38a641b773c41102dada573da81a897f956679faf442b77cc86ef01245740ed61466a9c7578600a44a003f39e33445f841e6601e5e2ea8494738b9e0da440f87ae902b1d067213609a08e9b1dee5ca85ea8533c370d053c43fa28748f8a6dfdff8f1a156f0be229beb997f14839b4f28eaec23717b3ce9b8610342635a6f1bef4421dbd20cd8033c5cb604d129826de517744b7cfd3e5570bdd125c27b3bb31f54fa046843bf2368e511df2e6b098e7fd74100c4e84ccd9a160cef8ec02482092c927fa7c70802dccc0c242aeba20dd60809a94c60c6ddbba50d81b92b040227fbe2a4edd8b32b26026362afc6a741450ee3ac12574412b30925749654688a253d7e599b2d6c052d0f0cf45a162c8ed910c63bab960784d97f039ea163e070b2d7e6691f991f262f764855fed02e449e36e9c169052654ef76fc1a1872241241436e2c809a6e0fe9a9a755c552ba041c0acc92b9f25e0ec8aa8b99a4c917cf2c2e837c09ecc0146e01e5004b39b673a6d0667d4f0350424b16981d45b49ba2763a31735c8dfc21ceec1f9807b8e7990e9d869924269607a38b9a755a5e30a581d40bcbfd3f932d8fef6e8bc51323c20bc56dd8fd3c7cf74da2dc6c996301194f88ac99a736f522ac586c05f5d3f25938bebcd98fd5cc9aa65ea93a7e6cf82a377371d11834d51e905db0f3db6de826ee70b84103078941c5ace5cd96b133224abc5abf45af812fc4c89af2b2986fc442eaea79ada92fddd2c816a508a9a255a94fef43418ebf000000000000000000000000000000000000000000000000000000000000000124b30a3a214a0fb736a11faeae9dd909c2438c857f44d5f22d01b18febeb71722f7a27d0885bc469721db8a45e3d839525dc4abf0d7fcdb9fd0ef7670ad53ca32cd2e6d001b971c2abbc3dc8456077d63f04dd8e1fb3b17b8146670270845a071e931d07cc7ee203014e2ca5a3319dba0f8fd921179fd20f604a6c59f1e9e93a28c44803116ba7bea61e0888b56f0507d609c078a967d587f086b9c1495442320c09f3b3a30c2eecae881963b24e45bd362b0189cfb04c0db12018c696a7e23a2ee5788e8f6f45670cd7a1c556df37dda94249c8793ef64b207197d47f4a600f04c02776dcc65686a7fd42e4a05640b250c9408f0495fd30bda0ae7fd05ae5470af6a6ce320a4f9a465305eb867e5512b0b85cc767c01b3b1cb7caf35e4316f1056e0b4d6e01de5b1a8f7730d4e51b51ae1f788344362c13018145b6b65b16741668ed15d4b1c88739e4235d33eb2af56afab4119e07add474880411066a23400da4060bcf87c24b042fb68893bf4299843d2215eafc0abc55926a272d36f90c1386addd04d30f99cc3615e138f5d48d9649474ae6b61ca9a2e46252d51019c52a277cb1ab015715bf38ad4f683bf19d0cd6ecc14f36824790addf8fb0b181f50c0444192c762c3a8586794e83faaacd89d0dadbf6c4864890e4539d55b6cca71b286fa7e140e8ec9521718eb430157ebe3e2f8ead239bdd7c0067937e19597c02e8a11957af3f5e7736b7b308dc0457836bbc7437f1930f785a3da0b243409d1ebc79f0f2bdb9e6dd59c48a1c6796250b50ca4717f5a6e48a6a96e12295d67a281950dc75dbfd0679f9bf0512a79572320467be67513edf984e937914416d9b18a6ed1d5a804b95223f41aaf6363ac72f828d76fdbc14f64811a2691af076fd21696f9801f2ec7713f70e238e56d3ef4158069a253742cce3022d97ceb10db61ec53b5c35b1f08ed251dfd8c8bb2f4e595e844a9c8c4a4b501b8036babd66480cb9def878d932e0cabf15881d13297d052d4e779e0c53f33bfd9016aafa5b1321e3579527c359f9870d6739b76af3bfb20f7593536aecb5e13c9864a96b7ef01093fdc69ad9f1c964913b99a0864e58b2200e7692845b97656685000796f98b027d9e0531159f98320ed5f39f63265dc5f9428e9c32cd92a75ad962c5d462db2f87b4f2805d9cba7e8cedf9b5de886d4e5ec0529fe0ecfa4bbbc11479c24a06 \ No newline at end of file diff --git a/axiom-eth/data/tests/batch_query/test_scheduler_final_assembly_recent.calldata b/axiom-eth/data/tests/batch_query/test_scheduler_final_assembly_recent.calldata new file mode 100644 index 000000000..4c7a72fad --- /dev/null +++ b/axiom-eth/data/tests/batch_query/test_scheduler_final_assembly_recent.calldata @@ -0,0 +1 @@ +0000000000000000000000000000000000000000007bc61858462f5b1ffe93850000000000000000000000000000000000000000000b2d5c501391bb116fd4670000000000000000000000000000000000000000000004ad881dcdfb040f4d6d000000000000000000000000000000000000000000a8e40fb4b2a32f498c08a900000000000000000000000000000000000000000024965eb9575274178ae2bb0000000000000000000000000000000000000000000028d9004996baaf097615000000000000000000000000000000000000000000c4c412f47e928b57cd91c9000000000000000000000000000000000000000000beae5d447419f74410486b00000000000000000000000000000000000000000000268a3903711d69d64fb4000000000000000000000000000000000000000000848086fe68b98944bd57940000000000000000000000000000000000000000009d447d54619656add459870000000000000000000000000000000000000000000026f9cd23cec6cec239b8091ecc3df9cfe52bdbf1bad5c6a7f8aa4416fab9b6a61152692d30d6c510c64700000000000000000000000000000000434bf7672b7657411e1824edbbea4cb90000000000000000000000000000000090a1a6ee56d9be0e659774cbe8f956dd0c79375f0e6e921f718640d65d36b4c0881ef22c9b245769236145725556c5b700000000000000000000000000000000ac95e2258648f8f86b3624bbc04b521f00000000000000000000000000000000df4b6ff03783b272e9861bff623530c72e1b4dac392cebfa1bbf4bb424a53a798ddaa129e15cc86f46e66630d4d7cb73000000000000000000000000000000007e3cd333fb85a005b3eabed6bfcb276000000000000000000000000000000000966650d0a5377a80e3d2ed449f4bb63f00000000000000000000000000000000bfe45f2d685be849578a95bea4b8b05300000000000000000000000000000000c63329084db7b06fee20ca2ad3c6556a000000000000000000000000000000008aa035eb40cf8dbc6305e610e3db64fa00000000000000000000000000000000f829076b70470a14fa0189f0dad2e55114096e4fefa74243e5ce6415ebac1c67da20cbf071ad634c5daeebc10bdfe3871ebc208476f676a5edbeecf4625615db1713759b8369a9f34b8edd60680d0cf91b66599205a09d9d83442a26ea247564dd997c78f27a7f52068ecd2205f85ee1161754afd8972ca4a997eb947766b7810bd11c51fd128a8bee09cbd365f05d8c1de171ea0e648a842b4148e48ca5da174bb79e2ab55b5ff4784dbd5779a94574053f6a5cfdd827695c6b940f636eafbf02d0011a4f0323cc250406e36fde5b3f1a8c9dc96fe17890b681dd02a41603181d8d9ed39f929fe322a739253bf27f3f237f8bfa20f2c96d2858f48951331c2422222f2565d5ce845083ec7620d958fc2835b84a88123245872db1e99dbbb9ca754056d04fcc1a22399d1b6b6b5fbaa32bf3abb58f0c546f77a16d9f85c15bf704f6739ca449ae9ffacedebe814f8a0e0ac26ffb0099ee1ea96b0df42e65097de229e7547e85fbaacc4a5acb60cd12d302880e791d02375f820df80e7181c8d23d2fd9d4582a6763976bd2cda445da4806260c020ef50def2f1c3526733d14ae3eaabda36d81e42eb845ad42cf5ccb242ad5385fad14ba2e60446a8500a8cbe5234c23b12ec97e282201a470a5e1d74e0c5b901cf9926a150fc68d7cc4157720851932abfa12e97a4f137d7160f66a0819ce33493d1650e91198a3bcb18229533ce0a3b8f94cc552ba67c61570f0c39e10466870519d81cc59574b69940d50577f94d3e82c3264c285968b9846e7942a05bff80e9104a19f03e125aa35a4cef41640cf6582a5db39fc65769c3812ffc910854b403c662924974a54ee7364433adf6bbe8d9a476b38b3937939f89e0dd4207f175f87c2e5f67dae03944b6f723059e5212a676243f35b9815c921f21a22301c139416ad4a4ab35529f36b40edfb0eee1e4050a4594e92d7e0f745a5fa7010d16534e201eb20c70f6796352912ea3934b85c67cc60c97a8a005821793e9d28b840ece92fa46480371489ee4a95d93c9c7b4f6be3bc033b763b321617c38b0d4b491bf5a6e018d60369634b59424022ace516817acadb7f56096e22e5f6a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000022a36b4b402ddfab69e75da6dbd36ee663188d1b356a61b3e8effd5876de854f310aac531866fa2dbb67a614804a49dc5e739e7778a38d586016157cfc6ae156c0ad2062179e249a6e39f9b693e9ecc4d5f548a11445cc31d68e6deb610c4597012100015a338fe5603cf54211f48851e38874f16ad7c3a9eb78b749f56e8afa720884205aa2b28ff169aa2486feda0639933a23b6ee7d4265a45bc0d48a01587034885f9b3a71581c8e14951a2c5f5b27c5321407384635dba9c209f0f124f2c2b868d56e00a6b0edd2a5e227ee84a9662cd510570fc44339b5e8ba494f138831e8e1fdff21f6982629b01a0280339b0ac115a8996d11e9bd3f82d3c7168092b1b9a5352f18858d80d3dbcefc2aa019a91fa72449030ffafb666c80690c59059130447fd50addc835d37f3a05b8a0b8fbb701c3510d4434ce495d95bf4c2bc341ca1e775a879579306d128968b21b83235e2b1fec03b299cc8ab69ed020bcf0d08aa83970712ba33c5189f870c72d6c8b87cf0c187acee496ba719ff4e96e50c28678c042f25649cdef01f58b10eb416501375fcf6a5c3020975e15f2f92bb6503d4d9afc780b9c93104d6711213f926927ccd741e0ac422687222bd7690224d11fd134ad0910b9bbb7fd72d8e3c7c6506ba1a90a119c636a9af1f6f5883046108d03339155ba1aa0cdcd5179ad1d1939de1d5b3c6a8b66d2846bab2502941cb194ac58bf5e4c3beb43331f16d7bf8df99436fa6582a0b4e659c1ac151981a4f0957f97a10764fc34ab544c25c79748b9948f8851c2219f07694834b26dc19cf093f841aa6cb837f44d80b75258e6aed8fdcac9870cc706cd982a041e87225ae17b81ba9428e6ebedc726671c76c0d6eec34bf5e053fa532860b188d52ae4f1d13e6c8453bf551497f4cad7ef4d9e199ad962babbddc6fb78dd4e69e0fdd1bd3130630fa7eaed8dd53f3014aba72b58425c32b9175cdc3cffa34210b640da1be1a9afd6d641492282c66062c564a80a726839f21e77cde396a553ee84bfd20b405abe4161d22f1f92541d52346257675bfc099e1c34013face204079bd46739801abf6db552dbf26683d35d39e3301374160fe8615e519e182c5f73f78fa9b142e1b7e4123b4e235477b1b95621034092eb466dedc0a082de8b381ccdf221a1809e5a99a8b60c6eeda4f10215febef02e8c826f4bfc7e90d79e037590764b1770c2aa1c7f89290db0cb465db92174cdb936dab88ea9e92bb5e944335f54f75831bdf191677cfbea73b6b247cbb9e112f28e6b90dc27d849dd94126006a9c4d6e000000000000000000000000000000000000000000000000000000000000000127786082d94fa3987c2e62643f343bee3e1b358f55bad5d0c6d228d05faedd0b053b0270a89f0a98c2ba0cf9ff8a1baac2d3903befcf634861abb87f80e7197a237517713689296219adab9e8e9edab73057dfcc7a26d51b7a3512ad82ccfd6816fbd00121ef460740f6970c29595c67c3c473ab416a617d2a0f2e9e1ec019990bde68daf1427ce325e886892219dfaafa28e4c366cd0fbf67aee616241394620f6c139c810de2100b3dbfc81dc43a3910a6bc88c8cc466ab0e17bc8e1379dab258fad6fb1c6cc004d51a8273ae4e4d9e2e920c55074ec261768f11b0cb44f3800a7909323a068b189ea8c49dddcb7209b3162a57ff8609c5723f7ccd86833cf1ecdead24c6948964f6d0d0c9d6da94f4c82a6c5938e1008db19e7fa281f76641c25a074ab91dfcaa700e687b85af1534661690db1f95439670c49e348a04736085e42a19f7c588f14040505b24c562f8f0807d817e00f4851ce42c3e576ba0107d18edc6a5e4978626346e0802f4322a23dfdb148ac1d1dc8a64f07ffb981340c81bbe1a36460a1a30770ff3c78bbd652b67d0d6ca68e65a27f633d543bd0382e6126a0246313bad7614538c1c8ddb6d2f1f6bcc4d40aacb53dd643ff6ad2e50e99c66c034d1623b5817403ea354eee48af4c7030a535048379d2f379d3439b1d64f28659f65ddd002c970767c75eec09ea49a1f804cc4342c6896759ab86b70fb6e3a6e50f5f21a8b9f3cb003b6793c47ad4b9cb626f9b8c072f5da97872521f17acb2edc9a78b7f1a4018c0ada0587f9e304db9bd5d84b755c6d931b5564f03893d49524ac16486b6e6310d9abff487c3105ec628a2c8eebab81e610be7172dfd4174932c821d89d642766a96eeccbf012d6bb543e93b69029a2d4d350f191def608ee8e81ca1aa198b9d51b9c6cc5585a03510b4d6d2c67a932604994ca9052f914e3f0c8b6ac3d32f00475d99ed33d4ab2de040b1f8be937115693300292627c953ad6cadbfbb251bdfdf11bfa5ac0f96219ecb90402a273e2de38ec7a60a842b64faeab7568be1c1d75af8ac9f00ae4452fa31fd06870a3923ccd9d31221d06b6e5c060b2b2ab21ee3fdbe884cd179b46bdfc131ef51b229b116b165753055f405ab17b2f5a47dab8336852dc227afe5b5aeb6b949d22f0887e288cdd9122d4973e35652705d96f16dcb804045ac787841e254fb4a1d958b5d7d70a487 \ No newline at end of file diff --git a/axiom-eth/rust-toolchain b/axiom-eth/rust-toolchain new file mode 100644 index 000000000..51ab47595 --- /dev/null +++ b/axiom-eth/rust-toolchain @@ -0,0 +1 @@ +nightly-2022-10-28 \ No newline at end of file diff --git a/axiom-eth/rustfmt.toml b/axiom-eth/rustfmt.toml new file mode 100644 index 000000000..f5a13f370 --- /dev/null +++ b/axiom-eth/rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 100 +use_small_heuristics = "Max" \ No newline at end of file diff --git a/scripts/input_gen/block.json b/axiom-eth/scripts/input_gen/block.json similarity index 100% rename from scripts/input_gen/block.json rename to axiom-eth/scripts/input_gen/block.json diff --git a/scripts/input_gen/default_storage_pf.json b/axiom-eth/scripts/input_gen/default_storage_pf.json similarity index 100% rename from scripts/input_gen/default_storage_pf.json rename to axiom-eth/scripts/input_gen/default_storage_pf.json diff --git a/scripts/input_gen/empty_storage_pf.json b/axiom-eth/scripts/input_gen/empty_storage_pf.json similarity index 100% rename from scripts/input_gen/empty_storage_pf.json rename to axiom-eth/scripts/input_gen/empty_storage_pf.json diff --git a/scripts/input_gen/headers b/axiom-eth/scripts/input_gen/headers similarity index 100% rename from scripts/input_gen/headers rename to axiom-eth/scripts/input_gen/headers diff --git a/scripts/input_gen/noninclusion_branch_pf.json b/axiom-eth/scripts/input_gen/noninclusion_branch_pf.json similarity index 100% rename from scripts/input_gen/noninclusion_branch_pf.json rename to axiom-eth/scripts/input_gen/noninclusion_branch_pf.json diff --git a/scripts/input_gen/noninclusion_extension_pf.json b/axiom-eth/scripts/input_gen/noninclusion_extension_pf.json similarity index 100% rename from scripts/input_gen/noninclusion_extension_pf.json rename to axiom-eth/scripts/input_gen/noninclusion_extension_pf.json diff --git a/scripts/input_gen/query_test.sh b/axiom-eth/scripts/input_gen/query_test.sh similarity index 100% rename from scripts/input_gen/query_test.sh rename to axiom-eth/scripts/input_gen/query_test.sh diff --git a/axiom-eth/scripts/input_gen/query_test_storage.json b/axiom-eth/scripts/input_gen/query_test_storage.json new file mode 100644 index 000000000..098c4efd3 --- /dev/null +++ b/axiom-eth/scripts/input_gen/query_test_storage.json @@ -0,0 +1,10 @@ +{ + "id": 1, + "jsonrpc": "2.0", + "method": "eth_getProof", + "params": [ + "0x0000000000000000000000000000000000000000", + [], + "0xf929e6" + ] +} diff --git a/scripts/input_gen/serialize_blocks.py b/axiom-eth/scripts/input_gen/serialize_blocks.py similarity index 100% rename from scripts/input_gen/serialize_blocks.py rename to axiom-eth/scripts/input_gen/serialize_blocks.py diff --git a/axiom-eth/src/batch_query/README.md b/axiom-eth/src/batch_query/README.md new file mode 100644 index 000000000..a9730fb2f --- /dev/null +++ b/axiom-eth/src/batch_query/README.md @@ -0,0 +1,3 @@ +# AxiomV1Query ZK Circuits + +Design doc: [here](https://hackmd.io/@axiom/S17K2drf2) diff --git a/axiom-eth/src/batch_query/aggregation/final_response.rs b/axiom-eth/src/batch_query/aggregation/final_response.rs new file mode 100644 index 000000000..81b976abf --- /dev/null +++ b/axiom-eth/src/batch_query/aggregation/final_response.rs @@ -0,0 +1,268 @@ +use std::{cell::RefCell, env::var, iter}; + +use halo2_base::{ + gates::{builder::CircuitBuilderStage, RangeChip, RangeInstructions}, + halo2_proofs::{ + halo2curves::bn256::{Bn256, Fr, G1Affine}, + poly::kzg::commitment::ParamsKZG, + }, +}; +use itertools::Itertools; +use snark_verifier::{loader::halo2::Halo2Loader, util::hash::Poseidon}; +use snark_verifier_sdk::{ + halo2::{aggregation::AggregationCircuit, POSEIDON_SPEC}, + Snark, LIMBS, SHPLONK, +}; + +use crate::{ + batch_query::{ + aggregation::{merklelize_instances, HashStrategy}, + response::{ + account::{ + ACCOUNT_BLOCK_RESPONSE_KECCAK_INDEX, ACCOUNT_FULL_RESPONSE_POSEIDON_INDEX, + ACCOUNT_INSTANCE_SIZE, ACCOUNT_KECCAK_ROOT_INDICES, ACCOUNT_POSEIDON_ROOT_INDICES, + KECCAK_ACCOUNT_FULL_RESPONSE_INDEX, + }, + block_header::{ + BLOCK_INSTANCE_SIZE, BLOCK_KECCAK_ROOT_INDICES, BLOCK_POSEIDON_ROOT_INDICES, + BLOCK_RESPONSE_POSEIDON_INDEX, KECCAK_BLOCK_RESPONSE_INDEX, + }, + row_consistency::{ + ROW_ACCT_BLOCK_KECCAK_INDEX, ROW_ACCT_POSEIDON_INDEX, ROW_BLOCK_POSEIDON_INDEX, + ROW_STORAGE_ACCT_KECCAK_INDEX, ROW_STORAGE_BLOCK_KECCAK_INDEX, + ROW_STORAGE_POSEIDON_INDEX, + }, + storage::{ + KECCAK_STORAGE_FULL_RESPONSE_INDEX, STORAGE_ACCOUNT_RESPONSE_KECCAK_INDEX, + STORAGE_BLOCK_RESPONSE_KECCAK_INDEX, STORAGE_FULL_RESPONSE_POSEIDON_INDEX, + STORAGE_INSTANCE_SIZE, STORAGE_KECCAK_ROOT_INDICES, STORAGE_POSEIDON_ROOT_INDICES, + }, + }, + DummyEccChip, + }, + keccak::{FixedLenRLCs, FnSynthesize, KeccakChip, VarLenRLCs}, + rlp::{ + builder::{RlcThreadBreakPoints, RlcThreadBuilder}, + rlc::FIRST_PHASE, + RlpChip, + }, + util::EthConfigParams, + EthCircuitBuilder, +}; + +/// Circuit that assembles the full response table, verifying +/// block hashes in the table are included in a Merkle Mountain Range (MMR). +/// The MMR will be a commitment to a contiguous list of block hashes, for block numbers `[0, mmr_list_len)`. +/// +/// Public instances: accumulators, followed by 13 field elements: +/// * `poseidon_tree_root(block_responses.poseidon)` // as a field element +/// * `keccak_tree_root(block_responses.keccak)` // 2 field elements, in hi-lo form +/// * `poseidon_tree_root(full_account_responses.poseidon)` // as a field element +/// * `keccak_tree_root(full_account_responses.keccak)` // 2 field elements, in hi-lo form +/// * `poseidon_tree_root(full_storage_response.poseidon)` // as a field element +/// * `keccak_tree_root(full_storage_response.keccak)` // 2 field elements, in hi-lo form +/// * `keccak256(abi.encodePacked(mmr[BLOCK_BATCH_DEPTH..]))` // 2 field elements, H256 in hi-lo form. +/// * `keccak256(abi.encodePacked(mmr[..BLOCK_BATCH_DEPTH]))` as 2 field elements, H256 in hi-lo form. +/// To be clear, `abi.encodedPacked(mmr[d..]) = mmr[d] . mmr[d + 1] . ... . mmr[mmr_num_peaks - 1]` where `.` is concatenation of byte arrays. +#[derive(Clone, Debug)] +pub struct FinalResponseAssemblyCircuit { + /// Snark with merklelized block responses, verified against MMR + pub column_block_snark: Snark, + /// Snark with merklelized account responses + pub column_account_snark: Snark, + /// Snark with merklelized storage responses + pub column_storage_snark: Snark, + /// Snark for checking consistency of each row of the table (block, account, storage) + pub row_consistency_snark: Snark, + /// True if `column_block_snark` was an aggregation circuit + pub column_block_has_accumulator: bool, + /// True if `column_account_snark` was an aggregation circuit + pub column_account_has_accumulator: bool, + /// True if `column_storage_snark` was an aggregation circuit + pub column_storage_has_accumulator: bool, + /// True if `row_consistency_snark` was an aggregation circuit + pub row_consistency_has_accumulator: bool, +} + +impl FinalResponseAssemblyCircuit { + pub fn new( + column_block: (Snark, bool), + column_account: (Snark, bool), + column_storage: (Snark, bool), + row_consistency: (Snark, bool), + ) -> Self { + Self { + column_block_snark: column_block.0, + column_account_snark: column_account.0, + column_storage_snark: column_storage.0, + row_consistency_snark: row_consistency.0, + column_block_has_accumulator: column_block.1, + column_account_has_accumulator: column_account.1, + column_storage_has_accumulator: column_storage.1, + row_consistency_has_accumulator: row_consistency.1, + } + } +} + +impl FinalResponseAssemblyCircuit { + fn create( + self, + stage: CircuitBuilderStage, + break_points: Option, + lookup_bits: usize, + params: &ParamsKZG, + ) -> EthCircuitBuilder> { + log::info!("New FinalResponseAggregationCircuit",); + // aggregate the snarks + let aggregation = AggregationCircuit::new::( + stage, + Some(Vec::new()), // break points aren't actually used, since we will just take the builder from this circuit + lookup_bits, + params, + [ + self.column_block_snark, + self.column_account_snark, + self.column_storage_snark, + self.row_consistency_snark, + ], + ); + let (block_instance, account_instance, storage_instance, row_consistency_instance) = + aggregation + .previous_instances + .iter() + .zip_eq([ + self.column_block_has_accumulator, + self.column_account_has_accumulator, + self.column_storage_has_accumulator, + self.row_consistency_has_accumulator, + ]) + .map(|(instance, has_accumulator)| { + let start = (has_accumulator as usize) * 4 * LIMBS; + &instance[start..] + }) + .collect_tuple() + .unwrap(); + + // TODO: should reuse RangeChip from aggregation circuit, but can't refactor right now + let range = RangeChip::default(lookup_bits); + let gate_builder = aggregation.inner.circuit.0.builder.take(); + let _chip = DummyEccChip(range.gate()); + let loader = Halo2Loader::::new(_chip, gate_builder); + + let mut keccak = KeccakChip::default(); + let mut poseidon = Poseidon::from_spec(&loader, POSEIDON_SPEC.clone()); + + let (block_instance, verify_mmr_instance) = + block_instance.split_at(block_instance.len() - 4); + let block_instance = merklelize_instances( + HashStrategy::Tree, + block_instance, + BLOCK_INSTANCE_SIZE - 4, // exclude mmrs + BLOCK_POSEIDON_ROOT_INDICES, + BLOCK_KECCAK_ROOT_INDICES, + &loader, + &mut poseidon, + &range, + &mut keccak, + ); + let account_instance = merklelize_instances( + HashStrategy::Onion, + account_instance, + ACCOUNT_INSTANCE_SIZE, + ACCOUNT_POSEIDON_ROOT_INDICES, + ACCOUNT_KECCAK_ROOT_INDICES, + &loader, + &mut poseidon, + &range, + &mut keccak, + ); + let storage_instance = merklelize_instances( + HashStrategy::Onion, + storage_instance, + STORAGE_INSTANCE_SIZE, + STORAGE_POSEIDON_ROOT_INDICES, + STORAGE_KECCAK_ROOT_INDICES, + &loader, + &mut poseidon, + &range, + &mut keccak, + ); + + let mut gate_builder = loader.take_ctx(); + let ctx = gate_builder.main(FIRST_PHASE); + // each root in row consistency circuit must match the corresponding root in the other column circuits + ctx.constrain_equal( + &row_consistency_instance[ROW_BLOCK_POSEIDON_INDEX], + &block_instance[BLOCK_RESPONSE_POSEIDON_INDEX], + ); + ctx.constrain_equal( + &row_consistency_instance[ROW_ACCT_POSEIDON_INDEX], + &account_instance[ACCOUNT_FULL_RESPONSE_POSEIDON_INDEX], + ); + ctx.constrain_equal( + &row_consistency_instance[ROW_ACCT_BLOCK_KECCAK_INDEX], + &account_instance[ACCOUNT_BLOCK_RESPONSE_KECCAK_INDEX], + ); + ctx.constrain_equal( + &row_consistency_instance[ROW_STORAGE_POSEIDON_INDEX], + &storage_instance[STORAGE_FULL_RESPONSE_POSEIDON_INDEX], + ); + ctx.constrain_equal( + &row_consistency_instance[ROW_STORAGE_BLOCK_KECCAK_INDEX], + &storage_instance[STORAGE_BLOCK_RESPONSE_KECCAK_INDEX], + ); + ctx.constrain_equal( + &row_consistency_instance[ROW_STORAGE_ACCT_KECCAK_INDEX], + &storage_instance[STORAGE_ACCOUNT_RESPONSE_KECCAK_INDEX], + ); + + // All computations are contained in the `aggregations`'s builder, so we take that to create a new RlcThreadBuilder + let builder = RlcThreadBuilder { threads_rlc: Vec::new(), gate_builder }; + let mut assigned_instances = aggregation.inner.assigned_instances; + + // add new public instances + assigned_instances.extend( + iter::once(block_instance[BLOCK_RESPONSE_POSEIDON_INDEX]) + .chain(block_instance[KECCAK_BLOCK_RESPONSE_INDEX..].iter().take(2).copied()) + .chain(iter::once(account_instance[ACCOUNT_FULL_RESPONSE_POSEIDON_INDEX])) + .chain( + account_instance[KECCAK_ACCOUNT_FULL_RESPONSE_INDEX..].iter().take(2).copied(), + ) + .chain(iter::once(storage_instance[STORAGE_FULL_RESPONSE_POSEIDON_INDEX])) + .chain( + storage_instance[KECCAK_STORAGE_FULL_RESPONSE_INDEX..].iter().take(2).copied(), + ) + .chain(verify_mmr_instance.iter().copied()), + ); + + EthCircuitBuilder::new( + assigned_instances, + builder, + RefCell::new(keccak), + range, + break_points, + |_: &mut RlcThreadBuilder, + _: RlpChip, + _: (FixedLenRLCs, VarLenRLCs)| {}, + ) + } + + pub fn create_circuit( + self, + stage: CircuitBuilderStage, + break_points: Option, + lookup_bits: usize, + params: &ParamsKZG, + ) -> EthCircuitBuilder> { + let circuit = self.create(stage, break_points, lookup_bits, params); + #[cfg(not(feature = "production"))] + if stage != CircuitBuilderStage::Prover { + let config_params: EthConfigParams = serde_json::from_str( + var("ETH_CONFIG_PARAMS").expect("ETH_CONFIG_PARAMS is not set").as_str(), + ) + .unwrap(); + circuit.config(config_params.degree as usize, Some(config_params.unusable_rows)); + } + circuit + } +} diff --git a/axiom-eth/src/batch_query/aggregation/merkle.rs b/axiom-eth/src/batch_query/aggregation/merkle.rs new file mode 100644 index 000000000..dbe1e7bf5 --- /dev/null +++ b/axiom-eth/src/batch_query/aggregation/merkle.rs @@ -0,0 +1,131 @@ +//! Aggregation circuit involving both Poseidon and Keccak hashes. This is used for +//! aggregation of a single column response, from initial circuits such as +//! `Multi{Block,Account,Storage}Circuit`. + +use std::rc::Rc; + +use halo2_base::{ + gates::{RangeChip, RangeInstructions}, + halo2_proofs::halo2curves::CurveAffine, + AssignedValue, +}; +use itertools::Itertools; +use snark_verifier::{ + loader::halo2::{Halo2Loader, Scalar}, + util::hash::Poseidon, +}; + +use crate::{ + batch_query::{ + hash::{keccak_packed, poseidon_onion, poseidon_tree_root}, + response::FixedByteArray, + EccInstructions, + }, + keccak::KeccakChip, + util::{bytes_be_to_u128, u128s_to_bytes_be}, + Field, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum HashStrategy { + Tree, + Onion, +} + +/// Aggregates snarks and computes keccak and Poseidon Merkle roots of previous public instances. +/// +/// We assume public instances in previous `snarks`, other than old accumulators, come in repeating chunks of size `chunk_size`. +/// +/// Computes the Keccak Merkle roots +/// * `keccak_strategy[H256(instance[chunk_size * j + i .. chunk_size * j + i + 2]) for all j])` for each `i` in `keccak_indices`. +/// +/// and the Poseidon Merkle roots +/// * `poseidon_strategy([instance[chunk_size * j + i] for all j])` for each `i` in `poseidon_indices` +/// +/// where `H256([hi,lo])` assumes that `hi, lo` are `u128` and forms the 256-bit integer `hi << 128 | lo`. +/// +/// Here `keccak_strategy` means `keccak_merkle_root` if `strategy == Tree` or `keccak(a_0 . keccak(a_1 . keccak( ... )))` if `strategy == Onion`. +/// Similarly for `poseidon`. +/// +/// Returns: +/// * Poseidon roots (`poseidon_indices.len()` field elements) +/// * Keccak roots (`keccak_indices.len() * 2` field elements in hi-lo u128 form) +/// +/// # Panics +/// If `strategy == Tree` and `instance.len() / chunk_size` is not a power of 2. +#[allow(clippy::too_many_arguments)] +pub fn merklelize_instances( + strategy: HashStrategy, + instances: &[AssignedValue], + chunk_size: usize, + poseidon_indices: &[usize], + keccak_indices: &[usize], + loader: &Rc>, + poseidon: &mut Poseidon, T, RATE>, + range: &RangeChip, + keccak: &mut KeccakChip, +) -> Vec> +where + F: Field, + C: CurveAffine, + EccChip: EccInstructions, +{ + for idx in poseidon_indices.iter().chain(keccak_indices.iter()) { + assert!(*idx < chunk_size, "poseidon and keccak indices must be < chunk_size"); + } + let mut tmp_builder = loader.ctx_mut(); + // keccak tree root + let ctx = tmp_builder.main(0); + // get hi-lo u128s from previous instances and convert to bytes + let mut keccak_leaves = vec![vec![]; keccak_indices.len()]; + for chunk in instances.chunks_exact(chunk_size) { + for (leaves, &idx) in keccak_leaves.iter_mut().zip(keccak_indices.iter()) { + let hi_lo = &chunk[idx..idx + 2]; + leaves.push(u128s_to_bytes_be(ctx, range, hi_lo)); + } + } + // compute the keccak merkle roots + let keccak_roots = keccak_leaves + .iter() + .flat_map(|leaves| { + let bytes = match strategy { + HashStrategy::Tree => keccak.merkle_tree_root(ctx, range.gate(), leaves), + HashStrategy::Onion => { + let mut onion = FixedByteArray(leaves[0].clone()); + for leaf in &leaves[1..] { + onion = keccak_packed( + ctx, + range.gate(), + keccak, + FixedByteArray([onion.as_ref(), &leaf[..]].concat()), + ); + } + onion.0 + } + }; + bytes_be_to_u128(ctx, range.gate(), &bytes) + }) + .collect_vec(); + debug_assert_eq!(keccak_roots.len(), keccak_indices.len() * 2); + drop(tmp_builder); + + // compute the poseidon merkle roots + // load field elements from prev instances to Scalar + let mut poseidon_leaves = vec![vec![]; poseidon_indices.len()]; + for chunk in instances.chunks_exact(chunk_size) { + for (leaves, &idx) in poseidon_leaves.iter_mut().zip(poseidon_indices.iter()) { + leaves.push(loader.scalar_from_assigned(chunk[idx])); + } + } + let poseidon_roots = poseidon_leaves + .into_iter() + .map(|leaves| match strategy { + HashStrategy::Tree => poseidon_tree_root(poseidon, leaves, &[]).into_assigned(), + HashStrategy::Onion => { + poseidon_onion(poseidon, leaves.into_iter().map(|leaf| leaf.into())).into_assigned() + } + }) + .collect_vec(); + + [keccak_roots, poseidon_roots].concat() +} diff --git a/axiom-eth/src/batch_query/aggregation/mod.rs b/axiom-eth/src/batch_query/aggregation/mod.rs new file mode 100644 index 000000000..4ac5502ea --- /dev/null +++ b/axiom-eth/src/batch_query/aggregation/mod.rs @@ -0,0 +1,7 @@ +mod final_response; +mod merkle; +mod poseidon; + +pub use final_response::*; +pub use merkle::*; +pub use poseidon::*; diff --git a/axiom-eth/src/batch_query/aggregation/poseidon.rs b/axiom-eth/src/batch_query/aggregation/poseidon.rs new file mode 100644 index 000000000..69ea99ebf --- /dev/null +++ b/axiom-eth/src/batch_query/aggregation/poseidon.rs @@ -0,0 +1,193 @@ +//! Aggregation circuits involving only Poseidon hashes, used for aggregation of +//! `RowConsistencyCircuit` and `VerifyVsMmrCircuit`. + +use halo2_base::{ + gates::{ + builder::{CircuitBuilderStage, MultiPhaseThreadBreakPoints}, + GateChip, + }, + halo2_proofs::{ + halo2curves::bn256::{Bn256, G1Affine}, + poly::kzg::commitment::ParamsKZG, + }, +}; +use itertools::Itertools; +use snark_verifier::{loader::halo2::Halo2Loader, util::hash::Poseidon}; +use snark_verifier_sdk::{ + halo2::{aggregation::AggregationCircuit, POSEIDON_SPEC}, + Snark, LIMBS, +}; + +use crate::{ + batch_query::{ + hash::{poseidon_onion, poseidon_tree_root}, + DummyEccChip, + }, + rlp::rlc::FIRST_PHASE, + util::circuit::PublicAggregationCircuit, + AggregationPreCircuit, +}; + +use super::HashStrategy; + +/// Aggregates snarks and computes *possibly multiple* Poseidon Merkle roots of previous public instances. +/// +/// See [`PublicAggregationCircuit`] for `snarks` format. +/// +/// Assumes public instances of previous `snarks`, excluding old accumulators, come in tuples of `num_roots` field elements. +/// +/// The circuit concatenates all previous public instances, aside from old accumulators, into `instance` and computes `num_roots` Poseidon Merkle roots. +/// * `i`th Poseidon Merkle root is computed from `instance[j * num_roots + i]` for all `j` +/// +/// Public instances of the circuit are the accumulators, followed by: +/// * `num_roots` Poseidon roots (each a single field element) +// +// This is same as `MerkleAggregationCircuit`, but with empty `keccak_indices`. This means we don't need `KeccakChip`. +#[derive(Clone, Debug)] +pub struct PoseidonAggregationCircuit { + pub strategy: HashStrategy, + pub snarks: Vec<(Snark, bool)>, + pub num_roots: usize, +} + +impl PoseidonAggregationCircuit { + pub fn new(strategy: HashStrategy, snarks: Vec<(Snark, bool)>, num_roots: usize) -> Self { + assert!(!snarks.is_empty(), "no snarks to aggregate"); + let mut total_instances = 0; + for (snark, has_acc) in &snarks { + let start = (*has_acc as usize) * 4 * LIMBS; + let n = snark.instances.iter().map(|x| x.len()).sum::() - start; + assert_eq!(n % num_roots, 0, "snark does not have correct number of instances"); + total_instances += n; + } + let num_leaves = total_instances / num_roots; + assert!(num_leaves > 0, "no leaves to merklelize"); + Self { strategy, snarks, num_roots } + } +} + +impl AggregationPreCircuit for PoseidonAggregationCircuit { + fn create( + self, + stage: CircuitBuilderStage, + break_points: Option, + lookup_bits: usize, + params: &ParamsKZG, + ) -> AggregationCircuit { + log::info!( + "New PoseidonAggregationCircuit | num_snarks: {} | num_roots: {}", + self.snarks.len(), + self.num_roots + ); + // aggregate the snarks + let mut aggregation = PublicAggregationCircuit::new(self.snarks).private( + stage, + break_points, + lookup_bits, + params, + ); + let previous_instances = &aggregation.previous_instances; + + let builder = aggregation.inner.circuit.0.builder.take(); + // TODO: should reuse GateChip from aggregation circuit, but can't refactor right now + let gate = GateChip::default(); + let _chip = DummyEccChip(&gate); + let loader = Halo2Loader::::new(_chip, builder); + // load field elements from prev instances to Scalar + let mut poseidon_leaves = vec![vec![]; self.num_roots]; + for prev_instance in previous_instances { + for hashes in prev_instance.chunks_exact(self.num_roots) { + for (hash, poseidon_leaves) in hashes.iter().zip_eq(poseidon_leaves.iter_mut()) { + poseidon_leaves.push(loader.scalar_from_assigned(*hash)); + } + } + } + let mut poseidon = Poseidon::from_spec(&loader, POSEIDON_SPEC.clone()); + let poseidon_roots = poseidon_leaves + .into_iter() + .map(|leaves| match self.strategy { + HashStrategy::Tree => { + poseidon_tree_root(&mut poseidon, leaves, &[]).into_assigned() + } + HashStrategy::Onion => { + poseidon_onion(&mut poseidon, leaves.into_iter().map(|leaf| leaf.into())) + .into_assigned() + } + }) + .collect_vec(); + // put builder back + aggregation.inner.circuit.0.builder.replace(loader.take_ctx()); + // add new public instances + aggregation.inner.assigned_instances.extend(poseidon_roots); + + aggregation + } +} + +// Special circuit, not worth generalizing to avoid confusion: + +/// Special circuit just for aggregating [`crate::batch_query::response::block_header::MultiBlockCircuit`] +/// +/// Assumes public instances of previous `snarks`, excluding old accumulators, have the form: +/// * ... +/// * `historical_mmr_keccak`: an H256, 2 field elements in hi-lo form +/// * `recent_mmr_keccak`: an H256, 2 field elements in hi-lo form +/// +/// The circuit passes through all previous public instances, excluding accumulators **and** excluding `historical_mmr_keccak` and `recent_mmr_keccak`. +/// +/// The circuit constrains that `historical_mmr_keccak[i] = historical_mmr_keccak[j]` and `recent_mmr_keccak[i] = recent_mmr_keccak[j]` for all `i, j` +/// +/// Public instances of the circuit is the accumulator, followed by: +/// * ...Pass through previous instances +/// * `historical_mmr_keccak` +/// * `recent_mmr_keccak` +#[derive(Clone, Debug)] +pub struct MultiBlockAggregationCircuit { + pub snarks: Vec<(Snark, bool)>, +} + +impl MultiBlockAggregationCircuit { + pub fn new(snarks: Vec<(Snark, bool)>) -> Self { + Self { snarks } + } +} + +impl AggregationPreCircuit for MultiBlockAggregationCircuit { + fn create( + self, + stage: CircuitBuilderStage, + break_points: Option, + lookup_bits: usize, + params: &ParamsKZG, + ) -> AggregationCircuit { + log::info!("New VerifyVsMmrAggregationCircuit | num_snarks: {}", self.snarks.len(),); + // aggregate the snarks + let mut aggregation = PublicAggregationCircuit::new(self.snarks).private( + stage, + break_points, + lookup_bits, + params, + ); + let previous_instances = &aggregation.previous_instances; + let len0 = previous_instances[0].len(); + + let mut builder = aggregation.inner.circuit.0.builder.borrow_mut(); + let ctx = builder.main(FIRST_PHASE); + for prev_instance in previous_instances.iter().skip(1) { + let len = prev_instance.len(); + for i in 1..=4 { + ctx.constrain_equal(&previous_instances[0][len0 - i], &prev_instance[len - i]); + } + } + drop(builder); + // add new public instances + aggregation.inner.assigned_instances.extend( + previous_instances + .iter() + .flat_map(|instance| instance[..instance.len() - 4].to_vec()) + .chain(previous_instances[0][len0 - 4..].to_vec()), + ); + + aggregation + } +} diff --git a/axiom-eth/src/batch_query/hash/keccak.rs b/axiom-eth/src/batch_query/hash/keccak.rs new file mode 100644 index 000000000..5c0ecee3f --- /dev/null +++ b/axiom-eth/src/batch_query/hash/keccak.rs @@ -0,0 +1,44 @@ +//! Helper module for doing Keccak hashes +use crate::{ + batch_query::{response::FixedByteArray, EccInstructions}, + keccak::KeccakChip, + Field, +}; +use ethers_core::utils::keccak256; +use halo2_base::{gates::GateInstructions, AssignedValue, Context}; +use lazy_static::lazy_static; + +lazy_static! { + static ref KECCAK_EMPTY_STRING: [u8; 32] = keccak256([]); +} + +pub fn keccak_packed( + ctx: &mut Context, + gate: &impl GateInstructions, + keccak: &mut KeccakChip, + words: FixedByteArray, +) -> FixedByteArray { + FixedByteArray(if words.0.is_empty() { + KECCAK_EMPTY_STRING + .iter() + .map(|b| ctx.load_witness(gate.get_field_element(*b as u64))) + .collect() + } else { + let hash_id = keccak.keccak_fixed_len(ctx, gate, words.0, None); + keccak.fixed_len_queries[hash_id].output_assigned.clone() + }) +} + +/// Assumes that `sel` is a bit (either 0 or 1). +/// Returns `bytes` if `sel` is 1, otherwise replaces every byte in `bytes` with 0. +pub(crate) fn bytes_select_or_zero( + ctx: &mut Context, + gate: &impl GateInstructions, + mut bytes: FixedByteArray, + sel: AssignedValue, +) -> FixedByteArray { + for byte in bytes.0.iter_mut() { + *byte = gate.mul(ctx, *byte, sel); + } + bytes +} diff --git a/axiom-eth/src/batch_query/hash/mod.rs b/axiom-eth/src/batch_query/hash/mod.rs new file mode 100644 index 000000000..1baa38b7b --- /dev/null +++ b/axiom-eth/src/batch_query/hash/mod.rs @@ -0,0 +1,10 @@ +//! We need to compute hash concentation and Merkle tree root for Poseidon and Keccak, +//! both in Halo2 and Native (Rust) +//! +//! We use the [`snark-verifier`] implementation of Poseidon which uses the `Loader` trait +//! to deal with Halo2 and Native loader simultaneously. + +mod keccak; +mod poseidon; +pub use keccak::*; +pub use poseidon::*; diff --git a/axiom-eth/src/batch_query/hash/poseidon.rs b/axiom-eth/src/batch_query/hash/poseidon.rs new file mode 100644 index 000000000..03fd1739f --- /dev/null +++ b/axiom-eth/src/batch_query/hash/poseidon.rs @@ -0,0 +1,352 @@ +use std::rc::Rc; + +use halo2_base::{ + halo2_proofs::halo2curves::{bn256::Fr, CurveAffine, FieldExt}, + AssignedValue, +}; +use itertools::Itertools; +use lazy_static::lazy_static; +use snark_verifier::{ + loader::{ + halo2::{Halo2Loader, Scalar}, + LoadedScalar, ScalarLoader, + }, + util::hash::Poseidon, +}; +use snark_verifier_sdk::{halo2::POSEIDON_SPEC, NativeLoader}; + +use crate::batch_query::EccInstructions; + +use self::shim::SelectLoader; + +lazy_static! { + pub static ref POSEIDON_EMPTY_ROOTS: Vec = generate_poseidon_empty_roots(32); +} + +/// An array of field elements that can be concatenated and hashed by Poseidon hasher. +/// Assumed to be of known fixed length. +#[derive(Clone, Debug)] +pub struct PoseidonWords(pub(crate) Vec); + +impl From for PoseidonWords { + fn from(x: L) -> Self { + Self(vec![x]) + } +} + +impl From> for PoseidonWords { + fn from(x: Option) -> Self { + Self(x.into_iter().collect()) + } +} + +impl AsRef<[L]> for PoseidonWords { + fn as_ref(&self) -> &[L] { + &self.0 + } +} + +impl PoseidonWords { + pub fn concat(&self, other: &Self) -> Self { + Self([&self.0[..], &other.0[..]].concat()) + } +} + +impl> PoseidonWords> { + pub fn from_witness( + loader: &Rc>, + witness: impl AsRef<[C::Scalar]>, + ) -> Self { + Self(witness.as_ref().iter().map(|x| loader.assign_scalar(*x)).collect()) + } +} + +/// A struct for an array of field elements that is either: +/// * of known fixed length `N`, or +/// * empty +/// If `is_some` is `None`, then the array is assumed to be of known fixed length `N`, in which case this is the same as `PoseidonWords`. +/// Otherwise `is_some` is a boolean value that indicates whether the array is empty or not. +/// In the case `is_some = Some(0)`, then `words` should still be a dummy array of the known fixed length `N`. +#[derive(Clone, Debug)] +pub struct OptPoseidonWords { + pub(crate) words: Vec, + pub(crate) is_some: Option, +} + +impl From for OptPoseidonWords { + fn from(x: L) -> Self { + Self { words: vec![x], is_some: None } + } +} + +impl From> for OptPoseidonWords { + fn from(x: PoseidonWords) -> Self { + Self { words: x.0, is_some: None } + } +} + +/// Computes the Poseidon hash of an array of field elements by adding them sequentially to buffer +/// and then squeezing once. +pub fn poseidon_packed, const T: usize, const RATE: usize>( + hasher: &mut Poseidon, + words: PoseidonWords, +) -> L { + hasher.clear(); // reset state + hasher.update(&words.0); + hasher.squeeze() +} + +pub(crate) fn poseidon_onion, const T: usize, const RATE: usize>( + hasher: &mut Poseidon, + leaves: impl IntoIterator>, +) -> L { + let mut leaves = leaves.into_iter(); + let mut onion = leaves.next().expect("leaves must be non-empty"); + for leaf in leaves { + onion = poseidon_packed(hasher, onion.concat(&leaf)).into(); + } + onion.0[0].clone() +} + +/// Computes the Poseidon Merkle root of a tree with leaves `leaves` +/// where each leaf is a either (1) a fixed length array of words or (2) empty array. +/// +/// The hash of two leaves is computed by concatenating the leaves and hashing the concatenation. +/// If there is a single leaf, we hash the leaf if it has length > 1, otherwise we return the leaf itself. +/// We assume the input is never a single leaf of the empty array. +/// +/// Returns the Merkle tree root as a single field element. +/// +/// Assumes `leaves` is non-empty. If `leaves` has length 1, then the leaf must be a fixed length array; in this case +/// we hash the leaf if it has length > 1, otherwise we return the leaf itself. +/// Does not assume `leaves.len()` is a power of two. If it is not, the tree is padded with leaves that are empty arrays. +/// +/// As an optimization, we pass in pre-computed empty Poseidon Merkle roots, where `poseidon_empty_roots[i]` +/// is the root of a tree of height `i + 1` with all empty leaves (so `poseidon_empty_roots[0] = poseidon([])`). +/// This function will panic if `poseidon_empty_roots.len()` < log2_floor( 2log2_ceil(leaves.len()) - leaves.len()). +// We could optimize even more by keeping a cache of the assigned constants for `poseidon_empty_roots`, +// but we'll avoid increasing code. +pub(crate) fn poseidon_tree_root( + hasher: &mut Poseidon, + leaves: Vec, + poseidon_empty_roots: &[F], +) -> L +where + F: FieldExt, + L: LoadedScalar, + L::Loader: SelectLoader, + W: Into>, +{ + let mut len = leaves.len(); + assert!(len > 0, "leaves must be non-empty"); + + if len == 1 { + let leaf: OptPoseidonWords<_> = leaves.into_iter().next().unwrap().into(); + assert!(leaf.is_some.is_none(), "single leaf must be fixed length array"); + let words = leaf.words; + assert!(!words.is_empty()); + if words.len() == 1 { + return words.into_iter().next().unwrap(); + } else { + return poseidon_packed(hasher, PoseidonWords(words)); + } + } + + let mut hashes = Vec::with_capacity((len + 1) / 2); + for mut pair in leaves.into_iter().chunks(2).into_iter() { + let left: OptPoseidonWords = pair.next().unwrap().into(); + let right: OptPoseidonWords = + pair.next().map(Into::into).unwrap_or_else(|| PoseidonWords(vec![]).into()); + hashes.push(poseidon_opt_pair(hasher, left, right)); + } + + len = (len + 1) / 2; + debug_assert_eq!(len, hashes.len()); + let mut level = 0; + while len > 1 { + for i in 0..(len + 1) / 2 { + let concat = if 2 * i + 1 < len { + vec![hashes[2 * i].clone(), hashes[2 * i + 1].clone()] + } else { + let empty_root = hashes[2 * i].loader().load_const( + poseidon_empty_roots.get(level).expect("poseidon_empty_roots too short"), + ); + vec![hashes[2 * i].clone(), empty_root] + }; + hashes[i] = poseidon_packed(hasher, PoseidonWords(concat)); + } + len = (len + 1) / 2; + level += 1; + } + hashes.into_iter().next().unwrap() +} + +/// Computes poseidon(left, right), taking into account the possibility that either left or right may be empty. +pub fn poseidon_opt_pair( + hasher: &mut Poseidon, + left: OptPoseidonWords, + right: OptPoseidonWords, +) -> L +where + F: FieldExt, + L: LoadedScalar, + L::Loader: SelectLoader, +{ + if let Some(is_some) = left.is_some { + let some_any = poseidon_opt_pair(hasher, PoseidonWords(left.words).into(), right.clone()); + let none_any = poseidon_opt_pair(hasher, PoseidonWords(vec![]).into(), right); + let loader = is_some.loader().clone(); + loader.select(some_any, none_any, is_some) + } else if let Some(is_some) = right.is_some { + let left = PoseidonWords(left.words); + let some_some = poseidon_packed(hasher, left.concat(&PoseidonWords(right.words))); + let some_none = poseidon_packed(hasher, left); + let loader = is_some.loader().clone(); + loader.select(some_some, some_none, is_some) + } else { + poseidon_packed(hasher, PoseidonWords([&left.words[..], &right.words[..]].concat())) + } +} + +/// Creates a Merkle proof proving inclusion of node `leaves[index]` into a tree with leaves `leaves`. +/// Assumes `leaves.len()` is a power of two. +pub fn create_merkle_proof( + hasher: &mut Poseidon, + leaves: Vec>, + index: usize, +) -> Vec> +where + F: FieldExt, + L: LoadedScalar, +{ + let mut len = leaves.len(); + assert!(len.is_power_of_two()); + let mut proof = Vec::with_capacity(len.ilog2() as usize); + let mut idx = index; + let mut current_hashes = leaves; + while len > 1 { + proof.push(current_hashes[idx ^ 1].clone()); + for i in 0..len / 2 { + current_hashes[i] = + poseidon_packed(hasher, current_hashes[2 * i].concat(¤t_hashes[2 * i + 1])) + .into(); + } + idx >>= 1; + len /= 2; + } + proof +} + +/// Computes the Poseidon Merkle root by traversing the Merkle proof. +pub fn traverse_merkle_proof( + hasher: &mut Poseidon, + proof: &[PoseidonWords], + leaf: PoseidonWords, + side: usize, +) -> PoseidonWords +where + F: FieldExt, + L: LoadedScalar, +{ + let mut current_hash = leaf; + for (i, node) in proof.iter().enumerate() { + if (side >> i) & 1 == 0 { + current_hash = poseidon_packed(hasher, current_hash.concat(node)).into(); + } else { + current_hash = poseidon_packed(hasher, node.concat(¤t_hash)).into(); + } + } + current_hash +} + +/// Assumes that `sel` is a bit (either 0 or 1). +/// Returns `word` if `sel` is 1, otherwise returns 0. +pub(crate) fn word_select_or_zero( + loader: &Rc>, + word: Scalar, + sel: AssignedValue, +) -> Scalar +where + F: FieldExt, + C: CurveAffine, + EccChip: EccInstructions, +{ + let sel = loader.scalar_from_assigned(sel); + word * &sel +} + +fn generate_poseidon_empty_roots(len: usize) -> Vec { + let mut hasher = Poseidon::from_spec(&NativeLoader, POSEIDON_SPEC.clone()); + let mut empty_roots = Vec::with_capacity(len); + empty_roots.push(poseidon_packed(&mut hasher, PoseidonWords(vec![]))); + for _ in 1..len { + let last = *empty_roots.last().unwrap(); + empty_roots.push(poseidon_packed(&mut hasher, PoseidonWords(vec![last, last]))); + } + empty_roots +} + +mod shim { + use snark_verifier::loader::ScalarLoader; + + pub trait SelectLoader: ScalarLoader + Clone { + fn select( + &self, + if_true: Self::LoadedScalar, + if_false: Self::LoadedScalar, + cond: Self::LoadedScalar, + ) -> Self::LoadedScalar; + } + + mod halo2_lib { + use crate::batch_query::EccInstructions; + use std::rc::Rc; + + use crate::{rlp::rlc::FIRST_PHASE, Field}; + use halo2_base::{gates::GateInstructions, halo2_proofs::halo2curves::CurveAffine}; + use snark_verifier::loader::halo2::Halo2Loader; + + use super::SelectLoader; + + impl SelectLoader for Rc> + where + C: CurveAffine, + C::Scalar: Field, + EccChip: EccInstructions, + { + fn select( + &self, + if_true: Self::LoadedScalar, + if_false: Self::LoadedScalar, + cond: Self::LoadedScalar, + ) -> Self::LoadedScalar { + let mut builder = self.ctx_mut(); + let ctx = builder.main(FIRST_PHASE); + let out = self.scalar_chip().select( + ctx, + if_true.into_assigned(), + if_false.into_assigned(), + cond.into_assigned(), + ); + self.scalar_from_assigned(out) + } + } + } + + mod native { + use halo2_base::halo2_proofs::halo2curves::FieldExt; + use snark_verifier_sdk::NativeLoader; + + use super::SelectLoader; + + impl SelectLoader for NativeLoader { + fn select(&self, if_true: F, if_false: F, cond: F) -> F { + if bool::from(cond.is_zero()) { + if_false + } else { + if_true + } + } + } + } +} diff --git a/axiom-eth/src/batch_query/mod.rs b/axiom-eth/src/batch_query/mod.rs new file mode 100644 index 000000000..ade005b10 --- /dev/null +++ b/axiom-eth/src/batch_query/mod.rs @@ -0,0 +1,92 @@ +use halo2_base::{ + gates::{builder::GateThreadBuilder, GateChip}, + halo2_proofs::halo2curves::CurveAffine, + utils::{BigPrimeField, ScalarField}, + AssignedValue, +}; + +#[cfg(feature = "aggregation")] +pub mod aggregation; +pub mod hash; +pub mod response; +#[cfg(feature = "aggregation")] +pub mod scheduler; + +#[cfg(feature = "providers")] +#[cfg(test)] +mod tests; + +pub trait EccInstructions = + snark_verifier::loader::halo2::EccInstructions< + C, + Context = GateThreadBuilder, + ScalarChip = GateChip, + AssignedScalar = AssignedValue, + >; + +/// We unfortunately need Halo2Loader to use Poseidon in circuit, which requires `EccChip: EccInstructions`. +/// Since we don't actually use the EccInstructions, we make a dummy chip wrapping just `GateChip`. +#[derive(Clone, Debug)] +pub(crate) struct DummyEccChip<'a, C: CurveAffine>(&'a GateChip) +where + C::ScalarExt: ScalarField; + +impl<'a, C: CurveAffine> snark_verifier::loader::halo2::EccInstructions for DummyEccChip<'a, C> +where + C::ScalarExt: BigPrimeField, +{ + type Context = GateThreadBuilder; + type ScalarChip = GateChip; + type AssignedScalar = AssignedValue; + type AssignedCell = AssignedValue; + type AssignedEcPoint = (); + + fn scalar_chip(&self) -> &Self::ScalarChip { + self.0 + } + + fn assign_constant(&self, _: &mut Self::Context, _: C) -> Self::AssignedEcPoint { + unreachable!(); + } + + fn assign_point(&self, _: &mut Self::Context, _: C) -> Self::AssignedEcPoint { + unreachable!(); + } + + fn sum_with_const( + &self, + _: &mut Self::Context, + _: &[impl lazy_static::__Deref], + _: C, + ) -> Self::AssignedEcPoint { + unreachable!(); + } + + fn fixed_base_msm( + &mut self, + _: &mut Self::Context, + _: &[(impl lazy_static::__Deref, C)], + ) -> Self::AssignedEcPoint { + unreachable!(); + } + + fn variable_base_msm( + &mut self, + _: &mut Self::Context, + _: &[( + impl lazy_static::__Deref, + impl lazy_static::__Deref, + )], + ) -> Self::AssignedEcPoint { + unreachable!(); + } + + fn assert_equal( + &self, + _: &mut Self::Context, + _: &Self::AssignedEcPoint, + _: &Self::AssignedEcPoint, + ) { + unreachable!(); + } +} diff --git a/axiom-eth/src/batch_query/response/account.rs b/axiom-eth/src/batch_query/response/account.rs new file mode 100644 index 000000000..bfa88c115 --- /dev/null +++ b/axiom-eth/src/batch_query/response/account.rs @@ -0,0 +1,374 @@ +//! Account Response +use super::*; +use crate::{ + batch_query::{ + hash::{ + bytes_select_or_zero, keccak_packed, poseidon_packed, poseidon_tree_root, + word_select_or_zero, + }, + response::storage::DEFAULT_STORAGE_QUERY, + DummyEccChip, + }, + keccak::{FixedLenRLCs, FnSynthesize, KeccakChip, VarLenRLCs}, + rlp::{ + builder::{RlcThreadBreakPoints, RlcThreadBuilder}, + rlc::FIRST_PHASE, + RlpChip, + }, + storage::{ + EthAccountTraceWitness, EthStorageChip, EthStorageInput, ACCOUNT_PROOF_MAX_DEPTH, + ACCOUNT_STATE_FIELD_IS_VAR_LEN, + }, + util::{bytes_be_to_u128, load_bool}, + EthChip, EthCircuitBuilder, EthPreCircuit, Field, ETH_LOOKUP_BITS, +}; +use ethers_core::types::Address; +#[cfg(feature = "providers")] +use ethers_providers::{JsonRpcClient, Provider}; +use halo2_base::{ + gates::{GateInstructions, RangeChip, RangeInstructions}, + halo2_proofs::halo2curves::bn256::G1Affine, + utils::ScalarField, + Context, +}; +use itertools::Itertools; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use snark_verifier::util::hash::Poseidon; +use snark_verifier_sdk::halo2::POSEIDON_SPEC; +use std::cell::RefCell; + +pub(crate) const STORAGE_ROOT_INDEX: usize = 2; + +/// | Account State Field | Max bytes | +/// |-------------------------|-------------| +/// | nonce | ≤8 | +/// | balance | ≤12 | +/// | storageRoot | 32 | +/// | codeHash | 32 | +/// +/// Struct that stores account state fields as an array of fixed length byte arrays. +/// For fields with variable length byte arrays, the byte arrays are left padded with 0s to the max fixed length. +#[derive(Clone, Debug)] +pub struct AccountState(Vec>); + +impl AccountState { + pub fn keccak( + &self, + ctx: &mut Context, + gate: &impl GateInstructions, + keccak: &mut KeccakChip, + ) -> FixedByteArray { + keccak_packed( + ctx, + gate, + keccak, + FixedByteArray(self.0.iter().map(|bytes| bytes.0.to_vec()).concat()), + ) + } +} + +/// A single response to an account query. +/// +/// | Field | Max bytes | +/// |-------------------------|-------------| +/// | stateRoot | 32 | +/// | address | 20 | +/// | accountState | | +/// +/// ``` +/// account_response = hash(stateRoot . address . hash_tree_root(account_state)) +/// ``` +/// This struct stores all the data necessary to compute the above hash. +#[derive(Clone, Debug)] +pub struct AccountResponse { + pub state_root: FixedByteArray, + pub address: FixedByteArray, + pub account_state: AccountState, +} + +impl AccountResponse { + pub fn from_witness( + witness: &EthAccountTraceWitness, + ctx: &mut Context, + gate: &impl GateInstructions, + ) -> Self { + let state_root = FixedByteArray(witness.mpt_witness.root_hash_bytes.clone()); + let address = FixedByteArray(witness.address.clone()); + let account_state = AccountState( + witness + .array_witness + .field_witness + .iter() + .enumerate() + .map(|(i, field)| { + if ACCOUNT_STATE_FIELD_IS_VAR_LEN[i] { + let field: ByteArray = field.into(); + field.to_fixed(ctx, gate) + } else { + field.into() + } + }) + .collect_vec(), + ); + Self { address, state_root, account_state } + } + + pub fn poseidon( + &self, + loader: &Rc>, + poseidon: &mut Poseidon, T, RATE>, + ) -> Scalar + where + F: Field, + C: CurveAffine, + EccChip: EccInstructions, + { + let account_state = + self.account_state.0.iter().map(|x| x.to_poseidon_words(loader)).collect_vec(); + // Uses fact that account_state length is power of 2 + let account_state_hash = poseidon_tree_root(poseidon, account_state, &[]); + let [state_root, address] = + [&self.state_root, &self.address].map(|x| x.to_poseidon_words(loader)); + poseidon_packed(poseidon, state_root.concat(&address).concat(&account_state_hash.into())) + } +} + +/// See [`MultiAccountCircuit`] for more details. +/// +/// Assumptions: +/// * `block_responses`, `account_responses`, `not_empty` are all of the same length, which is a **power of two**. +/// +/// Returns `(keccak_tree_root(full_account_responses.keccak), account_responses.keccak)` +pub fn get_account_response_keccak_root<'a, F: Field>( + ctx: &mut Context, + gate: &impl GateInstructions, + keccak: &mut KeccakChip, + block_numbers: impl IntoIterator>, + account_responses: impl IntoIterator>, + not_empty: impl IntoIterator>, +) -> FixedByteArray { + let full_responses: Vec<_> = block_numbers + .into_iter() + .zip_eq(account_responses) + .zip_eq(not_empty) + .map(|((bytes, account), not_empty)| { + let keccak_account_state = account.account_state.keccak(ctx, gate, keccak); + let hash = keccak_packed( + ctx, + gate, + keccak, + bytes.concat(&account.address).concat(&keccak_account_state), + ); + bytes_select_or_zero(ctx, gate, hash, not_empty).0 + }) + .collect(); + let keccak_root = keccak.merkle_tree_root(ctx, gate, &full_responses); + FixedByteArray(bytes_be_to_u128(ctx, gate, &keccak_root)) +} + +/// See [`MultiAccountCircuit`] for more details. +/// +/// Assumptions: +/// * `block_responses`, `account_responses`, `not_empty` are all of the same length, which is a **power of two**. +pub fn get_account_response_poseidon_roots( + loader: &Rc>, + poseidon: &mut Poseidon, T, RATE>, + block_responses: Vec<(F, FixedByteArray)>, + account_responses: &[AccountResponse], + not_empty: Vec>, +) -> Vec> +where + F: Field, + C: CurveAffine, + EccChip: EccInstructions, +{ + let (block_responses_keccak_hi_lo, full_responses): (Vec<_>, Vec<_>) = block_responses + .into_iter() + .zip_eq(account_responses.iter()) + .zip_eq(not_empty) + .map(|(((word, bytes), account), not_empty)| { + let account_hash = account.poseidon(loader, poseidon); + let word = loader.assign_scalar(word); + let block_response_keccak_hi_lo = bytes.to_poseidon_words(loader); + + let hash = poseidon_packed(poseidon, PoseidonWords(vec![word, account_hash])); + ( + block_response_keccak_hi_lo, + PoseidonWords::from(word_select_or_zero(loader, hash, not_empty)), + ) + }) + .unzip(); + + let [poseidon_root, block_response_root] = [full_responses, block_responses_keccak_hi_lo] + .map(|leaves| poseidon_tree_root(poseidon, leaves, &[]).into_assigned()); + vec![poseidon_root, block_response_root] +} + +// switching to just Fr for simplicity: + +/// The input datum for the circuit to generate multiple account responses. It is used to generate a circuit. +/// +/// Assumptions: +/// * `block_responses`, `queries`, `not_empty` are all of the same length, which is a **power of two**. +/// * `block_responses` has length greater than 1: the length 1 case still works but cannot be aggregated because +/// the single leaf of `block_responses[0].1` would get hashed as two words, whereas in a larger tree it gets +/// concatenated before hashing. +/// +/// The public instances of this circuit are 5 field elements: +/// * Keccak merkle tree root of `keccak(block_number[i] . address[i] . keccak_account_state[i])` over all queries: two field elements in hi-lo u128 format +/// * Poseidon merkle tree root of `full_response[i].poseidon := poseidon(block_responses[i].0 . account_responses[i].0)` over all queries: single field element +/// * Poseidon merkle tree root of `block_number[i]` over all queries: single field element +/// +/// Above `account_responses` refers to the hash of `AccountResponse`s generated by the circuit for all queries. +/// Since `block_number`s are given as private inputs, we need to expose a *Poseidon* merkle root of all `block_number`s to be checked again the BlockResponses. +// For compatibility with aggregation we keep all the poseidon roots together in the instance +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] +pub struct MultiAccountCircuit { + /// The block responses are provided as UNCHECKED private inputs; they will be checked in a separate circuit + pub block_responses: Vec<(Fr, u32)>, + /// The account queries + pub queries: Vec, // re-use EthStorageInput but storage pf will be empty + /// Private input to allow full_response[i].hash to be `Fr::zero()` or `H256(0x0)` for empty response + pub not_empty: Vec, +} + +pub const ACCOUNT_INSTANCE_SIZE: usize = 4; +pub(crate) const KECCAK_ACCOUNT_FULL_RESPONSE_INDEX: usize = 0; +pub(crate) const ACCOUNT_FULL_RESPONSE_POSEIDON_INDEX: usize = 2; +pub(crate) const ACCOUNT_BLOCK_RESPONSE_KECCAK_INDEX: usize = 3; +pub(crate) const ACCOUNT_POSEIDON_ROOT_INDICES: &[usize] = + &[ACCOUNT_FULL_RESPONSE_POSEIDON_INDEX, ACCOUNT_BLOCK_RESPONSE_KECCAK_INDEX]; +pub(crate) const ACCOUNT_KECCAK_ROOT_INDICES: &[usize] = &[KECCAK_ACCOUNT_FULL_RESPONSE_INDEX]; + +impl MultiAccountCircuit { + /// Creates circuit inputs from raw data and does basic input validation. Number of queries must be power of two. + pub fn new( + block_responses: Vec<(Fr, u32)>, + queries: Vec, + not_empty: Vec, + ) -> Self { + assert!(block_responses.len() > 1); + assert_eq!(block_responses.len(), queries.len()); + assert_eq!(queries.len(), not_empty.len()); + assert!(queries.len().is_power_of_two(), "Number of queries must be a power of 2"); + Self { block_responses, queries, not_empty } + } + + /// Creates circuit inputs from a JSON-RPC provider. + #[cfg(feature = "providers")] + pub fn from_provider( + provider: &Provider

, + block_responses: Vec<(Fr, u32)>, + queries: Vec<(u64, Address)>, + not_empty: Vec, + ) -> Self { + use crate::providers::get_account_queries; + let queries = get_account_queries(provider, queries, ACCOUNT_PROOF_MAX_DEPTH); + Self::new(block_responses, queries, not_empty) + } + + /// Resizes inputs to `new_len` queries, using [`DEFAULT_ACCOUNT_QUERY`] for new queries. + pub fn resize_from( + mut block_responses: Vec<(Fr, u32)>, + mut queries: Vec, + mut not_empty: Vec, + new_len: usize, + ) -> Self { + block_responses.resize(new_len, (Fr::zero(), 0)); + queries.resize_with(new_len, || DEFAULT_ACCOUNT_QUERY.clone()); + not_empty.resize(new_len, false); + Self::new(block_responses, queries, not_empty) + } +} + +impl EthPreCircuit for MultiAccountCircuit { + fn create( + self, + mut builder: RlcThreadBuilder, + break_points: Option, + ) -> EthCircuitBuilder> { + let range = RangeChip::default(ETH_LOOKUP_BITS); + let chip = EthChip::new(RlpChip::new(&range, None), None); + let mut keccak = KeccakChip::default(); + // ================= FIRST PHASE ================ + let ctx = builder.gate_builder.main(FIRST_PHASE); + let queries = self + .queries + .into_iter() + .map(|query| { + let query = query.assign_account(ctx, &range); + (query.address.0, query.acct_pf) + }) + .collect_vec(); + let witness = + chip.parse_account_proofs_phase0(&mut builder.gate_builder, &mut keccak, queries); + // constrain all accounts exist + let ctx = builder.gate_builder.main(FIRST_PHASE); + let (account_responses, not_empty): (Vec<_>, Vec<_>) = witness + .iter() + .zip_eq(self.not_empty) + .map(|(w, not_empty)| { + let not_empty = load_bool(ctx, range.gate(), not_empty); + // we only check if the MPT key is not empty if `not_empty = true`; otherwise we don't care + let key_check = range.gate().mul(ctx, w.mpt_witness.slot_is_empty, not_empty); + range.gate().assert_is_const(ctx, &key_check, &Fr::zero()); + (AccountResponse::from_witness(w, ctx, range.gate()), not_empty) + }) + .unzip(); + let block_responses = self + .block_responses + .into_iter() + .map(|(word, num)| { + let keccak_bytes = FixedByteArray::new(ctx, &range, &num.to_be_bytes()); + (word, keccak_bytes) + }) + .collect_vec(); + // hash responses + let keccak_root = get_account_response_keccak_root( + ctx, + range.gate(), + &mut keccak, + block_responses.iter().map(|(_, bytes)| bytes), + &account_responses, + not_empty.clone(), + ); + let loader = + Halo2Loader::::new(DummyEccChip(range.gate()), builder.gate_builder); + let mut poseidon = Poseidon::from_spec(&loader, POSEIDON_SPEC.clone()); + + let mut assigned_instances = keccak_root.0; + assigned_instances.extend(get_account_response_poseidon_roots( + &loader, + &mut poseidon, + block_responses, + &account_responses, + not_empty, + )); + builder.gate_builder = loader.take_ctx(); + + // ================= SECOND PHASE ================ + EthCircuitBuilder::new( + assigned_instances, + builder, + RefCell::new(keccak), + range, + break_points, + move |builder: &mut RlcThreadBuilder, + rlp: RlpChip, + keccak_rlcs: (FixedLenRLCs, VarLenRLCs)| { + // ======== SECOND PHASE =========== + let chip = EthChip::new(rlp, Some(keccak_rlcs)); + chip.parse_account_proofs_phase1(builder, witness); + }, + ) + } +} + +lazy_static! { + pub static ref DEFAULT_ACCOUNT_QUERY: EthStorageInput = { + let mut query = DEFAULT_STORAGE_QUERY.clone(); + query.storage_pfs.clear(); + query + }; +} diff --git a/axiom-eth/src/batch_query/response/block_header.rs b/axiom-eth/src/batch_query/response/block_header.rs new file mode 100644 index 000000000..d6000f7cb --- /dev/null +++ b/axiom-eth/src/batch_query/response/block_header.rs @@ -0,0 +1,553 @@ +//! Block Header Response +use std::cell::RefCell; + +use super::{mmr_verify::verify_mmr_proof, *}; +use crate::{ + batch_query::{ + hash::{ + bytes_select_or_zero, keccak_packed, poseidon_packed, poseidon_tree_root, + word_select_or_zero, OptPoseidonWords, POSEIDON_EMPTY_ROOTS, + }, + DummyEccChip, EccInstructions, + }, + block_header::{ + get_block_header_rlp_max_lens, EthBlockHeaderChip, EthBlockHeaderTraceWitness, + BLOCK_HEADER_FIELD_IS_VAR_LEN, BLOCK_NUMBER_INDEX, EXTRA_DATA_INDEX, + MIN_NUM_BLOCK_HEADER_FIELDS, NUM_BLOCK_HEADER_FIELDS, + }, + keccak::{FixedLenRLCs, FnSynthesize, KeccakChip, VarLenRLCs}, + rlp::{ + builder::{RlcThreadBreakPoints, RlcThreadBuilder}, + rlc::FIRST_PHASE, + RlpChip, + }, + util::{bytes_be_to_u128, is_zero_vec, load_bool}, + EthChip, EthCircuitBuilder, EthPreCircuit, Field, Network, ETH_LOOKUP_BITS, +}; +use ethers_core::types::{Block, H256}; +#[cfg(feature = "providers")] +use ethers_providers::{JsonRpcClient, Provider}; +use halo2_base::{ + gates::{GateInstructions, RangeChip, RangeInstructions}, + halo2_proofs::halo2curves::bn256::G1Affine, + utils::{bit_length, ScalarField}, + Context, + QuantumCell::Existing, +}; +use itertools::Itertools; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use snark_verifier::util::hash::Poseidon; +use snark_verifier_sdk::halo2::POSEIDON_SPEC; + +/// | Block Header Field | Max bytes | +/// |---------------------------|---------------| +/// | parentHash | 32 | +/// | ommersHash | 32 | +/// | beneficiary | 20 | +/// | stateRoot | 32 | +/// | transactionsRoot | 32 | +/// | receiptsRoot | 32 | +/// | logsBloom | 256 | +/// | difficulty | ≤7 | +/// | number | ≤4 | +/// | gasLimit | ≤4 | +/// | gasUsed | ≤4 | +/// | timestamp | ≤4 | +/// | extraData | ≤32 (mainnet) | +/// | mixHash | 32 | +/// | nonce | 8 | +/// | basefee (post-1559) | ≤6 or 0 | +/// | withdrawalsRoot (post-4895) | 32 or 0 | +/// +/// Struct that stores block header fields as an array of `ByteArray`s. The first [`MIN_NUM_BLOCK_HEADER_FIELDS`] +/// fields are of known fixed length, and the rest are either fixed length byte arrays or empty byte arrays. +/// For fields of `uint` type with variable length byte lens, the byte arrays are left padded with 0s to the max fixed length. +/// +/// We do something special for extraData because it is a variable length array of arbitrary bytes. In that case we +/// store `extraDataLength . extraDataRightPadded` as an array of field elements, where `extraDataRightPadded` +/// is right padded with 0s to max fixed length. +/// +/// Entry in the array consists of (bytes, is_some) +#[derive(Clone, Debug)] +pub struct BlockHeader { + as_list: [(FixedByteArray, Option>); NUM_BLOCK_HEADER_FIELDS], + extra_data_len: AssignedValue, +} + +/// A single response to a block header query. +/// +/// | Field | Max bytes | +/// |---------------------------|--------------| +/// | blockHash | 32 | +/// | blockNumber | ≤4 | +/// +/// ``` +/// block_response = hash(blockHash. blockNumber. hash_tree_root(block_header)) +/// ``` +/// This struct stores all the data necessary to compute the above hash. +/// +/// We store `blockNumber` twice because it needs to be accesses frequently with less hashing. +#[derive(Clone, Debug)] +pub struct BlockResponse { + pub block_hash: FixedByteArray, + pub block_header: BlockHeader, +} + +impl BlockResponse { + pub fn from_witness( + witness: &EthBlockHeaderTraceWitness, + ctx: &mut Context, + gate: &impl GateInstructions, + ) -> Self { + let block_hash = FixedByteArray(witness.block_hash.clone()); + let extra_data_len = witness.rlp_witness.field_witness[EXTRA_DATA_INDEX].field_len; + let block_header = BLOCK_HEADER_FIELD_IS_VAR_LEN + .iter() + .zip_eq(witness.rlp_witness.field_witness.iter()) + .enumerate() + .map(|(i, (is_var_len, witness))| { + if i == EXTRA_DATA_INDEX { + (FixedByteArray(witness.field_cells.clone()), None) + } else if i < MIN_NUM_BLOCK_HEADER_FIELDS { + // these fields are non-optional + let field = if *is_var_len { + // left pad with 0s to max len + ByteArray::from(witness).to_fixed(ctx, gate) + } else { + // checks to make sure actually fixed len + witness.into() + }; + (field, None) + } else { + let field = ByteArray::from(witness); + let var_len = field.var_len.unwrap(); + let is_empty = gate.is_zero(ctx, var_len); + let is_some = gate.not(ctx, is_empty); + let padded_field = if *is_var_len { + // left pad with 0s to max length + field.to_fixed(ctx, gate).0 + } else { + field.bytes + }; + (FixedByteArray(padded_field), Some(is_some)) + } + }) + .collect_vec(); + Self { + block_hash, + block_header: BlockHeader { as_list: block_header.try_into().unwrap(), extra_data_len }, + } + } + + pub fn keccak( + &self, + ctx: &mut Context, + gate: &impl GateInstructions, + keccak: &mut KeccakChip, + ) -> FixedByteArray { + let block_number_bytes = &self.block_header.as_list[BLOCK_NUMBER_INDEX].0; + keccak_packed(ctx, gate, keccak, self.block_hash.concat(block_number_bytes)) + } + + pub fn poseidon( + &self, + loader: &Rc>, + poseidon: &mut Poseidon, T, RATE>, + poseidon_empty_roots: &[F], + ) -> Scalar + where + F: Field, + C: CurveAffine, + EccChip: EccInstructions, + { + let header_as_words = self + .block_header + .as_list + .iter() + .enumerate() + .map(|(i, (bytes, is_some))| { + let mut words = bytes.to_poseidon_words(loader); + if i == EXTRA_DATA_INDEX { + // extra data is variable length, so we record (extraData.length . extraDataRightPadded) + let extra_data_len = + loader.scalar_from_assigned(self.block_header.extra_data_len); + words = PoseidonWords::from(extra_data_len).concat(&words); + } + OptPoseidonWords { + words: words.0, + is_some: is_some.map(|x| loader.scalar_from_assigned(x)), + } + }) + .collect_vec(); + + let block_number_words = PoseidonWords(header_as_words[8].words.clone()); + let header_poseidon = poseidon_tree_root(poseidon, header_as_words, poseidon_empty_roots); + + let block_hash = self.block_hash.to_poseidon_words(loader); + poseidon_packed( + poseidon, + block_hash.concat(&block_number_words).concat(&header_poseidon.into()), + ) + } +} + +/// See [`MultiBlockCircuit`] for more details. +/// +/// Returns `(keccak_tree_root(block_responses.keccak), block_responses.keccak)` +pub fn get_block_response_keccak_root( + ctx: &mut Context, + gate: &impl GateInstructions, + keccak: &mut KeccakChip, + block_responses: &[BlockResponse], + not_empty: Vec>, +) -> (FixedByteArray, Vec>) { + let block_responses = block_responses + .iter() + .zip_eq(not_empty) + .map(|(block_response, not_empty)| { + let hash = block_response.keccak(ctx, gate, keccak); + bytes_select_or_zero(ctx, gate, hash, not_empty) + }) + .collect_vec(); + let keccak_root = keccak.merkle_tree_root(ctx, gate, &block_responses); + (FixedByteArray(bytes_be_to_u128(ctx, gate, &keccak_root)), block_responses) +} + +/// See [`MultiBlockCircuit`] for more details. +/// +pub fn get_block_response_poseidon_roots( + loader: &Rc>, + poseidon: &mut Poseidon, T, RATE>, + block_responses: &[BlockResponse], + not_empty: Vec>, + poseidon_empty_roots: &[F], +) -> Vec> +where + F: Field, + C: CurveAffine, + EccChip: EccInstructions, +{ + let block_responses = block_responses + .iter() + .zip_eq(not_empty) + .map(|(block_response, not_empty)| { + let hash = block_response.poseidon(loader, poseidon, poseidon_empty_roots); + word_select_or_zero(loader, hash, not_empty) + }) + .collect_vec(); + let poseidon_root = + poseidon_tree_root(poseidon, block_responses, poseidon_empty_roots).into_assigned(); + vec![poseidon_root] +} + +// switching to just Fr for simplicity: + +/// The input datum for the circuit to generate multiple block responses. It is used to generate a circuit. +/// Additionally checks that all block hashes in a response column are in a given +/// Merkle Mountain Range (MMR). The MMR will be a commitment to a contiguous list of block hashes, for block +/// numbers `[0, mmr_list_len)`. +/// +/// Assumptions: +/// +/// +/// Assumptions: +/// * `header_rlp_encodings`, `not_empty`, `block_hashes`, `block_numbers`, `headers_poseidon`, `mmr_proofs` have the same length, which is a power of two. +/// * `header_rlp_encodings` has length greater than 1: the length 1 case still works but cannot be aggregated because +/// the single leaf of `block_responses[0].keccak` would get Poseidon hashed into a single word, whereas in a larger +/// tree it gets concatenated before hashing. +/// * `mmr_list_len < 2^MMR_MAX_NUM_PEAKS` +/// * `mmr_list_len >= 2^BLOCK_BATCH_DEPTH`, i.e., `mmr_num_peaks > BLOCK_BATCH_DEPTH` where `mmr_num_peaks := bit_length(mmr_list_len)` +/// +/// The public instances of this circuit are [`BLOCK_INSTANCE_SIZE`] field elements: +/// * Keccak merkle tree root of `keccakPacked(blockHash[i] . blockNumber[i])` over all queries: two field elements in hi-lo u128 format +/// * Poseidon merkle tree root of `block_responses[i].poseidon` over all queries: single field element +/// * `keccak256(abi.encodePacked(mmr[BLOCK_BATCH_DEPTH..]))` as 2 field elements, H256 in hi-lo form. +/// * `keccak256(abi.encodePacked(mmr[..BLOCK_BATCH_DEPTH]))` as 2 field elements, H256 in hi-lo form. +/// +/// Above `block_responses` refers to the hash of `BlockResponse`s generated by the circuit for all queries. +/// +/// To be clear, `abi.encodedPacked(mmr[d..]) = mmr[d] . mmr[d + 1] . ... . mmr[mmr_num_peaks - 1]` where `.` is concatenation of byte arrays. +/// +/// If a block entry has `not_empty = false`, then the MMR proof is skipped. +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] +pub struct MultiBlockCircuit { + /// The RLP-encoded block headers + pub header_rlp_encodings: Vec>, + /// Private input to allow block_responses[i] to be `(Fr::zero(), H256::zero())` for empty entry + // This is needed so we don't need to do the MMR proof + pub not_empty: Vec, + pub network: Network, + + /// Merkle Mountain Range of block hashes for blocks `[0, mmr_list_len)`, in *increasing* order of peak size. + /// Resized with 0x0 to a fixed length. + pub mmr: [H256; MMR_MAX_NUM_PEAKS], + /// Length of the original list that `mmr` is a commitment to. + pub mmr_list_len: usize, + /// `mmr_proofs[i]` is a Merkle proof of `block_hashes[i]` into `mmr`. Resized so `mmr_proofs[i].len() = mmr.len() - 1` + pub mmr_proofs: Vec<[H256; MMR_MAX_NUM_PEAKS - 1]>, +} + +pub const MMR_MAX_NUM_PEAKS: usize = 32; // assuming block number stays in u32, < 2^32 +/// The AxiomV1Core smart contract only stores Merkle Mountain Range of Merkle roots of block hashes of contiguous segments +/// of blocks of length 2BLOCK_BATCH_DEPTH. +pub const BLOCK_BATCH_DEPTH: usize = 10; + +pub const BLOCK_INSTANCE_SIZE: usize = 7; +pub(crate) const KECCAK_BLOCK_RESPONSE_INDEX: usize = 0; +pub(crate) const BLOCK_RESPONSE_POSEIDON_INDEX: usize = 2; +pub const BLOCK_POSEIDON_ROOT_INDICES: &[usize] = &[BLOCK_RESPONSE_POSEIDON_INDEX]; +pub const BLOCK_KECCAK_ROOT_INDICES: &[usize] = &[KECCAK_BLOCK_RESPONSE_INDEX]; + +impl MultiBlockCircuit { + /// Creates circuit inputs from raw RLP encodings. Panics if number of blocks is not a power of 2. + pub fn new( + mut header_rlps: Vec>, + not_empty: Vec, + network: Network, + mut mmr: Vec, + mmr_list_len: usize, + mmr_proofs: Vec>, + ) -> Self { + assert!(header_rlps.len() > 1); + assert_eq!(header_rlps.len(), not_empty.len()); + assert!(header_rlps.len().is_power_of_two(), "Number of blocks must be a power of 2"); + // resize RLPs + let (header_rlp_max_bytes, _) = get_block_header_rlp_max_lens(network); + for rlp in &mut header_rlps { + rlp.resize(header_rlp_max_bytes, 0); + } + assert_eq!(header_rlps.len(), mmr_proofs.len()); + + mmr.resize(MMR_MAX_NUM_PEAKS, H256::zero()); + let mmr_proofs = mmr_proofs + .into_iter() + .map(|mut proof| { + proof.resize(MMR_MAX_NUM_PEAKS - 1, H256::zero()); + proof.try_into().unwrap() + }) + .collect(); + Self { + header_rlp_encodings: header_rlps, + not_empty, + network, + mmr: mmr.try_into().unwrap(), + mmr_list_len, + mmr_proofs, + } + } + + /// Creates circuit inputs using JSON-RPC provider. Panics if provider error or any block is not found. + /// + /// Assumes that `network` is the same as the provider's network. + #[cfg(feature = "providers")] + pub fn from_provider( + provider: &Provider

, + block_numbers: Vec, + not_empty: Vec, + network: Network, + mmr: Vec, + mmr_list_len: usize, + mmr_proofs: Vec>, + ) -> Self { + use crate::providers::{get_block_rlp, get_blocks}; + + let header_rlp_encodings = get_blocks(provider, block_numbers) + .unwrap() + .into_iter() + .map(|block| get_block_rlp(&block.expect("block not found"))) + .collect(); + Self::new(header_rlp_encodings, not_empty, network, mmr, mmr_list_len, mmr_proofs) + } + + pub fn resize_from( + mut header_rlps: Vec>, + mut not_empty: Vec, + network: Network, + mmr: Vec, + mmr_list_len: usize, + mut mmr_proofs: Vec>, + new_len: usize, + ) -> Self { + header_rlps.resize_with(new_len, || GENESIS_BLOCK_RLP.to_vec()); + not_empty.resize(new_len, false); + mmr_proofs.resize(new_len, vec![]); + Self::new(header_rlps, not_empty, network, mmr, mmr_list_len, mmr_proofs) + } +} + +impl EthPreCircuit for MultiBlockCircuit { + fn create( + self, + mut builder: RlcThreadBuilder, + break_points: Option, + ) -> EthCircuitBuilder> { + let range = RangeChip::default(ETH_LOOKUP_BITS); + let gate = range.gate(); + let chip = EthChip::new(RlpChip::new(&range, None), None); + let mut keccak = KeccakChip::default(); + // ================= FIRST PHASE ================ + let witness = chip.decompose_block_headers_phase0( + &mut builder.gate_builder, + &mut keccak, + self.header_rlp_encodings, + self.network, + ); + let ctx = builder.gate_builder.main(FIRST_PHASE); + let block_responses = + witness.iter().map(|w| BlockResponse::from_witness(w, ctx, range.gate())).collect_vec(); + let not_empty = + self.not_empty.into_iter().map(|b| load_bool(ctx, range.gate(), b)).collect_vec(); + + // verify mmr + let mmr_list_len = ctx.load_witness(gate.get_field_element(self.mmr_list_len as u64)); + let mmr_bits = gate.num_to_bits(ctx, mmr_list_len, MMR_MAX_NUM_PEAKS); // implicitly range checks that mmr_list_len < 2^MMR_MAX_NUM_PEAKS + + let mmr = self + .mmr + .into_iter() + .map(|peak| FixedByteArray::new(ctx, &range, peak.as_bytes())) + .collect_vec(); + // check that mmr peaks agree with mmr_bits + for (bit, peak) in mmr_bits.iter().zip_eq(mmr.iter()) { + let no_peak = is_zero_vec(ctx, gate, &peak.0); + let is_peak = gate.not(ctx, no_peak); + ctx.constrain_equal(&is_peak, bit); + } + // verify mmr proofs + block_responses.iter().zip(self.mmr_proofs).zip_eq(not_empty.clone()).for_each( + |((response, mmr_proof), not_empty)| { + let block_hash = response.block_hash.clone(); + let block_number_be = &response.block_header.as_list[BLOCK_NUMBER_INDEX].0 .0; + // this is done again later in poseidon, so a duplicate for code conciseness + let block_number = bytes_be_to_u128(ctx, gate, block_number_be).pop().unwrap(); + let mmr_proof = mmr_proof + .into_iter() + .map(|node| FixedByteArray::new(ctx, &range, node.as_bytes())) + .collect_vec(); + verify_mmr_proof( + ctx, + &range, + &mut keccak, + &mmr, + mmr_list_len, + &mmr_bits, + block_number, + block_hash, + mmr_proof, + not_empty, + ); + }, + ); + // mmr_num_peaks = bit_length(mmr_list_len) = MMR_MAX_NUM_PEAKS - num_leading_zeros(mmr_list_len) + let mut is_leading = Constant(Fr::one()); + let mut num_leading_zeros = ctx.load_zero(); + for bit in mmr_bits.iter().rev() { + // is_zero = 1 - bit + // is_leading = is_leading * (is_zero) + is_leading = Existing(gate.mul_not(ctx, *bit, is_leading)); + num_leading_zeros = gate.add(ctx, num_leading_zeros, is_leading); + } + let max_num_peaks = gate.get_field_element(MMR_MAX_NUM_PEAKS as u64); + let num_peaks = gate.sub(ctx, Constant(max_num_peaks), num_leading_zeros); + let truncated_num_peaks = + gate.sub(ctx, num_peaks, Constant(gate.get_field_element(BLOCK_BATCH_DEPTH as u64))); + range.range_check(ctx, truncated_num_peaks, bit_length(MMR_MAX_NUM_PEAKS as u64)); // ensures not negative + let truncated_mmr_bytes = + gate.mul(ctx, truncated_num_peaks, Constant(gate.get_field_element(32u64))); + let keccak_id = keccak.keccak_var_len( + ctx, + &range, + mmr[BLOCK_BATCH_DEPTH..].iter().flat_map(|bytes| bytes.0.clone()).collect(), + None, + truncated_mmr_bytes, + 0, + ); + let keccak_bytes = keccak.var_len_queries[keccak_id].output_assigned.clone(); + let historical_mmr_keccak = bytes_be_to_u128(ctx, gate, &keccak_bytes); + + let recent_mmr_keccak_bytes = keccak_packed( + ctx, + gate, + &mut keccak, + FixedByteArray( + mmr[..BLOCK_BATCH_DEPTH].iter().flat_map(|bytes| bytes.0.clone()).collect(), + ), + ); + let recent_mmr_keccak = bytes_be_to_u128(ctx, gate, recent_mmr_keccak_bytes.as_ref()); + + // keccak responses + let (keccak_root, _) = get_block_response_keccak_root( + ctx, + range.gate(), + &mut keccak, + &block_responses, + not_empty.clone(), + ); + // poseidon responses + let loader = + Halo2Loader::::new(DummyEccChip(range.gate()), builder.gate_builder); + let mut poseidon = Poseidon::from_spec(&loader, POSEIDON_SPEC.clone()); + + let mut assigned_instances = keccak_root.0; + assigned_instances.extend( + get_block_response_poseidon_roots( + &loader, + &mut poseidon, + &block_responses, + not_empty, + &POSEIDON_EMPTY_ROOTS, + ) + .into_iter() + .chain(historical_mmr_keccak) + .chain(recent_mmr_keccak), + ); + + builder.gate_builder = loader.take_ctx(); + + // ================= SECOND PHASE ================ + EthCircuitBuilder::new( + assigned_instances, + builder, + RefCell::new(keccak), + range, + break_points, + move |builder: &mut RlcThreadBuilder, + rlp: RlpChip, + keccak_rlcs: (FixedLenRLCs, VarLenRLCs)| { + // ======== SECOND PHASE =========== + let chip = EthChip::new(rlp, Some(keccak_rlcs)); + chip.decompose_block_headers_phase1(builder, witness); + }, + ) + } +} + +pub const GENESIS_BLOCK_RLP: &[u8] = &[ + 249, 2, 20, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 160, 29, 204, 77, 232, 222, 199, 93, 122, 171, 133, 181, 103, 182, 204, 212, + 26, 211, 18, 69, 27, 148, 138, 116, 19, 240, 161, 66, 253, 64, 212, 147, 71, 148, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160, 215, 248, 151, 79, 181, 172, 120, 217, + 172, 9, 155, 154, 213, 1, 139, 237, 194, 206, 10, 114, 218, 209, 130, 122, 23, 9, 218, 48, 88, + 15, 5, 68, 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, + 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 86, 232, 31, 23, 27, + 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, + 98, 47, 181, 227, 99, 180, 33, 185, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 133, 4, 0, 0, 0, 0, 128, 130, 19, 136, 128, 128, 160, + 17, 187, 232, 219, 78, 52, 123, 78, 140, 147, 124, 28, 131, 112, 228, 181, 237, 51, 173, 179, + 219, 105, 203, 219, 122, 56, 225, 229, 11, 27, 130, 250, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 0, 0, 0, 0, 0, 0, 0, 66, +]; + +lazy_static! { + pub static ref GENESIS_BLOCK: Block = serde_json::from_str(r#" + {"hash":"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","number":"0x0","gasUsed":"0x0","gasLimit":"0x1388","extraData":"0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","difficulty":"0x400000000","totalDifficulty":"0x400000000","sealFields":[],"uncles":[],"transactions":[],"size":"0x21c","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000042","baseFeePerGas":null} + "#).unwrap(); +} diff --git a/axiom-eth/src/batch_query/response/mmr_verify.rs b/axiom-eth/src/batch_query/response/mmr_verify.rs new file mode 100644 index 000000000..3b369550e --- /dev/null +++ b/axiom-eth/src/batch_query/response/mmr_verify.rs @@ -0,0 +1,97 @@ +//! Verify all block hashes in BlockResponse column against a given Merkle Mountain Range + +use halo2_base::{ + gates::{GateInstructions, RangeInstructions}, + utils::ScalarField, + AssignedValue, Context, + QuantumCell::{Constant, Existing, Witness}, +}; +use itertools::Itertools; + +use crate::{ + batch_query::hash::keccak_packed, keccak::KeccakChip, util::select_array_by_indicator, Field, +}; + +use super::FixedByteArray; + +/// Input assumptions which must hold but which are not constrained in the circuit: +/// * `mmr` is Merkle Mountain Range in *increasing* order of peak size. Array of fixed length 32 byte arrays. +/// Array `mmr` is resized to a fixed max length. +/// * `mmr_list_len` is the length of the original list that `mmr` is a commitment to. +/// * `mmr_bits` is the same length as `mmr`. `mmr_bits[i]` is a bit that is 1 if `mmr[i]` is a non-empty peak, and 0 otherwise. In other words, `mmr_bits` is the little-endian bit representation of `mmr_list_len`. +#[allow(clippy::too_many_arguments)] +pub fn verify_mmr_proof( + ctx: &mut Context, + range: &impl RangeInstructions, + keccak: &mut KeccakChip, + mmr: &[impl AsRef<[AssignedValue]>], + mmr_list_len: AssignedValue, + mmr_bits: &[AssignedValue], + list_id: AssignedValue, // the index in underlying list + leaf: FixedByteArray, // the leaf node at `list_id` in underlying list + merkle_proof: Vec>, + not_empty: AssignedValue, // actually do the proof check +) { + assert!(!mmr.is_empty()); + let gate = range.gate(); + assert_eq!(mmr.len(), mmr_bits.len()); + let index_bits = range.gate().num_to_bits(ctx, list_id, mmr.len()); + range.check_less_than(ctx, list_id, mmr_list_len, mmr.len()); + // count how many leading (big-endian) bits `mmr_bits` and `index_bits` have in common + let mut agree = Constant(F::one()); + let mut num_leading_agree = ctx.load_zero(); + for (a, b) in mmr_bits.iter().rev().zip(index_bits.iter().rev()) { + let is_equal = bit_is_equal(ctx, gate, *a, *b); + agree = Existing(gate.mul(ctx, agree, is_equal)); + num_leading_agree = gate.add(ctx, num_leading_agree, agree); + } + // if num_leading_agree = mmr.len() that means peak_id = mmr_list_len is outside of this MMR + let max_peak_id = gate.get_field_element(mmr.len() as u64 - 1); + let peak_id = gate.sub(ctx, Constant(max_peak_id), num_leading_agree); + + // we merkle prove `leaf` into `mmr[peak_id]` using `index_bits[..peak_id]` as the "side" + assert_eq!(merkle_proof.len() + 1, mmr.len()); // max depth of a peak is mmr.len() - 1 + let mut intermediate_hashes = Vec::with_capacity(mmr.len()); + intermediate_hashes.push(leaf); + // last index_bit is never used: if it were 1 then leading bit of mmr_bits would also have to be 1 + for (side, node) in index_bits.into_iter().zip(merkle_proof) { + let cur = intermediate_hashes.last().unwrap(); + // Possible optimization: if merkle_proof consists of unassigned witnesses, they can be assigned while `select`ing here. We avoid this low-level optimization for code clarity for now. + let concat = cur + .0 + .iter() + .chain(node.0.iter()) + .zip_eq(node.0.iter().chain(cur.0.iter())) + .map(|(a, b)| gate.select(ctx, *b, *a, side)) + .collect_vec(); + let hash = keccak_packed(ctx, gate, keccak, FixedByteArray(concat)); + intermediate_hashes.push(hash); + } + let peak_indicator = gate.idx_to_indicator(ctx, peak_id, mmr.len()); + // get mmr[peak_id] + debug_assert_eq!(mmr[0].as_ref().len(), 32); + let peak = select_array_by_indicator(ctx, gate, mmr, &peak_indicator); + let proof_peak = select_array_by_indicator(ctx, gate, &intermediate_hashes, &peak_indicator); + for (a, b) in peak.into_iter().zip_eq(proof_peak) { + let a = gate.mul(ctx, a, not_empty); + let b = gate.mul(ctx, b, not_empty); + ctx.constrain_equal(&a, &b); + } +} + +/// Assumes `a, b` are both bits. +/// +/// Returns `a == b` as a bit. +pub fn bit_is_equal( + ctx: &mut Context, + gate: &impl GateInstructions, + a: AssignedValue, + b: AssignedValue, +) -> AssignedValue { + // (a == b) = 1 - (a - b)^2 + let diff = gate.sub(ctx, a, b); + // | 1 - (a-b)^2 | a-b | a-b | 1 | + let out_val = F::one() - diff.value().square(); + ctx.assign_region([Witness(out_val), Existing(diff), Existing(diff), Constant(F::one())], [0]); + ctx.get(-4) +} diff --git a/axiom-eth/src/batch_query/response/mod.rs b/axiom-eth/src/batch_query/response/mod.rs new file mode 100644 index 000000000..dc061f7f7 --- /dev/null +++ b/axiom-eth/src/batch_query/response/mod.rs @@ -0,0 +1,176 @@ +//! The containers for different query response types + +use super::hash::PoseidonWords; +use super::EccInstructions; +use crate::rlp::RlpFieldWitness; +use crate::util::bytes_be_var_to_fixed; +use crate::{mpt::AssignedBytes, util::bytes_be_to_uint}; +use halo2_base::gates::{GateChip, RangeInstructions}; +use halo2_base::halo2_proofs::halo2curves::bn256::Fr; +use halo2_base::halo2_proofs::halo2curves::CurveAffine; +use halo2_base::{ + gates::GateInstructions, utils::ScalarField, AssignedValue, Context, QuantumCell::Constant, +}; +use snark_verifier::loader::halo2::{Halo2Loader, Scalar}; +use std::iter; +use std::rc::Rc; + +pub mod account; +pub mod block_header; +pub mod mmr_verify; +pub mod native; +pub mod row_consistency; +pub mod storage; + +/// An assigned byte array of known fixed length. +#[derive(Clone, Debug)] +pub struct FixedByteArray(pub AssignedBytes); + +impl<'a, F: ScalarField> From<&'a RlpFieldWitness> for FixedByteArray { + fn from(value: &'a RlpFieldWitness) -> Self { + assert_eq!(value.field_len.value().get_lower_32() as usize, value.field_cells.len()); + Self(value.field_cells.clone()) + } +} + +impl AsRef<[AssignedValue]> for FixedByteArray { + fn as_ref(&self) -> &[AssignedValue] { + &self.0 + } +} + +/// An assigned byte array. Entries of `bytes` assumed to be bytes. +/// +/// If `var_len` is `None`, then the byte array is assumed to be of known fixed length. +/// Otherwise, `var_len` is the variable length of the byte array, and it is assumed that `bytes` has been right padded by 0s to a max fixed length. +#[derive(Clone, Debug)] +pub struct ByteArray { + pub bytes: AssignedBytes, + pub var_len: Option>, +} + +impl<'a, F: ScalarField> From<&'a RlpFieldWitness> for ByteArray { + fn from(value: &'a RlpFieldWitness) -> Self { + Self { var_len: Some(value.field_len), bytes: value.field_cells.clone() } + } +} + +impl From> for ByteArray { + fn from(value: FixedByteArray) -> Self { + Self { var_len: None, bytes: value.0 } + } +} + +impl ByteArray { + /// Evaluates a variable-length byte string to a big endian number. + /// + /// If the resulting number is larger than the size of the scalar field `F`, then the result + /// is modulo the prime of the scalar field. (We do not recommend using it in this setting.) + pub fn evaluate( + &self, + ctx: &mut Context, + gate: &impl GateInstructions, + ) -> AssignedValue { + if let Some(len) = self.var_len { + evaluate_byte_array(ctx, gate, &self.bytes, len) + } else { + bytes_be_to_uint(ctx, gate, &self.bytes, self.bytes.len()) + } + } + + /// Converts a variable-length byte array to a fixed-length byte array by left padding with 0s. + /// Assumes that `self.bytes` has been right padded with 0s to a max fixed length. + pub fn to_fixed( + self, + ctx: &mut Context, + gate: &impl GateInstructions, + ) -> FixedByteArray { + FixedByteArray(if let Some(len) = self.var_len { + bytes_be_var_to_fixed(ctx, gate, &self.bytes, len, self.bytes.len()) + } else { + self.bytes + }) + } +} + +impl FixedByteArray { + /// Loads bytes as witnesses and range checks each witness to be 8 bits. + pub fn new(ctx: &mut Context, range: &impl RangeInstructions, bytes: &[u8]) -> Self { + let bytes = + ctx.assign_witnesses(bytes.iter().map(|x| range.gate().get_field_element(*x as u64))); + // range check bytes + for byte in &bytes { + range.range_check(ctx, *byte, 8); + } + Self(bytes) + } + + /// Loads bytes as constants. + pub fn new_const(ctx: &mut Context, gate: &impl GateInstructions, bytes: &[u8]) -> Self { + let bytes = + bytes.iter().map(|b| ctx.load_constant(gate.get_field_element(*b as u64))).collect(); + Self(bytes) + } + + /// Evaluates a fixed-length byte string to a big endian number. + /// + /// If the resulting number is larger than the size of the scalar field `F`, then the result + /// is modulo the prime of the scalar field. (We do not recommend using it in this setting.) + pub fn evaluate( + &self, + ctx: &mut Context, + gate: &impl GateInstructions, + ) -> AssignedValue { + bytes_be_to_uint(ctx, gate, &self.0, self.0.len()) + } + + pub fn to_poseidon_words( + &self, + loader: &Rc>, + ) -> PoseidonWords> + where + C: CurveAffine, + EccChip: EccInstructions, + { + assert!(F::CAPACITY >= 128); + if self.0.is_empty() { + return PoseidonWords(vec![]); + } + let mut builder = loader.ctx_mut(); + let gate: &GateChip = &loader.scalar_chip(); + let ctx = builder.main(0); + PoseidonWords(if 8 * self.0.len() <= F::CAPACITY as usize { + vec![loader.scalar_from_assigned(self.evaluate(ctx, gate))] + } else { + self.0 + .chunks(16) + .map(|chunk| { + loader.scalar_from_assigned(bytes_be_to_uint(ctx, gate, chunk, chunk.len())) + }) + .collect() + }) + } + + pub fn concat(&self, other: &Self) -> Self { + Self([&self.0[..], &other.0[..]].concat()) + } +} + +/// Evaluate a variable length byte array `array[..len]` to a big endian number +pub fn evaluate_byte_array( + ctx: &mut Context, + gate: &impl GateInstructions, + array: &[AssignedValue], + len: AssignedValue, +) -> AssignedValue { + let f_256 = gate.get_field_element(256); + if !array.is_empty() { + let incremental_evals = + gate.accumulated_product(ctx, iter::repeat(Constant(f_256)), array.iter().copied()); + let len_minus_one = gate.sub(ctx, len, Constant(F::one())); + // if `len = 0` then `len_minus_one` will be very large, so `select_from_idx` will return 0. + gate.select_from_idx(ctx, incremental_evals.iter().copied(), len_minus_one) + } else { + ctx.load_zero() + } +} diff --git a/axiom-eth/src/batch_query/response/native.rs b/axiom-eth/src/batch_query/response/native.rs new file mode 100644 index 000000000..6b3914b55 --- /dev/null +++ b/axiom-eth/src/batch_query/response/native.rs @@ -0,0 +1,263 @@ +/// The native implementation of the {Poseidon, Keccak} response of queries. +use crate::{ + batch_query::hash::{poseidon_packed, poseidon_tree_root, PoseidonWords}, + block_header::{ + EXTRA_DATA_INDEX, GOERLI_EXTRA_DATA_MAX_BYTES, MAINNET_EXTRA_DATA_MAX_BYTES, + MAINNET_HEADER_FIELDS_MAX_BYTES, NUM_BLOCK_HEADER_FIELDS, + }, + providers::get_block_rlp, + storage::{EthBlockStorageInput, EthStorageInput}, + Network, +}; +use ethers_core::{ + types::{Address, Block, H256}, + utils::keccak256, +}; +use halo2_base::halo2_proofs::halo2curves::FieldExt; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use snark_verifier::util::hash::Poseidon; + +use super::storage::DEFAULT_STORAGE_QUERY; + +#[derive(Clone, Debug)] +pub(crate) struct NativeBlockResponse { + pub block_hash: PoseidonWords, + pub header_list: Vec>, + pub header_poseidon: F, +} + +/// Computes +/// ``` +/// block_response = hash(blockHash . blockNumber . hash_tree_root(blockHeader)) +/// ``` +/// where `hash` is {Poseidon, Keccak} +/// +/// Also returns block header as list of PoseidonWords. +pub(crate) fn get_block_response( + poseidon: &mut Poseidon, + block: Block, + network: Network, +) -> ((F, H256), NativeBlockResponse) { + let mut header_list = Vec::with_capacity(32); + header_list.push(block.parent_hash.0.to_vec()); + header_list.push(block.uncles_hash.0.to_vec()); + header_list.push(block.author.unwrap().0.to_vec()); + header_list.push(block.state_root.0.to_vec()); + header_list.push(block.transactions_root.0.to_vec()); + header_list.push(block.receipts_root.0.to_vec()); + header_list.push(block.logs_bloom.unwrap().0.to_vec()); + let mut difficulty = [0u8; 32]; + block.difficulty.to_big_endian(&mut difficulty); + let difficulty = difficulty[32 - MAINNET_HEADER_FIELDS_MAX_BYTES[7]..].to_vec(); + header_list.push(difficulty); + let mut number = [0u8; 8]; + block.number.unwrap().to_big_endian(&mut number); + let number = number[8 - MAINNET_HEADER_FIELDS_MAX_BYTES[8]..].to_vec(); + header_list.push(number.clone()); + let mut gas_limit = [0u8; 32]; + block.gas_limit.to_big_endian(&mut gas_limit); + let gas_limit = gas_limit[32 - MAINNET_HEADER_FIELDS_MAX_BYTES[9]..].to_vec(); + header_list.push(gas_limit); + let mut gas_used = [0u8; 32]; + block.gas_used.to_big_endian(&mut gas_used); + let gas_used = gas_used[32 - MAINNET_HEADER_FIELDS_MAX_BYTES[10]..].to_vec(); + header_list.push(gas_used); + let mut timestamp = [0u8; 32]; + block.timestamp.to_big_endian(&mut timestamp); + let timestamp = timestamp[32 - MAINNET_HEADER_FIELDS_MAX_BYTES[11]..].to_vec(); + header_list.push(timestamp); + let extra_data_len = match network { + Network::Mainnet => MAINNET_EXTRA_DATA_MAX_BYTES, + Network::Goerli => GOERLI_EXTRA_DATA_MAX_BYTES, + }; + let mut extra_data = vec![0u8; extra_data_len]; + extra_data[..block.extra_data.len()].copy_from_slice(&block.extra_data); + header_list.push(extra_data); + header_list.push(block.mix_hash.unwrap().0.to_vec()); + header_list.push(block.nonce.unwrap().0.to_vec()); + header_list.push( + block + .base_fee_per_gas + .map(|uint| { + let mut bytes = [0u8; 32]; + uint.to_big_endian(&mut bytes); + bytes[32 - MAINNET_HEADER_FIELDS_MAX_BYTES[15]..].to_vec() + }) + .unwrap_or_default(), + ); + header_list.push(block.withdrawals_root.map(|root| root.0.to_vec()).unwrap_or_default()); + assert_eq!( + header_list.len(), + NUM_BLOCK_HEADER_FIELDS, + "Discrepancy in assumed max number of block header fields. Has there been a hard fork recently?" + ); + let mut header_list = header_list.iter().map(|x| bytes_to_poseidon_words(x)).collect_vec(); + header_list[EXTRA_DATA_INDEX].0.insert(0, F::from(block.extra_data.len() as u64)); + let mut depth = header_list.len().ilog2(); + if 1 << depth != header_list.len() { + depth += 1; + } + header_list.resize(1 << depth, PoseidonWords(vec![])); + let header_poseidon = poseidon_tree_root(poseidon, header_list.clone(), &[]); + + let block_hash = block.hash.unwrap(); + let response_keccak = keccak256([block_hash.as_bytes(), &number[..]].concat()); + let block_hash = bytes_to_poseidon_words(block_hash.as_bytes()); + let response_poseidon = poseidon_packed( + poseidon, + block_hash.concat(&bytes_to_poseidon_words(&number[..])).concat(&header_poseidon.into()), + ); + ( + (response_poseidon, response_keccak.into()), + NativeBlockResponse { block_hash, header_list, header_poseidon }, + ) +} + +#[derive(Clone, Debug)] +pub(crate) struct NativeAccountResponse { + pub state_root: PoseidonWords, + pub address: PoseidonWords, + pub state_list: Vec>, +} + +/// Computes +/// ``` +/// account_response = hash(stateRoot . address . hash_tree_root(account_state)) +/// ``` +/// where `hash` is Poseidon +pub(crate) fn get_account_response( + poseidon: &mut Poseidon, + input: &EthStorageInput, +) -> ((F, Vec), NativeAccountResponse) { + let state_list = input.acct_state.iter().map(|x| bytes_to_poseidon_words(x)).collect_vec(); + let state_poseidon = poseidon_tree_root(poseidon, state_list.clone(), &[]); + let state_keccak = keccak256(input.acct_state.concat()); + let response_keccak = [input.addr.as_bytes(), &state_keccak].concat(); + let state_root = bytes_to_poseidon_words(input.acct_pf.root_hash.as_bytes()); + let address = bytes_to_poseidon_words(input.addr.as_bytes()); + let response_poseidon = + poseidon_packed(poseidon, state_root.concat(&address).concat(&state_poseidon.into())); + ( + (response_poseidon, response_keccak), + NativeAccountResponse { state_root, address, state_list }, + ) +} + +/// Computes +/// ``` +/// hash(block_response . account_response) +/// ``` +pub fn get_full_account_response( + poseidon: &mut Poseidon, + (block_response, block_number): (F, u32), + account_response: (F, Vec), +) -> (F, H256) { + ( + poseidon_packed(poseidon, PoseidonWords(vec![block_response, account_response.0])), + keccak256([block_number.to_be_bytes().to_vec(), account_response.1].concat()).into(), + ) +} + +#[derive(Clone, Debug)] +pub struct NativeStorageResponse { + pub storage_root: PoseidonWords, + pub slot: PoseidonWords, + pub value: PoseidonWords, +} + +/// Computes +/// ``` +/// storage_response = hash(storageRoot . slot . value) +/// ``` +/// where `hash` is {Poseidon, Keccak} and `value` is left padded to 32 bytes. +pub fn get_storage_response( + poseidon: &mut Poseidon, + input: &EthStorageInput, +) -> ((F, Vec), NativeStorageResponse) { + assert_eq!(input.storage_pfs.len(), 1); + let (slot, _value, proof) = input.storage_pfs.last().unwrap(); + let storage_root = proof.root_hash; + let mut value = [0u8; 32]; + _value.to_big_endian(&mut value); + + let response_keccak = [slot.as_bytes(), &value[..]].concat(); + let [storage_root, slot, value] = + [storage_root.as_bytes(), slot.as_bytes(), &value[..]].map(bytes_to_poseidon_words); + let response_poseidon = poseidon_packed(poseidon, storage_root.concat(&slot).concat(&value)); + ((response_poseidon, response_keccak), NativeStorageResponse { storage_root, slot, value }) +} + +/// Computes +/// ``` +/// hash(block_response . account_response . storage_response) +/// ``` +pub fn get_full_storage_response( + poseidon: &mut Poseidon, + block_response: (F, u32), + account_response: (F, Address), + storage_response: (F, Vec), +) -> (F, H256) { + ( + poseidon_packed( + poseidon, + PoseidonWords(vec![block_response.0, account_response.0, storage_response.0]), + ), + keccak256( + [&block_response.1.to_be_bytes(), account_response.1.as_bytes(), &storage_response.1] + .concat(), + ) + .into(), + ) +} + +// PoseidonWords with NativeLoader +pub(crate) fn bytes_to_poseidon_words(bytes: &[u8]) -> PoseidonWords { + PoseidonWords(if bytes.is_empty() { + vec![] + } else if bytes.len() < 32 { + vec![evaluate_bytes(bytes)] + } else { + bytes.chunks(16).map(evaluate_bytes).collect() + }) +} + +/// Assumes `F::from_repr` uses little endian. +pub(crate) fn evaluate_bytes(bytes_be: &[u8]) -> F { + let mut bytes_le = F::Repr::default(); + assert!(bytes_be.len() < bytes_le.as_ref().len()); + for (le, be) in bytes_le.as_mut().iter_mut().zip(bytes_be.iter().rev()) { + *le = *be; + } + F::from_repr(bytes_le).unwrap() +} + +/// Query for a block header, optional account state, and optional storage proof(s). +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct FullStorageQuery { + pub block_number: u64, + pub addr_slots: Option<(Address, Vec)>, +} + +/// Response with a block header, optional account state, and optional storage proof(s). +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct FullStorageResponse { + /// Assumes `block` is an existing block, not a pending block. + pub block: Block, + pub account_storage: Option, +} + +impl From for EthBlockStorageInput { + fn from(value: FullStorageResponse) -> Self { + let number = value.block.number.unwrap(); + let block_hash = value.block.hash.unwrap(); + let block_header = get_block_rlp(&value.block); + Self { + block: value.block, + block_number: number.as_u32(), + block_hash, + block_header, + storage: value.account_storage.unwrap_or_else(|| DEFAULT_STORAGE_QUERY.clone()), + } + } +} diff --git a/axiom-eth/src/batch_query/response/row_consistency.rs b/axiom-eth/src/batch_query/response/row_consistency.rs new file mode 100644 index 000000000..f6bb5c31f --- /dev/null +++ b/axiom-eth/src/batch_query/response/row_consistency.rs @@ -0,0 +1,326 @@ +//! Checks consistency of `BlockResponse`, `AccountResponse` and `StorageResponse` +//! by decommiting hash and going row-by-row. +use std::env::var; +use std::str::FromStr; + +use ethers_core::types::H256; +use halo2_base::gates::builder::{ + CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, + RangeWithInstanceCircuitBuilder, +}; +use halo2_base::halo2_proofs::halo2curves::bn256::G1Affine; +use itertools::Itertools; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use snark_verifier::{loader::ScalarLoader, util::hash::Poseidon}; +use snark_verifier_sdk::{halo2::POSEIDON_SPEC, NativeLoader}; + +use super::storage::DEFAULT_STORAGE_QUERY; +use super::{native::get_block_response, *}; +use crate::batch_query::hash::poseidon_tree_root; +use crate::batch_query::response::block_header::{GENESIS_BLOCK, GENESIS_BLOCK_RLP}; +use crate::batch_query::response::native::NativeBlockResponse; +use crate::batch_query::DummyEccChip; +use crate::Field; +use crate::{ + batch_query::{ + hash::{create_merkle_proof, poseidon_packed, traverse_merkle_proof}, + response::{ + account::STORAGE_ROOT_INDEX, + native::{ + get_account_response, get_storage_response, NativeAccountResponse, + NativeStorageResponse, + }, + }, + }, + block_header::{BLOCK_NUMBER_INDEX, STATE_ROOT_INDEX}, + rlp::rlc::FIRST_PHASE, + storage::EthBlockStorageInput, + util::load_bool, + Network, +}; + +/// The input data for a circuit that checks consistency of columns for `BlockResponse`, `AccountResponse` and `StorageResponse`. +/// +/// Assumptions: +/// * `responses`, `block_not_empty`, `account_not_empty` and `storage_not_empty` to all be the same length, and length is a power of two. +/// +/// Public instances consist of [`ROW_CIRCUIT_NUM_INSTANCES`] field elements: +/// * `poseidon_tree_root(block_responses.poseidon)` +/// * `poseidon_tree_root(block_responses.keccak)` +/// * `poseidon_tree_root(full_account_responses.poseidon)` +/// * `poseidon_tree_root([account_not_empty[i] ? block_number[i] : 0x0 for all i])` +/// * `poseidon_tree_root(account_responses.keccak)` +/// * `poseidon_tree_root(full_storage_responses.poseidon)` +/// * `poseidon_tree_root([storage_not_empty[i] ? block_number[i] : 0x0 for all i])` +/// * `poseidon_tree_root([storage_not_empty[i] ? address[i] : 0x0 for all i])` +/// +/// Since a Poseidon hash is `0` with vanishingly small probability, the Poseidon Merkle roots above commit to the data of whether a column entry is empty or not. +/// `block_responses[i]` is zero if `block_not_empty[i]` is false, and similarly for `full_account_responses[i]` and `full_storage_responses[i]`. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RowConsistencyCircuit { + pub responses: Vec, + pub block_not_empty: Vec, + pub account_not_empty: Vec, + pub storage_not_empty: Vec, + pub network: Network, +} + +pub(crate) const ROW_CIRCUIT_NUM_INSTANCES: usize = 6; +pub(crate) const ROW_BLOCK_POSEIDON_INDEX: usize = 0; +pub(crate) const ROW_ACCT_POSEIDON_INDEX: usize = 1; +pub(crate) const ROW_ACCT_BLOCK_KECCAK_INDEX: usize = 2; +pub(crate) const ROW_STORAGE_POSEIDON_INDEX: usize = 3; +pub(crate) const ROW_STORAGE_BLOCK_KECCAK_INDEX: usize = 4; +pub(crate) const ROW_STORAGE_ACCT_KECCAK_INDEX: usize = 5; + +impl RowConsistencyCircuit { + pub fn new( + responses: Vec, + block_not_empty: Vec, + account_not_empty: Vec, + storage_not_empty: Vec, + network: Network, + ) -> Self { + assert!(responses.len().is_power_of_two()); + assert_eq!(responses.len(), account_not_empty.len()); + assert_eq!(account_not_empty.len(), storage_not_empty.len()); + for ((&block_not_empty, &account_not_empty), &storage_not_empty) in + block_not_empty.iter().zip_eq(account_not_empty.iter()).zip_eq(storage_not_empty.iter()) + { + assert_eq!(account_not_empty, account_not_empty && block_not_empty); + assert_eq!(storage_not_empty, storage_not_empty && account_not_empty); + } + Self { responses, block_not_empty, account_not_empty, storage_not_empty, network } + } + + pub fn resize_from( + mut responses: Vec, + mut block_not_empty: Vec, + mut account_not_empty: Vec, + mut storage_not_empty: Vec, + network: Network, + new_len: usize, + ) -> Self { + responses.resize_with(new_len, || DEFAULT_ROW_CONSISTENCY_INPUT.clone()); + block_not_empty.resize(new_len, false); + account_not_empty.resize(new_len, false); + storage_not_empty.resize(new_len, false); + Self::new(responses, block_not_empty, account_not_empty, storage_not_empty, network) + } + + pub fn verify( + self, + loader: &Rc>, + hasher: &mut Poseidon, T, RATE>, + native_hasher: &mut Poseidon, + ) -> Vec> + where + C: CurveAffine, + C::Scalar: Field, + EccChip: EccInstructions, + { + let mut row_data = + [(); ROW_CIRCUIT_NUM_INSTANCES].map(|_| Vec::with_capacity(self.responses.len())); + + let mut tmp_builder = loader.ctx_mut(); + let ctx = tmp_builder.main(FIRST_PHASE); + let gate: &GateChip = &loader.scalar_chip(); + let ((block_not_empty, account_not_empty), storage_not_empty): ((Vec<_>, Vec<_>), Vec<_>) = + self.block_not_empty + .into_iter() + .zip_eq(self.account_not_empty.into_iter()) + .zip_eq(self.storage_not_empty.into_iter()) + .map(|((block_not_empty, account_not_empty), storage_not_empty)| { + let [block_not_empty, account_not_empty, storage_not_empty] = + [block_not_empty, account_not_empty, storage_not_empty] + .map(|x| load_bool(ctx, gate, x)); + let account_not_empty = gate.mul(ctx, block_not_empty, account_not_empty); + // storage can only be empty if account is empty + let storage_not_empty = gate.mul(ctx, account_not_empty, storage_not_empty); + ((block_not_empty, account_not_empty), storage_not_empty) + }) + .unzip(); + drop(tmp_builder); + + for (((full_response, block_not_empty), account_not_empty), storage_not_empty) in self + .responses + .into_iter() + .zip(block_not_empty) + .zip(account_not_empty) + .zip(storage_not_empty) + { + // ================== + // load poseidon hashes of responses as private witnesses and merkle proof into relevant fields + // ================== + // generate block header natively + let ( + (_block_res_p, _block_res_k), + NativeBlockResponse { block_hash, header_list, header_poseidon: _ }, + ) = get_block_response(native_hasher, full_response.block, self.network); + // merkle proof state root all the way into block_response_poseidon + // load witnesses (loader) + let [state_root, block_hash, block_number] = + [&header_list[STATE_ROOT_INDEX], &block_hash, &header_list[BLOCK_NUMBER_INDEX]] + .map(|x| PoseidonWords::from_witness(loader, x)); + // create merkle proof for state root + let merkle_proof = create_merkle_proof(native_hasher, header_list, STATE_ROOT_INDEX); + let merkle_proof = merkle_proof + .into_iter() + .map(|w| PoseidonWords::from_witness(loader, w.0)) + .collect_vec(); + // use proof to compute root + let header_poseidon = + traverse_merkle_proof(hasher, &merkle_proof, state_root.clone(), STATE_ROOT_INDEX); + let mut block_response_poseidon = + poseidon_packed(hasher, block_hash.concat(&block_number).concat(&header_poseidon)); + debug_assert_eq!(block_response_poseidon.assigned().value(), &_block_res_p); + block_response_poseidon *= loader.scalar_from_assigned(block_not_empty); + row_data[ROW_BLOCK_POSEIDON_INDEX] + .push(PoseidonWords::from(block_response_poseidon.clone())); + + // generate account response natively + let ( + (_account_res_p, _), + NativeAccountResponse { state_root: _state_root, state_list, address }, + ) = get_account_response(native_hasher, &full_response.storage); + let [storage_root, _state_root, address] = + [&state_list[STORAGE_ROOT_INDEX], &_state_root, &address] + .map(|x| PoseidonWords::from_witness(loader, x)); + // create merkle proof for storage root + let merkle_proof = create_merkle_proof(native_hasher, state_list, STORAGE_ROOT_INDEX); + let merkle_proof = merkle_proof + .into_iter() + .map(|w| PoseidonWords::from_witness(loader, w.0)) + .collect_vec(); + let state_poseidon = traverse_merkle_proof( + hasher, + &merkle_proof, + storage_root.clone(), + STORAGE_ROOT_INDEX, + ); + let account_response_poseidon = + poseidon_packed(hasher, _state_root.concat(&address).concat(&state_poseidon)); + debug_assert_eq!(account_response_poseidon.assigned().value(), &_account_res_p); + let mut full_acct_res_p = poseidon_packed( + hasher, + PoseidonWords(vec![ + block_response_poseidon.clone(), + account_response_poseidon.clone(), + ]), + ); + + // generate storage response natively + let (_, NativeStorageResponse { storage_root: _, slot, value }) = + get_storage_response(native_hasher, &full_response.storage); + let [slot, value] = [&slot, &value].map(|x| PoseidonWords::from_witness(loader, x)); + let storage_response_poseidon = + poseidon_packed(hasher, storage_root.concat(&slot).concat(&value)); + let mut full_storage_res_p = poseidon_packed( + hasher, + PoseidonWords(vec![ + block_response_poseidon, + account_response_poseidon, + storage_response_poseidon, + ]), + ); + + // ================== + // check row consistency + // ================== + let [account_not_empty, storage_not_empty] = + [account_not_empty, storage_not_empty].map(|x| loader.scalar_from_assigned(x)); + // state_root from block response must equal _state_root from account response unless account is empty + // both roots are H256 in hi-lo form; we do not range check these since they are Poseidon committed to + for (lhs, rhs) in state_root.0.into_iter().zip_eq(_state_root.0.into_iter()) { + loader.assert_eq( + "state root consistency", + &(lhs * &account_not_empty), + &(rhs * &account_not_empty), + ); + } + // we do not need to enforce storage_root since it was direclty used in computation of storage response above + + // in theory the block_response.keccak in the account response could be anything if the account is empty, but for simplicity we enforce that it should be H256::zero() + let [acct_block_res_k, st_block_res_k] = + [&account_not_empty, &storage_not_empty].map(|not_empty| { + PoseidonWords(block_number.0.iter().map(|x| x.clone() * not_empty).collect()) + }); + // in theory the account_response.keccak in the storage response could be anything if the storage is empty, but for simplicity we enforce that it should be H256::zero() + let st_acct_res_k = + PoseidonWords(address.0.iter().map(|x| x.clone() * &storage_not_empty).collect()); + // full account response (poseidon) should be 0 if account is empty + full_acct_res_p *= account_not_empty; + // full storage response (poseidon) should be 0 if storage is empty + full_storage_res_p *= storage_not_empty; + + row_data[ROW_ACCT_POSEIDON_INDEX].push(full_acct_res_p.into()); + row_data[ROW_ACCT_BLOCK_KECCAK_INDEX].push(acct_block_res_k); + row_data[ROW_STORAGE_POSEIDON_INDEX].push(full_storage_res_p.into()); + row_data[ROW_STORAGE_BLOCK_KECCAK_INDEX].push(st_block_res_k); + row_data[ROW_STORAGE_ACCT_KECCAK_INDEX].push(st_acct_res_k); + } + row_data + .into_iter() + .map(|leaves| poseidon_tree_root(hasher, leaves, &[]).into_assigned()) + .collect() + } +} + +impl RowConsistencyCircuit { + fn create( + self, + stage: CircuitBuilderStage, + break_points: Option, + ) -> RangeWithInstanceCircuitBuilder { + let builder = GateThreadBuilder::new(stage == CircuitBuilderStage::Prover); + + let gate = GateChip::default(); + let loader = Halo2Loader::new(DummyEccChip::(&gate), builder); + let mut poseidon = Poseidon::from_spec(&loader, POSEIDON_SPEC.clone()); + let mut native_poseidon = Poseidon::from_spec(&NativeLoader, POSEIDON_SPEC.clone()); + + let assigned_instances = self.verify(&loader, &mut poseidon, &mut native_poseidon); + let builder = loader.take_ctx(); + RangeWithInstanceCircuitBuilder::new( + match stage { + CircuitBuilderStage::Mock => RangeCircuitBuilder::mock(builder), + CircuitBuilderStage::Keygen => RangeCircuitBuilder::keygen(builder), + CircuitBuilderStage::Prover => { + RangeCircuitBuilder::prover(builder, break_points.unwrap()) + } + }, + assigned_instances, + ) + } + + pub fn create_circuit( + self, + stage: CircuitBuilderStage, + break_points: Option, + k: u32, + ) -> RangeWithInstanceCircuitBuilder { + let circuit = self.create(stage, break_points); + + #[cfg(not(feature = "production"))] + if stage != CircuitBuilderStage::Prover { + let minimum_rows = var("UNUSABLE_ROWS").map(|s| s.parse().unwrap_or(10)).unwrap_or(10); + circuit.config(k, Some(minimum_rows)); + } + circuit + } +} + +lazy_static! { + /// Default row. NOTE: block and storage are NOT consistent. Assumed that account and storage should be marked "empty". + pub static ref DEFAULT_ROW_CONSISTENCY_INPUT: EthBlockStorageInput = EthBlockStorageInput { + block: GENESIS_BLOCK.clone(), + block_hash: H256::from_str( + "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + ) + .unwrap(), + block_header: GENESIS_BLOCK_RLP.to_vec(), + block_number: 0u32, + storage: DEFAULT_STORAGE_QUERY.clone(), + }; +} diff --git a/axiom-eth/src/batch_query/response/storage.rs b/axiom-eth/src/batch_query/response/storage.rs new file mode 100644 index 000000000..bec164dfe --- /dev/null +++ b/axiom-eth/src/batch_query/response/storage.rs @@ -0,0 +1,412 @@ +//! Storage Response +use std::{cell::RefCell, str::FromStr}; + +use super::*; +use crate::{ + batch_query::{ + hash::{ + bytes_select_or_zero, keccak_packed, poseidon_packed, poseidon_tree_root, + word_select_or_zero, + }, + DummyEccChip, EccInstructions, + }, + keccak::{FixedLenRLCs, FnSynthesize, KeccakChip, VarLenRLCs}, + mpt::MPTFixedKeyInput, + providers::{get_acct_list, get_acct_rlp}, + rlp::{ + builder::{RlcThreadBreakPoints, RlcThreadBuilder}, + rlc::FIRST_PHASE, + RlpChip, + }, + storage::{ + EthStorageChip, EthStorageInput, EthStorageTraceWitness, ACCOUNT_PROOF_VALUE_MAX_BYTE_LEN, + STORAGE_PROOF_VALUE_MAX_BYTE_LEN, {ACCOUNT_PROOF_MAX_DEPTH, STORAGE_PROOF_MAX_DEPTH}, + }, + util::{bytes_be_to_u128, load_bool}, + EthChip, EthCircuitBuilder, EthPreCircuit, Field, ETH_LOOKUP_BITS, +}; +use ethers_core::{ + types::{Address, EIP1186ProofResponse, H256}, + utils::keccak256, +}; +#[cfg(feature = "providers")] +use ethers_providers::{JsonRpcClient, Provider}; +use halo2_base::{ + gates::{GateInstructions, RangeChip, RangeInstructions}, + halo2_proofs::halo2curves::bn256::G1Affine, + utils::ScalarField, + Context, +}; +use itertools::Itertools; +use lazy_static::lazy_static; +use rlp::Encodable; +use serde::{Deserialize, Serialize}; +use snark_verifier::util::hash::Poseidon; +use snark_verifier_sdk::halo2::POSEIDON_SPEC; + +/// A single response to a storage slot query. +/// | Field | Max bytes | +/// |-------------------------|--------------| +/// | storageRoot | 32 | +/// | slot | 32 | +/// | value | ≤32 | +/// +/// We define `storage_response = hash(storageRoot . slot . value)` +/// +/// This struct stores the data needed to compute the above hash. +#[derive(Clone, Debug)] +pub struct StorageResponse { + pub storage_root: FixedByteArray, + pub slot: FixedByteArray, + pub value: FixedByteArray, +} + +impl StorageResponse { + pub fn from_witness( + witness: &EthStorageTraceWitness, + ctx: &mut Context, + gate: &impl GateInstructions, + ) -> Self { + let storage_root = FixedByteArray(witness.mpt_witness.root_hash_bytes.clone()); + let slot = FixedByteArray(witness.slot.clone()); + let value: ByteArray = (&witness.value_witness.witness).into(); + let value = value.to_fixed(ctx, gate); + Self { storage_root, slot, value } + } + + pub fn poseidon( + &self, + loader: &Rc>, + poseidon: &mut Poseidon, T, RATE>, + ) -> Scalar + where + F: Field, + C: CurveAffine, + EccChip: EccInstructions, + { + let [storage_root, slot, value] = + [&self.storage_root, &self.slot, &self.value].map(|x| x.to_poseidon_words(loader)); + poseidon_packed(poseidon, storage_root.concat(&slot).concat(&value)) + } +} + +/// See [`MultiStorageCircuit`] for more details. +/// +/// Assumptions: +/// * `block_responses`, `account_responses`, `storage_responses`, `not_empty` are all of the same length, which is a **power of two**. +/// +/// Returns `keccak_tree_root(full_storage_responses.keccak)` +pub fn get_storage_response_keccak_root<'a, F: Field>( + ctx: &mut Context, + gate: &impl GateInstructions, + keccak: &mut KeccakChip, + block_numbers: impl IntoIterator>, + addresses: impl IntoIterator>, + storage_responses: impl IntoIterator>, + not_empty: impl IntoIterator>, +) -> FixedByteArray { + let full_responses: Vec<_> = block_numbers + .into_iter() + .zip_eq(addresses) + .zip_eq(storage_responses) + .zip_eq(not_empty) + .map(|(((block_num, address), storage), not_empty)| { + let slot_value = storage.slot.concat(&storage.value); + // keccak_storage = keccak(block_response . acct_response . storage_response) + let hash = + keccak_packed(ctx, gate, keccak, block_num.concat(address).concat(&slot_value)); + bytes_select_or_zero(ctx, gate, hash, not_empty).0 + }) + .collect(); + let keccak_root = keccak.merkle_tree_root(ctx, gate, &full_responses); + FixedByteArray(bytes_be_to_u128(ctx, gate, &keccak_root)) +} + +/// See [`MultiStorageCircuit`] for more details. +/// +/// Assumptions: +/// * `block_responses`, `account_responses`, `storage_responses`, `not_empty` are all of the same length, which is a **power of two**. +pub fn get_storage_response_poseidon_roots( + loader: &Rc>, + poseidon: &mut Poseidon, T, RATE>, + block_responses: Vec<(F, FixedByteArray)>, + account_responses: Vec<(F, FixedByteArray)>, + storage_responses: &[StorageResponse], + not_empty: Vec>, +) -> Vec> +where + F: Field, + C: CurveAffine, + EccChip: EccInstructions, +{ + let ((block_numbers, addresses), full_responses): ((Vec<_>, Vec<_>), Vec<_>) = block_responses + .into_iter() + .zip_eq(account_responses.into_iter()) + .zip_eq(storage_responses.iter()) + .zip_eq(not_empty.into_iter()) + .map(|(((block_response, acct_response), storage), not_empty)| { + let storage_response = storage.poseidon(loader, poseidon); + let block_number = block_response.1.to_poseidon_words(loader); + let address = acct_response.1.to_poseidon_words(loader); + // full_response = hash(block_response . acct_response . storage_response) + let hash = poseidon_packed( + poseidon, + PoseidonWords(vec![ + loader.assign_scalar(block_response.0), + loader.assign_scalar(acct_response.0), + storage_response, + ]), + ); + ( + (block_number, address), + PoseidonWords::from(word_select_or_zero(loader, hash, not_empty)), + ) + }) + .unzip(); + let [poseidon_root, block_number_root, address_root] = + [full_responses, block_numbers, addresses] + .map(|leaves| poseidon_tree_root(poseidon, leaves, &[]).into_assigned()); + vec![poseidon_root, block_number_root, address_root] +} + +// switching to just Fr for simplicity: + +/// The input datum for the circuit to generate multiple storage responses. It is used to generate a circuit. +/// +/// Assumptions: +/// * `block_responses`, `account_responses`, `queries`, `not_empty` are all of the same length, which is a **power of two**. +/// * `block_responses` has length greater than 1: the length 1 case still works but cannot be aggregated because +/// the single leaf of `block_responses[0].1` (and of `account_responses[0].1`) would get hashed as two words, +/// whereas in a larger tree it gets concatenated before hashing. +/// +/// The public instances of this circuit are 5 field elements: +/// * Keccak merkle root of `keccak(block_number[i] . address[i], slot[i], value[i])` over all queries: two field elements in hi-lo u128 format +/// * Poseidon merkle root of `poseidon(block_responses[i].0 . account_responses[i].0, storage_responses[i].0)` over all queries: single field element +/// * Poseidon merkle root of `block_number[i]` over all queries: single field element +/// * Poseidon merkle root of `address[i]` over all queries: single field element +/// +/// Above `storage_responses` refers to the hash of `StorageResponse`s generated by the circuit for all queries. +/// Since `block_number, address` are given as private inputs, we expose Poseidon merkle roots of them as public inputs to be checked against BlockResponse and AccountResponse. +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] +pub struct MultiStorageCircuit { + /// The block responses are provided as UNCHECKED private inputs; they will be checked in a separate circuit + pub block_responses: Vec<(Fr, u32)>, + /// The account responses are provided as UNCHECKED private inputs; they will be checked in a separate circuit + pub account_responses: Vec<(Fr, Address)>, + /// The storage queries + pub queries: Vec, + /// Private input to allow full_response_hash[i] to be `Fr::zero()` or `H256(0x0)` for empty response + pub not_empty: Vec, +} + +pub const STORAGE_INSTANCE_SIZE: usize = 5; + +pub(crate) const KECCAK_STORAGE_FULL_RESPONSE_INDEX: usize = 0; +pub(crate) const STORAGE_KECCAK_ROOT_INDICES: &[usize] = &[KECCAK_STORAGE_FULL_RESPONSE_INDEX]; + +pub(crate) const STORAGE_FULL_RESPONSE_POSEIDON_INDEX: usize = 2; +pub(crate) const STORAGE_BLOCK_RESPONSE_KECCAK_INDEX: usize = 3; +pub(crate) const STORAGE_ACCOUNT_RESPONSE_KECCAK_INDEX: usize = 4; +pub(crate) const STORAGE_POSEIDON_ROOT_INDICES: &[usize] = &[ + STORAGE_FULL_RESPONSE_POSEIDON_INDEX, + STORAGE_BLOCK_RESPONSE_KECCAK_INDEX, + STORAGE_ACCOUNT_RESPONSE_KECCAK_INDEX, +]; + +impl MultiStorageCircuit { + /// Creates circuit inputs from raw data. Does basic sanity checks. Number of queries must be a power of two. + pub fn new( + block_responses: Vec<(Fr, u32)>, + account_responses: Vec<(Fr, Address)>, + queries: Vec, + not_empty: Vec, + ) -> Self { + assert!(queries.len() > 1); + assert_eq!(block_responses.len(), account_responses.len()); + assert_eq!(block_responses.len(), not_empty.len()); + assert_eq!(queries.len(), not_empty.len()); + assert!(queries.len().is_power_of_two(), "Number of queries must be a power of 2"); + Self { block_responses, account_responses, queries, not_empty } + } + + /// Creates circuit inputs from a JSON-RPC provider. + #[cfg(feature = "providers")] + pub fn from_provider( + provider: &Provider

, + block_responses: Vec<(Fr, u32)>, + account_responses: Vec<(Fr, Address)>, + queries: Vec<(u64, Address, H256)>, + not_empty: Vec, + ) -> Self { + use crate::providers::get_storage_queries; + let queries = get_storage_queries( + provider, + queries, + ACCOUNT_PROOF_MAX_DEPTH, + STORAGE_PROOF_MAX_DEPTH, + ); + Self::new(block_responses, account_responses, queries, not_empty) + } + + pub fn resize_from( + mut block_responses: Vec<(Fr, u32)>, + mut account_responses: Vec<(Fr, Address)>, + mut queries: Vec, + mut not_empty: Vec, + new_len: usize, + ) -> Self { + block_responses.resize(new_len, (Fr::zero(), 0)); + account_responses.resize(new_len, (Fr::zero(), Address::zero())); + queries.resize_with(new_len, || DEFAULT_STORAGE_QUERY.clone()); + not_empty.resize(new_len, false); + Self::new(block_responses, account_responses, queries, not_empty) + } +} + +impl EthPreCircuit for MultiStorageCircuit { + fn create( + self, + mut builder: RlcThreadBuilder, + break_points: Option, + ) -> EthCircuitBuilder> { + let range = RangeChip::default(ETH_LOOKUP_BITS); + let chip = EthChip::new(RlpChip::new(&range, None), None); + let mut keccak = KeccakChip::default(); + // ================= FIRST PHASE ================ + let ctx = builder.gate_builder.main(FIRST_PHASE); + let queries = self + .queries + .into_iter() + .map(|query| { + let query = query.assign_storage(ctx, &range); + (query.slot.0, query.storage_pf) + }) + .collect_vec(); + let witness = + chip.parse_storage_proofs_phase0(&mut builder.gate_builder, &mut keccak, queries); + let ctx = builder.gate_builder.main(FIRST_PHASE); + let (mut storage_responses, not_empty): (Vec<_>, Vec<_>) = witness + .iter() + .zip_eq(self.not_empty) + .map(|(w, not_empty)| { + let not_empty = load_bool(ctx, range.gate(), not_empty); + (StorageResponse::from_witness(w, ctx, range.gate()), not_empty) + }) + .unzip(); + // set slot value to uint256(0) when the slot does not exist in the storage trie + for (w, storage) in witness.iter().zip(storage_responses.iter_mut()) { + // constrain the MPT proof must have non-zero depth to exclude the unsupported case of an empty storage trie + let depth_is_zero = range.gate.is_zero(ctx, w.mpt_witness.depth); + let depth_is_nonzero = range.gate.not(ctx, depth_is_zero); + range.gate.assert_is_const(ctx, &depth_is_nonzero, &Fr::one()); + + let slot_is_empty = w.mpt_witness.slot_is_empty; + for byte in &mut storage.value.0 { + *byte = range.gate().mul_not(ctx, slot_is_empty, *byte); + } + } + let block_responses = self + .block_responses + .into_iter() + .map(|(word, num)| { + let keccak_bytes = FixedByteArray::new(ctx, &range, &num.to_be_bytes()); + (word, keccak_bytes) + }) + .collect_vec(); + let account_responses = self + .account_responses + .into_iter() + .map(|(word, addr)| { + let keccak_bytes = FixedByteArray::new(ctx, &range, addr.as_bytes()); + (word, keccak_bytes) + }) + .collect_vec(); + // hash responses + let keccak_root = get_storage_response_keccak_root( + ctx, + range.gate(), + &mut keccak, + block_responses.iter().map(|(_, bytes)| bytes), + account_responses.iter().map(|(_, bytes)| bytes), + &storage_responses, + not_empty.clone(), + ); + + let loader = + Halo2Loader::::new(DummyEccChip(range.gate()), builder.gate_builder); + let mut poseidon = Poseidon::from_spec(&loader, POSEIDON_SPEC.clone()); + + let mut assigned_instances = keccak_root.0; + assigned_instances.extend(get_storage_response_poseidon_roots( + &loader, + &mut poseidon, + block_responses, + account_responses, + &storage_responses, + not_empty, + )); + builder.gate_builder = loader.take_ctx(); + + // ================= SECOND PHASE ================ + EthCircuitBuilder::new( + assigned_instances, + builder, + RefCell::new(keccak), + range, + break_points, + move |builder: &mut RlcThreadBuilder, + rlp: RlpChip, + keccak_rlcs: (FixedLenRLCs, VarLenRLCs)| { + // ======== SECOND PHASE =========== + let chip = EthChip::new(rlp, Some(keccak_rlcs)); + chip.parse_storage_proofs_phase1(builder, witness); + }, + ) + } +} + +lazy_static! { + pub static ref DEFAULT_STORAGE_PROOF: EIP1186ProofResponse = serde_json::from_str( + r#"{"address":"0x01d5b501c1fc0121e1411970fb79c322737025c2","balance":"0x0","codeHash":"0xa633dd6234bb961a983a1a1e6d22088dfbbd623dede31449808b7b1eda575b7e","nonce":"0x1","storageHash":"0x209f60372065d53cb2b7d98ffbb1c6c3dcb60c2a30317619da189ccb2c6bad55","accountProof":["0xf90211a01dd0fec2b3f9d15468345ac46f6f57fc94d605c3fea7ca46938b3ecc125d3a4ca0850577263ea02739b6f62b8be0295a75550f80003a11842e5fe5d5afe30ac227a097b6edf8102741cc9b6d71ff2f642ddf33dd92c9ae8aba278517e0b91d2fbaa6a0e09ee77f537b2be646ecd82b264eac2c1845dd386a2935ac3313784158f31dd1a05efaa9476bdcbc6d6b4ddd0f05469824a9050d0492f1964f172beb29048a34bda0787865ffaac02ab287f93750b4dda4210bec374f7ffe3d5ea1d868154b2cc4bca01b046ca2bba9829937a96a9a33a49640a21d73ceaa31c96773cef9ac1cd5971ca037a0acaec88503df71185243fab25f327d07518dc7955da9450fcaa544137fc9a0e7b8d8f71ac648d0627c1c3a384763ce76b11550bc754f39c73ef686b0f0eddea03a7580472f61533e60be8e3d07c99446ad82dbf281546ca319d9324957bccf64a0d23283f05bc9bfa37c6b848832ccb94eef6e5a122d7c01a8fac8682b5f5a6724a0985070f0f845671b55f27534b013f183259ad19fe85e5473dd391a691eedfd99a028d103e427c16fedaa9cf5c038a85c86b901906fcb9bd856f45f4cc3ebee2562a06420015278390e56ec77f1f7dc6974f662da50060403be62b3e1e1913555a12aa0847f86badfd749741d5fca3d45459423fdf246368dfb73ee5e1359b9d3a81768a0f3f59fe568d6e11a0bc5f530ba75845529cf615b7365270a37c739180d37c9b080","0xf90211a0591bec78bcafecec4293f55fcc09da0eb48a77bf5f7790507c1fdce6416d4490a09e087976bc3828fba335f473c7a98df55a734375db72eda2201cdda3392b8af1a075e0854232f5c27abb59a60df7d875a1a87372461b28e4de5d16672b8a190af0a009522bf32fc4d343e40a3e64fd2915ee86694de766480e1135b47b5f2e3742bfa0b318c26d659f0ca40072bf8a37b7d1effa956844f839ae59918ec4695a257aefa0d7ccbc5b52984e066d5129aa0ccc3b4a09fbd43dbd59ecd2175c5ba8dacfa76aa0a7e1e7d10f3dcd1ae968a8cedd5b3cb859cb1eccd659e8d41da0174f68ffe291a08215e04c748b8e7260accc0506fe42c51ceb40d7fad92cb77abe2984cb7398f9a095eff22ca4336d5a65a772b46f8f0ac85b7dc6661108c667d0e7a7addd0bfed0a07ba3addc959ca81049c5dc65667d9d6a73484446fe2f3fe09b480c88d37f72dba02c6fa34d13c6e61ce4163b5590ceed16d4385a8a4dd51a391cedf98b3d94c153a0f525881d63bf204a5f7d8e60696581245dc3a3819963e9c7f587cfe6c3068f1ca0b47f5c965b1ce8ab9418f0570ad7b3b4b6296f9b3cba0b181d528d63e288dce2a0a5cf76a3d817e0fc74e47b15e1fc0eca75c4515120dfba7652cf3f637e5603a7a018c157a9be63ea00f432f94bacac4433cc32af504c616dc23fbd44fcd508d8e7a0c799da8c943c40802bc5553fa2efd5ffe1cc63fc2d3bccdca80c8e42e1c9d5a680","0xf90211a0c1d8a57bb338396435d7ac695fa8d74afdb65691ae29901d5e6bde73fb6b2ea3a00f44d497551cceaab46983cc1d79d3e8e2e4389f985bdaafbede19bbea73d8e4a0d6b55d1744d625bd588749810060e73eb411fe440a256f20580f46a39bdd1e8da0b506b59abbc8efd27e3d7b6aba7ce645c945a6cf3ecd8997d4a0dda0fad3c40da0b1abdd1ae9e9e4b19a583ea4ec0036c20c564fb421713d77c66de5106a21294aa07ef4df175184c42cc2ce7c9a88bb8769a3fd54a0d695fdbab7de0fe69bb4431fa06c537d7439d232a0cd16b17f509cba3c9f148e4e1d3ee2847e1fca9714378f96a06ef083d4b9ee50b9577d00cbc3e725cf154f7e531127c8c16fc8b449b25f73a7a0eb41fea54f05ea279ac0f44a31541fed62dad135faaa280570f549a924c27b66a09a85cc6a4bd49e2777247f00b4753f66612045013c7055e8ffe0ff2c54ebeca4a0ae0372d3b2998eed3f808f7cdd32c2fabb249ffa0ae1971bb3649fc58a160ceca0b185f343a574a15ca2f24d1ec776c0976fdff168bc26e9eef6dbc9ec09afcf0ea0c636a2dc92e4afdcd8430786f61e6d373de50d095e52ca463339c198d5359699a00a3d985dc08852e742257921a3762a14ef7894902ab81b8803383718e6bbb328a0842bccd655d45b45de291da6220e08286f421af7ab93223b23487b06a9dd8613a0e2a5a0f6e34ab2a72f2c31b064f34680cfd6d249314d62c5dfa6a036022c000480","0xf90211a043517cac2a83a56c58aa8f6c850c449f62e832d3fb8a9c2197334ef13719ba13a0ff9f673042cf317d247a1a4c0daa4998f593cba1ba40e80ed60a48d382b52e55a0c3bf4ce63800fa68a0314dcfa32849f873c82141ef622a6c11dbc8b7ee940a85a0a70d654d0718c839cc39971edb2ec70c3258828a523b6a155dcb20e81c48cf70a0f0635baf8c1a8a0ac4a03b73ab73d615c7a8eebaddc35ca0a50ca0ef0962bbf6a01202be6997900e1c699f8a2b7a2cd672c620f81fe8dcb2a407d1ed1c0fa56392a0a9773fca2325dd223ee16dc4c4cc3833a6949460ff13958cba2332173e44b829a0f1d33803fc9608a4b0ef316335297c5e7d22aadb3b5ace10b67e691ec126d11ea0d1bf809ac42ce2fa1f9df3c2d99843cf8a5d9f3c54650ee82dd9666ea7b47d24a0c3eada190e706f5be791c34bb7f35a83514246f3d8ff05bd1f78d6afa1a7f2f6a0740224e6867c4264d22e2617688b54bb3a6800a287056f2a55bcaf43f010fb75a03811a47a97b63f394e3b6fad9dc48bf03021ec993a7c2b3a7e8daa3a77ff78c9a0d3df72d1f0e4152d7c529b9ddce5598021c3004a1428fcee8304b6295687d439a099ea137fc015f2bf06247754bbd21ddbd891e914aa48e2db817afb7c36115f8da03001015ae6d4d974ec474445c8e09f7acd78fba0b34069f0f4ceff48618bd571a0ba1d7ce92b926a621e10b3dbceb60a42059ff3883fb3644c3fb51c96a3cfaa9480","0xf90211a0b0f95458177d0353a5b90006078160e0b2d87686230fee05f229feaf20b4d394a0da99a1217fc5aa21888eb2f845a0598690f6ac8d9cc1d48c294f8a83e75b8eaca0bf541d94d39c03263a6b42184efec7e896a58df03bd5933aae9d7fc6dd83db7da03f46a7a06b873492728188390004981c20562264ca270443ea06e4f55e3bab41a091a645a1d5936bf4ead6177a1225b8d6b92199aa3bea18a8bac109615218a53ca00065275af4349b8296593480173b24de236ec8944e5ff6b4c63682e4eed00a91a0a3ec50593073d617e7ae4cdb22192197c34577857fb532684127bf3b7774702ea0e339fc34ed27c2070926b6fe029c51630fb99381e21813b998933aa6575a4322a034d29a9082f9951c53991f299720d4a833feab0ee24dbccac629bc4dcb79ccc0a089e19dd8d40e42a3902dacc8727fdc5b13ee631d5ff09bd7d77b1536a9bd429da0088c7a47f46758b7d78233b9c116afeff2b11cd7e3bad52bc9a3a84d3c5fd3caa017a3e64263dcd5a4b5143963d87eec7c717d19b4e47aeb4b91c18039c62d502fa0be8d80129c4255b6cc9696f845ae5d07894f2505f32f62263d937ecf26834a91a0884fecc706698cabaf7fcf141bea6030167a93b745c5d232eeaab9078d579494a0df2b6f2700fd2df95b3349f261b1c8f9573dd41dfb4bbd47d2a3be9a1b0b90c2a036576ca9089c79fb55d210de4db554d9e638191fa38e1a37b60683a7d12b5dc180","0xf90211a0270f956b324726c987eaa3cded56b9bef8aaf385458daab7acdd70bfd203ee28a031fdda7c3b21ef9eab72b3e64b7fd3bec6b79a200697f7f4b5156a478e7833fba08dccb69de56beabb4084d50f35fc9cd45f7127ca254b529b13c625838ee15a33a0fad147b7a08030aa316015d13d0cc8a3dbadd3c107dfa7853b5dfffac9a5a738a01e03b5dddc0c2f6666b1c75b29909e7db6cfc26050a2ebbc08788a34e332a280a090b012cd85d1fe14241a1f58c8b02715f6b2d7835e9e5b61422ae05124034536a0113d75e61d8f40e30c4ef41974d81a99978a257af9f187086cd3e768ab20b2caa0a3ca2c0ba374ac77bb2e852dafc5169bfddacca5955a7dc43e8cb6d1d1ea9fbaa0f32d3b24f5495371dfbe423e74889e1d08028517dba2a8bab415cdb09a70ce07a0e5e71a4b10aca494fefe50259e497ba4d64d65f719c170bc9dca2a8178e9e8a3a03795fbe7c7fb69733db3e70e04ac1a297181214f0fca67c15480b7adf95cf52ba0e647bb44f3f3150774d478476b7977f7c2e1c2484746c46263ff74e7c5a9078aa07d7d448016779bd028116dea371aebe74036ed8356bcc07ce457a5b7bdd737dfa0100b0edbd15981e7f97fb293dee6a2fa18df5b9399caf20535158f5009d3c7f1a0a13439769cf90bcd6cd9d311e20f2a87ec616354314e3e1b4b2ff083ff4fc5dca0468329217298d1ffa8e41eab4678d02c7f589eca3c935f0dd191bdf44646947080","0xf90111a00e3484808a216730502daa7ab011fdcc6eb6df9dfcf65a97a170223b71fb0ac080a085a500be60f07ff9d75dcda7c1a9149a15219218188153b9b0227fa4bc523d75a0314d5dceb46c93156dd04a452d883b33bc55532b9f753530576c962cf80dc31880808080a0228d682f2312e51f484d2a3c3e53e987536e5a08afdc5bdd91345eff0c6c07f48080a0a478b8506e6cffc9dddb38a334a15a112d2ed155c2eb02438a4a329821ddf0dea0d37bd3a8c6197e501c6259a6bb50da18900c253f06eabec3b4c17633214c450da076eca457a853dfbfca74edb837bbd53865c0ba85734cece65f06242a6d6fe25c80a03aefcdb44bcd162364bafeddae1b7f8e2c7575e1c1b43032056650e17aab470480","0xf8669d373d3b2842e26222b7332b138c965d6e98a2f70add6324f24cc764fa8eb846f8440180a0209f60372065d53cb2b7d98ffbb1c6c3dcb60c2a30317619da189ccb2c6bad55a0a633dd6234bb961a983a1a1e6d22088dfbbd623dede31449808b7b1eda575b7e"],"storageProof":[{"key":"0x0000000000000000000000000000000000000000000000000000000000000000","proof":["0xf8518080a000ebce886332cd57419a907349ecfbd07043791d641877410ca470e69dcdd9f48080808080808080a06e3176d9ae4126e5bee6550649d603653ecde555bb105b6a81178f8908fb473e8080808080","0xf7a0390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639594f0e3b9aada6d89ddeb34aab7e9cd1744cf90d82f"],"value":"0xf0e3b9aada6d89ddeb34aab7e9cd1744cf90d82f"}]}"# + ).unwrap(); + + pub static ref DEFAULT_STORAGE_QUERY: EthStorageInput = { + let pf = &DEFAULT_STORAGE_PROOF; + let addr = Address::from_str("0x01d5b501c1fc0121e1411970fb79c322737025c2").unwrap(); + let state_root = H256::from_str("0x32b26146b9b2a3ea68eb74585a124f912b8cbfe788696c7a86a79c91086c89f0").unwrap(); + + let acct_key = H256(keccak256(addr)); + let acct_state = get_acct_list(pf); + let acct_pf = MPTFixedKeyInput { + path: acct_key, + value: get_acct_rlp(pf), + root_hash: state_root, + proof: pf.account_proof.iter().map(|x| x.to_vec()).collect(), + value_max_byte_len: ACCOUNT_PROOF_VALUE_MAX_BYTE_LEN, + max_depth: ACCOUNT_PROOF_MAX_DEPTH, + slot_is_empty: false, + }; + let storage_pfs = pf + .storage_proof + .iter() + .map(|storage_pf| { + let path = H256(keccak256(storage_pf.key)); + let value = storage_pf.value.rlp_bytes().to_vec(); + ( + storage_pf.key, + storage_pf.value, + MPTFixedKeyInput { + path, + value, + root_hash: pf.storage_hash, + proof: storage_pf.proof.iter().map(|x| x.to_vec()).collect(), + value_max_byte_len: STORAGE_PROOF_VALUE_MAX_BYTE_LEN, + max_depth: STORAGE_PROOF_MAX_DEPTH, + slot_is_empty: false, + }, + ) + }) + .collect(); + EthStorageInput { addr, acct_state, acct_pf, storage_pfs } + }; +} diff --git a/axiom-eth/src/batch_query/scheduler/circuit_types.rs b/axiom-eth/src/batch_query/scheduler/circuit_types.rs new file mode 100644 index 000000000..c31eb96fe --- /dev/null +++ b/axiom-eth/src/batch_query/scheduler/circuit_types.rs @@ -0,0 +1,183 @@ +use std::{hash::Hash, path::Path}; + +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::{ + util::{scheduler, AggregationConfigPinning, EthConfigPinning, Halo2ConfigPinning}, + Network, +}; + +/// Schema to aggregate the proof of 2total_arity rows of either a single response column or a response table. +/// +/// Let `n = start_arity` and `N = total_arity`. We recursively prove snarks for 2n, 2n, 2n + 1, ..., 2N - 1 entries, for a total of 2N entries. +/// +/// At level `i`, we prove 2n + max(0, i - 1) entries in a single snark and then recursively aggregate this snark with the snark for levels `> i`. The last level is level `N - n`. +/// +/// At level `> 0` we use `PublicAggregationCircuit` for the aggregation. At level `0` we use `{Merkle,Poseidon}AggregationCircuit` to compute Merkle tree roots. +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct ExponentialSchema { + pub start_arity: usize, + pub total_arity: usize, + pub level: usize, +} + +impl ExponentialSchema { + pub fn new(start_arity: usize, total_arity: usize, level: usize) -> Self { + assert!(start_arity <= total_arity); + assert!(level <= total_arity - start_arity); + Self { start_arity, total_arity, level } + } + + pub fn next(&self) -> Self { + Self::new(self.start_arity, self.total_arity, self.level + 1) + } + + pub fn level_arity(&self) -> usize { + self.start_arity + self.level.saturating_sub(1) + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum InitialCircuitType { + Account, + Storage, + RowConsistency, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct ResponseCircuitType { + pub initial_type: InitialCircuitType, + pub schema: ExponentialSchema, + pub aggregate: bool, +} + +impl scheduler::CircuitType for ResponseCircuitType { + fn name(&self) -> String { + // mostly for human readability, does not need to be collision resistant (we just manually delete old pkeys) + let prefix = match self.initial_type { + InitialCircuitType::Account => "account".to_string(), + InitialCircuitType::Storage => "storage".to_string(), + InitialCircuitType::RowConsistency => "row".to_string(), + }; + if !self.aggregate { + format!("{prefix}_{}", self.schema.level_arity()) + } else { + format!( + "{prefix}_{}_{}_{}", + self.schema.start_arity, self.schema.total_arity, self.schema.level + ) + } + } + + fn get_degree_from_pinning(&self, path: impl AsRef) -> u32 { + let col_degree = |agg: bool| match agg { + false => EthConfigPinning::from_path(path.as_ref()).degree(), + true => AggregationConfigPinning::from_path(path.as_ref()).degree(), + }; + match self.initial_type { + InitialCircuitType::Account => col_degree(self.aggregate), + InitialCircuitType::Storage => col_degree(self.aggregate), + InitialCircuitType::RowConsistency => { + AggregationConfigPinning::from_path(path).degree() + } + } + } +} + +/// Schema to aggregate the proof of 2arities.sum() rows. +/// +/// * `arities`: At level `i` we prove 2arities\[i\] entries if i = 0 or aggregate 2arities\[i\] previous proofs. +/// Here level is “reverse” from depth, so the root has the highest level, while leaf is level 0. +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct BlockVerifyVsMmrCircuitType { + pub network: Network, + pub arities: Vec, +} + +impl scheduler::CircuitType for BlockVerifyVsMmrCircuitType { + fn name(&self) -> String { + format!("{}_block_{}", self.network, self.arities.iter().join("_")) + } + fn get_degree_from_pinning(&self, pinning_path: impl AsRef) -> u32 { + if self.arities.len() == 1 { + EthConfigPinning::from_path(pinning_path.as_ref()).degree() + } else { + AggregationConfigPinning::from_path(pinning_path.as_ref()).degree() + } + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct FinalAssemblyCircuitType { + /// Performs `round` rounds of SNARK verification using `PublicAggregationCircuit` on the final circuit. + /// This is used to reduce circuit size and final EVM verification gas costs. + pub round: usize, + pub network: Network, + pub block_arities: Vec, + pub account_schema: ExponentialSchema, + pub storage_schema: ExponentialSchema, + pub row_schema: ExponentialSchema, +} + +impl FinalAssemblyCircuitType { + pub fn new( + round: usize, + network: Network, + block_arities: Vec, + account_schema: ExponentialSchema, + storage_schema: ExponentialSchema, + row_schema: ExponentialSchema, + ) -> Self { + let block_arity = block_arities.iter().sum::(); + assert_eq!(block_arity, account_schema.total_arity); + assert_eq!(block_arity, storage_schema.total_arity); + assert_eq!(block_arity, row_schema.total_arity); + Self { round, network, block_arities, account_schema, storage_schema, row_schema } + } +} + +impl scheduler::CircuitType for FinalAssemblyCircuitType { + fn name(&self) -> String { + format!("final_{}", self.round) + } + + fn get_degree_from_pinning(&self, pinning_path: impl AsRef) -> u32 { + if self.round == 0 { + EthConfigPinning::from_path(pinning_path.as_ref()).degree() + } else { + AggregationConfigPinning::from_path(pinning_path.as_ref()).degree() + } + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum CircuitType { + /// Circuit to either create a response column or check consistency of the response table + Response(ResponseCircuitType), + /// Circuit to verify block responses against block hash MMR + VerifyVsMmr(BlockVerifyVsMmrCircuitType), + /// Circuit to aggregate all response columns into a single response table and verify consistency. + Final(FinalAssemblyCircuitType), +} + +impl scheduler::CircuitType for CircuitType { + fn name(&self) -> String { + match self { + CircuitType::Response(circuit_type) => circuit_type.name(), + CircuitType::VerifyVsMmr(circuit_type) => circuit_type.name(), + CircuitType::Final(circuit_type) => circuit_type.name(), + } + } + fn get_degree_from_pinning(&self, pinning_path: impl AsRef) -> u32 { + match self { + CircuitType::Response(circuit_type) => { + circuit_type.get_degree_from_pinning(pinning_path) + } + CircuitType::VerifyVsMmr(circuit_type) => { + circuit_type.get_degree_from_pinning(pinning_path) + } + CircuitType::Final(circuit_type) => circuit_type.get_degree_from_pinning(pinning_path), + } + } +} diff --git a/axiom-eth/src/batch_query/scheduler/mod.rs b/axiom-eth/src/batch_query/scheduler/mod.rs new file mode 100644 index 000000000..df70668c8 --- /dev/null +++ b/axiom-eth/src/batch_query/scheduler/mod.rs @@ -0,0 +1,4 @@ +pub mod circuit_types; +mod precircuits; +pub mod router; +pub mod tasks; diff --git a/axiom-eth/src/batch_query/scheduler/precircuits.rs b/axiom-eth/src/batch_query/scheduler/precircuits.rs new file mode 100644 index 000000000..3b57ebccd --- /dev/null +++ b/axiom-eth/src/batch_query/scheduler/precircuits.rs @@ -0,0 +1,100 @@ +use std::env::var; + +use halo2_base::{ + gates::builder::CircuitBuilderStage, + halo2_proofs::{ + halo2curves::bn256::{Bn256, Fr}, + poly::{commitment::Params, kzg::commitment::ParamsKZG}, + }, +}; + +use crate::{ + batch_query::{ + aggregation::{ + FinalResponseAssemblyCircuit, MultiBlockAggregationCircuit, PoseidonAggregationCircuit, + }, + response::row_consistency::RowConsistencyCircuit, + }, + util::{ + circuit::{PinnableCircuit, PreCircuit}, + AggregationConfigPinning, EthConfigPinning, Halo2ConfigPinning, + }, + AggregationPreCircuit, +}; + +// MultiBlockCircuit, MultiAccountCircuit, MultiStorageCircuit are all EthPreCircuits, which auto-implement PreCircuit +// Rust does not allow two different traits to both auto-implement PreCircuit (because it cannot then determine conflicting implementations), so the rest we do manually: + +impl PreCircuit for RowConsistencyCircuit { + type Pinning = AggregationConfigPinning; + + fn create_circuit( + self, + stage: CircuitBuilderStage, + pinning: Option, + params: &ParamsKZG, + ) -> impl PinnableCircuit { + let break_points = pinning.map(|p| p.break_points()); + RowConsistencyCircuit::create_circuit(self, stage, break_points, params.k()) + } +} + +impl PreCircuit for PoseidonAggregationCircuit { + type Pinning = AggregationConfigPinning; + + fn create_circuit( + self, + stage: CircuitBuilderStage, + pinning: Option, + params: &ParamsKZG, + ) -> impl PinnableCircuit { + // look for lookup_bits either from pinning, if available, or from env var + let lookup_bits = pinning + .as_ref() + .map(|p| p.params.lookup_bits) + .or_else(|| var("LOOKUP_BITS").map(|v| v.parse().unwrap()).ok()) + .expect("LOOKUP_BITS is not set"); + let break_points = pinning.map(|p| p.break_points()); + AggregationPreCircuit::create_circuit(self, stage, break_points, lookup_bits, params) + } +} + +impl PreCircuit for MultiBlockAggregationCircuit { + type Pinning = AggregationConfigPinning; + + fn create_circuit( + self, + stage: CircuitBuilderStage, + pinning: Option, + params: &ParamsKZG, + ) -> impl PinnableCircuit { + // look for lookup_bits either from pinning, if available, or from env var + let lookup_bits = pinning + .as_ref() + .map(|p| p.params.lookup_bits) + .or_else(|| var("LOOKUP_BITS").map(|v| v.parse().unwrap()).ok()) + .expect("LOOKUP_BITS is not set"); + let break_points = pinning.map(|p| p.break_points()); + AggregationPreCircuit::create_circuit(self, stage, break_points, lookup_bits, params) + } +} + +impl PreCircuit for FinalResponseAssemblyCircuit { + type Pinning = EthConfigPinning; + + fn create_circuit( + self, + stage: CircuitBuilderStage, + pinning: Option, + params: &ParamsKZG, + ) -> impl PinnableCircuit { + // look for lookup_bits either from pinning, if available, or from env var + let lookup_bits = pinning + .as_ref() + .map(|p| p.params.lookup_bits.unwrap()) + .or_else(|| var("LOOKUP_BITS").map(|v| v.parse().unwrap()).ok()) + .expect("LOOKUP_BITS is not set"); + let break_points = pinning.map(|p| p.break_points()); + FinalResponseAssemblyCircuit::create_circuit(self, stage, break_points, lookup_bits, params) + } +} diff --git a/axiom-eth/src/batch_query/scheduler/router.rs b/axiom-eth/src/batch_query/scheduler/router.rs new file mode 100644 index 000000000..8af26d1f8 --- /dev/null +++ b/axiom-eth/src/batch_query/scheduler/router.rs @@ -0,0 +1,119 @@ +use std::path::Path; + +use any_circuit_derive::AnyCircuit; +use halo2_base::halo2_proofs::{ + halo2curves::bn256::{Bn256, G1Affine}, + plonk::ProvingKey, + poly::kzg::commitment::ParamsKZG, +}; +use itertools::Itertools; +use snark_verifier_sdk::Snark; + +use crate::{ + batch_query::{ + aggregation::{ + FinalResponseAssemblyCircuit, HashStrategy, MultiBlockAggregationCircuit, + PoseidonAggregationCircuit, + }, + response::{ + account::MultiAccountCircuit, + block_header::MultiBlockCircuit, + row_consistency::{RowConsistencyCircuit, ROW_CIRCUIT_NUM_INSTANCES}, + storage::MultiStorageCircuit, + }, + }, + util::{ + circuit::{AnyCircuit, PublicAggregationCircuit}, + scheduler::{self, EthScheduler}, + }, +}; + +use super::tasks::{ResponseInput, Task}; + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, AnyCircuit)] +pub enum CircuitRouter { + // response circuits + InitialAccount(MultiAccountCircuit), + InitialStorage(MultiStorageCircuit), + RowConsistency(RowConsistencyCircuit), + BlockVerifyVsMmr(MultiBlockCircuit), + // aggregation circuits + FinalAssembly(FinalResponseAssemblyCircuit), + Passthrough(PublicAggregationCircuit), + Poseidon(PoseidonAggregationCircuit), + BlockVerifyMmrAggregation(MultiBlockAggregationCircuit), +} + +pub type BatchQueryScheduler = EthScheduler; + +impl scheduler::Scheduler for BatchQueryScheduler { + type Task = Task; + type CircuitRouter = CircuitRouter; + + fn get_circuit(&self, task: Task, prev_snarks: Vec) -> CircuitRouter { + match task { + Task::Final(final_task) => { + if final_task.circuit_type.round != 0 { + assert_eq!(prev_snarks.len(), 1); + return CircuitRouter::Passthrough(PublicAggregationCircuit::new( + prev_snarks.into_iter().map(|snark| (snark, true)).collect(), + )); + } + let circuit_type = &final_task.circuit_type; + let block_has_acc = circuit_type.block_arities.len() != 1; + let [account_has_acc, storage_has_acc, row_has_acc] = [ + &circuit_type.account_schema, + &circuit_type.storage_schema, + &circuit_type.row_schema, + ] + .map(|schema| schema.start_arity != schema.total_arity); + let [block_snark, account_snark, storage_snark, row_snark]: [_; 4] = + prev_snarks.try_into().unwrap(); + CircuitRouter::FinalAssembly(FinalResponseAssemblyCircuit::new( + (block_snark, block_has_acc), + (account_snark, account_has_acc), + (storage_snark, storage_has_acc), + (row_snark, row_has_acc), + )) + } + Task::Response(task) => { + if task.aggregate { + let acc = (task.schema.level + 1) + != (task.schema.total_arity - task.schema.start_arity); + let prev_snarks = prev_snarks + .into_iter() + .zip_eq([false, acc]) + .map(|(snark, has_acc)| (snark, has_acc)) + .collect_vec(); + if !matches!(&task.input, ResponseInput::Row(_)) || task.schema.level != 0 { + CircuitRouter::Passthrough(PublicAggregationCircuit::new(prev_snarks)) + } else { + // final aggregation of row consistency should do poseidon hash onion + CircuitRouter::Poseidon(PoseidonAggregationCircuit::new( + HashStrategy::Onion, + prev_snarks, + ROW_CIRCUIT_NUM_INSTANCES, // all poseidon roots + )) + } + } else { + match task.input { + ResponseInput::Account(input) => CircuitRouter::InitialAccount(input), + ResponseInput::Storage(input) => CircuitRouter::InitialStorage(input), + ResponseInput::Row(input) => CircuitRouter::RowConsistency(input), + } + } + } + Task::BlockVerifyVsMmr(task) => { + if task.arities.len() == 1 { + CircuitRouter::BlockVerifyVsMmr(task.input) + } else { + let has_acc = task.arities.len() > 2; + CircuitRouter::BlockVerifyMmrAggregation(MultiBlockAggregationCircuit { + snarks: prev_snarks.into_iter().map(|snark| (snark, has_acc)).collect(), + }) + } + } + } + } +} diff --git a/axiom-eth/src/batch_query/scheduler/tasks.rs b/axiom-eth/src/batch_query/scheduler/tasks.rs new file mode 100644 index 000000000..3b904d985 --- /dev/null +++ b/axiom-eth/src/batch_query/scheduler/tasks.rs @@ -0,0 +1,400 @@ +use ethers_core::{ + types::{Address, H256}, + utils::keccak256, +}; +use halo2_base::halo2_proofs::halo2curves::bn256::Fr; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use snark_verifier::util::hash::Poseidon; +use snark_verifier_sdk::{halo2::POSEIDON_SPEC, NativeLoader}; + +use crate::{ + batch_query::response::{ + account::MultiAccountCircuit, + block_header::MultiBlockCircuit, + native::{get_account_response, get_block_response, FullStorageResponse}, + row_consistency::RowConsistencyCircuit, + storage::{MultiStorageCircuit, DEFAULT_STORAGE_QUERY}, + }, + storage::{EthBlockStorageInput, EthStorageInput}, + util::scheduler, +}; + +use super::circuit_types::{ + BlockVerifyVsMmrCircuitType, CircuitType, ExponentialSchema, FinalAssemblyCircuitType, + InitialCircuitType, ResponseCircuitType, +}; + +/// The input queries for {block, account, storage} column task. +/// +/// The lengths of the queries do not need to be a power of two, because we will +/// pad it with "default" entries to optimize caching. +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ResponseInput { + Account(MultiAccountCircuit), + Storage(MultiStorageCircuit), + Row(RowConsistencyCircuit), +} + +impl ResponseInput { + fn initial_type(&self) -> InitialCircuitType { + match self { + ResponseInput::Account(_) => InitialCircuitType::Account, + ResponseInput::Storage(_) => InitialCircuitType::Storage, + ResponseInput::Row(_) => InitialCircuitType::RowConsistency, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ResponseTask { + pub input: ResponseInput, + pub schema: ExponentialSchema, + pub aggregate: bool, +} + +impl scheduler::Task for ResponseTask { + type CircuitType = ResponseCircuitType; + + fn circuit_type(&self) -> Self::CircuitType { + ResponseCircuitType { + initial_type: self.input.initial_type(), + schema: self.schema.clone(), + aggregate: self.aggregate, + } + } + + /// This needs to be collision-resistant because we are using file system for caching right now. + // Not the most efficient, but we're just going to serialize the whole task and keccak it + fn name(&self) -> String { + let hash = match &self.input { + ResponseInput::Row(_) => { + // something about `Block` makes this hard to serialize with bincode + keccak256(serde_json::to_vec(&self).expect("failed to serialize task")) + } + _ => keccak256(bincode::serialize(&self).expect("failed to serialize task")), + }; + format!("{:?}", H256(hash)) + } + + fn dependencies(&self) -> Vec { + if !self.aggregate || self.schema.level == self.schema.total_arity - self.schema.start_arity + { + return vec![]; + } + let arity = self.schema.level_arity(); + let mid = 1 << arity; + let prev_inputs = match &self.input { + ResponseInput::Account(input) => { + let (block_responses1, block_responses2) = input.block_responses.split_at(mid); + let (queries1, queries2) = input.queries.split_at(mid); + let (not_empty1, not_empty2) = input.not_empty.split_at(mid); + [block_responses1, block_responses2] + .zip([queries1, queries2]) + .zip([not_empty1, not_empty2]) + .map(|((block_responses, queries), not_empty)| MultiAccountCircuit { + block_responses: block_responses.to_vec(), + queries: queries.to_vec(), + not_empty: not_empty.to_vec(), + }) + .map(ResponseInput::Account) + } + ResponseInput::Storage(input) => { + let (block_responses1, block_responses2) = input.block_responses.split_at(mid); + let (account_responses1, account_responses2) = + input.account_responses.split_at(mid); + let (queries1, queries2) = input.queries.split_at(mid); + let (not_empty1, not_empty2) = input.not_empty.split_at(mid); + [block_responses1, block_responses2] + .zip([account_responses1, account_responses2]) + .zip([queries1, queries2]) + .zip([not_empty1, not_empty2]) + .map(|(((block_responses, account_responses), queries), not_empty)| { + MultiStorageCircuit { + block_responses: block_responses.to_vec(), + account_responses: account_responses.to_vec(), + queries: queries.to_vec(), + not_empty: not_empty.to_vec(), + } + }) + .map(ResponseInput::Storage) + } + ResponseInput::Row(input) => { + let (responses1, responses2) = input.responses.split_at(mid); + let (block_not_empty1, block_not_empty2) = input.block_not_empty.split_at(mid); + let (account_not_empty1, account_not_empty2) = + input.account_not_empty.split_at(mid); + let (storage_not_empty1, storage_not_empty2) = + input.storage_not_empty.split_at(mid); + [responses1, responses2] + .zip([block_not_empty1, block_not_empty2]) + .zip([account_not_empty1, account_not_empty2]) + .zip([storage_not_empty1, storage_not_empty2]) + .map( + |(((responses, block_not_empty), account_not_empty), storage_not_empty)| { + RowConsistencyCircuit { + responses: responses.to_vec(), + block_not_empty: block_not_empty.to_vec(), + account_not_empty: account_not_empty.to_vec(), + storage_not_empty: storage_not_empty.to_vec(), + network: input.network, + } + }, + ) + .map(ResponseInput::Row) + } + }; + let next_schema = self.schema.next(); + let agg = self.schema.level != (self.schema.total_arity - self.schema.start_arity - 1); + prev_inputs + .into_iter() + .zip([self.schema.clone(), next_schema]) + .zip([false, agg]) + .map(|((input, schema), aggregate)| Self { input, schema, aggregate }) + .collect() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BlockVerifyVsMmrTask { + pub input: MultiBlockCircuit, + pub arities: Vec, +} + +impl scheduler::Task for BlockVerifyVsMmrTask { + type CircuitType = BlockVerifyVsMmrCircuitType; + + fn circuit_type(&self) -> Self::CircuitType { + BlockVerifyVsMmrCircuitType { network: self.input.network, arities: self.arities.clone() } + } + + fn name(&self) -> String { + format!( + "{:?}", + H256(keccak256(bincode::serialize(&self).expect("failed to serialize task"))) + ) + } + + fn dependencies(&self) -> Vec { + assert!(!self.arities.is_empty()); + if self.arities.len() == 1 { + return vec![]; + } + let arity = self.arities.last().unwrap(); + let prev_arities = self.arities[..self.arities.len() - 1].to_vec(); + let prev_arity = prev_arities.iter().sum::(); + let chunk_size = 1 << prev_arity; + let num_chunks = 1 << arity; + + let mut prev_inputs = self + .input + .header_rlp_encodings + .chunks(chunk_size) + .zip_eq(self.input.not_empty.chunks(chunk_size)) + .zip_eq(self.input.mmr_proofs.chunks(chunk_size)) + .map(|((header_rlps, not_empty), mmr_proofs)| { + MultiBlockCircuit::resize_from( + header_rlps.to_vec(), + not_empty.to_vec(), + self.input.network, + self.input.mmr.to_vec(), + self.input.mmr_list_len, + mmr_proofs.iter().map(|pf| pf.to_vec()).collect(), + chunk_size, + ) + }) + .collect_vec(); + let dup = prev_inputs[0].clone(); + prev_inputs.resize(num_chunks, dup); + + prev_inputs.into_iter().map(|input| Self { input, arities: prev_arities.clone() }).collect() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct FinalAssemblyTask { + pub circuit_type: FinalAssemblyCircuitType, + pub input: Vec, + pub mmr: Vec, + pub mmr_num_blocks: usize, + pub mmr_proofs: Vec>, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum Task { + Response(ResponseTask), + BlockVerifyVsMmr(BlockVerifyVsMmrTask), + Final(FinalAssemblyTask), +} + +impl scheduler::Task for Task { + type CircuitType = CircuitType; + + fn circuit_type(&self) -> Self::CircuitType { + match self { + Task::Response(task) => CircuitType::Response(task.circuit_type()), + Task::BlockVerifyVsMmr(task) => CircuitType::VerifyVsMmr(task.circuit_type()), + Task::Final(task) => CircuitType::Final(task.circuit_type.clone()), + } + } + + fn name(&self) -> String { + match self { + Task::Response(task) => task.name(), + Task::BlockVerifyVsMmr(task) => task.name(), + Task::Final(task) => { + format!( + "final_{:?}", + H256(keccak256(serde_json::to_vec(&task).expect("failed to serialize task"))) + ) + } + } + } + + fn dependencies(&self) -> Vec { + match self { + Task::Response(task) => task.dependencies().into_iter().map(Task::Response).collect(), + Task::BlockVerifyVsMmr(task) => { + task.dependencies().into_iter().map(Task::BlockVerifyVsMmr).collect() + } + Task::Final(task) => { + if task.circuit_type.round != 0 { + let mut circuit_type = task.circuit_type.clone(); + circuit_type.round -= 1; + return vec![Task::Final(FinalAssemblyTask { circuit_type, ..task.clone() })]; + } + let circuit_type = &task.circuit_type; + + let total_arity: usize = circuit_type.block_arities.iter().sum::(); + assert_eq!(total_arity, circuit_type.account_schema.total_arity); + assert_eq!(total_arity, circuit_type.storage_schema.total_arity); + assert_eq!(total_arity, circuit_type.row_schema.total_arity); + let mut account_schema = circuit_type.account_schema.clone(); + let mut storage_schema = circuit_type.storage_schema.clone(); + let mut row_schema = circuit_type.row_schema.clone(); + account_schema.level = 0; + storage_schema.level = 0; + row_schema.level = 0; + + let network = circuit_type.network; + let len = 1 << total_arity; + let mut block_header_rlps = Vec::with_capacity(len); + let mut block_headers_poseidon = Vec::with_capacity(len); + let mut block_responses = Vec::with_capacity(len); + let mut account_responses = Vec::with_capacity(len); + let mut storage_inputs = Vec::with_capacity(len); + let mut block_not_empty = Vec::with_capacity(len); + let mut account_not_empty = Vec::with_capacity(len); + let mut storage_not_empty = Vec::with_capacity(len); + let mut responses = Vec::with_capacity(len); + + let mut poseidon = Poseidon::from_spec(&NativeLoader, POSEIDON_SPEC.clone()); + let hasher = &mut poseidon; + + for input in &task.input { + let mut response = EthBlockStorageInput::from(input.clone()); + block_header_rlps.push(response.block_header.clone()); + let ((block_res_p, _block_res_k), block_res) = + get_block_response(hasher, input.block.clone(), network); + block_responses.push((block_res_p, input.block.number.unwrap().as_u32())); + block_not_empty.push(true); + block_headers_poseidon.push(block_res.header_poseidon); + + if let Some(acct_storage) = &input.account_storage { + let ((acct_res_p, _acct_res_k), _) = + get_account_response(hasher, acct_storage); + account_responses.push((acct_res_p, acct_storage.addr)); + account_not_empty.push(true); + if !acct_storage.storage_pfs.is_empty() { + assert!(acct_storage.storage_pfs.len() == 1); + storage_inputs.push(acct_storage.clone()); + storage_not_empty.push(true); + } else { + response.storage.storage_pfs = + DEFAULT_STORAGE_QUERY.storage_pfs.clone(); + storage_inputs.push(response.storage.clone()); + storage_not_empty.push(false); + } + } else { + account_responses.push((Fr::zero(), Address::zero())); + account_not_empty.push(false); + storage_inputs.push(DEFAULT_STORAGE_QUERY.clone()); + storage_not_empty.push(false); + } + responses.push(response); + } + + let block_task = MultiBlockCircuit::resize_from( + block_header_rlps, + block_not_empty.clone(), + network, + task.mmr.clone(), + task.mmr_num_blocks, + task.mmr_proofs.clone(), + len, + ); + let acct_inputs = storage_inputs + .iter() + .map(|input| EthStorageInput { storage_pfs: vec![], ..input.clone() }) + .collect_vec(); + let acct_task = MultiAccountCircuit::resize_from( + block_responses + .iter() + .zip(account_not_empty.iter()) + .map(|(res, &ne)| if ne { *res } else { (Fr::zero(), 0) }) + .collect_vec(), + acct_inputs, + account_not_empty.clone(), + len, + ); + let storage_task = MultiStorageCircuit::resize_from( + block_responses + .iter() + .zip(storage_not_empty.iter()) + .map(|(res, &ne)| if ne { *res } else { (Fr::zero(), 0) }) + .collect_vec(), + account_responses + .iter() + .zip(storage_not_empty.iter()) + .map(|(res, &ne)| if ne { *res } else { (Fr::zero(), Address::zero()) }) + .collect_vec(), + storage_inputs, + storage_not_empty.clone(), + len, + ); + let row_consistency_task = RowConsistencyCircuit::resize_from( + responses, + block_not_empty, + account_not_empty, + storage_not_empty, + network, + len, + ); + + vec![ + Task::BlockVerifyVsMmr(BlockVerifyVsMmrTask { + input: block_task, + arities: circuit_type.block_arities.clone(), + }), + Task::Response(ResponseTask { + aggregate: account_schema.start_arity != account_schema.total_arity, + schema: account_schema, + input: ResponseInput::Account(acct_task), + }), + Task::Response(ResponseTask { + aggregate: storage_schema.start_arity != storage_schema.total_arity, + schema: storage_schema, + input: ResponseInput::Storage(storage_task), + }), + Task::Response(ResponseTask { + aggregate: row_schema.start_arity != row_schema.total_arity, + schema: row_schema, + input: ResponseInput::Row(row_consistency_task), + }), + ] + } + } + } +} diff --git a/axiom-eth/src/batch_query/tests/account.rs b/axiom-eth/src/batch_query/tests/account.rs new file mode 100644 index 000000000..9ba1fa3d5 --- /dev/null +++ b/axiom-eth/src/batch_query/tests/account.rs @@ -0,0 +1,124 @@ +use super::setup_provider; +use crate::{ + batch_query::{ + hash::{poseidon_tree_root, PoseidonWords}, + response::{ + account::MultiAccountCircuit, + native::{get_account_response, get_full_account_response}, + }, + tests::get_latest_block_number, + }, + rlp::builder::RlcThreadBuilder, + storage::EthStorageInput, + util::{encode_h256_to_field, h256_tree_root, EthConfigParams}, + EthPreCircuit, +}; +use ethers_core::types::H256; +use ff::Field; +use halo2_base::halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; +use itertools::Itertools; +use rand::{thread_rng, Rng}; +use rand_core::OsRng; +use snark_verifier::util::hash::Poseidon; +use snark_verifier_sdk::{halo2::POSEIDON_SPEC, NativeLoader}; +use std::env::set_var; +use test_log::test; + +pub fn native_account_instance( + block_responses: &[(Fr, u32)], + queries: &[EthStorageInput], + not_empty: &[bool], +) -> Vec { + // instance calculation natively for test validation + let mut poseidon = Poseidon::from_spec(&NativeLoader, POSEIDON_SPEC.clone()); + let block_numbers = + block_responses.iter().map(|res| PoseidonWords(vec![Fr::from(res.1 as u64)])).collect_vec(); + let block_num_root_p = poseidon_tree_root(&mut poseidon, block_numbers, &[]); + let (res_p, res_k): (Vec<_>, Vec<_>) = block_responses + .iter() + .zip_eq(queries.iter()) + .zip_eq(not_empty.iter()) + .map(|((block_response, query), not_empty)| { + let (acct_res, _) = get_account_response(&mut poseidon, query); + let (mut pos, mut kec) = + get_full_account_response(&mut poseidon, *block_response, acct_res); + if !not_empty { + pos = Fr::zero(); + kec = H256([0u8; 32]); + } + (pos, kec) + }) + .unzip(); + let root_p = poseidon_tree_root(&mut poseidon, res_p, &[]); + let root_k = h256_tree_root(&res_k); + let root_k = encode_h256_to_field(&root_k); + vec![root_k[0], root_k[1], root_p, block_num_root_p] +} + +fn test_mock_account_queries( + block_responses: Vec<(Fr, u32)>, + queries: Vec<(u64, &str)>, + not_empty: Vec, +) { + let params = EthConfigParams::from_path("configs/tests/account_query.json"); + set_var("ETH_CONFIG_PARAMS", serde_json::to_string(¶ms).unwrap()); + let k = params.degree; + + let queries = queries + .into_iter() + .map(|(block_number, address)| { + let address = address.parse().unwrap(); + (block_number, address) + }) + .collect(); + + let input = + MultiAccountCircuit::from_provider(&setup_provider(), block_responses, queries, not_empty); + let instance = + native_account_instance(&input.block_responses, &input.queries, &input.not_empty); + let circuit = input.create_circuit(RlcThreadBuilder::mock(), None); + + MockProver::run(k, &circuit, vec![instance]).unwrap().assert_satisfied(); +} + +#[test] +fn test_mock_account_queries_simple() { + let queries = vec![ + (17143006, "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B"), + (17143000, "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"), + (15000000, "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"), + (15411056, "0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6"), + ]; + let mut rng = thread_rng(); + let block_responses = queries.iter().map(|_| (Fr::random(OsRng), rng.gen())).collect(); + let not_empty = vec![true; queries.len()]; + test_mock_account_queries(block_responses, queries, not_empty); +} + +#[test] +fn test_mock_account_queries_genesis() { + // address existing in block 0 + let address = "0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0"; + let mut rng = thread_rng(); + let latest = get_latest_block_number(); + let mut queries: Vec<_> = (0..7).map(|_| (rng.gen_range(0..latest), address)).collect(); + queries.push((0, address)); + let block_responses = queries.iter().map(|_| (Fr::random(OsRng), rng.gen())).collect(); + let mut not_empty = vec![true; queries.len()]; + for ne in not_empty.iter_mut().take(queries.len() / 2) { + *ne = false; + } + test_mock_account_queries(block_responses, queries, not_empty); +} + +#[test] +fn test_mock_account_queries_empty() { + let address = "0x0000000000000000000000000000000000000000"; + let mut rng = thread_rng(); + let latest = get_latest_block_number(); + let mut queries: Vec<_> = (0..7).map(|_| (rng.gen_range(0..latest), address)).collect(); + queries.push((0, address)); + let block_responses = queries.iter().map(|_| (Fr::random(OsRng), rng.gen())).collect(); + let not_empty = vec![false; queries.len()]; + test_mock_account_queries(block_responses, queries, not_empty); +} diff --git a/axiom-eth/src/batch_query/tests/aggregation.rs b/axiom-eth/src/batch_query/tests/aggregation.rs new file mode 100644 index 000000000..24ee50494 --- /dev/null +++ b/axiom-eth/src/batch_query/tests/aggregation.rs @@ -0,0 +1,206 @@ +use crate::{ + batch_query::{ + aggregation::{HashStrategy, PoseidonAggregationCircuit}, + response::row_consistency::{RowConsistencyCircuit, ROW_CIRCUIT_NUM_INSTANCES}, + tests::storage::get_full_storage_inputs_nouns, + }, + storage::EthBlockStorageInput, + AggregationPreCircuit, Network, +}; + +use halo2_base::{ + gates::builder::CircuitBuilderStage, halo2_proofs::dev::MockProver, utils::fs::gen_srs, +}; +use itertools::Itertools; +use rand::{thread_rng, Rng}; + +use snark_verifier_sdk::{gen_pk, halo2::gen_snark_shplonk, LIMBS}; +use std::env::set_var; +use test_log::test; + +/*fn create_account_snark( + provider: &Provider, + block_responses: Vec<(Fr, H256)>, + queries: Vec<(u64, Address)>, + not_empty: Vec, +) -> Snark { + let params = EthConfigParams::from_path("configs/tests/account_query.json"); + set_var("ETH_CONFIG_PARAMS", serde_json::to_string(¶ms).unwrap()); + let k = params.degree; + + let input = MultiAccountCircuit::from_provider(provider, block_responses, queries, not_empty); + let params = gen_srs(k); + let circuit = input.create_circuit(CircuitBuilderStage::Mock, None, ¶ms); + let pk = gen_pk(¶ms, &circuit, None); + gen_snark_shplonk(¶ms, &pk, circuit, None::<&str>) +} + +fn test_mock_account_aggregation_depth( + block_responses: Vec<(Fr, H256)>, + queries: Vec<(u64, &str)>, + not_empty: Vec, + depth: usize, +) { + let provider = setup_provider(); + let chunk_size = queries.len() >> depth; + assert!(chunk_size != 0, "depth too large"); + let queries = queries + .into_iter() + .map(|(block_number, address)| { + let address = address.parse().unwrap(); + (block_number, address) + }) + .collect_vec(); + let snarks = block_responses + .chunks(chunk_size) + .zip(queries.chunks(chunk_size).zip(not_empty.chunks(chunk_size))) + .into_iter() + .map(|(block_responses, (queries, not_empty))| { + ( + create_account_snark( + &provider, + block_responses.to_vec(), + queries.to_vec(), + not_empty.to_vec(), + ), + false, + ) + }) + .collect_vec(); + + let input = MerkleAggregationCircuit::new( + HashStrategy::Tree, + snarks, + ACCOUNT_INSTANCE_SIZE, + ACCOUNT_POSEIDON_ROOT_INDICES.to_vec(), + ACCOUNT_KECCAK_ROOT_INDICES.to_vec(), + ); + let agg_k = 20; + let config_params = EthConfigParams { + degree: agg_k, + lookup_bits: Some(agg_k as usize - 1), + unusable_rows: 50, + ..Default::default() + }; + set_var("ETH_CONFIG_PARAMS", serde_json::to_string(&config_params).unwrap()); + let agg_params = gen_srs(agg_k); + let circuit = + input.create_circuit(CircuitBuilderStage::Mock, None, agg_k as usize - 1, &agg_params); + + let mut instance = circuit.instance(); + MockProver::run(agg_k, &circuit, vec![instance.clone()]).unwrap().assert_satisfied(); + // now check against expected + let queries = get_account_queries(&provider, queries, ACCOUNT_PROOF_MAX_DEPTH); + let native_instance = native_account_instance(&block_responses, &queries, ¬_empty); + assert_eq!(instance.split_off(4 * LIMBS), native_instance); +} + +#[test] +fn test_mock_account_aggregation() { + let mut queries = vec![ + (17143006, "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B"), + (17143000, "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"), + (15000000, "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"), + (15411056, "0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6"), + ]; + let address = "0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0"; + let mut rng = thread_rng(); + let latest = get_latest_block_number(); + let queries2: Vec<_> = (0..4).map(|_| (rng.gen_range(0..latest), address)).collect(); + queries.extend(queries2); + + let block_resp = queries.iter().map(|_| (Fr::random(OsRng), H256::random())).collect_vec(); + let not_empty = vec![true; queries.len()]; + + test_mock_account_aggregation_depth(block_resp.clone(), queries.clone(), not_empty.clone(), 1); + test_mock_account_aggregation_depth(block_resp, queries, not_empty, 2); +}*/ + +fn test_mock_row_consistency_aggregation_depth( + responses: Vec, + block_not_empty: Vec, + account_not_empty: Vec, + storage_not_empty: Vec, + depth: usize, +) { + assert!(responses.len().is_power_of_two()); + let chunk_size = responses.len() >> depth; + assert!(chunk_size != 0, "depth too large"); + let k = 20 - depth as u32; + let params = gen_srs(k); + let mut pk = None; + let snarks = + responses + .chunks(chunk_size) + .zip(block_not_empty.chunks(chunk_size).zip( + account_not_empty.chunks(chunk_size).zip(storage_not_empty.chunks(chunk_size)), + )) + .into_iter() + .map(|(response, (block_not_empty, (account_not_empty, storage_not_empty)))| { + let input = RowConsistencyCircuit::new( + response.to_vec(), + block_not_empty.to_vec(), + account_not_empty.to_vec(), + storage_not_empty.to_vec(), + Network::Mainnet, + ); + let circuit = input.create_circuit(CircuitBuilderStage::Mock, None, k); + if pk.is_none() { + pk = Some(gen_pk(¶ms, &circuit, None)); + } + (gen_snark_shplonk(¶ms, pk.as_ref().unwrap(), circuit, None::<&str>), false) + }) + .collect_vec(); + + let input = + PoseidonAggregationCircuit::new(HashStrategy::Tree, snarks, ROW_CIRCUIT_NUM_INSTANCES); + let agg_k = 20; + let agg_params = gen_srs(agg_k); + let lookup_bits = agg_k as usize - 1; + set_var("LOOKUP_BITS", lookup_bits.to_string()); + let circuit = AggregationPreCircuit::create_circuit( + input, + CircuitBuilderStage::Mock, + None, + lookup_bits, + &agg_params, + ); + let mut instance = circuit.instance(); + MockProver::run(agg_k, &circuit, vec![instance.clone()]).unwrap().assert_satisfied(); + + // now check against non-aggregated + let input = RowConsistencyCircuit::new( + responses, + block_not_empty, + account_not_empty, + storage_not_empty, + Network::Mainnet, + ); + set_var("LOOKUP_BITS", "0"); + let circuit = input.create_circuit(CircuitBuilderStage::Mock, None, agg_k); + assert_eq!(instance.split_off(4 * LIMBS), circuit.instance()); +} + +#[test] +fn test_mock_row_consistency_aggregation_nouns() { + let responses = get_full_storage_inputs_nouns(128); + let mut rng = thread_rng(); + let block_not_empty = responses.iter().map(|_| rng.gen_bool(0.8)).collect_vec(); + let account_not_empty = block_not_empty.iter().map(|ne| rng.gen_bool(0.8) && *ne).collect_vec(); + let storage_not_empty = + account_not_empty.iter().map(|ne| rng.gen_bool(0.8) && *ne).collect_vec(); + test_mock_row_consistency_aggregation_depth( + responses.clone(), + block_not_empty.clone(), + account_not_empty.clone(), + storage_not_empty.clone(), + 1, + ); + test_mock_row_consistency_aggregation_depth( + responses, + block_not_empty, + account_not_empty, + storage_not_empty, + 3, + ); +} diff --git a/axiom-eth/src/batch_query/tests/block_header.rs b/axiom-eth/src/batch_query/tests/block_header.rs new file mode 100644 index 000000000..4d547e72e --- /dev/null +++ b/axiom-eth/src/batch_query/tests/block_header.rs @@ -0,0 +1,106 @@ +use super::{setup_provider, setup_provider_goerli}; +use crate::{ + batch_query::{ + hash::poseidon_tree_root, + response::{ + block_header::{ + MultiBlockCircuit, GENESIS_BLOCK, GENESIS_BLOCK_RLP, MMR_MAX_NUM_PEAKS, + }, + native::get_block_response, + }, + tests::get_latest_block_number, + }, + providers::{get_block_rlp, get_blocks}, + rlp::builder::RlcThreadBuilder, + util::{encode_h256_to_field, h256_tree_root, EthConfigParams}, + EthPreCircuit, Network, +}; +use ethers_core::{types::H256, utils::keccak256}; +use halo2_base::halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; +use itertools::Itertools; +use rand::Rng; +use rand_chacha::ChaChaRng; +use rand_core::SeedableRng; +use snark_verifier::util::hash::Poseidon; +use snark_verifier_sdk::{halo2::POSEIDON_SPEC, NativeLoader}; +use std::env::set_var; +use test_log::test; + +fn test_mock_block_queries( + block_numbers: Vec, + not_empty: Vec, + network: Network, + expected: bool, +) { + let params = EthConfigParams::from_path("configs/tests/block_query.json"); + set_var("ETH_CONFIG_PARAMS", serde_json::to_string(¶ms).unwrap()); + let k = params.degree; + + let provider = match network { + Network::Mainnet => setup_provider(), + Network::Goerli => setup_provider_goerli(), + }; + let mmr_len = *block_numbers.iter().max().unwrap_or(&0) as usize + 1; + let blocks = get_blocks(&provider, block_numbers).unwrap(); + let header_rlps = blocks + .iter() + .map(|block| get_block_rlp(block.as_ref().expect("block not found"))) + .collect_vec(); + // this is just to get mmr_bit check to pass, we do not check real mmr in this test + let mmr = (0..MMR_MAX_NUM_PEAKS) + .map(|i| H256::from_low_u64_be((mmr_len as u64 >> i) & 1u64)) + .collect_vec(); + let mmr_proofs = vec![vec![]; header_rlps.len()]; + let input = + MultiBlockCircuit::new(header_rlps, not_empty.clone(), network, mmr, mmr_len, mmr_proofs); + // instance calculation natively for test validation + let mut poseidon = Poseidon::from_spec(&NativeLoader, POSEIDON_SPEC.clone()); + let (res_p, res_k): (Vec<_>, Vec<_>) = blocks + .into_iter() + .zip_eq(not_empty) + .map(|(block, not_empty)| { + let ((mut pos, mut kec), _) = + get_block_response(&mut poseidon, block.unwrap(), network); + if !not_empty { + pos = Fr::zero(); + kec = H256([0u8; 32]); + } + (pos, kec) + }) + .unzip(); + let root_p = poseidon_tree_root(&mut poseidon, res_p, &[]); + let root_k = h256_tree_root(&res_k); + let root_k = encode_h256_to_field(&root_k); + + let circuit = input.create_circuit(RlcThreadBuilder::mock(), None); + let instance = circuit.instance(); + for (a, b) in [root_k[0], root_k[1], root_p].into_iter().zip(instance.iter()) { + assert_eq!(a, *b); + } + + if expected { + MockProver::run(k, &circuit, vec![instance]).unwrap().assert_satisfied(); + } +} + +#[test] +fn test_mock_block_queries_random() { + let len = 32; + let mut rng = ChaChaRng::from_seed([0u8; 32]); + let latest = get_latest_block_number(); + let block_numbers = (0..len).map(|_| rng.gen_range(0..latest)).collect_vec(); + // test instance generation but not mock prover since no mmr proof + test_mock_block_queries(block_numbers.clone(), vec![true; len], Network::Mainnet, false); + // test circuit but not hash values + test_mock_block_queries(block_numbers, vec![false; len], Network::Mainnet, true); +} + +#[test] +fn test_genesis_block() { + let block = get_blocks(&setup_provider(), [0]).unwrap().pop().unwrap().unwrap(); + assert_eq!(GENESIS_BLOCK.clone(), block); + assert_eq!( + format!("{:?}", H256(keccak256(GENESIS_BLOCK_RLP))), + "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + ); +} diff --git a/axiom-eth/src/batch_query/tests/mod.rs b/axiom-eth/src/batch_query/tests/mod.rs new file mode 100644 index 000000000..1c0b8e26e --- /dev/null +++ b/axiom-eth/src/batch_query/tests/mod.rs @@ -0,0 +1,31 @@ +use crate::providers::{GOERLI_PROVIDER_URL, MAINNET_PROVIDER_URL}; +use ethers_providers::{Http, Middleware, Provider, RetryClient}; +use std::env::var; +use tokio::runtime::Runtime; + +mod account; +#[cfg(feature = "aggregation")] +mod aggregation; +mod block_header; +mod row_consistency; +#[cfg(feature = "aggregation")] +mod scheduler; +mod storage; + +fn setup_provider() -> Provider> { + let infura_id = var("INFURA_ID").expect("INFURA_ID environmental variable not set"); + let provider_url = format!("{MAINNET_PROVIDER_URL}{infura_id}"); + Provider::new_client(&provider_url, 10, 500).expect("could not instantiate HTTP Provider") +} + +fn setup_provider_goerli() -> Provider> { + let infura_id = var("INFURA_ID").expect("INFURA_ID environmental variable not set"); + let provider_url = format!("{GOERLI_PROVIDER_URL}{infura_id}"); + Provider::new_client(&provider_url, 10, 500).expect("could not instantiate HTTP Provider") +} + +fn get_latest_block_number() -> u64 { + let provider = setup_provider(); + let rt = Runtime::new().unwrap(); + rt.block_on(provider.get_block_number()).unwrap().as_u64() +} diff --git a/axiom-eth/src/batch_query/tests/row_consistency.rs b/axiom-eth/src/batch_query/tests/row_consistency.rs new file mode 100644 index 000000000..56bf67da1 --- /dev/null +++ b/axiom-eth/src/batch_query/tests/row_consistency.rs @@ -0,0 +1,70 @@ +use crate::{ + batch_query::{ + hash::{poseidon_tree_root, PoseidonWords}, + response::{ + native::{ + get_account_response, get_block_response, get_full_account_response, + get_full_storage_response, get_storage_response, + }, + row_consistency::{ + RowConsistencyCircuit, ROW_ACCT_POSEIDON_INDEX, ROW_BLOCK_POSEIDON_INDEX, + ROW_STORAGE_POSEIDON_INDEX, + }, + }, + tests::storage::get_full_storage_inputs_nouns, + }, + Network, +}; +use halo2_base::{gates::builder::CircuitBuilderStage, halo2_proofs::dev::MockProver}; +use snark_verifier::util::hash::Poseidon; +use snark_verifier_sdk::{halo2::POSEIDON_SPEC, NativeLoader}; +use test_log::test; + +fn test_mock_row_consistency_nouns_gen(k: u32, num_rows: usize) { + let network = Network::Mainnet; + assert!(num_rows.is_power_of_two()); + let responses = get_full_storage_inputs_nouns(num_rows); + + // compute expected roots + let mut poseidon = Poseidon::from_spec(&NativeLoader, POSEIDON_SPEC.clone()); + let mut block_responses: Vec> = vec![]; + let mut full_acct_responses: Vec> = vec![]; + let mut full_st_responses: Vec> = vec![]; + for responses in &responses { + let (block_res, _) = get_block_response(&mut poseidon, responses.block.clone(), network); + let (acct_res, _) = get_account_response(&mut poseidon, &responses.storage); + let block_res = (block_res.0, responses.block.number.unwrap().as_u32()); + let full_acct_res = get_full_account_response(&mut poseidon, block_res, acct_res.clone()); + let acct_res = (acct_res.0, responses.storage.addr); + let (storage_res, _) = get_storage_response(&mut poseidon, &responses.storage); + let full_st_res = + get_full_storage_response(&mut poseidon, block_res, acct_res, storage_res); + block_responses.push(block_res.0.into()); + full_acct_responses.push(full_acct_res.0.into()); + full_st_responses.push(full_st_res.0.into()); + } + let block_root = poseidon_tree_root(&mut poseidon, block_responses, &[]); + let full_acct_root = poseidon_tree_root(&mut poseidon, full_acct_responses, &[]); + let full_st_root = poseidon_tree_root(&mut poseidon, full_st_responses, &[]); + // + + let input = RowConsistencyCircuit::new( + responses, + vec![true; num_rows], + vec![true; num_rows], + vec![true; num_rows], + network, + ); + let circuit = input.create_circuit(CircuitBuilderStage::Mock, None, k); + + let instance = circuit.instance(); + assert_eq!(instance[ROW_BLOCK_POSEIDON_INDEX], block_root); + assert_eq!(instance[ROW_ACCT_POSEIDON_INDEX], full_acct_root); + assert_eq!(instance[ROW_STORAGE_POSEIDON_INDEX], full_st_root); + MockProver::run(k, &circuit, vec![instance]).unwrap().assert_satisfied(); +} + +#[test] +fn test_mock_row_consistency_nouns() { + test_mock_row_consistency_nouns_gen(19, 128); +} diff --git a/axiom-eth/src/batch_query/tests/scheduler.rs b/axiom-eth/src/batch_query/tests/scheduler.rs new file mode 100644 index 000000000..c8a6931ba --- /dev/null +++ b/axiom-eth/src/batch_query/tests/scheduler.rs @@ -0,0 +1,551 @@ +use super::setup_provider; +use crate::{ + batch_query::{ + response::{ + account::MultiAccountCircuit, + block_header::{MultiBlockCircuit, BLOCK_BATCH_DEPTH}, + native::FullStorageQuery, + row_consistency::RowConsistencyCircuit, + storage::MultiStorageCircuit, + }, + scheduler::{ + circuit_types::{ + BlockVerifyVsMmrCircuitType, ExponentialSchema, FinalAssemblyCircuitType, + }, + router::BatchQueryScheduler, + tasks::{BlockVerifyVsMmrTask, FinalAssemblyTask, ResponseInput, ResponseTask, Task}, + }, + tests::storage::get_full_storage_queries_nouns_single_block, + }, + providers::{get_blocks, get_full_storage_queries}, + storage::{ + EthBlockStorageInput, {ACCOUNT_PROOF_MAX_DEPTH, STORAGE_PROOF_MAX_DEPTH}, + }, + util::{ + get_merkle_mountain_range, + scheduler::{Scheduler, Task as _}, + }, + Network, +}; +use ethers_core::{ + types::{Address, H256}, + utils::keccak256, +}; +use halo2_base::{halo2_proofs::halo2curves::bn256::Fr, utils::bit_length}; +use itertools::Itertools; +use std::{fs::File, path::PathBuf, str::FromStr}; +use test_log::test; + +fn test_scheduler(network: Network) -> BatchQueryScheduler { + BatchQueryScheduler::new( + network, + false, + false, + PathBuf::from("configs/tests/batch_query"), + PathBuf::from("data/tests/batch_query"), + ) +} + +#[test] +fn test_scheduler_account() { + let schema: ExponentialSchema = serde_json::from_reader( + File::open("configs/tests/batch_query/schema.account.json").unwrap(), + ) + .unwrap(); + let len = 1 << schema.total_arity; + let queries = [ + (17143006, "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B"), + (17143000, "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"), + (15000000, "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"), + (15411056, "0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6"), + ] + .map(|(num, address)| (num, address.parse().unwrap())) + .to_vec(); + let block_responses = queries.iter().map(|_| (Fr::zero(), 0)).collect(); + let not_empty = vec![true; queries.len()]; + + let scheduler = test_scheduler(Network::Mainnet); + let input = + MultiAccountCircuit::from_provider(&setup_provider(), block_responses, queries, not_empty); + let input = MultiAccountCircuit::resize_from( + input.block_responses, + input.queries, + input.not_empty, + len, + ); + let task = ResponseTask { + aggregate: schema.total_arity != schema.start_arity, + schema, + input: ResponseInput::Account(input), + }; + + scheduler.get_snark(Task::Response(task)); +} + +#[test] +fn test_scheduler_storage() { + let schema: ExponentialSchema = serde_json::from_reader( + File::open("configs/tests/batch_query/schema.storage.json").unwrap(), + ) + .unwrap(); + let len = 1 << schema.total_arity; + let queries = get_full_storage_queries_nouns_single_block(len, 14985438); + let responses: Vec<_> = get_full_storage_queries( + &setup_provider(), + queries, + ACCOUNT_PROOF_MAX_DEPTH, + STORAGE_PROOF_MAX_DEPTH, + ) + .unwrap() + .into_iter() + .map(|response| EthBlockStorageInput::from(response).storage) + .collect(); + let not_empty = vec![true; len]; + + let scheduler = test_scheduler(Network::Mainnet); + let input = MultiStorageCircuit::new( + vec![(Fr::zero(), 0); len], + vec![(Fr::zero(), Address::zero()); len], + responses, + not_empty, + ); + let task = ResponseTask { + aggregate: schema.total_arity != schema.start_arity, + schema, + input: ResponseInput::Storage(input), + }; + + scheduler.get_snark(Task::Response(task)); +} + +#[test] +fn test_scheduler_row_consistency() { + let schema: ExponentialSchema = + serde_json::from_reader(File::open("configs/tests/batch_query/schema.row.json").unwrap()) + .unwrap(); + let len = 1 << schema.total_arity; + let queries = get_full_storage_queries_nouns_single_block(len, 12985438); + let responses: Vec<_> = get_full_storage_queries( + &setup_provider(), + queries, + ACCOUNT_PROOF_MAX_DEPTH, + STORAGE_PROOF_MAX_DEPTH, + ) + .unwrap() + .into_iter() + .map(EthBlockStorageInput::from) + .collect(); + + let scheduler = test_scheduler(Network::Mainnet); + let input = RowConsistencyCircuit::new( + responses, + vec![true; len], + vec![true; len], + vec![true; len], + Network::Mainnet, + ); + let task = ResponseTask { + aggregate: schema.total_arity != schema.start_arity, + schema, + input: ResponseInput::Row(input), + }; + + scheduler.get_snark(Task::Response(task)); +} + +#[test] +fn test_scheduler_verify_vs_mmr() { + let block_number = 12_985_438; + let merkle_proof = [ + "0xebd5fc0be32c2298e2ee18dac2db5b1508a64741ba7186dd59229ec0620d9d64", + "0x9139f12e0f47241172143fc5d0b0245b5ffbcdf9130da9efb14c155f6036697e", + "0x97f5d40bc9a10e06b5eef7609a3e364f30ab10675d22fbc3304179a381b39b18", + "0xc8c07e6c877f0cd60903d376c1aa911f47c96d3967d989101ed8b873cf6e38de", + "0x96cf53edbe3be783378433c518939a7e0b4e657edb6558b1f6e14edc0a125a18", + "0xfa3448a664e9406ffdc3b53e24f06fcf6b576621f854e421385bd1603ea257ee", + "0x9dffc8cb737d72da73df5e333bb7716cfec51e4b761281c6c7ff4db55689911c", + "0xef3fb7b7274810ec5bc63e7c248ea7dfe26d95abcd8bcb8d97b1f5fb617b8dc8", + "0x6a4d92e38592f280afc95efe5dd178a37c155bfad49759db7a066d597bc804d3", + "0x7db79de6d79e2ff264f4d171243f5038b575b380d31b052dda979e28fae7fc08", + "0x3106ece6d5a3c317f17c9313e7d0a3cd73649662301f50fdcedc67254b3fe153", + "0x902c8cf11e8d5cf14137e632061a52574515a2254fbd3b70cfc85a45f9dbcb4a", + "0xc48c7fe69133ac6f0c2200e600a3c15fe1832577156bc8851a7538403eafadfa", + "0x4434e3730dbe222cb8b98703748da1f07f05564c64ea66fe4765484ea982f5d6", + "0x69d2bc461de5dba21f741bf757d60ec8a92c3f29e417cb99fa76459bc3e86278", + "0xe18396e487f6c0bcd73a2d4c4c8c3583be7edefe59f20b2ce67c7f363b8a856a", + "0xa10b0dd9e041c793d0dbdf615bee9e18c3f6e3b191469bbb8cc9912d5d228050", + "0xa51d50eb9feaaf85b7ddacb99f71886135f1c4f59de3e788a5e29a485d5fdce5", + "0xa46b70512bfe0b85498e28ae8187cfadff9e58680b84ddcde450cd880ea489b1", + "0x33552dfc75e340bca3c698e4fb486ae540d07cf2a845465575cff24d866a161a", + "0x0fec590ac8394abe8477b828bf31b470d95772b3f331ff5be34ba0a899975a17", + ] + .into_iter() + .map(|s| H256::from_str(s).unwrap()) + .collect_vec(); + + let schema: BlockVerifyVsMmrCircuitType = + serde_json::from_reader(File::open("configs/tests/batch_query/schema.block.json").unwrap()) + .unwrap(); + let arity: usize = schema.arities.iter().sum(); + let len = 1 << arity; + let mmr_list_len = 16_525_312; + let mmr = vec![H256::zero(); BLOCK_BATCH_DEPTH] + .into_iter() + .chain(MMR_16525312.iter().map(|s| H256::from_str(s).unwrap())) + .collect(); + let block_numbers = vec![block_number; len]; + + let scheduler = test_scheduler(Network::Mainnet); + let input = MultiBlockCircuit::from_provider( + &setup_provider(), + block_numbers, + vec![true; len], + Network::Mainnet, + mmr, + mmr_list_len, + vec![merkle_proof; len], + ); + let task = BlockVerifyVsMmrTask { input, arities: schema.arities }; + println!("{}", task.name()); + + scheduler.get_snark(Task::BlockVerifyVsMmr(task)); +} + +const MMR_16525312: &[&str] = &[ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xe81cc62bb288e100856ea7d40af72b844e9dcb9ff8ebed659a475e2635cd4e18", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xb169c87af2d231bc71f910481d6d8315a6fc4edfab212ee003d206b9643339c0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x43062e083f5f40510f30723d9cebb51c4ae67c35d86c5b791a043bae317350e3", + "0x6cddc980f4c3b403d99080c32b3f0a6205e39560b9021d5f233c04d96c23381e", + "0x6a42052cabd8d66a584b8823c6aadf64dd2755321210c66a2b7acd1da5bdeacf", + "0xebf08ca711cbab09109bb51277c545ee43073d8fa8b46c0cbbedd46ce85e73be", + "0x477c055e69de14e3bbfe2af0389e6c3ac28ffb5b0cc8fa26b543ac47857fd646", + "0xf47e6584af17667881494720b50dd063d0900b288438df7a37e2e91440aedd23", +]; + +#[test] +fn test_scheduler_final_assembly_old() { + let block_number = 14_985_438; + let merkle_proof = [ + "0x4ee507551ceeb4e7cd160e1d6a546f78d7dc5ea29be99e79476a52781e5422a2", + "0xdfba2ede28a828481f99a0277f6062b3f409370c75dbbdb1f7513fdf43e114c5", + "0xe315409409b8ad6c0502debcf846d8f4f4d648c7c7598e12306caebb6879cf4d", + "0x319f29f57fd20fcbd67cf16d1547f2c7206fc402d11640759834574413c7c073", + "0xd7283aa12b799f869ddb6805d6ee0a6ac70bae8af5eda1fa83d910605902d31a", + "0xb6ababeeff584afc2ffd2a6239a8421cd36617a07fdfbaaf78e0d98ee5cdd2b2", + "0x7738be39d3d440a968245f93f2659da6c3955306b70de635caf704a2cd248012", + "0x9c5f767b3e6bf3e6e3716642d4beaeeb0705e4bce45411b4b130739050b85e3b", + "0xde3605c75c7c1e971b9615d608112661d407af3ef24945e226b7f0b3694ba102", + "0x24b2de47bd4094e61e07ee06d40f5a4f4d66999eea97037866eb06c535c70c5d", + "0x7c8e45373e748b8ec69371841bccbc438e4308d345729e5684c1264ac243dd9d", + "0x82779b69d134f2dac8998450a341591d53eb19d7b51d57645d6a67fafa0e4cfd", + "0x6b47cb78db4428a19df5344391d54e1f695576595eadedc82aef81156e3f85a6", + "0xd9e8dbccb0368b6a0b9d2886a0b1c30776684018474174d7666c08068fba49a9", + "0xda256132c245db47f729f5d9b8742a5074ec38dd376a5b01496e179863b8e6ef", + "0xc2cea502d15df8518ddb1834aa172cd4460c5b52d37ac0f38c3a232c1d8d19fb", + "0x282d4ca5df280766756c3f34dbd986145b41b3ebb1bca021c6cceb9ce7214aba", + "0xee9f461f804095981853ed2af093769936c30b686f242f67cb8b65d6e59746dd", + "0x03bb87fd41000fc97ba8639b2439aac2a80389bcb973086d8334930e100f5765", + "0x3267b39f880cdc657d6639cc25b454e4bf72099572682e3e95dc080c4bd1aa59", + ] + .into_iter() + .map(|s| H256::from_str(s).unwrap()) + .collect_vec(); + let mmr_num_blocks = 16_525_312; + let mmr = vec![H256::zero(); BLOCK_BATCH_DEPTH] + .into_iter() + .chain(MMR_16525312.iter().map(|s| H256::from_str(s).unwrap())) + .collect(); + + let circuit_type: FinalAssemblyCircuitType = + serde_json::from_reader(File::open("configs/tests/batch_query/schema.final.json").unwrap()) + .unwrap(); + assert_eq!(circuit_type.network, Network::Mainnet); + let total_arity = circuit_type.account_schema.total_arity; + let len = 1 << total_arity; + + let queries = get_full_storage_queries_nouns_single_block(len, block_number); + let input = get_full_storage_queries( + &setup_provider(), + queries, + ACCOUNT_PROOF_MAX_DEPTH, + STORAGE_PROOF_MAX_DEPTH, + ) + .unwrap(); + let task = Task::Final(FinalAssemblyTask { + circuit_type, + input, + mmr, + mmr_num_blocks, + mmr_proofs: vec![merkle_proof; len], + }); + println!("{}", task.name()); + + let scheduler = test_scheduler(Network::Mainnet); + scheduler.get_calldata(task, true); +} + +#[test] +fn test_scheduler_final_assembly_recent() { + let provider = setup_provider(); + + let mmr_num_blocks = 16_525_312; + let mut side = 777usize; + let list_len = 888usize; + let block_number = mmr_num_blocks + side; + let leaves = get_blocks(&provider, (0..list_len).map(|i| (mmr_num_blocks + i) as u64)) + .unwrap() + .into_iter() + .map(|block| block.unwrap().hash.unwrap()) + .collect_vec(); + let mut mmr = get_merkle_mountain_range(&leaves, BLOCK_BATCH_DEPTH - 1); + mmr.reverse(); + assert_eq!(mmr.len(), BLOCK_BATCH_DEPTH); + mmr.extend(MMR_16525312.iter().map(|s| H256::from_str(s).unwrap())); + + let mut peak_id = bit_length(list_len as u64) - 1; + let mut start = 0; + while (list_len >> peak_id) & 1 == (side >> peak_id) & 1 { + if (list_len >> peak_id) & 1 == 1 { + start += 1 << peak_id; + side -= 1 << peak_id; + } + peak_id -= 1; + } + let mut current_hashes = leaves[start..start + (1 << peak_id)].to_vec(); + let mut merkle_proof = vec![]; + for i in (1..=peak_id).rev() { + merkle_proof.push(current_hashes[side ^ 1]); + for i in 0..(1 << (i - 1)) { + current_hashes[i] = H256(keccak256( + [current_hashes[i * 2].as_bytes(), current_hashes[i * 2 + 1].as_bytes()].concat(), + )); + } + side >>= 1; + } + + let circuit_type: FinalAssemblyCircuitType = + serde_json::from_reader(File::open("configs/tests/batch_query/schema.final.json").unwrap()) + .unwrap(); + assert_eq!(circuit_type.network, Network::Mainnet); + let total_arity = circuit_type.account_schema.total_arity; + let len = 1 << total_arity; + + let queries = get_full_storage_queries_nouns_single_block(len, block_number as u64); + let input = get_full_storage_queries( + &setup_provider(), + queries, + ACCOUNT_PROOF_MAX_DEPTH, + STORAGE_PROOF_MAX_DEPTH, + ) + .unwrap(); + let task = Task::Final(FinalAssemblyTask { + circuit_type, + input, + mmr, + mmr_num_blocks: mmr_num_blocks + list_len, + mmr_proofs: vec![merkle_proof; len], + }); + println!("{}", task.name()); + + let scheduler = test_scheduler(Network::Mainnet); + let str = scheduler.get_calldata(task, true); + println!("{str:?}"); +} + +#[test] +fn test_scheduler_final_assembly_empty_slots() { + let block_number = 14_985_438; + let merkle_proof = [ + "0x4ee507551ceeb4e7cd160e1d6a546f78d7dc5ea29be99e79476a52781e5422a2", + "0xdfba2ede28a828481f99a0277f6062b3f409370c75dbbdb1f7513fdf43e114c5", + "0xe315409409b8ad6c0502debcf846d8f4f4d648c7c7598e12306caebb6879cf4d", + "0x319f29f57fd20fcbd67cf16d1547f2c7206fc402d11640759834574413c7c073", + "0xd7283aa12b799f869ddb6805d6ee0a6ac70bae8af5eda1fa83d910605902d31a", + "0xb6ababeeff584afc2ffd2a6239a8421cd36617a07fdfbaaf78e0d98ee5cdd2b2", + "0x7738be39d3d440a968245f93f2659da6c3955306b70de635caf704a2cd248012", + "0x9c5f767b3e6bf3e6e3716642d4beaeeb0705e4bce45411b4b130739050b85e3b", + "0xde3605c75c7c1e971b9615d608112661d407af3ef24945e226b7f0b3694ba102", + "0x24b2de47bd4094e61e07ee06d40f5a4f4d66999eea97037866eb06c535c70c5d", + "0x7c8e45373e748b8ec69371841bccbc438e4308d345729e5684c1264ac243dd9d", + "0x82779b69d134f2dac8998450a341591d53eb19d7b51d57645d6a67fafa0e4cfd", + "0x6b47cb78db4428a19df5344391d54e1f695576595eadedc82aef81156e3f85a6", + "0xd9e8dbccb0368b6a0b9d2886a0b1c30776684018474174d7666c08068fba49a9", + "0xda256132c245db47f729f5d9b8742a5074ec38dd376a5b01496e179863b8e6ef", + "0xc2cea502d15df8518ddb1834aa172cd4460c5b52d37ac0f38c3a232c1d8d19fb", + "0x282d4ca5df280766756c3f34dbd986145b41b3ebb1bca021c6cceb9ce7214aba", + "0xee9f461f804095981853ed2af093769936c30b686f242f67cb8b65d6e59746dd", + "0x03bb87fd41000fc97ba8639b2439aac2a80389bcb973086d8334930e100f5765", + "0x3267b39f880cdc657d6639cc25b454e4bf72099572682e3e95dc080c4bd1aa59", + ] + .into_iter() + .map(|s| H256::from_str(s).unwrap()) + .collect_vec(); + let mmr_num_blocks = 16_525_312; + let mmr = vec![H256::zero(); BLOCK_BATCH_DEPTH] + .into_iter() + .chain(MMR_16525312.iter().map(|s| H256::from_str(s).unwrap())) + .collect(); + + let circuit_type: FinalAssemblyCircuitType = + serde_json::from_reader(File::open("configs/tests/batch_query/schema.final.json").unwrap()) + .unwrap(); + assert_eq!(circuit_type.network, Network::Mainnet); + let total_arity = circuit_type.account_schema.total_arity; + let len = 1 << total_arity; + + let address = Address::from_str("0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03").unwrap(); // NounsToken + let queries = (0..len) + .map(|_| FullStorageQuery { block_number, addr_slots: Some((address, vec![])) }) + .collect_vec(); + let input = get_full_storage_queries( + &setup_provider(), + queries, + ACCOUNT_PROOF_MAX_DEPTH, + STORAGE_PROOF_MAX_DEPTH, + ) + .unwrap(); + let task = Task::Final(FinalAssemblyTask { + circuit_type, + input, + mmr, + mmr_num_blocks, + mmr_proofs: vec![merkle_proof; len], + }); + println!("{}", task.name()); + + let scheduler = test_scheduler(Network::Mainnet); + scheduler.get_calldata(task, true); +} + +#[test] +fn test_scheduler_final_assembly_empty_accounts() { + let block_number = 14_985_438; + let merkle_proof = [ + "0x4ee507551ceeb4e7cd160e1d6a546f78d7dc5ea29be99e79476a52781e5422a2", + "0xdfba2ede28a828481f99a0277f6062b3f409370c75dbbdb1f7513fdf43e114c5", + "0xe315409409b8ad6c0502debcf846d8f4f4d648c7c7598e12306caebb6879cf4d", + "0x319f29f57fd20fcbd67cf16d1547f2c7206fc402d11640759834574413c7c073", + "0xd7283aa12b799f869ddb6805d6ee0a6ac70bae8af5eda1fa83d910605902d31a", + "0xb6ababeeff584afc2ffd2a6239a8421cd36617a07fdfbaaf78e0d98ee5cdd2b2", + "0x7738be39d3d440a968245f93f2659da6c3955306b70de635caf704a2cd248012", + "0x9c5f767b3e6bf3e6e3716642d4beaeeb0705e4bce45411b4b130739050b85e3b", + "0xde3605c75c7c1e971b9615d608112661d407af3ef24945e226b7f0b3694ba102", + "0x24b2de47bd4094e61e07ee06d40f5a4f4d66999eea97037866eb06c535c70c5d", + "0x7c8e45373e748b8ec69371841bccbc438e4308d345729e5684c1264ac243dd9d", + "0x82779b69d134f2dac8998450a341591d53eb19d7b51d57645d6a67fafa0e4cfd", + "0x6b47cb78db4428a19df5344391d54e1f695576595eadedc82aef81156e3f85a6", + "0xd9e8dbccb0368b6a0b9d2886a0b1c30776684018474174d7666c08068fba49a9", + "0xda256132c245db47f729f5d9b8742a5074ec38dd376a5b01496e179863b8e6ef", + "0xc2cea502d15df8518ddb1834aa172cd4460c5b52d37ac0f38c3a232c1d8d19fb", + "0x282d4ca5df280766756c3f34dbd986145b41b3ebb1bca021c6cceb9ce7214aba", + "0xee9f461f804095981853ed2af093769936c30b686f242f67cb8b65d6e59746dd", + "0x03bb87fd41000fc97ba8639b2439aac2a80389bcb973086d8334930e100f5765", + "0x3267b39f880cdc657d6639cc25b454e4bf72099572682e3e95dc080c4bd1aa59", + ] + .into_iter() + .map(|s| H256::from_str(s).unwrap()) + .collect_vec(); + let mmr_num_blocks = 16_525_312; + let mmr = vec![H256::zero(); BLOCK_BATCH_DEPTH] + .into_iter() + .chain(MMR_16525312.iter().map(|s| H256::from_str(s).unwrap())) + .collect(); + + let circuit_type: FinalAssemblyCircuitType = + serde_json::from_reader(File::open("configs/tests/batch_query/schema.final.json").unwrap()) + .unwrap(); + assert_eq!(circuit_type.network, Network::Mainnet); + let total_arity = circuit_type.account_schema.total_arity; + let len = 1 << total_arity; + + let queries = + (0..len).map(|_| FullStorageQuery { block_number, addr_slots: None }).collect_vec(); + let input = get_full_storage_queries( + &setup_provider(), + queries, + ACCOUNT_PROOF_MAX_DEPTH, + STORAGE_PROOF_MAX_DEPTH, + ) + .unwrap(); + let task = Task::Final(FinalAssemblyTask { + circuit_type, + input, + mmr, + mmr_num_blocks, + mmr_proofs: vec![merkle_proof; len], + }); + println!("{}", task.name()); + + let scheduler = test_scheduler(Network::Mainnet); + scheduler.get_calldata(task, true); +} + +#[test] +fn test_scheduler_final_assembly_resize() { + let block_number = 14_985_438; + let merkle_proof = [ + "0x4ee507551ceeb4e7cd160e1d6a546f78d7dc5ea29be99e79476a52781e5422a2", + "0xdfba2ede28a828481f99a0277f6062b3f409370c75dbbdb1f7513fdf43e114c5", + "0xe315409409b8ad6c0502debcf846d8f4f4d648c7c7598e12306caebb6879cf4d", + "0x319f29f57fd20fcbd67cf16d1547f2c7206fc402d11640759834574413c7c073", + "0xd7283aa12b799f869ddb6805d6ee0a6ac70bae8af5eda1fa83d910605902d31a", + "0xb6ababeeff584afc2ffd2a6239a8421cd36617a07fdfbaaf78e0d98ee5cdd2b2", + "0x7738be39d3d440a968245f93f2659da6c3955306b70de635caf704a2cd248012", + "0x9c5f767b3e6bf3e6e3716642d4beaeeb0705e4bce45411b4b130739050b85e3b", + "0xde3605c75c7c1e971b9615d608112661d407af3ef24945e226b7f0b3694ba102", + "0x24b2de47bd4094e61e07ee06d40f5a4f4d66999eea97037866eb06c535c70c5d", + "0x7c8e45373e748b8ec69371841bccbc438e4308d345729e5684c1264ac243dd9d", + "0x82779b69d134f2dac8998450a341591d53eb19d7b51d57645d6a67fafa0e4cfd", + "0x6b47cb78db4428a19df5344391d54e1f695576595eadedc82aef81156e3f85a6", + "0xd9e8dbccb0368b6a0b9d2886a0b1c30776684018474174d7666c08068fba49a9", + "0xda256132c245db47f729f5d9b8742a5074ec38dd376a5b01496e179863b8e6ef", + "0xc2cea502d15df8518ddb1834aa172cd4460c5b52d37ac0f38c3a232c1d8d19fb", + "0x282d4ca5df280766756c3f34dbd986145b41b3ebb1bca021c6cceb9ce7214aba", + "0xee9f461f804095981853ed2af093769936c30b686f242f67cb8b65d6e59746dd", + "0x03bb87fd41000fc97ba8639b2439aac2a80389bcb973086d8334930e100f5765", + "0x3267b39f880cdc657d6639cc25b454e4bf72099572682e3e95dc080c4bd1aa59", + ] + .into_iter() + .map(|s| H256::from_str(s).unwrap()) + .collect_vec(); + let mmr_num_blocks = 16_525_312; + let mmr = vec![H256::zero(); BLOCK_BATCH_DEPTH] + .into_iter() + .chain(MMR_16525312.iter().map(|s| H256::from_str(s).unwrap())) + .collect(); + + let circuit_type: FinalAssemblyCircuitType = + serde_json::from_reader(File::open("configs/tests/batch_query/schema.final.json").unwrap()) + .unwrap(); + assert_eq!(circuit_type.network, Network::Mainnet); + let len = 5; + + let address = Address::from_str("0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03").unwrap(); // NounsToken + let queries = (0..len) + .map(|_| FullStorageQuery { block_number, addr_slots: Some((address, vec![])) }) + .collect_vec(); + let input = get_full_storage_queries( + &setup_provider(), + queries, + ACCOUNT_PROOF_MAX_DEPTH, + STORAGE_PROOF_MAX_DEPTH, + ) + .unwrap(); + let task = Task::Final(FinalAssemblyTask { + circuit_type, + input, + mmr, + mmr_num_blocks, + mmr_proofs: vec![merkle_proof; len], + }); + println!("{}", task.name()); + + let scheduler = test_scheduler(Network::Mainnet); + scheduler.get_calldata(task, true); +} diff --git a/axiom-eth/src/batch_query/tests/storage.rs b/axiom-eth/src/batch_query/tests/storage.rs new file mode 100644 index 000000000..bc78de66d --- /dev/null +++ b/axiom-eth/src/batch_query/tests/storage.rs @@ -0,0 +1,270 @@ +use crate::{ + batch_query::{ + hash::{poseidon_tree_root, PoseidonWords}, + response::{ + native::{get_full_storage_response, get_storage_response, FullStorageQuery}, + storage::{MultiStorageCircuit, DEFAULT_STORAGE_QUERY}, + }, + tests::get_latest_block_number, + }, + providers::get_full_storage_queries, + rlp::builder::RlcThreadBuilder, + storage::{ + EthBlockStorageInput, {ACCOUNT_PROOF_MAX_DEPTH, STORAGE_PROOF_MAX_DEPTH}, + }, + util::{encode_addr_to_field, encode_h256_to_field, h256_tree_root, EthConfigParams}, + EthPreCircuit, +}; +use ethers_core::{ + types::{Address, H256}, + utils::keccak256, +}; +use ff::Field; +use halo2_base::halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; +use itertools::Itertools; +use rand::{thread_rng, Rng}; +use rand_chacha::ChaChaRng; +use rand_core::{OsRng, SeedableRng}; +use snark_verifier::util::hash::Poseidon; +use snark_verifier_sdk::{halo2::POSEIDON_SPEC, NativeLoader}; +use std::{env::set_var, str::FromStr}; +use test_log::test; + +use super::setup_provider; + +fn test_mock_storage_queries( + block_responses: Vec<(Fr, u32)>, + acct_responses: Vec<(Fr, Address)>, + queries: Vec<(u64, &str, H256)>, + not_empty: Vec, +) { + let params = EthConfigParams::from_path("configs/tests/storage_query.json"); + set_var("ETH_CONFIG_PARAMS", serde_json::to_string(¶ms).unwrap()); + let k = params.degree; + + let queries = queries + .into_iter() + .map(|(block_number, address, slot)| { + let address = address.parse().unwrap(); + (block_number, address, slot) + }) + .collect(); + + let input = MultiStorageCircuit::from_provider( + &setup_provider(), + block_responses, + acct_responses, + queries, + not_empty.clone(), + ); + // instance calculation natively for test validation + let mut poseidon = Poseidon::from_spec(&NativeLoader, POSEIDON_SPEC.clone()); + let block_num_root = poseidon_tree_root( + &mut poseidon, + input + .block_responses + .iter() + .map(|(_, num)| PoseidonWords(vec![Fr::from(*num as u64)])) + .collect(), + &[], + ); + let addr_root = poseidon_tree_root( + &mut poseidon, + input + .account_responses + .iter() + .map(|(_, addr)| PoseidonWords(vec![encode_addr_to_field::(addr)])) + .collect_vec(), + &[], + ); + let (res_p, res_k): (Vec<_>, Vec<_>) = input + .block_responses + .iter() + .zip_eq(input.account_responses.iter()) + .zip_eq(input.queries.iter()) + .zip_eq(not_empty.iter()) + .map(|(((block_response, acct_response), query), not_empty)| { + let (storage_response, _) = get_storage_response(&mut poseidon, query); + let (mut pos, mut kec) = get_full_storage_response( + &mut poseidon, + *block_response, + *acct_response, + storage_response, + ); + if !not_empty { + pos = Fr::zero(); + kec = H256([0u8; 32]); + } + (pos, kec) + }) + .unzip(); + let root_p = poseidon_tree_root(&mut poseidon, res_p, &[]); + let root_k = encode_h256_to_field(&h256_tree_root(&res_k)); + let instance = vec![root_k[0], root_k[1], root_p, block_num_root, addr_root]; + + let circuit = input.create_circuit(RlcThreadBuilder::mock(), None); + + MockProver::run(k, &circuit, vec![instance]).unwrap().assert_satisfied(); +} + +#[test] +fn test_mock_storage_queries_slot0() { + let queries = vec![ + (17143006, "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B", H256::zero()), + (17143000, "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", H256::zero()), + (16356350, "0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB", H256::zero()), + (15411056, "0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6", H256::zero()), + ]; + let mut rng = thread_rng(); + // note that block response is not checked in any way in the circuit, in particular the poseidon and keccak parts don't even need to be consistent! + let block_responses = queries.iter().map(|_| (Fr::random(OsRng), rng.gen())).collect(); + let acct_responses = queries.iter().map(|_| (Fr::random(OsRng), Address::random())).collect(); + let not_empty = vec![true; queries.len()]; + test_mock_storage_queries(block_responses, acct_responses, queries, not_empty); +} + +#[test] +fn test_mock_storage_queries_uni_v3() { + let address = "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"; // uniswap v3 eth-usdc 5bps pool + let mut rng = thread_rng(); + let latest = get_latest_block_number(); + let queries = [0, 1, 2, 8] + .map(|x| (rng.gen_range(12376729..latest), address, H256::from_low_u64_be(x))) + .to_vec(); + let block_responses = queries.iter().map(|_| (Fr::random(OsRng), rng.gen())).collect(); + let acct_responses = queries.iter().map(|_| (Fr::random(OsRng), Address::random())).collect(); + let not_empty = vec![true; queries.len()]; + test_mock_storage_queries(block_responses, acct_responses, queries, not_empty); +} + +#[test] +fn test_mock_storage_queries_mapping() { + let address = "0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB"; // cryptopunks + let mut rng = thread_rng(); + let slots = (0..4).map(|x| { + let mut bytes = [0u8; 64]; + bytes[31] = x; + bytes[63] = 10; + H256::from_slice(&keccak256(bytes)) + }); + let latest = get_latest_block_number(); + let queries: Vec<_> = + slots.map(|slot| (rng.gen_range(3914495..latest), address, slot)).collect(); + let block_responses = queries.iter().map(|_| (Fr::random(OsRng), rng.gen())).collect(); + let acct_responses = queries.iter().map(|_| (Fr::random(OsRng), Address::random())).collect(); + let not_empty = vec![true; queries.len()]; + test_mock_storage_queries(block_responses, acct_responses, queries, not_empty); +} + +#[test] +fn test_mock_storage_queries_empty() { + let address = "0xbb9bc244d798123fde783fcc1c72d3bb8c189413"; // TheDAO Token + let mut rng = thread_rng(); + let latest = get_latest_block_number(); + let mut queries: Vec<_> = (0..8) + .map(|_| (rng.gen_range(1428757..latest), address, H256::from_low_u64_be(3))) + .collect(); + let block_responses = queries.iter().map(|_| (Fr::random(OsRng), rng.gen())).collect(); + let acct_responses = queries.iter().map(|_| (Fr::random(OsRng), Address::random())).collect(); + let mut not_empty = vec![true; queries.len()]; + for (ne, q) in not_empty.iter_mut().zip(queries.iter_mut()).take(4) { + *ne = false; + q.2 = H256::random(); + } + test_mock_storage_queries(block_responses, acct_responses, queries, not_empty); +} + +// some of the slots will be empty, we test that the value returned is 0 +#[test] +fn test_mock_storage_queries_random() { + let address = "0xbb9bc244d798123fde783fcc1c72d3bb8c189413"; // TheDAO Token + let mut rng = ChaChaRng::from_seed([0u8; 32]); + let latest = get_latest_block_number(); + let queries: Vec<_> = + (0..8).map(|_| (rng.gen_range(1428757..latest), address, H256::random())).collect(); + let block_responses = queries.iter().map(|_| (Fr::random(OsRng), rng.gen())).collect(); + let acct_responses = queries.iter().map(|_| (Fr::random(OsRng), Address::random())).collect(); + let not_empty = vec![true; queries.len()]; + test_mock_storage_queries(block_responses, acct_responses, queries, not_empty); +} + +pub fn get_full_storage_queries_nouns_single_block( + len: usize, + block_number: u64, +) -> Vec { + let address = "0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03"; // NounsToken + let address: Address = address.parse().unwrap(); + let mut queries = vec![]; + for i in 0..3 { + queries.push(FullStorageQuery { + block_number, + addr_slots: Some((address, vec![H256::from_low_u64_be(i)])), + }); + } + if len <= 3 { + queries.truncate(len); + } else { + for i in 0..len - 3 { + let mut bytes = [0u8; 64]; + bytes[31] = i as u8; + bytes[63] = 3; // slot 3 is _owners mapping(uint256 => address) + let slot = H256::from_slice(&keccak256(bytes)); + queries + .push(FullStorageQuery { block_number, addr_slots: Some((address, vec![slot])) }); + } + } + queries +} + +pub fn get_full_storage_queries_nouns(len: usize) -> Vec { + let creation_block = 12985438; + let latest = get_latest_block_number(); + let mut rng = rand::thread_rng(); + let mut queries = vec![]; + + let mut remaining_len = len; + + while remaining_len > 0 { + let block_number: u64 = rng.gen_range(creation_block..latest); + let current_len = rng.gen_range(1..=remaining_len); + let mut current_queries = + get_full_storage_queries_nouns_single_block(current_len, block_number); + queries.append(&mut current_queries); + remaining_len -= current_len; + } + queries +} + +pub fn get_full_storage_inputs_nouns(len: usize) -> Vec { + let queries = get_full_storage_queries_nouns(len); + let responses = get_full_storage_queries( + &setup_provider(), + queries, + ACCOUNT_PROOF_MAX_DEPTH, + STORAGE_PROOF_MAX_DEPTH, + ) + .unwrap(); + responses.into_iter().map(|response| response.try_into().unwrap()).collect() +} + +#[test] +fn test_default_storage_query() { + let address = Address::from_str("0x01d5b501C1fc0121e1411970fb79c322737025c2").unwrap(); // AxiomV0 + let provider = setup_provider(); + let query: EthBlockStorageInput = get_full_storage_queries( + &provider, + vec![FullStorageQuery { + block_number: 16504035, + addr_slots: Some((address, vec![H256::zero()])), + }], + ACCOUNT_PROOF_MAX_DEPTH, + STORAGE_PROOF_MAX_DEPTH, + ) + .unwrap() + .pop() + .unwrap() + .try_into() + .unwrap(); + + assert_eq!(format!("{:?}", query.storage), format!("{:?}", DEFAULT_STORAGE_QUERY.clone())) +} diff --git a/axiom-eth/src/bin/AxiomV1.md b/axiom-eth/src/bin/AxiomV1.md new file mode 100644 index 000000000..973b62653 --- /dev/null +++ b/axiom-eth/src/bin/AxiomV1.md @@ -0,0 +1,160 @@ +# AxiomV1 SNARK Verifier Circuits + +# Public Instance Formats + +Any `Snark` has an associated `Vec` of public instances. We describe the format for the ones relevant to the core AxiomV1 circuits below. + +## `EthBlockHeaderChainCircuit` + +```rust +pub struct EthBlockHeaderChainCircuit { + header_rlp_encodings: Vec>, + num_blocks: u32, // num_blocks in [0, 2 ** max_depth) + max_depth: usize, + network: Network, + _marker: PhantomData, +} +``` + +This depends on a `max_depth` parameter. The public instances are: + +- `prev_hash`: `H256` as two `Fr` elements in hi-lo format +- `end_hash`: `H256` as two `Fr` elements in hi-lo format +- `start_block_number . end_block_number`: we assume both numbers are `u32` and encode them to a single `Fr` element as `start_block_number * 2^32 + end_block_number` +- `merkle_mountain_range`: a sequence of `max_depth + 1` `H256` elements, each encoded as two `Fr` elements in hi-lo format + +Notes: + +- `prev_hash` is the parent hash of block number `start_block_number` +- `end_hash` is the block hash of block number `end_block_number` +- `end_block_number - start_block_number` is constrained to be `<= 2^max_depth` + - This was previously assumed in `axiom-eth` `v0.1.1` but not enforced because the block numbers are public instances, but we now enforce it for safety +- `merkle_mountain_range` is ordered from largest peak (depth `max_depth`) first to smallest peak (depth `0`) last + +## `EthBlockHeaderChainAggregationCircuit` + +```rust +pub struct EthBlockHeaderChainAggregationCircuit { + num_blocks: u32, + snarks: Vec, + pub max_depth: usize, + pub initial_depth: usize, +} +``` + +This circuit takes two [`EthBlockHeaderChainCircuit`s](#ethblockheaderchaincircuit) and aggregates them. The public instances are: + +- `4 * LIMBS = 12` `Fr` elements for the two BN254 `G1` points representing the _accumulator_, used by the verifier for a pairing check +- `prev_hash`: `H256` as two `Fr` elements in hi-lo format +- `end_hash`: `H256` as two `Fr` elements in hi-lo format +- `start_block_number . end_block_number`: we assume both numbers are `u32` and encode them to a single `Fr` element as `start_block_number * 2^32 + end_block_number` +- `merkle_mountain_range`: a sequence of `2^{max_depth - initial_depth} + initial_depth` `H256` elements, each encoded as two `Fr` elements in hi-lo format + +Notes: + +- Same notes as [`EthBlockHeaderChainCircuit`](#ethblockheaderchaincircuit) **except** that `merkle_mountain_range` is not actually a Merkle mountain range: we recover a Merkle mountain range of length `max_depth + 1` by forming a Merkle mountain range from leaves `merkle_mountain_range[..2^{max_depth - initial_depth}]` and then appending `merkle_mountain_range[2^{max_depth - initial_depth}..]` to the end of it. + - The reason is that we want to delay Keccaks + +## `EthBlockHeaderChainFinalAggregationCircuit` + +```rust +pub struct EthBlockHeaderChainFinalAggregationCircuit(pub EthBlockHeaderChainAggregationCircuit); +``` + +This circuit takes two [`EthBlockHeaderChainAggregationCircuit`s](#ethblockheaderchainaggregationcircuit) and aggregates them. The public instances are: + +- `4 * LIMBS = 12` `Fr` elements for the two BN254 `G1` points representing the _accumulator_, used by the verifier for a pairing check +- `prev_hash`: `H256` as two `Fr` elements in hi-lo format +- `end_hash`: `H256` as two `Fr` elements in hi-lo format +- `start_block_number . end_block_number`: we assume both numbers are `u32` and encode them to a single `Fr` element as `start_block_number * 2^32 + end_block_number` +- `merkle_mountain_range`: a sequence of `max_depth + 1` `H256` elements, each encoded as two `Fr` elements in hi-lo format + +Notes: + +- Same notes as [`EthBlockHeaderChainCircuit`](#ethblockheaderchaincircuit) +- This circuit is the same as [`EthBlockHeaderChainAggregationCircuit`](#ethblockheaderchainaggregationcircuit) except that it does do the final Keccaks to form the full Merkle mountain range + +## `PublicAggregationCircuit` + +```rust +pub struct PublicAggregationCircuit { + pub snarks: Vec, + pub has_prev_accumulators: bool, +} +``` + +This circuit aggregates snarks and re-exposes previous public inputs. The public instances are: + +- `4 * LIMBS = 12` `Fr` elements for the two BN254 `G1` points representing the _accumulator_, used by the verifier for a pairing check +- Sequentially appends the public instances from each `Snark` in `snarks` + - If `has_prev_accumulators` is true, then it assumes all previous snarks are already aggregation circuits and does not re-expose the old accumulators (the first `4 * LIMBS` elements) as public inputs. + +## `EthBlockStorageCircuit` + +```rust +pub struct EthBlockStorageCircuit { + pub inputs: EthBlockStorageInput, + pub network: Network, +} +``` + +The public instances are: + +- `block_hash`: `H256` as two `Fr` elements in hi-lo format +- `block_number`: `u32` as a single `Fr` element +- `address`: `H160` as a single `Fr` element +- Sequence of `inputs.slots.len()` pairs of `(slot, value)` where + - `slot`: `H256` as two `Fr` elements in hi-lo format + - `value`: `U256` as two `Fr` elements in hi-lo format (big endian) + +# `AxiomV1Core` SNARK Verifier + +This snark is created by calling + +```bash +cargo run --bin header_chain --release -- --start 0 --end 1023 --max-depth 10 --initial-depth 7 --final evm --extra-rounds 1 --calldata --create-contract +``` + +This recursively creates the following snarks in a tree: + +``` +PublicAggregationCircuit (10) -> PublicAggregationCircuit (10) -> EthBlockHeaderChainFinalAggregationCircuit (10) -> EthBlockHeaderChainAggregationCircuit (9) -> ... -> EthBlockHeaderChainAggregationCircuit (8) -> EthBlockHeaderChainCircuit (7) +``` + +where the number in parenthesis is a tracker of the `max_depth` for the circuit. We do two rounds of `PublicAggregationCircuit` to minimize final verification gas cost. + +The public instances are the same as for [`EthBlockHeaderChainFinalAggregationCircuit`](#ethblockheaderchainfinalaggregationcircuit). + +# `AxiomV1Core` Historical SNARK Verifier + +This snark is created by calling + +```bash +cargo run --bin header_chain --release -- --start 0 --end 1023 --max-depth 17 --initial-depth 7 --final evm --extra-rounds 1 --calldata --create-contract +``` + +This recursively creates the following snarks in a tree: + +``` +PublicAggregationCircuit (17) -> PublicAggregationCircuit (17) -> EthBlockHeaderChainFinalAggregationCircuit (17) -> EthBlockHeaderChainAggregationCircuit (16) -> ... -> EthBlockHeaderChainAggregationCircuit (8) -> EthBlockHeaderChainCircuit (7) +``` + +where the number in parenthesis is a tracker of the `max_depth` for the circuit. + +The public instances are the same as for [`EthBlockHeaderChainFinalAggregationCircuit`](#ethblockheaderchainfinalaggregationcircuit). + +# `AxiomV1StoragePf` SNARK Verifier + +This snark is created by calling + +```bash +cargo run --bin storage_proof --release -- --path data/storage/task.t.json --create-contract +``` + +with this [`task.t.json`](../../data/storage/task.t.json) file. In particular `inputs.slots.len() = 10`. + +This recursively creates the following snarks: + +``` +PublicAggregationCircuit -> EthBlockStorageCircuit +``` diff --git a/src/bin/README.md b/axiom-eth/src/bin/README.md similarity index 100% rename from src/bin/README.md rename to axiom-eth/src/bin/README.md diff --git a/src/bin/header_chain.rs b/axiom-eth/src/bin/header_chain.rs similarity index 100% rename from src/bin/header_chain.rs rename to axiom-eth/src/bin/header_chain.rs diff --git a/src/bin/storage_proof.rs b/axiom-eth/src/bin/storage_proof.rs similarity index 100% rename from src/bin/storage_proof.rs rename to axiom-eth/src/bin/storage_proof.rs diff --git a/src/block_header/aggregation/final_merkle.rs b/axiom-eth/src/block_header/aggregation/final_merkle.rs similarity index 94% rename from src/block_header/aggregation/final_merkle.rs rename to axiom-eth/src/block_header/aggregation/final_merkle.rs index 3cfb8fdab..84df3068d 100644 --- a/src/block_header/aggregation/final_merkle.rs +++ b/axiom-eth/src/block_header/aggregation/final_merkle.rs @@ -7,7 +7,7 @@ use crate::{ builder::{RlcThreadBreakPoints, RlcThreadBuilder}, RlpChip, }, - util::{bytes_be_to_u128, get_merkle_mountain_range, num_to_bytes_be, NUM_BYTES_IN_U128}, + util::{bytes_be_to_u128, num_to_bytes_be, NUM_BYTES_IN_U128}, EthCircuitBuilder, }; #[cfg(feature = "display")] @@ -38,14 +38,13 @@ impl EthBlockHeaderChainFinalAggregationCircuit { max_depth: usize, initial_depth: usize, ) -> Self { - let mut inner = EthBlockHeaderChainAggregationCircuit::new( + let inner = EthBlockHeaderChainAggregationCircuit::new( snarks, num_blocks, max_depth, initial_depth, ); - #[cfg(debug_assertions)] - { + /* // Only for testing let leaves = &inner.chain_instance.merkle_mountain_range[..num_blocks as usize >> initial_depth]; let mut new_mmr = get_merkle_mountain_range(leaves, max_depth - initial_depth); @@ -53,7 +52,7 @@ impl EthBlockHeaderChainFinalAggregationCircuit { &inner.chain_instance.merkle_mountain_range[1 << (max_depth - initial_depth)..], ); inner.chain_instance.merkle_mountain_range = new_mmr; - } + */ Self(inner) } @@ -158,9 +157,4 @@ impl EthBlockHeaderChainFinalAggregationCircuit { } circuit } - - /// The number of instances NOT INCLUDING the accumulator - pub fn get_num_instance(max_depth: usize) -> usize { - 5 + 2 * (max_depth + 1) - } } diff --git a/src/block_header/aggregation/mod.rs b/axiom-eth/src/block_header/aggregation/mod.rs similarity index 87% rename from src/block_header/aggregation/mod.rs rename to axiom-eth/src/block_header/aggregation/mod.rs index fc318960c..c3c7d0ea5 100644 --- a/src/block_header/aggregation/mod.rs +++ b/axiom-eth/src/block_header/aggregation/mod.rs @@ -1,11 +1,5 @@ -use std::{ - env::{set_var, var}, - mem, -}; +use std::mem; -use crate::{block_header::EthBlockHeaderChainInstance, Field}; -#[cfg(feature = "display")] -use ark_std::{end_timer, start_timer}; use halo2_base::{ gates::{ builder::{CircuitBuilderStage, MultiPhaseThreadBreakPoints}, @@ -13,7 +7,7 @@ use halo2_base::{ }, halo2_proofs::{ halo2curves::bn256::{Bn256, Fr}, - poly::{commitment::Params, kzg::commitment::ParamsKZG}, + poly::kzg::commitment::ParamsKZG, }, utils::ScalarField, AssignedValue, Context, @@ -22,6 +16,8 @@ use halo2_base::{ use itertools::Itertools; use snark_verifier_sdk::{halo2::aggregation::AggregationCircuit, Snark, LIMBS, SHPLONK}; +use crate::{AggregationPreCircuit, Field}; + mod final_merkle; pub use final_merkle::*; @@ -34,8 +30,7 @@ pub struct EthBlockHeaderChainAggregationCircuit { pub initial_depth: usize, // because the aggregation circuit doesn't have a keccak chip, in the mountain range // vector we will store the `2^{max_depth - initial_depth}` "new roots" as well as the length `initial_depth` mountain range tail, which determines the smallest entries in the mountain range. - #[cfg(debug_assertions)] - pub chain_instance: EthBlockHeaderChainInstance, + // chain_instance: EthBlockHeaderChainInstance, // only needed for testing } impl EthBlockHeaderChainAggregationCircuit { @@ -55,9 +50,9 @@ impl EthBlockHeaderChainAggregationCircuit { assert!(max_depth > initial_depth); assert!(num_blocks <= 1 << max_depth); - #[cfg(debug_assertions)] + /* + // OLD, no longer needed except for debugging let chain_instance = { - // OLD, no longer needed except for debugging // basically the same logic as `join_previous_instances` except in native rust let instance_start_idx = usize::from(initial_depth + 1 != max_depth) * 4 * LIMBS; let [instance0, instance1] = [0, 1].map(|i| { @@ -100,21 +95,25 @@ impl EthBlockHeaderChainAggregationCircuit { end_block_number: instance0.start_block_number + num_blocks - 1, merkle_mountain_range: roots, } - }; + };*/ Self { snarks, num_blocks, max_depth, initial_depth, - #[cfg(debug_assertions)] - chain_instance, + // chain_instance, } } - /// `params` should be the universal trusted setup for the present aggregation circuit. - /// We assume the trusted setup for the previous SNARKs is compatible with `params` in the sense that - /// the generator point and toxic waste `tau` are the same. - pub fn create_circuit( + /// The number of instances NOT INCLUDING the accumulator + pub fn get_num_instance(max_depth: usize, initial_depth: usize) -> usize { + debug_assert!(max_depth >= initial_depth); + 5 + 2 * ((1 << (max_depth - initial_depth)) + initial_depth) + } +} + +impl AggregationPreCircuit for EthBlockHeaderChainAggregationCircuit { + fn create( self, stage: CircuitBuilderStage, break_points: Option, @@ -124,10 +123,9 @@ impl EthBlockHeaderChainAggregationCircuit { let num_blocks = self.num_blocks; let max_depth = self.max_depth; let initial_depth = self.initial_depth; - #[cfg(feature = "display")] - let timer = start_timer!(|| { - format!("New EthBlockHeaderChainAggregationCircuit | num_blocks: {num_blocks} | max_depth: {max_depth} | initial_depth: {initial_depth}") - }); + log::info!( + "New EthBlockHeaderChainAggregationCircuit | num_blocks: {num_blocks} | max_depth: {max_depth} | initial_depth: {initial_depth}" + ); let mut aggregation = AggregationCircuit::new::( stage, break_points, @@ -151,27 +149,9 @@ impl EthBlockHeaderChainAggregationCircuit { ); drop(builder); aggregation.inner.assigned_instances.append(&mut new_instances); - #[cfg(feature = "display")] - end_timer!(timer); - #[cfg(not(feature = "production"))] - match stage { - CircuitBuilderStage::Prover => {} - _ => { - let minimum_rows = - var("UNUSABLE_ROWS").map(|s| s.parse().unwrap_or(10)).unwrap_or(10); - set_var("LOOKUP_BITS", lookup_bits.to_string()); - aggregation.config(params.k(), Some(minimum_rows)); - } - } aggregation } - - /// The number of instances NOT INCLUDING the accumulator - pub fn get_num_instance(max_depth: usize, initial_depth: usize) -> usize { - debug_assert!(max_depth >= initial_depth); - 5 + 2 * ((1 << (max_depth - initial_depth)) + initial_depth) - } } /// Takes the concatenated previous instances from two `EthBlockHeaderChainAggregationCircuit`s diff --git a/src/block_header/helpers.rs b/axiom-eth/src/block_header/helpers.rs similarity index 60% rename from src/block_header/helpers.rs rename to axiom-eth/src/block_header/helpers.rs index 092570b61..6b938c4aa 100644 --- a/src/block_header/helpers.rs +++ b/axiom-eth/src/block_header/helpers.rs @@ -5,15 +5,15 @@ use super::{ EthBlockHeaderChainCircuit, }; use crate::{ - rlp::builder::RlcThreadBuilder, util::{ circuit::{AnyCircuit, PinnableCircuit}, circuit::{PreCircuit, PublicAggregationCircuit}, scheduler::{self, EthScheduler, Scheduler}, AggregationConfigPinning, EthConfigPinning, Halo2ConfigPinning, }, - Network, + AggregationPreCircuit, Network, }; +use any_circuit_derive::AnyCircuit; use core::cmp::min; use halo2_base::{ gates::builder::CircuitBuilderStage, @@ -90,6 +90,26 @@ impl CircuitType { } } +impl scheduler::CircuitType for CircuitType { + fn name(&self) -> String { + format!("{}{}", self.fname_prefix(), self.fname_suffix()) + } + + fn get_degree_from_pinning(&self, path: impl AsRef) -> u32 { + let CircuitType { network: _, depth, initial_depth, finality } = self; + if depth == initial_depth { + EthConfigPinning::from_path(path).degree() + } else { + match finality { + Finality::None | Finality::Evm(_) => { + AggregationConfigPinning::from_path(path).degree() + } + Finality::Merkle => EthConfigPinning::from_path(path).degree(), + } + } + } +} + #[derive(Clone, Copy, Debug)] pub struct Task { pub start: u32, @@ -109,9 +129,7 @@ impl scheduler::Task for Task { fn circuit_type(&self) -> Self::CircuitType { self.circuit_type } - fn type_name(circuit_type: Self::CircuitType) -> String { - format!("{}{}", circuit_type.fname_prefix(), circuit_type.fname_suffix()) - } + fn name(&self) -> String { format!( "{}_{:06x}_{:06x}{}", @@ -140,9 +158,9 @@ impl scheduler::Task for Task { /// The public/private inputs for various circuits. // This is an enum of `PreCircuit`s. -// We implement `AnyCircuit` for `CircuitRouter` by passing through the implementations from each enum variant. TODO: use procedural macro for this (enum_dispatch does not quite work, not sure about enum_delegate) +// We implement `AnyCircuit` for `CircuitRouter` by passing through the implementations from each enum variant using a procedural macro. // This is because Rust traits do not allow a single type to output different kinds of `PreCircuit`s. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, AnyCircuit)] pub enum CircuitRouter { Initial(EthBlockHeaderChainCircuit), Intermediate(EthBlockHeaderChainAggregationCircuit), @@ -156,26 +174,6 @@ impl Scheduler for BlockHeaderScheduler { type Task = Task; type CircuitRouter = CircuitRouter; - fn get_degree(&self, circuit_type: CircuitType) -> u32 { - if let Some(k) = self.degree.read().unwrap().get(&circuit_type) { - return *k; - } - let path = self.pinning_path(circuit_type); - let CircuitType { network: _, depth, initial_depth, finality } = circuit_type; - let k = if depth == initial_depth { - EthConfigPinning::from_path(path).params.degree - } else { - match finality { - Finality::None | Finality::Evm(_) => { - AggregationConfigPinning::from_path(path).params.degree - } - Finality::Merkle => EthConfigPinning::from_path(path).params.degree, - } - }; - self.degree.write().unwrap().insert(circuit_type, k); - k - } - fn get_circuit(&self, task: Task, mut snarks: Vec) -> CircuitRouter { let Task { start, end, circuit_type } = task; let CircuitType { network, depth, initial_depth, finality } = circuit_type; @@ -219,34 +217,15 @@ impl Scheduler for BlockHeaderScheduler { } Finality::Evm(_) => { assert_eq!(snarks.len(), 1); // currently just passthrough - CircuitRouter::ForEvm(PublicAggregationCircuit { - snarks, - has_prev_accumulators: true, - }) + CircuitRouter::ForEvm(PublicAggregationCircuit::new( + snarks.into_iter().map(|snark| (snark, true)).collect(), + )) } } } } } -impl PreCircuit for EthBlockHeaderChainCircuit { - type Pinning = EthConfigPinning; - - fn create_circuit( - self, - stage: CircuitBuilderStage, - pinning: Option, - _: &ParamsKZG, - ) -> impl PinnableCircuit { - let builder = match stage { - CircuitBuilderStage::Prover => RlcThreadBuilder::new(true), - _ => RlcThreadBuilder::new(false), - }; - let break_points = pinning.map(|p| p.break_points()); - EthBlockHeaderChainCircuit::create_circuit(self, builder, break_points) - } -} - impl PreCircuit for EthBlockHeaderChainAggregationCircuit { type Pinning = AggregationConfigPinning; @@ -256,15 +235,14 @@ impl PreCircuit for EthBlockHeaderChainAggregationCircuit { pinning: Option, params: &ParamsKZG, ) -> impl PinnableCircuit { - let lookup_bits = var("LOOKUP_BITS").expect("LOOKUP_BITS is not set").parse().unwrap(); + // look for lookup_bits either from pinning, if available, or from env var + let lookup_bits = pinning + .as_ref() + .map(|p| p.params.lookup_bits) + .or_else(|| var("LOOKUP_BITS").map(|v| v.parse().unwrap()).ok()) + .expect("LOOKUP_BITS is not set"); let break_points = pinning.map(|p| p.break_points()); - EthBlockHeaderChainAggregationCircuit::create_circuit( - self, - stage, - break_points, - lookup_bits, - params, - ) + AggregationPreCircuit::create_circuit(self, stage, break_points, lookup_bits, params) } } @@ -277,7 +255,12 @@ impl PreCircuit for EthBlockHeaderChainFinalAggregationCircuit { pinning: Option, params: &ParamsKZG, ) -> impl PinnableCircuit { - let lookup_bits = var("LOOKUP_BITS").expect("LOOKUP_BITS is not set").parse().unwrap(); + // look for lookup_bits either from pinning, if available, or from env var + let lookup_bits = pinning + .as_ref() + .map(|p| p.params.lookup_bits.unwrap()) + .or_else(|| var("LOOKUP_BITS").map(|v| v.parse().unwrap()).ok()) + .expect("LOOKUP_BITS is not set"); let break_points = pinning.map(|p| p.break_points()); EthBlockHeaderChainFinalAggregationCircuit::create_circuit( self, @@ -288,95 +271,3 @@ impl PreCircuit for EthBlockHeaderChainFinalAggregationCircuit { ) } } - -// just copy/paste.. cannot find better way right now -impl AnyCircuit for CircuitRouter { - fn read_or_create_pk( - self, - params: &ParamsKZG, - pk_path: impl AsRef, - pinning_path: impl AsRef, - read_only: bool, - ) -> ProvingKey { - // does almost the same thing for each circuit type; don't know how to get around this with rust - match self { - Self::Initial(pre_circuit) => { - pre_circuit.read_or_create_pk(params, pk_path, pinning_path, read_only) - } - Self::Intermediate(pre_circuit) => { - pre_circuit.read_or_create_pk(params, pk_path, pinning_path, read_only) - } - Self::Final(pre_circuit) => { - pre_circuit.read_or_create_pk(params, pk_path, pinning_path, read_only) - } - Self::ForEvm(pre_circuit) => { - pre_circuit.read_or_create_pk(params, pk_path, pinning_path, read_only) - } - } - } - - fn gen_snark_shplonk( - self, - params: &ParamsKZG, - pk: &ProvingKey, - pinning_path: impl AsRef, - path: Option>, - ) -> Snark { - match self { - Self::Initial(pre_circuit) => { - pre_circuit.gen_snark_shplonk(params, pk, pinning_path, path) - } - Self::Intermediate(pre_circuit) => { - pre_circuit.gen_snark_shplonk(params, pk, pinning_path, path) - } - Self::Final(pre_circuit) => { - pre_circuit.gen_snark_shplonk(params, pk, pinning_path, path) - } - Self::ForEvm(pre_circuit) => { - pre_circuit.gen_snark_shplonk(params, pk, pinning_path, path) - } - } - } - - fn gen_evm_verifier_shplonk( - self, - params: &ParamsKZG, - pk: &ProvingKey, - yul_path: impl AsRef, - ) -> Vec { - match self { - Self::Initial(pre_circuit) => { - pre_circuit.gen_evm_verifier_shplonk(params, pk, yul_path) - } - Self::Intermediate(pre_circuit) => { - pre_circuit.gen_evm_verifier_shplonk(params, pk, yul_path) - } - Self::Final(pre_circuit) => pre_circuit.gen_evm_verifier_shplonk(params, pk, yul_path), - Self::ForEvm(pre_circuit) => pre_circuit.gen_evm_verifier_shplonk(params, pk, yul_path), - } - } - - fn gen_calldata( - self, - params: &ParamsKZG, - pk: &ProvingKey, - pinning_path: impl AsRef, - path: impl AsRef, - deployment_code: Option>, - ) -> String { - match self { - Self::Initial(pre_circuit) => { - pre_circuit.gen_calldata(params, pk, pinning_path, path, deployment_code) - } - Self::Intermediate(pre_circuit) => { - pre_circuit.gen_calldata(params, pk, pinning_path, path, deployment_code) - } - Self::Final(pre_circuit) => { - pre_circuit.gen_calldata(params, pk, pinning_path, path, deployment_code) - } - Self::ForEvm(pre_circuit) => { - pre_circuit.gen_calldata(params, pk, pinning_path, path, deployment_code) - } - } - } -} diff --git a/src/block_header/mod.rs b/axiom-eth/src/block_header/mod.rs similarity index 62% rename from src/block_header/mod.rs rename to axiom-eth/src/block_header/mod.rs index e8d8c4f5a..e8908dd13 100644 --- a/src/block_header/mod.rs +++ b/axiom-eth/src/block_header/mod.rs @@ -1,34 +1,32 @@ -use super::{ - util::{bytes_be_to_u128, encode_h256_to_field, EthConfigParams}, - Field, Network, -}; +use super::{util::bytes_be_to_u128, Field, Network}; use crate::{ - keccak::{FixedLenRLCs, FnSynthesize, KeccakChip, VarLenRLCs}, + keccak::{ + parallelize_keccak_phase0, ContainsParallelizableKeccakQueries, FixedLenRLCs, FnSynthesize, + KeccakChip, VarLenRLCs, + }, rlp::{ - builder::{RlcThreadBreakPoints, RlcThreadBuilder}, + builder::{parallelize_phase1, RlcThreadBreakPoints, RlcThreadBuilder}, rlc::{RlcContextPair, RlcFixedTrace, RlcTrace, FIRST_PHASE, RLC_PHASE}, RlpArrayTraceWitness, RlpChip, RlpFieldTrace, RlpFieldWitness, }, - util::{bytes_be_var_to_fixed, decode_field_to_h256}, - EthChip, EthCircuitBuilder, ETH_LOOKUP_BITS, + util::bytes_be_var_to_fixed, + EthChip, EthCircuitBuilder, EthPreCircuit, ETH_LOOKUP_BITS, }; use core::{ iter::{self, once}, marker::PhantomData, }; -use ethers_core::types::H256; #[cfg(feature = "providers")] -use ethers_providers::{Http, Provider}; +use ethers_providers::{JsonRpcClient, Provider}; use halo2_base::{ - gates::{builder::GateThreadBuilder, GateInstructions, RangeChip}, + gates::{builder::GateThreadBuilder, GateInstructions, RangeChip, RangeInstructions}, + halo2_proofs::halo2curves::bn256::Fr, utils::bit_length, AssignedValue, Context, QuantumCell::{Constant, Existing}, }; use itertools::Itertools; -use rayon::prelude::*; -use serde::{Deserialize, Serialize}; -use std::{cell::RefCell, env::var}; +use std::cell::RefCell; #[cfg(feature = "aggregation")] pub mod aggregation; @@ -38,13 +36,13 @@ pub mod helpers; mod tests; // extra data max byte length is different for different networks -const MAINNET_EXTRA_DATA_MAX_BYTES: usize = 32; -const MAINNET_EXTRA_DATA_RLP_MAX_BYTES: usize = MAINNET_EXTRA_DATA_MAX_BYTES + 1; -const GOERLI_EXTRA_DATA_MAX_BYTES: usize = 97; -const GOERLI_EXTRA_DATA_RLP_MAX_BYTES: usize = GOERLI_EXTRA_DATA_MAX_BYTES + 1; +pub const MAINNET_EXTRA_DATA_MAX_BYTES: usize = 32; +pub const MAINNET_EXTRA_DATA_RLP_MAX_BYTES: usize = MAINNET_EXTRA_DATA_MAX_BYTES + 1; +pub const GOERLI_EXTRA_DATA_MAX_BYTES: usize = 97; +pub const GOERLI_EXTRA_DATA_RLP_MAX_BYTES: usize = GOERLI_EXTRA_DATA_MAX_BYTES + 1; /// This is the minimum possible RLP byte length of a block header *at any block* (including pre EIPs) -const BLOCK_HEADER_RLP_MIN_BYTES: usize = 479; +pub const BLOCK_HEADER_RLP_MIN_BYTES: usize = 479; /// The maximum possible RLP byte length of a block header *at any block* (including all EIPs). /// /// Provided that the total length is < 256^2, this will be 1 + 2 + sum(max RLP byte length of each field) @@ -53,32 +51,43 @@ pub const MAINNET_BLOCK_HEADER_RLP_MAX_BYTES: usize = pub const GOERLI_BLOCK_HEADER_RLP_MAX_BYTES: usize = 1 + 2 + (521 + GOERLI_EXTRA_DATA_RLP_MAX_BYTES + 33); -const NUM_BLOCK_HEADER_FIELDS: usize = 17; -const MAINNET_HEADER_FIELDS_MAX_BYTES: [usize; NUM_BLOCK_HEADER_FIELDS] = +pub const MIN_NUM_BLOCK_HEADER_FIELDS: usize = 15; +pub const NUM_BLOCK_HEADER_FIELDS: usize = 17; +pub const MAINNET_HEADER_FIELDS_MAX_BYTES: [usize; NUM_BLOCK_HEADER_FIELDS] = [32, 32, 20, 32, 32, 32, 256, 7, 4, 4, 4, 4, MAINNET_EXTRA_DATA_MAX_BYTES, 32, 8, 6, 32]; -const GOERLI_HEADER_FIELDS_MAX_BYTES: [usize; NUM_BLOCK_HEADER_FIELDS] = +pub const GOERLI_HEADER_FIELDS_MAX_BYTES: [usize; NUM_BLOCK_HEADER_FIELDS] = [32, 32, 20, 32, 32, 32, 256, 7, 4, 4, 4, 4, GOERLI_EXTRA_DATA_MAX_BYTES, 32, 8, 6, 32]; +pub const BLOCK_HEADER_FIELD_IS_VAR_LEN: [bool; NUM_BLOCK_HEADER_FIELDS] = [ + false, false, false, false, false, false, false, true, true, true, true, true, true, false, + false, true, false, +]; /// The maximum number of bytes it takes to represent a block number, without any RLP encoding. -pub const BLOCK_NUMBER_MAX_BYTES: usize = MAINNET_HEADER_FIELDS_MAX_BYTES[8]; - -// Field Type Size (bytes) RLP size (bytes) RLP size (bits) -// parentHash 256 bits 32 33 264 -// ommersHash 256 bits 32 33 264 -// beneficiary 160 bits 20 21 168 -// stateRoot 256 bits 32 33 264 -// transactionsRoot 256 bits 32 33 264 -// receiptsRoot 256 bits 32 33 264 -// logsBloom 256 bytes 256 259 2072 -// difficulty big int scalar variable 8 64 -// number big int scalar variable <= 5 <= 40 -// gasLimit big int scalar variable 5 40 -// gasUsed big int scalar variable <= 5 <= 40 -// timestamp big int scalar variable 5 40 -// extraData up to 256 bits variable, <= 32 <= 33 <= 264 (Mainnet) -// mixHash 256 bits 32 33 264 -// nonce 64 bits 8 9 72 -// basefee (post-1559) big int scalar variable <= 6 <= 48 -// withdrawals_root (post-4895) 256 bits 32 33 264 +pub const BLOCK_NUMBER_MAX_BYTES: usize = MAINNET_HEADER_FIELDS_MAX_BYTES[BLOCK_NUMBER_INDEX]; +pub(crate) const STATE_ROOT_INDEX: usize = 3; +pub(crate) const BLOCK_NUMBER_INDEX: usize = 8; +pub(crate) const EXTRA_DATA_INDEX: usize = 12; + +/** +| Field | Type | Size (bytes) | RLP size (bytes) | RLP size (bits) | +|------------------------------|-----------------|-----------------|------------------|-----------------| +| parentHash | 256 bits | 32 | 33 | 264 | +| ommersHash | 256 bits | 32 | 33 | 264 | +| beneficiary | 160 bits | 20 | 21 | 168 | +| stateRoot | 256 bits | 32 | 33 | 264 | +| transactionsRoot | 256 bits | 32 | 33 | 264 | +| receiptsRoot | 256 bits | 32 | 33 | 264 | +| logsBloom | 256 bytes | 256 | 259 | 2072 | +| difficulty | big int scalar | variable | 8 | 64 | +| number | big int scalar | variable | <= 5 | <= 40 | +| gasLimit | big int scalar | variable | 5 | 40 | +| gasUsed | big int scalar | variable | <= 5 | <= 40 | +| timestamp | big int scalar | variable | 5 | 40 | +| extraData (Mainnet) | up to 256 bits | variable, <= 32 | <= 33 | <= 264 | +| mixHash | 256 bits | 32 | 33 | 264 | +| nonce | 64 bits | 8 | 9 | 72 | +| basefee (post-1559) | big int scalar | variable | <= 6 | <= 48 | +| withdrawalsRoot (post-4895) | 256 bits | 32 | 33 | 264 | +*/ #[allow(dead_code)] #[derive(Clone, Debug)] pub struct EthBlockHeaderTrace { @@ -114,27 +123,66 @@ pub struct EthBlockHeaderTraceWitness { } impl EthBlockHeaderTraceWitness { - pub fn get(&self, header_field: &str) -> &RlpFieldWitness { - match header_field { - "parent_hash" | "parentHash" => &self.rlp_witness.field_witness[0], - "ommers_hash" | "ommersHash" => &self.rlp_witness.field_witness[1], - "beneficiary" => &self.rlp_witness.field_witness[2], - "state_root" | "stateRoot" => &self.rlp_witness.field_witness[3], - "transactions_root" | "transactionsRoot" => &self.rlp_witness.field_witness[4], - "receipts_root" | "receiptsRoot" => &self.rlp_witness.field_witness[5], - "logs_bloom" | "logsBloom" => &self.rlp_witness.field_witness[6], - "difficulty" => &self.rlp_witness.field_witness[7], - "number" => &self.rlp_witness.field_witness[8], - "gas_limit" | "gasLimit" => &self.rlp_witness.field_witness[9], - "gas_used" | "gasUsed" => &self.rlp_witness.field_witness[10], - "timestamp" => &self.rlp_witness.field_witness[11], - "extra_data" | "extraData" => &self.rlp_witness.field_witness[12], - "mix_hash" | "mixHash" => &self.rlp_witness.field_witness[13], - "nonce" => &self.rlp_witness.field_witness[14], - "basefee" => &self.rlp_witness.field_witness[15], - "withdrawals_root" => &self.rlp_witness.field_witness[16], - _ => panic!("Invalid header field"), - } + pub fn get_parent_hash(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[0] + } + pub fn get_ommers_hash(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[1] + } + pub fn get_beneficiary(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[2] + } + pub fn get_state_root(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[3] + } + pub fn get_transactions_root(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[4] + } + pub fn get_receipts_root(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[5] + } + pub fn get_logs_bloom(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[6] + } + pub fn get_difficulty(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[7] + } + pub fn get_number(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[8] + } + pub fn get_gas_limit(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[9] + } + pub fn get_gas_used(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[10] + } + pub fn get_timestamp(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[11] + } + pub fn get_extra_data(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[12] + } + pub fn get_mix_hash(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[13] + } + pub fn get_nonce(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[14] + } + pub fn get_basefee(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[15] + } + pub fn get_withdrawals_root(&self) -> &RlpFieldWitness { + &self.rlp_witness.field_witness[16] + } + pub fn get_index(&self, idx: usize) -> Option<&RlpFieldWitness> { + self.rlp_witness.field_witness.get(idx) + } +} + +impl ContainsParallelizableKeccakQueries for EthBlockHeaderTraceWitness { + // Currently all indices are with respect to `keccak.var_len_queries` + fn shift_query_indices(&mut self, _: usize, var_shift: usize) { + self.block_hash_query_idx += var_shift; } } @@ -151,7 +199,7 @@ pub trait EthBlockHeaderChip { &self, ctx: &mut Context, keccak: &mut KeccakChip, - block_header: &[u8], + block_header_rlp: &[u8], network: Network, ) -> EthBlockHeaderTraceWitness; @@ -172,6 +220,34 @@ pub trait EthBlockHeaderChip { witness: EthBlockHeaderTraceWitness, ) -> EthBlockHeaderTrace; + /// Makes multiple calls to `decompose_block_header_phase0` in parallel threads. Should be called in FirstPhase. + fn decompose_block_headers_phase0( + &self, + thread_pool: &mut GateThreadBuilder, + keccak: &mut KeccakChip, + block_headers: Vec>, + network: Network, + ) -> Vec> + where + Self: Sync, + { + parallelize_keccak_phase0( + thread_pool, + keccak, + block_headers, + |ctx, keccak, block_header| { + self.decompose_block_header_phase0(ctx, keccak, &block_header, network) + }, + ) + } + + /// Makes multiple calls to `decompose_block_header_phase1` in parallel threads. Should be called in SecondPhase. + fn decompose_block_headers_phase1( + &self, + thread_pool: &mut RlcThreadBuilder, + witnesses: Vec>, + ) -> Vec>; + /// Takes a list of (purported) RLP encoded block headers and /// decomposes each header into it's fields. /// `headers[0]` is the earliest block. @@ -182,9 +258,14 @@ pub trait EthBlockHeaderChip { &self, thread_pool: &mut GateThreadBuilder, keccak: &mut KeccakChip, - headers: &[Vec], + block_headers: Vec>, network: Network, - ) -> Vec>; + ) -> Vec> + where + Self: Sync, + { + self.decompose_block_headers_phase0(thread_pool, keccak, block_headers, network) + } /// Takes a list of `2^max_depth` (purported) RLP encoded block headers. /// Decomposes each header into it's fields. @@ -215,12 +296,7 @@ impl<'chip, F: Field> EthBlockHeaderChip for EthChip<'chip, F> { block_header: &[u8], network: Network, ) -> EthBlockHeaderTraceWitness { - let (max_len, max_field_lens) = match network { - Network::Mainnet => { - (MAINNET_BLOCK_HEADER_RLP_MAX_BYTES, &MAINNET_HEADER_FIELDS_MAX_BYTES) - } - Network::Goerli => (GOERLI_BLOCK_HEADER_RLP_MAX_BYTES, &GOERLI_HEADER_FIELDS_MAX_BYTES), - }; + let (max_len, max_field_lens) = get_block_header_rlp_max_lens(network); assert_eq!(block_header.len(), max_len); let block_header_assigned = ctx.assign_witnesses(block_header.iter().map(|byte| F::from(*byte as u64))); @@ -274,61 +350,10 @@ impl<'chip, F: Field> EthBlockHeaderChip for EthChip<'chip, F> { } } - fn decompose_block_header_chain_phase0( - &self, - thread_pool: &mut GateThreadBuilder, - keccak: &mut KeccakChip, - headers: &[Vec], - network: Network, - ) -> Vec> { - let (max_len, max_field_lens) = match network { - Network::Mainnet => { - (MAINNET_BLOCK_HEADER_RLP_MAX_BYTES, &MAINNET_HEADER_FIELDS_MAX_BYTES) - } - Network::Goerli => (GOERLI_BLOCK_HEADER_RLP_MAX_BYTES, &GOERLI_HEADER_FIELDS_MAX_BYTES), - }; - // we cannot directly parallelize `decompose_block_header_phase0` because `KeccakChip` is not thread-safe (we need to deterministically add new queries), so we explicitly parallelize the logic here: - let witness_gen_only = thread_pool.witness_gen_only(); - let ctx_ids = headers.iter().map(|_| thread_pool.get_new_thread_id()).collect::>(); - let (rlp_witnesses, mut ctxs): (Vec<_>, Vec<_>) = headers - .par_iter() - .zip(ctx_ids.into_par_iter()) - .map(|(header, ctx_id)| { - assert_eq!(header.len(), max_len); - let mut ctx = Context::new(witness_gen_only, ctx_id); - let header = ctx.assign_witnesses(header.iter().map(|byte| F::from(*byte as u64))); - let rlp_witness = - self.rlp().decompose_rlp_array_phase0(&mut ctx, header, max_field_lens, true); // `is_variable_len = true` because RLP can have either 15 or 16 fields, depending on whether block is pre-London or not - (rlp_witness, ctx) - }) - .unzip(); - // single-threaded adding of keccak queries - thread_pool.threads[FIRST_PHASE].append(&mut ctxs); - let ctx = thread_pool.main(FIRST_PHASE); - rlp_witnesses - .into_iter() - .zip(headers.iter()) - .map(|(rlp_witness, header)| { - let block_hash_query_idx = keccak.keccak_var_len( - ctx, - self.range(), - rlp_witness.rlp_array.clone(), // this is `block_header_assigned` - Some(header.to_vec()), - rlp_witness.rlp_len, - BLOCK_HEADER_RLP_MIN_BYTES, - ); - let block_hash = - keccak.var_len_queries[block_hash_query_idx].output_assigned.clone(); - EthBlockHeaderTraceWitness { rlp_witness, block_hash, block_hash_query_idx } - }) - .collect() - } - - fn decompose_block_header_chain_phase1( + fn decompose_block_headers_phase1( &self, thread_pool: &mut RlcThreadBuilder, witnesses: Vec>, - num_blocks_minus_one: Option<(AssignedValue, Vec>)>, ) -> Vec> { assert!(!witnesses.is_empty()); let ctx = thread_pool.rlc_ctx_pair(); @@ -337,26 +362,19 @@ impl<'chip, F: Field> EthBlockHeaderChip for EthChip<'chip, F> { let cache_bits = bit_length(witnesses[0].rlp_witness.rlp_array.len() as u64); self.rlc().load_rlc_cache(ctx, self.gate(), cache_bits); // now multi-threading: - let witness_gen_only = thread_pool.witness_gen_only(); - let ctx_ids = witnesses - .iter() - .map(|_| (thread_pool.get_new_thread_id(), thread_pool.get_new_thread_id())) - .collect_vec(); - let (traces, ctxs): (Vec<_>, Vec<_>) = witnesses - .into_par_iter() - .zip(ctx_ids.into_par_iter()) - .map(|(witness, (gate_id, rlc_id))| { - let mut ctx_gate = Context::new(witness_gen_only, gate_id); - let mut ctx_rlc = Context::new(witness_gen_only, rlc_id); - let trace = - self.decompose_block_header_phase1((&mut ctx_gate, &mut ctx_rlc), witness); - (trace, (ctx_gate, ctx_rlc)) - }) - .unzip(); - let (mut ctxs_gate, mut ctxs_rlc): (Vec<_>, Vec<_>) = ctxs.into_iter().unzip(); - thread_pool.gate_builder.threads[RLC_PHASE].append(&mut ctxs_gate); - thread_pool.threads_rlc.append(&mut ctxs_rlc); + parallelize_phase1(thread_pool, witnesses, |(ctx_gate, ctx_rlc), witness| { + self.decompose_block_header_phase1((ctx_gate, ctx_rlc), witness) + }) + } + fn decompose_block_header_chain_phase1( + &self, + thread_pool: &mut RlcThreadBuilder, + witnesses: Vec>, + num_blocks_minus_one: Option<(AssignedValue, Vec>)>, + ) -> Vec> { + assert!(!witnesses.is_empty()); + let traces = self.decompose_block_headers_phase1(thread_pool, witnesses); let ctx_gate = thread_pool.gate_builder.main(RLC_PHASE); let thirty_two = self.gate().get_field_element(32); // record for each idx whether hash of headers[idx] is in headers[idx + 1] @@ -427,7 +445,7 @@ pub fn get_boundary_block_data( indicator: &[AssignedValue], ) -> ([AssignedValue; 2], [AssignedValue; 2], AssignedValue) { let prev_block_hash: [_; 2] = - bytes_be_to_u128(ctx, gate, &chain[0].get("parent_hash").field_cells).try_into().unwrap(); + bytes_be_to_u128(ctx, gate, &chain[0].get_parent_hash().field_cells).try_into().unwrap(); let end_block_hash: [_; 2] = { let end_block_hash_bytes = (0..32) .map(|idx| { @@ -443,12 +461,12 @@ pub fn get_boundary_block_data( // start_block_number || end_block_number let block_numbers = { - debug_assert_eq!(chain[0].get("number").max_field_len, BLOCK_NUMBER_MAX_BYTES); + debug_assert_eq!(chain[0].get_number().max_field_len, BLOCK_NUMBER_MAX_BYTES); let start_block_number_bytes = bytes_be_var_to_fixed( ctx, gate, - &chain[0].get("number").field_cells, - chain[0].get("number").field_len, + &chain[0].get_number().field_cells, + chain[0].get_number().field_len, BLOCK_NUMBER_MAX_BYTES, ); // TODO: is there a way to do this without so many selects @@ -456,13 +474,13 @@ pub fn get_boundary_block_data( core::array::from_fn(|i| i).map(|idx| { gate.select_by_indicator( ctx, - chain.iter().map(|header| header.get("number").field_cells[idx]), + chain.iter().map(|header| header.get_number().field_cells[idx]), indicator.iter().copied(), ) }); let end_block_number_len = gate.select_by_indicator( ctx, - chain.iter().map(|header| header.get("number").field_len), + chain.iter().map(|header| header.get_number().field_len), indicator.iter().copied(), ); let mut end_block_number_bytes = bytes_be_var_to_fixed( @@ -482,57 +500,6 @@ pub fn get_boundary_block_data( (prev_block_hash, end_block_hash, block_numbers) } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct EthBlockHeaderChainInstance { - pub prev_hash: H256, - pub end_hash: H256, - pub start_block_number: u32, - pub end_block_number: u32, - pub merkle_mountain_range: Vec, -} - -impl EthBlockHeaderChainInstance { - pub fn new( - prev_hash: H256, - end_hash: H256, - start_block_number: u32, - end_block_number: u32, - merkle_mountain_range: Vec, - ) -> Self { - Self { prev_hash, end_hash, start_block_number, end_block_number, merkle_mountain_range } - } - - pub fn to_instance(&self) -> Vec { - // * prevHash: uint256 represented as 2 uint128s - // * endHash: uint256 represented as 2 uint128s - // * startBlockNumber || endBlockNumber: 0..0 || uint32 || 0..0 || uint32 as u64 (exactly 64 bits) - // * merkleRoots: Vec, each represented as 2 uint128s - let [prev_hash, end_hash] = - [&self.prev_hash, &self.end_hash].map(|hash| encode_h256_to_field::(hash)); - let block_numbers = - F::from(((self.start_block_number as u64) << 32) + (self.end_block_number as u64)); - let merkle_mountain_range = self - .merkle_mountain_range - .iter() - .flat_map(|hash| encode_h256_to_field::(hash)) - .collect_vec(); - - [&prev_hash[..], &end_hash[..], &[block_numbers], &merkle_mountain_range].concat() - } - - pub fn from_instance(instance: &[F]) -> Self { - let prev_hash = decode_field_to_h256(&instance[0..2]); - let end_hash = decode_field_to_h256(&instance[2..4]); - let block_numbers = instance[4].to_repr(); // little endian - let start_block_number = u32::from_le_bytes(block_numbers[4..8].try_into().unwrap()); - let end_block_number = u32::from_le_bytes(block_numbers[..4].try_into().unwrap()); - let merkle_mountain_range = - instance[5..].chunks(2).map(|chunk| decode_field_to_h256(chunk)).collect_vec(); - - Self::new(prev_hash, end_hash, start_block_number, end_block_number, merkle_mountain_range) - } -} - #[derive(Clone, Debug)] /// The input datum for the block header chain circuit. It is used to generate a circuit. pub struct EthBlockHeaderChainCircuit { @@ -548,12 +515,35 @@ pub struct EthBlockHeaderChainCircuit { } impl EthBlockHeaderChainCircuit { - pub fn create_circuit( + #[cfg(feature = "providers")] + pub fn from_provider( + provider: &Provider

, + network: Network, + start_block_number: u32, + num_blocks: u32, + max_depth: usize, + ) -> Self { + let (header_rlp_max_bytes, _) = get_block_header_rlp_max_lens(network); + let mut block_rlps = + crate::providers::get_blocks_input(provider, start_block_number, num_blocks, max_depth); + for block_rlp in block_rlps.iter_mut() { + block_rlp.resize(header_rlp_max_bytes, 0u8); + } + + Self { + header_rlp_encodings: block_rlps, + num_blocks, + max_depth, + network, + _marker: PhantomData, + } + } + + pub fn create( self, mut builder: RlcThreadBuilder, break_points: Option, ) -> EthCircuitBuilder> { - let prover = builder.witness_gen_only(); let range = RangeChip::default(ETH_LOOKUP_BITS); // KECCAK_ROWS should be set if prover = true let chip = EthChip::new(RlpChip::new(&range, None), None); @@ -564,14 +554,16 @@ impl EthBlockHeaderChainCircuit { // ==== Load private inputs ===== let num_blocks = ctx.load_witness(F::from(self.num_blocks as u64)); let num_blocks_minus_one = chip.gate().sub(ctx, num_blocks, Constant(F::one())); - // `num_blocks_minus_one` should be < 2^max_depth. We do not check this because `num_blocks_minus_one` will equal the difference of the start, end block numbers, which are public inputs + // `num_blocks_minus_one` should be < 2^max_depth. + // We check this for safety, although it is not technically necessary because `num_blocks_minus_one` will equal the difference of the start, end block numbers, which are public inputs + chip.range().range_check(ctx, num_blocks_minus_one, self.max_depth); // ==== Load RLP encoding and decode ==== // The block header RLPs are assigned as witnesses in this function let block_chain_witness = chip.decompose_block_header_chain_phase0( &mut builder.gate_builder, &mut keccak, - &self.header_rlp_encodings, + self.header_rlp_encodings, self.network, ); // All keccaks must be done in FirstPhase, so we compute the merkle mountain range from the RLP decoded witnesses now @@ -609,7 +601,7 @@ impl EthBlockHeaderChainCircuit { .chain(mountain_range) .collect_vec(); - let circuit = EthCircuitBuilder::new( + EthCircuitBuilder::new( assigned_instances, builder, RefCell::new(keccak), @@ -626,46 +618,23 @@ impl EthBlockHeaderChainCircuit { Some((num_blocks_minus_one, indicator)), ); }, - ); - #[cfg(not(feature = "production"))] - if !prover { - let config_params: EthConfigParams = serde_json::from_str( - var("ETH_CONFIG_PARAMS").expect("ETH_CONFIG_PARAMS is not set").as_str(), - ) - .unwrap(); - circuit.config(config_params.degree as usize, Some(config_params.unusable_rows)); - } - circuit + ) } +} - pub fn get_num_instance(max_depth: usize) -> usize { - 5 + 2 * (max_depth + 1) +impl EthPreCircuit for EthBlockHeaderChainCircuit { + fn create( + self, + builder: RlcThreadBuilder, + break_points: Option, + ) -> EthCircuitBuilder> { + self.create(builder, break_points) } +} - #[cfg(feature = "providers")] - pub fn from_provider( - provider: &Provider, - network: Network, - start_block_number: u32, - num_blocks: u32, - max_depth: usize, - ) -> Self { - let header_rlp_max_bytes = match network { - Network::Mainnet => MAINNET_BLOCK_HEADER_RLP_MAX_BYTES, - Network::Goerli => GOERLI_BLOCK_HEADER_RLP_MAX_BYTES, - }; - let (mut block_rlps, _) = - crate::providers::get_blocks_input(provider, start_block_number, num_blocks, max_depth); - for block_rlp in block_rlps.iter_mut() { - block_rlp.resize(header_rlp_max_bytes, 0u8); - } - - Self { - header_rlp_encodings: block_rlps, - num_blocks, - max_depth, - network, - _marker: PhantomData, - } +pub fn get_block_header_rlp_max_lens(network: Network) -> (usize, &'static [usize]) { + match network { + Network::Mainnet => (MAINNET_BLOCK_HEADER_RLP_MAX_BYTES, &MAINNET_HEADER_FIELDS_MAX_BYTES), + Network::Goerli => (GOERLI_BLOCK_HEADER_RLP_MAX_BYTES, &GOERLI_HEADER_FIELDS_MAX_BYTES), } } diff --git a/axiom-eth/src/block_header/tests/chain_instance.rs b/axiom-eth/src/block_header/tests/chain_instance.rs new file mode 100644 index 000000000..0b00dc7b4 --- /dev/null +++ b/axiom-eth/src/block_header/tests/chain_instance.rs @@ -0,0 +1,83 @@ +use super::{ + util::{bytes_be_to_u128, encode_h256_to_field, EthConfigParams}, + Field, Network, +}; +use crate::{ + keccak::{FixedLenRLCs, FnSynthesize, KeccakChip, VarLenRLCs}, + rlp::{ + builder::{RlcThreadBreakPoints, RlcThreadBuilder}, + rlc::{RlcContextPair, RlcFixedTrace, RlcTrace, FIRST_PHASE, RLC_PHASE}, + RlpArrayTraceWitness, RlpChip, RlpFieldTrace, RlpFieldWitness, + }, + util::{bytes_be_var_to_fixed, decode_field_to_h256}, + EthChip, EthCircuitBuilder, ETH_LOOKUP_BITS, +}; +use core::{ + iter::{self, once}, + marker::PhantomData, +}; +use ethers_core::types::H256; +#[cfg(feature = "providers")] +use ethers_providers::{Http, Provider}; +use halo2_base::{ + gates::{builder::GateThreadBuilder, GateInstructions, RangeChip}, + utils::bit_length, + AssignedValue, Context, + QuantumCell::{Constant, Existing}, +}; +use itertools::Itertools; +use rayon::prelude::*; +use serde::{Deserialize, Serialize}; +use std::{cell::RefCell, env::var}; + +/* // No longer used, For testing only +#[derive(Serialize, Deserialize, Debug, Clone)] +struct EthBlockHeaderChainInstance { + pub prev_hash: H256, + pub end_hash: H256, + pub start_block_number: u32, + pub end_block_number: u32, + merkle_mountain_range: Vec, +} + +impl EthBlockHeaderChainInstance { + pub fn new( + prev_hash: H256, + end_hash: H256, + start_block_number: u32, + end_block_number: u32, + merkle_mountain_range: Vec, + ) -> Self { + Self { prev_hash, end_hash, start_block_number, end_block_number, merkle_mountain_range } + } + fn to_instance(&self) -> Vec { + // * prevHash: uint256 represented as 2 uint128s + // * endHash: uint256 represented as 2 uint128s + // * startBlockNumber || endBlockNumber: 0..0 || uint32 || 0..0 || uint32 as u64 (exactly 64 bits) + // * merkleRoots: Vec, each represented as 2 uint128s + let [prev_hash, end_hash] = + [&self.prev_hash, &self.end_hash].map(|hash| encode_h256_to_field::(hash)); + let block_numbers = + F::from(((self.start_block_number as u64) << 32) + (self.end_block_number as u64)); + let merkle_mountain_range = self + .merkle_mountain_range + .iter() + .flat_map(|hash| encode_h256_to_field::(hash)) + .collect_vec(); + + [&prev_hash[..], &end_hash[..], &[block_numbers], &merkle_mountain_range].concat() + } + + fn from_instance(instance: &[F]) -> Self { + let prev_hash = decode_field_to_h256(&instance[0..2]); + let end_hash = decode_field_to_h256(&instance[2..4]); + let block_numbers = instance[4].to_repr(); // little endian + let start_block_number = u32::from_le_bytes(block_numbers[4..8].try_into().unwrap()); + let end_block_number = u32::from_le_bytes(block_numbers[..4].try_into().unwrap()); + let merkle_mountain_range = + instance[5..].chunks(2).map(|chunk| decode_field_to_h256(chunk)).collect_vec(); + + Self::new(prev_hash, end_hash, start_block_number, end_block_number, merkle_mountain_range) + } +} +*/ diff --git a/src/block_header/tests.rs b/axiom-eth/src/block_header/tests/mod.rs similarity index 99% rename from src/block_header/tests.rs rename to axiom-eth/src/block_header/tests/mod.rs index b594e7b56..be09d16a4 100644 --- a/src/block_header/tests.rs +++ b/axiom-eth/src/block_header/tests/mod.rs @@ -1,6 +1,6 @@ use crate::{ keccak::SharedKeccakChip, - util::{EthConfigPinning, Halo2ConfigPinning}, + util::{EthConfigParams, EthConfigPinning, Halo2ConfigPinning}, }; use super::*; @@ -24,7 +24,11 @@ use halo2_base::{ }; use hex::FromHex; use rand_core::OsRng; -use std::{env::set_var, fs::File, marker::PhantomData}; +use std::{ + env::{set_var, var}, + fs::File, + marker::PhantomData, +}; use test_log::test; fn block_header_test_circuit( @@ -40,7 +44,7 @@ fn block_header_test_circuit( let chain_witness = chip.decompose_block_header_chain_phase0( &mut builder.gate_builder, &mut keccak.borrow_mut(), - &inputs, + inputs, network, ); diff --git a/axiom-eth/src/keccak/builder.rs b/axiom-eth/src/keccak/builder.rs new file mode 100644 index 000000000..3c102977c --- /dev/null +++ b/axiom-eth/src/keccak/builder.rs @@ -0,0 +1,299 @@ +use halo2_base::gates::builder::GateThreadBuilder; + +use crate::rlp::rlc::FIRST_PHASE; + +use super::*; + +/// We need a more custom synthesize function to work with the outputs of keccak RLCs. +pub trait FnSynthesize = + FnOnce(&mut RlcThreadBuilder, RlpChip, (FixedLenRLCs, VarLenRLCs)) + Clone; + +pub struct KeccakCircuitBuilder +where + FnPhase1: FnSynthesize, +{ + pub builder: RefCell>, + pub break_points: RefCell, + pub synthesize_phase1: RefCell>, + pub keccak: SharedKeccakChip, + pub range: RangeChip, +} + +impl KeccakCircuitBuilder +where + FnPhase1: FnSynthesize, +{ + pub fn new( + builder: RlcThreadBuilder, + keccak: SharedKeccakChip, + range: RangeChip, + break_points: Option, + synthesize_phase1: FnPhase1, + ) -> Self { + Self { + builder: RefCell::new(builder), + break_points: RefCell::new(break_points.unwrap_or_default()), + synthesize_phase1: RefCell::new(Some(synthesize_phase1)), + keccak, + range, + } + } + + /// Does a dry run of multi-phase synthesize to calculate optimal configuration parameters + /// + /// Beware: the `KECCAK_ROWS` is calculated based on the `minimum_rows = UNUSABLE_ROWS`, + /// however at configuration time the `minimum_rows` will depend on `KECCAK_ROWS`. + /// If you then reset `minimum_rows` to this smaller number, it might auto-configure + /// to a higher `KECCAK_ROWS`, which now requires higher `minimum_rows`... + pub fn config(&self, k: usize, minimum_rows: Option) -> EthConfigParams { + // clone everything so we don't alter the circuit in any way for later calls + let mut builder = self.builder.borrow().clone(); + let mut keccak = self.keccak.borrow().clone(); + let optimal_rows_per_round = + rows_per_round((1 << k) - minimum_rows.unwrap_or(0), keccak.capacity()); + // we don't want to actually call `keccak.assign_phase{0,1}` so we fake the output + let table_input_rlcs = vec![ + AssignedValue { + value: Assigned::Trivial(F::zero()), + cell: Some(ContextCell { context_id: KECCAK_CONTEXT_ID, offset: 0 }) + }; + keccak.capacity() + ]; + let table_output_rlcs = table_input_rlcs.clone(); + let rlc_chip = RlcChip::new(F::zero()); + let rlp_chip = RlpChip::new(&self.range, Some(&rlc_chip)); + let keccak_rlcs = keccak.process_phase1( + &mut builder, + &rlc_chip, + &self.range, + table_input_rlcs, + table_output_rlcs, + ); + let f = self.synthesize_phase1.borrow().clone().expect("synthesize_phase1 should exist"); + f(&mut builder, rlp_chip, keccak_rlcs); + let mut params = builder.config(k, minimum_rows); + params.keccak_rows_per_round = std::cmp::min(optimal_rows_per_round, 50); // empirically more than 50 rows per round makes the rotation offsets too large + self.keccak.borrow_mut().num_rows_per_round = params.keccak_rows_per_round; + #[cfg(feature = "display")] + log::info!("KeccakCircuitBuilder auto-calculated config params: {:#?}", params); + set_var("ETH_CONFIG_PARAMS", serde_json::to_string(¶ms).unwrap()); + set_var("KECCAK_DEGREE", k.to_string()); + set_var("KECCAK_ROWS", params.keccak_rows_per_round.to_string()); + + params + } + + // re-usable function for synthesize + pub fn two_phase_synthesize( + &self, + config: &MPTConfig, + layouter: &mut impl Layouter, + ) -> HashMap<(usize, usize), (circuit::Cell, usize)> { + config.rlp.range.load_lookup_table(layouter).expect("load range lookup table"); + config.keccak.load_aux_tables(layouter).expect("load keccak lookup tables"); + + let mut first_pass = SKIP_FIRST_PASS; + let witness_gen_only = self.builder.borrow().witness_gen_only(); + + let mut gamma = None; + if !witness_gen_only { + // in these cases, synthesize is called twice, and challenge can be gotten after the first time, or we use dummy value 0 + layouter.get_challenge(config.rlp.rlc.gamma).map(|gamma_| gamma = Some(gamma_)); + } + let mut assigned_advices = HashMap::new(); + + layouter + .assign_region( + || "KeccakCircuitBuilder generated circuit", + |mut region| { + if first_pass { + first_pass = false; + return Ok(()); + } + if !witness_gen_only { + let start = std::time::Instant::now(); + let mut builder = self.builder.borrow().clone(); + let mut keccak = self.keccak.borrow().clone(); + let f = self + .synthesize_phase1 + .borrow() + .clone() + .expect("synthesize_phase1 should exist"); + // tells zkevm keccak to assign its cells + let squeeze_digests = keccak.assign_phase0(&mut region, &config.keccak); + println!("{:?}", start.elapsed()); + // end of FirstPhase + let rlc_chip = RlcChip::new(gamma.unwrap_or_else(|| F::zero())); + // SecondPhase: tell zkevm keccak to assign RLC cells and upload them into `assigned_advices` + let (keccak_advices, table_input_rlcs, table_output_rlcs) = keccak + .assign_phase1( + &mut region, + &config.keccak.keccak_table, + squeeze_digests, + *rlc_chip.gamma(), + Some(HashMap::new()), + ); + // Constrain RLCs so keccak chip witnesses are actually correct + let keccak_rlcs = keccak.process_phase1( + &mut builder, + &rlc_chip, + &self.range, + table_input_rlcs, + table_output_rlcs, + ); + // Do any custom synthesize functions in SecondPhase + let mut assignments = KeygenAssignments { + assigned_advices: keccak_advices, + ..Default::default() + }; + let rlp_chip = RlpChip::new(&self.range, Some(&rlc_chip)); + f(&mut builder, rlp_chip, keccak_rlcs); + assignments = builder.assign_all( + &config.rlp.range.gate, + &config.rlp.range.lookup_advice, + &config.rlp.range.q_lookup, + &config.rlp.rlc, + &mut region, + assignments, + ); + *self.break_points.borrow_mut() = assignments.break_points; + assigned_advices = assignments.assigned_advices; + } else { + let builder = &mut self.builder.borrow_mut(); + let break_points = &mut self.break_points.borrow_mut(); + assign_prover_phase0( + &mut region, + &config.rlp.range.gate, + &config.rlp.range.lookup_advice, + builder, + break_points, + ); + let squeeze_digests = + self.keccak.borrow().assign_phase0(&mut region, &config.keccak); + // == END OF FIRST PHASE == + // this is a special backend API function (in halo2-axiom only) that computes the KZG commitments for all columns in FirstPhase and performs Fiat-Shamir on them to return the challenge value + #[cfg(feature = "halo2-axiom")] + region.next_phase(); + // == BEGIN SECOND PHASE == + // get challenge value + let mut gamma = None; + #[cfg(feature = "halo2-axiom")] + region.get_challenge(config.rlp.rlc.gamma).map(|gamma_| { + log::info!("gamma: {gamma_:?}"); + gamma = Some(gamma_); + }); + let rlc_chip = + RlcChip::new(gamma.expect("Could not get challenge in second phase")); + let (_, table_input_rlcs, table_output_rlcs) = + self.keccak.borrow().assign_phase1( + &mut region, + &config.keccak.keccak_table, + squeeze_digests, + *rlc_chip.gamma(), + None, + ); + // Constrain RLCs so keccak chip witnesses are actually correct + let keccak_rlcs = self.keccak.borrow_mut().process_phase1( + builder, + &rlc_chip, + &self.range, + table_input_rlcs, + table_output_rlcs, + ); + let f = RefCell::take(&self.synthesize_phase1) + .expect("synthesize_phase1 should exist"); // we `take` the closure during proving to avoid cloning captured variables (the captured variables would be the AssignedValue payload sent from FirstPhase to SecondPhase) + assign_prover_phase1( + &mut region, + &config.rlp.range.gate, + &config.rlp.range.lookup_advice, + &config.rlp.rlc, + &rlc_chip, + builder, + break_points, + |builder: &mut RlcThreadBuilder, rlc_chip: &RlcChip| { + let rlp_chip = RlpChip::new(&self.range, Some(rlc_chip)); + f(builder, rlp_chip, keccak_rlcs); + }, + ); + } + Ok(()) + }, + ) + .unwrap(); + assigned_advices + } +} + +impl> Circuit for KeccakCircuitBuilder { + type Config = MPTConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + unimplemented!() + } + + fn configure(meta: &mut ConstraintSystem) -> MPTConfig { + let params: EthConfigParams = + serde_json::from_str(&std::env::var("ETH_CONFIG_PARAMS").unwrap()).unwrap(); + MPTConfig::configure(meta, params) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + self.two_phase_synthesize(&config, &mut layouter); + Ok(()) + } +} + +// The following is a hack to allow parallelization of KeccakChip. Should refactor more generally in future. + +/// Trait for structs that contain references to keccak query indices. +/// Due to current [`KeccakChip`] implementation, these indices must be shifted +/// if multiple structs are created in parallel, to avoid race conditions on the circuit. +pub trait ContainsParallelizableKeccakQueries { + /// Shifts all fixed (resp. variable) length keccak query indices by `fixed_shift` (resp. `var_shift`). + /// This is necessary when joining parallel (multi-threaded) keccak chips into one. + fn shift_query_indices(&mut self, fixed_shift: usize, var_shift: usize); +} + +/// Utility function to parallelize an operation involving KeccakChip that outputs something referencing +/// keccak query indices. This should be done in FirstPhase. +/// +/// When `R` stores keccak queries by index, in a parallel setting, we need to shift +/// the indices at the end. +pub fn parallelize_keccak_phase0( + thread_pool: &mut GateThreadBuilder, + keccak: &mut KeccakChip, + input: Vec, + f: FR, +) -> Vec +where + F: Field, + T: Send, + R: ContainsParallelizableKeccakQueries + Send, + FR: Fn(&mut Context, &mut KeccakChip, T) -> R + Send + Sync, +{ + let witness_gen_only = thread_pool.witness_gen_only(); + let ctx_ids = input.iter().map(|_| thread_pool.get_new_thread_id()).collect_vec(); + let (mut trace, ctxs): (Vec<_>, Vec<_>) = input + .into_par_iter() + .zip(ctx_ids.into_par_iter()) + .map(|(input, ctx_id)| { + let mut ctx = Context::new(witness_gen_only, ctx_id); + let mut keccak = KeccakChip::default(); + let trace = f(&mut ctx, &mut keccak, input); + (trace, (ctx, keccak)) + }) + .unzip(); + // join gate contexts and keccak queries; need to shift keccak query indices because of the join + for (trace, (ctx, mut _keccak)) in trace.iter_mut().zip(ctxs.into_iter()) { + thread_pool.threads[FIRST_PHASE].push(ctx); + trace.shift_query_indices(keccak.fixed_len_queries.len(), keccak.var_len_queries.len()); + keccak.fixed_len_queries.append(&mut _keccak.fixed_len_queries); + keccak.var_len_queries.append(&mut _keccak.var_len_queries); + } + trace +} diff --git a/src/keccak/mod.rs b/axiom-eth/src/keccak/mod.rs similarity index 71% rename from src/keccak/mod.rs rename to axiom-eth/src/keccak/mod.rs index da97bb232..eeed51c81 100644 --- a/src/keccak/mod.rs +++ b/axiom-eth/src/keccak/mod.rs @@ -54,11 +54,13 @@ type KeccakAssignedValue<'v, F> = AssignedCell<&'v Assigned, F>; #[cfg(not(feature = "halo2-axiom"))] type KeccakAssignedValue<'v, F> = AssignedCell; +mod builder; #[cfg(test)] mod tests; -pub type FixedLenRLCs = Vec<(RlcFixedTrace, RlcFixedTrace)>; -pub type VarLenRLCs = Vec<(RlcTrace, RlcFixedTrace)>; +pub use builder::*; +pub(crate) type FixedLenRLCs = Vec<(RlcFixedTrace, RlcFixedTrace)>; +pub(crate) type VarLenRLCs = Vec<(RlcTrace, RlcFixedTrace)>; pub(crate) const KECCAK_CONTEXT_ID: usize = usize::MAX; @@ -154,7 +156,7 @@ impl KeccakChip { /// /// Constrains `min_len <= len <= bytes.len()`. /// - /// Returns output in bytes. + /// Returns the index in `self.var_len_queries` of the query. pub fn keccak_var_len( &mut self, ctx: &mut Context, @@ -198,22 +200,25 @@ impl KeccakChip { /// Computes the keccak merkle root of a tree with leaves `leaves`. /// - /// Assumptions: + /// Returns the merkle tree root as a byte array. + /// + /// # Assumptions /// - `leaves.len()` is a power of two. /// - Each element of `leaves` is a slice of assigned byte values. /// - The byte length of each element of `leaves` is known and fixed, i.e., we use `keccak_fixed_len` to perform the hashes. /// - /// Returns the merkle tree root as a byte array. + /// # Warning + /// - This implementation currently has no domain separation between hashing leaves versus hashing inner nodes pub fn merkle_tree_root( &mut self, ctx: &mut Context, gate: &impl GateInstructions, - leaves: &[Vec>], + leaves: &[impl AsRef<[AssignedValue]>], ) -> Vec> { let depth = leaves.len().ilog2() as usize; debug_assert_eq!(1 << depth, leaves.len()); if depth == 0 { - return leaves[0].to_vec(); + return leaves[0].as_ref().to_vec(); } // bottom layer hashes @@ -221,7 +226,7 @@ impl KeccakChip { .chunks(2) .into_iter() .map(|pair| { - let leaves_concat = [&pair[0][..], &pair[1][..]].concat(); + let leaves_concat = [pair[0].as_ref(), pair[1].as_ref()].concat(); self.keccak_fixed_len(ctx, gate, leaves_concat, None) }) .collect_vec(); @@ -632,245 +637,3 @@ pub(crate) fn rows_per_round(max_rows: usize, num_keccak_f: usize) -> usize { log::info!("Optimal keccak rows per round: {rows_per_round}"); rows_per_round } - -/// We need a more custom synthesize function to work with the outputs of keccak RLCs. -pub trait FnSynthesize = - FnOnce(&mut RlcThreadBuilder, RlpChip, (FixedLenRLCs, VarLenRLCs)) + Clone; - -pub struct KeccakCircuitBuilder -where - FnPhase1: FnSynthesize, -{ - pub builder: RefCell>, - pub break_points: RefCell, - pub synthesize_phase1: RefCell>, - pub keccak: SharedKeccakChip, - pub range: RangeChip, -} - -impl KeccakCircuitBuilder -where - FnPhase1: FnSynthesize, -{ - pub fn new( - builder: RlcThreadBuilder, - keccak: SharedKeccakChip, - range: RangeChip, - break_points: Option, - synthesize_phase1: FnPhase1, - ) -> Self { - Self { - builder: RefCell::new(builder), - break_points: RefCell::new(break_points.unwrap_or_default()), - synthesize_phase1: RefCell::new(Some(synthesize_phase1)), - keccak, - range, - } - } - - /// Does a dry run of multi-phase synthesize to calculate optimal configuration parameters - /// - /// Beware: the `KECCAK_ROWS` is calculated based on the `minimum_rows = UNUSABLE_ROWS`, - /// however at configuration time the `minimum_rows` will depend on `KECCAK_ROWS`. - /// If you then reset `minimum_rows` to this smaller number, it might auto-configure - /// to a higher `KECCAK_ROWS`, which now requires higher `minimum_rows`... - pub fn config(&self, k: usize, minimum_rows: Option) -> EthConfigParams { - // clone everything so we don't alter the circuit in any way for later calls - let mut builder = self.builder.borrow().clone(); - let mut keccak = self.keccak.borrow().clone(); - let optimal_rows_per_round = - rows_per_round((1 << k) - minimum_rows.unwrap_or(0), keccak.capacity()); - // we don't want to actually call `keccak.assign_phase{0,1}` so we fake the output - let table_input_rlcs = vec![ - AssignedValue { - value: Assigned::Trivial(F::zero()), - cell: Some(ContextCell { context_id: KECCAK_CONTEXT_ID, offset: 0 }) - }; - keccak.capacity() - ]; - let table_output_rlcs = table_input_rlcs.clone(); - let rlc_chip = RlcChip::new(F::zero()); - let rlp_chip = RlpChip::new(&self.range, Some(&rlc_chip)); - let keccak_rlcs = keccak.process_phase1( - &mut builder, - &rlc_chip, - &self.range, - table_input_rlcs, - table_output_rlcs, - ); - let f = self.synthesize_phase1.borrow().clone().expect("synthesize_phase1 should exist"); - f(&mut builder, rlp_chip, keccak_rlcs); - let mut params = builder.config(k, minimum_rows); - params.keccak_rows_per_round = std::cmp::min(optimal_rows_per_round, 50); // empirically more than 50 rows per round makes the rotation offsets too large - self.keccak.borrow_mut().num_rows_per_round = params.keccak_rows_per_round; - #[cfg(feature = "display")] - log::info!("KeccakCircuitBuilder auto-calculated config params: {:#?}", params); - set_var("ETH_CONFIG_PARAMS", serde_json::to_string(¶ms).unwrap()); - set_var("KECCAK_DEGREE", k.to_string()); - set_var("KECCAK_ROWS", params.keccak_rows_per_round.to_string()); - - params - } - - // re-usable function for synthesize - pub fn two_phase_synthesize( - &self, - config: &MPTConfig, - layouter: &mut impl Layouter, - ) -> HashMap<(usize, usize), (circuit::Cell, usize)> { - config.rlp.range.load_lookup_table(layouter).expect("load range lookup table"); - config.keccak.load_aux_tables(layouter).expect("load keccak lookup tables"); - - let mut first_pass = SKIP_FIRST_PASS; - let witness_gen_only = self.builder.borrow().witness_gen_only(); - - let mut gamma = None; - if !witness_gen_only { - // in these cases, synthesize is called twice, and challenge can be gotten after the first time, or we use dummy value 0 - layouter.get_challenge(config.rlp.rlc.gamma).map(|gamma_| gamma = Some(gamma_)); - } - let mut assigned_advices = HashMap::new(); - - layouter - .assign_region( - || "KeccakCircuitBuilder generated circuit", - |mut region| { - if first_pass { - first_pass = false; - return Ok(()); - } - if !witness_gen_only { - let mut builder = self.builder.borrow().clone(); - let mut keccak = self.keccak.borrow().clone(); - let f = self - .synthesize_phase1 - .borrow() - .clone() - .expect("synthesize_phase1 should exist"); - // tells zkevm keccak to assign its cells - let squeeze_digests = keccak.assign_phase0(&mut region, &config.keccak); - // end of FirstPhase - let rlc_chip = RlcChip::new(gamma.unwrap_or_else(|| F::zero())); - // SecondPhase: tell zkevm keccak to assign RLC cells and upload them into `assigned_advices` - let (keccak_advices, table_input_rlcs, table_output_rlcs) = keccak - .assign_phase1( - &mut region, - &config.keccak.keccak_table, - squeeze_digests, - *rlc_chip.gamma(), - Some(HashMap::new()), - ); - // Constrain RLCs so keccak chip witnesses are actually correct - let keccak_rlcs = keccak.process_phase1( - &mut builder, - &rlc_chip, - &self.range, - table_input_rlcs, - table_output_rlcs, - ); - // Do any custom synthesize functions in SecondPhase - let mut assignments = KeygenAssignments { - assigned_advices: keccak_advices, - ..Default::default() - }; - let rlp_chip = RlpChip::new(&self.range, Some(&rlc_chip)); - f(&mut builder, rlp_chip, keccak_rlcs); - assignments = builder.assign_all( - &config.rlp.range.gate, - &config.rlp.range.lookup_advice, - &config.rlp.range.q_lookup, - &config.rlp.rlc, - &mut region, - assignments, - ); - *self.break_points.borrow_mut() = assignments.break_points; - assigned_advices = assignments.assigned_advices; - } else { - let builder = &mut self.builder.borrow_mut(); - let break_points = &mut self.break_points.borrow_mut(); - assign_prover_phase0( - &mut region, - &config.rlp.range.gate, - &config.rlp.range.lookup_advice, - builder, - break_points, - ); - let squeeze_digests = - self.keccak.borrow().assign_phase0(&mut region, &config.keccak); - // == END OF FIRST PHASE == - // this is a special backend API function (in halo2-axiom only) that computes the KZG commitments for all columns in FirstPhase and performs Fiat-Shamir on them to return the challenge value - #[cfg(feature = "halo2-axiom")] - region.next_phase(); - // == BEGIN SECOND PHASE == - // get challenge value - let mut gamma = None; - #[cfg(feature = "halo2-axiom")] - region.get_challenge(config.rlp.rlc.gamma).map(|gamma_| { - log::info!("gamma: {gamma_:?}"); - gamma = Some(gamma_); - }); - let rlc_chip = - RlcChip::new(gamma.expect("Could not get challenge in second phase")); - let (_, table_input_rlcs, table_output_rlcs) = - self.keccak.borrow().assign_phase1( - &mut region, - &config.keccak.keccak_table, - squeeze_digests, - *rlc_chip.gamma(), - None, - ); - // Constrain RLCs so keccak chip witnesses are actually correct - let keccak_rlcs = self.keccak.borrow_mut().process_phase1( - builder, - &rlc_chip, - &self.range, - table_input_rlcs, - table_output_rlcs, - ); - let f = RefCell::take(&self.synthesize_phase1) - .expect("synthesize_phase1 should exist"); // we `take` the closure during proving to avoid cloning captured variables (the captured variables would be the AssignedValue payload sent from FirstPhase to SecondPhase) - assign_prover_phase1( - &mut region, - &config.rlp.range.gate, - &config.rlp.range.lookup_advice, - &config.rlp.rlc, - &rlc_chip, - builder, - break_points, - |builder: &mut RlcThreadBuilder, rlc_chip: &RlcChip| { - let rlp_chip = RlpChip::new(&self.range, Some(rlc_chip)); - f(builder, rlp_chip, keccak_rlcs); - }, - ); - } - Ok(()) - }, - ) - .unwrap(); - assigned_advices - } -} - -impl> Circuit for KeccakCircuitBuilder { - type Config = MPTConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - unimplemented!() - } - - fn configure(meta: &mut ConstraintSystem) -> MPTConfig { - let params: EthConfigParams = - serde_json::from_str(&std::env::var("ETH_CONFIG_PARAMS").unwrap()).unwrap(); - MPTConfig::configure(meta, params) - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - self.two_phase_synthesize(&config, &mut layouter); - Ok(()) - } -} diff --git a/src/keccak/tests.rs b/axiom-eth/src/keccak/tests.rs similarity index 98% rename from src/keccak/tests.rs rename to axiom-eth/src/keccak/tests.rs index 3c4ad94a6..8a88421a8 100644 --- a/src/keccak/tests.rs +++ b/axiom-eth/src/keccak/tests.rs @@ -53,7 +53,8 @@ fn test_keccak_circuit( let bytes = input.to_vec(); let mut bytes_assigned = ctx.assign_witnesses(bytes.iter().map(|byte| F::from(*byte as u64))); - let len = if var_len { rng.gen_range(0..bytes.len()) } else { bytes.len() }; + let len = + if var_len && !bytes.is_empty() { rng.gen_range(0..bytes.len()) } else { bytes.len() }; for byte in bytes_assigned[len..].iter_mut() { *byte = ctx.load_zero(); } diff --git a/src/lib.rs b/axiom-eth/src/lib.rs similarity index 64% rename from src/lib.rs rename to axiom-eth/src/lib.rs index cae7d01cf..fe268b62c 100644 --- a/src/lib.rs +++ b/axiom-eth/src/lib.rs @@ -1,41 +1,56 @@ +#![feature(array_zip)] #![feature(int_log)] #![feature(trait_alias)] #![feature(return_position_impl_trait_in_trait)] #![allow(incomplete_features)] +#![warn(clippy::useless_conversion)] -pub mod block_header; -pub mod keccak; -pub mod mpt; -pub mod rlp; -pub mod storage; -pub mod util; - -#[cfg(feature = "providers")] -pub mod providers; +use std::env::{set_var, var}; -use crate::rlp::{ - builder::{RlcThreadBreakPoints, RlcThreadBuilder}, - rlc::RlcConfig, - RlpConfig, -}; +#[cfg(feature = "display")] +use ark_std::{end_timer, start_timer}; use halo2_base::{ - gates::{flex_gate::FlexGateConfig, range::RangeConfig, RangeChip}, + gates::{ + builder::{CircuitBuilderStage, MultiPhaseThreadBreakPoints}, + flex_gate::FlexGateConfig, + range::RangeConfig, + RangeChip, + }, halo2_proofs::{ self, circuit::{Layouter, SimpleFloorPlanner}, + halo2curves::bn256::{Bn256, Fr}, plonk::{Circuit, Column, ConstraintSystem, Error, Instance}, + poly::{commitment::Params, kzg::commitment::ParamsKZG}, }, AssignedValue, }; -use keccak::{FnSynthesize, KeccakCircuitBuilder, SharedKeccakChip}; pub use mpt::EthChip; use serde::{Deserialize, Serialize}; -use std::env::{set_var, var}; -use util::EthConfigParams; +use snark_verifier_sdk::halo2::aggregation::AggregationCircuit; pub use zkevm_keccak::util::eth_types::Field; use zkevm_keccak::KeccakConfig; -pub const ETH_LOOKUP_BITS: usize = 8; // always want 8 to range check bytes +use crate::rlp::{ + builder::{RlcThreadBreakPoints, RlcThreadBuilder}, + rlc::RlcConfig, + RlpConfig, +}; +use keccak::{FnSynthesize, KeccakCircuitBuilder, SharedKeccakChip}; +use util::EthConfigParams; + +pub mod batch_query; +pub mod block_header; +pub mod keccak; +pub mod mpt; +pub mod rlp; +pub mod storage; +pub mod util; + +#[cfg(feature = "providers")] +pub mod providers; + +pub(crate) const ETH_LOOKUP_BITS: usize = 8; // always want 8 to range check bytes #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] @@ -213,3 +228,76 @@ impl> snark_verifier_sdk::CircuitExt vec![self.instance()] } } + +/// Trait for objects that can be used to create an [`EthCircuitBuilder`] instantiation. +pub trait EthPreCircuit: Sized { + /// Creates a circuit without auto-configuring it. + fn create( + self, + builder: RlcThreadBuilder, + break_points: Option, + ) -> EthCircuitBuilder>; + + /// If feature 'production' is on, this is the same as `create`. Otherwise, it will read `ETH_CONFIG_PARAMS` + /// from the environment to determine the desired circuit degree and number of unusable rows and then auto-configure + /// the circuit and set environmental variables. + fn create_circuit( + self, + builder: RlcThreadBuilder, + break_points: Option, + ) -> EthCircuitBuilder> { + let prover = builder.witness_gen_only(); + #[cfg(feature = "display")] + let start = start_timer!(|| "EthPreCircuit: create_circuit"); + let circuit = self.create(builder, break_points); + #[cfg(feature = "display")] + end_timer!(start); + #[cfg(not(feature = "production"))] + if !prover { + let config_params: EthConfigParams = serde_json::from_str( + var("ETH_CONFIG_PARAMS").expect("ETH_CONFIG_PARAMS is not set").as_str(), + ) + .unwrap(); + circuit.config(config_params.degree as usize, Some(config_params.unusable_rows)); + } + circuit + } +} + +/// Trait for objects that can be used to create a [`RangeWithInstanceCircuitBuilder`] instantiation. +pub trait AggregationPreCircuit: Sized { + /// Creates a circuit without auto-configuring it. + /// + /// `params` should be the universal trusted setup for the present aggregation circuit. + /// We assume the trusted setup for the previous SNARKs is compatible with `params` in the sense that + /// the generator point and toxic waste `tau` are the same. + fn create( + self, + stage: CircuitBuilderStage, + break_points: Option, + lookup_bits: usize, + params: &ParamsKZG, + ) -> AggregationCircuit; + + /// If feature 'production' is on, this is the same as `create`. Otherwise, it will determine the desired + /// circuit degree from `params.k()` and auto-configure the circuit and set environmental variables. + fn create_circuit( + self, + stage: CircuitBuilderStage, + break_points: Option, + lookup_bits: usize, + params: &ParamsKZG, + ) -> AggregationCircuit { + #[cfg(feature = "display")] + let start = start_timer!(|| "AggregationPreCircuit: create_circuit"); + let circuit = self.create(stage, break_points, lookup_bits, params); + #[cfg(feature = "display")] + end_timer!(start); + #[cfg(not(feature = "production"))] + if stage != CircuitBuilderStage::Prover { + let minimum_rows = var("UNUSABLE_ROWS").map(|s| s.parse().unwrap_or(10)).unwrap_or(10); + circuit.config(params.k(), Some(minimum_rows)); + } + circuit + } +} diff --git a/src/mpt/mod.rs b/axiom-eth/src/mpt/mod.rs similarity index 97% rename from src/mpt/mod.rs rename to axiom-eth/src/mpt/mod.rs index 4db03f962..0c9dc4670 100644 --- a/src/mpt/mod.rs +++ b/axiom-eth/src/mpt/mod.rs @@ -2,12 +2,12 @@ //! //! See https://hackmd.io/@axiom/ry35GZ4l3 for a technical walkthrough of circuit structure and logic use crate::{ - keccak::{self, KeccakChip}, + keccak::{self, ContainsParallelizableKeccakQueries, KeccakChip}, rlp::{ max_rlp_len_len, rlc::{ - rlc_constrain_equal, rlc_is_equal, rlc_select, rlc_select_by_indicator, - rlc_select_from_idx, RlcContextPair, RlcTrace, RlcVar, + rlc_is_equal, rlc_select, rlc_select_by_indicator, rlc_select_from_idx, RlcContextPair, + RlcTrace, RlcVar, }, RlpChip, RlpFieldTrace, }, @@ -24,6 +24,7 @@ use halo2_base::{ use itertools::Itertools; use lazy_static::lazy_static; use rlp::Rlp; +use serde::{Deserialize, Serialize}; use std::{ cmp::max, iter::{self, once}, @@ -197,12 +198,9 @@ pub struct MPTFixedKeyProofWitness { pub key_hexs: AssignedNibbles, } -impl MPTFixedKeyProofWitness { - /// Shifts all keccak query indices by `shift`. This is necessary when - /// joining parallel (multi-threaded) keccak chips into one. - /// - /// Currently all indices are with respect to `keccak.var_len_queries` (see [`EthChip::mpt_hash_phase0`]). - pub fn shift_query_indices(&mut self, shift: usize) { +impl ContainsParallelizableKeccakQueries for MPTFixedKeyProofWitness { + // Currently all indices are with respect to `keccak.var_len_queries` (see `EthChip::mpt_hash_phase0`). + fn shift_query_indices(&mut self, _: usize, shift: usize) { self.leaf_parsed.hash_query_idx += shift; for node in self.nodes.iter_mut() { node.ext.hash_query_idx += shift; @@ -211,7 +209,7 @@ impl MPTFixedKeyProofWitness { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] /// The pre-assigned inputs for the MPT fixed key proof pub struct MPTFixedKeyInput { // claim specification: (path, value) @@ -434,7 +432,7 @@ impl<'chip, F: Field> EthChip<'chip, F> { let (max_field_bytes, max_ext_bytes) = max_ext_lens(max_key_bytes); let max_branch_bytes = MAX_BRANCH_LENS.1; let max_ext_bytes = max(max_ext_bytes, max_branch_bytes); - debug_assert_eq!(ext_bytes.len(), max_ext_bytes); + assert_eq!(ext_bytes.len(), max_ext_bytes); let rlp_witness = self.rlp.decompose_rlp_array_phase0(ctx, ext_bytes, &max_field_bytes, false); @@ -571,7 +569,7 @@ impl<'chip, F: Field> EthChip<'chip, F> { .nodes .into_iter() .map(|node| { - debug_assert_eq!(node.rlp_bytes.len(), node_max_byte_len); + assert_eq!(node.rlp_bytes.len(), node_max_byte_len); let (ext_in, branch_in): (AssignedBytes, AssignedBytes) = node .rlp_bytes .iter() @@ -599,7 +597,7 @@ impl<'chip, F: Field> EthChip<'chip, F> { // assert to avoid capacity checks? assert_eq!(proof.key_frag.len(), max_depth); for (idx, key_frag) in proof.key_frag.iter().enumerate() { - debug_assert_eq!(key_frag.nibbles.len(), 2 * key_byte_len); + assert_eq!(key_frag.nibbles.len(), 2 * key_byte_len); let leaf_path_bytes = hex_prefix_encode( ctx, self.gate(), @@ -812,8 +810,11 @@ impl<'chip, F: Field> EthChip<'chip, F> { witness.value_bytes, witness.value_byte_len, ); - // value doesn't matter if slot is empty; by default we will make value = 0 and leaf.value = 0 in that case - rlc_constrain_equal(ctx_gate, &value_rlc_trace, &leaf_parsed.value.field_trace); + // value doesn't matter if slot is empty; by default we will make leaf.value = 0 in that case + let value_equals_leaf = + rlc_is_equal(ctx_gate, self.gate(), value_rlc_trace, leaf_parsed.value.field_trace); + let value_check = self.gate().or(ctx_gate, value_equals_leaf, slot_is_empty); + self.gate().assert_is_const(ctx_gate, &value_check, &F::one()); } // if proof_is_empty, then root_hash = keccak(rlp("")) = keccak(0x80) = 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 @@ -957,6 +958,8 @@ impl<'chip, F: Field> EthChip<'chip, F> { */ } +/// # Assumptions +/// * `is_odd` is either 0 or 1 pub fn hex_prefix_encode_first( ctx: &mut Context, gate: &impl GateInstructions, @@ -992,6 +995,8 @@ pub fn hex_prefix_encode_first( } } +/// # Assumptions +/// * `is_odd` is either 0 or 1 pub fn hex_prefix_encode( ctx: &mut Context, gate: &impl GateInstructions, @@ -1124,7 +1129,7 @@ impl MPTFixedKeyInput { process_node(&leaf); leaf.resize(max_leaf_bytes, 0); - // if slot_is_empty, we make modify key_frag so it still concatenates to `path` + // if slot_is_empty, we modify key_frag so it still concatenates to `path` if slot_is_empty { // remove just added leaf frag key_frag.pop().unwrap(); diff --git a/src/mpt/tests.rs b/axiom-eth/src/mpt/tests.rs similarity index 96% rename from src/mpt/tests.rs rename to axiom-eth/src/mpt/tests.rs index c6f3d91c7..1a7ee4202 100644 --- a/src/mpt/tests.rs +++ b/axiom-eth/src/mpt/tests.rs @@ -150,15 +150,6 @@ pub fn test_mpt_noninclusion_extension_fixed() { MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied(); } -#[test] -pub fn test_mpt_empty_root() { - let params = EthConfigParams::from_path("configs/tests/mpt.json"); - let k = params.degree; - let input = mpt_input("scripts/input_gen/empty_storage_pf.json", true, 5); - let circuit = test_mpt_circuit(k, RlcThreadBuilder::::mock(), input); - MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied(); -} - #[test] fn bench_mpt_inclusion_fixed() -> Result<(), Box> { let bench_params_file = File::open("configs/bench/mpt.json").unwrap(); diff --git a/axiom-eth/src/providers.rs b/axiom-eth/src/providers.rs new file mode 100644 index 000000000..41397d20f --- /dev/null +++ b/axiom-eth/src/providers.rs @@ -0,0 +1,428 @@ +use crate::{ + batch_query::response::native::{FullStorageQuery, FullStorageResponse}, + mpt::MPTFixedKeyInput, + storage::{ + EthBlockStorageInput, EthStorageInput, ACCOUNT_PROOF_VALUE_MAX_BYTE_LEN, + ACCOUNT_STATE_FIELDS_MAX_BYTES, STORAGE_PROOF_VALUE_MAX_BYTE_LEN, + }, +}; +use ethers_core::types::{Address, Block, Bytes, EIP1186ProofResponse, H256}; +use ethers_core::utils::keccak256; +use ethers_providers::{JsonRpcClient, Middleware, Provider, ProviderError}; +// use halo2_mpt::mpt::{max_branch_lens, max_leaf_lens}; +use futures::future::{join, join_all}; +use rlp::{Encodable, Rlp, RlpStream}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use std::{ + fs::{self, File}, + path::PathBuf, +}; +use tokio::runtime::Runtime; + +pub const MAINNET_PROVIDER_URL: &str = "https://mainnet.infura.io/v3/"; +pub const GOERLI_PROVIDER_URL: &str = "https://goerli.infura.io/v3/"; + +/// Makes concurrent JSON-RPC calls to get the blocks with the given block numbers. +pub fn get_blocks( + provider: &Provider

, + block_numbers: impl IntoIterator, +) -> Result>>, ProviderError> { + let rt = Runtime::new().unwrap(); + rt.block_on(join_all( + block_numbers.into_iter().map(|block_number| provider.get_block(block_number)), + )) + .into_iter() + .collect() +} + +async fn get_account_query( + provider: &Provider

, + block_number: u64, + addr: Address, + acct_pf_max_depth: usize, +) -> EthStorageInput { + let block = provider.get_block(block_number).await.unwrap().unwrap(); + let pf = provider.get_proof(addr, vec![], Some(block_number.into())).await.unwrap(); + + let acct_key = H256(keccak256(addr)); + let slot_is_empty = !is_assigned_slot(&acct_key, &pf.account_proof); + EthStorageInput { + addr, + acct_state: get_acct_list(&pf), + acct_pf: MPTFixedKeyInput { + path: acct_key, + value: get_acct_rlp(&pf), + root_hash: block.state_root, + proof: pf.account_proof.into_iter().map(|x| x.to_vec()).collect(), + value_max_byte_len: ACCOUNT_PROOF_VALUE_MAX_BYTE_LEN, + max_depth: acct_pf_max_depth, + slot_is_empty, + }, + storage_pfs: vec![], + } +} + +pub fn get_account_queries( + provider: &Provider

, + queries: Vec<(u64, Address)>, + acct_pf_max_depth: usize, +) -> Vec { + let rt = Runtime::new().unwrap(); + rt.block_on(join_all(queries.into_iter().map(|(block_number, addr)| { + get_account_query(provider, block_number, addr, acct_pf_max_depth) + }))) +} + +/// Does not provide state root +async fn get_storage_query( + provider: &Provider

, + block_number: u64, + addr: Address, + slots: Vec, + acct_pf_max_depth: usize, + storage_pf_max_depth: usize, +) -> EthStorageInput { + let pf = provider.get_proof(addr, slots, Some(block_number.into())).await.unwrap(); + + let acct_key = H256(keccak256(addr)); + let slot_is_empty = !is_assigned_slot(&acct_key, &pf.account_proof); + log::info!("block: {block_number}, address: {addr}, account is empty: {slot_is_empty}"); + let acct_state = get_acct_list(&pf); + let acct_pf = MPTFixedKeyInput { + path: acct_key, + value: get_acct_rlp(&pf), + root_hash: H256([0u8; 32]), + proof: pf.account_proof.into_iter().map(|x| x.to_vec()).collect(), + value_max_byte_len: ACCOUNT_PROOF_VALUE_MAX_BYTE_LEN, + max_depth: acct_pf_max_depth, + slot_is_empty, + }; + let storage_pfs = pf + .storage_proof + .into_iter() + .map(|storage_pf| { + let path = H256(keccak256(storage_pf.key)); + let slot_is_empty = !is_assigned_slot(&path, &storage_pf.proof); + log::info!("block: {block_number}, address: {addr}, slot: {}, storage slot is empty: {slot_is_empty}", storage_pf.key); + let value = storage_pf.value.rlp_bytes().to_vec(); + ( + storage_pf.key, + storage_pf.value, + MPTFixedKeyInput { + path, + value, + root_hash: pf.storage_hash, + proof: storage_pf.proof.into_iter().map(|x| x.to_vec()).collect(), + value_max_byte_len: STORAGE_PROOF_VALUE_MAX_BYTE_LEN, + max_depth: storage_pf_max_depth, + slot_is_empty, + }, + ) + }) + .collect(); + EthStorageInput { addr, acct_state, acct_pf, storage_pfs } +} + +pub fn get_full_storage_queries( + provider: &Provider

, + queries: Vec, + acct_pf_max_depth: usize, + storage_pf_max_depth: usize, +) -> Result, String> { + let block_futures = + join_all(queries.iter().map(|query| provider.get_block(query.block_number))); + let storage_futures = + queries.into_iter().map(|FullStorageQuery { block_number, addr_slots }| { + let res = addr_slots.map(|(addr, slots)| { + get_storage_query( + provider, + block_number, + addr, + slots, + acct_pf_max_depth, + storage_pf_max_depth, + ) + }); + async { + match res { + Some(res) => Some(res.await), + None => None, + } + } + }); + let storage_futures = join_all(storage_futures); + let (blocks, pfs) = + Runtime::new().unwrap().block_on(async { join(block_futures, storage_futures).await }); + blocks + .into_iter() + .zip(pfs.into_iter()) + .map(|(block, mut pf)| { + block + .map_err(|e| format!("get_block JSON-RPC call failed: {e}")) + .and_then(|block| block.ok_or_else(|| "Block not found".to_string())) + .map(|block| { + if let Some(pf) = pf.as_mut() { + pf.acct_pf.root_hash = block.state_root; + } + FullStorageResponse { block, account_storage: pf } + }) + }) + .collect::, _>>() +} + +pub fn get_storage_queries( + provider: &Provider

, + queries: Vec<(u64, Address, H256)>, + acct_pf_max_depth: usize, + storage_pf_max_depth: usize, +) -> Vec { + let rt = Runtime::new().unwrap(); + rt.block_on(join_all(queries.into_iter().map(|(block_number, addr, slot)| { + get_storage_query( + provider, + block_number, + addr, + vec![slot], + acct_pf_max_depth, + storage_pf_max_depth, + ) + }))) +} + +pub fn get_block_storage_input( + provider: &Provider

, + block_number: u32, + addr: Address, + slots: Vec, + acct_pf_max_depth: usize, + storage_pf_max_depth: usize, +) -> EthBlockStorageInput { + let rt = Runtime::new().unwrap(); + let block = rt + .block_on(provider.get_block(block_number as u64)) + .unwrap() + .unwrap_or_else(|| panic!("Block {block_number} not found")); + let block_hash = block.hash.unwrap(); + let block_header = get_block_rlp(&block); + + let mut storage = rt.block_on(get_storage_query( + provider, + block_number as u64, + addr, + slots, + acct_pf_max_depth, + storage_pf_max_depth, + )); + storage.acct_pf.root_hash = block.state_root; + + EthBlockStorageInput { block, block_number, block_hash, block_header, storage } +} + +pub fn is_assigned_slot(key: &H256, proof: &[Bytes]) -> bool { + let mut key_nibbles = Vec::new(); + for &byte in key.as_bytes() { + key_nibbles.push(byte / 16); + key_nibbles.push(byte % 16); + } + let mut key_frags = Vec::new(); + let mut path_idx = 0; + for node in proof.iter() { + let rlp = Rlp::new(node); + if rlp.item_count().unwrap() == 2 { + let path = rlp.at(0).unwrap().data().unwrap(); + let is_odd = (path[0] / 16 == 1u8) || (path[0] / 16 == 3u8); + let mut frag = Vec::new(); + if is_odd { + frag.push(path[0] % 16); + path_idx += 1; + } + for byte in path.iter().skip(1) { + frag.push(*byte / 16); + frag.push(*byte % 16); + path_idx += 2; + } + key_frags.extend(frag); + } else { + key_frags.extend(vec![key_nibbles[path_idx]]); + path_idx += 1; + } + } + if path_idx == 64 { + for idx in 0..64 { + if key_nibbles[idx] != key_frags[idx] { + return false; + } + } + } else { + return false; + } + true +} + +pub fn get_acct_rlp(pf: &EIP1186ProofResponse) -> Vec { + let mut rlp: RlpStream = RlpStream::new_list(4); + rlp.append(&pf.nonce); + rlp.append(&pf.balance); + rlp.append(&pf.storage_hash); + rlp.append(&pf.code_hash); + rlp.out().into() +} + +/// Format AccountState into list of fixed-length byte arrays +pub fn get_acct_list(pf: &EIP1186ProofResponse) -> Vec> { + let mut nonce_bytes = vec![0u8; 8]; + pf.nonce.to_big_endian(&mut nonce_bytes); + let mut balance_bytes = [0u8; 32]; + pf.balance.to_big_endian(&mut balance_bytes); + let balance_bytes = balance_bytes[32 - ACCOUNT_STATE_FIELDS_MAX_BYTES[1]..].to_vec(); + let storage_root = pf.storage_hash.as_bytes().to_vec(); + let code_hash = pf.code_hash.as_bytes().to_vec(); + vec![nonce_bytes, balance_bytes, storage_root, code_hash] +} + +pub fn get_block_rlp(block: &Block) -> Vec { + let withdrawals_root: Option = block.withdrawals_root; + let base_fee = block.base_fee_per_gas; + let rlp_len = 15 + usize::from(base_fee.is_some()) + usize::from(withdrawals_root.is_some()); + let mut rlp = RlpStream::new_list(rlp_len); + rlp.append(&block.parent_hash); + rlp.append(&block.uncles_hash); + rlp.append(&block.author.unwrap()); + rlp.append(&block.state_root); + rlp.append(&block.transactions_root); + rlp.append(&block.receipts_root); + rlp.append(&block.logs_bloom.unwrap()); + rlp.append(&block.difficulty); + rlp.append(&block.number.unwrap()); + rlp.append(&block.gas_limit); + rlp.append(&block.gas_used); + rlp.append(&block.timestamp); + rlp.append(&block.extra_data.to_vec()); + rlp.append(&block.mix_hash.unwrap()); + rlp.append(&block.nonce.unwrap()); + base_fee.map(|base_fee| rlp.append(&base_fee)); + withdrawals_root.map(|withdrawals_root| rlp.append(&withdrawals_root)); + let encoding: Vec = rlp.out().into(); + assert_eq!(keccak256(&encoding), block.hash.unwrap().0); + encoding +} + +serde_with::serde_conv!( + BytesBase64, + Vec, + |bytes: &Vec| { + use base64::{engine::general_purpose, Engine as _}; + general_purpose::STANDARD.encode(bytes) + }, + |encoded: String| { + use base64::{engine::general_purpose, Engine as _}; + general_purpose::STANDARD.decode(encoded) + } +); + +#[serde_as] +#[derive(Debug, Serialize, Deserialize)] +pub struct ProcessedBlock { + #[serde_as(as = "Vec")] + pub block_rlps: Vec>, + pub block_hashes: Vec, + pub prev_hash: H256, +} + +/// returns tuple of: +/// * vector of RLP bytes of each block +/// * tuple of +/// * parentHash (H256) +/// * endHash (H256) +/// * startBlockNumber (u32) +/// * endBlockNumber (u32) +/// * merkleRoots (Vec) +/// * where merkleRoots is a length `max_depth + 1` vector representing a merkle mountain range, ordered largest mountain first +// second tuple `instance` is only used for debugging now +pub fn get_blocks_input( + provider: &Provider

, + start_block_number: u32, + num_blocks: u32, + max_depth: usize, +) -> Vec> { + assert!(num_blocks <= (1 << max_depth)); + assert!(num_blocks > 0); + let chain_data_dir = PathBuf::from("data/chain"); + fs::create_dir_all(&chain_data_dir).unwrap(); + let end_block_number = start_block_number + num_blocks - 1; + let rt = Runtime::new().unwrap(); + let chain_id = rt.block_on(provider.get_chainid()).unwrap(); + let path = chain_data_dir + .join(format!("chainid{chain_id}_{start_block_number:06x}_{end_block_number:06x}.json")); + // block_hashes and prev_hash no longer used, but keeping this format for compatibility with old cached chaindata + let ProcessedBlock { mut block_rlps, block_hashes: _, prev_hash: _ } = + if let Ok(f) = File::open(&path) { + serde_json::from_reader(f).unwrap() + } else { + let blocks = get_blocks( + provider, + start_block_number as u64..(start_block_number + num_blocks) as u64, + ) + .unwrap_or_else(|e| panic!("get_blocks JSON-RPC call failed: {e}")); + let prev_hash = blocks[0].as_ref().expect("block not found").parent_hash; + let (block_rlps, block_hashes): (Vec<_>, Vec<_>) = blocks + .into_iter() + .map(|block| { + let block = block.expect("block not found"); + (get_block_rlp(&block), block.hash.unwrap()) + }) + .unzip(); + // write this to file + let file = File::create(&path).unwrap(); + let payload = ProcessedBlock { block_rlps, block_hashes, prev_hash }; + serde_json::to_writer(file, &payload).unwrap(); + payload + }; + // pad to correct length with dummies + let dummy_block_rlp = block_rlps[0].clone(); + block_rlps.resize(1 << max_depth, dummy_block_rlp); + + /*let end_hash = *block_hashes.last().unwrap(); + let mmr = get_merkle_mountain_range(&block_hashes, max_depth); + + let instance = EthBlockHeaderChainInstance::new( + prev_hash, + end_hash, + start_block_number, + end_block_number, + mmr, + );*/ + block_rlps +} + +#[cfg(test)] +mod tests { + use std::env::var; + + use ethers_providers::Http; + + use super::*; + + #[test] + fn test_provider() { + let provider_uri = var("JSON_RPC_URL").expect("JSON_RPC_URL not found"); + let provider = + Provider::::try_from(provider_uri).expect("could not instantiate HTTP Provider"); + + let rt = Runtime::new().unwrap(); + let block = rt.block_on(provider.get_block(17034973)).unwrap().unwrap(); + get_block_rlp(&block); + } + + #[test] + fn test_retry_provider() { + let provider_uri = var("JSON_RPC_URL").expect("JSON_RPC_URL not found"); + let provider = Provider::new_client(&provider_uri, 10, 500) + .expect("could not instantiate HTTP Provider"); + + let rt = Runtime::new().unwrap(); + let block = rt.block_on(provider.get_block(17034973)).unwrap().unwrap(); + get_block_rlp(&block); + } +} diff --git a/src/rlp/builder.rs b/axiom-eth/src/rlp/builder.rs similarity index 93% rename from src/rlp/builder.rs rename to axiom-eth/src/rlp/builder.rs index 34f56a13d..f4e1523ca 100644 --- a/src/rlp/builder.rs +++ b/axiom-eth/src/rlp/builder.rs @@ -7,7 +7,7 @@ use std::{ use halo2_base::{ gates::{ builder::{ - assign_threads_in, FlexGateConfigParams, GateThreadBuilder, + assign_threads_in, CircuitBuilderStage, FlexGateConfigParams, GateThreadBuilder, KeygenAssignments as GateKeygenAssignments, MultiPhaseThreadBreakPoints, ThreadBreakPoints, }, @@ -20,6 +20,8 @@ use halo2_base::{ utils::ScalarField, Context, }; +use itertools::Itertools; +use rayon::prelude::*; use serde::{Deserialize, Serialize}; use super::rlc::{RlcChip, RlcConfig, RlcContextPair, FIRST_PHASE, RLC_PHASE}; @@ -59,6 +61,10 @@ impl RlcThreadBuilder { Self { threads_rlc: Vec::new(), gate_builder: GateThreadBuilder::new(witness_gen_only) } } + pub fn from_stage(stage: CircuitBuilderStage) -> Self { + Self::new(stage == CircuitBuilderStage::Prover) + } + pub fn mock() -> Self { Self::new(false) } @@ -167,7 +173,7 @@ impl RlcThreadBuilder { mut break_points, }: KeygenAssignments, ) -> KeygenAssignments { - // assert!(!self.witness_gen_only()); + assert!(!self.witness_gen_only()); if rlc.basic_gates.is_empty() { return KeygenAssignments { assigned_advices, assigned_constants, break_points }; } @@ -316,7 +322,7 @@ pub fn assign_prover_phase0( FIRST_PHASE, threads, gate, - &lookup_advice[FIRST_PHASE], + lookup_advice.get(FIRST_PHASE).unwrap_or(&vec![]), region, break_points_gate, ); @@ -347,7 +353,7 @@ pub fn assign_prover_phase1( RLC_PHASE, threads, gate, - &lookup_advice[RLC_PHASE], + lookup_advice.get(RLC_PHASE).unwrap_or(&vec![]), region, break_points_gate, ); @@ -356,6 +362,44 @@ pub fn assign_prover_phase1( assign_threads_rlc(threads_rlc, rlc_config, region, break_points_rlc); } +/// Utility function to parallelize an operation involving RLC. This should be called in SecondPhase. +/// +/// **Warning:** if `f` calls `rlc.load_rlc_cache`, then this call must be done *before* calling `parallelize_phase1`. +/// Otherwise the cells where the rlc_cache gets stored will be different depending on which thread calls it first, +/// leading to non-deterministic behavior. +pub fn parallelize_phase1( + thread_pool: &mut RlcThreadBuilder, + input: Vec, + f: FR, +) -> Vec +where + F: ScalarField, + T: Send, + R: Send, + FR: Fn(RlcContextPair, T) -> R + Send + Sync, +{ + let witness_gen_only = thread_pool.witness_gen_only(); + let ctx_ids = input + .iter() + .map(|_| (thread_pool.get_new_thread_id(), thread_pool.get_new_thread_id())) + .collect_vec(); + let (trace, ctxs): (Vec<_>, Vec<_>) = input + .into_par_iter() + .zip(ctx_ids.into_par_iter()) + .map(|(input, (gate_id, rlc_id))| { + let mut ctx_gate = Context::new(witness_gen_only, gate_id); + let mut ctx_rlc = Context::new(witness_gen_only, rlc_id); + let trace = f((&mut ctx_gate, &mut ctx_rlc), input); + (trace, (ctx_gate, ctx_rlc)) + }) + .unzip(); + let (mut ctxs_gate, mut ctxs_rlc): (Vec<_>, Vec<_>) = ctxs.into_iter().unzip(); + thread_pool.gate_builder.threads[RLC_PHASE].append(&mut ctxs_gate); + thread_pool.threads_rlc.append(&mut ctxs_rlc); + + trace +} + pub use circuit_builder::*; mod circuit_builder { use std::cell::RefCell; diff --git a/src/rlp/mod.rs b/axiom-eth/src/rlp/mod.rs similarity index 99% rename from src/rlp/mod.rs rename to axiom-eth/src/rlp/mod.rs index 08a44ac49..c4fadd068 100644 --- a/src/rlp/mod.rs +++ b/axiom-eth/src/rlp/mod.rs @@ -322,8 +322,8 @@ impl<'range, F: ScalarField> RlpChip<'range, F> { // // * rlp_field_rlc.rlc_len = 1 + len_rlc.rlc_len + field_rlc.rlc_len // * len_rlc.rlc_len = prefix_parsed.is_big * prefix_parsed.next_len - // * field_rlc.rlc_len = prefix_parsed.is_big * prefix_parsed.next_len - // + (1 - prefix_parsed.is_big) * byte_value(len) + // * field_rlc.rlc_len = (1 - prefix_parsed.is_big) * prefix_parsed.next_len + // + prefix_parsed.is_big * byte_value(len) // // * rlp_field_rlc = accumulate( // [(prefix, 1), diff --git a/src/rlp/rlc.rs b/axiom-eth/src/rlp/rlc.rs similarity index 100% rename from src/rlp/rlc.rs rename to axiom-eth/src/rlp/rlc.rs diff --git a/src/rlp/tests.rs b/axiom-eth/src/rlp/tests.rs similarity index 98% rename from src/rlp/tests.rs rename to axiom-eth/src/rlp/tests.rs index a09047403..56b6266fd 100644 --- a/src/rlp/tests.rs +++ b/axiom-eth/src/rlp/tests.rs @@ -185,8 +185,8 @@ mod rlc { } rlc.load_rlc_cache((ctx_gate, ctx_rlc), &gate, inputs.4 as usize); - let gamma_pow = rlc.rlc_pow(ctx_gate, &gate, b_len, bit_length(inputs.4 as u64)); - let real_gamma_pow = rlc.gamma().pow_vartime(&[inputs.3 as u64]); + let gamma_pow = rlc.rlc_pow(ctx_gate, &gate, b_len, bit_length(inputs.4)); + let real_gamma_pow = rlc.gamma().pow_vartime([inputs.3]); if *gamma_pow.value() != real_gamma_pow { return Err(RlcTestErrors::GammaPowError); @@ -237,8 +237,8 @@ mod rlc { assert_eq!(rlc_val_b, real_rlc_val_b); rlc.load_rlc_cache((ctx_gate, ctx_rlc), &gate, combined_len as usize); - let gamma_pow = rlc.rlc_pow(ctx_gate, &gate, b_len, bit_length(combined_len as u64)); - let real_gamma_pow = rlc.gamma().pow_vartime(&[inputs.1.len() as u64]); + let gamma_pow = rlc.rlc_pow(ctx_gate, &gate, b_len, bit_length(combined_len)); + let real_gamma_pow = rlc.gamma().pow_vartime([inputs.1.len() as u64]); assert_eq!(*gamma_pow.value(), real_gamma_pow); assert_eq!(rlc_val, rlc_val_a * gamma_pow.value() + rlc_val_b); @@ -352,6 +352,7 @@ mod rlp { let prover = builder.witness_gen_only(); let ctx = builder.gate_builder.main(0); let inputs = ctx.assign_witnesses(encoded.iter().map(|x| F::from(*x as u64))); + set_var("LOOKUP_BITS", "8"); let range = RangeChip::default(8); let chip = RlpChip::new(&range, None); let witness = chip.decompose_rlp_array_phase0(ctx, inputs, max_field_lens, is_var_len); diff --git a/src/storage/helpers.rs b/axiom-eth/src/storage/helpers.rs similarity index 59% rename from src/storage/helpers.rs rename to axiom-eth/src/storage/helpers.rs index ac9524999..0abee1380 100644 --- a/src/storage/helpers.rs +++ b/axiom-eth/src/storage/helpers.rs @@ -1,12 +1,10 @@ use crate::{ - rlp::builder::RlcThreadBuilder, util::{ - circuit::{PinnableCircuit, PreCircuit}, scheduler::{ evm_wrapper::{EvmWrapper, SimpleTask}, - Task, + CircuitType, Task, }, - EthConfigPinning, + EthConfigPinning, Halo2ConfigPinning, }, Network, }; @@ -14,14 +12,7 @@ use ethers_core::{ types::{Address, H256}, utils::keccak256, }; -use ethers_providers::{Http, Provider}; -use halo2_base::{ - gates::builder::CircuitBuilderStage, - halo2_proofs::{ - halo2curves::bn256::{Bn256, Fr}, - poly::kzg::commitment::ParamsKZG, - }, -}; +use ethers_providers::{Http, Provider, RetryClient}; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -55,17 +46,24 @@ impl StorageTask { } } +impl CircuitType for (Network, usize) { + fn name(&self) -> String { + format!("{}_{}", self.0, self.1) + } + fn get_degree_from_pinning(&self, pinning_path: impl AsRef) -> u32 { + let pinning_path = pinning_path.as_ref(); + let pinning = EthConfigPinning::from_path(pinning_path); + pinning.degree() + } +} impl Task for StorageTask { type CircuitType = (Network, usize); // num slots fn circuit_type(&self) -> Self::CircuitType { (self.network(), self.slots.len()) } - fn type_name((network, num_slots): Self::CircuitType) -> String { - format!("{network}_{num_slots}") - } fn name(&self) -> String { - format!("{}_{:?}", Self::type_name(self.circuit_type()), self.digest()) + format!("{}_{:?}", self.circuit_type().name(), self.digest()) } fn dependencies(&self) -> Vec { vec![] @@ -75,7 +73,11 @@ impl Task for StorageTask { impl SimpleTask for StorageTask { type PreCircuit = EthBlockStorageCircuit; - fn get_circuit(&self, provider: Arc>, network: Network) -> Self::PreCircuit { + fn get_circuit( + &self, + provider: Arc>>, + network: Network, + ) -> Self::PreCircuit { EthBlockStorageCircuit::from_provider( &provider, self.block_number, @@ -87,21 +89,3 @@ impl SimpleTask for StorageTask { ) } } - -impl PreCircuit for EthBlockStorageCircuit { - type Pinning = EthConfigPinning; - - fn create_circuit( - self, - stage: CircuitBuilderStage, - pinning: Option, - _: &ParamsKZG, - ) -> impl PinnableCircuit { - let builder = match stage { - CircuitBuilderStage::Prover => RlcThreadBuilder::new(true), - _ => RlcThreadBuilder::new(false), - }; - let break_points = pinning.map(|p| p.break_points); - self.create_circuit::(builder, break_points) - } -} diff --git a/src/storage/mod.rs b/axiom-eth/src/storage/mod.rs similarity index 62% rename from src/storage/mod.rs rename to axiom-eth/src/storage/mod.rs index d20834996..7472f9c2f 100644 --- a/src/storage/mod.rs +++ b/axiom-eth/src/storage/mod.rs @@ -1,38 +1,70 @@ use crate::{ + batch_query::response::{ByteArray, FixedByteArray}, block_header::{ EthBlockHeaderChip, EthBlockHeaderTrace, EthBlockHeaderTraceWitness, GOERLI_BLOCK_HEADER_RLP_MAX_BYTES, MAINNET_BLOCK_HEADER_RLP_MAX_BYTES, }, - keccak::{FixedLenRLCs, FnSynthesize, KeccakChip, VarLenRLCs}, + keccak::{ + parallelize_keccak_phase0, ContainsParallelizableKeccakQueries, FixedLenRLCs, FnSynthesize, + KeccakChip, VarLenRLCs, + }, mpt::{AssignedBytes, MPTFixedKeyInput, MPTFixedKeyProof, MPTFixedKeyProofWitness}, rlp::{ - builder::{RlcThreadBreakPoints, RlcThreadBuilder}, - rlc::{RlcContextPair, RlcTrace, FIRST_PHASE, RLC_PHASE}, + builder::{parallelize_phase1, RlcThreadBreakPoints, RlcThreadBuilder}, + rlc::{RlcContextPair, RlcTrace, FIRST_PHASE}, RlpArrayTraceWitness, RlpChip, RlpFieldTraceWitness, RlpFieldWitness, }, util::{ bytes_be_to_u128, bytes_be_to_uint, bytes_be_var_to_fixed, encode_addr_to_field, encode_h256_to_field, encode_u256_to_field, uint_to_bytes_be, AssignedH256, - EthConfigParams, }, - EthChip, EthCircuitBuilder, Field, Network, ETH_LOOKUP_BITS, + EthChip, EthCircuitBuilder, EthPreCircuit, Field, Network, ETH_LOOKUP_BITS, }; use ethers_core::types::{Address, Block, H256, U256}; #[cfg(feature = "providers")] -use ethers_providers::{Http, Provider}; +use ethers_providers::{JsonRpcClient, Provider}; use halo2_base::{ - gates::{builder::GateThreadBuilder, GateInstructions, RangeChip}, - utils::bit_length, + gates::{builder::GateThreadBuilder, GateInstructions, RangeChip, RangeInstructions}, + halo2_proofs::halo2curves::bn256::Fr, AssignedValue, Context, }; use itertools::Itertools; -use rayon::prelude::*; -use std::{cell::RefCell, env::var}; +use serde::{Deserialize, Serialize}; +use std::cell::RefCell; pub mod helpers; #[cfg(all(test, feature = "providers"))] mod tests; +/* +| Account State Field | Max bytes | +|-------------------------|-------------| +| nonce | ≤8 | +| balance | ≤12 | +| storageRoot | 32 | +| codeHash | 32 | + +account nonce is uint64 by https://eips.ethereum.org/EIPS/eip-2681 +*/ +pub(crate) const NUM_ACCOUNT_STATE_FIELDS: usize = 4; +pub(crate) const ACCOUNT_STATE_FIELDS_MAX_BYTES: [usize; NUM_ACCOUNT_STATE_FIELDS] = + [8, 12, 32, 32]; +pub(crate) const ACCOUNT_STATE_FIELD_IS_VAR_LEN: [bool; NUM_ACCOUNT_STATE_FIELDS] = + [true, true, false, false]; +pub(crate) const ACCOUNT_PROOF_VALUE_MAX_BYTE_LEN: usize = 90; +pub(crate) const STORAGE_PROOF_VALUE_MAX_BYTE_LEN: usize = 33; +/* +let max_branch_bytes = MAX_BRANCH_LENS.1; +let (_, max_ext_bytes) = max_ext_lens(32); +// max_len dominated by max branch rlp length = 533 +let max_len = max(max_ext_bytes, max_branch_bytes, 2 * 32, {ACCOUNT,STORAGE}_PROOF_VALUE_MAX_BYTE_LEN); +let cache_bits = bit_length(max_len) +*/ +const CACHE_BITS: usize = 10; + +pub const ACCOUNT_PROOF_MAX_DEPTH: usize = 10; +pub const STORAGE_PROOF_MAX_DEPTH: usize = 9; + #[derive(Clone, Debug)] pub struct EthAccountTrace { pub nonce_trace: RlcTrace, @@ -43,19 +75,23 @@ pub struct EthAccountTrace { #[derive(Clone, Debug)] pub struct EthAccountTraceWitness { - array_witness: RlpArrayTraceWitness, - mpt_witness: MPTFixedKeyProofWitness, + pub address: AssignedBytes, + pub(crate) array_witness: RlpArrayTraceWitness, + pub(crate) mpt_witness: MPTFixedKeyProofWitness, } impl EthAccountTraceWitness { - pub fn get(&self, acct_field: &str) -> &RlpFieldWitness { - match acct_field { - "nonce" => &self.array_witness.field_witness[0], - "balance" => &self.array_witness.field_witness[1], - "storage_root" | "storageRoot" => &self.array_witness.field_witness[2], - "code_hash" | "codeHash" => &self.array_witness.field_witness[3], - _ => panic!("invalid account field"), - } + pub fn get_nonce(&self) -> &RlpFieldWitness { + &self.array_witness.field_witness[0] + } + pub fn get_balance(&self) -> &RlpFieldWitness { + &self.array_witness.field_witness[1] + } + pub fn get_storage_root(&self) -> &RlpFieldWitness { + &self.array_witness.field_witness[2] + } + pub fn get_code_hash(&self) -> &RlpFieldWitness { + &self.array_witness.field_witness[3] } } @@ -66,8 +102,9 @@ pub struct EthStorageTrace { #[derive(Clone, Debug)] pub struct EthStorageTraceWitness { - value_witness: RlpFieldTraceWitness, - mpt_witness: MPTFixedKeyProofWitness, + pub slot: AssignedBytes, + pub(crate) value_witness: RlpFieldTraceWitness, + pub(crate) mpt_witness: MPTFixedKeyProofWitness, } #[derive(Clone, Debug)] @@ -95,46 +132,136 @@ pub struct EIP1186ResponseDigest { pub slot_is_empty: Vec>, } +impl ContainsParallelizableKeccakQueries for EthStorageTraceWitness { + fn shift_query_indices(&mut self, fixed_shift: usize, var_shift: usize) { + self.mpt_witness.shift_query_indices(fixed_shift, var_shift); + } +} + +impl ContainsParallelizableKeccakQueries for EthAccountTraceWitness { + fn shift_query_indices(&mut self, fixed_shift: usize, var_shift: usize) { + self.mpt_witness.shift_query_indices(fixed_shift, var_shift); + } +} + pub trait EthStorageChip { + /// Does inclusion/exclusion proof of `key = keccak(addr)` into an alleged MPT state trie. + /// RLP decodes the ethereumAccount. + /// + /// There is one global state trie, and it is updated every time a client processes a block. In it, a path is always: keccak256(ethereumAddress) and a value is always: rlp(ethereumAccount). More specifically an ethereum account is a 4 item array of [nonce,balance,storageRoot,codeHash]. fn parse_account_proof_phase0( &self, ctx: &mut Context, keccak: &mut KeccakChip, - state_root_bytes: &[AssignedValue], addr: AssignedBytes, proof: MPTFixedKeyProof, ) -> EthAccountTraceWitness; + /// SecondPhase of account proof parsing. fn parse_account_proof_phase1( &self, ctx: RlcContextPair, witness: EthAccountTraceWitness, ) -> EthAccountTrace; + /// Does multiple calls to [`parse_account_proof_phase0`] in parallel. + fn parse_account_proofs_phase0( + &self, + thread_pool: &mut GateThreadBuilder, + keccak: &mut KeccakChip, + addr_proofs: Vec<(AssignedBytes, MPTFixedKeyProof)>, + ) -> Vec> + where + Self: Sync, + { + parallelize_keccak_phase0(thread_pool, keccak, addr_proofs, |ctx, keccak, (addr, proof)| { + self.parse_account_proof_phase0(ctx, keccak, addr, proof) + }) + } + + /// SecondPhase of account proofs parsing. + fn parse_account_proofs_phase1( + &self, + thread_pool: &mut RlcThreadBuilder, + witness: Vec>, + ) -> Vec>; + + /// Does inclusion/exclusion proof of `keccak(slot)` into an alleged MPT storage trie. + /// + /// storageRoot: A 256-bit hash of the root node of a Merkle Patricia tree that encodes the storage contents of the account (a mapping between 256-bit integer values), encoded into the trie as a mapping from the Keccak 256-bit hash of the 256-bit integer keys to the RLP-encoded 256-bit integer values. The hash is formally denoted σ[a]_s. fn parse_storage_proof_phase0( &self, ctx: &mut Context, keccak: &mut KeccakChip, - storage_root_bytes: &[AssignedValue], - slot_bytes: AssignedBytes, + slot: AssignedBytes, proof: MPTFixedKeyProof, ) -> EthStorageTraceWitness; + /// SecondPhase of storage proof parsing. fn parse_storage_proof_phase1( &self, ctx: RlcContextPair, witness: EthStorageTraceWitness, ) -> EthStorageTrace; + /// Does multiple calls to [`parse_storage_proof_phase0`] in parallel. + fn parse_storage_proofs_phase0( + &self, + thread_pool: &mut GateThreadBuilder, + keccak: &mut KeccakChip, + slot_proofs: Vec<(AssignedBytes, MPTFixedKeyProof)>, + ) -> Vec> + where + Self: Sync, + { + parallelize_keccak_phase0(thread_pool, keccak, slot_proofs, |ctx, keccak, (slot, proof)| { + self.parse_storage_proof_phase0(ctx, keccak, slot, proof) + }) + } + + /// SecondPhase of account proofs parsing. + fn parse_storage_proofs_phase1( + &self, + thread_pool: &mut RlcThreadBuilder, + witness: Vec>, + ) -> Vec>; + + /// Does inclusion/exclusion proof of `key = keccak(addr)` into an alleged MPT state trie. + /// RLP decodes the ethereumAccount, which in particular gives the storageRoot. + /// Does (multiple) inclusion/exclusion proof of `keccak(slot)` into the MPT storage trie with root storageRoot. fn parse_eip1186_proofs_phase0( &self, thread_pool: &mut GateThreadBuilder, keccak: &mut KeccakChip, - state_root_bytes: &[AssignedValue], addr: AssignedBytes, acct_pf: MPTFixedKeyProof, storage_pfs: Vec<(AssignedBytes, MPTFixedKeyProof)>, // (slot_bytes, storage_proof) - ) -> (EthAccountTraceWitness, Vec>); + ) -> (EthAccountTraceWitness, Vec>) + where + Self: Sync, + { + // TODO: spawn separate thread for account proof; just need to get storage_root first somehow + let ctx = thread_pool.main(FIRST_PHASE); + let acct_trace = self.parse_account_proof_phase0(ctx, keccak, addr, acct_pf); + // ctx dropped + let storage_root = &acct_trace.get_storage_root().field_cells; + let storage_trace = parallelize_keccak_phase0( + thread_pool, + keccak, + storage_pfs, + |ctx, keccak, (slot, storage_pf)| { + let witness = self.parse_storage_proof_phase0(ctx, keccak, slot, storage_pf); + // check MPT root is storage_root + for (pf_byte, byte) in + witness.mpt_witness.root_hash_bytes.iter().zip_eq(storage_root.iter()) + { + ctx.constrain_equal(pf_byte, byte); + } + witness + }, + ); + (acct_trace, storage_trace) + } fn parse_eip1186_proofs_phase1( &self, @@ -169,38 +296,32 @@ impl<'chip, F: Field> EthStorageChip for EthChip<'chip, F> { &self, ctx: &mut Context, keccak: &mut KeccakChip, - state_root_bytes: &[AssignedValue], - addr: AssignedBytes, + address: AssignedBytes, proof: MPTFixedKeyProof, ) -> EthAccountTraceWitness { assert_eq!(32, proof.key_bytes.len()); // check key is keccak(addr) - assert_eq!(addr.len(), 20); - let hash_query_idx = keccak.keccak_fixed_len(ctx, self.gate(), addr, None); - let hash_bytes = &keccak.fixed_len_queries[hash_query_idx].output_assigned; + assert_eq!(address.len(), 20); + let hash_query_idx = keccak.keccak_fixed_len(ctx, self.gate(), address.clone(), None); + let hash_addr = &keccak.fixed_len_queries[hash_query_idx].output_assigned; - for (hash, key) in hash_bytes.iter().zip(proof.key_bytes.iter()) { - ctx.constrain_equal(hash, key); - } - - // check MPT root is state root - for (pf_root, root) in proof.root_hash_bytes.iter().zip(state_root_bytes.iter()) { - ctx.constrain_equal(pf_root, root); + for (byte, key) in hash_addr.iter().zip_eq(proof.key_bytes.iter()) { + ctx.constrain_equal(byte, key); } // parse value RLP([nonce, balance, storage_root, code_hash]) let array_witness = self.rlp().decompose_rlp_array_phase0( ctx, proof.value_bytes.clone(), - &[33, 13, 33, 33], + &ACCOUNT_STATE_FIELDS_MAX_BYTES, false, ); // Check MPT inclusion for: // keccak(addr) => RLP([nonce, balance, storage_root, code_hash]) - let mpt_witness = self.parse_mpt_inclusion_fixed_key_phase0(ctx, keccak, proof); // 32, 114, max_depth); + let mpt_witness = self.parse_mpt_inclusion_fixed_key_phase0(ctx, keccak, proof); - EthAccountTraceWitness { array_witness, mpt_witness } + EthAccountTraceWitness { address, array_witness, mpt_witness } } fn parse_account_proof_phase1( @@ -208,7 +329,10 @@ impl<'chip, F: Field> EthStorageChip for EthChip<'chip, F> { (ctx_gate, ctx_rlc): RlcContextPair, witness: EthAccountTraceWitness, ) -> EthAccountTrace { + // Comments below just to log what load_rlc_cache calls are done in the internal functions: + // load_rlc_cache bit_length(2*mpt_witness.key_byte_len) self.parse_mpt_inclusion_fixed_key_phase1((ctx_gate, ctx_rlc), witness.mpt_witness); + // load rlc_cache bit_length(array_witness.rlp_array.len()) let array_trace: [_; 4] = self .rlp() .decompose_rlp_array_phase1((ctx_gate, ctx_rlc), witness.array_witness, false) @@ -220,34 +344,43 @@ impl<'chip, F: Field> EthStorageChip for EthChip<'chip, F> { EthAccountTrace { nonce_trace, balance_trace, storage_root_trace, code_hash_trace } } + fn parse_account_proofs_phase1( + &self, + thread_pool: &mut RlcThreadBuilder, + acct_witness: Vec>, + ) -> Vec> { + // pre-load rlc cache so later parallelization is deterministic + let (ctx_gate, ctx_rlc) = thread_pool.rlc_ctx_pair(); + self.rlc().load_rlc_cache((ctx_gate, ctx_rlc), self.gate(), CACHE_BITS); + + parallelize_phase1(thread_pool, acct_witness, |(ctx_gate, ctx_rlc), witness| { + self.parse_account_proof_phase1((ctx_gate, ctx_rlc), witness) + }) + } + fn parse_storage_proof_phase0( &self, ctx: &mut Context, keccak: &mut KeccakChip, - storage_root_bytes: &[AssignedValue], slot: AssignedBytes, proof: MPTFixedKeyProof, ) -> EthStorageTraceWitness { assert_eq!(32, proof.key_bytes.len()); // check key is keccak(slot) - let hash_query_idx = keccak.keccak_fixed_len(ctx, self.gate(), slot, None); + let hash_query_idx = keccak.keccak_fixed_len(ctx, self.gate(), slot.clone(), None); let hash_bytes = &keccak.fixed_len_queries[hash_query_idx].output_assigned; - for (hash, key) in hash_bytes.iter().zip(proof.key_bytes.iter()) { + for (hash, key) in hash_bytes.iter().zip_eq(proof.key_bytes.iter()) { ctx.constrain_equal(hash, key); } - // check MPT root is storage_root - for (pf_root, root) in proof.root_hash_bytes.iter().zip(storage_root_bytes.iter()) { - ctx.constrain_equal(pf_root, root); - } // parse slot value let value_witness = self.rlp().decompose_rlp_field_phase0(ctx, proof.value_bytes.clone(), 32); // check MPT inclusion let mpt_witness = self.parse_mpt_inclusion_fixed_key_phase0(ctx, keccak, proof); - EthStorageTraceWitness { value_witness, mpt_witness } + EthStorageTraceWitness { slot, value_witness, mpt_witness } } fn parse_storage_proof_phase1( @@ -266,49 +399,17 @@ impl<'chip, F: Field> EthStorageChip for EthChip<'chip, F> { EthStorageTrace { value_trace } } - fn parse_eip1186_proofs_phase0( + fn parse_storage_proofs_phase1( &self, - thread_pool: &mut GateThreadBuilder, - keccak: &mut KeccakChip, - state_root: &[AssignedValue], - addr: AssignedBytes, - acct_pf: MPTFixedKeyProof, - storage_pfs: Vec<(AssignedBytes, MPTFixedKeyProof)>, // (slot_bytes, storage_proof) - ) -> (EthAccountTraceWitness, Vec>) { - // TODO: spawn separate thread for account proof; just need to get storage_root first somehow - let ctx = thread_pool.main(FIRST_PHASE); - let acct_trace = self.parse_account_proof_phase0(ctx, keccak, state_root, addr, acct_pf); - // ctx dropped - let storage_root = &acct_trace.get("storage_root").field_cells; - - // parallelize storage proofs - let witness_gen_only = thread_pool.witness_gen_only(); - let ctx_ids = storage_pfs.iter().map(|_| thread_pool.get_new_thread_id()).collect_vec(); - let (mut storage_trace, ctx_keccaks): (Vec<_>, Vec<_>) = storage_pfs - .into_par_iter() - .zip(ctx_ids.into_par_iter()) - .map(|((slot, storage_pf), ctx_id)| { - let mut ctx = Context::new(witness_gen_only, ctx_id); - let mut keccak = KeccakChip::default(); - let trace = self.parse_storage_proof_phase0( - &mut ctx, - &mut keccak, - storage_root, - slot, - storage_pf, - ); - (trace, (ctx, keccak)) - }) - .unzip(); - // join gate contexts and keccak queries; need to shift keccak query indices because of the join - for (trace, (ctx, mut keccak_)) in storage_trace.iter_mut().zip(ctx_keccaks.into_iter()) { - thread_pool.threads[FIRST_PHASE].push(ctx); - keccak.fixed_len_queries.append(&mut keccak_.fixed_len_queries); - trace.mpt_witness.shift_query_indices(keccak.var_len_queries.len()); - keccak.var_len_queries.append(&mut keccak_.var_len_queries); - } - - (acct_trace, storage_trace) + thread_pool: &mut RlcThreadBuilder, + storage_witness: Vec>, + ) -> Vec> { + let (ctx_gate, ctx_rlc) = thread_pool.rlc_ctx_pair(); + // pre-load rlc cache so later parallelization is deterministic + self.rlc().load_rlc_cache((ctx_gate, ctx_rlc), self.gate(), CACHE_BITS); + parallelize_phase1(thread_pool, storage_witness, |(ctx_gate, ctx_rlc), witness| { + self.parse_storage_proof_phase1((ctx_gate, ctx_rlc), witness) + }) } fn parse_eip1186_proofs_phase1( @@ -321,35 +422,8 @@ impl<'chip, F: Field> EthStorageChip for EthChip<'chip, F> { ) -> (EthAccountTrace, Vec>) { let (ctx_gate, ctx_rlc) = thread_pool.rlc_ctx_pair(); let acct_trace = self.parse_account_proof_phase1((ctx_gate, ctx_rlc), acct_witness); + let storage_trace = self.parse_storage_proofs_phase1(thread_pool, storage_witness); - // pre-load rlc cache so later parallelization is deterministic - let max_len = storage_witness - .iter() - .map(|w| (2 * w.mpt_witness.key_byte_len).max(w.value_witness.rlp_field.len())) - .max() - .unwrap_or(1); - let cache_bits = bit_length(max_len as u64); - self.rlc().load_rlc_cache((ctx_gate, ctx_rlc), self.gate(), cache_bits); - // parallelize - let witness_gen_only = thread_pool.witness_gen_only(); - let ctx_ids = storage_witness - .iter() - .map(|_| (thread_pool.get_new_thread_id(), thread_pool.get_new_thread_id())) - .collect_vec(); - let (storage_trace, ctxs): (Vec<_>, Vec<_>) = storage_witness - .into_par_iter() - .zip(ctx_ids.into_par_iter()) - .map(|(storage_witness, (gate_id, rlc_id))| { - let mut ctx_gate = Context::new(witness_gen_only, gate_id); - let mut ctx_rlc = Context::new(witness_gen_only, rlc_id); - let trace = - self.parse_storage_proof_phase1((&mut ctx_gate, &mut ctx_rlc), storage_witness); - (trace, (ctx_gate, ctx_rlc)) - }) - .unzip(); - let (mut ctxs_gate, mut ctxs_rlc): (Vec<_>, Vec<_>) = ctxs.into_iter().unzip(); - thread_pool.gate_builder.threads[RLC_PHASE].append(&mut ctxs_gate); - thread_pool.threads_rlc.append(&mut ctxs_rlc); (acct_trace, storage_trace) } @@ -373,12 +447,12 @@ impl<'chip, F: Field> EthStorageChip for EthChip<'chip, F> { block_header.resize(max_len, 0); let block_witness = self.decompose_block_header_phase0(ctx, keccak, &block_header, network); - let state_root = &block_witness.get("state_root").field_cells; + let state_root = &block_witness.get_state_root().field_cells; let block_hash_hi_lo = bytes_be_to_u128(ctx, self.gate(), &block_witness.block_hash); // compute block number from big-endian bytes - let block_num_bytes = &block_witness.get("number").field_cells; - let block_num_len = block_witness.get("number").field_len; + let block_num_bytes = &block_witness.get_number().field_cells; + let block_num_len = block_witness.get_number().field_len; let block_number = bytes_be_var_to_fixed(ctx, self.gate(), block_num_bytes, block_num_len, 4); let block_number = bytes_be_to_uint(ctx, self.gate(), &block_number, 4); @@ -399,24 +473,28 @@ impl<'chip, F: Field> EthStorageChip for EthChip<'chip, F> { let (acct_witness, storage_witness) = self.parse_eip1186_proofs_phase0( thread_pool, keccak, - state_root, addr_bytes, input.storage.acct_pf, storage_pfs, ); let ctx = thread_pool.main(FIRST_PHASE); + // check MPT root of acct_witness is state root + for (pf_byte, byte) in + acct_witness.mpt_witness.root_hash_bytes.iter().zip_eq(state_root.iter()) + { + ctx.constrain_equal(pf_byte, byte); + } + let slots_values = slots .into_iter() .zip(storage_witness.iter()) .map(|(slot, witness)| { // get value as U256 from RLP decoding, convert to H256, then to hi-lo - let value_bytes = &witness.value_witness.witness.field_cells; - let value_len = witness.value_witness.witness.field_len; - let value_bytes = - bytes_be_var_to_fixed(ctx, self.gate(), value_bytes, value_len, 32); + let value_bytes: ByteArray = (&witness.value_witness.witness).into(); + let value_bytes = value_bytes.to_fixed(ctx, self.gate()); let value: [_; 2] = - bytes_be_to_u128(ctx, self.gate(), &value_bytes).try_into().unwrap(); + bytes_be_to_u128(ctx, self.gate(), &value_bytes.0).try_into().unwrap(); (slot, value) }) .collect_vec(); @@ -455,14 +533,16 @@ impl<'chip, F: Field> EthStorageChip for EthChip<'chip, F> { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] pub struct EthStorageInput { pub addr: Address, pub acct_pf: MPTFixedKeyInput, - pub storage_pfs: Vec<(H256, U256, MPTFixedKeyInput)>, // (slot, value, proof) + pub acct_state: Vec>, + /// A vector of (slot, value, proof) tuples + pub storage_pfs: Vec<(H256, U256, MPTFixedKeyInput)>, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct EthBlockStorageInput { pub block: Block, pub block_number: u32, @@ -488,6 +568,28 @@ impl EthStorageInput { .collect(); EthStorageInputAssigned { address, acct_pf, storage_pfs } } + + pub fn assign_account( + self, + ctx: &mut Context, + range: &impl RangeInstructions, + ) -> AccountQuery { + let address = FixedByteArray::new(ctx, range, self.addr.as_bytes()); + let acct_pf = self.acct_pf.assign(ctx); + AccountQuery { address, acct_pf } + } + + pub fn assign_storage( + self, + ctx: &mut Context, + range: &impl RangeInstructions, + ) -> StorageQuery { + assert_eq!(self.storage_pfs.len(), 1); + let (slot, _, storage_pf) = self.storage_pfs.into_iter().next().unwrap(); + let slot = FixedByteArray::new(ctx, range, slot.as_bytes()); + let storage_pf = storage_pf.assign(ctx); + StorageQuery { slot, storage_pf } + } } impl EthBlockStorageInput { @@ -499,6 +601,18 @@ impl EthBlockStorageInput { } } +#[derive(Clone, Debug)] +pub struct AccountQuery { + pub address: FixedByteArray, // 20 bytes + pub acct_pf: MPTFixedKeyProof, +} + +#[derive(Clone, Debug)] +pub struct StorageQuery { + pub slot: FixedByteArray, // 20 bytes + pub storage_pf: MPTFixedKeyProof, +} + #[derive(Clone, Debug)] pub struct EthStorageInputAssigned { pub address: AssignedValue, // U160 @@ -521,8 +635,8 @@ pub struct EthBlockStorageCircuit { impl EthBlockStorageCircuit { #[cfg(feature = "providers")] - pub fn from_provider( - provider: &Provider, + pub fn from_provider( + provider: &Provider

, block_number: u32, address: Address, slots: Vec, @@ -559,13 +673,14 @@ impl EthBlockStorageCircuit { } instance } +} - pub fn create_circuit( +impl EthPreCircuit for EthBlockStorageCircuit { + fn create( self, - mut builder: RlcThreadBuilder, + mut builder: RlcThreadBuilder, break_points: Option, - ) -> EthCircuitBuilder> { - let prover = builder.witness_gen_only(); + ) -> EthCircuitBuilder> { let range = RangeChip::default(ETH_LOOKUP_BITS); let chip = EthChip::new(RlpChip::new(&range, None), None); let mut keccak = KeccakChip::default(); @@ -598,34 +713,25 @@ impl EthBlockStorageCircuit { // For now this circuit is going to constrain that all slots are occupied. We can also create a circuit that exposes the bitmap of slot_is_empty { let ctx = builder.gate_builder.main(FIRST_PHASE); - range.gate.assert_is_const(ctx, &address_is_empty, &F::zero()); + range.gate.assert_is_const(ctx, &address_is_empty, &Fr::zero()); for slot_is_empty in slot_is_empty { - range.gate.assert_is_const(ctx, &slot_is_empty, &F::zero()); + range.gate.assert_is_const(ctx, &slot_is_empty, &Fr::zero()); } } - let circuit = EthCircuitBuilder::new( + EthCircuitBuilder::new( assigned_instances, builder, RefCell::new(keccak), range, break_points, - move |builder: &mut RlcThreadBuilder, - rlp: RlpChip, - keccak_rlcs: (FixedLenRLCs, VarLenRLCs)| { + move |builder: &mut RlcThreadBuilder, + rlp: RlpChip, + keccak_rlcs: (FixedLenRLCs, VarLenRLCs)| { // ======== SECOND PHASE =========== let chip = EthChip::new(rlp, Some(keccak_rlcs)); let _trace = chip.parse_eip1186_proofs_from_block_phase1(builder, witness); }, - ); - #[cfg(not(feature = "production"))] - if !prover { - let config_params: EthConfigParams = serde_json::from_str( - var("ETH_CONFIG_PARAMS").expect("ETH_CONFIG_PARAMS is not set").as_str(), - ) - .unwrap(); - circuit.config(config_params.degree as usize, Some(config_params.unusable_rows)); - } - circuit + ) } } diff --git a/src/storage/tests.rs b/axiom-eth/src/storage/tests.rs similarity index 90% rename from src/storage/tests.rs rename to axiom-eth/src/storage/tests.rs index 6bd44657d..4cd6265e0 100644 --- a/src/storage/tests.rs +++ b/axiom-eth/src/storage/tests.rs @@ -1,9 +1,9 @@ use super::*; -use crate::util::scheduler::evm_wrapper::ForEvm; +use crate::util::EthConfigParams; use crate::{ halo2_proofs::{ dev::MockProver, - halo2curves::bn256::{Bn256, Fr, G1Affine}, + halo2curves::bn256::{Bn256, G1Affine}, plonk::*, poly::commitment::ParamsProver, poly::kzg::{ @@ -16,19 +16,17 @@ use crate::{ }, }, providers::{GOERLI_PROVIDER_URL, MAINNET_PROVIDER_URL}, - storage::helpers::{StorageScheduler, StorageTask}, - util::scheduler::Scheduler, }; use ark_std::{end_timer, start_timer}; use ethers_core::utils::keccak256; use halo2_base::utils::fs::gen_srs; use rand_core::OsRng; use serde::{Deserialize, Serialize}; +use std::env::var; use std::{ env::set_var, fs::File, io::{BufReader, Write}, - path::PathBuf, }; use test_log::test; @@ -39,8 +37,8 @@ fn get_test_circuit(network: Network, num_slots: usize) -> EthBlockStorageCircui Network::Mainnet => format!("{MAINNET_PROVIDER_URL}{infura_id}"), Network::Goerli => format!("{GOERLI_PROVIDER_URL}{infura_id}"), }; - let provider = Provider::::try_from(provider_url.as_str()) - .expect("could not instantiate HTTP Provider"); + let provider = + Provider::new_client(&provider_url, 10, 500).expect("could not instantiate HTTP Provider"); let addr; let block_number; match network { @@ -66,6 +64,7 @@ fn get_test_circuit(network: Network, num_slots: usize) -> EthBlockStorageCircui }) .collect::>(); slots.extend(slot_nums.iter().map(|x| H256::from_low_u64_be(*x))); + slots.truncate(num_slots); // let slots: Vec<_> = (0..num_slots).map(|x| H256::from_low_u64_be(x as u64)).collect(); slots.truncate(num_slots); EthBlockStorageCircuit::from_provider(&provider, block_number, addr, slots, 8, 8, network) @@ -77,32 +76,12 @@ pub fn test_mock_single_eip1186() -> Result<(), Box> { set_var("ETH_CONFIG_PARAMS", serde_json::to_string(¶ms).unwrap()); let k = params.degree; - let input = get_test_circuit(Network::Mainnet, 10); - let circuit = input.create_circuit::(RlcThreadBuilder::mock(), None); + let input = get_test_circuit(Network::Mainnet, 1); + let circuit = input.create_circuit(RlcThreadBuilder::mock(), None); MockProver::run(k, &circuit, vec![circuit.instance()]).unwrap().assert_satisfied(); Ok(()) } -#[test] -pub fn test_storage_scheduler() { - let network = Network::Mainnet; - let scheduler = StorageScheduler::new( - network, - false, - false, - PathBuf::from("configs/storage"), - PathBuf::from("data/storage"), - ); - let slots = (0..10).map(|x| H256::from_low_u64_be(x as u64)).collect::>(); - let task = StorageTask::new( - 16356350, - "0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB".parse::

().unwrap(), - slots, - network, - ); - scheduler.get_calldata(ForEvm(task), true); -} - #[derive(Serialize, Deserialize)] struct BenchParams(EthConfigParams, usize); // (params, num_slots) @@ -230,8 +209,7 @@ pub fn bench_evm_eip1186() -> Result<(), Box> { let pk = gen_pk(¶ms, &circuit, None); let break_points = circuit.circuit.break_points.take(); let storage_proof_time = start_timer!(|| "Storage Proof SHPLONK"); - let circuit = - input.create_circuit::(RlcThreadBuilder::prover(), Some(break_points)); + let circuit = input.create_circuit(RlcThreadBuilder::prover(), Some(break_points)); let snark = gen_snark_shplonk(¶ms, &pk, circuit, None::<&str>); end_timer!(storage_proof_time); (snark, storage_proof_time) diff --git a/src/util/circuit.rs b/axiom-eth/src/util/circuit.rs similarity index 68% rename from src/util/circuit.rs rename to axiom-eth/src/util/circuit.rs index 784a2dead..13f8666b0 100644 --- a/src/util/circuit.rs +++ b/axiom-eth/src/util/circuit.rs @@ -1,19 +1,26 @@ use super::{AggregationConfigPinning, EthConfigPinning, Halo2ConfigPinning}; -use crate::{keccak::FnSynthesize, rlp::builder::RlcThreadBreakPoints, EthCircuitBuilder, Field}; +use crate::{ + keccak::FnSynthesize, + rlp::builder::{RlcThreadBreakPoints, RlcThreadBuilder}, + EthCircuitBuilder, EthPreCircuit, Field, +}; use halo2_base::{ - gates::builder::{CircuitBuilderStage, MultiPhaseThreadBreakPoints}, + gates::builder::{ + CircuitBuilderStage, MultiPhaseThreadBreakPoints, RangeWithInstanceCircuitBuilder, + }, halo2_proofs::{ halo2curves::bn256::{Bn256, Fr, G1Affine}, plonk::{Circuit, ProvingKey, VerifyingKey}, poly::{commitment::Params, kzg::commitment::ParamsKZG}, }, + utils::ScalarField, }; #[cfg(feature = "evm")] use snark_verifier_sdk::evm::{gen_evm_proof_shplonk, gen_evm_verifier_shplonk}; use snark_verifier_sdk::{ gen_pk, halo2::{aggregation::AggregationCircuit, gen_snark_shplonk}, - read_pk, CircuitExt, Snark, SHPLONK, + read_pk, CircuitExt, Snark, LIMBS, SHPLONK, }; use std::{env::var, fs::File, path::Path}; @@ -45,6 +52,14 @@ impl PinnableCircuit for AggregationCircuit { } } +impl PinnableCircuit for RangeWithInstanceCircuitBuilder { + type Pinning = AggregationConfigPinning; + + fn break_points(&self) -> MultiPhaseThreadBreakPoints { + RangeWithInstanceCircuitBuilder::break_points(self) + } +} + /// Common functionality we want to get out of any kind of circuit. /// In particular used for types that hold multiple `PreCircuit`s. pub trait AnyCircuit: Sized { @@ -86,6 +101,11 @@ pub trait AnyCircuit: Sized { pub trait PreCircuit: Sized { type Pinning: Halo2ConfigPinning; + /// Creates a [`PinnableCircuit`], auto-configuring the circuit if not in production or prover mode. + /// + /// `params` should be the universal trusted setup for the present aggregation circuit. + /// We assume the trusted setup for the previous SNARKs is compatible with `params` in the sense that + /// the generator point and toxic waste `tau` are the same. fn create_circuit( self, stage: CircuitBuilderStage, @@ -100,6 +120,8 @@ pub trait PreCircuit: Sized { custom_read_pk(path, &circuit) } + /// Creates the proving key for the pre-circuit if file at `pk_path` is not found. + /// If a new proving key is created, the new pinning data is written to `pinning_path`. fn create_pk( self, params: &ParamsKZG, @@ -115,6 +137,26 @@ pub trait PreCircuit: Sized { } pk } + + fn get_degree(pinning_path: impl AsRef) -> u32 { + let pinning = Self::Pinning::from_path(pinning_path); + pinning.degree() + } +} + +impl PreCircuit for C { + type Pinning = EthConfigPinning; + + fn create_circuit( + self, + stage: CircuitBuilderStage, + pinning: Option, + _: &ParamsKZG, + ) -> impl PinnableCircuit { + let builder = RlcThreadBuilder::from_stage(stage); + let break_points = pinning.map(|p| p.break_points()); + EthPreCircuit::create_circuit(self, builder, break_points) + } } impl AnyCircuit for C { @@ -172,16 +214,35 @@ impl AnyCircuit for C { /// Aggregates snarks and re-exposes previous public inputs. /// -/// If `has_prev_accumulators` is true, then it assumes all previous snarks are already aggregation circuits and does not re-expose the old accumulators as public inputs. #[derive(Clone, Debug)] pub struct PublicAggregationCircuit { - pub snarks: Vec, - pub has_prev_accumulators: bool, + /// The previous snarks to aggregate. + /// `snarks` consists of a vector of `(snark, has_prev_accumulator)` pairs, where `snark` is [Snark] and `has_prev_accumulator` is boolean. If `has_prev_accumulator` is true, then it assumes `snark` is already an + /// aggregation circuit and does not re-expose the old accumulator from `snark` as public inputs. + pub snarks: Vec<(Snark, bool)>, } impl PublicAggregationCircuit { - pub fn new(snarks: Vec, has_prev_accumulators: bool) -> Self { - Self { snarks, has_prev_accumulators } + pub fn new(snarks: Vec<(Snark, bool)>) -> Self { + Self { snarks } + } + + // excludes old accumulators from prev instance + pub fn private( + self, + stage: CircuitBuilderStage, + break_points: Option, + lookup_bits: usize, + params: &ParamsKZG, + ) -> AggregationCircuit { + let (snarks, has_prev_acc): (Vec<_>, Vec<_>) = self.snarks.into_iter().unzip(); + let mut private = + AggregationCircuit::new::(stage, break_points, lookup_bits, params, snarks); + for (prev_instance, has_acc) in private.previous_instances.iter_mut().zip(has_prev_acc) { + let start = (has_acc as usize) * 4 * LIMBS; + *prev_instance = prev_instance.split_off(start); + } + private } } @@ -194,27 +255,29 @@ impl PreCircuit for PublicAggregationCircuit { pinning: Option, params: &ParamsKZG, ) -> impl PinnableCircuit { - let lookup_bits = var("LOOKUP_BITS").expect("LOOKUP_BITS is not set").parse().unwrap(); + // look for lookup_bits either from pinning, if available, or from env var + let lookup_bits = pinning + .as_ref() + .map(|p| p.params.lookup_bits) + .or_else(|| var("LOOKUP_BITS").map(|v| v.parse().unwrap()).ok()) + .expect("LOOKUP_BITS is not set"); let break_points = pinning.map(|p| p.break_points()); - let circuit = AggregationCircuit::public::( - stage, - break_points, - lookup_bits, - params, - self.snarks.clone(), - self.has_prev_accumulators, - ); + let mut private = self.private(stage, break_points, lookup_bits, params); + for prev in &private.previous_instances { + private.inner.assigned_instances.extend_from_slice(prev); + } + #[cfg(not(feature = "production"))] match stage { CircuitBuilderStage::Prover => {} _ => { - circuit.config( + private.config( params.k(), Some(var("MINIMUM_ROWS").unwrap_or_else(|_| "10".to_string()).parse().unwrap()), ); } } - circuit + private } } diff --git a/src/util/mod.rs b/axiom-eth/src/util/mod.rs similarity index 71% rename from src/util/mod.rs rename to axiom-eth/src/util/mod.rs index 4858263a4..fd16f36a7 100644 --- a/src/util/mod.rs +++ b/axiom-eth/src/util/mod.rs @@ -12,7 +12,7 @@ use halo2_base::{ }, utils::{bit_length, decompose, decompose_fe_to_u64_limbs, BigPrimeField, ScalarField}, AssignedValue, Context, - QuantumCell::{Constant, Witness}, + QuantumCell::{Constant, Existing, Witness}, }; use itertools::Itertools; use serde::{Deserialize, Serialize}; @@ -31,7 +31,9 @@ pub mod scheduler; pub(crate) const NUM_BYTES_IN_U128: usize = 16; -#[derive(Clone, Debug, Serialize, Deserialize)] +pub type AssignedH256 = [AssignedValue; 2]; // H256 as hi-lo (u128, u128) + +#[derive(Default, Clone, Debug, Serialize, Deserialize)] pub struct EthConfigParams { pub degree: u32, // number of SecondPhase advice columns used in RlcConfig @@ -52,23 +54,6 @@ impl EthConfigParams { pub fn from_path>(path: P) -> Self { serde_json::from_reader(File::open(&path).expect("path does not exist")).unwrap() } - /* MAYBE DELETE - pub fn get_header() -> Self { - let path = - var("BLOCK_HEADER_CONFIG").unwrap_or_else(|_| "configs/block_header.json".to_string()); - serde_json::from_reader( - File::open(&path).unwrap_or_else(|e| panic!("{path} does not exist. {e:?}")), - ) - .unwrap() - } - pub fn get_storage() -> Self { - let path = var("STORAGE_CONFIG").unwrap_or_else(|_| "configs/storage.json".to_string()); - serde_json::from_reader( - File::open(&path).unwrap_or_else(|e| panic!("{path} does not exist. {e:?}")), - ) - .unwrap() - } - */ } pub trait Halo2ConfigPinning: Serialize { @@ -85,7 +70,7 @@ pub trait Halo2ConfigPinning: Serialize { fn degree(&self) -> u32; } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Default, Clone, Debug, Serialize, Deserialize)] pub struct EthConfigPinning { pub params: EthConfigParams, pub break_points: RlcThreadBreakPoints, @@ -182,15 +167,13 @@ impl Halo2ConfigPinning for AggregationConfigPinning { } } -pub type AssignedH256 = [AssignedValue; 2]; // H256 as hi-lo (u128, u128) - pub fn get_merkle_mountain_range(leaves: &[H256], max_depth: usize) -> Vec { let num_leaves = leaves.len(); let mut merkle_roots = Vec::with_capacity(max_depth + 1); let mut start_idx = 0; for depth in (0..max_depth + 1).rev() { if (num_leaves >> depth) & 1 == 1 { - merkle_roots.push(hash_tree_root(&leaves[start_idx..start_idx + (1 << depth)])); + merkle_roots.push(h256_tree_root(&leaves[start_idx..start_idx + (1 << depth)])); start_idx += 1 << depth; } else { merkle_roots.push(H256::zero()); @@ -199,23 +182,28 @@ pub fn get_merkle_mountain_range(leaves: &[H256], max_depth: usize) -> Vec merkle_roots } -pub fn hash_tree_root(leaves: &[H256]) -> H256 { +/// # Assumptions +/// * `leaves` should not be empty +pub fn h256_tree_root(leaves: &[H256]) -> H256 { + assert!(!leaves.is_empty(), "leaves should not be empty"); let depth = leaves.len().ilog2(); assert_eq!(leaves.len(), 1 << depth); if depth == 0 { return leaves[0]; } - let mut hash_bytes = leaves - .chunks(2) - .map(|pair| keccak256([pair[0].as_bytes(), pair[1].as_bytes()].concat())) - .collect_vec(); - for d in (0..depth - 1).rev() { + keccak256_tree_root(leaves.iter().map(|leaf| leaf.as_bytes().to_vec()).collect()) +} + +pub fn keccak256_tree_root(mut leaves: Vec>) -> H256 { + assert!(leaves.len() > 1); + let depth = leaves.len().ilog2(); + assert_eq!(leaves.len(), 1 << depth, "leaves.len() must be a power of 2"); + for d in (0..depth).rev() { for i in 0..(1 << d) { - hash_bytes[i] = - keccak256([&hash_bytes[2 * i][..], &hash_bytes[2 * i + 1][..]].concat()); + leaves[i] = keccak256([&leaves[2 * i][..], &leaves[2 * i + 1][..]].concat()).to_vec(); } } - H256::from_slice(&hash_bytes[0]) + H256::from_slice(&leaves[0]) } pub fn u256_to_bytes32_be(input: &U256) -> Vec { @@ -225,25 +213,25 @@ pub fn u256_to_bytes32_be(input: &U256) -> Vec { } // Field is has PrimeField -/// Takes hash as bytes32 and returns (hash[..16], hash[16..]) represented as big endian numbers in the prime field +/// Takes `hash` as `bytes32` and returns `(hash[..16], hash[16..])` represented as big endian numbers in the prime field pub fn encode_h256_to_field(hash: &H256) -> [F; 2] { let mut bytes = hash.as_bytes().to_vec(); bytes.reverse(); // repr is in little endian let mut repr = [0u8; 32]; repr[..16].copy_from_slice(&bytes[16..]); - let val1 = F::from_repr(repr).unwrap(); + let val1 = F::from_bytes_le(&repr); let mut repr = [0u8; 32]; repr[..16].copy_from_slice(&bytes[..16]); - let val2 = F::from_repr(repr).unwrap(); + let val2 = F::from_bytes_le(&repr); [val1, val2] } pub fn decode_field_to_h256(fe: &[F]) -> H256 { assert_eq!(fe.len(), 2); let mut bytes = [0u8; 32]; - bytes[..16].copy_from_slice(&fe[1].to_repr()[..16]); - bytes[16..].copy_from_slice(&fe[0].to_repr()[..16]); + bytes[..16].copy_from_slice(&fe[1].to_bytes_le()[..16]); + bytes[16..].copy_from_slice(&fe[0].to_bytes_le()[..16]); bytes.reverse(); H256(bytes) } @@ -255,18 +243,18 @@ pub fn encode_u256_to_field(input: &U256) -> [F; 2] { // repr is in little endian let mut repr = [0u8; 32]; repr[..16].copy_from_slice(&bytes[16..]); - let val1 = F::from_repr(repr).unwrap(); + let val1 = F::from_bytes_le(&repr); let mut repr = [0u8; 32]; repr[..16].copy_from_slice(&bytes[..16]); - let val2 = F::from_repr(repr).unwrap(); + let val2 = F::from_bytes_le(&repr); [val1, val2] } pub fn decode_field_to_u256(fe: &[F]) -> U256 { assert_eq!(fe.len(), 2); let mut bytes = [0u8; 32]; - bytes[16..].copy_from_slice(&fe[0].to_repr()[..16]); - bytes[..16].copy_from_slice(&fe[1].to_repr()[..16]); + bytes[16..].copy_from_slice(&fe[0].to_bytes_le()[..16]); + bytes[..16].copy_from_slice(&fe[1].to_bytes_le()[..16]); U256::from_little_endian(&bytes) } @@ -275,18 +263,67 @@ pub fn encode_addr_to_field(input: &Address) -> F { bytes.reverse(); let mut repr = [0u8; 32]; repr[..20].copy_from_slice(&bytes); - F::from_repr(repr).unwrap() + F::from_bytes_le(&repr) } pub fn decode_field_to_addr(fe: &F) -> Address { let mut bytes = [0u8; 20]; - bytes.copy_from_slice(&fe.to_repr()[..20]); + bytes.copy_from_slice(&fe.to_bytes_le()[..20]); bytes.reverse(); Address::from_slice(&bytes) } // circuit utils: +/// Loads boolean `val` as witness and asserts it is a bit. +pub fn load_bool( + ctx: &mut Context, + gate: &impl GateInstructions, + val: bool, +) -> AssignedValue { + let bit = ctx.load_witness(F::from(val)); + gate.assert_bit(ctx, bit); + bit +} + +/// Enforces `lhs` equals `rhs` only if `cond` is true. +/// +/// Assumes that `cond` is a bit. +pub fn enforce_conditional_equality( + ctx: &mut Context, + gate: &impl GateInstructions, + lhs: AssignedValue, + rhs: AssignedValue, + cond: AssignedValue, +) { + let [lhs, rhs] = [lhs, rhs].map(|x| gate.mul(ctx, x, cond)); + ctx.constrain_equal(&lhs, &rhs); +} + +/// `array2d` is an array of fixed length arrays. +/// Assumes: +/// * `array2d[i].len() = array2d[j].len()` for all `i,j`. +/// * the values of `indicator` are boolean and that `indicator` has at most one `1` bit. +/// * the lengths of `array2d` and `indicator` are the same. +/// +/// Returns the "dot product" of `array2d` with `indicator` as a fixed length (1d) array of length `array2d[0].len()`. +pub fn select_array_by_indicator( + ctx: &mut Context, + gate: &impl GateInstructions, + array2d: &[impl AsRef<[AssignedValue]>], + indicator: &[AssignedValue], +) -> Vec> { + (0..array2d[0].as_ref().len()) + .map(|j| { + gate.select_by_indicator( + ctx, + array2d.iter().map(|array_i| array_i.as_ref()[j]), + indicator.iter().copied(), + ) + }) + .collect() +} + /// Assumes that `bytes` have witnesses that are bytes. pub fn bytes_be_to_u128( ctx: &mut Context, @@ -302,6 +339,7 @@ pub(crate) fn limbs_be_to_u128( limbs: &[AssignedValue], limb_bits: usize, ) -> Vec> { + assert!(!limbs.is_empty(), "limbs must not be empty"); assert_eq!(128 % limb_bits, 0); limbs .chunks(128 / limb_bits) @@ -315,7 +353,9 @@ pub(crate) fn limbs_be_to_u128( .collect_vec() } -// `num` in u64 +/// Decomposes `num` into `num_bytes` bytes in big endian and constrains the decomposition holds. +/// +/// Assumes `num` has value in `u64`. pub fn num_to_bytes_be( ctx: &mut Context, range: &RangeChip, @@ -356,21 +396,32 @@ pub fn bytes_be_var_to_fixed( // If `bytes` is an RLP field, then `len <= bytes.len()` was already checked during `decompose_rlp_array_phase0` so we don't need to do it again: // range.range_check(ctx, len, bit_length(bytes.len() as u64)); - - // TODO: the indicator for byte_idx can maybe be created from indicator for len just by shifts and 0 pads since out_len and idx are not witnesses, but this function isn't used that often so not optimizing for now - // out[idx] = len >= out_len - idx ? bytes[idx + len - out_len] : 0 - (0..out_len) - .map(|idx| { - let byte_idx = - gate.sub(ctx, len, Constant(gate.get_field_element((out_len - idx) as u64))); - // If `len - (out_len - idx) < 0` then the `F` value will be >= `bytes.len()` provided that `out_len` is not too big -- namely `bit_length(out_len) <= F::CAPACITY - 1` - // Thus select_from_idx at idx < 0 will return 0 - gate.select_from_idx(ctx, bytes.iter().copied(), byte_idx) - }) - .collect() + let mut padded_bytes = bytes.to_vec(); + padded_bytes.resize(out_len, padded_bytes[0]); + // We use a barrel shifter to shift `bytes` to the right by `out_len - len` bits. + let shift = gate.sub(ctx, Constant(gate.get_field_element(out_len as u64)), len); + let shift_bits = gate.num_to_bits(ctx, shift, bit_length(out_len as u64)); + for (i, shift_bit) in shift_bits.into_iter().enumerate() { + let shifted_bytes = (0..out_len) + .map(|j| { + if j >= (1 << i) { + Existing(padded_bytes[j - (1 << i)]) + } else { + Constant(F::zero()) + } + }) + .collect_vec(); + padded_bytes = padded_bytes + .into_iter() + .zip(shifted_bytes) + .map(|(noshift, shift)| gate.select(ctx, shift, noshift, shift_bit)) + .collect_vec(); + } + padded_bytes } -/// See [`num_to_bytes_be`] for details. Here `uint` can now be any uint that fits into `F`. +/// Decomposes `uint` into `num_bytes` bytes and constrains the decomposition. +/// Here `uint` can be any uint that fits into `F`. pub fn uint_to_bytes_be( ctx: &mut Context, range: &RangeChip, @@ -428,3 +479,24 @@ pub fn bytes_be_to_uint( (0..num_bytes).map(|idx| Constant(gate.pow_of_two()[8 * idx])), ) } + +/// Converts a fixed length array of `u128` values into a fixed length array of big endian bytes. +pub fn u128s_to_bytes_be( + ctx: &mut Context, + range: &RangeChip, + u128s: &[AssignedValue], +) -> Vec> { + u128s.iter().map(|u128| uint_to_bytes_be(ctx, range, u128, 16)).concat() +} + +/// Returns 1 if all entries of `input` are zero, 0 otherwise. +pub fn is_zero_vec( + ctx: &mut Context, + gate: &impl GateInstructions, + input: &[AssignedValue], +) -> AssignedValue { + let is_zeros = input.iter().map(|x| gate.is_zero(ctx, *x)).collect_vec(); + let sum = gate.sum(ctx, is_zeros); + let total_len = gate.get_field_element(input.len() as u64); + gate.is_equal(ctx, sum, Constant(total_len)) +} diff --git a/src/util/scheduler/evm_wrapper.rs b/axiom-eth/src/util/scheduler/evm_wrapper.rs similarity index 81% rename from src/util/scheduler/evm_wrapper.rs rename to axiom-eth/src/util/scheduler/evm_wrapper.rs index ddab522da..c22ede3a4 100644 --- a/src/util/scheduler/evm_wrapper.rs +++ b/axiom-eth/src/util/scheduler/evm_wrapper.rs @@ -1,6 +1,6 @@ ///! A simple scheduler that just wraps a `PreCircuit` with a `PublicAggregationCircuit` circuit, to produce a SNARK that is cheap to verify in EVM /// -use super::{EthScheduler, Scheduler, Task}; +use super::{CircuitType, EthScheduler, Scheduler, Task}; use crate::{ util::{ circuit::{AnyCircuit, PreCircuit, PublicAggregationCircuit}, @@ -8,7 +8,7 @@ use crate::{ }, Network, }; -use ethers_providers::{Http, Provider}; +use ethers_providers::{Http, Provider, RetryClient}; use halo2_base::halo2_proofs::{ halo2curves::bn256::{Bn256, G1Affine}, plonk::ProvingKey, @@ -25,6 +25,23 @@ pub enum Wrapper { // commonly used pub use Wrapper::ForEvm; +impl CircuitType for Wrapper { + fn get_degree_from_pinning(&self, pinning_path: impl AsRef) -> u32 { + match self { + Self::Initial(t) => t.get_degree_from_pinning(pinning_path), + Self::ForEvm(_) => { + let pinning = AggregationConfigPinning::from_path(pinning_path); + pinning.degree() + } + } + } + fn name(&self) -> String { + match self { + Self::Initial(t) => t.name(), + Self::ForEvm(t) => format!("{}_evm", t.name()), + } + } +} impl Task for Wrapper { type CircuitType = Wrapper; @@ -34,12 +51,6 @@ impl Task for Wrapper { Self::ForEvm(t) => Wrapper::ForEvm(t.circuit_type()), } } - fn type_name(circuit_type: Self::CircuitType) -> String { - match circuit_type { - Wrapper::Initial(circuit_type) => T::type_name(circuit_type), - Wrapper::ForEvm(circuit_type) => format!("{}_evm", T::type_name(circuit_type)), - } - } fn name(&self) -> String { match self { Wrapper::Initial(t) => t.name(), @@ -57,7 +68,11 @@ impl Task for Wrapper { pub trait SimpleTask: Task { type PreCircuit: PreCircuit + Clone; - fn get_circuit(&self, provider: Arc>, network: Network) -> Self::PreCircuit; + fn get_circuit( + &self, + provider: Arc>>, + network: Network, + ) -> Self::PreCircuit; } #[derive(Clone, Debug)] @@ -72,19 +87,6 @@ impl Scheduler for EvmWrapper { type Task = Wrapper; type CircuitRouter = WrapperRouter; - fn get_degree(&self, circuit_type: Wrapper) -> u32 { - if let Some(k) = self.degree.read().unwrap().get(&circuit_type) { - return *k; - } - let path = self.pinning_path(circuit_type); - let k = match circuit_type { - Wrapper::Initial(_) => ::Pinning::from_path(path).degree(), - Wrapper::ForEvm(_) => AggregationConfigPinning::from_path(path).degree(), - }; - self.degree.write().unwrap().insert(circuit_type, k); - k - } - fn get_circuit(&self, task: Self::Task, prev_snarks: Vec) -> Self::CircuitRouter { match task { Wrapper::Initial(t) => { @@ -93,7 +95,10 @@ impl Scheduler for EvmWrapper { } Wrapper::ForEvm(_) => { assert_eq!(prev_snarks.len(), 1); - WrapperRouter::ForEvm(PublicAggregationCircuit::new(prev_snarks, false)) + WrapperRouter::ForEvm(PublicAggregationCircuit::new(vec![( + prev_snarks[0].clone(), + false, + )])) } } } diff --git a/src/util/scheduler/mod.rs b/axiom-eth/src/util/scheduler/mod.rs similarity index 81% rename from src/util/scheduler/mod.rs rename to axiom-eth/src/util/scheduler/mod.rs index c94353e91..7a6f81be8 100644 --- a/src/util/scheduler/mod.rs +++ b/axiom-eth/src/util/scheduler/mod.rs @@ -1,5 +1,5 @@ use ethers_core::types::U256; -use ethers_providers::{Http, Middleware, Provider}; +use ethers_providers::{Http, Middleware, Provider, RetryClient}; use halo2_base::{ halo2_proofs::{ halo2curves::bn256::{Bn256, G1Affine}, @@ -22,23 +22,25 @@ use std::{ sync::{Arc, RwLock}, }; -use crate::{ - Network, -}; +use crate::Network; use super::circuit::AnyCircuit; pub mod evm_wrapper; +/// This is a tag for the type of a circuit, independent of the circuit's inputs. +/// For example, it can be used to fetch the proving key for the circuit. +pub trait CircuitType: Clone + Debug + Eq + Hash { + fn name(&self) -> String; + fn get_degree_from_pinning(&self, pinning_path: impl AsRef) -> u32; +} + /// This is an identifier for a specific proof request, consisting of the circuit type together with any data necessary to create the circuit inputs. /// It should be thought of as a node in a DAG (directed acyclic graph), where the edges specify previous SNARKs this one depends on. pub trait Task: Clone + Debug { - /// This is a tag for the type of a circuit, independent of the circuit's inputs. - /// For example, it can be used to fetch the proving key for the circuit. - type CircuitType: Copy + Eq + Hash; + type CircuitType: CircuitType; fn circuit_type(&self) -> Self::CircuitType; - fn type_name(circuit_type: Self::CircuitType) -> String; fn name(&self) -> String; /// The previous tasks this task depends on (i.e., the edges of the DAG that point to this node). @@ -46,12 +48,20 @@ pub trait Task: Clone + Debug { } pub trait SchedulerCommon { - type CircuitType: Hash + Eq; + type CircuitType: CircuitType; fn config_dir(&self) -> &Path; fn data_dir(&self) -> &Path; fn pkey_readonly(&self) -> bool; fn srs_readonly(&self) -> bool; + /// The path to the file with the circuit configuration pinning. + fn pinning_path(&self, circuit_type: &Self::CircuitType) -> PathBuf { + self.config_dir().join(format!("{}.json", circuit_type.name())) + } + /// Returns the degree of the circuit from file. + /// + /// Recommended: use `HashMap` to cache this. + fn get_degree(&self, circuit_type: &Self::CircuitType) -> u32; /// Read (or generate) the universal trusted setup by reading configuration file. /// /// Recommended: Cache the params in a hashmap if they are not already cached. @@ -74,7 +84,7 @@ pub struct EthScheduler { pub pkeys: RwLock>>>, pub degree: RwLock>, pub params: RwLock>>>, - pub provider: Arc>, + pub provider: Arc>>, pub network: Network, _marker: PhantomData, @@ -89,8 +99,8 @@ impl EthScheduler { data_dir: PathBuf, ) -> Self { let provider_url = var("JSON_RPC_URL").expect("JSON_RPC_URL not found"); - let provider = - Provider::::try_from(provider_url).expect("could not instantiate HTTP Provider"); + let provider = Provider::new_client(&provider_url, 10, 500) + .expect("could not instantiate HTTP Provider"); tokio::runtime::Runtime::new().unwrap().block_on(async { let chain_id = provider.get_chainid().await.unwrap(); match network { @@ -139,6 +149,15 @@ impl SchedulerCommon for EthScheduler { fn pkey_readonly(&self) -> bool { self.read_only } + fn get_degree(&self, circuit_type: &Self::CircuitType) -> u32 { + if let Some(k) = self.degree.read().unwrap().get(circuit_type) { + return *k; + } + let path = self.pinning_path(circuit_type); + let k = CircuitType::get_degree_from_pinning(circuit_type, path); + self.degree.write().unwrap().insert(circuit_type.clone(), k); + k + } fn get_params(&self, k: u32) -> Arc> { if let Some(params) = self.params.read().unwrap().get(&k) { return Arc::clone(params); @@ -164,26 +183,17 @@ pub trait Scheduler: SchedulerCommon::Circuit // TODO: better way to do this with macros? `PreCircuit` is not object safe so cannot use `dyn` type CircuitRouter: AnyCircuit + Clone; - /// Returns the degree of the circuit from file. - /// - /// Recommended: use `HashMap` to cache this. - fn get_degree(&self, circuit_type: ::CircuitType) -> u32; - /// `prev_snarks` is assumed to be the SNARKs associated to the previous tasks in `task.dependencies()`. From those snarks (if any), this /// function constructs the pre-circuit for this task. fn get_circuit(&self, task: Self::Task, prev_snarks: Vec) -> Self::CircuitRouter; // ==== automatically generated functions below ==== - /// The path to the file with the circuit configuration pinning. - fn pinning_path(&self, circuit_type: ::CircuitType) -> PathBuf { - self.config_dir().join(format!("{}.json", Self::Task::type_name(circuit_type))) - } - fn pkey_path(&self, circuit_type: ::CircuitType) -> PathBuf { - self.data_dir().join(format!("{}.pk", Self::Task::type_name(circuit_type))) + fn pkey_path(&self, circuit_type: &::CircuitType) -> PathBuf { + self.data_dir().join(format!("{}.pk", circuit_type.name())) } - fn yul_path(&self, circuit_type: ::CircuitType) -> PathBuf { - self.data_dir().join(format!("{}.yul", Self::Task::type_name(circuit_type))) + fn yul_path(&self, circuit_type: &::CircuitType) -> PathBuf { + self.data_dir().join(format!("{}.yul", circuit_type.name())) } fn snark_path(&self, task: &Self::Task) -> PathBuf { self.data_dir().join(format!("{}.snark", task.name())) @@ -206,19 +216,19 @@ pub trait Scheduler: SchedulerCommon::Circuit task.dependencies().into_iter().map(|dep| self.get_snark(dep)).collect(); let circuit_type = task.circuit_type(); - let k = self.get_degree(circuit_type); + let k = self.get_degree(&circuit_type); let params = &self.get_params(k); // Construct the pre-circuit for this task from the dependency SNARKs. let pre_circuit = self.get_circuit(task, dep_snarks); - let pk_path = self.pkey_path(circuit_type); - let pinning_path = self.pinning_path(circuit_type); + let pk_path = self.pkey_path(&circuit_type); + let pinning_path = self.pinning_path(&circuit_type); let pk = if let Some(pk) = self.get_pkey(&circuit_type) { pk } else { let pk = pre_circuit.clone().read_or_create_pk(params, pk_path, &pinning_path, read_only); - self.insert_pkey(circuit_type, pk); + self.insert_pkey(circuit_type.clone(), pk); self.get_pkey(&circuit_type).unwrap() }; let snark_path = Some(snark_path); @@ -239,12 +249,12 @@ pub trait Scheduler: SchedulerCommon::Circuit task.dependencies().into_iter().map(|dep| self.get_snark(dep)).collect(); let circuit_type = task.circuit_type(); - let k = self.get_degree(circuit_type); + let k = self.get_degree(&circuit_type); let params = &self.get_params(k); // Construct the pre-circuit for this task from the dependency SNARKs. let pre_circuit = self.get_circuit(task, dep_snarks); - let pk_path = self.pkey_path(circuit_type); - let pinning_path = self.pinning_path(circuit_type); + let pk_path = self.pkey_path(&circuit_type); + let pinning_path = self.pinning_path(&circuit_type); let pk = if let Some(pk) = self.get_pkey(&circuit_type) { pk @@ -252,12 +262,12 @@ pub trait Scheduler: SchedulerCommon::Circuit let read_only = self.pkey_readonly(); let pk = pre_circuit.clone().read_or_create_pk(params, pk_path, &pinning_path, read_only); - self.insert_pkey(circuit_type, pk); + self.insert_pkey(circuit_type.clone(), pk); self.get_pkey(&circuit_type).unwrap() }; let deployment_code = generate_smart_contract.then(|| { - pre_circuit.clone().gen_evm_verifier_shplonk(params, &pk, self.yul_path(circuit_type)) + pre_circuit.clone().gen_evm_verifier_shplonk(params, &pk, self.yul_path(&circuit_type)) }); pre_circuit.gen_calldata(params, &pk, pinning_path, calldata_path, deployment_code) } diff --git a/configs/headers/mainnet_10_7.json b/configs/headers/mainnet_10_7.json deleted file mode 100644 index e6b75c29d..000000000 --- a/configs/headers/mainnet_10_7.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "strategy": "Simple", - "degree": 20, - "num_advice": [12], - "num_lookup_advice": [2], - "num_fixed": 1, - "lookup_bits": 19, - "limb_bits": 88, - "num_limbs": 3 -} diff --git a/configs/headers/mainnet_11_7.json b/configs/headers/mainnet_11_7.json deleted file mode 100644 index 93ac10110..000000000 --- a/configs/headers/mainnet_11_7.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "strategy": "Simple", - "degree": 20, - "num_advice": [15], - "num_lookup_advice": [2], - "num_fixed": 1, - "lookup_bits": 19, - "limb_bits": 88, - "num_limbs": 3 -} diff --git a/configs/headers/mainnet_12_7.json b/configs/headers/mainnet_12_7.json deleted file mode 100644 index a71e980d6..000000000 --- a/configs/headers/mainnet_12_7.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "strategy": "Simple", - "degree": 21, - "num_advice": [9], - "num_lookup_advice": [1], - "num_fixed": 1, - "lookup_bits": 20, - "limb_bits": 88, - "num_limbs": 3 -} diff --git a/configs/headers/mainnet_13_7.json b/configs/headers/mainnet_13_7.json deleted file mode 100644 index e6b75c29d..000000000 --- a/configs/headers/mainnet_13_7.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "strategy": "Simple", - "degree": 20, - "num_advice": [12], - "num_lookup_advice": [2], - "num_fixed": 1, - "lookup_bits": 19, - "limb_bits": 88, - "num_limbs": 3 -} diff --git a/configs/headers/mainnet_14_7.json b/configs/headers/mainnet_14_7.json deleted file mode 100644 index b5cf86359..000000000 --- a/configs/headers/mainnet_14_7.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "strategy": "Simple", - "degree": 20, - "num_advice": [16], - "num_lookup_advice": [2], - "num_fixed": 1, - "lookup_bits": 19, - "limb_bits": 88, - "num_limbs": 3 -} diff --git a/configs/headers/mainnet_15_7.json b/configs/headers/mainnet_15_7.json deleted file mode 100644 index a71e980d6..000000000 --- a/configs/headers/mainnet_15_7.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "strategy": "Simple", - "degree": 21, - "num_advice": [9], - "num_lookup_advice": [1], - "num_fixed": 1, - "lookup_bits": 20, - "limb_bits": 88, - "num_limbs": 3 -} diff --git a/configs/headers/mainnet_16_7.json b/configs/headers/mainnet_16_7.json deleted file mode 100644 index 166fb4682..000000000 --- a/configs/headers/mainnet_16_7.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "strategy": "Simple", - "degree": 20, - "num_advice": [13], - "num_lookup_advice": [2], - "num_fixed": 1, - "lookup_bits": 19, - "limb_bits": 88, - "num_limbs": 3 -} diff --git a/configs/headers/mainnet_17_7_final.json b/configs/headers/mainnet_17_7_final.json deleted file mode 100644 index e09baaf2b..000000000 --- a/configs/headers/mainnet_17_7_final.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "aggregation": { - "strategy": "Simple", - "degree": 22, - "num_advice": [93,1], - "num_lookup_advice": [1,0], - "num_fixed": 1, - "lookup_bits": 21, - "limb_bits": 88, - "num_limbs": 3 - }, - "num_rlc_columns": 1, - "unusable_rows": 109, - "keccak_rows_per_round": 50 -} diff --git a/configs/headers/mainnet_17_7_for_evm_0.json b/configs/headers/mainnet_17_7_for_evm_0.json deleted file mode 100644 index 712933b3b..000000000 --- a/configs/headers/mainnet_17_7_for_evm_0.json +++ /dev/null @@ -1 +0,0 @@ -{"strategy":"Simple","degree":22,"num_advice":[12],"num_lookup_advice":[2],"num_fixed":1,"lookup_bits":21,"limb_bits":88,"num_limbs":3} diff --git a/configs/headers/mainnet_17_7_for_evm_1.json b/configs/headers/mainnet_17_7_for_evm_1.json deleted file mode 100644 index 378d08148..000000000 --- a/configs/headers/mainnet_17_7_for_evm_1.json +++ /dev/null @@ -1 +0,0 @@ -{"strategy":"Simple","degree":23,"num_advice":[1],"num_lookup_advice":[1],"num_fixed":1,"lookup_bits":22,"limb_bits":88,"num_limbs":3} diff --git a/configs/tests/multi_block.json b/configs/tests/multi_block.json deleted file mode 100644 index 127f0d309..000000000 --- a/configs/tests/multi_block.json +++ /dev/null @@ -1 +0,0 @@ -{"params":{"degree":14,"num_rlc_columns":3,"num_range_advice":[46,16,0],"num_lookup_advice":[1,1,0],"num_fixed":1,"unusable_rows":61,"keccak_rows_per_round":12,"lookup_bits":8},"break_points":{"gate":[[16322,16321,16320,16322,16320,16321,16321,16321,16320,16322,16320,16321,16321,16322,16322,16320,16322,16322,16322,16322,16322,16320,16322,16321,16321,16322,16322,16322,16322,16320,16322,16322,16320,16320,16321,16322,16322,16322,16322,16322,16322,16320,16320,16320,16322],[16322,16320,16322,16320,16320,16320,16322,16321,16322,16322,16322,16322,16321,16321,16320],[]],"rlc":[16322,16322]}} \ No newline at end of file diff --git a/configs/tests/one_block.json b/configs/tests/one_block.json deleted file mode 100644 index 968964cda..000000000 --- a/configs/tests/one_block.json +++ /dev/null @@ -1 +0,0 @@ -{"params":{"degree":12,"num_rlc_columns":1,"num_range_advice":[23,8,0],"num_lookup_advice":[1,1,0],"num_fixed":1,"unusable_rows":109,"keccak_rows_per_round":27,"lookup_bits":8},"break_points":{"gate":[[4003,4003,4004,4003,4002,4004,4003,4002,4004,4002,4003,4004,4004,4003,4004,4002,4004,4004,4004,4003,4004,4002],[4004,4004,4004,4004,4003,4004,4002],[]],"rlc":[]}} \ No newline at end of file diff --git a/scripts/input_gen/query_test_storage.json b/scripts/input_gen/query_test_storage.json deleted file mode 100644 index d180f7e9c..000000000 --- a/scripts/input_gen/query_test_storage.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "id": 1, - "jsonrpc": "2.0", - "method": "eth_getProof", - "params": [ - "0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB", - ["000000000000000000000000000000000000000000000000000000000000005"], - "0xf929e6" - ] -} diff --git a/src/providers.rs b/src/providers.rs deleted file mode 100644 index 9dd534863..000000000 --- a/src/providers.rs +++ /dev/null @@ -1,297 +0,0 @@ -#![allow(unused_imports)] // until storage proof is refactored -use crate::{ - block_header::{ - EthBlockHeaderChainInstance, GOERLI_BLOCK_HEADER_RLP_MAX_BYTES, - MAINNET_BLOCK_HEADER_RLP_MAX_BYTES, - }, - mpt::MPTFixedKeyInput, - storage::{EthBlockStorageInput, EthStorageInput}, - util::{get_merkle_mountain_range, u256_to_bytes32_be}, - Network, -}; -use ethers_core::types::{ - Address, Block, BlockId, BlockId::Number, BlockNumber, Bytes, EIP1186ProofResponse, - StorageProof, H256, U256, -}; -use ethers_core::utils::hex::FromHex; -use ethers_core::utils::keccak256; -use ethers_providers::{Http, Middleware, Provider}; -// use halo2_mpt::mpt::{max_branch_lens, max_leaf_lens}; -use itertools::Itertools; -use lazy_static::__Deref; -use rlp::{decode, decode_list, Encodable, Rlp, RlpStream}; -use serde::{Deserialize, Serialize}; -use serde_with::serde_as; -use std::{ - convert::TryFrom, - fs::{self, File}, - io::{Read, Write}, - iter, num, - path::Path, -}; -use tokio::runtime::Runtime; - -pub const MAINNET_PROVIDER_URL: &str = "https://mainnet.infura.io/v3/"; -pub const GOERLI_PROVIDER_URL: &str = "https://goerli.infura.io/v3/"; - -const ACCOUNT_PROOF_VALUE_MAX_BYTE_LEN: usize = 114; -const STORAGE_PROOF_VALUE_MAX_BYTE_LEN: usize = 33; - -pub fn get_block_storage_input( - provider: &Provider, - block_number: u32, - addr: Address, - slots: Vec, - acct_pf_max_depth: usize, - storage_pf_max_depth: usize, -) -> EthBlockStorageInput { - let rt = Runtime::new().unwrap(); - let block = rt.block_on(provider.get_block(block_number as u64)).unwrap().unwrap(); - let block_hash = block.hash.unwrap(); - let block_header = get_block_rlp(&block); - - let pf = rt - .block_on(provider.get_proof(addr, slots, Some(Number(BlockNumber::from(block_number))))) - .unwrap(); - - let acct_key = H256(keccak256(addr)); - let slot_is_empty = !is_assigned_slot(&acct_key, &pf.account_proof); - let acct_pf = MPTFixedKeyInput { - path: acct_key, - value: get_acct_rlp(&pf), - root_hash: block.state_root, - proof: pf.account_proof.into_iter().map(|x| x.to_vec()).collect(), - value_max_byte_len: ACCOUNT_PROOF_VALUE_MAX_BYTE_LEN, - max_depth: acct_pf_max_depth, - slot_is_empty, - }; - - let storage_pfs = pf - .storage_proof - .into_iter() - .map(|storage_pf| { - let path = H256(keccak256(storage_pf.key)); - let slot_is_empty = !is_assigned_slot(&path, &storage_pf.proof); - let value = - if slot_is_empty { vec![0u8] } else { storage_pf.value.rlp_bytes().to_vec() }; - ( - storage_pf.key, - storage_pf.value, - MPTFixedKeyInput { - path, - value, - root_hash: pf.storage_hash, - proof: storage_pf.proof.into_iter().map(|x| x.to_vec()).collect(), - value_max_byte_len: STORAGE_PROOF_VALUE_MAX_BYTE_LEN, - max_depth: storage_pf_max_depth, - slot_is_empty, - }, - ) - }) - .collect(); - - EthBlockStorageInput { - block, - block_number, - block_hash, - block_header, - storage: EthStorageInput { addr, acct_pf, storage_pfs }, - } -} - -pub fn is_assigned_slot(key: &H256, proof: &[Bytes]) -> bool { - let mut key_nibbles = Vec::new(); - for &byte in key.as_bytes() { - key_nibbles.push(byte / 16); - key_nibbles.push(byte % 16); - } - let mut key_frags = Vec::new(); - let mut path_idx = 0; - for node in proof.iter() { - let rlp = Rlp::new(node); - if rlp.item_count().unwrap() == 2 { - let path = rlp.at(0).unwrap().data().unwrap(); - let is_odd = (path[0] / 16 == 1u8) || (path[0] / 16 == 3u8); - let mut frag = Vec::new(); - if is_odd { - frag.push(path[0] % 16); - path_idx += 1; - } - for byte in path.iter().skip(1) { - frag.push(*byte / 16); - frag.push(*byte % 16); - path_idx += 2; - } - key_frags.extend(frag); - } else { - key_frags.extend(vec![key_nibbles[path_idx]]); - path_idx += 1; - } - } - if path_idx == 64 { - for idx in 0..64 { - if key_nibbles[idx] != key_frags[idx] { - return false; - } - } - } else { - return false; - } - true -} - -pub fn get_acct_rlp(pf: &EIP1186ProofResponse) -> Vec { - let mut rlp: RlpStream = RlpStream::new_list(4); - rlp.append(&pf.nonce); - rlp.append(&pf.balance); - rlp.append(&pf.storage_hash); - rlp.append(&pf.code_hash); - rlp.out().into() -} - -pub fn get_block_rlp(block: &Block) -> Vec { - let withdrawals_root: Option = block.withdrawals_root; - let base_fee = block.base_fee_per_gas; - let rlp_len = 15 + usize::from(base_fee.is_some()) + usize::from(withdrawals_root.is_some()); - let mut rlp = RlpStream::new_list(rlp_len); - rlp.append(&block.parent_hash); - rlp.append(&block.uncles_hash); - rlp.append(&block.author.unwrap()); - rlp.append(&block.state_root); - rlp.append(&block.transactions_root); - rlp.append(&block.receipts_root); - rlp.append(&block.logs_bloom.unwrap()); - rlp.append(&block.difficulty); - rlp.append(&block.number.unwrap()); - rlp.append(&block.gas_limit); - rlp.append(&block.gas_used); - rlp.append(&block.timestamp); - rlp.append(&block.extra_data.to_vec()); - rlp.append(&block.mix_hash.unwrap()); - rlp.append(&block.nonce.unwrap()); - base_fee.map(|base_fee| rlp.append(&base_fee)); - withdrawals_root.map(|withdrawals_root| rlp.append(&withdrawals_root)); - let encoding = rlp.out().into(); - assert_eq!(keccak256(&encoding), block.hash.unwrap().0); - encoding -} - -serde_with::serde_conv!( - BytesBase64, - Vec, - |bytes: &Vec| { - use base64::{engine::general_purpose, Engine as _}; - general_purpose::STANDARD.encode(bytes) - }, - |encoded: String| { - use base64::{engine::general_purpose, Engine as _}; - general_purpose::STANDARD.decode(encoded) - } -); - -#[serde_as] -#[derive(Debug, Serialize, Deserialize)] -pub struct ProcessedBlock { - #[serde_as(as = "Vec")] - pub block_rlps: Vec>, - pub block_hashes: Vec, - pub prev_hash: H256, -} - -/// returns tuple of: -/// * vector of RLP bytes of each block -/// * tuple of -/// * parentHash (H256) -/// * endHash (H256) -/// * startBlockNumber (u32) -/// * endBlockNumber (u32) -/// * merkleRoots (Vec) -/// * where merkleRoots is a length `max_depth + 1` vector representing a merkle mountain range, ordered largest mountain first -// second tuple `instance` is only used for debugging now -pub fn get_blocks_input( - provider: &Provider, - start_block_number: u32, - num_blocks: u32, - max_depth: usize, -) -> (Vec>, EthBlockHeaderChainInstance) { - assert!(num_blocks <= (1 << max_depth)); - fs::create_dir_all("./data/headers").unwrap(); - let end_block_number = start_block_number + num_blocks - 1; - let rt = Runtime::new().unwrap(); - let chain_id = rt.block_on(provider.get_chainid()).unwrap(); - let path = format!( - "./data/headers/chainid{chain_id}_{start_block_number:06x}_{end_block_number:06x}.json" - ); - - let ProcessedBlock { mut block_rlps, block_hashes, prev_hash } = - if let Ok(f) = File::open(path.as_str()) { - serde_json::from_reader(f).unwrap() - } else { - let mut block_rlps = Vec::with_capacity(max_depth); - let mut block_hashes = Vec::with_capacity(num_blocks as usize); - let mut prev_hash = H256::zero(); - - for block_number in start_block_number..start_block_number + num_blocks { - let block = rt - .block_on(provider.get_block(block_number as u64)) - .expect("get_block JSON-RPC call") - .unwrap_or_else(|| panic!("block {block_number} should exist")); - if block_number == start_block_number { - prev_hash = block.parent_hash; - } - block_hashes.push(block.hash.unwrap()); - block_rlps.push(get_block_rlp(&block)); - } - // write this to file - let file = File::create(path.as_str()).unwrap(); - let payload = ProcessedBlock { block_rlps, block_hashes, prev_hash }; - serde_json::to_writer(file, &payload).unwrap(); - payload - }; - // pad to correct length with dummies - let dummy_block_rlp = block_rlps[0].clone(); - block_rlps.resize(1 << max_depth, dummy_block_rlp); - - let end_hash = *block_hashes.last().unwrap(); - let mmr = get_merkle_mountain_range(&block_hashes, max_depth); - - let instance = EthBlockHeaderChainInstance::new( - prev_hash, - end_hash, - start_block_number, - end_block_number, - mmr, - ); - (block_rlps, instance) -} - -#[cfg(test)] -mod tests { - use std::env::var; - - use super::*; - - #[test] - fn test_infura() { - let infura_id = var("INFURA_ID").expect("Infura ID not found"); - let provider = Provider::::try_from( - format!("https://mainnet.infura.io/v3/{infura_id}").as_str(), - ) - .expect("could not instantiate HTTP Provider"); - - let rt = Runtime::new().unwrap(); - let block = rt.block_on(provider.get_block(17034973)).unwrap().unwrap(); - get_block_rlp(&block); - } - - #[test] - fn test_provider() { - let provider_uri = var("JSON_RPC_URL").expect("JSON_RPC_URL not found"); - let provider = - Provider::::try_from(provider_uri).expect("could not instantiate HTTP Provider"); - - let rt = Runtime::new().unwrap(); - let block = rt.block_on(provider.get_block(17034973)).unwrap().unwrap(); - get_block_rlp(&block); - } -}