From 7dca32603ed139abd4c4708a0eab008d76661bdd Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Fri, 1 Jul 2022 11:09:02 -0600 Subject: [PATCH] feat: add ECDSA component --- .github/workflows/axiom-core-tests.yml | 26 + .github/workflows/axiom-eth-mock-tests.yml | 41 + .github/workflows/axiom-eth-prover-tests.yml | 28 + .github/workflows/axiom-query-tests.yml | 28 + .github/workflows/build.yml | 18 + .github/workflows/lints.yml | 33 + .gitignore | 24 + .gitmodules | 3 + Cargo.toml | 54 + LICENSE | 21 + README.md | 21 + axiom-codec/Cargo.toml | 38 + axiom-codec/axiom-tools | 1 + axiom-codec/build.rs.bak | 84 + axiom-codec/src/constants.rs | 20 + axiom-codec/src/decoder/field_elements.rs | 120 + axiom-codec/src/decoder/mod.rs | 2 + axiom-codec/src/decoder/native.rs | 164 + axiom-codec/src/encoder/field_elements.rs | 347 ++ axiom-codec/src/encoder/mod.rs | 3 + axiom-codec/src/encoder/native.rs | 333 + axiom-codec/src/lib.rs | 16 + axiom-codec/src/special_values.rs | 24 + axiom-codec/src/types/field_elements.rs | 175 + axiom-codec/src/types/mod.rs | 4 + axiom-codec/src/types/native.rs | 151 + axiom-codec/src/utils/mod.rs | 5 + axiom-codec/src/utils/native.rs | 46 + axiom-codec/src/utils/reader.rs | 44 + axiom-codec/src/utils/shim.rs | 30 + axiom-codec/src/utils/writer.rs | 32 + axiom-components/.gitignore | 22 + axiom-components/Cargo.toml | 49 + axiom-components/LICENSE | 21 + axiom-components/README.md | 47 + axiom-components/component-derive/Cargo.toml | 14 + .../component-derive/src/dummy.rs | 36 + .../component-derive/src/flatten.rs | 141 + axiom-components/component-derive/src/lib.rs | 50 + .../component-derive/src/new_component.rs | 142 + .../component-derive/src/params.rs | 30 + axiom-components/rust-toolchain | 1 + axiom-components/rustfmt.toml | 2 + axiom-components/src/ecdsa/mod.rs | 200 + axiom-components/src/ecdsa/test.rs | 98 + axiom-components/src/ecdsa/utils.rs | 178 + axiom-components/src/example/mod.rs | 48 + axiom-components/src/example/test.rs | 79 + axiom-components/src/example_generics/mod.rs | 57 + axiom-components/src/example_generics/test.rs | 71 + axiom-components/src/groth16/mod.rs | 196 + axiom-components/src/groth16/test.rs | 186 + .../src/groth16/test_data/default.json | 99 + .../src/groth16/test_data/default_proof.json | 28 + .../test_data/default_public_inputs.json | 4 + .../src/groth16/test_data/proof.json | 28 + .../src/groth16/test_data/proof_swap_ac.json | 28 + .../src/groth16/test_data/public_inputs.json | 13 + .../test_data/public_inputs_modified.json | 13 + .../src/groth16/test_data/puzzle.json | 144 + .../groth16/test_data/puzzle_modified.json | 144 + axiom-components/src/groth16/utils.rs | 64 + axiom-components/src/lib.rs | 13 + axiom-components/src/scaffold/mod.rs | 211 + axiom-components/src/utils/flatten.rs | 282 + axiom-components/src/utils/mod.rs | 3 + axiom-components/src/utils/testing.rs | 166 + axiom-core/Cargo.toml | 60 + axiom-core/KEYGEN.md | 64 + axiom-core/README.md | 100 + axiom-core/configs/production/core.yml | 8 + .../configs/production/core_historical.yml | 8 + axiom-core/configs/tests/mainnet_3.json | 52 + axiom-core/configs/tests/mainnet_4_3.json | 65 + .../configs/tests/mainnet_5_3_for_evm_0.json | 16 + .../configs/tests/mainnet_5_3_root.json | 78 + axiom-core/configs/tests/multi_block.json | 21 + .../data/headers/default_blocks_goerli.json | 1 + .../data/headers/default_hashes_goerli.json | 1 + .../data/headers/mainnet_10_7_for_evm_1.yul | 1500 +++++ .../data/headers/mainnet_17_7_for_evm_1.yul | 1668 +++++ axiom-core/data/headers/shasums.txt | 21 + axiom-core/data/production/v2.0.12.cids | 26 + .../data/production/v2.0.12.historical.cids | 54 + axiom-core/src/aggregation/final_merkle.rs | 184 + axiom-core/src/aggregation/intermediate.rs | 271 + axiom-core/src/aggregation/mod.rs | 2 + axiom-core/src/bin/keygen.rs | 79 + axiom-core/src/bin/readme.md | 29 + axiom-core/src/bin/rename_snark_verifier.sh | 38 + axiom-core/src/header_chain.rs | 170 + axiom-core/src/keygen/mod.rs | 392 ++ axiom-core/src/lib.rs | 16 + axiom-core/src/tests/chain_instance.rs | 115 + axiom-core/src/tests/integration.rs | 403 ++ axiom-core/src/tests/mod.rs | 103 + axiom-core/src/types.rs | 132 + axiom-eth/.env.example | 2 + axiom-eth/.gitignore | 6 + axiom-eth/Cargo.toml | 93 + axiom-eth/LICENSE | 21 + axiom-eth/README.md | 77 + axiom-eth/configs/bench/mpt.json | 56 + axiom-eth/configs/bench/storage.json | 65 + axiom-eth/configs/bench/transaction.json | 65 + axiom-eth/configs/tests/keccak_shard.json | 32 + .../configs/tests/keccak_shard2_merkle.json | 39 + axiom-eth/configs/tests/one_block.json | 11 + axiom-eth/configs/tests/storage.json | 11 + axiom-eth/configs/tests/storage_mapping.json | 11 + axiom-eth/configs/tests/transaction.json | 11 + .../data/mappings/anvil_dynamic_uint.json | 9 + axiom-eth/data/mappings/block_mapping.t.json | 42 + .../cryptopunks_balance_of_addr_uint.json | 9 + axiom-eth/data/mappings/mapping.t.json | 42 + .../data/mappings/uni_v3_factory_fake.json | 9 + .../uni_v3_factory_get_pool_addr_addr.json | 9 + .../uni_v3_factory_get_pool_addr_uint.json | 8 + .../uni_v3_factory_get_pool_uint_addr.json | 8 + .../unisocks_erc20_balance_of_addr_uint.json | 9 + .../unisocks_erc721_balance_of_addr_uint.json | 9 + .../mappings/weth_allowance_addr_addr.json | 9 + .../mappings/weth_allowance_addr_uint.json | 9 + .../mappings/weth_balance_of_addr_uint.json | 9 + .../weth_balance_of_bytes32_uint.json | 9 + axiom-eth/data/receipts/task.goerli.json | 93 + axiom-eth/data/receipts/task.mainnet.json | 64 + axiom-eth/data/storage/mainnet_10_evm.yul | 1949 ++++++ axiom-eth/data/storage/task.t.json | 16 + axiom-eth/data/transaction/task.t.json | 91 + axiom-eth/data/transaction/test.calldata | 1 + axiom-eth/data/transaction/test.yul | 1794 ++++++ axiom-eth/data/tx_receipts/task.goerli.json | 128 + axiom-eth/data/tx_receipts/task.mainnet.json | 61 + .../proptest-regressions/solidity/tests.txt | 7 + axiom-eth/rustfmt.toml | 2 + axiom-eth/scripts/input_gen/block.json | 5351 +++++++++++++++++ .../scripts/input_gen/default_storage_pf.json | 30 + .../scripts/input_gen/empty_storage_pf.json | 25 + axiom-eth/scripts/input_gen/headers | 1 + .../input_gen/noninclusion_branch_pf.json | 29 + .../input_gen/noninclusion_extension_pf.json | 30 + .../input_gen/noninclusion_extension_pf2.json | 28 + axiom-eth/scripts/input_gen/query_test.sh | 7 + .../scripts/input_gen/query_test_storage.json | 10 + .../scripts/input_gen/serialize_blocks.py | 142 + .../input_gen/small_val_storage_pf.json | 25 + axiom-eth/src/beacon/data_gen/beacon_api.rs | 75 + .../beacon_state_components.json | 23 + .../data_gen/cached_computations/zeroes.json | 42 + axiom-eth/src/beacon/data_gen/mod.rs | 427 ++ axiom-eth/src/beacon/data_gen/test.rs | 346 ++ axiom-eth/src/beacon/mod.rs | 286 + axiom-eth/src/beacon/tests/balance_idx.json | 3 + axiom-eth/src/beacon/tests/balance_test.json | 100 + .../beacon/tests/balance_test_sandbox.json | 100 + .../beacon/tests/generated_tests/pair.json | 4 + .../tests/generated_tests/validator.json | 10 + axiom-eth/src/beacon/tests/info_idx.json | 3 + axiom-eth/src/beacon/tests/info_test.json | 109 + .../src/beacon/tests/info_test_sandbox.json | 109 + axiom-eth/src/beacon/tests/mod.rs | 244 + axiom-eth/src/beacon/tests/readme.md | 9 + axiom-eth/src/beacon/types.rs | 157 + axiom-eth/src/block_header/mod.rs | 578 ++ axiom-eth/src/block_header/tests.rs | 192 + axiom-eth/src/keccak/README.md | 12 + axiom-eth/src/keccak/component_shim.rs | 85 + axiom-eth/src/keccak/mod.rs | 269 + axiom-eth/src/keccak/promise.rs | 343 ++ axiom-eth/src/keccak/tests.rs | 186 + axiom-eth/src/keccak/types.rs | 394 ++ axiom-eth/src/lib.rs | 30 + axiom-eth/src/mpt/mod.rs | 1015 ++++ axiom-eth/src/mpt/tests/README.md | 7 + axiom-eth/src/mpt/tests/mod.rs | 231 + axiom-eth/src/mpt/tests/tx.rs | 445 ++ axiom-eth/src/mpt/types/input.rs | 224 + axiom-eth/src/mpt/types/mod.rs | 249 + axiom-eth/src/providers/account.rs | 55 + axiom-eth/src/providers/block.rs | 136 + axiom-eth/src/providers/mod.rs | 38 + axiom-eth/src/providers/receipt.rs | 471 ++ axiom-eth/src/providers/storage.rs | 272 + axiom-eth/src/providers/transaction.rs | 540 ++ axiom-eth/src/receipt/mod.rs | 283 + .../data/field/single_rc_pos_test_legacy.json | 5 + .../data/field/single_rc_pos_test_new.json | 5 + .../tests/data/multi_tx_pos_test_legacy.json | 5 + .../tests/data/multi_tx_pos_test_new.json | 5 + .../tests/data/single_rc_pos_test_legacy.json | 3 + .../tests/data/single_rc_pos_test_new.json | 3 + .../src/receipt/tests/data/stress_test.json | 1 + .../tests/data/zero_tx_pos_test_legacy.json | 5 + .../tests/data/zero_tx_pos_test_new.json | 5 + axiom-eth/src/receipt/tests/field.rs | 215 + axiom-eth/src/receipt/tests/mod.rs | 185 + axiom-eth/src/receipt/types.rs | 103 + axiom-eth/src/rlc/chip.rs | 387 ++ axiom-eth/src/rlc/circuit/builder.rs | 278 + axiom-eth/src/rlc/circuit/instructions.rs | 103 + axiom-eth/src/rlc/circuit/mod.rs | 105 + axiom-eth/src/rlc/concat_array.rs | 61 + axiom-eth/src/rlc/mod.rs | 19 + axiom-eth/src/rlc/tests/mod.rs | 160 + axiom-eth/src/rlc/tests/utils/executor.rs | 119 + axiom-eth/src/rlc/tests/utils/mod.rs | 3 + axiom-eth/src/rlc/tests/utils/two_phase.rs | 89 + axiom-eth/src/rlc/types.rs | 102 + axiom-eth/src/rlc/virtual_region/manager.rs | 165 + axiom-eth/src/rlc/virtual_region/mod.rs | 10 + axiom-eth/src/rlp/mod.rs | 604 ++ .../rlp/test_data/array_of_literals_big.json | 18 + .../src/rlp/test_data/arrays_and_fields.json | 42 + .../src/rlp/test_data/list_of_strings.json | 12 + .../src/rlp/test_data/nested_arrays.json | 12 + .../src/rlp/test_data/nested_arrays_big.json | 16 + axiom-eth/src/rlp/tests/combo.rs | 74 + axiom-eth/src/rlp/tests/list.rs | 177 + axiom-eth/src/rlp/tests/mod.rs | 24 + axiom-eth/src/rlp/tests/string.rs | 122 + axiom-eth/src/rlp/types.rs | 90 + axiom-eth/src/sha256/mod.rs | 226 + axiom-eth/src/solidity/array_types.rs | 59 + axiom-eth/src/solidity/mod.rs | 366 ++ axiom-eth/src/solidity/tests/mapping.rs | 138 + .../src/solidity/tests/mapping_storage.rs | 129 + axiom-eth/src/solidity/tests/mod.rs | 36 + .../src/solidity/tests/nested_mappings.rs | 172 + axiom-eth/src/solidity/tests/prop_pos.rs | 122 + axiom-eth/src/solidity/tests/utils.rs | 237 + axiom-eth/src/solidity/types.rs | 72 + axiom-eth/src/ssz/mod.rs | 361 ++ axiom-eth/src/ssz/tests/basic_types/bool.json | 5 + axiom-eth/src/ssz/tests/basic_types/byte.json | 5 + .../src/ssz/tests/basic_types/neg_bool.json | 5 + .../src/ssz/tests/basic_types/neg_byte.json | 5 + axiom-eth/src/ssz/tests/basic_types/u16.json | 5 + axiom-eth/src/ssz/tests/basic_types/u64.json | 5 + .../src/ssz/tests/lists/bad_len_list.json | 13 + axiom-eth/src/ssz/tests/lists/empty_list.json | 7 + .../src/ssz/tests/lists/unfull_list.json | 13 + .../tests/merkle_proof/incorrect_proof.json | 14 + .../tests/merkle_proof/misdirected_proof.json | 14 + .../src/ssz/tests/merkle_proof/proof.json | 14 + .../tests/merkle_proof/real_beacon_proof.json | 100 + .../ssz/tests/merkle_proof/real_proof.json | 90 + axiom-eth/src/ssz/tests/mod.rs | 272 + axiom-eth/src/ssz/tests/test_circuits.rs | 202 + axiom-eth/src/ssz/tests/vectors/six_u64.json | 12 + axiom-eth/src/ssz/types.rs | 384 ++ axiom-eth/src/storage/circuit.rs | 161 + axiom-eth/src/storage/mod.rs | 470 ++ axiom-eth/src/storage/tests.rs | 190 + axiom-eth/src/transaction/mod.rs | 519 ++ .../data/field/single_tx_pos_test_legacy.json | 9 + .../data/field/single_tx_pos_test_new.json | 9 + .../tests/data/multi_tx_pos_test_legacy.json | 5 + .../tests/data/multi_tx_pos_test_new.json | 5 + .../tests/data/single_tx_pos_test_legacy.json | 5 + .../tests/data/single_tx_pos_test_new.json | 5 + .../transaction/tests/data/stress_test.json | 1 + .../tests/data/zero_tx_pos_test_legacy.json | 5 + .../tests/data/zero_tx_pos_test_new.json | 5 + axiom-eth/src/transaction/tests/field.rs | 260 + axiom-eth/src/transaction/tests/mod.rs | 458 ++ axiom-eth/src/transaction/types.rs | 133 + axiom-eth/src/utils/README.md | 71 + .../src/utils/build_utils/aggregation.rs | 20 + axiom-eth/src/utils/build_utils/dummy.rs | 6 + axiom-eth/src/utils/build_utils/keygen.rs | 118 + axiom-eth/src/utils/build_utils/mod.rs | 8 + axiom-eth/src/utils/build_utils/pinning.rs | 254 + axiom-eth/src/utils/circuit_utils/bytes.rs | 126 + axiom-eth/src/utils/circuit_utils/mod.rs | 217 + .../component/circuit/comp_circuit_impl.rs | 441 ++ axiom-eth/src/utils/component/circuit/mod.rs | 139 + axiom-eth/src/utils/component/mod.rs | 279 + axiom-eth/src/utils/component/param.rs | 4 + .../src/utils/component/promise_collector.rs | 229 + .../utils/component/promise_loader/combo.rs | 98 + .../component/promise_loader/comp_loader.rs | 372 ++ .../utils/component/promise_loader/empty.rs | 71 + .../src/utils/component/promise_loader/mod.rs | 23 + .../utils/component/promise_loader/multi.rs | 337 ++ .../utils/component/promise_loader/single.rs | 205 + .../component/promise_loader/tests/combo.rs | 43 + .../promise_loader/tests/comp_loader.rs | 242 + .../component/promise_loader/tests/empty.rs | 10 + .../component/promise_loader/tests/mod.rs | 5 + .../component/promise_loader/tests/multi.rs | 74 + .../component/promise_loader/tests/single.rs | 29 + .../utils/component/promise_loader/utils.rs | 60 + .../src/utils/component/tests/collector.rs | 114 + .../src/utils/component/tests/dummy_comp.rs | 300 + axiom-eth/src/utils/component/tests/mod.rs | 276 + .../src/utils/component/tests/sum_comp.rs | 135 + axiom-eth/src/utils/component/types.rs | 266 + axiom-eth/src/utils/component/utils.rs | 168 + axiom-eth/src/utils/eth_circuit.rs | 471 ++ axiom-eth/src/utils/hilo.rs | 94 + axiom-eth/src/utils/keccak/decorator.rs | 598 ++ axiom-eth/src/utils/keccak/mod.rs | 57 + axiom-eth/src/utils/keccak/tests/merkle.rs | 117 + axiom-eth/src/utils/keccak/tests/mod.rs | 2 + axiom-eth/src/utils/keccak/tests/shard.rs | 68 + axiom-eth/src/utils/merkle_aggregation.rs | 291 + axiom-eth/src/utils/mod.rs | 261 + axiom-eth/src/utils/snark_verifier.rs | 303 + axiom-query/Cargo.toml | 77 + axiom-query/KEYGEN.md | 64 + axiom-query/README.md | 284 + .../production/all_128_each_default.yml | 326 + .../production/all_32_each_default.yml | 243 + axiom-query/configs/production/all_large.yml | 282 + axiom-query/configs/production/all_max.yml | 271 + axiom-query/configs/production/all_small.yml | 192 + .../configs/templates/account.leaf.yml | 15 + axiom-query/configs/templates/axiom_agg_1.yml | 104 + axiom-query/configs/templates/axiom_agg_2.yml | 106 + axiom-query/configs/templates/header.leaf.yml | 12 + axiom-query/configs/templates/header.tree.yml | 20 + axiom-query/configs/templates/keccak.leaf.yml | 6 + axiom-query/configs/templates/keccak.tree.yml | 9 + .../configs/templates/receipt.leaf.yml | 22 + .../configs/templates/results_root.leaf.yml | 28 + .../templates/soliditymapping.leaf.yml | 15 + .../configs/templates/storage.leaf.yml | 15 + .../configs/templates/subquery_agg.yml | 36 + .../configs/templates/verifycompute.yml | 21 + .../test/axiom_aggregation1_for_agg.json | 21 + .../configs/test/axiom_aggregation2.json | 12 + axiom-query/configs/test/header_subquery.json | 39 + .../test/header_subquery_core_params.json | 4 + .../configs/test/header_subquery_for_agg.json | 39 + .../test/header_subquery_loader_params.json | 8 + axiom-query/configs/test/keccak_for_agg.json | 29 + axiom-query/configs/test/results_root.json | 59 + .../configs/test/results_root_for_agg.json | 38 + .../test/subquery_aggregation_for_agg.json | 31 + .../configs/test/verify_compute_for_agg.json | 46 + .../data/production/aggregate_vk_hashes.json | 7 + .../data/production/all_128_each_default.tree | 765 +++ .../data/production/all_32_each_default.tree | 198 + axiom-query/data/production/all_large.tree | 369 ++ axiom-query/data/production/all_max.tree | 316 + axiom-query/data/production/all_small.tree | 59 + .../axiom_aggregation1_for_agg.snark.json | 1 + .../data/test/input_header_for_agg.json | 2157 +++++++ .../test/input_keccak_shard_for_header.json | 137 + .../data/test/input_mmr_proof_for_header.json | 47 + axiom-query/data/test/input_results_root.json | 671 +++ .../data/test/input_results_root_for_agg.json | 73 + .../data/test/input_verify_compute.json | 1 + .../test/input_verify_compute_for_agg.json | 1 + .../test/promise_results_header_for_agg.json | 25 + .../test/promise_results_keccak_for_agg.json | 1 + .../subquery_aggregation_for_agg.snark.json | 1 + axiom-query/src/axiom_aggregation1/circuit.rs | 138 + axiom-query/src/axiom_aggregation1/mod.rs | 14 + axiom-query/src/axiom_aggregation1/tests.rs | 171 + axiom-query/src/axiom_aggregation1/types.rs | 75 + axiom-query/src/axiom_aggregation2/circuit.rs | 80 + .../axiom_aggregation2/integration_test.sh | 27 + axiom-query/src/axiom_aggregation2/mod.rs | 10 + axiom-query/src/axiom_aggregation2/readme.md | 18 + axiom-query/src/axiom_aggregation2/tests.rs | 138 + axiom-query/src/bin/keygen.rs | 81 + axiom-query/src/bin/readme.md | 22 + axiom-query/src/bin/rename_snark_verifier.sh | 27 + axiom-query/src/components/mod.rs | 17 + axiom-query/src/components/results/circuit.rs | 201 + axiom-query/src/components/results/mod.rs | 27 + .../src/components/results/results_root.rs | 137 + .../src/components/results/subquery_hash.rs | 142 + .../src/components/results/table/join.rs | 115 + .../src/components/results/table/mod.rs | 54 + axiom-query/src/components/results/tests.rs | 476 ++ axiom-query/src/components/results/types.rs | 273 + .../components/subqueries/account/circuit.rs | 298 + .../src/components/subqueries/account/mod.rs | 40 + .../components/subqueries/account/tests.rs | 219 + .../components/subqueries/account/types.rs | 129 + .../subqueries/block_header/circuit.rs | 372 ++ .../subqueries/block_header/mmr_verify.rs | 166 + .../components/subqueries/block_header/mod.rs | 46 + .../subqueries/block_header/tests.rs | 150 + .../subqueries/block_header/types.rs | 163 + .../src/components/subqueries/common.rs | 96 + axiom-query/src/components/subqueries/mod.rs | 8 + .../components/subqueries/receipt/circuit.rs | 568 ++ .../src/components/subqueries/receipt/mod.rs | 38 + .../components/subqueries/receipt/tests.rs | 331 + .../components/subqueries/receipt/types.rs | 136 + .../subqueries/solidity_mappings/circuit.rs | 240 + .../subqueries/solidity_mappings/mod.rs | 16 + .../subqueries/solidity_mappings/tests.rs | 158 + .../subqueries/solidity_mappings/types.rs | 99 + .../components/subqueries/storage/circuit.rs | 260 + .../src/components/subqueries/storage/mod.rs | 15 + .../components/subqueries/storage/tests.rs | 208 + .../components/subqueries/storage/types.rs | 112 + .../subqueries/transaction/circuit.rs | 524 ++ .../components/subqueries/transaction/mod.rs | 53 + .../subqueries/transaction/tests.rs | 251 + .../subqueries/transaction/types.rs | 123 + axiom-query/src/global_constants.rs | 2 + axiom-query/src/keygen/agg/axiom_agg_1.rs | 200 + axiom-query/src/keygen/agg/axiom_agg_2.rs | 143 + axiom-query/src/keygen/agg/common.rs | 105 + axiom-query/src/keygen/agg/mod.rs | 127 + axiom-query/src/keygen/agg/single_type.rs | 111 + axiom-query/src/keygen/agg/subquery_agg.rs | 203 + axiom-query/src/keygen/mod.rs | 60 + axiom-query/src/keygen/shard/keccak.rs | 95 + axiom-query/src/keygen/shard/mod.rs | 314 + axiom-query/src/lib.rs | 24 + .../src/subquery_aggregation/circuit.rs | 251 + axiom-query/src/subquery_aggregation/mod.rs | 13 + axiom-query/src/subquery_aggregation/tests.rs | 353 ++ axiom-query/src/subquery_aggregation/types.rs | 118 + .../utils/client_circuit/default_circuit.rs | 109 + .../src/utils/client_circuit/metadata.rs | 155 + axiom-query/src/utils/client_circuit/mod.rs | 12 + axiom-query/src/utils/client_circuit/vkey.rs | 77 + axiom-query/src/utils/codec.rs | 70 + axiom-query/src/utils/mod.rs | 4 + axiom-query/src/verify_compute/README.md | 214 + axiom-query/src/verify_compute/circuit.rs | 363 ++ axiom-query/src/verify_compute/mod.rs | 16 + axiom-query/src/verify_compute/query_hash.rs | 400 ++ .../src/verify_compute/tests/aggregation.rs | 259 + axiom-query/src/verify_compute/tests/mod.rs | 455 ++ axiom-query/src/verify_compute/tests/prove.rs | 112 + axiom-query/src/verify_compute/tests/utils.rs | 138 + axiom-query/src/verify_compute/types.rs | 256 + axiom-query/src/verify_compute/utils.rs | 333 + rust-toolchain | 1 + rustfmt.toml | 2 + trusted_setup_s3.sh | 8 + 440 files changed, 64322 insertions(+) create mode 100644 .github/workflows/axiom-core-tests.yml create mode 100644 .github/workflows/axiom-eth-mock-tests.yml create mode 100644 .github/workflows/axiom-eth-prover-tests.yml create mode 100644 .github/workflows/axiom-query-tests.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/lints.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 axiom-codec/Cargo.toml create mode 160000 axiom-codec/axiom-tools create mode 100644 axiom-codec/build.rs.bak create mode 100644 axiom-codec/src/constants.rs create mode 100644 axiom-codec/src/decoder/field_elements.rs create mode 100644 axiom-codec/src/decoder/mod.rs create mode 100644 axiom-codec/src/decoder/native.rs create mode 100644 axiom-codec/src/encoder/field_elements.rs create mode 100644 axiom-codec/src/encoder/mod.rs create mode 100644 axiom-codec/src/encoder/native.rs create mode 100644 axiom-codec/src/lib.rs create mode 100644 axiom-codec/src/special_values.rs create mode 100644 axiom-codec/src/types/field_elements.rs create mode 100644 axiom-codec/src/types/mod.rs create mode 100644 axiom-codec/src/types/native.rs create mode 100644 axiom-codec/src/utils/mod.rs create mode 100644 axiom-codec/src/utils/native.rs create mode 100644 axiom-codec/src/utils/reader.rs create mode 100644 axiom-codec/src/utils/shim.rs create mode 100644 axiom-codec/src/utils/writer.rs create mode 100644 axiom-components/.gitignore create mode 100644 axiom-components/Cargo.toml create mode 100644 axiom-components/LICENSE create mode 100644 axiom-components/README.md create mode 100644 axiom-components/component-derive/Cargo.toml create mode 100644 axiom-components/component-derive/src/dummy.rs create mode 100644 axiom-components/component-derive/src/flatten.rs create mode 100644 axiom-components/component-derive/src/lib.rs create mode 100644 axiom-components/component-derive/src/new_component.rs create mode 100644 axiom-components/component-derive/src/params.rs create mode 100644 axiom-components/rust-toolchain create mode 100644 axiom-components/rustfmt.toml create mode 100644 axiom-components/src/ecdsa/mod.rs create mode 100644 axiom-components/src/ecdsa/test.rs create mode 100644 axiom-components/src/ecdsa/utils.rs create mode 100644 axiom-components/src/example/mod.rs create mode 100644 axiom-components/src/example/test.rs create mode 100644 axiom-components/src/example_generics/mod.rs create mode 100644 axiom-components/src/example_generics/test.rs create mode 100644 axiom-components/src/groth16/mod.rs create mode 100644 axiom-components/src/groth16/test.rs create mode 100644 axiom-components/src/groth16/test_data/default.json create mode 100644 axiom-components/src/groth16/test_data/default_proof.json create mode 100644 axiom-components/src/groth16/test_data/default_public_inputs.json create mode 100644 axiom-components/src/groth16/test_data/proof.json create mode 100644 axiom-components/src/groth16/test_data/proof_swap_ac.json create mode 100644 axiom-components/src/groth16/test_data/public_inputs.json create mode 100644 axiom-components/src/groth16/test_data/public_inputs_modified.json create mode 100644 axiom-components/src/groth16/test_data/puzzle.json create mode 100644 axiom-components/src/groth16/test_data/puzzle_modified.json create mode 100644 axiom-components/src/groth16/utils.rs create mode 100644 axiom-components/src/lib.rs create mode 100644 axiom-components/src/scaffold/mod.rs create mode 100644 axiom-components/src/utils/flatten.rs create mode 100644 axiom-components/src/utils/mod.rs create mode 100644 axiom-components/src/utils/testing.rs create mode 100644 axiom-core/Cargo.toml create mode 100644 axiom-core/KEYGEN.md create mode 100644 axiom-core/README.md create mode 100644 axiom-core/configs/production/core.yml create mode 100644 axiom-core/configs/production/core_historical.yml create mode 100644 axiom-core/configs/tests/mainnet_3.json create mode 100644 axiom-core/configs/tests/mainnet_4_3.json create mode 100644 axiom-core/configs/tests/mainnet_5_3_for_evm_0.json create mode 100644 axiom-core/configs/tests/mainnet_5_3_root.json create mode 100644 axiom-core/configs/tests/multi_block.json create mode 100644 axiom-core/data/headers/default_blocks_goerli.json create mode 100644 axiom-core/data/headers/default_hashes_goerli.json create mode 100644 axiom-core/data/headers/mainnet_10_7_for_evm_1.yul create mode 100644 axiom-core/data/headers/mainnet_17_7_for_evm_1.yul create mode 100644 axiom-core/data/headers/shasums.txt create mode 100644 axiom-core/data/production/v2.0.12.cids create mode 100644 axiom-core/data/production/v2.0.12.historical.cids create mode 100644 axiom-core/src/aggregation/final_merkle.rs create mode 100644 axiom-core/src/aggregation/intermediate.rs create mode 100644 axiom-core/src/aggregation/mod.rs create mode 100644 axiom-core/src/bin/keygen.rs create mode 100644 axiom-core/src/bin/readme.md create mode 100644 axiom-core/src/bin/rename_snark_verifier.sh create mode 100644 axiom-core/src/header_chain.rs create mode 100644 axiom-core/src/keygen/mod.rs create mode 100644 axiom-core/src/lib.rs create mode 100644 axiom-core/src/tests/chain_instance.rs create mode 100644 axiom-core/src/tests/integration.rs create mode 100644 axiom-core/src/tests/mod.rs create mode 100644 axiom-core/src/types.rs create mode 100644 axiom-eth/.env.example create mode 100644 axiom-eth/.gitignore create mode 100644 axiom-eth/Cargo.toml create mode 100644 axiom-eth/LICENSE create mode 100644 axiom-eth/README.md create mode 100644 axiom-eth/configs/bench/mpt.json create mode 100644 axiom-eth/configs/bench/storage.json create mode 100644 axiom-eth/configs/bench/transaction.json create mode 100644 axiom-eth/configs/tests/keccak_shard.json create mode 100644 axiom-eth/configs/tests/keccak_shard2_merkle.json create mode 100644 axiom-eth/configs/tests/one_block.json create mode 100644 axiom-eth/configs/tests/storage.json create mode 100644 axiom-eth/configs/tests/storage_mapping.json create mode 100644 axiom-eth/configs/tests/transaction.json create mode 100644 axiom-eth/data/mappings/anvil_dynamic_uint.json create mode 100644 axiom-eth/data/mappings/block_mapping.t.json create mode 100644 axiom-eth/data/mappings/cryptopunks_balance_of_addr_uint.json create mode 100644 axiom-eth/data/mappings/mapping.t.json create mode 100644 axiom-eth/data/mappings/uni_v3_factory_fake.json create mode 100644 axiom-eth/data/mappings/uni_v3_factory_get_pool_addr_addr.json create mode 100644 axiom-eth/data/mappings/uni_v3_factory_get_pool_addr_uint.json create mode 100644 axiom-eth/data/mappings/uni_v3_factory_get_pool_uint_addr.json create mode 100644 axiom-eth/data/mappings/unisocks_erc20_balance_of_addr_uint.json create mode 100644 axiom-eth/data/mappings/unisocks_erc721_balance_of_addr_uint.json create mode 100644 axiom-eth/data/mappings/weth_allowance_addr_addr.json create mode 100644 axiom-eth/data/mappings/weth_allowance_addr_uint.json create mode 100644 axiom-eth/data/mappings/weth_balance_of_addr_uint.json create mode 100644 axiom-eth/data/mappings/weth_balance_of_bytes32_uint.json create mode 100644 axiom-eth/data/receipts/task.goerli.json create mode 100644 axiom-eth/data/receipts/task.mainnet.json create mode 100644 axiom-eth/data/storage/mainnet_10_evm.yul create mode 100644 axiom-eth/data/storage/task.t.json create mode 100644 axiom-eth/data/transaction/task.t.json create mode 100644 axiom-eth/data/transaction/test.calldata create mode 100644 axiom-eth/data/transaction/test.yul create mode 100644 axiom-eth/data/tx_receipts/task.goerli.json create mode 100644 axiom-eth/data/tx_receipts/task.mainnet.json create mode 100644 axiom-eth/proptest-regressions/solidity/tests.txt create mode 100644 axiom-eth/rustfmt.toml create mode 100644 axiom-eth/scripts/input_gen/block.json create mode 100644 axiom-eth/scripts/input_gen/default_storage_pf.json create mode 100644 axiom-eth/scripts/input_gen/empty_storage_pf.json create mode 120000 axiom-eth/scripts/input_gen/headers create mode 100644 axiom-eth/scripts/input_gen/noninclusion_branch_pf.json create mode 100644 axiom-eth/scripts/input_gen/noninclusion_extension_pf.json create mode 100644 axiom-eth/scripts/input_gen/noninclusion_extension_pf2.json create mode 100755 axiom-eth/scripts/input_gen/query_test.sh create mode 100644 axiom-eth/scripts/input_gen/query_test_storage.json create mode 100644 axiom-eth/scripts/input_gen/serialize_blocks.py create mode 100644 axiom-eth/scripts/input_gen/small_val_storage_pf.json create mode 100644 axiom-eth/src/beacon/data_gen/beacon_api.rs create mode 100644 axiom-eth/src/beacon/data_gen/cached_computations/beacon_state_components.json create mode 100644 axiom-eth/src/beacon/data_gen/cached_computations/zeroes.json create mode 100644 axiom-eth/src/beacon/data_gen/mod.rs create mode 100644 axiom-eth/src/beacon/data_gen/test.rs create mode 100644 axiom-eth/src/beacon/mod.rs create mode 100644 axiom-eth/src/beacon/tests/balance_idx.json create mode 100644 axiom-eth/src/beacon/tests/balance_test.json create mode 100644 axiom-eth/src/beacon/tests/balance_test_sandbox.json create mode 100644 axiom-eth/src/beacon/tests/generated_tests/pair.json create mode 100644 axiom-eth/src/beacon/tests/generated_tests/validator.json create mode 100644 axiom-eth/src/beacon/tests/info_idx.json create mode 100644 axiom-eth/src/beacon/tests/info_test.json create mode 100644 axiom-eth/src/beacon/tests/info_test_sandbox.json create mode 100644 axiom-eth/src/beacon/tests/mod.rs create mode 100644 axiom-eth/src/beacon/tests/readme.md create mode 100644 axiom-eth/src/beacon/types.rs create mode 100644 axiom-eth/src/block_header/mod.rs create mode 100644 axiom-eth/src/block_header/tests.rs create mode 100644 axiom-eth/src/keccak/README.md create mode 100644 axiom-eth/src/keccak/component_shim.rs create mode 100644 axiom-eth/src/keccak/mod.rs create mode 100644 axiom-eth/src/keccak/promise.rs create mode 100644 axiom-eth/src/keccak/tests.rs create mode 100644 axiom-eth/src/keccak/types.rs create mode 100644 axiom-eth/src/lib.rs create mode 100644 axiom-eth/src/mpt/mod.rs create mode 100644 axiom-eth/src/mpt/tests/README.md create mode 100644 axiom-eth/src/mpt/tests/mod.rs create mode 100644 axiom-eth/src/mpt/tests/tx.rs create mode 100644 axiom-eth/src/mpt/types/input.rs create mode 100644 axiom-eth/src/mpt/types/mod.rs create mode 100644 axiom-eth/src/providers/account.rs create mode 100644 axiom-eth/src/providers/block.rs create mode 100644 axiom-eth/src/providers/mod.rs create mode 100644 axiom-eth/src/providers/receipt.rs create mode 100644 axiom-eth/src/providers/storage.rs create mode 100644 axiom-eth/src/providers/transaction.rs create mode 100644 axiom-eth/src/receipt/mod.rs create mode 100644 axiom-eth/src/receipt/tests/data/field/single_rc_pos_test_legacy.json create mode 100644 axiom-eth/src/receipt/tests/data/field/single_rc_pos_test_new.json create mode 100644 axiom-eth/src/receipt/tests/data/multi_tx_pos_test_legacy.json create mode 100644 axiom-eth/src/receipt/tests/data/multi_tx_pos_test_new.json create mode 100644 axiom-eth/src/receipt/tests/data/single_rc_pos_test_legacy.json create mode 100644 axiom-eth/src/receipt/tests/data/single_rc_pos_test_new.json create mode 100644 axiom-eth/src/receipt/tests/data/stress_test.json create mode 100644 axiom-eth/src/receipt/tests/data/zero_tx_pos_test_legacy.json create mode 100644 axiom-eth/src/receipt/tests/data/zero_tx_pos_test_new.json create mode 100644 axiom-eth/src/receipt/tests/field.rs create mode 100644 axiom-eth/src/receipt/tests/mod.rs create mode 100644 axiom-eth/src/receipt/types.rs create mode 100644 axiom-eth/src/rlc/chip.rs create mode 100644 axiom-eth/src/rlc/circuit/builder.rs create mode 100644 axiom-eth/src/rlc/circuit/instructions.rs create mode 100644 axiom-eth/src/rlc/circuit/mod.rs create mode 100644 axiom-eth/src/rlc/concat_array.rs create mode 100644 axiom-eth/src/rlc/mod.rs create mode 100644 axiom-eth/src/rlc/tests/mod.rs create mode 100644 axiom-eth/src/rlc/tests/utils/executor.rs create mode 100644 axiom-eth/src/rlc/tests/utils/mod.rs create mode 100644 axiom-eth/src/rlc/tests/utils/two_phase.rs create mode 100644 axiom-eth/src/rlc/types.rs create mode 100644 axiom-eth/src/rlc/virtual_region/manager.rs create mode 100644 axiom-eth/src/rlc/virtual_region/mod.rs create mode 100644 axiom-eth/src/rlp/mod.rs create mode 100644 axiom-eth/src/rlp/test_data/array_of_literals_big.json create mode 100644 axiom-eth/src/rlp/test_data/arrays_and_fields.json create mode 100644 axiom-eth/src/rlp/test_data/list_of_strings.json create mode 100644 axiom-eth/src/rlp/test_data/nested_arrays.json create mode 100644 axiom-eth/src/rlp/test_data/nested_arrays_big.json create mode 100644 axiom-eth/src/rlp/tests/combo.rs create mode 100644 axiom-eth/src/rlp/tests/list.rs create mode 100644 axiom-eth/src/rlp/tests/mod.rs create mode 100644 axiom-eth/src/rlp/tests/string.rs create mode 100644 axiom-eth/src/rlp/types.rs create mode 100644 axiom-eth/src/sha256/mod.rs create mode 100644 axiom-eth/src/solidity/array_types.rs create mode 100644 axiom-eth/src/solidity/mod.rs create mode 100644 axiom-eth/src/solidity/tests/mapping.rs create mode 100644 axiom-eth/src/solidity/tests/mapping_storage.rs create mode 100644 axiom-eth/src/solidity/tests/mod.rs create mode 100644 axiom-eth/src/solidity/tests/nested_mappings.rs create mode 100644 axiom-eth/src/solidity/tests/prop_pos.rs create mode 100644 axiom-eth/src/solidity/tests/utils.rs create mode 100644 axiom-eth/src/solidity/types.rs create mode 100644 axiom-eth/src/ssz/mod.rs create mode 100644 axiom-eth/src/ssz/tests/basic_types/bool.json create mode 100644 axiom-eth/src/ssz/tests/basic_types/byte.json create mode 100644 axiom-eth/src/ssz/tests/basic_types/neg_bool.json create mode 100644 axiom-eth/src/ssz/tests/basic_types/neg_byte.json create mode 100644 axiom-eth/src/ssz/tests/basic_types/u16.json create mode 100644 axiom-eth/src/ssz/tests/basic_types/u64.json create mode 100644 axiom-eth/src/ssz/tests/lists/bad_len_list.json create mode 100644 axiom-eth/src/ssz/tests/lists/empty_list.json create mode 100644 axiom-eth/src/ssz/tests/lists/unfull_list.json create mode 100644 axiom-eth/src/ssz/tests/merkle_proof/incorrect_proof.json create mode 100644 axiom-eth/src/ssz/tests/merkle_proof/misdirected_proof.json create mode 100644 axiom-eth/src/ssz/tests/merkle_proof/proof.json create mode 100644 axiom-eth/src/ssz/tests/merkle_proof/real_beacon_proof.json create mode 100644 axiom-eth/src/ssz/tests/merkle_proof/real_proof.json create mode 100644 axiom-eth/src/ssz/tests/mod.rs create mode 100644 axiom-eth/src/ssz/tests/test_circuits.rs create mode 100644 axiom-eth/src/ssz/tests/vectors/six_u64.json create mode 100644 axiom-eth/src/ssz/types.rs create mode 100644 axiom-eth/src/storage/circuit.rs create mode 100644 axiom-eth/src/storage/mod.rs create mode 100644 axiom-eth/src/storage/tests.rs create mode 100644 axiom-eth/src/transaction/mod.rs create mode 100644 axiom-eth/src/transaction/tests/data/field/single_tx_pos_test_legacy.json create mode 100644 axiom-eth/src/transaction/tests/data/field/single_tx_pos_test_new.json create mode 100644 axiom-eth/src/transaction/tests/data/multi_tx_pos_test_legacy.json create mode 100644 axiom-eth/src/transaction/tests/data/multi_tx_pos_test_new.json create mode 100644 axiom-eth/src/transaction/tests/data/single_tx_pos_test_legacy.json create mode 100644 axiom-eth/src/transaction/tests/data/single_tx_pos_test_new.json create mode 100644 axiom-eth/src/transaction/tests/data/stress_test.json create mode 100644 axiom-eth/src/transaction/tests/data/zero_tx_pos_test_legacy.json create mode 100644 axiom-eth/src/transaction/tests/data/zero_tx_pos_test_new.json create mode 100644 axiom-eth/src/transaction/tests/field.rs create mode 100644 axiom-eth/src/transaction/tests/mod.rs create mode 100644 axiom-eth/src/transaction/types.rs create mode 100644 axiom-eth/src/utils/README.md create mode 100644 axiom-eth/src/utils/build_utils/aggregation.rs create mode 100644 axiom-eth/src/utils/build_utils/dummy.rs create mode 100644 axiom-eth/src/utils/build_utils/keygen.rs create mode 100644 axiom-eth/src/utils/build_utils/mod.rs create mode 100644 axiom-eth/src/utils/build_utils/pinning.rs create mode 100644 axiom-eth/src/utils/circuit_utils/bytes.rs create mode 100644 axiom-eth/src/utils/circuit_utils/mod.rs create mode 100644 axiom-eth/src/utils/component/circuit/comp_circuit_impl.rs create mode 100644 axiom-eth/src/utils/component/circuit/mod.rs create mode 100644 axiom-eth/src/utils/component/mod.rs create mode 100644 axiom-eth/src/utils/component/param.rs create mode 100644 axiom-eth/src/utils/component/promise_collector.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/combo.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/comp_loader.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/empty.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/mod.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/multi.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/single.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/tests/combo.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/tests/comp_loader.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/tests/empty.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/tests/mod.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/tests/multi.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/tests/single.rs create mode 100644 axiom-eth/src/utils/component/promise_loader/utils.rs create mode 100644 axiom-eth/src/utils/component/tests/collector.rs create mode 100644 axiom-eth/src/utils/component/tests/dummy_comp.rs create mode 100644 axiom-eth/src/utils/component/tests/mod.rs create mode 100644 axiom-eth/src/utils/component/tests/sum_comp.rs create mode 100644 axiom-eth/src/utils/component/types.rs create mode 100644 axiom-eth/src/utils/component/utils.rs create mode 100644 axiom-eth/src/utils/eth_circuit.rs create mode 100644 axiom-eth/src/utils/hilo.rs create mode 100644 axiom-eth/src/utils/keccak/decorator.rs create mode 100644 axiom-eth/src/utils/keccak/mod.rs create mode 100644 axiom-eth/src/utils/keccak/tests/merkle.rs create mode 100644 axiom-eth/src/utils/keccak/tests/mod.rs create mode 100644 axiom-eth/src/utils/keccak/tests/shard.rs create mode 100644 axiom-eth/src/utils/merkle_aggregation.rs create mode 100644 axiom-eth/src/utils/mod.rs create mode 100644 axiom-eth/src/utils/snark_verifier.rs create mode 100644 axiom-query/Cargo.toml create mode 100644 axiom-query/KEYGEN.md create mode 100644 axiom-query/README.md create mode 100644 axiom-query/configs/production/all_128_each_default.yml create mode 100644 axiom-query/configs/production/all_32_each_default.yml create mode 100644 axiom-query/configs/production/all_large.yml create mode 100644 axiom-query/configs/production/all_max.yml create mode 100644 axiom-query/configs/production/all_small.yml create mode 100644 axiom-query/configs/templates/account.leaf.yml create mode 100644 axiom-query/configs/templates/axiom_agg_1.yml create mode 100644 axiom-query/configs/templates/axiom_agg_2.yml create mode 100644 axiom-query/configs/templates/header.leaf.yml create mode 100644 axiom-query/configs/templates/header.tree.yml create mode 100644 axiom-query/configs/templates/keccak.leaf.yml create mode 100644 axiom-query/configs/templates/keccak.tree.yml create mode 100644 axiom-query/configs/templates/receipt.leaf.yml create mode 100644 axiom-query/configs/templates/results_root.leaf.yml create mode 100644 axiom-query/configs/templates/soliditymapping.leaf.yml create mode 100644 axiom-query/configs/templates/storage.leaf.yml create mode 100644 axiom-query/configs/templates/subquery_agg.yml create mode 100644 axiom-query/configs/templates/verifycompute.yml create mode 100644 axiom-query/configs/test/axiom_aggregation1_for_agg.json create mode 100644 axiom-query/configs/test/axiom_aggregation2.json create mode 100644 axiom-query/configs/test/header_subquery.json create mode 100644 axiom-query/configs/test/header_subquery_core_params.json create mode 100644 axiom-query/configs/test/header_subquery_for_agg.json create mode 100644 axiom-query/configs/test/header_subquery_loader_params.json create mode 100644 axiom-query/configs/test/keccak_for_agg.json create mode 100644 axiom-query/configs/test/results_root.json create mode 100644 axiom-query/configs/test/results_root_for_agg.json create mode 100644 axiom-query/configs/test/subquery_aggregation_for_agg.json create mode 100644 axiom-query/configs/test/verify_compute_for_agg.json create mode 100644 axiom-query/data/production/aggregate_vk_hashes.json create mode 100644 axiom-query/data/production/all_128_each_default.tree create mode 100644 axiom-query/data/production/all_32_each_default.tree create mode 100644 axiom-query/data/production/all_large.tree create mode 100644 axiom-query/data/production/all_max.tree create mode 100644 axiom-query/data/production/all_small.tree create mode 100644 axiom-query/data/test/axiom_aggregation1_for_agg.snark.json create mode 100644 axiom-query/data/test/input_header_for_agg.json create mode 100644 axiom-query/data/test/input_keccak_shard_for_header.json create mode 100644 axiom-query/data/test/input_mmr_proof_for_header.json create mode 100644 axiom-query/data/test/input_results_root.json create mode 100644 axiom-query/data/test/input_results_root_for_agg.json create mode 100644 axiom-query/data/test/input_verify_compute.json create mode 100644 axiom-query/data/test/input_verify_compute_for_agg.json create mode 100644 axiom-query/data/test/promise_results_header_for_agg.json create mode 100644 axiom-query/data/test/promise_results_keccak_for_agg.json create mode 100644 axiom-query/data/test/subquery_aggregation_for_agg.snark.json create mode 100644 axiom-query/src/axiom_aggregation1/circuit.rs create mode 100644 axiom-query/src/axiom_aggregation1/mod.rs create mode 100644 axiom-query/src/axiom_aggregation1/tests.rs create mode 100644 axiom-query/src/axiom_aggregation1/types.rs create mode 100644 axiom-query/src/axiom_aggregation2/circuit.rs create mode 100644 axiom-query/src/axiom_aggregation2/integration_test.sh create mode 100644 axiom-query/src/axiom_aggregation2/mod.rs create mode 100644 axiom-query/src/axiom_aggregation2/readme.md create mode 100644 axiom-query/src/axiom_aggregation2/tests.rs create mode 100644 axiom-query/src/bin/keygen.rs create mode 100644 axiom-query/src/bin/readme.md create mode 100644 axiom-query/src/bin/rename_snark_verifier.sh create mode 100644 axiom-query/src/components/mod.rs create mode 100644 axiom-query/src/components/results/circuit.rs create mode 100644 axiom-query/src/components/results/mod.rs create mode 100644 axiom-query/src/components/results/results_root.rs create mode 100644 axiom-query/src/components/results/subquery_hash.rs create mode 100644 axiom-query/src/components/results/table/join.rs create mode 100644 axiom-query/src/components/results/table/mod.rs create mode 100644 axiom-query/src/components/results/tests.rs create mode 100644 axiom-query/src/components/results/types.rs create mode 100644 axiom-query/src/components/subqueries/account/circuit.rs create mode 100644 axiom-query/src/components/subqueries/account/mod.rs create mode 100644 axiom-query/src/components/subqueries/account/tests.rs create mode 100644 axiom-query/src/components/subqueries/account/types.rs create mode 100644 axiom-query/src/components/subqueries/block_header/circuit.rs create mode 100644 axiom-query/src/components/subqueries/block_header/mmr_verify.rs create mode 100644 axiom-query/src/components/subqueries/block_header/mod.rs create mode 100644 axiom-query/src/components/subqueries/block_header/tests.rs create mode 100644 axiom-query/src/components/subqueries/block_header/types.rs create mode 100644 axiom-query/src/components/subqueries/common.rs create mode 100644 axiom-query/src/components/subqueries/mod.rs create mode 100644 axiom-query/src/components/subqueries/receipt/circuit.rs create mode 100644 axiom-query/src/components/subqueries/receipt/mod.rs create mode 100644 axiom-query/src/components/subqueries/receipt/tests.rs create mode 100644 axiom-query/src/components/subqueries/receipt/types.rs create mode 100644 axiom-query/src/components/subqueries/solidity_mappings/circuit.rs create mode 100644 axiom-query/src/components/subqueries/solidity_mappings/mod.rs create mode 100644 axiom-query/src/components/subqueries/solidity_mappings/tests.rs create mode 100644 axiom-query/src/components/subqueries/solidity_mappings/types.rs create mode 100644 axiom-query/src/components/subqueries/storage/circuit.rs create mode 100644 axiom-query/src/components/subqueries/storage/mod.rs create mode 100644 axiom-query/src/components/subqueries/storage/tests.rs create mode 100644 axiom-query/src/components/subqueries/storage/types.rs create mode 100644 axiom-query/src/components/subqueries/transaction/circuit.rs create mode 100644 axiom-query/src/components/subqueries/transaction/mod.rs create mode 100644 axiom-query/src/components/subqueries/transaction/tests.rs create mode 100644 axiom-query/src/components/subqueries/transaction/types.rs create mode 100644 axiom-query/src/global_constants.rs create mode 100644 axiom-query/src/keygen/agg/axiom_agg_1.rs create mode 100644 axiom-query/src/keygen/agg/axiom_agg_2.rs create mode 100644 axiom-query/src/keygen/agg/common.rs create mode 100644 axiom-query/src/keygen/agg/mod.rs create mode 100644 axiom-query/src/keygen/agg/single_type.rs create mode 100644 axiom-query/src/keygen/agg/subquery_agg.rs create mode 100644 axiom-query/src/keygen/mod.rs create mode 100644 axiom-query/src/keygen/shard/keccak.rs create mode 100644 axiom-query/src/keygen/shard/mod.rs create mode 100644 axiom-query/src/lib.rs create mode 100644 axiom-query/src/subquery_aggregation/circuit.rs create mode 100644 axiom-query/src/subquery_aggregation/mod.rs create mode 100644 axiom-query/src/subquery_aggregation/tests.rs create mode 100644 axiom-query/src/subquery_aggregation/types.rs create mode 100644 axiom-query/src/utils/client_circuit/default_circuit.rs create mode 100644 axiom-query/src/utils/client_circuit/metadata.rs create mode 100644 axiom-query/src/utils/client_circuit/mod.rs create mode 100644 axiom-query/src/utils/client_circuit/vkey.rs create mode 100644 axiom-query/src/utils/codec.rs create mode 100644 axiom-query/src/utils/mod.rs create mode 100644 axiom-query/src/verify_compute/README.md create mode 100644 axiom-query/src/verify_compute/circuit.rs create mode 100644 axiom-query/src/verify_compute/mod.rs create mode 100644 axiom-query/src/verify_compute/query_hash.rs create mode 100644 axiom-query/src/verify_compute/tests/aggregation.rs create mode 100644 axiom-query/src/verify_compute/tests/mod.rs create mode 100644 axiom-query/src/verify_compute/tests/prove.rs create mode 100644 axiom-query/src/verify_compute/tests/utils.rs create mode 100644 axiom-query/src/verify_compute/types.rs create mode 100644 axiom-query/src/verify_compute/utils.rs create mode 100644 rust-toolchain create mode 100644 rustfmt.toml create mode 100644 trusted_setup_s3.sh diff --git a/.github/workflows/axiom-core-tests.yml b/.github/workflows/axiom-core-tests.yml new file mode 100644 index 00000000..896da427 --- /dev/null +++ b/.github/workflows/axiom-core-tests.yml @@ -0,0 +1,26 @@ +name: Tests + +on: + push: + branches: ["main", "develop"] + pull_request: + paths: + - "axiom-eth/**" # axiom-eth changes affect axiom-core + - "axiom-core/**" + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest-64core-256ram + + steps: + - uses: actions/checkout@v3 + + - name: Run axiom-core tests + working-directory: "axiom-core" + run: | + export ALCHEMY_KEY=${{ secrets.ALCHEMY_KEY }} + export JSON_RPC_URL=${{ secrets.JSON_RPC_URL }} + cargo t diff --git a/.github/workflows/axiom-eth-mock-tests.yml b/.github/workflows/axiom-eth-mock-tests.yml new file mode 100644 index 00000000..0ce3a65c --- /dev/null +++ b/.github/workflows/axiom-eth-mock-tests.yml @@ -0,0 +1,41 @@ +name: axiom-eth mock prover tests + +on: + push: + branches: ["main"] + pull_request: + paths: + - "axiom-eth/**" + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest-64core-256ram + + steps: + - uses: actions/checkout@v3 + + - name: Build + run: | + export AXIOM_SKIP_CONSTANT_GEN=1 + cargo build --verbose + + - name: Run axiom-eth tests MockProver + working-directory: "axiom-eth" + run: | + export ALCHEMY_KEY=${{ secrets.ALCHEMY_KEY}} + export JSON_RPC_URL=${{ secrets.JSON_RPC_URL }} + cargo t test_keccak + cargo t rlc::tests + cargo t rlp::tests + cargo t keccak::tests + cargo t block_header::tests + cargo t mpt + cargo t storage::tests::test_mock + cargo t transaction::tests + BLOCK_NUM=17000000 cargo test receipt::tests + cargo t solidity::tests::mapping::test_mock + cargo t solidity::tests::nested_mappings::test_mock + cargo t solidity::tests::mapping_storage::test_mock diff --git a/.github/workflows/axiom-eth-prover-tests.yml b/.github/workflows/axiom-eth-prover-tests.yml new file mode 100644 index 00000000..7288a7ae --- /dev/null +++ b/.github/workflows/axiom-eth-prover-tests.yml @@ -0,0 +1,28 @@ +name: Tests + +on: + push: + branches: ["main", "develop"] + pull_request: + branches: ["main", "develop", "release*", "release/*", "*audit*"] + paths: + - "axiom-eth/**" + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest-64core-256ram + + steps: + - uses: actions/checkout@v3 + + - name: Run axiom-eth tests real prover + working-directory: "axiom-eth" + run: | + export ALCHEMY_KEY=${{ secrets.ALCHEMY_KEY }} + export JSON_RPC_URL=${{ secrets.JSON_RPC_URL }} + cargo t --release -- test_one_mainnet_header_prover + cargo t --release -- bench_mpt_inclusion_fixed --ignored + cargo t --release utils::component:: diff --git a/.github/workflows/axiom-query-tests.yml b/.github/workflows/axiom-query-tests.yml new file mode 100644 index 00000000..8e5a2256 --- /dev/null +++ b/.github/workflows/axiom-query-tests.yml @@ -0,0 +1,28 @@ +name: Tests + +on: + push: + branches: ["main", "develop"] + pull_request: + paths: + - "axiom-eth/**" # axiom-eth changes affect axiom-query + - "axiom-query/**" + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest-64core-256ram + + steps: + - uses: actions/checkout@v3 + + - name: Run axiom-query tests + working-directory: "axiom-query" + run: | + export AXIOM_SKIP_CONSTANT_GEN=1 + export ALCHEMY_KEY=${{ secrets.ALCHEMY_KEY }} + export JSON_RPC_URL=${{ secrets.JSON_RPC_URL }} + mkdir -p data/test + cargo t --no-default-features --features "halo2-axiom, jemallocator" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..69cc03b6 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,18 @@ +name: Build workspace + +on: + push: + branches: ["main"] + pull_request: {} + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Build + run: | + export AXIOM_SKIP_CONSTANT_GEN=1 + cargo build --verbose diff --git a/.github/workflows/lints.yml b/.github/workflows/lints.yml new file mode 100644 index 00000000..f8b8f634 --- /dev/null +++ b/.github/workflows/lints.yml @@ -0,0 +1,33 @@ +name: Lints + +on: + push: + branches: ["main"] + pull_request: {} + +jobs: + lint: + name: Lint + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + override: false + components: rustfmt, clippy + + - uses: Swatinem/rust-cache@v1 + with: + cache-on-failure: true + + - name: Run fmt + run: | + export AXIOM_SKIP_CONSTANT_GEN=1 + cargo fmt --all -- --check + + - name: Run clippy + run: cargo clippy --all -- -D warnings diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..05396974 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# 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 +# ======= +target/ +params/ +!params/integration_test + +*.png + +/halo2_ecc/src/bn254/data/ +/halo2_ecc/src/secp256k1/data/ +**/.env + +*.pk +*.snark +.DS_Store \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..6a742040 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "axiom-codec/axiom-tools"] + path = axiom-codec/axiom-tools + url = git@github.com:axiom-crypto/axiom-tools.git diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..da10b50e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,54 @@ +[workspace] +members = ["axiom-eth", "axiom-core", "axiom-query", "axiom-codec", "axiom-components"] +resolver = "2" + +[workspace.dependencies] +ethers-core = { version = "=2.0.14", features = ["optimism"] } # fix the version as it affects what fields are included in the `Block` struct +# generating circuit inputs from blockchain +ethers-providers = { version = "=2.0.14", features = ["optimism"] } + +# halo2 +# halo2-base = { version = "=0.4.1", default-features = false, features = ["test-utils"] } +halo2-base = { git = "https://github.com/axiom-crypto/halo2-lib.git", branch = "develop", default-features = false, features = ["test-utils"] } +halo2-ecc = { git = "https://github.com/axiom-crypto/halo2-lib.git", branch = "develop", default-features = false } +# zkevm-hashes = { version = "=0.2.1", default-features = false } +zkevm-hashes = { git = "https://github.com/axiom-crypto/halo2-lib.git", branch = "develop", default-features = false } +# snark-verifier = { version = "=0.1.7", default-features = false } +snark-verifier = { git = "https://github.com/axiom-crypto/snark-verifier.git", branch = "develop", default-features = false } +# snark-verifier-sdk = { version = "=0.1.7", default-features = false } +snark-verifier-sdk = { git = "https://github.com/axiom-crypto/snark-verifier.git", branch = "develop", default-features = false } + + +[profile.dev] +opt-level = 3 +debug = 2 # 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/LICENSE b/LICENSE new file mode 100644 index 00000000..69ec60b5 --- /dev/null +++ b/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/README.md b/README.md new file mode 100644 index 00000000..53dd5d33 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Axiom ZK Circuits + +This repository builds on [halo2-lib](https://github.com/axiom-crypto/halo2-lib/tree/main) and [snark-verifier](https://github.com/axiom-crypto/snark-verifier/) to create a library of zero-knowledge proof circuits that [Axiom](https://axiom.xyz) uses to read data from the Ethereum virtual machine (EVM). + +This monorepo consists of multiple crates: + +## `axiom-eth` + +This is the main library of chips and frameworks for building ZK circuits that prove data from the Ethereum virtual machine (EVM). For more details, see the [README](./axiom-eth/README.md). + +## `axiom-core` + +This contains the ZK circuits that generate proofs for the `AxiomV2Core` smart contract. These circuits read the RLP encoded block headers for a chain of blocks and verify that the block headers form a chain. They output a Merkle Mountain Range of the block hashes of the chain. This crate also contains aggregation circuits to aggregate multiple circuits for the purpose of proving longer chains. For more details, see the [README](./axiom-core/README.md). + +## `axiom-query` + +This contains the ZK circuits that generate proofs for the `AxiomV2Query` smart contract. For more details, see the [README](./axiom-query/README.md). + +### `axiom-codec` + +This crate does not contain any ZK circuits, but it contains Rust types for Axiom queries and specifies how to encode/decode them to field elements for in-circuit use. diff --git a/axiom-codec/Cargo.toml b/axiom-codec/Cargo.toml new file mode 100644 index 00000000..b3428159 --- /dev/null +++ b/axiom-codec/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "axiom-codec" +version = "0.3.0-alpha.1" +authors = ["Intrinsic Technologies"] +license = "MIT" +edition = "2021" +repository = "https://github.com/axiom-crypto/axiom-eth" +readme = "README.md" +description = "This crate contains Rust types for Axiom queries and specifies how to encode/decode them to field elements for in-circuit use." +rust-version = "1.73.0" + +[dependencies] +byteorder = { version = "1.4.3" } +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0", default-features = false } +serde_repr = "0.1" +base64 = { version = "0.21", optional = true } +serde_with = { version = "2.2", optional = true } +anyhow = "1.0" + +# halo2, features turned on by axiom-eth +axiom-eth = { version = "0.4.1", path = "../axiom-eth", default-features = false } +axiom-components = { path = "../axiom-components", default-features = false } + +# ethereum +ethers-core = { workspace = true } + +[dev-dependencies] +hex = "0.4.3" + +[build-dependencies] +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } + +[features] +default = ["halo2-axiom"] +halo2-pse = ["axiom-eth/halo2-pse"] +halo2-axiom = ["axiom-eth/halo2-axiom"] diff --git a/axiom-codec/axiom-tools b/axiom-codec/axiom-tools new file mode 160000 index 00000000..43891b2d --- /dev/null +++ b/axiom-codec/axiom-tools @@ -0,0 +1 @@ +Subproject commit 43891b2d93228cb12d48b5612c4256f4d2ed9af7 diff --git a/axiom-codec/build.rs.bak b/axiom-codec/build.rs.bak new file mode 100644 index 00000000..a9a15339 --- /dev/null +++ b/axiom-codec/build.rs.bak @@ -0,0 +1,84 @@ +use std::fs::File; +use std::io::Write; +use std::path::{Path, PathBuf}; + +// Helper function to convert camelCase to UPPER_SNAKE_CASE +fn to_upper_snake_case(s: &str) -> String { + let mut result = String::new(); + for (i, ch) in s.chars().enumerate() { + if ch.is_uppercase() && i != 0 { + result.push('_'); + } + result.push(ch.to_ascii_uppercase()); + } + result +} + +fn main() { + println!("cargo:rerun-if-changed=axiom-tools/src/constants/v2/circuit.json"); + println!("cargo:rerun-if-changed=axiom-tools/src/constants/v2/fieldValues.json"); + + if std::env::var("AXIOM_SKIP_CONSTANT_GEN").unwrap_or("0".to_string()) == "1" { + return; + } + let special_values_path: PathBuf = + ["axiom-tools", "src", "constants", "v2", "fieldValues.json"].into_iter().collect(); + + // Read the JSON file + let json = + std::fs::read_to_string(special_values_path).expect("Unable to read special_values.json"); + + // Parse the JSON into a serde_json::Value + let data: serde_json::Value = serde_json::from_str(&json).expect("Error parsing the JSON"); + + // Begin generating the Rust code + let mut output = String::new(); + + for (section_name, section) in data.as_object().unwrap() { + for (key, value) in section.as_object().unwrap() { + if let Some(value) = value.as_number() { + let const_name = + format!("{}_{}", to_upper_snake_case(section_name), to_upper_snake_case(key)); + output += &format!("pub const {}: usize = {};\n", const_name, value); + } + } + } + + // Determine the output directory + let dest_path = Path::new("src").join("special_values.rs"); + + // Write the generated Rust code to a file + let mut f = File::create(dest_path).expect("Could not create file"); + f.write_all(output.as_bytes()).expect("Could not write data"); + + // Print a message for debugging purposes + println!("Generated src/special_values.rs"); + + // Read the JSON file + let constants_path: PathBuf = + ["axiom-tools", "src", "constants", "v2", "circuit.json"].into_iter().collect(); + let json = std::fs::read_to_string(constants_path).expect("Unable to read constants.json"); + + // Parse the JSON into a serde_json::Value + let data: serde_json::Value = serde_json::from_str(&json).expect("Error parsing the JSON"); + + // Begin generating the Rust code + let mut output = String::new(); + + for (key, value) in data.as_object().unwrap() { + if let Some(value) = value.as_number() { + let const_name = to_upper_snake_case(key); + output += &format!("pub const {}: usize = {};\n", const_name, value); + } + } + + // Determine the output directory + let dest_path = Path::new("src").join("constants.rs"); + + // Write the generated Rust code to a file + let mut f = File::create(dest_path).expect("Could not create file"); + f.write_all(output.as_bytes()).expect("Could not write data"); + + // Print a message for debugging purposes + println!("Generated src/constants.rs"); +} diff --git a/axiom-codec/src/constants.rs b/axiom-codec/src/constants.rs new file mode 100644 index 00000000..21a58fc8 --- /dev/null +++ b/axiom-codec/src/constants.rs @@ -0,0 +1,20 @@ +pub const ENCODED_K_BYTES: usize = 1; +pub const ENCODED_VKEY_LENGTH_BYTES: usize = 1; +pub const FIELD_IDX_BITS: usize = 32; +pub const FIELD_IDX_BYTES: usize = 4; +pub const MAX_SOLIDITY_MAPPING_KEYS: usize = 4; +pub const MAX_SUBQUERY_INPUTS: usize = 13; +pub const MAX_SUBQUERY_OUTPUTS: usize = 2; +pub const NUM_SUBQUERY_TYPES: usize = 8; +pub const SOURCE_CHAIN_ID_BYTES: usize = 8; +pub const SUBQUERY_TYPE_BYTES: usize = 2; +pub const USER_ADVICE_COLS: usize = 4; +pub const USER_FIXED_COLS: usize = 1; +pub const USER_INSTANCE_COLS: usize = 1; +pub const USER_LOOKUP_ADVICE_COLS: usize = 1; +pub const USER_MAX_OUTPUTS: usize = 128; +pub const USER_MAX_SUBQUERIES: usize = 128; +pub const USER_PROOF_LEN_BYTES: usize = 4; +pub const USER_RESULT_BYTES: usize = 32; +pub const USER_RESULT_FIELD_ELEMENTS: usize = 2; +pub const USER_RESULT_LEN_BYTES: usize = 2; diff --git a/axiom-codec/src/decoder/field_elements.rs b/axiom-codec/src/decoder/field_elements.rs new file mode 100644 index 00000000..e1853dc1 --- /dev/null +++ b/axiom-codec/src/decoder/field_elements.rs @@ -0,0 +1,120 @@ +use std::io::{Error, ErrorKind, Result}; + +use crate::{ + constants::MAX_SOLIDITY_MAPPING_KEYS, + encoder::field_elements::{ + NUM_FE_SOLIDITY_NESTED_MAPPING, NUM_FE_SOLIDITY_NESTED_MAPPING_WITHOUT_KEYS, + }, + types::field_elements::{ + FieldAccountSubquery, FieldHeaderSubquery, FieldReceiptSubquery, + FieldSolidityNestedMappingSubquery, FieldStorageSubquery, FieldTxSubquery, + FlattenedSubqueryResult, SubqueryKey, SubqueryOutput, SUBQUERY_KEY_LEN, + SUBQUERY_RESULT_LEN, + }, + HiLo, +}; + +impl TryFrom> for FlattenedSubqueryResult { + type Error = Error; + + fn try_from(value: Vec) -> Result { + if value.len() != SUBQUERY_RESULT_LEN { + return Err(Error::new(ErrorKind::InvalidInput, "invalid array length")); + } + let mut key = value; + let value = key.split_off(SUBQUERY_KEY_LEN); + let key = key.try_into().map_err(|_| Error::other("should never happen"))?; + let value = value.try_into().map_err(|_| Error::other("should never happen"))?; + Ok(Self { key: SubqueryKey(key), value: SubqueryOutput(value) }) + } +} + +impl TryFrom> for FieldHeaderSubquery { + type Error = Error; + + fn try_from(value: Vec) -> Result { + let [block_number, field_idx] = value + .try_into() + .map_err(|_| Error::new(ErrorKind::InvalidInput, "invalid array length"))?; + Ok(Self { block_number, field_idx }) + } +} + +impl TryFrom> for FieldAccountSubquery { + type Error = Error; + + fn try_from(value: Vec) -> Result { + let [block_number, addr, field_idx] = value + .try_into() + .map_err(|_| Error::new(ErrorKind::InvalidInput, "invalid array length"))?; + Ok(Self { block_number, addr, field_idx }) + } +} + +impl TryFrom> for FieldStorageSubquery { + type Error = Error; + + fn try_from(value: Vec) -> Result { + let [block_number, addr, slot_hi, slot_lo] = value + .try_into() + .map_err(|_| Error::new(ErrorKind::InvalidInput, "invalid array length"))?; + Ok(Self { block_number, addr, slot: HiLo::from_hi_lo([slot_hi, slot_lo]) }) + } +} + +impl TryFrom> for FieldTxSubquery { + type Error = Error; + + fn try_from(value: Vec) -> Result { + let [block_number, tx_idx, field_or_calldata_idx] = value + .try_into() + .map_err(|_| Error::new(ErrorKind::InvalidInput, "invalid array length"))?; + Ok(Self { block_number, tx_idx, field_or_calldata_idx }) + } +} + +impl TryFrom> for FieldReceiptSubquery { + type Error = Error; + + fn try_from(value: Vec) -> Result { + let [block_number, tx_idx, field_or_log_idx, topic_or_data_or_address_idx, event_schema_hi, event_schema_lo] = + value + .try_into() + .map_err(|_| Error::new(ErrorKind::InvalidInput, "invalid array length"))?; + Ok(Self { + block_number, + tx_idx, + field_or_log_idx, + topic_or_data_or_address_idx, + event_schema: HiLo::from_hi_lo([event_schema_hi, event_schema_lo]), + }) + } +} + +impl TryFrom> for FieldSolidityNestedMappingSubquery { + type Error = Error; + + fn try_from(mut value: Vec) -> Result { + if value.len() != NUM_FE_SOLIDITY_NESTED_MAPPING { + return Err(Error::new(ErrorKind::InvalidInput, "invalid array length")); + } + let keys = value.split_off(NUM_FE_SOLIDITY_NESTED_MAPPING_WITHOUT_KEYS); + let [block_number, addr, slot_hi, slot_lo, mapping_depth] = + value.try_into().map_err(|_| Error::other("should never happen"))?; + let mut keys_iter = keys.into_iter(); + let keys: Vec<_> = (0..MAX_SOLIDITY_MAPPING_KEYS) + .map(|_| { + let key_hi = keys_iter.next().unwrap(); + let key_lo = keys_iter.next().unwrap(); + HiLo::from_hi_lo([key_hi, key_lo]) + }) + .collect(); + Ok(Self { + block_number, + addr, + mapping_slot: HiLo::from_hi_lo([slot_hi, slot_lo]), + mapping_depth, + keys: keys.try_into().map_err(|_| Error::other("max keys wrong length"))?, + }) + } +} diff --git a/axiom-codec/src/decoder/mod.rs b/axiom-codec/src/decoder/mod.rs new file mode 100644 index 00000000..3e24b446 --- /dev/null +++ b/axiom-codec/src/decoder/mod.rs @@ -0,0 +1,2 @@ +pub mod field_elements; +pub mod native; diff --git a/axiom-codec/src/decoder/native.rs b/axiom-codec/src/decoder/native.rs new file mode 100644 index 00000000..bc978813 --- /dev/null +++ b/axiom-codec/src/decoder/native.rs @@ -0,0 +1,164 @@ +use std::io::{self, Read, Result}; + +use axiom_components::ecdsa::ECDSAComponentNativeInput; +use axiom_eth::halo2curves::bn256::G1Affine; +use byteorder::{BigEndian, ReadBytesExt}; +use ethers_core::types::Bytes; + +use crate::{ + constants::MAX_SOLIDITY_MAPPING_KEYS, + types::native::{ + AccountSubquery, AnySubquery, AxiomV2ComputeSnark, HeaderSubquery, ReceiptSubquery, + SolidityNestedMappingSubquery, StorageSubquery, Subquery, SubqueryType, TxSubquery, + }, + utils::reader::{read_address, read_curve_compressed, read_field_be, read_h256, read_u256}, +}; + +impl TryFrom for AnySubquery { + type Error = io::Error; + + fn try_from(subquery: Subquery) -> Result { + let mut reader = &subquery.encoded_subquery_data[..]; + let subquery_type = subquery.subquery_type; + Ok(match subquery_type { + SubqueryType::Null => AnySubquery::Null, + SubqueryType::Header => AnySubquery::Header(decode_header_subquery(&mut reader)?), + SubqueryType::Account => AnySubquery::Account(decode_account_subquery(&mut reader)?), + SubqueryType::Storage => AnySubquery::Storage(decode_storage_subquery(&mut reader)?), + SubqueryType::Transaction => AnySubquery::Transaction(decode_tx_subquery(&mut reader)?), + SubqueryType::Receipt => AnySubquery::Receipt(decode_receipt_subquery(&mut reader)?), + SubqueryType::SolidityNestedMapping => AnySubquery::SolidityNestedMapping( + decode_solidity_nested_mapping_subquery(&mut reader)?, + ), + SubqueryType::ECDSA => AnySubquery::ECDSA(decode_ecdsa_subquery(&mut reader)?), + }) + } +} + +impl TryFrom for SubqueryType { + type Error = io::Error; + fn try_from(value: u16) -> Result { + match value { + 0 => Ok(Self::Null), + 1 => Ok(Self::Header), + 2 => Ok(Self::Account), + 3 => Ok(Self::Storage), + 4 => Ok(Self::Transaction), + 5 => Ok(Self::Receipt), + 6 => Ok(Self::SolidityNestedMapping), + 7 => Ok(Self::ECDSA), + // 7 => Ok(Self::BeaconValidator), + _ => Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid SubqueryType")), + } + } +} + +/// Decoder from `compute_proof` in bytes into `AxiomV2ComputeSnark`. +/// `reader` should be a reader on `compute_proof` in `AxiomV2ComputeQuery`. +pub fn decode_compute_snark( + mut reader: impl Read, + result_len: u16, + is_aggregation: bool, +) -> Result { + let kzg_accumulator = if is_aggregation { + let lhs = read_curve_compressed::(&mut reader)?; + let rhs = read_curve_compressed::(&mut reader)?; + Some((lhs, rhs)) + } else { + read_h256(&mut reader)?; + read_h256(&mut reader)?; + None + }; + let mut compute_results = Vec::with_capacity(result_len as usize); + for _ in 0..result_len { + compute_results.push(read_h256(&mut reader)?); + } + let mut proof_transcript = vec![]; + reader.read_to_end(&mut proof_transcript)?; + Ok(AxiomV2ComputeSnark { kzg_accumulator, compute_results, proof_transcript }) +} + +pub fn decode_subquery(mut reader: impl Read) -> Result { + let subquery_type = reader.read_u16::()?; + let subquery_type = subquery_type.try_into()?; + let mut buf = vec![]; + reader.read_to_end(&mut buf)?; + let encoded_subquery_data = Bytes::from(buf); + let subquery = Subquery { subquery_type, encoded_subquery_data }; + subquery.try_into() +} + +pub fn decode_header_subquery(mut reader: impl Read) -> Result { + let block_number = reader.read_u32::()?; + let field_idx = reader.read_u32::()?; + Ok(HeaderSubquery { block_number, field_idx }) +} + +pub fn decode_account_subquery(mut reader: impl Read) -> Result { + let block_number = reader.read_u32::()?; + let addr = read_address(&mut reader)?; + let field_idx = reader.read_u32::()?; + Ok(AccountSubquery { block_number, addr, field_idx }) +} + +pub fn decode_storage_subquery(mut reader: impl Read) -> Result { + let block_number = reader.read_u32::()?; + let addr = read_address(&mut reader)?; + let slot = read_u256(&mut reader)?; + Ok(StorageSubquery { block_number, addr, slot }) +} + +pub fn decode_tx_subquery(mut reader: impl Read) -> Result { + let block_number = reader.read_u32::()?; + let tx_idx = reader.read_u16::()?; + let field_or_calldata_idx = reader.read_u32::()?; + Ok(TxSubquery { block_number, tx_idx, field_or_calldata_idx }) +} + +pub fn decode_receipt_subquery(mut reader: impl Read) -> Result { + let block_number = reader.read_u32::()?; + let tx_idx = reader.read_u16::()?; + let field_or_log_idx = reader.read_u32::()?; + let topic_or_data_or_address_idx = reader.read_u32::()?; + let event_schema = read_h256(&mut reader)?; + Ok(ReceiptSubquery { + block_number, + tx_idx, + field_or_log_idx, + topic_or_data_or_address_idx, + event_schema, + }) +} + +pub fn decode_solidity_nested_mapping_subquery( + mut reader: impl Read, +) -> Result { + let block_number = reader.read_u32::()?; + let addr = read_address(&mut reader)?; + let mapping_slot = read_u256(&mut reader)?; + let mapping_depth = reader.read_u8()?; + if mapping_depth as usize > MAX_SOLIDITY_MAPPING_KEYS { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "SolidityNestedMappingSubquery mapping_depth {} exceeds MAX_SOLIDITY_MAPPING_KEYS {}", + mapping_depth, MAX_SOLIDITY_MAPPING_KEYS + ), + )); + } + let mut keys = Vec::with_capacity(mapping_depth as usize); + for _ in 0..mapping_depth { + keys.push(read_h256(&mut reader)?); + } + Ok(SolidityNestedMappingSubquery { block_number, addr, mapping_slot, mapping_depth, keys }) +} + +pub fn decode_ecdsa_subquery(mut reader: impl Read) -> Result { + let pubkey_x = read_field_be(&mut reader)?; + let pubkey_y = read_field_be(&mut reader)?; + let r = read_field_be(&mut reader)?; + let s = read_field_be(&mut reader)?; + let msg_hash = read_h256(&mut reader)?; + let out = ECDSAComponentNativeInput { pubkey: (pubkey_x, pubkey_y), r, s, msg_hash }; + Ok(out) +} diff --git a/axiom-codec/src/encoder/field_elements.rs b/axiom-codec/src/encoder/field_elements.rs new file mode 100644 index 00000000..6775f3fc --- /dev/null +++ b/axiom-codec/src/encoder/field_elements.rs @@ -0,0 +1,347 @@ +use std::io; + +use axiom_components::{ecdsa::ECDSAComponentInput, utils::flatten::InputFlatten}; +use axiom_eth::halo2curves::bn256::Fr; +use ethers_core::types::{Bytes, H256}; + +use crate::{ + constants::*, + types::field_elements::*, + types::native::*, + utils::native::{encode_addr_to_field, encode_h256_to_hilo, encode_u256_to_hilo}, + Field, +}; + +pub const NUM_FE_HEADER: usize = 2; +pub const NUM_FE_ACCOUNT: usize = 3; +pub const NUM_FE_STORAGE: usize = 4; +pub const NUM_FE_TX: usize = 3; +pub const NUM_FE_RECEIPT: usize = 6; +pub const NUM_FE_SOLIDITY_NESTED_MAPPING: usize = + NUM_FE_SOLIDITY_NESTED_MAPPING_WITHOUT_KEYS + MAX_SOLIDITY_MAPPING_KEYS * 2; +pub const NUM_FE_SOLIDITY_NESTED_MAPPING_WITHOUT_KEYS: usize = 5; +pub const NUM_FE_SOLIDITY_NESTED_MAPPING_MIN: usize = 7; // assumes >=1 key +pub const NUM_FE_ANY: [usize; NUM_SUBQUERY_TYPES] = [ + 0, + NUM_FE_HEADER, + NUM_FE_ACCOUNT, + NUM_FE_STORAGE, + NUM_FE_TX, + NUM_FE_RECEIPT, + NUM_FE_SOLIDITY_NESTED_MAPPING, + ECDSAComponentInput::::NUM_FE, +]; + +/// The index of the mapping depth in [`FieldSolidityNestedMappingSubquery`]. +pub const FIELD_SOLIDITY_NESTED_MAPPING_DEPTH_IDX: usize = 4; +/// The index of the mapping depth in [`SubqueryKey`], where the first index holds the subquery type. +pub const FIELD_ENCODED_SOLIDITY_NESTED_MAPPING_DEPTH_IDX: usize = + FIELD_SOLIDITY_NESTED_MAPPING_DEPTH_IDX + 1; + +// The following constants describe how to convert from the field element +// encoding into the bytes encoding, by specifying the fixed width bytes each field +// element represented (in big endian order). +pub const BYTES_PER_FE_HEADER: [usize; NUM_FE_HEADER] = [4, 4]; +pub const BITS_PER_FE_HEADER: [usize; NUM_FE_HEADER] = [32, 32]; +pub const BYTES_PER_FE_ACCOUNT: [usize; NUM_FE_ACCOUNT] = [4, 20, 4]; +pub const BITS_PER_FE_ACCOUNT: [usize; NUM_FE_ACCOUNT] = [32, 160, 32]; +pub const BYTES_PER_FE_STORAGE: [usize; NUM_FE_STORAGE] = [4, 20, 16, 16]; +pub const BITS_PER_FE_STORAGE: [usize; NUM_FE_STORAGE] = [32, 160, 128, 128]; +pub const BYTES_PER_FE_TX: [usize; NUM_FE_TX] = [4, 2, 4]; +pub const BITS_PER_FE_TX: [usize; NUM_FE_TX] = [32, 16, 32]; +pub const BYTES_PER_FE_RECEIPT: [usize; NUM_FE_RECEIPT] = [4, 2, 4, 4, 16, 16]; +pub const BITS_PER_FE_RECEIPT: [usize; NUM_FE_RECEIPT] = [32, 16, 32, 32, 128, 128]; +pub const BYTES_PER_FE_SOLIDITY_NESTED_MAPPING: [usize; NUM_FE_SOLIDITY_NESTED_MAPPING] = + bytes_per_fe_solidity_nested_mapping(); +pub const BITS_PER_FE_SOLIDITY_NESTED_MAPPING: [usize; NUM_FE_SOLIDITY_NESTED_MAPPING] = + bits_per_fe_solidity_nested_mapping(); +pub const BYTES_PER_FE_ECDSA: [usize; ECDSAComponentInput::::NUM_FE] = + [16; ECDSAComponentInput::::NUM_FE]; +pub const BYTES_PER_FE_ANY: [&[usize]; NUM_SUBQUERY_TYPES] = [ + &[], + &BYTES_PER_FE_HEADER, + &BYTES_PER_FE_ACCOUNT, + &BYTES_PER_FE_STORAGE, + &BYTES_PER_FE_TX, + &BYTES_PER_FE_RECEIPT, + &BYTES_PER_FE_SOLIDITY_NESTED_MAPPING, + &BYTES_PER_FE_ECDSA, +]; +pub const BYTES_PER_FE_SUBQUERY_OUTPUT: usize = 16; + +pub const BITS_PER_FE_SUBQUERY_RESULT: [usize; SUBQUERY_RESULT_LEN] = [128; SUBQUERY_RESULT_LEN]; +pub const SUBQUERY_OUTPUT_BYTES: usize = MAX_SUBQUERY_OUTPUTS * BYTES_PER_FE_SUBQUERY_OUTPUT; + +const fn bytes_per_fe_solidity_nested_mapping() -> [usize; NUM_FE_SOLIDITY_NESTED_MAPPING] { + let mut bytes_per = [16; MAX_SUBQUERY_INPUTS]; + bytes_per[0] = 4; + bytes_per[1] = 20; + bytes_per[2] = 16; + bytes_per[3] = 16; + bytes_per[4] = 1; + bytes_per +} +const fn bits_per_fe_solidity_nested_mapping() -> [usize; NUM_FE_SOLIDITY_NESTED_MAPPING] { + let mut bits_per = [128; MAX_SUBQUERY_INPUTS]; + bits_per[0] = 32; + bits_per[1] = 160; + bits_per[2] = 128; + bits_per[3] = 128; + bits_per[4] = 8; + bits_per +} + +pub const BITS_PER_FE_SUBQUERY_OUTPUT: usize = BYTES_PER_FE_SUBQUERY_OUTPUT * 8; + +impl From for SubqueryOutput { + fn from(value: H256) -> Self { + let mut output = [F::ZERO; MAX_SUBQUERY_OUTPUTS]; + let hilo = encode_h256_to_hilo(&value); + output[0] = hilo.hi(); + output[1] = hilo.lo(); + Self(output) + } +} + +impl TryFrom for SubqueryOutput { + type Error = io::Error; + fn try_from(bytes: Bytes) -> Result { + if bytes.len() != 32 { + Err(io::Error::new(io::ErrorKind::InvalidData, "result length is not 32")) + } else { + let result = H256::from_slice(&bytes[..]); + let hilo = encode_h256_to_hilo(&result); + let mut result = [F::ZERO; MAX_SUBQUERY_OUTPUTS]; + result[0] = hilo.hi(); + result[1] = hilo.lo(); + Ok(Self(result)) + } + } +} + +impl TryFrom for FlattenedSubqueryResult { + type Error = io::Error; + fn try_from(result: SubqueryResult) -> Result { + let result: FieldSubqueryResult = result.try_into()?; + Ok(result.into()) + } +} + +impl TryFrom for FieldSubqueryResult { + type Error = io::Error; + fn try_from(result: SubqueryResult) -> Result { + let subquery = result.subquery.try_into()?; + let value = result.value.try_into()?; + Ok(FieldSubqueryResult { subquery, value }) + } +} + +impl TryFrom for FieldSubquery { + type Error = io::Error; + fn try_from(subquery: Subquery) -> Result { + let subquery = AnySubquery::try_from(subquery)?; + Ok(subquery.into()) + } +} + +impl From for FieldSubquery { + fn from(subquery: AnySubquery) -> Self { + match subquery { + AnySubquery::Null => FieldSubquery { + subquery_type: SubqueryType::Null, + encoded_subquery_data: Default::default(), + }, + AnySubquery::Header(subquery) => FieldHeaderSubquery::from(subquery).into(), + AnySubquery::Account(subquery) => FieldAccountSubquery::from(subquery).into(), + AnySubquery::Storage(subquery) => FieldStorageSubquery::from(subquery).into(), + AnySubquery::Transaction(subquery) => FieldTxSubquery::from(subquery).into(), + AnySubquery::Receipt(subquery) => FieldReceiptSubquery::from(subquery).into(), + AnySubquery::SolidityNestedMapping(subquery) => { + FieldSolidityNestedMappingSubquery::from(subquery).into() + } + AnySubquery::ECDSA(subquery) => ECDSAComponentInput::from(subquery).into(), + } + } +} + +impl From for FieldHeaderSubquery { + fn from(subquery: HeaderSubquery) -> Self { + Self { + block_number: F::from(subquery.block_number as u64), + field_idx: F::from(subquery.field_idx as u64), + } + } +} + +impl FieldHeaderSubquery { + pub fn flatten(self) -> [T; NUM_FE_HEADER] { + [self.block_number, self.field_idx] + } +} + +impl From> for FieldSubquery { + fn from(subquery: FieldHeaderSubquery) -> Self { + let mut encoded_subquery_data = [F::ZERO; MAX_SUBQUERY_INPUTS]; + encoded_subquery_data[..NUM_FE_HEADER].copy_from_slice(&subquery.flatten()); + Self { subquery_type: SubqueryType::Header, encoded_subquery_data } + } +} + +impl From for FieldAccountSubquery { + fn from(subquery: AccountSubquery) -> Self { + Self { + block_number: F::from(subquery.block_number as u64), + addr: encode_addr_to_field(&subquery.addr), + field_idx: F::from(subquery.field_idx as u64), + } + } +} + +impl FieldAccountSubquery { + pub fn flatten(self) -> [T; NUM_FE_ACCOUNT] { + [self.block_number, self.addr, self.field_idx] + } +} + +impl From> for FieldSubquery { + fn from(value: FieldAccountSubquery) -> Self { + let mut encoded_subquery_data = [F::ZERO; MAX_SUBQUERY_INPUTS]; + encoded_subquery_data[..NUM_FE_ACCOUNT].copy_from_slice(&value.flatten()); + Self { subquery_type: SubqueryType::Account, encoded_subquery_data } + } +} + +impl From for FieldStorageSubquery { + fn from(subquery: StorageSubquery) -> Self { + Self { + block_number: F::from(subquery.block_number as u64), + addr: encode_addr_to_field(&subquery.addr), + slot: encode_u256_to_hilo(&subquery.slot), + } + } +} + +impl FieldStorageSubquery { + pub fn flatten(self) -> [T; NUM_FE_STORAGE] { + [self.block_number, self.addr, self.slot.hi(), self.slot.lo()] + } +} + +impl From> for FieldSubquery { + fn from(value: FieldStorageSubquery) -> Self { + let mut encoded_subquery_data = [F::ZERO; MAX_SUBQUERY_INPUTS]; + encoded_subquery_data[..NUM_FE_STORAGE].copy_from_slice(&value.flatten()); + Self { subquery_type: SubqueryType::Storage, encoded_subquery_data } + } +} + +impl From for FieldTxSubquery { + fn from(subquery: TxSubquery) -> Self { + Self { + block_number: F::from(subquery.block_number as u64), + tx_idx: F::from(subquery.tx_idx as u64), + field_or_calldata_idx: F::from(subquery.field_or_calldata_idx as u64), + } + } +} + +impl FieldTxSubquery { + pub fn flatten(self) -> [T; NUM_FE_TX] { + [self.block_number, self.tx_idx, self.field_or_calldata_idx] + } +} + +impl From> for FieldSubquery { + fn from(value: FieldTxSubquery) -> Self { + let mut encoded_subquery_data = [F::ZERO; MAX_SUBQUERY_INPUTS]; + encoded_subquery_data[..NUM_FE_TX].copy_from_slice(&value.flatten()); + Self { subquery_type: SubqueryType::Transaction, encoded_subquery_data } + } +} + +impl From for FieldReceiptSubquery { + fn from(subquery: ReceiptSubquery) -> Self { + Self { + block_number: F::from(subquery.block_number as u64), + tx_idx: F::from(subquery.tx_idx as u64), + field_or_log_idx: F::from(subquery.field_or_log_idx as u64), + topic_or_data_or_address_idx: F::from(subquery.topic_or_data_or_address_idx as u64), + event_schema: encode_h256_to_hilo(&subquery.event_schema), + } + } +} + +impl FieldReceiptSubquery { + pub fn flatten(self) -> [T; NUM_FE_RECEIPT] { + [ + self.block_number, + self.tx_idx, + self.field_or_log_idx, + self.topic_or_data_or_address_idx, + self.event_schema.hi(), + self.event_schema.lo(), + ] + } +} + +impl From> for FieldSubquery { + fn from(value: FieldReceiptSubquery) -> Self { + let mut encoded_subquery_data = [F::ZERO; MAX_SUBQUERY_INPUTS]; + encoded_subquery_data[..NUM_FE_RECEIPT].copy_from_slice(&value.flatten()); + Self { subquery_type: SubqueryType::Receipt, encoded_subquery_data } + } +} + +impl From for FieldSolidityNestedMappingSubquery { + fn from(mut subquery: SolidityNestedMappingSubquery) -> Self { + assert!(subquery.keys.len() <= MAX_SOLIDITY_MAPPING_KEYS); + subquery.keys.resize(MAX_SOLIDITY_MAPPING_KEYS, H256::zero()); + + Self { + block_number: F::from(subquery.block_number as u64), + addr: encode_addr_to_field(&subquery.addr), + mapping_slot: encode_u256_to_hilo(&subquery.mapping_slot), + mapping_depth: F::from(subquery.mapping_depth as u64), + keys: subquery + .keys + .iter() + .map(|k| encode_h256_to_hilo(k)) + .collect::>() + .try_into() + .unwrap(), + } + } +} + +impl FieldSolidityNestedMappingSubquery { + pub fn flatten(self) -> [T; NUM_FE_SOLIDITY_NESTED_MAPPING] { + assert_eq!(5 + self.keys.len() * 2, NUM_FE_SOLIDITY_NESTED_MAPPING); + let mut result = [self.block_number; NUM_FE_SOLIDITY_NESTED_MAPPING]; // default will be overwritten in all indices so doesn't matter + result[0] = self.block_number; + result[1] = self.addr; + result[2] = self.mapping_slot.hi(); + result[3] = self.mapping_slot.lo(); + result[4] = self.mapping_depth; + for (i, key) in self.keys.iter().enumerate() { + result[5 + i * 2] = key.hi(); + result[5 + i * 2 + 1] = key.lo(); + } + result + } +} + +impl From> for FieldSubquery { + fn from(value: FieldSolidityNestedMappingSubquery) -> Self { + let mut encoded_subquery_data = [F::ZERO; MAX_SUBQUERY_INPUTS]; + encoded_subquery_data[..NUM_FE_SOLIDITY_NESTED_MAPPING].copy_from_slice(&value.flatten()); + Self { subquery_type: SubqueryType::SolidityNestedMapping, encoded_subquery_data } + } +} + +impl From> for FieldSubquery { + fn from(value: ECDSAComponentInput) -> Self { + let mut encoded_subquery_data = [F::ZERO; MAX_SUBQUERY_INPUTS]; + encoded_subquery_data[..ECDSAComponentInput::::NUM_FE].copy_from_slice(&value.flatten()); + Self { subquery_type: SubqueryType::ECDSA, encoded_subquery_data } + } +} diff --git a/axiom-codec/src/encoder/mod.rs b/axiom-codec/src/encoder/mod.rs new file mode 100644 index 00000000..1f0e0c19 --- /dev/null +++ b/axiom-codec/src/encoder/mod.rs @@ -0,0 +1,3 @@ +/// Encode different subqueries into in-circuit field element format +pub mod field_elements; +pub mod native; diff --git a/axiom-codec/src/encoder/native.rs b/axiom-codec/src/encoder/native.rs new file mode 100644 index 00000000..9f7ef524 --- /dev/null +++ b/axiom-codec/src/encoder/native.rs @@ -0,0 +1,333 @@ +use std::{ + io::{Error, ErrorKind, Result, Write}, + iter, +}; + +use axiom_components::ecdsa::ECDSAComponentNativeInput; +use byteorder::{BigEndian, WriteBytesExt}; +use ethers_core::{types::H256, utils::keccak256}; + +use crate::{ + constants::USER_PROOF_LEN_BYTES, + types::native::{ + AccountSubquery, AnySubquery, AxiomV2ComputeQuery, AxiomV2ComputeSnark, AxiomV2DataQuery, + HeaderSubquery, ReceiptSubquery, SolidityNestedMappingSubquery, StorageSubquery, Subquery, + SubqueryResult, SubqueryType, TxSubquery, + }, + utils::writer::{write_curve_compressed, write_field_be, write_u256}, + VERSION, +}; + +pub fn get_query_hash_v2( + source_chain_id: u64, + data_query: &AxiomV2DataQuery, + compute_query: &AxiomV2ComputeQuery, +) -> Result { + if source_chain_id != data_query.source_chain_id { + return Err(Error::new(ErrorKind::InvalidInput, "source_chain_id mismatch")); + } + let data_query_hash = data_query.keccak(); + let encoded_compute_query = compute_query.encode()?; + + let mut encoded = vec![]; + encoded.write_u8(VERSION)?; + encoded.write_u64::(source_chain_id)?; + encoded.write_all(data_query_hash.as_bytes())?; + encoded.write_all(&encoded_compute_query)?; + Ok(H256(keccak256(encoded))) +} + +impl AxiomV2ComputeQuery { + pub fn encode(&self) -> Result> { + if self.k == 0 { + return Ok([&[0u8], &self.result_len.to_be_bytes()[..]].concat()); + } + let encoded_query_schema = encode_query_schema(self.k, self.result_len, &self.vkey)?; + let proof_len = self.compute_proof.len() as u32; + let encoded_proof_len = proof_len.to_be_bytes(); + assert_eq!(encoded_proof_len.len(), USER_PROOF_LEN_BYTES); + let encoded_compute_proof = [&encoded_proof_len[..], &self.compute_proof].concat(); + Ok([encoded_query_schema, encoded_compute_proof].concat()) + } + + pub fn keccak(&self) -> Result { + Ok(H256(keccak256(self.encode()?))) + } +} + +impl AxiomV2ComputeSnark { + /// Encoded `kzg_accumulator` (is any) followed by `compute_results`. + pub fn encode_instances(&self) -> Result> { + let mut encoded = vec![]; + if let Some((lhs, rhs)) = self.kzg_accumulator { + write_curve_compressed(&mut encoded, lhs)?; + write_curve_compressed(&mut encoded, rhs)?; + } else { + encoded = vec![0u8; 64]; + } + for &output in &self.compute_results { + encoded.write_all(&output[..])?; + } + Ok(encoded) + } + + pub fn encode(&self) -> Result> { + let mut encoded = self.encode_instances()?; + encoded.write_all(&self.proof_transcript)?; + Ok(encoded) + } +} + +impl AxiomV2DataQuery { + pub fn keccak(&self) -> H256 { + get_data_query_hash(self.source_chain_id, &self.subqueries) + } +} + +pub fn get_data_query_hash(source_chain_id: u64, subqueries: &[Subquery]) -> H256 { + let subquery_hashes = subqueries.iter().flat_map(|subquery| subquery.keccak().0); + let encoded: Vec<_> = + iter::empty().chain(source_chain_id.to_be_bytes()).chain(subquery_hashes).collect(); + H256(keccak256(encoded)) +} + +pub fn get_query_schema_hash(k: u8, result_len: u16, vkey: &[H256]) -> Result { + if k == 0 { + return Ok(H256::zero()); + } + let encoded_query_schema = encode_query_schema(k, result_len, vkey)?; + Ok(H256(keccak256(encoded_query_schema))) +} + +pub fn encode_query_schema(k: u8, result_len: u16, vkey: &[H256]) -> Result> { + if k >= 28 { + return Err(Error::new(ErrorKind::InvalidInput, "k must be less than 28")); + } + if k == 0 { + unreachable!() + } + let mut encoded = Vec::with_capacity(3 + vkey.len() * 32); + encoded.write_u8(k)?; + encoded.write_u16::(result_len)?; + let vkey_len: u8 = vkey + .len() + .try_into() + .map_err(|_| Error::new(ErrorKind::InvalidInput, "vkey len exceeds u8"))?; + encoded.write_u8(vkey_len)?; + for fe in vkey { + encoded.write_all(fe.as_bytes())?; + } + Ok(encoded) +} + +impl Subquery { + pub fn encode(&self) -> Vec { + let sub_type = (self.subquery_type as u16).to_be_bytes(); + let subquery_data = self.encoded_subquery_data.as_ref(); + [&sub_type[..], subquery_data].concat() + } + pub fn keccak(&self) -> H256 { + H256(keccak256(self.encode())) + } +} + +impl SubqueryResult { + pub fn encode(&self) -> Vec { + let subquery = self.subquery.encode(); + let value = self.value.as_ref(); + [&subquery[..], value].concat() + } + pub fn keccak(&self) -> H256 { + H256(keccak256(self.encode())) + } +} + +pub fn encode_header_subquery(writer: &mut impl Write, subquery: HeaderSubquery) -> Result<()> { + let HeaderSubquery { block_number, field_idx } = subquery; + writer.write_u32::(block_number)?; + writer.write_u32::(field_idx)?; + Ok(()) +} + +pub fn encode_account_subquery(writer: &mut impl Write, subquery: AccountSubquery) -> Result<()> { + let AccountSubquery { block_number, addr, field_idx } = subquery; + writer.write_u32::(block_number)?; + writer.write_all(&addr[..])?; + writer.write_u32::(field_idx)?; + Ok(()) +} + +pub fn encode_storage_subquery(writer: &mut impl Write, subquery: StorageSubquery) -> Result<()> { + let StorageSubquery { block_number, addr, slot } = subquery; + writer.write_u32::(block_number)?; + writer.write_all(&addr[..])?; + write_u256(writer, slot)?; + Ok(()) +} + +pub fn encode_tx_subquery(writer: &mut impl Write, subquery: TxSubquery) -> Result<()> { + let TxSubquery { block_number, tx_idx, field_or_calldata_idx } = subquery; + writer.write_u32::(block_number)?; + writer.write_u16::(tx_idx)?; + writer.write_u32::(field_or_calldata_idx)?; + Ok(()) +} + +pub fn encode_receipt_subquery(writer: &mut impl Write, subquery: ReceiptSubquery) -> Result<()> { + let ReceiptSubquery { + block_number, + tx_idx, + field_or_log_idx, + topic_or_data_or_address_idx, + event_schema, + } = subquery; + writer.write_u32::(block_number)?; + writer.write_u16::(tx_idx)?; + writer.write_u32::(field_or_log_idx)?; + writer.write_u32::(topic_or_data_or_address_idx)?; + writer.write_all(&event_schema[..])?; + Ok(()) +} + +pub fn encode_solidity_nested_mapping_subquery( + writer: &mut impl Write, + subquery: SolidityNestedMappingSubquery, +) -> Result<()> { + let SolidityNestedMappingSubquery { block_number, addr, mapping_slot, mapping_depth, mut keys } = + subquery; + writer.write_u32::(block_number)?; + writer.write_all(&addr[..])?; + write_u256(writer, mapping_slot)?; + writer.write_u8(mapping_depth)?; + keys.resize(mapping_depth as usize, H256::zero()); + for key in keys { + writer.write_all(&key[..])?; + } + Ok(()) +} + +pub fn encode_ecdsa_component_native_input( + writer: &mut impl Write, + input: ECDSAComponentNativeInput, +) -> Result<()> { + let ECDSAComponentNativeInput { pubkey, r, s, msg_hash } = input; + write_field_be(writer, pubkey.0)?; + write_field_be(writer, pubkey.1)?; + write_field_be(writer, r)?; + write_field_be(writer, s)?; + writer.write_all(&msg_hash[..])?; + Ok(()) +} + +impl From for Subquery { + fn from(value: HeaderSubquery) -> Self { + let mut bytes = vec![]; + encode_header_subquery(&mut bytes, value).unwrap(); + Self { subquery_type: SubqueryType::Header, encoded_subquery_data: bytes.into() } + } +} + +impl From for Subquery { + fn from(value: AccountSubquery) -> Self { + let mut bytes = vec![]; + encode_account_subquery(&mut bytes, value).unwrap(); + Self { subquery_type: SubqueryType::Account, encoded_subquery_data: bytes.into() } + } +} + +impl From for Subquery { + fn from(value: StorageSubquery) -> Self { + let mut bytes = vec![]; + encode_storage_subquery(&mut bytes, value).unwrap(); + Self { subquery_type: SubqueryType::Storage, encoded_subquery_data: bytes.into() } + } +} + +impl From for Subquery { + fn from(value: TxSubquery) -> Self { + let mut bytes = vec![]; + encode_tx_subquery(&mut bytes, value).unwrap(); + Self { subquery_type: SubqueryType::Transaction, encoded_subquery_data: bytes.into() } + } +} + +impl From for Subquery { + fn from(value: ReceiptSubquery) -> Self { + let mut bytes = vec![]; + encode_receipt_subquery(&mut bytes, value).unwrap(); + Self { subquery_type: SubqueryType::Receipt, encoded_subquery_data: bytes.into() } + } +} + +impl From for Subquery { + fn from(value: SolidityNestedMappingSubquery) -> Self { + let mut bytes = vec![]; + encode_solidity_nested_mapping_subquery(&mut bytes, value).unwrap(); + Self { + subquery_type: SubqueryType::SolidityNestedMapping, + encoded_subquery_data: bytes.into(), + } + } +} + +impl From for Subquery { + fn from(value: ECDSAComponentNativeInput) -> Self { + let mut bytes = vec![]; + encode_ecdsa_component_native_input(&mut bytes, value).unwrap(); + Self { subquery_type: SubqueryType::ECDSA, encoded_subquery_data: bytes.into() } + } +} + +impl From for Subquery { + fn from(value: AnySubquery) -> Self { + match value { + AnySubquery::Null => { + Self { subquery_type: SubqueryType::Null, encoded_subquery_data: vec![].into() } + } + AnySubquery::Header(subquery) => subquery.into(), + AnySubquery::Account(subquery) => subquery.into(), + AnySubquery::Storage(subquery) => subquery.into(), + AnySubquery::Transaction(subquery) => subquery.into(), + AnySubquery::Receipt(subquery) => subquery.into(), + AnySubquery::SolidityNestedMapping(subquery) => subquery.into(), + AnySubquery::ECDSA(subquery) => subquery.into(), + } + } +} + +impl From for AnySubquery { + fn from(value: HeaderSubquery) -> Self { + AnySubquery::Header(value) + } +} +impl From for AnySubquery { + fn from(value: AccountSubquery) -> Self { + AnySubquery::Account(value) + } +} +impl From for AnySubquery { + fn from(value: StorageSubquery) -> Self { + AnySubquery::Storage(value) + } +} +impl From for AnySubquery { + fn from(value: TxSubquery) -> Self { + AnySubquery::Transaction(value) + } +} +impl From for AnySubquery { + fn from(value: ReceiptSubquery) -> Self { + AnySubquery::Receipt(value) + } +} +impl From for AnySubquery { + fn from(value: SolidityNestedMappingSubquery) -> Self { + AnySubquery::SolidityNestedMapping(value) + } +} + +impl From for AnySubquery { + fn from(value: ECDSAComponentNativeInput) -> Self { + AnySubquery::ECDSA(value) + } +} diff --git a/axiom-codec/src/lib.rs b/axiom-codec/src/lib.rs new file mode 100644 index 00000000..5b91790c --- /dev/null +++ b/axiom-codec/src/lib.rs @@ -0,0 +1,16 @@ +#![feature(trait_alias)] +#![feature(io_error_other)] + +pub use axiom_eth::utils::hilo::HiLo; +pub use axiom_eth::Field; + +/// Constants +pub mod constants; +pub mod decoder; +pub mod encoder; +/// Special constants used in subquery specification +pub mod special_values; +pub mod types; +pub mod utils; + +pub const VERSION: u8 = 0x02; diff --git a/axiom-codec/src/special_values.rs b/axiom-codec/src/special_values.rs new file mode 100644 index 00000000..8d0e12ee --- /dev/null +++ b/axiom-codec/src/special_values.rs @@ -0,0 +1,24 @@ +pub const HEADER_EXTRA_DATA_LEN_FIELD_IDX: usize = 52; +pub const HEADER_HASH_FIELD_IDX: usize = 50; +pub const HEADER_HEADER_SIZE_FIELD_IDX: usize = 51; +pub const HEADER_LOGS_BLOOM_FIELD_IDX_OFFSET: usize = 70; +pub const RECEIPT_ADDRESS_IDX: usize = 50; +pub const RECEIPT_BLOCK_NUMBER_FIELD_IDX: usize = 52; +pub const RECEIPT_DATA_IDX_OFFSET: usize = 100; +pub const RECEIPT_FIELD_IDX_OFFSET: usize = 0; +pub const RECEIPT_LOG_IDX_OFFSET: usize = 100; +pub const RECEIPT_LOGS_BLOOM_IDX_OFFSET: usize = 70; +pub const RECEIPT_TOPIC_IDX_OFFSET: usize = 0; +pub const RECEIPT_TX_INDEX_FIELD_IDX: usize = 53; +pub const RECEIPT_TX_TYPE_FIELD_IDX: usize = 51; +pub const TX_BLOCK_NUMBER_FIELD_IDX: usize = 52; +pub const TX_CALLDATA_HASH_FIELD_IDX: usize = 55; +pub const TX_CALLDATA_IDX_OFFSET: usize = 100; +pub const TX_CONTRACT_DATA_IDX_OFFSET: usize = 100000; +pub const TX_CONTRACT_DEPLOY_SELECTOR_VALUE: usize = 60; +pub const TX_DATA_LENGTH_FIELD_IDX: usize = 56; +pub const TX_FIELD_IDX_OFFSET: usize = 0; +pub const TX_FUNCTION_SELECTOR_FIELD_IDX: usize = 54; +pub const TX_NO_CALLDATA_SELECTOR_VALUE: usize = 61; +pub const TX_TX_INDEX_FIELD_IDX: usize = 53; +pub const TX_TX_TYPE_FIELD_IDX: usize = 51; diff --git a/axiom-codec/src/types/field_elements.rs b/axiom-codec/src/types/field_elements.rs new file mode 100644 index 00000000..d8bfba9c --- /dev/null +++ b/axiom-codec/src/types/field_elements.rs @@ -0,0 +1,175 @@ +use std::{fmt::Debug, ops::Deref}; + +use axiom_components::ecdsa::ECDSAComponentInput; +use serde::{Deserialize, Serialize}; + +use crate::{ + constants::{MAX_SOLIDITY_MAPPING_KEYS, MAX_SUBQUERY_INPUTS, MAX_SUBQUERY_OUTPUTS}, + Field, HiLo, +}; + +use super::native::SubqueryType; + +pub const SUBQUERY_KEY_LEN: usize = 1 + MAX_SUBQUERY_INPUTS; +pub const SUBQUERY_RESULT_LEN: usize = SUBQUERY_KEY_LEN + MAX_SUBQUERY_OUTPUTS; + +/// Subquery type and data +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct SubqueryKey(pub [T; SUBQUERY_KEY_LEN]); + +/// Only the output of the subquery. Does not include subquery data. +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct SubqueryOutput(pub [T; MAX_SUBQUERY_OUTPUTS]); + +impl Deref for SubqueryKey { + type Target = [T]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Deref for SubqueryOutput { + type Target = [T]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct FlattenedSubqueryResult { + pub key: SubqueryKey, + pub value: SubqueryOutput, +} + +impl FlattenedSubqueryResult { + pub fn new(key: SubqueryKey, value: SubqueryOutput) -> Self { + Self { key, value } + } +} + +impl FlattenedSubqueryResult { + pub fn flatten(&self) -> [T; SUBQUERY_RESULT_LEN] { + self.to_fixed_array() + } + + pub fn to_fixed_array(&self) -> [T; SUBQUERY_RESULT_LEN] { + [&self.key.0[..], &self.value.0[..]].concat().try_into().unwrap_or_else(|_| unreachable!()) + } +} + +/// You should probably use [FlattenedSubqueryResult] instead. +#[derive(Clone, Copy, Debug)] +pub struct FieldSubqueryResult { + pub subquery: FieldSubquery, + pub value: SubqueryOutput, +} + +impl FieldSubqueryResult { + pub fn flatten(self) -> FlattenedSubqueryResult { + FlattenedSubqueryResult { key: self.subquery.flatten(), value: self.value } + } + pub fn to_fixed_array(self) -> [F; SUBQUERY_RESULT_LEN] { + self.flatten().to_fixed_array() + } +} + +impl From> for FlattenedSubqueryResult { + fn from(value: FieldSubqueryResult) -> Self { + value.flatten() + } +} + +impl From> for SubqueryKey { + fn from(subquery: FieldSubquery) -> Self { + let mut key = [F::ZERO; 1 + MAX_SUBQUERY_INPUTS]; + key[0] = F::from(subquery.subquery_type as u64); + key[1..].copy_from_slice(&subquery.encoded_subquery_data); + Self(key) + } +} + +/// Subquery resized to fixed length. For ZK use. +/// Consider using [SubqueryKey] instead. +#[derive(Clone, Copy, Debug)] +pub struct FieldSubquery { + pub subquery_type: SubqueryType, + pub encoded_subquery_data: [T; MAX_SUBQUERY_INPUTS], +} + +impl FieldSubquery { + pub fn flatten(&self) -> SubqueryKey { + let mut key = [F::ZERO; SUBQUERY_KEY_LEN]; + key[0] = F::from(self.subquery_type as u64); + key[1..].copy_from_slice(&self.encoded_subquery_data); + SubqueryKey(key) + } +} + +// unpacked for now +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct FieldHeaderSubquery { + pub block_number: T, + pub field_idx: T, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct FieldAccountSubquery { + pub block_number: T, + pub addr: T, // F::CAPACITY >= 160 + pub field_idx: T, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct FieldStorageSubquery { + pub block_number: T, + pub addr: T, // F::CAPACITY >= 160 + pub slot: HiLo, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct FieldTxSubquery { + pub block_number: T, + pub tx_idx: T, + pub field_or_calldata_idx: T, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct FieldReceiptSubquery { + pub block_number: T, + pub tx_idx: T, + pub field_or_log_idx: T, + pub topic_or_data_or_address_idx: T, + pub event_schema: HiLo, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct FieldSolidityNestedMappingSubquery { + pub block_number: T, + pub addr: T, // F::CAPACITY >= 160 + pub mapping_slot: HiLo, + pub mapping_depth: T, + pub keys: [HiLo; MAX_SOLIDITY_MAPPING_KEYS], +} + +/// A result consists of a pair of the original subquery and the output value. +/// This type is just a pair, but has nicer JSON serialization. +#[derive(Clone, Copy, Debug, Hash, Serialize, Deserialize, PartialEq, Eq)] +pub struct AnySubqueryResult { + pub subquery: S, + pub value: T, +} + +impl AnySubqueryResult { + pub fn new(subquery: S, value: T) -> Self { + Self { subquery, value } + } +} + +pub type FieldHeaderSubqueryResult = AnySubqueryResult, HiLo>; +pub type FieldAccountSubqueryResult = AnySubqueryResult, HiLo>; +pub type FieldStorageSubqueryResult = AnySubqueryResult, HiLo>; +pub type FieldTxSubqueryResult = AnySubqueryResult, HiLo>; +pub type FieldReceiptSubqueryResult = AnySubqueryResult, HiLo>; +pub type FieldSolidityNestedMappingSubqueryResult = + AnySubqueryResult, HiLo>; +pub type FieldECDSASubqueryResult = AnySubqueryResult, T>; diff --git a/axiom-codec/src/types/mod.rs b/axiom-codec/src/types/mod.rs new file mode 100644 index 00000000..b3deaa5c --- /dev/null +++ b/axiom-codec/src/types/mod.rs @@ -0,0 +1,4 @@ +/// Types consisting of fixed width field elements, for a > 253 bit field. +pub mod field_elements; +/// Rust native types, bytes based +pub mod native; diff --git a/axiom-codec/src/types/native.rs b/axiom-codec/src/types/native.rs new file mode 100644 index 00000000..2a2c90a2 --- /dev/null +++ b/axiom-codec/src/types/native.rs @@ -0,0 +1,151 @@ +use axiom_components::ecdsa::ECDSAComponentNativeInput; +use axiom_eth::halo2curves::bn256::G1Affine; +use ethers_core::types::{Address, Bytes, H256, U256}; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] +#[serde(rename_all = "camelCase")] +pub struct AxiomV2ComputeQuery { + pub k: u8, + pub result_len: u16, + // Should be bytes32[] + /// The onchain vkey + pub vkey: Vec, + /// This is actually the concatenation of public instances and proof transcript + pub compute_proof: Bytes, +} + +#[derive(Clone, Debug)] +pub struct AxiomV2ComputeSnark { + /// (lhs G1 of pairing, rhs G1 of pairing) if this snark is from an aggregation circuit. + pub kzg_accumulator: Option<(G1Affine, G1Affine)>, + pub compute_results: Vec, + pub proof_transcript: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] +#[serde(rename_all = "camelCase")] +pub struct AxiomV2DataQuery { + pub source_chain_id: u64, + pub subqueries: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] +#[serde(rename_all = "camelCase")] +pub struct SubqueryResult { + pub subquery: Subquery, + /// The output of the subquery. In V2, always bytes32. + pub value: Bytes, +} + +impl Default for SubqueryResult { + fn default() -> Self { + Self { subquery: Subquery::default(), value: Bytes::from([0u8; 32]) } + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub struct Subquery { + /// uint16 type of subquery + pub subquery_type: SubqueryType, + /// Subquery data encoded, _without_ the subquery type. Length is variable and **not** resized. + pub encoded_subquery_data: Bytes, +} + +#[derive( + Clone, + Copy, + Debug, + Default, + Serialize_repr, + Deserialize_repr, + Hash, + PartialEq, + Eq, + PartialOrd, + Ord, +)] +#[repr(u16)] +pub enum SubqueryType { + #[default] + Null = 0, // For lookup tables, important to have a null type + Header = 1, + Account = 2, + Storage = 3, + Transaction = 4, + Receipt = 5, + SolidityNestedMapping = 6, + ECDSA = 7, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum AnySubquery { + Null, + Header(HeaderSubquery), + Account(AccountSubquery), + Storage(StorageSubquery), + Transaction(TxSubquery), + Receipt(ReceiptSubquery), + SolidityNestedMapping(SolidityNestedMappingSubquery), + ECDSA(ECDSAComponentNativeInput), +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub struct HeaderSubquery { + pub block_number: u32, + pub field_idx: u32, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub struct AccountSubquery { + pub block_number: u32, + pub addr: Address, + pub field_idx: u32, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub struct StorageSubquery { + pub block_number: u32, + pub addr: Address, + pub slot: U256, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub struct TxSubquery { + /// The block number with the requested transaction. + pub block_number: u32, + /// The index of the transaction in the block. + pub tx_idx: u16, + /// Special index to specify what subquery value to extract from the transaction. + pub field_or_calldata_idx: u32, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub struct ReceiptSubquery { + /// The block number with the requested transaction. + pub block_number: u32, + /// The index of the transaction in the block. + pub tx_idx: u16, + /// Special index to specify what subquery value to extract from the transaction. + pub field_or_log_idx: u32, + pub topic_or_data_or_address_idx: u32, + pub event_schema: H256, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub struct SolidityNestedMappingSubquery { + pub block_number: u32, + pub addr: Address, + pub mapping_slot: U256, + /// Should be equal to `keys.len()` + pub mapping_depth: u8, + pub keys: Vec, +} diff --git a/axiom-codec/src/utils/mod.rs b/axiom-codec/src/utils/mod.rs new file mode 100644 index 00000000..b63267cf --- /dev/null +++ b/axiom-codec/src/utils/mod.rs @@ -0,0 +1,5 @@ +pub mod native; +pub mod reader; +/// Implement conversion from codec types to [axiom_eth::utils::component::types::Flatten] types +mod shim; +pub mod writer; diff --git a/axiom-codec/src/utils/native.rs b/axiom-codec/src/utils/native.rs new file mode 100644 index 00000000..c24af17e --- /dev/null +++ b/axiom-codec/src/utils/native.rs @@ -0,0 +1,46 @@ +pub use axiom_eth::utils::{encode_addr_to_field, encode_h256_to_hilo}; +use ethers_core::types::{Address, H256, U256}; + +use crate::{Field, HiLo}; + +pub fn u256_to_h256(input: &U256) -> H256 { + let mut bytes = [0; 32]; + input.to_big_endian(&mut bytes); + H256(bytes) +} + +pub fn decode_hilo_to_h256(fe: HiLo) -> H256 { + let mut bytes = [0u8; 32]; + bytes[..16].copy_from_slice(&fe.lo().to_bytes_le()[..16]); + bytes[16..].copy_from_slice(&fe.hi().to_bytes_le()[..16]); + bytes.reverse(); + H256(bytes) +} + +/// Takes U256, converts to bytes32 (big endian) and returns (hash[..16], hash[16..]) represented as big endian numbers in the prime field +pub fn encode_u256_to_hilo(input: &U256) -> HiLo { + let mut bytes = vec![0; 32]; + input.to_little_endian(&mut bytes); + // repr is in little endian + let mut repr = [0u8; 32]; + repr[..16].copy_from_slice(&bytes[16..]); + let hi = F::from_bytes_le(&repr); + let mut repr = [0u8; 32]; + repr[..16].copy_from_slice(&bytes[..16]); + let lo = F::from_bytes_le(&repr); + HiLo::from_lo_hi([lo, hi]) +} + +pub fn decode_hilo_to_u256(fe: HiLo) -> U256 { + let mut bytes = [0u8; 32]; + bytes[..16].copy_from_slice(&fe.lo().to_bytes_le()[..16]); + bytes[16..].copy_from_slice(&fe.hi().to_bytes_le()[..16]); + U256::from_little_endian(&bytes) +} + +pub fn decode_field_to_addr(fe: &F) -> Address { + let mut bytes = [0u8; 20]; + bytes.copy_from_slice(&fe.to_bytes_le()[..20]); + bytes.reverse(); + Address::from_slice(&bytes) +} diff --git a/axiom-codec/src/utils/reader.rs b/axiom-codec/src/utils/reader.rs new file mode 100644 index 00000000..eb3d6610 --- /dev/null +++ b/axiom-codec/src/utils/reader.rs @@ -0,0 +1,44 @@ +use std::io::{Error, ErrorKind, Read, Result}; + +use axiom_eth::halo2curves::CurveAffine; +use ethers_core::types::{Address, H256, U256}; + +use crate::Field; + +pub fn read_address(reader: &mut impl Read) -> Result
{ + let mut addr = [0u8; 20]; + reader.read_exact(&mut addr)?; + Ok(Address::from_slice(&addr)) +} + +pub fn read_u256(reader: &mut impl Read) -> Result { + let mut word = [0u8; 32]; + reader.read_exact(&mut word)?; + Ok(U256::from_big_endian(&word)) +} + +pub fn read_h256(reader: &mut impl Read) -> Result { + let mut hash = [0u8; 32]; + reader.read_exact(&mut hash)?; + Ok(H256(hash)) +} + +pub fn read_field_le(reader: &mut impl Read) -> Result { + let mut repr = [0u8; 32]; + reader.read_exact(&mut repr)?; + Ok(F::from_bytes_le(&repr)) +} + +pub fn read_field_be(reader: &mut impl Read) -> Result { + let mut repr = [0u8; 32]; + reader.read_exact(&mut repr)?; + repr.reverse(); + Ok(F::from_bytes_le(&repr)) +} + +pub fn read_curve_compressed(reader: &mut impl Read) -> Result { + let mut compressed = C::Repr::default(); + reader.read_exact(compressed.as_mut())?; + Option::from(C::from_bytes(&compressed)) + .ok_or_else(|| Error::new(ErrorKind::Other, "Invalid compressed point encoding")) +} diff --git a/axiom-codec/src/utils/shim.rs b/axiom-codec/src/utils/shim.rs new file mode 100644 index 00000000..061324df --- /dev/null +++ b/axiom-codec/src/utils/shim.rs @@ -0,0 +1,30 @@ +use axiom_eth::{impl_flatten_conversion, impl_logical_input, Field}; + +use crate::{ + encoder::field_elements::{ + BITS_PER_FE_ACCOUNT, BITS_PER_FE_HEADER, BITS_PER_FE_RECEIPT, + BITS_PER_FE_SOLIDITY_NESTED_MAPPING, BITS_PER_FE_STORAGE, BITS_PER_FE_SUBQUERY_RESULT, + BITS_PER_FE_TX, + }, + types::field_elements::{ + FieldAccountSubquery, FieldHeaderSubquery, FieldReceiptSubquery, + FieldSolidityNestedMappingSubquery, FieldStorageSubquery, FieldTxSubquery, + FlattenedSubqueryResult, + }, +}; + +// Inputs by subquery type, in field elements +impl_flatten_conversion!(FieldHeaderSubquery, BITS_PER_FE_HEADER); +impl_logical_input!(FieldHeaderSubquery, 1); +impl_flatten_conversion!(FieldAccountSubquery, BITS_PER_FE_ACCOUNT); +impl_logical_input!(FieldAccountSubquery, 1); +impl_flatten_conversion!(FieldStorageSubquery, BITS_PER_FE_STORAGE); +impl_logical_input!(FieldStorageSubquery, 1); +impl_flatten_conversion!(FieldTxSubquery, BITS_PER_FE_TX); +impl_logical_input!(FieldTxSubquery, 1); +impl_flatten_conversion!(FieldReceiptSubquery, BITS_PER_FE_RECEIPT); +impl_logical_input!(FieldReceiptSubquery, 1); +impl_flatten_conversion!(FieldSolidityNestedMappingSubquery, BITS_PER_FE_SOLIDITY_NESTED_MAPPING); +impl_logical_input!(FieldSolidityNestedMappingSubquery, 1); +impl_flatten_conversion!(FlattenedSubqueryResult, BITS_PER_FE_SUBQUERY_RESULT); +impl_logical_input!(FlattenedSubqueryResult, 0); diff --git a/axiom-codec/src/utils/writer.rs b/axiom-codec/src/utils/writer.rs new file mode 100644 index 00000000..96236c25 --- /dev/null +++ b/axiom-codec/src/utils/writer.rs @@ -0,0 +1,32 @@ +use std::io::{Result, Write}; + +use axiom_eth::{halo2_base::utils::ScalarField, halo2curves::CurveAffine}; +use ethers_core::types::U256; + +use crate::Field; + +pub fn write_u256(writer: &mut impl Write, word: U256) -> Result<()> { + let mut buf = [0u8; 32]; + word.to_big_endian(&mut buf); + writer.write_all(&buf)?; + Ok(()) +} + +pub fn write_field_le(writer: &mut impl Write, fe: F) -> Result<()> { + let repr = ScalarField::to_bytes_le(&fe); + writer.write_all(&repr)?; + Ok(()) +} + +pub fn write_field_be(writer: &mut impl Write, fe: F) -> Result<()> { + let mut repr = ScalarField::to_bytes_le(&fe); + repr.reverse(); + writer.write_all(&repr)?; + Ok(()) +} + +pub fn write_curve_compressed(writer: &mut impl Write, point: C) -> Result<()> { + let compressed = point.to_bytes(); + writer.write_all(compressed.as_ref())?; + Ok(()) +} diff --git a/axiom-components/.gitignore b/axiom-components/.gitignore new file mode 100644 index 00000000..68316418 --- /dev/null +++ b/axiom-components/.gitignore @@ -0,0 +1,22 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +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 + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Circuit generated files +params/ +*.pk +*.snark + +**/.env +.DS_Store \ No newline at end of file diff --git a/axiom-components/Cargo.toml b/axiom-components/Cargo.toml new file mode 100644 index 00000000..16a4794b --- /dev/null +++ b/axiom-components/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "axiom-components" +version = "0.1.0" +edition = "2021" + +[dependencies] +itertools = "0.11" +lazy_static = "1.4.0" +rayon = "1.8" + +# serialization +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0", default-features = false } + +# misc +log = "0.4" +anyhow = "1.0" +num-bigint = "0.4" +num-traits = "0.2" +component-derive = { path = "component-derive" } + +# halo2, features turned on by axiom-eth +axiom-eth = { path = "../axiom-eth", default-features = false, features = [ + "jemallocator", + "providers", + "aggregation", + "evm", + "keygen", +] } +halo2-ecc = { workspace = true } +ethers-core = { workspace = true } +secp256k1 = "0.28.2" + +[dev-dependencies] +ark-std = { version = "0.3.0", features = ["print-trace"] } + +[features] +default = ["halo2-axiom", "display"] +display = ["axiom-eth/display"] +halo2-pse = ["axiom-eth/halo2-pse"] +halo2-axiom = ["axiom-eth/halo2-axiom"] +asm = ["axiom-eth/asm"] +revm = ["axiom-eth/revm"] + +[profile.dev] +opt-level = 3 +debug = 2 # change to 0 or 2 for more or less debug info +overflow-checks = true +incremental = true diff --git a/axiom-components/LICENSE b/axiom-components/LICENSE new file mode 100644 index 00000000..d7234de6 --- /dev/null +++ b/axiom-components/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 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-components/README.md b/axiom-components/README.md new file mode 100644 index 00000000..59786335 --- /dev/null +++ b/axiom-components/README.md @@ -0,0 +1,47 @@ +# Axiom Components + +To use `axiom-eth-working` with ssh, + +```bash +mkdir .cargo +``` +Add the following to `.cargo/config.toml`: +```toml +[net] +git-fetch-with-cli = true +``` + +## `BasicComponentScaffold` + +The scaffold provides an streamlined to implement component circuits for the [component framework](https://github.com/axiom-crypto/axiom-eth-working/blob/develop/axiom-eth/src/utils/README.md). At the moment, the scaffold only supports `RlcCircuitBuilder` circuits that have fix-len inputs and do not make calls to other component circuits. + +To implement the `BasicComponentScaffold` trait (for some struct `ExampleComponent`), the following structs must first be specified: + +* `ExampleComponentParams` -- to specify any config options of the component. This struct should include `pub capacity: usize,`, specifying the max number of calls that can be made, for the `ComponentParams` derive macro to work. +* `ExampleComponentInput`/`ExampleComponentOutput` -- the input/output structs of a *single* component call. See all the traits that must be derived for these structs [here](https://github.com/axiom-crypto/axiom-components/blob/2fe9dacdf9bd06d6f9949c7b3738f1c6a562fe9b/src/example/mod.rs#L29). We use the `ComponentIO` derive macro to auto-implement some of the component traits for IO values/witnesses. This macro requires that all fields within the struct implement the `InputFlatten` trait, which has been implemented for some primitive types in [./src/utils/flatten.rs](./src/utils/flatten.rs). + +Once you specify these structs, using the `component!` macro on your component name (ie. `component!(Example)`) will create the following: + +* `ExampleComponent` -- the struct on which the `BasicComponentScaffold` trait must be implemented on. **Note: without implementing the `BasicComponentScaffold` trait on this struct, the macro may give some errors** +* `ComponentTypeExample` -- a wrapper type around `BasicComponentScaffoldImpl>` (which implements the `ComponentType` trait) for identifying the component when making calls from other circuits. +* `ExampleComponentCall` -- a wrapper around `ExampleComponentInput` that implements `PromiseCallWitness`, for making calls to the `ExampleComponent` from other circuits. + +The `component!` macro requires adhering to the naming convention specified above. The easiest way to start writing your own `BasicComponentScaffold` is by forking [./src/example/mod.rs](./src/example/mod.rs), replacing `Example` with your own component name, changing the input/output structs for your use case, and then adding your circuit logic to the `BasicComponentScaffold` trait impl. + +The scaffold implements the `CoreBuilder` and `ComponentBuilder` traits on `BasicComponentScaffoldImpl>`. To create the component circuit, simply construct `ComponentCircuitImpl>, P: EmptyPromiseLoader>` using its [constructor](https://github.com/axiom-crypto/axiom-eth-working/blob/6c88fa354eabf5c26a87255f693127553893639f/axiom-eth/src/utils/component/circuit/comp_circuit_impl.rs#L57): + +``` +pub fn new( + core_builder_params: C::Params, + promise_builder_params: P::Params, + prompt_rlc_params: RlcCircuitParams, +) -> Self +``` + +### Testing + +To test the IO proc macro flattening, use `fix_len_logical_input_test`. To test that the output of your component is what you expect for some given input, use `basic_component_outputs_test`. To test that circuit keygen/proving work for your circuit, use `basic_component_test_prove`. All of these testing functions can be found in [./src/utils/testing.rs](./src/utils/testing.rs) and example usage is in [./src/example/test.rs](./src/example/test.rs). + +## Notes + +Each component lives in its own folder. diff --git a/axiom-components/component-derive/Cargo.toml b/axiom-components/component-derive/Cargo.toml new file mode 100644 index 00000000..750e0890 --- /dev/null +++ b/axiom-components/component-derive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "component-derive" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +syn = "2.0.43" +quote = "1.0.33" +proc-macro2 = "1.0.72" + +[lib] +proc-macro = true diff --git a/axiom-components/component-derive/src/dummy.rs b/axiom-components/component-derive/src/dummy.rs new file mode 100644 index 00000000..a19cd363 --- /dev/null +++ b/axiom-components/component-derive/src/dummy.rs @@ -0,0 +1,36 @@ +// use proc_macro::{Ident, Span}; +use proc_macro2::{Ident, Span, TokenStream}; +use syn::DeriveInput; + +pub fn impl_dummy_derive(ast: &DeriveInput) -> TokenStream { + let name = &ast.ident; + + let (impl_generics, ty_generics, _) = ast.generics.split_for_impl(); + + let component_name = name.to_string().trim_end_matches("Input").to_string(); + + let component_name_ident = Ident::new(component_name.as_str(), Span::call_site()); + + let component_params_ident = Ident::new( + format!("{}Params", component_name).as_str(), + Span::call_site(), + ); + + quote! { + impl #impl_generics axiom_eth::utils::build_utils::dummy::DummyFrom<<#component_name_ident #ty_generics as BasicComponentScaffold>::Params> for #name #ty_generics where T: axiom_eth::Field { + fn dummy_from(_: <#component_name_ident #ty_generics as BasicComponentScaffold>::Params) -> Self { + let num_fe = >::NUM_FE; + let vec = vec![T::ZERO; num_fe]; + >::unflatten(vec).unwrap() + } + } + + //todo: actually implement dummy from for the component params + impl #impl_generics axiom_eth::utils::build_utils::dummy::DummyFrom<#component_params_ident> for Vec<#name #ty_generics> where T: axiom_eth::Field { + fn dummy_from(params: #component_params_ident) -> Self { + let single = <#name #ty_generics as axiom_eth::utils::build_utils::dummy::DummyFrom<<#component_name_ident #ty_generics as BasicComponentScaffold>::Params>>::dummy_from(params.clone()); + vec![single; params.capacity] + } + } + } +} diff --git a/axiom-components/component-derive/src/flatten.rs b/axiom-components/component-derive/src/flatten.rs new file mode 100644 index 00000000..f02d4448 --- /dev/null +++ b/axiom-components/component-derive/src/flatten.rs @@ -0,0 +1,141 @@ +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{Data, DeriveInput}; + +pub fn impl_flatten_derive(ast: &DeriveInput) -> TokenStream { + let name = &ast.ident; + + let fields = match ast.data { + Data::Struct(ref data_struct) => &data_struct.fields, + _ => { + return quote! { + compile_error!("CoreBuilderParams macro only supports structs"); + } + } + }; + + let field_types: Vec<_> = fields + .iter() + .map(|field| field.ty.to_token_stream()) + .collect(); + + let field_names: Vec<_> = fields + .iter() + .map(|field| { + if let Some(ref field) = field.ident { + return field.to_token_stream(); + } + quote! { + compile_error!("InputFlatten macro only supports named fields"); + } + }) + .collect(); + + let (impl_generics, ty_generics, _) = ast.generics.split_for_impl(); + + let num_fe_tokens = field_types.iter().map(|ident| { + quote! { + <#ident as crate::utils::flatten::InputFlatten>::NUM_FE + } + }); + let num_fe_tokens_clone = num_fe_tokens.clone(); + + let flatten_tokens = field_names.iter().map(|ident| { + quote! { + self.#ident.flatten_vec(), + } + }); + + let create_struct_tokens: Vec<_> = field_names + .iter() + .zip(field_types.iter()) + .enumerate() + .map(|(index, (name, field_type))| { + quote! { + #name: <#field_type as crate::utils::flatten::InputFlatten>::unflatten(segmented_fe[#index].clone())?, + } + }) + .collect(); + + quote! { + impl #impl_generics crate::utils::flatten::InputFlatten for #name #ty_generics { + const NUM_FE: usize = #(#num_fe_tokens + )* 0; + fn flatten_vec(&self) -> Vec { + let flattened = vec![#(#flatten_tokens)*]; + flattened.into_iter().flatten().collect() + } + + fn unflatten(vec: Vec) -> anyhow::Result { + if vec.len() != >::NUM_FE { + anyhow::bail!( + "Invalid input length: {} != {}", + vec.len(), + >::NUM_FE + ); + } + + let mut fe = vec.clone(); + let num_fe_per_field = vec![#(#num_fe_tokens_clone),*]; + + let mut segmented_fe = Vec::>::new(); + for num_fe in num_fe_per_field.iter() { + let new_vec = fe.drain(0..*num_fe).collect(); + segmented_fe.push(new_vec); + } + + Ok(#name { + #(#create_struct_tokens)* + }) + } + } + impl #impl_generics #name #ty_generics { + pub fn flatten(self) -> Vec { + crate::utils::flatten::InputFlatten::::flatten_vec(&self) + } + } + + impl #impl_generics TryFrom> for #name #ty_generics { + type Error = anyhow::Error; + + fn try_from(value: Vec) -> anyhow::Result { + >::unflatten(value) + } + } + + impl #impl_generics TryFrom> for #name #ty_generics { + type Error = anyhow::Error; + + fn try_from( + value: crate::framework::types::Flatten, + ) -> anyhow::Result { + if value.field_size.to_vec() != vec![999; >::NUM_FE] { + anyhow::bail!("invalid field size"); + } + if value.field_size.len() != value.fields.len() { + anyhow::bail!("field length doesn't match"); + } + let res = value.fields.try_into()?; + Ok(res) + } + } + + impl #impl_generics From<#name #ty_generics> for crate::framework::types::Flatten { + fn from(value: #name #ty_generics) -> Self { + let vec = value.flatten().to_vec(); + let field_size_box = vec![999; vec.len()]; + crate::framework::types::Flatten:: { + fields: vec, + field_size: crate::utils::flatten::into_static_slice(field_size_box), + } + } + } + + impl #impl_generics crate::framework::types::FixLenLogical for #name #ty_generics { + fn get_field_size() -> &'static [usize] { + let num_fe = >::NUM_FE; + let vec = vec![999; num_fe]; + crate::utils::flatten::into_static_slice(vec) + } + } + } +} diff --git a/axiom-components/component-derive/src/lib.rs b/axiom-components/component-derive/src/lib.rs new file mode 100644 index 00000000..712e0eab --- /dev/null +++ b/axiom-components/component-derive/src/lib.rs @@ -0,0 +1,50 @@ +extern crate proc_macro; +extern crate syn; +#[macro_use] +extern crate quote; + +use dummy::impl_dummy_derive; +use flatten::impl_flatten_derive; +use new_component::{impl_component, impl_component_generic, ComponentImplInput}; +use params::impl_params_derive; +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; +mod dummy; +mod flatten; +mod new_component; +mod params; + +#[proc_macro_derive(ComponentParams)] +pub fn core_builder_params_derive(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let gen = impl_params_derive(&ast); + gen.into() +} + +#[proc_macro_derive(ComponentIO)] +pub fn core_flatten_derive(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let gen = impl_flatten_derive(&ast); + gen.into() +} + +#[proc_macro] +pub fn component(input: TokenStream) -> TokenStream { + let parsed = parse_macro_input!(input as ComponentImplInput); + let gen = impl_component(parsed); + gen.into() +} + +#[proc_macro_derive(Component)] +pub fn core_component_derive(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let gen = impl_component_generic(&ast); + gen.into() +} + +#[proc_macro_derive(Dummy)] +pub fn dummy_derive(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let gen = impl_dummy_derive(&ast); + gen.into() +} diff --git a/axiom-components/component-derive/src/new_component.rs b/axiom-components/component-derive/src/new_component.rs new file mode 100644 index 00000000..6a20a0c0 --- /dev/null +++ b/axiom-components/component-derive/src/new_component.rs @@ -0,0 +1,142 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::ToTokens; +use syn::{parse::Parse, DeriveInput}; + +pub struct ComponentImplInput { + a: Ident, +} + +impl Parse for ComponentImplInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + Ok(Self { a: input.parse()? }) + } +} + +pub fn impl_component(input: ComponentImplInput) -> TokenStream { + let name = &input.a; + + let struct_type = Ident::new(format!("{}Component", name).as_str(), Span::call_site()); + + quote! { + #[derive(component_derive::Component)] + pub struct #struct_type(std::marker::PhantomData); + } +} + +pub fn impl_component_generic(ast: &DeriveInput) -> TokenStream { + let name = &ast.ident; + + let ends_with_component = name.to_string().ends_with("Component"); + let name_prefix = name.to_string().trim_end_matches("Component").to_string(); + + if !ends_with_component { + return quote! { + compile_error!("Struct must be named `Component`. Ex: `ExampleComponent`"); + }; + } + + let (impl_generics, ty_generics, _) = ast.generics.split_for_impl(); + + let generics_with_t = { + let token_stream = ty_generics.to_token_stream(); + let token_stream_string = token_stream.to_string(); + let symbols = token_stream_string + .split_ascii_whitespace() + .collect::>(); + let mut symbols = symbols + .iter() + .map(|s| s.trim_end_matches(',').to_string()) + .collect::>(); + symbols.remove(0); //remove "<" + symbols.remove(0); // remove "F" + symbols.pop(); // remove ">" + symbols + .iter() + .map(|s| Ident::new(s.as_str(), Span::call_site())) + .collect::>() + }; + + let component_type = Ident::new( + format!("ComponentType{}", name_prefix).as_str(), + Span::call_site(), + ); + + let input_type = Ident::new( + format!("{}ComponentInput", name_prefix).as_str(), + Span::call_site(), + ); + + let output_type = Ident::new( + format!("{}ComponentOutput", name_prefix).as_str(), + Span::call_site(), + ); + + let component_call_type = Ident::new( + format!("{}ComponentCall", name_prefix).as_str(), + Span::call_site(), + ); + + quote! { + #[allow(type_alias_bounds)] + pub type #component_type #impl_generics = + crate::scaffold::BasicComponentScaffoldImpl; + + impl #impl_generics crate::scaffold::BasicComponentScaffoldIOTypes for #name #ty_generics { + type InputType = #input_type ; + type OutputType = #output_type ; + } + + impl #impl_generics crate::framework::LogicalInputValue + for #input_type #ty_generics + { + fn get_capacity(&self) -> usize { + 1 + } + } + + #[derive(Clone, Debug)] + pub struct #component_call_type #impl_generics( + pub #input_type #(, #generics_with_t)*>, + ); + impl #impl_generics crate::framework::PromiseCallWitness + for #component_call_type #ty_generics + { + fn get_component_type_id(&self) -> crate::framework::ComponentTypeId { + <#component_type :: #ty_generics as crate::framework::ComponentType> ::get_type_id() + } + fn get_capacity(&self) -> usize { + 1 + } + fn to_rlc( + &self, + (_, rlc_ctx): ( + &mut axiom_eth::halo2_base::Context, + &mut axiom_eth::halo2_base::Context, + ), + _range_chip: &axiom_eth::halo2_base::gates::RangeChip, + rlc_chip: &axiom_eth::rlc::chip::RlcChip, + ) -> axiom_eth::halo2_base::AssignedValue { + crate::framework::promise_loader::flatten_witness_to_rlc( + rlc_ctx, + &rlc_chip, + &self.0.clone().into(), + ) + } + fn to_typeless_logical_input(&self) -> crate::framework::TypelessLogicalInput { + let f_a: crate::framework::types::Flatten> = self.0.clone().into(); + let f_v: crate::framework::types::Flatten = f_a.into(); + let l_v: <#component_type #ty_generics as crate::framework::ComponentType>::LogicalInput = + f_v.try_into().unwrap(); + crate::framework::utils::into_key(l_v) + } + fn get_mock_output(&self) -> crate::framework::types::Flatten { + let output_val: <#component_type #ty_generics as crate::framework::ComponentType>::OutputValue = + Default::default(); + output_val.into() + } + fn as_any(&self) -> &dyn std::any::Any { + self + } + } + } +} diff --git a/axiom-components/component-derive/src/params.rs b/axiom-components/component-derive/src/params.rs new file mode 100644 index 00000000..ad48a259 --- /dev/null +++ b/axiom-components/component-derive/src/params.rs @@ -0,0 +1,30 @@ +use proc_macro2::TokenStream; +use syn::{Data, DeriveInput}; + +pub fn impl_params_derive(ast: &DeriveInput) -> TokenStream { + let name = &ast.ident; + + match ast.data { + Data::Struct(ref data_struct) => { + for field in data_struct.fields.iter() { + if let Some(ref ident) = field.ident { + if ident == "capacity" { + return quote! { + impl crate::framework::circuit::CoreBuilderParams for #name { + fn get_output_params(&self) -> crate::framework::circuit::CoreBuilderOutputParams { + crate::framework::circuit::CoreBuilderOutputParams::new(vec![self.capacity]) + } + } + }; + } + } + } + quote! { + compile_error!("Struct does not have a 'capacity' field"); + } + } + _ => quote! { + compile_error!("CoreBuilderParams macro only supports structs"); + }, + } +} diff --git a/axiom-components/rust-toolchain b/axiom-components/rust-toolchain new file mode 100644 index 00000000..6f6d7b39 --- /dev/null +++ b/axiom-components/rust-toolchain @@ -0,0 +1 @@ +nightly-2023-08-12 diff --git a/axiom-components/rustfmt.toml b/axiom-components/rustfmt.toml new file mode 100644 index 00000000..f6b633a4 --- /dev/null +++ b/axiom-components/rustfmt.toml @@ -0,0 +1,2 @@ +imports_granularity = "Crate" +group_imports = "StdExternalCrate" \ No newline at end of file diff --git a/axiom-components/src/ecdsa/mod.rs b/axiom-components/src/ecdsa/mod.rs new file mode 100644 index 00000000..80b58ce0 --- /dev/null +++ b/axiom-components/src/ecdsa/mod.rs @@ -0,0 +1,200 @@ +use axiom_eth::{ + halo2_base::{ + gates::{flex_gate::threads::parallelize_core, RangeChip}, + AssignedValue, Context, + }, + halo2curves::secp256k1::{Fp, Fq, Secp256k1Affine}, + rlc::circuit::builder::RlcCircuitBuilder, + utils::{encode_h256_to_hilo, hilo::HiLo}, + zkevm_hashes::util::eth_types::H256, + Field, +}; +use component_derive::{component, ComponentIO, ComponentParams, Dummy}; +use halo2_ecc::halo2_base::utils::{biguint_to_fe, fe_to_biguint, BigPrimeField}; +use num_bigint::BigUint; +use num_traits::Num; +use serde::{Deserialize, Serialize}; + +use self::utils::{ + biguint_to_hilo, decode_hilo_to_biguint, decode_hilo_to_h256, load_fp_from_hilo, + load_secp256k1_pubkey, +}; +use crate::{ + halo2_ecc::{ + ecc::{ecdsa::ecdsa_verify_no_pubkey_check, EccChip}, + fields::fp::FpChip, + secp256k1::FqChip, + }, + scaffold::{BasicComponentScaffold, BasicComponentScaffoldIO}, +}; +#[cfg(test)] +mod test; +pub mod utils; + +/// Config params for the ECDSA component. +#[derive(Default, Clone, Serialize, Deserialize, ComponentParams)] +pub struct ECDSAComponentParams { + pub capacity: usize, + pub limb_bits: usize, + pub num_limbs: usize, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(from = "ECDSAComponentSerdeInput")] +#[serde(into = "ECDSAComponentSerdeInput")] +pub struct ECDSAComponentNativeInput { + /// The public key is a Secp256k1Affine point represented as (x, y) + pub pubkey: (Fp, Fp), + pub r: Fq, + pub s: Fq, + pub msg_hash: H256, +} + +impl From for ECDSAComponentInput { + fn from(input: ECDSAComponentNativeInput) -> Self { + ECDSAComponentInput { + pubkey: ( + biguint_to_hilo(fe_to_biguint(&input.pubkey.0)), + biguint_to_hilo(fe_to_biguint(&input.pubkey.1)), + ), + r: biguint_to_hilo(fe_to_biguint(&input.r)), + s: biguint_to_hilo(fe_to_biguint(&input.s)), + msg_hash: encode_h256_to_hilo(&input.msg_hash), + } + } +} + +impl From> for ECDSAComponentNativeInput { + fn from(input: ECDSAComponentInput) -> Self { + ECDSAComponentNativeInput { + pubkey: ( + biguint_to_fe::(&decode_hilo_to_biguint(input.pubkey.0)), + biguint_to_fe::(&decode_hilo_to_biguint(input.pubkey.1)), + ), + r: biguint_to_fe::(&decode_hilo_to_biguint(input.r)), + s: biguint_to_fe::(&decode_hilo_to_biguint(input.s)), + msg_hash: decode_hilo_to_h256(input.msg_hash), + } + } +} + +/// A single input of the ECDSA component. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ComponentIO, Dummy)] +pub struct ECDSAComponentInput { + pub pubkey: (HiLo, HiLo), + pub r: HiLo, + pub s: HiLo, + pub msg_hash: HiLo, +} + +/// A single output of the ECDSA component. +/// The `success` field is 1 if the signature is valid, and 0 otherwise. +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ComponentIO)] +pub struct ECDSAComponentOutput { + pub success: HiLo, +} + +impl From> for H256 { + fn from(value: ECDSAComponentOutput) -> Self { + decode_hilo_to_h256(value.success) + } +} + +impl From for ECDSAComponentOutput { + fn from(value: H256) -> Self { + ECDSAComponentOutput { + success: encode_h256_to_hilo(&value), + } + } +} + +component!(ECDSA); + +impl BasicComponentScaffold for ECDSAComponent { + type Params = ECDSAComponentParams; + + fn virtual_assign_phase0( + params: ECDSAComponentParams, + builder: &mut RlcCircuitBuilder, + input: Vec>, + ) -> BasicComponentScaffoldIO { + let range = builder.base.range_chip(); + let pool = builder.base.pool(0); + let res = parallelize_core(pool, input, |ctx, subquery| { + let input = Self::assign_input(ctx, subquery); + handle_single_ecdsa_verify(ctx, &range, input, params.limb_bits, params.num_limbs) + }); + ((), res) + } +} + +/// Helper function for handling a single ECDSA verification. +pub fn handle_single_ecdsa_verify( + ctx: &mut Context, + range: &RangeChip, + input: ECDSAComponentInput>, + limb_bits: usize, + num_limbs: usize, +) -> ( + ECDSAComponentInput>, + ECDSAComponentOutput>, +) { + let fp_chip = FpChip::::new(range, limb_bits, num_limbs); + let fq_chip = FqChip::new(range, limb_bits, num_limbs); + let [r, s, msg_hash] = + [input.r, input.s, input.msg_hash].map(|x| load_fp_from_hilo(ctx, range, &fq_chip, x)); + let ecc_chip = EccChip::new(&fp_chip); + let pubkey = load_secp256k1_pubkey(ctx, range, &fp_chip, input.pubkey); + let success = ecdsa_verify_no_pubkey_check::( + &ecc_chip, ctx, pubkey, r, s, msg_hash, 4, 4, + ); + let zero = ctx.load_constant(F::ZERO); + ( + input, + ECDSAComponentOutput { + success: HiLo::from_hi_lo([zero, success]), + }, + ) +} + +/// Field elements are serialized as big-endian hex strings with 0x prefix. +#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub struct ECDSAComponentSerdeInput { + /// The public key is a Secp256k1Affine point represented as (x, y) + pub pubkey: (String, String), + pub r: String, + pub s: String, + pub msg_hash: H256, +} + +impl From for ECDSAComponentSerdeInput { + fn from(input: ECDSAComponentNativeInput) -> Self { + ECDSAComponentSerdeInput { + pubkey: ( + format!("{:?}", input.pubkey.0), + format!("{:?}", input.pubkey.1), + ), + r: format!("{:?}", input.r), + s: format!("{:?}", input.s), + msg_hash: input.msg_hash, + } + } +} + +impl From for ECDSAComponentNativeInput { + fn from(input: ECDSAComponentSerdeInput) -> Self { + ECDSAComponentNativeInput { + pubkey: (string_to_fe(&input.pubkey.0), string_to_fe(&input.pubkey.1)), + r: string_to_fe(&input.r), + s: string_to_fe(&input.s), + msg_hash: input.msg_hash, + } + } +} + +fn string_to_fe(s: &str) -> F { + assert!(s.starts_with("0x"), "Hex string must start with 0x"); + let num = BigUint::from_str_radix(&s[2..], 16).unwrap(); + biguint_to_fe(&num) +} diff --git a/axiom-components/src/ecdsa/test.rs b/axiom-components/src/ecdsa/test.rs new file mode 100644 index 00000000..024201cd --- /dev/null +++ b/axiom-components/src/ecdsa/test.rs @@ -0,0 +1,98 @@ +use axiom_eth::{halo2curves::bn256::Fr, utils::hilo::HiLo}; +use lazy_static::lazy_static; + +use super::{ + utils::{testing::custom_parameters_ecdsa, verify_signature}, + ECDSAComponent, ECDSAComponentInput, ECDSAComponentNativeInput, ECDSAComponentOutput, + ECDSAComponentParams, +}; +use crate::utils::testing::{ + basic_component_outputs_test, basic_component_test_prove, fix_len_logical_input_test, +}; + +#[test] +fn test_ecdsa_input_flatten() { + fix_len_logical_input_test( + ECDSAComponentInput { + pubkey: (HiLo::from_hi_lo([1, 2]), HiLo::from_hi_lo([3, 4])), + r: HiLo::from_hi_lo([5, 6]), + s: HiLo::from_hi_lo([7, 8]), + msg_hash: HiLo::from_hi_lo([9, 10]), + }, + vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + ); +} + +fn get_ecdsa_output(success: bool) -> ECDSAComponentOutput { + ECDSAComponentOutput { + success: if success { + HiLo::from_hi_lo([Fr::zero(), Fr::one()]) + } else { + HiLo::from_hi_lo([Fr::zero(), Fr::zero()]) + }, + } +} + +lazy_static! { + static ref ECDSA_PARAMS: ECDSAComponentParams = ECDSAComponentParams { + capacity: 2, + limb_bits: 88, + num_limbs: 3, + }; +} + +#[test] +fn test_ecdsa_output() { + basic_component_outputs_test::>( + 15, + vec![ + custom_parameters_ecdsa(1, 1, 1), + custom_parameters_ecdsa(2, 2, 2), + ], + vec![get_ecdsa_output(true), get_ecdsa_output(true)], + ECDSA_PARAMS.clone(), + ); +} + +#[test] +fn test_native_ecdsa_verification() { + let input = custom_parameters_ecdsa(1, 1, 1); + let native_input = ECDSAComponentNativeInput::from(input.clone()); + assert!(verify_signature(native_input).unwrap()); +} + +#[test] +fn test_native_ecdsa_verification_fail() { + let mut input = custom_parameters_ecdsa(1, 1, 1); + let second = custom_parameters_ecdsa(2, 2, 2); + input.s = second.s; + input.r = second.r; + let native_input = ECDSAComponentNativeInput::from(input.clone()); + assert!(!verify_signature(native_input).unwrap()); +} + +#[test] +fn test_ecdsa_output_with_wrong_signature() { + let mut input = custom_parameters_ecdsa(1, 1, 1); + //change the signature so the verification should fail + input.s = HiLo::from_hi_lo([Fr::from(2), Fr::from(2)]); + basic_component_outputs_test::>( + 15, + vec![custom_parameters_ecdsa(1, 1, 1), input], + vec![get_ecdsa_output(true), get_ecdsa_output(false)], + ECDSA_PARAMS.clone(), + ); +} + +#[test] +fn test_ecdsa_prove() { + basic_component_test_prove::>( + 15, + vec![ + custom_parameters_ecdsa(1, 1, 1), + custom_parameters_ecdsa(2, 2, 2), + ], + ECDSA_PARAMS.clone(), + ) + .unwrap(); +} diff --git a/axiom-components/src/ecdsa/utils.rs b/axiom-components/src/ecdsa/utils.rs new file mode 100644 index 00000000..f622ff54 --- /dev/null +++ b/axiom-components/src/ecdsa/utils.rs @@ -0,0 +1,178 @@ +use std::str::FromStr; + +use anyhow::Result; +use axiom_eth::{ + halo2_base::{ + gates::{GateInstructions, RangeChip, RangeInstructions}, + utils::{biguint_to_fe, fe_to_biguint, modulus, BigPrimeField}, + AssignedValue, Context, QuantumCell, + }, + halo2curves::secp256k1::{Fp, Secp256k1Affine}, + utils::hilo::HiLo, + Field, +}; +use ethers_core::types::{BigEndianHash, H256}; +use itertools::Itertools; +use num_bigint::BigUint; +use num_traits::identities::One; +use secp256k1::{ecdsa::Signature, Message, PublicKey, Secp256k1}; + +use super::ECDSAComponentNativeInput; +use crate::halo2_ecc::{ + bigint::ProperCrtUint, + ecc::{EcPoint, EccChip}, + fields::{fp::FpChip, FieldChip}, +}; + +pub fn load_fp_from_hilo( + ctx: &mut Context, + range: &RangeChip, + fp_chip: &FpChip, + hilo: HiLo>, +) -> ProperCrtUint { + let [hi_val, lo_val] = hilo.hi_lo().map(|x| fe_to_biguint(x.value())); + let fp = (hi_val << 128) + lo_val; + assert!(fp < modulus::()); + let fp = biguint_to_fe::(&fp); + let fp = fp_chip.load_private(ctx, fp); + constrain_limbs_equality( + ctx, + range, + [hilo.hi(), hilo.lo()], + fp.limbs(), + fp_chip.limb_bits(), + ); + fp +} + +pub fn load_secp256k1_pubkey( + ctx: &mut Context, + range: &RangeChip, + fp_chip: &FpChip, + pubkey: (HiLo>, HiLo>), +) -> EcPoint> { + let [x, y] = [pubkey.0, pubkey.1].map(|c| load_fp_from_hilo(ctx, range, fp_chip, c)); + let pt = EcPoint::new(x, y); + let chip = EccChip::new(fp_chip); + //ensures the pubkey is valid since it does not allow (0,0) + chip.assert_is_on_curve::(ctx, &pt); + pt +} + +//should generalize this and move to halo2-lib +pub fn constrain_limbs_equality( + ctx: &mut Context, + range: &RangeChip, + [hi, lo]: [AssignedValue; 2], + limbs: &[AssignedValue], + limb_bits: usize, +) { + assert!(limb_bits <= 128); + assert!(limb_bits > 64); + // limb_bits, 128 - limb_bits + let (tmp0, limb0) = range.div_mod(ctx, lo, BigUint::one() << limb_bits, 128); + // limb_bits - (128 - limb_bits) = 2 * limb_bits - 128 > 0 + let rem_bits = limb_bits - (128 - limb_bits); + let (limb2, tmp1) = range.div_mod(ctx, hi, BigUint::one() << rem_bits, 128); + let multiplier = biguint_to_fe(&(BigUint::one() << (128 - limb_bits))); + let limb1 = range + .gate + .mul_add(ctx, tmp1, QuantumCell::Constant(multiplier), tmp0); + for (l0, l1) in limbs.iter().zip_eq([limb0, limb1, limb2]) { + ctx.constrain_equal(l0, &l1); + } +} + +pub fn biguint_to_hilo(x: BigUint) -> HiLo { + assert!(x.bits() <= 256); + let hi = x.clone() >> 128; + let lo = x % (BigUint::one() << 128); + HiLo::from_hi_lo([biguint_to_fe(&hi), biguint_to_fe(&lo)]) +} + +pub fn biguint_to_h256(value: BigUint) -> H256 { + let bytes = value.to_bytes_be(); + assert!(bytes.len() <= 32); + let mut padded = vec![0u8; 32 - bytes.len()]; + padded.extend_from_slice(&bytes); + H256::from_slice(&padded) +} + +pub fn decode_hilo_to_h256(fe: HiLo) -> H256 { + let mut bytes = [0u8; 32]; + bytes[..16].copy_from_slice(&fe.lo().to_bytes_le()[..16]); + bytes[16..].copy_from_slice(&fe.hi().to_bytes_le()[..16]); + bytes.reverse(); + H256(bytes) +} + +pub fn decode_hilo_to_biguint(fe: HiLo) -> BigUint { + let u256 = decode_hilo_to_h256(fe).into_uint(); + let mut bytes = [0u8; 32]; + u256.to_big_endian(&mut bytes); + BigUint::from_bytes_be(&bytes) +} + +pub fn verify_signature(input: ECDSAComponentNativeInput) -> Result { + let pubkey_x = fe_to_biguint(&input.pubkey.0); + let pubkey_y = fe_to_biguint(&input.pubkey.1); + let r = fe_to_biguint(&input.r); + let s = fe_to_biguint(&input.s); + + let secp = Secp256k1::verification_only(); + + let pubkey_serialized = format!("04{:x}{:x}", pubkey_x, pubkey_y); + let pk = PublicKey::from_str(&pubkey_serialized).unwrap(); + let msg = Message::from_digest_slice(input.msg_hash.as_bytes()).unwrap(); + + let r_bytes = r.to_bytes_be(); + let s_bytes = s.to_bytes_be(); + let sig_bytes = [&r_bytes[..], &s_bytes[..]].concat(); + let sig = Signature::from_compact(&sig_bytes).unwrap(); + let res = secp.verify_ecdsa(&msg, &sig, &pk); + + Ok(res.is_ok()) +} + +pub mod testing { + use axiom_eth::{ + halo2_base::utils::{biguint_to_fe, fe_to_biguint, modulus}, + halo2curves::{ + bn256::Fr, + secp256k1::{Fq, Secp256k1Affine}, + CurveAffine, + }, + }; + + use super::biguint_to_h256; + use crate::ecdsa::{ECDSAComponentInput, ECDSAComponentNativeInput}; + + // Based on https://github.com/axiom-crypto/halo2-lib/blob/8cdbf542a70455042ff7c8cdbedb552ca174a00d/halo2-ecc/src/secp256k1/tests/ecdsa_tests.rs#L12 + pub fn custom_parameters_ecdsa(sk: u64, msg_hash: u64, k: u64) -> ECDSAComponentInput { + let sk_fe = ::ScalarExt::from(sk); + let pubkey = Secp256k1Affine::from(Secp256k1Affine::generator() * sk_fe); + let msg_hash_fe = ::ScalarExt::from(msg_hash); + let msg_hash = fe_to_biguint(&msg_hash_fe); + + let k = ::ScalarExt::from(k); + let k_inv = k.invert().unwrap(); + + let r_point = Secp256k1Affine::from(Secp256k1Affine::generator() * k) + .coordinates() + .unwrap(); + let x = r_point.x(); + let x_bigint = fe_to_biguint(x); + + let r = x_bigint % modulus::(); + let r_fe = biguint_to_fe::(&r); + let s_fe = k_inv * (msg_hash_fe + (r_fe * sk_fe)); + + let ecdsa_native_input = ECDSAComponentNativeInput { + pubkey: (pubkey.x, pubkey.y), + r: r_fe, + s: s_fe, + msg_hash: biguint_to_h256(msg_hash), + }; + ecdsa_native_input.into() + } +} diff --git a/axiom-components/src/example/mod.rs b/axiom-components/src/example/mod.rs new file mode 100644 index 00000000..57ced77f --- /dev/null +++ b/axiom-components/src/example/mod.rs @@ -0,0 +1,48 @@ +use axiom_eth::{ + halo2_base::gates::{flex_gate::threads::parallelize_core, GateChip, GateInstructions}, + rlc::circuit::builder::RlcCircuitBuilder, + Field, +}; +use component_derive::{component, ComponentIO, ComponentParams, Dummy}; +use serde::{Deserialize, Serialize}; + +use crate::scaffold::{BasicComponentScaffold, BasicComponentScaffoldIO}; +#[cfg(test)] +mod test; + +#[derive(Default, Clone, ComponentParams)] +pub struct ExampleComponentParams { + pub capacity: usize, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ComponentIO, Dummy)] +pub struct ExampleComponentInput { + pub a: T, + pub b: T, +} + +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ComponentIO)] +pub struct ExampleComponentOutput { + pub sum: T, +} + +component!(Example); + +impl BasicComponentScaffold for ExampleComponent { + type Params = ExampleComponentParams; + + fn virtual_assign_phase0( + _: ExampleComponentParams, + builder: &mut RlcCircuitBuilder, + input: Vec>, + ) -> BasicComponentScaffoldIO { + let pool = builder.base.pool(0); + let gate = GateChip::::default(); + let res = parallelize_core(pool, input, |ctx, subquery| { + let input = Self::assign_input(ctx, subquery); + let sum = gate.add(ctx, input.a, input.b); + (input, ExampleComponentOutput { sum }) + }); + ((), res) + } +} diff --git a/axiom-components/src/example/test.rs b/axiom-components/src/example/test.rs new file mode 100644 index 00000000..4b351794 --- /dev/null +++ b/axiom-components/src/example/test.rs @@ -0,0 +1,79 @@ +use axiom_eth::{halo2curves::bn256::Fr, rlc::circuit::RlcCircuitParams}; +use halo2_ecc::halo2_base::gates::circuit::BaseCircuitParams; +use lazy_static::lazy_static; + +use super::{ExampleComponent, ExampleComponentInput, ExampleComponentOutput}; +use crate::{ + example::ExampleComponentParams, + utils::testing::{ + basic_component_outputs_test, basic_component_test_prove, fix_len_logical_input_test, + get_type_id, + }, +}; + +#[test] +fn test_example_input_flatten() { + fix_len_logical_input_test(ExampleComponentInput { a: 1, b: 2 }, vec![1, 2]); +} + +lazy_static! { + static ref EXAMPLE_INPUT: Vec> = vec![ + ExampleComponentInput { + a: Fr::from(1), + b: Fr::from(2), + }, + ExampleComponentInput { + a: Fr::from(3), + b: Fr::from(4), + }, + ExampleComponentInput { + a: Fr::from(5), + b: Fr::from(6), + }, + ]; + static ref EXAMPLE_OUTPUT: Vec> = vec![ + ExampleComponentOutput { sum: Fr::from(3) }, + ExampleComponentOutput { sum: Fr::from(7) }, + ExampleComponentOutput { sum: Fr::from(11) } + ]; + static ref EXAMPLE_PARAMS: ExampleComponentParams = ExampleComponentParams { capacity: 3 }; + static ref EXAMPLE_RLC_CIRCUIT_PARAMS: RlcCircuitParams = RlcCircuitParams { + base: BaseCircuitParams { + k: 15, + num_advice_per_phase: vec![20, 0], + num_fixed: 1, + num_lookup_advice_per_phase: vec![3, 0], + lookup_bits: Some(14), + num_instance_columns: 1, + }, + num_rlc_columns: 0, + }; +} + +#[test] +fn test_component_outputs() { + basic_component_outputs_test::>( + 15, + EXAMPLE_INPUT.clone(), + EXAMPLE_OUTPUT.clone(), + EXAMPLE_PARAMS.clone(), + ) +} + +#[test] +fn test_prove() { + basic_component_test_prove::>( + 15, + EXAMPLE_INPUT.clone(), + EXAMPLE_PARAMS.clone(), + ) + .unwrap() +} + +#[test] +fn test_component_id() { + assert_eq!( + get_type_id::>(), + "axiom_components::example::ExampleComponent".to_string() + ); +} diff --git a/axiom-components/src/example_generics/mod.rs b/axiom-components/src/example_generics/mod.rs new file mode 100644 index 00000000..9e717f8b --- /dev/null +++ b/axiom-components/src/example_generics/mod.rs @@ -0,0 +1,57 @@ +use axiom_eth::{ + halo2_base::gates::{flex_gate::threads::parallelize_core, GateChip, GateInstructions}, + rlc::circuit::builder::RlcCircuitBuilder, + Field, +}; +use component_derive::{Component, ComponentIO, ComponentParams, Dummy}; +use serde::{Deserialize, Serialize}; + +use crate::{ + scaffold::{BasicComponentScaffold, BasicComponentScaffoldIO}, + utils::flatten::FixLenVec, +}; +#[cfg(test)] +mod test; + +#[derive(Component)] +pub struct GenericComponent(std::marker::PhantomData); + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ComponentIO, Dummy)] +pub struct GenericComponentInput { + pub a: FixLenVec, + pub b: FixLenVec, +} + +#[derive(Default, Clone, ComponentParams)] +pub struct GenericComponentParams { + pub capacity: usize, +} + +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ComponentIO)] +pub struct GenericComponentOutput { + pub sum: T, +} + +impl BasicComponentScaffold + for GenericComponent +{ + type Params = GenericComponentParams; + fn virtual_assign_phase0( + _: GenericComponentParams, + builder: &mut RlcCircuitBuilder, + input: Vec>, + ) -> BasicComponentScaffoldIO { + let pool = builder.base.pool(0); + let gate = GateChip::::default(); + let res = parallelize_core(pool, input, |ctx, subquery| { + let input = Self::assign_input(ctx, subquery); + let a = input.clone().a.into_inner(); + let b = input.clone().b.into_inner(); + let sum_a = gate.sum(ctx, a); + let sum_b = gate.sum(ctx, b); + let sum = gate.add(ctx, sum_a, sum_b); + (input, GenericComponentOutput { sum }) + }); + ((), res) + } +} diff --git a/axiom-components/src/example_generics/test.rs b/axiom-components/src/example_generics/test.rs new file mode 100644 index 00000000..9ba8b9c3 --- /dev/null +++ b/axiom-components/src/example_generics/test.rs @@ -0,0 +1,71 @@ +use axiom_eth::{halo2curves::bn256::Fr, rlc::circuit::RlcCircuitParams}; +use halo2_ecc::halo2_base::gates::circuit::BaseCircuitParams; +use lazy_static::lazy_static; + +use super::{ + GenericComponent, GenericComponentInput, GenericComponentOutput, GenericComponentParams, +}; +use crate::utils::{ + flatten::FixLenVec, + testing::{ + basic_component_outputs_test, basic_component_test_prove, fix_len_logical_input_test, + }, +}; + +#[test] +fn test_generic_input_flatten() { + let input: GenericComponentInput = GenericComponentInput { + a: FixLenVec::new(vec![1, 2]).unwrap(), + b: FixLenVec::new(vec![3, 4, 5]).unwrap(), + }; + fix_len_logical_input_test(input, vec![1, 2, 3, 4, 5]); +} + +lazy_static! { + static ref GENERIC_INPUT: Vec> = vec![ + GenericComponentInput { + a: FixLenVec::new(vec![Fr::from(1), Fr::from(2)]).unwrap(), + b: FixLenVec::new(vec![Fr::from(3), Fr::from(4), Fr::from(5)]).unwrap(), + }, + GenericComponentInput { + a: FixLenVec::new(vec![Fr::from(6), Fr::from(7)]).unwrap(), + b: FixLenVec::new(vec![Fr::from(8), Fr::from(9), Fr::from(10)]).unwrap(), + }, + ]; + static ref GENERIC_OUTPUT: Vec> = vec![ + GenericComponentOutput { sum: Fr::from(15) }, + GenericComponentOutput { sum: Fr::from(40) }, + ]; + static ref GENERIC_PARAMS: GenericComponentParams = GenericComponentParams { capacity: 2 }; + static ref GENERIC_RLC_CIRCUIT_PARAMS: RlcCircuitParams = RlcCircuitParams { + base: BaseCircuitParams { + k: 15, + num_advice_per_phase: vec![20, 0], + num_fixed: 1, + num_lookup_advice_per_phase: vec![3, 0], + lookup_bits: Some(14), + num_instance_columns: 1, + }, + num_rlc_columns: 0, + }; +} + +#[test] +fn test_component_outputs() { + basic_component_outputs_test::>( + 15, + GENERIC_INPUT.clone(), + GENERIC_OUTPUT.clone(), + GENERIC_PARAMS.clone(), + ) +} + +#[test] +fn test_prove() { + basic_component_test_prove::>( + 15, + GENERIC_INPUT.clone(), + GENERIC_PARAMS.clone(), + ) + .unwrap() +} diff --git a/axiom-components/src/groth16/mod.rs b/axiom-components/src/groth16/mod.rs new file mode 100644 index 00000000..ac81eeba --- /dev/null +++ b/axiom-components/src/groth16/mod.rs @@ -0,0 +1,196 @@ +use axiom_eth::{ + halo2_base::{ + gates::{flex_gate::threads::parallelize_core, RangeChip}, + AssignedValue, Context, + }, + rlc::circuit::builder::RlcCircuitBuilder, + utils::build_utils::dummy::DummyFrom, + Field, +}; +use component_derive::{Component, ComponentIO, ComponentParams}; +use groth_verifier::{types::*, *}; +use halo2_ecc::{ + bn254::{pairing::PairingChip, Fp2Chip, FpChip}, + ecc::EccChip, +}; +use serde::{Deserialize, Serialize}; + +use self::utils::*; +use crate::{ + scaffold::{BasicComponentScaffold, BasicComponentScaffoldIO}, + utils::flatten::{FixLenVec, VecKey}, +}; + +#[cfg(test)] +pub mod test; +pub mod utils; + +#[derive(Default, Clone, ComponentParams)] +pub struct Groth16VerifierComponentParams { + pub capacity: usize, + pub limb_bits: usize, + pub num_limbs: usize, +} + +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ComponentIO)] +pub struct Groth16VerifierComponentVerificationKey { + pub alpha_g1: HiLoPoint, + pub beta_g2: HiLoPair, + pub gamma_g2: HiLoPair, + pub delta_g2: HiLoPair, + pub gamma_abc_g1: VecKey, MAX_PUBLIC_INPUTS>, // will create vector of size MAX_PUBLIC_INPUTS + 1 +} + +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ComponentIO)] +pub struct Groth16VerifierComponentProof { + pub a: HiLoPoint, + pub b: HiLoPair, + pub c: HiLoPoint, +} + +impl Groth16VerifierComponentProof> { + pub fn convert_to_affine( + &self, + ctx: &mut Context, + range: &RangeChip, + g1_chip: &EccChip>, + ) -> ProofAssigned { + let a = hilo_point_to_affine(ctx, range, g1_chip, self.a); + let b = hilo_pair_to_affine(ctx, range, g1_chip, self.b); + let c = hilo_point_to_affine(ctx, range, g1_chip, self.c); + + ProofAssigned { a, b, c } + } +} + +impl + Groth16VerifierComponentVerificationKey, MAX_PUBLIC_INPUTS> +{ + pub fn convert_to_affine( + &self, + ctx: &mut Context, + range: &RangeChip, + g1_chip: &EccChip>, + num_public_inputs: AssignedValue, + max_len: usize, + ) -> VerifyingKeyAssigned { + let alpha_g1 = hilo_point_to_affine(ctx, range, g1_chip, self.alpha_g1); + let beta_g2 = hilo_pair_to_affine(ctx, range, g1_chip, self.beta_g2); + let gamma_g2 = hilo_pair_to_affine(ctx, range, g1_chip, self.gamma_g2); + let delta_g2 = hilo_pair_to_affine(ctx, range, g1_chip, self.delta_g2); + let mut gamma_abc_g1 = self + .gamma_abc_g1 + .iter() + .map(|pt| hilo_point_to_affine(ctx, range, g1_chip, *pt)) + .collect::>(); + gamma_abc_g1.resize(max_len, gamma_abc_g1[0].clone()); + VerifyingKeyAssigned { + alpha_g1, + beta_g2, + gamma_g2, + delta_g2, + gamma_abc_g1, + abc_len: num_public_inputs, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ComponentIO)] +pub struct Groth16VerifierComponentInput { + pub vk: Groth16VerifierComponentVerificationKey, + pub proof: Groth16VerifierComponentProof, + pub public_inputs: FixLenVec, // MAX_PUBLIC_INPUTS + pub num_public_inputs: T, +} + +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ComponentIO)] +pub struct Groth16VerifierComponentOutput { + pub success: T, +} + +#[derive(Component)] +pub struct Groth16VerifierComponent( + std::marker::PhantomData, +); + +impl BasicComponentScaffold + for Groth16VerifierComponent +{ + type Params = Groth16VerifierComponentParams; + fn virtual_assign_phase0( + params: Groth16VerifierComponentParams, + builder: &mut RlcCircuitBuilder, + input: Vec>, + ) -> BasicComponentScaffoldIO { + let range = builder.base.range_chip(); + let pool = builder.base.pool(0); + let res = parallelize_core(pool, input, |ctx, subquery| { + let input = Self::assign_input(ctx, subquery); + handle_single_groth16verify(ctx, &range, input, params.limb_bits, params.num_limbs) + }); + ((), res) + } +} + +impl DummyFrom + for Groth16VerifierComponentInput +{ + fn dummy_from(_core_params: Groth16VerifierComponentParams) -> Self { + Groth16VerifierComponentInput { + vk: Groth16VerifierComponentVerificationKey::default(), + proof: Groth16VerifierComponentProof::default(), + public_inputs: FixLenVec::new(vec![F::ZERO; MAX_PUBLIC_INPUTS]).unwrap(), + num_public_inputs: F::from(MAX_PUBLIC_INPUTS as u64), + } + } +} + +//todo: actually implement dummy from for the component params +impl DummyFrom + for Vec> +{ + fn dummy_from(_core_params: Groth16VerifierComponentParams) -> Self { + todo!() + } +} + +pub fn handle_single_groth16verify( + ctx: &mut Context, + range: &RangeChip, + input: Groth16VerifierComponentInput, MAX_PUBLIC_INPUTS>, + limb_bits: usize, + num_limbs: usize, +) -> ( + Groth16VerifierComponentInput, MAX_PUBLIC_INPUTS>, + Groth16VerifierComponentOutput, MAX_PUBLIC_INPUTS>, +) { + let fp_chip = FpChip::::new(range, limb_bits, num_limbs); + let fp2_chip = Fp2Chip::::new(&fp_chip); + let g1_chip = EccChip::new(&fp_chip); + let g2_chip = EccChip::new(&fp2_chip); + let pairing_chip = PairingChip::new(&fp_chip); + + let p = input.proof.convert_to_affine(ctx, range, &g1_chip); + let vk = input.vk.convert_to_affine( + ctx, + range, + &g1_chip, + input.num_public_inputs, + MAX_PUBLIC_INPUTS + 1, + ); + + let public_inputs = input.public_inputs.clone(); + + let success = verify_proof( + ctx, + range, + &pairing_chip, + &g1_chip, + &g2_chip, + &vk, + &p, + &public_inputs.vec, + ); + + (input, Groth16VerifierComponentOutput { success }) +} diff --git a/axiom-components/src/groth16/test.rs b/axiom-components/src/groth16/test.rs new file mode 100644 index 00000000..905fffd7 --- /dev/null +++ b/axiom-components/src/groth16/test.rs @@ -0,0 +1,186 @@ +use std::str::FromStr; + +use axiom_eth::{halo2_proofs::halo2curves::bn256::Fr, utils::hilo::HiLo}; +use itertools::Itertools; +use lazy_static::lazy_static; + +use super::{ + Groth16VerifierComponent, Groth16VerifierComponentInput, Groth16VerifierComponentOutput, + Groth16VerifierComponentParams, Groth16VerifierComponentProof, + Groth16VerifierComponentVerificationKey, +}; +use crate::{ + groth16::{vec_to_hilo_pair, vec_to_hilo_point, HiLoPair, HiLoPoint}, + utils::{ + flatten::{FixLenVec, VecKey}, + testing::{basic_component_outputs_test, basic_component_test_prove}, + }, +}; + +macro_rules! deserialize_key { + ($json: expr, $val: expr) => { + serde_json::from_value($json[$val].clone()).unwrap() + }; +} + +const MAX_PUBLIC_INPUTS: usize = 11; + +pub fn read_input( + vk_file: String, + pf_file: String, + pub_file: String, +) -> Groth16VerifierComponentInput { + let verification_key_file = std::fs::read_to_string(vk_file).unwrap(); + let verification_key_file: serde_json::Value = + serde_json::from_str(verification_key_file.as_str()).unwrap(); + + let vk_alpha_1: [String; 3] = deserialize_key!(verification_key_file, "vk_alpha_1"); + let vk_beta_2: [[String; 2]; 3] = deserialize_key!(verification_key_file, "vk_beta_2"); + let vk_gamma_2: [[String; 2]; 3] = deserialize_key!(verification_key_file, "vk_gamma_2"); + let vk_delta_2: [[String; 2]; 3] = deserialize_key!(verification_key_file, "vk_delta_2"); + + let alpha_g1: HiLoPoint = vec_to_hilo_point(&vk_alpha_1); + let beta_g2: HiLoPair = vec_to_hilo_pair(&vk_beta_2); + let gamma_g2: HiLoPair = vec_to_hilo_pair(&vk_gamma_2); + let delta_g2: HiLoPair = vec_to_hilo_pair(&vk_delta_2); + + let ic: Vec<[String; 3]> = deserialize_key!(verification_key_file, "IC"); + let mut ic_vec = ic.into_iter().map(|s| vec_to_hilo_point(&s)).collect_vec(); + ic_vec.resize(MAX_PUBLIC_INPUTS + 1, (HiLo::default(), HiLo::default())); + let gamma_abc_g1 = VecKey::new(ic_vec).unwrap(); + + let vk = Groth16VerifierComponentVerificationKey { + alpha_g1, + beta_g2, + gamma_g2, + delta_g2, + gamma_abc_g1, + }; + + let proof_file = std::fs::read_to_string(pf_file).unwrap(); + let proof_file: serde_json::Value = serde_json::from_str(proof_file.as_str()).unwrap(); + + // get proof + let a: [String; 3] = deserialize_key!(proof_file, "pi_a"); + let b: [[String; 2]; 3] = deserialize_key!(proof_file, "pi_b"); + let c: [String; 3] = deserialize_key!(proof_file, "pi_c"); + + let a: HiLoPoint = vec_to_hilo_point(&a); + let b: HiLoPair = vec_to_hilo_pair(&b); + let c: HiLoPoint = vec_to_hilo_point(&c); + + let pf = Groth16VerifierComponentProof { a, b, c }; + + // get public inputs + let public_file = std::fs::read_to_string(pub_file).unwrap(); + let public_file: serde_json::Value = serde_json::from_str(public_file.as_str()).unwrap(); + let pi: Vec = serde_json::from_value(public_file.clone()).unwrap(); + let len = pi.len(); + let mut pi = pi + .into_iter() + .map(|p| Fr::from(u64::from_str(&p).unwrap())) + .collect_vec(); + pi.resize(MAX_PUBLIC_INPUTS, Fr::from(0)); + let public_inputs = FixLenVec::new(pi).unwrap(); + + Groth16VerifierComponentInput { + vk, + proof: pf, + public_inputs, + num_public_inputs: Fr::from(len as u64 + 1), + } +} + +fn get_groth16_output(success: bool) -> Groth16VerifierComponentOutput { + Groth16VerifierComponentOutput { + success: if success { Fr::one() } else { Fr::zero() }, + } +} + +lazy_static! { + static ref GROTH16VERIFY_PARAMS: Groth16VerifierComponentParams = + Groth16VerifierComponentParams { + capacity: 1, + limb_bits: 88, + num_limbs: 3, + }; +} + +lazy_static! { + static ref GROTH16VERIFY_PARAMS_CAP2: Groth16VerifierComponentParams = + Groth16VerifierComponentParams { + capacity: 2, + limb_bits: 88, + num_limbs: 3, + }; +} + +#[test] +fn test_groth16_output() { + basic_component_outputs_test::>( + 20, + vec![read_input( + "src/groth16/test_data/puzzle.json".to_string(), + "src/groth16/test_data/proof.json".to_string(), + "src/groth16/test_data/public_inputs.json".to_string(), + )], + vec![get_groth16_output(true)], + GROTH16VERIFY_PARAMS.clone(), + ); +} + +#[test] +fn test_groth16_output_default() { + basic_component_outputs_test::>( + 20, + vec![read_input( + "src/groth16/test_data/default.json".to_string(), + "src/groth16/test_data/default_proof.json".to_string(), + "src/groth16/test_data/default_public_inputs.json".to_string(), + )], + vec![get_groth16_output(true)], + GROTH16VERIFY_PARAMS.clone(), + ); +} + +#[test] +fn test_groth16_output_with_wrong_signature() { + basic_component_outputs_test::>( + 20, + vec![read_input( + "src/groth16/test_data/puzzle_modified.json".to_string(), + "src/groth16/test_data/proof.json".to_string(), + "src/groth16/test_data/public_inputs_modified.json".to_string(), + )], + vec![get_groth16_output(false)], + GROTH16VERIFY_PARAMS.clone(), + ); +} + +#[test] +fn test_groth16_prove() { + basic_component_test_prove::>( + 20, + vec![read_input( + "src/groth16/test_data/puzzle.json".to_string(), + "src/groth16/test_data/proof.json".to_string(), + "src/groth16/test_data/public_inputs.json".to_string(), + )], + GROTH16VERIFY_PARAMS.clone(), + ) + .unwrap(); +} + +#[test] +fn test_groth16_prove_default() { + basic_component_test_prove::>( + 20, + vec![read_input( + "src/groth16/test_data/default.json".to_string(), + "src/groth16/test_data/default_proof.json".to_string(), + "src/groth16/test_data/default_public_inputs.json".to_string(), + )], + GROTH16VERIFY_PARAMS.clone(), + ) + .unwrap(); +} diff --git a/axiom-components/src/groth16/test_data/default.json b/axiom-components/src/groth16/test_data/default.json new file mode 100644 index 00000000..86d5ac7e --- /dev/null +++ b/axiom-components/src/groth16/test_data/default.json @@ -0,0 +1,99 @@ +{ + "protocol": "groth16", + "curve": "bn128", + "nPublic": 2, + "vk_alpha_1": [ + "20491192805390485299153009773594534940189261866228447918068658471970481763042", + "9383485363053290200918347156157836566562967994039712273449902621266178545958", + "1" + ], + "vk_beta_2": [ + [ + "6375614351688725206403948262868962793625744043794305715222011528459656738731", + "4252822878758300859123897981450591353533073413197771768651442665752259397132" + ], + [ + "10505242626370262277552901082094356697409835680220590971873171140371331206856", + "21847035105528745403288232691147584728191162732299865338377159692350059136679" + ], + [ + "1", + "0" + ] + ], + "vk_gamma_2": [ + [ + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + ], + [ + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + ], + [ + "1", + "0" + ] + ], + "vk_delta_2": [ + [ + "3467004866717634776339573347423169002547635002985878815508898961522743506452", + "19865331138541507750394553392171827109124121759749012695741881283803108508347" + ], + [ + "2189040294157466407187767498553323210668670372829423239655416479671231034454", + "9060993844443059227358689399147552366673505348777583132029617367152835581644" + ], + [ + "1", + "0" + ] + ], + "vk_alphabeta_12": [ + [ + [ + "2029413683389138792403550203267699914886160938906632433982220835551125967885", + "21072700047562757817161031222997517981543347628379360635925549008442030252106" + ], + [ + "5940354580057074848093997050200682056184807770593307860589430076672439820312", + "12156638873931618554171829126792193045421052652279363021382169897324752428276" + ], + [ + "7898200236362823042373859371574133993780991612861777490112507062703164551277", + "7074218545237549455313236346927434013100842096812539264420499035217050630853" + ] + ], + [ + [ + "7077479683546002997211712695946002074877511277312570035766170199895071832130", + "10093483419865920389913245021038182291233451549023025229112148274109565435465" + ], + [ + "4595479056700221319381530156280926371456704509942304414423590385166031118820", + "19831328484489333784475432780421641293929726139240675179672856274388269393268" + ], + [ + "11934129596455521040620786944827826205713621633706285934057045369193958244500", + "8037395052364110730298837004334506829870972346962140206007064471173334027475" + ] + ] + ], + "IC": [ + [ + "18635637960557560857094512684047887771596119327447282264110915689478359176884", + "2787739109285944951079006253283606975566487445507222876760682453501866793407", + "1" + ], + [ + "5924520914420601398188001890215477493578059223690495899194579533928493711633", + "10458548929339196911259801917183441320857030738864516659971366674948828826220", + "1" + ], + [ + "700433635233066042387364180517373526251183001161528242738155819104314938031", + "5917976447441905930232643207659393469418720332008787407970690068286695117993", + "1" + ] + ] +} \ No newline at end of file diff --git a/axiom-components/src/groth16/test_data/default_proof.json b/axiom-components/src/groth16/test_data/default_proof.json new file mode 100644 index 00000000..a7facfc5 --- /dev/null +++ b/axiom-components/src/groth16/test_data/default_proof.json @@ -0,0 +1,28 @@ +{ + "pi_a": [ + "8680517028102508686697833871754756412796114974847015697141940673533053840142", + "11141740546280350879096131061836524403590156119612784562000052415659458825489", + "1" + ], + "pi_b": [ + [ + "1440891193609311978764021485940260427550408426443948025356285139253983656114", + "394601482735020272172278930519584503435819882918609688334533005392757855204" + ], + [ + "20494951949987457972693384400773181275472641495294869490116939117698276584704", + "10308299421766040557229866656950534872860339113790990257433934972338427371767" + ], + [ + "1", + "0" + ] + ], + "pi_c": [ + "15673413881925436037817830665630792674139737175244287780191582909763810965191", + "15925053412344238109167921622693154977078903172031475553777735264199083434300", + "1" + ], + "protocol": "groth16", + "curve": "bn128" +} \ No newline at end of file diff --git a/axiom-components/src/groth16/test_data/default_public_inputs.json b/axiom-components/src/groth16/test_data/default_public_inputs.json new file mode 100644 index 00000000..20bcc187 --- /dev/null +++ b/axiom-components/src/groth16/test_data/default_public_inputs.json @@ -0,0 +1,4 @@ +[ + "385", + "5" +] \ No newline at end of file diff --git a/axiom-components/src/groth16/test_data/proof.json b/axiom-components/src/groth16/test_data/proof.json new file mode 100644 index 00000000..639687d0 --- /dev/null +++ b/axiom-components/src/groth16/test_data/proof.json @@ -0,0 +1,28 @@ +{ + "pi_a": [ + "962423428081562910901059144273450716544121041259586243377276693905507059495", + "6269920170316908075332568210114679585341557519371079712445757779930945262488", + "1" + ], + "pi_b": [ + [ + "3473546770835491692959700059780143623805915270641221528428153874725348976522", + "21682306474032690305208973865406962638362599403795888996124023885056643621936" + ], + [ + "7879676437216769559260035791177908989880202205086660019060147485090308560994", + "5959841779051761543175716123558168850061269357803176163494356782589776655729" + ], + [ + "1", + "0" + ] + ], + "pi_c": [ + "6442167158077394342423479055624667285312664150565764656182370204000970612507", + "8419072572960491648316888824475879537159658539985586234540725052311960683330", + "1" + ], + "protocol": "groth16", + "curve": "bn128" +} \ No newline at end of file diff --git a/axiom-components/src/groth16/test_data/proof_swap_ac.json b/axiom-components/src/groth16/test_data/proof_swap_ac.json new file mode 100644 index 00000000..7dea2a57 --- /dev/null +++ b/axiom-components/src/groth16/test_data/proof_swap_ac.json @@ -0,0 +1,28 @@ +{ + "pi_a": [ + "6442167158077394342423479055624667285312664150565764656182370204000970612507", + "8419072572960491648316888824475879537159658539985586234540725052311960683330", + "1" + ], + "pi_b": [ + [ + "3473546770835491692959700059780143623805915270641221528428153874725348976522", + "21682306474032690305208973865406962638362599403795888996124023885056643621936" + ], + [ + "7879676437216769559260035791177908989880202205086660019060147485090308560994", + "5959841779051761543175716123558168850061269357803176163494356782589776655729" + ], + [ + "1", + "0" + ] + ], + "pi_c": [ + "962423428081562910901059144273450716544121041259586243377276693905507059495", + "6269920170316908075332568210114679585341557519371079712445757779930945262488", + "1" + ], + "protocol": "groth16", + "curve": "bn128" +} \ No newline at end of file diff --git a/axiom-components/src/groth16/test_data/public_inputs.json b/axiom-components/src/groth16/test_data/public_inputs.json new file mode 100644 index 00000000..d6635c5d --- /dev/null +++ b/axiom-components/src/groth16/test_data/public_inputs.json @@ -0,0 +1,13 @@ +[ + "2", + "0", + "42", + "0", + "93", + "0", + "0", + "0", + "0", + "0", + "0" +] \ No newline at end of file diff --git a/axiom-components/src/groth16/test_data/public_inputs_modified.json b/axiom-components/src/groth16/test_data/public_inputs_modified.json new file mode 100644 index 00000000..a4a2b232 --- /dev/null +++ b/axiom-components/src/groth16/test_data/public_inputs_modified.json @@ -0,0 +1,13 @@ +[ + "2", + "1", + "42", + "0", + "93", + "0", + "0", + "0", + "0", + "0", + "0" +] \ No newline at end of file diff --git a/axiom-components/src/groth16/test_data/puzzle.json b/axiom-components/src/groth16/test_data/puzzle.json new file mode 100644 index 00000000..f9a6de67 --- /dev/null +++ b/axiom-components/src/groth16/test_data/puzzle.json @@ -0,0 +1,144 @@ +{ + "protocol": "groth16", + "curve": "bn128", + "nPublic": 11, + "vk_alpha_1": [ + "20491192805390485299153009773594534940189261866228447918068658471970481763042", + "9383485363053290200918347156157836566562967994039712273449902621266178545958", + "1" + ], + "vk_beta_2": [ + [ + "6375614351688725206403948262868962793625744043794305715222011528459656738731", + "4252822878758300859123897981450591353533073413197771768651442665752259397132" + ], + [ + "10505242626370262277552901082094356697409835680220590971873171140371331206856", + "21847035105528745403288232691147584728191162732299865338377159692350059136679" + ], + [ + "1", + "0" + ] + ], + "vk_gamma_2": [ + [ + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + ], + [ + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + ], + [ + "1", + "0" + ] + ], + "vk_delta_2": [ + [ + "1301440655462093245373106360799083795679765904685639582722491593797402927781", + "17270602004643109894840507932129928847986959895556589085593770125596184179253" + ], + [ + "7278453974459869831319652276588135268214457204781099311602054961995150138030", + "20418515439689032165082990135653730520348201748084898057163411226452716243365" + ], + [ + "1", + "0" + ] + ], + "vk_alphabeta_12": [ + [ + [ + "2029413683389138792403550203267699914886160938906632433982220835551125967885", + "21072700047562757817161031222997517981543347628379360635925549008442030252106" + ], + [ + "5940354580057074848093997050200682056184807770593307860589430076672439820312", + "12156638873931618554171829126792193045421052652279363021382169897324752428276" + ], + [ + "7898200236362823042373859371574133993780991612861777490112507062703164551277", + "7074218545237549455313236346927434013100842096812539264420499035217050630853" + ] + ], + [ + [ + "7077479683546002997211712695946002074877511277312570035766170199895071832130", + "10093483419865920389913245021038182291233451549023025229112148274109565435465" + ], + [ + "4595479056700221319381530156280926371456704509942304414423590385166031118820", + "19831328484489333784475432780421641293929726139240675179672856274388269393268" + ], + [ + "11934129596455521040620786944827826205713621633706285934057045369193958244500", + "8037395052364110730298837004334506829870972346962140206007064471173334027475" + ] + ] + ], + "IC": [ + [ + "18167771208750482521664353324581113888786036463728198018832762940102508097002", + "17711170233362048290109207474322791677468986701017872546245578800991296094696", + "1" + ], + [ + "966318491506385384433820986812083268212622165270177861148356454778869002232", + "12114772687954634880011360513716141889810582679914666097428323992707649859104", + "1" + ], + [ + "18393657841510818459469230522772996059530981572945018454314108550626631617527", + "276334269663650328837397605522283156034503830349805250057780405888201213112", + "1" + ], + [ + "16361238224044463679028850957619268678224328152031202067600851935504132392092", + "8115289544716230745249872822550627587849486188059323095337402735638528534943", + "1" + ], + [ + "4501021300713040170128100282694205933215643868499056087516268985299692248186", + "8866650512592555474100861234944456371229573811986557277208611135301752464969", + "1" + ], + [ + "8751169105335636126636423485234847719377678968608523360912563425089513697203", + "18354904053207603032473804602849348199773049772911728463407024994416832078772", + "1" + ], + [ + "19041090949071767298121095835413695230901989091063431413912328949275529571269", + "1273859052217812649967290238850120065610027013280338876204936664135645867320", + "1" + ], + [ + "8287897639050195051852241102875516544560479336810940411726368968479279693299", + "10493560368612830610822803054766919101097826808855218458157472156591039824339", + "1" + ], + [ + "4118005374697876124133431815759892172945263502984254652155368432421062299260", + "18826221436630695308561191755571537965013809579866827080510241464368990881657", + "1" + ], + [ + "16626554046938633513345160443980820123598827257126507810481396073938757816988", + "5629263397341214830175029039454296059147936147078195333329216799882697934812", + "1" + ], + [ + "9921961020773465365163240017772384831076363116743061634790600644627436670127", + "8619602004893756663896331306750419481660616421746429181660238728010382543104", + "1" + ], + [ + "6813321130173844295182009954144572636402406965910222794496456473506943506945", + "5122397177857741038527713786520363152191319777204337927775026534156845690456", + "1" + ] + ] +} \ No newline at end of file diff --git a/axiom-components/src/groth16/test_data/puzzle_modified.json b/axiom-components/src/groth16/test_data/puzzle_modified.json new file mode 100644 index 00000000..87c522ca --- /dev/null +++ b/axiom-components/src/groth16/test_data/puzzle_modified.json @@ -0,0 +1,144 @@ +{ + "protocol": "groth16", + "curve": "bn128", + "nPublic": 11, + "vk_alpha_1": [ + "20491192805390485299153009773594534940189261866228447918068658471970481763042", + "9383485363053290200918347156157836566562967994039712273449902621266178545958", + "1" + ], + "vk_beta_2": [ + [ + "6375614351688725206403948262868962793625744043794305715222011528459656738731", + "4252822878758300859123897981450591353533073413197771768651442665752259397132" + ], + [ + "10505242626370262277552901082094356697409835680220590971873171140371331206856", + "21847035105528745403288232691147584728191162732299865338377159692350059136679" + ], + [ + "1", + "0" + ] + ], + "vk_gamma_2": [ + [ + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + ], + [ + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + ], + [ + "1", + "0" + ] + ], + "vk_delta_2": [ + [ + "1301440655462093245373106360799083795679765904685639582722491593797402927781", + "17270602004643109894840507932129928847986959895556589085593770125596184179253" + ], + [ + "7278453974459869831319652276588135268214457204781099311602054961995150138030", + "20418515439689032165082990135653730520348201748084898057163411226452716243365" + ], + [ + "1", + "0" + ] + ], + "vk_alphabeta_12": [ + [ + [ + "2029413683389138792403550203267699914886160938906632433982220835551125967885", + "21072700047562757817161031222997517981543347628379360635925549008442030252106" + ], + [ + "5940354580057074848093997050200682056184807770593307860589430076672439820312", + "12156638873931618554171829126792193045421052652279363021382169897324752428276" + ], + [ + "7898200236362823042373859371574133993780991612861777490112507062703164551277", + "7074218545237549455313236346927434013100842096812539264420499035217050630853" + ] + ], + [ + [ + "7077479683546002997211712695946002074877511277312570035766170199895071832130", + "10093483419865920389913245021038182291233451549023025229112148274109565435465" + ], + [ + "4595479056700221319381530156280926371456704509942304414423590385166031118820", + "19831328484489333784475432780421641293929726139240675179672856274388269393268" + ], + [ + "11934129596455521040620786944827826205713621633706285934057045369193958244500", + "8037395052364110730298837004334506829870972346962140206007064471173334027475" + ] + ] + ], + "IC": [ + [ + "18167771208750482521664353324581113888786036463728198018832762940102508097002", + "17711170233362048290109207474322791677468986701017872546245578800991296094696", + "1" + ], + [ + "966318491506385384433820986812083268212622165270177861148356454778869002232", + "12114772687954634880011360513716141889810582679914666097428323992707649859104", + "1" + ], + [ + "18393657841510818459469230522772996059530981572945018454314108550626631617527", + "276334269663650328837397605522283156034503830349805250057780405888201213112", + "1" + ], + [ + "16361238224044463679028850957619268678224328152031202067600851935504132392092", + "8115289544716230745249872822550627587849486188059323095337402735638528534943", + "1" + ], + [ + "4501021300713040170128100282694205933215643868499056087516268985299692248186", + "8866650512592555474100861234944456371229573811986557277208611135301752464969", + "1" + ], + [ + "8751169105335636126636423485234847719377678968608523360912563425089513697203", + "18354904053207603032473804602849348199773049772911728463407024994416832078772", + "1" + ], + [ + "19041090949071767298121095835413695230901989091063431413912328949275529571269", + "1273859052217812649967290238850120065610027013280338876204936664135645867320", + "1" + ], + [ + "8287897639050195051852241102875516544560479336810940411726368968479279693299", + "10493560368612830610822803054766919101097826808855218458157472156591039824339", + "1" + ], + [ + "4118005374697876124133431815759892172945263502984254652155368432421062299260", + "18826221436630695308561191755571537965013809579866827080510241464368990881657", + "1" + ], + [ + "16626554046938633513345160443980820123598827257126507810481396073938757816988", + "5629263397341214830175029039454296059147936147078195333329216799882697934812", + "1" + ], + [ + "6813321130173844295182009954144572636402406965910222794496456473506943506945", + "5122397177857741038527713786520363152191319777204337927775026534156845690456", + "1" + ], + [ + "9921961020773465365163240017772384831076363116743061634790600644627436670127", + "8619602004893756663896331306750419481660616421746429181660238728010382543104", + "1" + ] + ] +} \ No newline at end of file diff --git a/axiom-components/src/groth16/utils.rs b/axiom-components/src/groth16/utils.rs new file mode 100644 index 00000000..cc5520e4 --- /dev/null +++ b/axiom-components/src/groth16/utils.rs @@ -0,0 +1,64 @@ +use std::str::FromStr; + +use axiom_eth::{ + halo2_base::{gates::RangeChip, utils::biguint_to_fe, AssignedValue, Context}, + utils::hilo::HiLo, + Field, +}; +use groth_verifier::types::*; +use halo2_ecc::{bn254::FpChip, ecc::EccChip, fields::vector::FieldVector}; +use num_bigint::BigUint; +use num_traits::One; + +use crate::ecdsa::utils::load_fp_from_hilo; + +// HiLoPoint represents a point on the G1 curve +// and HiLoPair represents a point on the G2 curve +pub type HiLoPoint = (HiLo, HiLo); +pub type HiLoPair = (HiLoPoint, HiLoPoint); + +pub fn hilo_point_to_affine( + ctx: &mut Context, + range: &RangeChip, + g1_chip: &EccChip>, + point: HiLoPoint>, +) -> G1AffineAssigned { + let fp_chip = g1_chip.field_chip(); + let x = load_fp_from_hilo(ctx, range, fp_chip, point.0); + let y = load_fp_from_hilo(ctx, range, fp_chip, point.1); + G1AffineAssigned::new(x, y) +} + +pub fn hilo_pair_to_affine( + ctx: &mut Context, + range: &RangeChip, + g1_chip: &EccChip>, + pair: HiLoPair> +) -> G2AffineAssigned { + let fp_chip = g1_chip.field_chip(); + let bx0 = load_fp_from_hilo(ctx, range, fp_chip, pair.0 .0); + let bx1 = load_fp_from_hilo(ctx, range, fp_chip, pair.0 .1); + let by0 = load_fp_from_hilo(ctx, range, fp_chip, pair.1 .0); + let by1 = load_fp_from_hilo(ctx, range, fp_chip, pair.1 .1); + let bx = FieldVector(vec![bx0, bx1]); + let by = FieldVector(vec![by0, by1]); + + G2AffineAssigned::new(bx, by) +} + +pub fn biguint_to_hilo(x: BigUint) -> HiLo { + let hi = x.clone() >> 128; + let lo = x % (BigUint::one() << 128); + HiLo::from_hi_lo([biguint_to_fe(&hi), biguint_to_fe(&lo)]) +} + +pub fn vec_to_hilo_point(arr: &[String]) -> HiLoPoint { + ( + biguint_to_hilo(FromStr::from_str(&arr[0]).unwrap()), + biguint_to_hilo(FromStr::from_str(&arr[1]).unwrap()) + ) +} + +pub fn vec_to_hilo_pair(arr: &[[String; 2]]) -> HiLoPair { + (vec_to_hilo_point(&arr[0]), vec_to_hilo_point(&arr[1])) +} diff --git a/axiom-components/src/lib.rs b/axiom-components/src/lib.rs new file mode 100644 index 00000000..b7f83c76 --- /dev/null +++ b/axiom-components/src/lib.rs @@ -0,0 +1,13 @@ +#![feature(associated_type_defaults)] +#![feature(associated_type_bounds)] +#![feature(return_position_impl_trait_in_trait)] +#![allow(incomplete_features)] +#![feature(generic_const_exprs)] +pub use axiom_eth::utils::component as framework; +pub use halo2_ecc; +pub mod ecdsa; +mod example; +// pub mod groth16; +mod example_generics; +pub mod scaffold; +pub mod utils; diff --git a/axiom-components/src/scaffold/mod.rs b/axiom-components/src/scaffold/mod.rs new file mode 100644 index 00000000..24517d74 --- /dev/null +++ b/axiom-components/src/scaffold/mod.rs @@ -0,0 +1,211 @@ +use std::any::type_name; + +use axiom_eth::{ + halo2_base::{AssignedValue, Context}, + halo2_proofs::plonk::ConstraintSystem, + rlc::circuit::builder::RlcCircuitBuilder, + utils::build_utils::{aggregation::CircuitMetadata, dummy::DummyFrom}, + Field, +}; +use component_derive::ComponentParams; +use itertools::Itertools; + +use crate::framework::{ + circuit::{ + ComponentBuilder, CoreBuilder, CoreBuilderInput, CoreBuilderOutput, CoreBuilderParams, + }, + promise_collector::PromiseCaller, + types::{FixLenLogical, LogicalEmpty}, + utils::get_logical_value, + ComponentType, ComponentTypeId, FlattenVirtualTable, LogicalInputValue, LogicalResult, +}; + +pub type BasicComponentScaffoldIOPair = Vec<( + >::Input>, + >::Output>, +)>; + +pub type BasicComponentScaffoldIO = ( + >::Phase0Payload, + BasicComponentScaffoldIOPair, +); + +/// Basic component paramater struct for components whose configuration is only based on capacity. +#[derive(Default, Clone, ComponentParams)] +pub struct BasicComponentParams { + pub capacity: usize, +} + +impl BasicComponentParams { + pub fn new(capacity: usize) -> Self { + Self { capacity } + } +} + +/// The struct on which `ComponentType` and `CoreBuilder` are implemented, +/// given some `BasicComponentScaffold` implementation. +pub struct BasicComponentScaffoldImpl> { + pub params: I::Params, + pub input: Option>>, + pub payload: Option, +} + +/// Trait for specifying the types for a **single** input and output of a `BasicComponentScaffold` component. +/// The input of the component is `Vec>` and the output is `Vec>`. +pub trait BasicComponentScaffoldIOTypes { + type InputType: FixLenLogical; + type OutputType: FixLenLogical; +} + +/// Trait for defining a fixed-len component that uses `RlcCircuitBuilder` and does not +/// make calls to other components. +/// +/// See `./README.md` for more information on how to use this trait, and `src/example/mod.rs` +/// for an example of how to implement this trait +pub trait BasicComponentScaffold: BasicComponentScaffoldIOTypes { + type Input: FixLenLogical = Self::InputType; + type Output: FixLenLogical = Self::OutputType; + type Params: Clone + Default + CoreBuilderParams = BasicComponentParams; + type Phase0Payload = (); + type LogicalPublicInstance: FixLenLogical = LogicalEmpty; + + fn virtual_assign_phase0( + params: Self::Params, + builder: &mut RlcCircuitBuilder, + input: Vec>, + ) -> BasicComponentScaffoldIO; + + #[allow(unused_variables)] + fn virtual_assign_phase1(builder: &mut RlcCircuitBuilder, payload: Self::Phase0Payload) {} + + //optional helper function to assign input + fn assign_input(ctx: &mut Context, input: Self::Input) -> Self::Input> { + let flattened_input = input.into_raw(); + let assigned_input = ctx.assign_witnesses(flattened_input); + Self::Input::>::try_from_raw(assigned_input).unwrap() + } +} + +impl + 'static> ComponentType + for BasicComponentScaffoldImpl +where + I::Input: LogicalInputValue + DummyFrom, +{ + type InputValue = I::Input; + type InputWitness = I::Input>; + type OutputValue = I::Output; + type OutputWitness = I::Output>; + type LogicalInput = Self::InputValue; + + fn get_type_id() -> ComponentTypeId { + //type_name includes generic parameters, so we remove them + type_name::().split('<').next().unwrap().to_string() + } + + fn logical_result_to_virtual_rows_impl( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + vec![(ins.input.clone(), ins.output.clone())] + } + fn logical_input_to_virtual_rows_impl(li: &Self::LogicalInput) -> Vec { + vec![li.clone()] + } +} + +impl> ComponentBuilder + for BasicComponentScaffoldImpl +{ + type Config = (); + type Params = I::Params; + + fn new(params: Self::Params) -> Self { + Self { + input: None, + params, + payload: None, + } + } + + fn get_params(&self) -> Self::Params { + self.params.clone() + } + + fn configure_with_params(_: &mut ConstraintSystem, _: Self::Params) -> Self::Config {} + + fn calculate_params(&mut self) -> Self::Params { + self.params.clone() + } + + fn clear_witnesses(&mut self) { + self.payload = None; + } +} + +impl + 'static> CoreBuilder + for BasicComponentScaffoldImpl +where + I::Input: LogicalInputValue + DummyFrom, + Vec>: DummyFrom, +{ + type CompType = Self; + type PublicInstanceValue = I::LogicalPublicInstance; + type PublicInstanceWitness = I::LogicalPublicInstance>; + type CoreInput = Vec>; + + fn feed_input(&mut self, mut input: Self::CoreInput) -> anyhow::Result<()> { + let capacity = self.params.get_output_params().cap_per_shard()[0]; + if input.len() > capacity { + anyhow::bail!( + "Subquery results table is greater than capcaity - {} > {}", + input.len(), + capacity + ); + } + input.resize(capacity, input.get(0).unwrap().clone()); + self.input = Some(input); + Ok(()) + } + + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + _: PromiseCaller, + ) -> CoreBuilderOutput { + //otherwise will fail if phase1 doesn't exist + builder.base.main(1); + let (payload, output_vec) = + I::virtual_assign_phase0(self.params.clone(), builder, self.input.clone().unwrap()); + self.payload = Some(payload); + let vt: FlattenVirtualTable> = output_vec + .iter() + .map(|output| (output.0.clone().into(), output.1.clone().into())) + .collect_vec(); + let lr = output_vec + .iter() + .map(|output| { + LogicalResult::::new( + get_logical_value(&output.0.clone()), + get_logical_value(&output.1.clone()), + ) + }) + .collect_vec(); + + CoreBuilderOutput { + public_instances: vec![], + virtual_table: vt, + logical_results: lr, + } + } + + fn virtual_assign_phase1(&mut self, builder: &mut RlcCircuitBuilder) { + let payload = self.payload.take().unwrap(); + I::virtual_assign_phase1(builder, payload); + } +} + +impl> CircuitMetadata for BasicComponentScaffoldImpl { + const HAS_ACCUMULATOR: bool = false; + fn num_instance(&self) -> Vec { + unreachable!() + } +} diff --git a/axiom-components/src/utils/flatten.rs b/axiom-components/src/utils/flatten.rs new file mode 100644 index 00000000..f04bdcfa --- /dev/null +++ b/axiom-components/src/utils/flatten.rs @@ -0,0 +1,282 @@ +use anyhow::Result; +use axiom_eth::utils::hilo::HiLo; +use serde::{Deserialize, Serialize}; + +use crate::{impl_input_flatten_for_fixed_array, impl_input_flatten_for_tuple}; + +/// Trait for flattening/unflattening input to/from a vector of field elements. +/// Can be used to flatten `Vec`, witness the flattened vector, and then unflatten it to `Vec>`. +pub trait InputFlatten: Sized { + const NUM_FE: usize; + fn flatten_vec(&self) -> Vec; + fn unflatten(vec: Vec) -> Result; +} + +/// Wrapper struct around a vector of fixed length; used to implement `InputFlatten` for fixed-length vectors +/// (useful in a `ComponentIO` struct with a fixed-length vector as a field) +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct FixLenVec { + pub vec: Vec, +} + +impl Default for FixLenVec { + fn default() -> Self { + FixLenVec { + vec: vec![T::default(); N], + } + } +} + +impl FixLenVec { + pub fn new(vec: Vec) -> anyhow::Result { + if vec.len() != N { + anyhow::bail!("Invalid input length: {} != {}", vec.len(), N); + } + Ok(FixLenVec { vec }) + } + + pub fn into_inner(self) -> Vec { + self.vec + } +} + +impl From> for FixLenVec { + fn from(vec: Vec) -> Self { + Self { vec } + } +} + +impl FixLenVec { + pub fn iter(&self) -> std::slice::Iter<'_, T> { + self.vec.iter() + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct VecKey { + // did not make vec an array because cannot serialize + pub vec: Vec, +} + +impl Default for VecKey { + fn default() -> Self { + VecKey { + // VecKey length is MAX_PUBLIC_INPUTS + 1 because it includes the variable length + vec: vec![T::default(); MAX_PUBLIC_INPUTS + 1], + } + } +} + +impl VecKey { + // VecKey length is MAX_PUBLIC_INPUTS + 1 because it includes the variable length + pub fn new(vec: Vec) -> anyhow::Result { + if vec.len() != MAX_PUBLIC_INPUTS + 1 { + anyhow::bail!( + "Invalid input length: {} != {}", + vec.len(), + MAX_PUBLIC_INPUTS + 1 + ); + } + Ok(VecKey { vec }) + } + + pub fn into_inner(self) -> Vec { + self.vec + } +} + +impl From> for VecKey { + fn from(vec: Vec) -> Self { + Self { vec } + } +} + +impl VecKey { + pub fn iter(&self) -> std::slice::Iter<'_, T> { + self.vec.iter() + } +} + +/// Convert a vector to a static slice. +pub fn into_static_slice(vec: Vec) -> &'static [usize] { + let boxed_slice: Box<[usize]> = vec.into_boxed_slice(); + Box::leak(boxed_slice) +} + +macro_rules! check_input_length { + ($vec:ident) => { + if $vec.len() != >::NUM_FE { + anyhow::bail!( + "Invalid input length: {} != {}", + $vec.len(), + >::NUM_FE + ); + } + }; +} + +impl InputFlatten for FixLenVec { + const NUM_FE: usize = N; + fn flatten_vec(&self) -> Vec { + self.vec.clone() + } + fn unflatten(vec: Vec) -> Result { + check_input_length!(vec); + Ok(FixLenVec { vec }) + } +} + +impl InputFlatten for VecKey { + const NUM_FE: usize = MAX_PUBLIC_INPUTS + 1; + fn flatten_vec(&self) -> Vec { + self.vec.clone() + } + fn unflatten(vec: Vec) -> Result { + check_input_length!(vec); + Ok(VecKey { vec }) + } +} + +impl InputFlatten for HiLo { + const NUM_FE: usize = 2; + fn flatten_vec(&self) -> Vec { + vec![self.hi(), self.lo()] + } + fn unflatten(vec: Vec) -> Result { + check_input_length!(vec); + Ok(HiLo::from_hi_lo([vec[0], vec[1]])) + } +} + +impl_input_flatten_for_tuple!(HiLo, HiLo); +impl_input_flatten_for_tuple!((HiLo, HiLo), (HiLo, HiLo)); + +impl InputFlatten for T { + const NUM_FE: usize = 1; + fn flatten_vec(&self) -> Vec { + vec![*self] + } + fn unflatten(vec: Vec) -> Result { + check_input_length!(vec); + Ok(vec[0]) + } +} + +impl_input_flatten_for_fixed_array!(T); +impl_input_flatten_for_fixed_array!(HiLo); +impl_input_flatten_for_fixed_array!((HiLo, HiLo)); + +#[macro_export] +macro_rules! impl_input_flatten_for_tuple { + ($type1:ty, $type2:ty) => { + impl InputFlatten for ($type1, $type2) + where + $type1: InputFlatten, + $type2: InputFlatten, + { + const NUM_FE: usize = <$type1>::NUM_FE + <$type2>::NUM_FE; + + fn flatten_vec(&self) -> Vec { + let mut first_vec = self.0.flatten_vec(); + first_vec.extend(self.1.flatten_vec()); + first_vec + } + + fn unflatten(vec: Vec) -> anyhow::Result { + check_input_length!(vec); + let (first_part, second_part) = vec.split_at(<$type1>::NUM_FE); + let first = <$type1>::unflatten(first_part.to_vec())?; + let second = <$type2>::unflatten(second_part.to_vec())?; + Ok((first, second)) + } + } + }; +} + +#[macro_export] +macro_rules! impl_input_flatten_for_fixed_array { + ($type1:ty) => { + impl InputFlatten for [$type1; N] + where + $type1: InputFlatten, + { + const NUM_FE: usize = <$type1>::NUM_FE * N; + + fn flatten_vec(&self) -> Vec { + self.to_vec() + .iter() + .map(|x| x.flatten_vec()) + .flatten() + .collect() + } + + fn unflatten(vec: Vec) -> anyhow::Result { + check_input_length!(vec); + let res = vec + .chunks(<$type1>::NUM_FE) + .into_iter() + .map(|x| <$type1>::unflatten(x.to_vec()).unwrap()) + .collect::>(); + let mut array = [res[0]; N]; + for (i, item) in res.into_iter().enumerate() { + array[i] = item; + } + Ok(array) + } + } + }; +} + +impl InputFlatten + for VecKey<(HiLo, HiLo), MAX_PUBLIC_INPUTS> +{ + const NUM_FE: usize = 4 * (MAX_PUBLIC_INPUTS + 1); + fn flatten_vec(&self) -> Vec { + self.vec.iter().flat_map(|x| x.flatten_vec()).collect() + } + fn unflatten(vec: Vec) -> Result { + check_input_length!(vec); + let vec = vec + .chunks(4) + .map(|x| <(HiLo, HiLo)>::unflatten(x.to_vec()).unwrap()) + .collect::>(); + Ok(VecKey { vec }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::utils::testing::generic_test_flatten; + + #[test] + fn test_flatten_hilo() { + generic_test_flatten(HiLo::from_hi_lo([1, 2]), vec![1, 2]); + } + + #[test] + fn test_flatten_hilo_tuple() { + generic_test_flatten( + (HiLo::from_hi_lo([1, 2]), HiLo::from_hi_lo([3, 4])), + vec![1, 2, 3, 4], + ); + } + + #[test] + fn test_flatten_fe() { + generic_test_flatten(1, vec![1]); + } + + #[test] + fn test_flatten_array() { + generic_test_flatten([1, 2, 3], vec![1, 2, 3]); + } + + #[test] + fn test_flatten_hilo_array() { + generic_test_flatten( + [HiLo::from_hi_lo([1, 2]), HiLo::from_hi_lo([3, 4])], + vec![1, 2, 3, 4], + ); + } +} diff --git a/axiom-components/src/utils/mod.rs b/axiom-components/src/utils/mod.rs new file mode 100644 index 00000000..47a2db8d --- /dev/null +++ b/axiom-components/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod flatten; +#[cfg(test)] +pub(crate) mod testing; diff --git a/axiom-components/src/utils/testing.rs b/axiom-components/src/utils/testing.rs new file mode 100644 index 00000000..5a62635e --- /dev/null +++ b/axiom-components/src/utils/testing.rs @@ -0,0 +1,166 @@ +use ark_std::{end_timer, start_timer}; +use axiom_eth::{ + halo2_base::{ + gates::circuit::BaseCircuitParams, + utils::{ + fs::gen_srs, + testing::{check_proof_with_instances, gen_proof_with_instances}, + }, + }, + halo2_proofs::plonk::{keygen_pk, keygen_vk}, + halo2curves::bn256::Fr, + rlc::circuit::RlcCircuitParams, + utils::{ + build_utils::{dummy::DummyFrom, pinning::CircuitPinningInstructions}, + component::{ + circuit::ComponentCircuitImpl, promise_loader::empty::EmptyPromiseLoader, + types::FixLenLogical, ComponentCircuit, ComponentType, LogicalInputValue, + LogicalResult, SelectedDataShardsInMerkle, + }, + }, + Field, +}; + +use super::flatten::InputFlatten; +use crate::scaffold::{BasicComponentScaffold, BasicComponentScaffoldImpl}; + +/// Test that an `InputFlatten` implementation is correct. +/// Checks that `flatten_vec` and `unflatten` are inverses of each other. +pub fn generic_test_flatten + std::cmp::PartialEq + std::fmt::Debug>( + input: T, + expected: Vec, +) { + let flattened = input.flatten_vec(); + let unflattened = >::unflatten(flattened.clone()).unwrap(); + assert_eq!(flattened.clone(), expected); + assert_eq!(unflattened, input); + assert_eq!(>::NUM_FE, expected.len()); +} + +/// Test that a FixLenLogical implementation is correct. +/// Checks that for a struct `T` that implements `InputFlatten`, +/// `into_raw` and `try_from_raw` are inverses of each other. +pub fn fix_len_logical_input_test< + T: FixLenLogical + InputFlatten + std::cmp::PartialEq + std::fmt::Debug, +>( + input: T, + expected: Vec, +) { + let flattened = input.clone().into_raw(); + let unflattened = T::try_from_raw(flattened.clone()).unwrap(); + assert_eq!(unflattened, input); + assert_eq!(flattened, expected); +} + +/// Returns a vector of `LogicalResult`s from a vector of inputs and outputs. +pub fn logical_result_from_io>( + input: Vec, + output: Vec, +) -> Vec> { + input + .into_iter() + .zip(output) + .map(|(input, output)| LogicalResult::::new(input, output)) + .collect() +} + +/// Test that the outputs of some `BasicComponentScaffold` component are correct, +/// given some inputs and an expected output. +pub fn basic_component_outputs_test + 'static>( + k: usize, + input: Vec< as ComponentType>::LogicalInput>, + expected_output: Vec< as ComponentType>::OutputValue>, + component_params: I::Params, +) where + I::Input: LogicalInputValue + DummyFrom, + Vec>: DummyFrom, +{ + let rlc_circuit_params = RlcCircuitParams { + base: BaseCircuitParams { + k, + lookup_bits: Some(k - 1), + num_instance_columns: 1, + ..Default::default() + }, + num_rlc_columns: 0, + }; + + let mut circuit = ComponentCircuitImpl::< + Fr, + BasicComponentScaffoldImpl, + EmptyPromiseLoader, + >::new(component_params, (), rlc_circuit_params); + circuit.feed_input(Box::new(input.clone())).unwrap(); + circuit.calculate_params(); + let output = circuit.compute_outputs().unwrap(); + let logical_result: Vec>> = + logical_result_from_io(input.clone(), expected_output.clone()); + let expected_output_shard = SelectedDataShardsInMerkle::from_single_shard(logical_result); + assert_eq!(output, expected_output_shard); +} + +/// Test that a `BasicComponentScaffold` component can be proven. +pub fn basic_component_test_prove + 'static>( + k: usize, + input: Vec< as ComponentType>::LogicalInput>, + component_params: >::Params, +) -> anyhow::Result<()> +where + I::Input: LogicalInputValue + DummyFrom, + Vec>: DummyFrom, +{ + let rlc_circuit_params = RlcCircuitParams { + base: BaseCircuitParams { + k, + lookup_bits: Some(k - 1), + num_instance_columns: 1, + ..Default::default() + }, + num_rlc_columns: 0, + }; + + let mut circuit = ComponentCircuitImpl::< + Fr, + BasicComponentScaffoldImpl, + EmptyPromiseLoader, + >::new(component_params.clone(), (), rlc_circuit_params.clone()); + circuit.feed_input(Box::new(input.clone())).unwrap(); + circuit.calculate_params(); + + let params = gen_srs(rlc_circuit_params.base.k as u32); + let vk_time = start_timer!(|| "vk gen"); + let vk = keygen_vk(¶ms, &circuit).unwrap(); + end_timer!(vk_time); + let pk_time = start_timer!(|| "pk gen"); + let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); + end_timer!(pk_time); + + let pinning = circuit.pinning(); + circuit = + ComponentCircuitImpl::, EmptyPromiseLoader>::new( + component_params, + (), + pinning.params.clone(), + ) + .use_break_points(pinning.break_points); + circuit.feed_input(Box::new(input))?; + + let pf_time = start_timer!(|| "proof gen"); + let instances: Vec = circuit.get_public_instances().into(); + + let proof = gen_proof_with_instances(¶ms, &pk, circuit, &[&instances]); + end_timer!(pf_time); + + let verify_time = start_timer!(|| "verify"); + check_proof_with_instances(¶ms, pk.get_vk(), &proof, &[&instances], true); + end_timer!(verify_time); + + Ok(()) +} + +pub fn get_type_id + 'static>() -> String +where + I::Input: LogicalInputValue + DummyFrom, +{ + BasicComponentScaffoldImpl::::get_type_id() +} diff --git a/axiom-core/Cargo.toml b/axiom-core/Cargo.toml new file mode 100644 index 00000000..c70d76ac --- /dev/null +++ b/axiom-core/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "axiom-core" +version = "2.0.13" +authors = ["Intrinsic Technologies"] +license = "MIT" +edition = "2021" +repository = "https://github.com/axiom-crypto/axiom-eth" +readme = "README.md" +description = "This contains the ZK circuits that generate proofs for the `AxiomV2Core` smart contract. These circuits read the RLP encoded block headers for a chain of blocks and verify that the block headers form a chain. They output a Merkle Mountain Range of the block hashes of the chain. This crate also contains aggregation circuits to aggregate multiple circuits for the purpose of proving longer chains." +rust-version = "1.73.0" + +[[bin]] +name = "axiom-core-keygen" +path = "src/bin/keygen.rs" +required-features = ["keygen"] + +[dependencies] +itertools = "0.11" +lazy_static = "1.4.0" +# serialization +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0", default-features = false } +serde_with = { version = "3.3", features = ["base64"] } +# misc +log = "0.4" +env_logger = "0.10" +#getset = "0.1.2" +anyhow = "1.0" +hex = "0.4.3" + +# halo2, features turned on by axiom-eth +axiom-eth = { version = "0.4.1", path = "../axiom-eth", default-features = false, features = ["providers", "aggregation", "evm"] } + +# crypto +ethers-core = { workspace = true } + +# keygen +clap = { version = "=4.4.7", features = ["derive"], optional = true } +blake3 = { version = "=1.5", optional = true } +serde_yaml = { version = "=0.9.16", optional = true } + +[dev-dependencies] +hex = "0.4.3" +ark-std = { version = "0.3.0", features = ["print-trace"] } +test-log = "0.2.11" +test-case = "3.1.0" +rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } +rand = "0.8" +rand_chacha = "0.3.1" +axiom-eth = { path = "../axiom-eth", features = ["revm"] } + +[features] +default = ["halo2-axiom", "jemallocator", "keygen", "display"] +display = ["axiom-eth/display"] +asm = ["axiom-eth/asm"] +revm = ["axiom-eth/revm"] +halo2-pse = ["axiom-eth/halo2-pse"] +halo2-axiom = ["axiom-eth/halo2-axiom"] +jemallocator = ["axiom-eth/jemallocator"] +keygen = ["axiom-eth/keygen", "dep:clap", "dep:serde_yaml"] diff --git a/axiom-core/KEYGEN.md b/axiom-core/KEYGEN.md new file mode 100644 index 00000000..84e05de7 --- /dev/null +++ b/axiom-core/KEYGEN.md @@ -0,0 +1,64 @@ +# AxiomV2Core ZK Circuits + +# Proving and Verifying Key Generation + +To generate the exact proving and verifying keys we use in production on Ethereum Mainnet, you can do the following: + +1. Download the KZG trusted setup that we use with [this script](../trusted_setup_s3.sh). + +``` +bash ../trusted_setup_s3.sh +``` + +You can read more about the trusted setup we use and how it was generated [here](https://docs.axiom.xyz/docs/transparency-and-security/kzg-trusted-setup). + +The trusted setup will be downloaded to a directory called `params/` by default. You can move the directory elsewhere. We'll refer to the directory as `$SRS_DIR` below. + +2. Install `axiom-core-keygen` binary to your path via: + +```bash +cargo install --path axiom-core --force +``` + +This builds the `axiom-core-keygen` binary in release mode and installs it to your path. +Additional details about the binary can be found [here](./src/bin/README.md). + +3. Generate the proving and verifying keys for the `AxiomV2CoreVerifier` smart contract via: + +```bash +axiom-core-keygen --srs-dir $SRS_DIR --intent configs/production/core.yml --tag v2.0.12 --data-dir $CIRCUIT_DATA_DIR +``` + +where `$CIRCUIT_DATA_DIR` is the directory you want to store the output files. After the process is complete, a summary JSON with the different circuit IDs created will be output to `$CIRCUIT_DATA_DIR/v2.0.12.cids`. + +4. Rename and forge format the Solidity SNARK verifier file for `AxiomV2CoreVerifier`: + +Check that in `$CIRCUIT_DATA_DIR/v2.0.12.cids` the final aggregation circuit with `"node_type": {"Evm":1}` has circuit ID `39cb264c605428fc752e90b6ac1b77427ab06b795419a759e237e283b95f377f`. +Then run + +```bash +bash src/bin/rename_snark_verifier.sh $CIRCUIT_DATA_DIR/39cb264c605428fc752e90b6ac1b77427ab06b795419a759e237e283b95f377f.sol +``` + +The final Solidity file will be output to `AxiomV2CoreVerifier.sol`. + +5. Generate the proving and verifying keys for the `AxiomV2CoreHistoricalVerifier` smart contract via: + +```bash +axiom-core-keygen --srs-dir $SRS_DIR --intent configs/production/core_historical.yml --tag v2.0.12.historical --data-dir $CIRCUIT_DATA_DIR +``` + +where `$CIRCUIT_DATA_DIR` is the directory you want to store the output files. After the process is complete, a summary JSON with the different circuit IDs created will be output to `$CIRCUIT_DATA_DIR/v2.0.12.historical.cids`. + +6. Rename and forge format the Solidity SNARK verifier file for `AxiomV2CoreHistoricalVerifier`: + +Check that in `$CIRCUIT_DATA_DIR/v2.0.12.historical.cids` the final aggregation circuit with `"node_type": {"Evm":1}` has circuit ID `0379c723deafac09822de4f36da40a5595331c447a5cc7c342eb839cd199be02`. +Then run + +```bash +bash src/bin/rename_snark_verifier.sh $CIRCUIT_DATA_DIR/0379c723deafac09822de4f36da40a5595331c447a5cc7c342eb839cd199be02.sol +``` + +The final Solidity file will be output to `AxiomV2CoreHistoricalVerifier.sol`. + +7. Compare the summary JSONs `v2.0.12.cids` and `v2.0.12.historical.cids` with the ones we use in production [here](./data/production/). diff --git a/axiom-core/README.md b/axiom-core/README.md new file mode 100644 index 00000000..0d1c5f9c --- /dev/null +++ b/axiom-core/README.md @@ -0,0 +1,100 @@ +# AxiomV2Core ZK Circuits + +# Proving and Verifying Key Generation + +For instructions on how to generate the exact proving and verifying keys we use in production on Ethereum Mainnet, see [here](./KEYGEN.md). + +# Public Instance Formats + +Any `Snark` has an associated `Vec` of public instances. We describe the format for the ones relevant to the `AxiomV2Core` circuits below. + +## `EthBlockHeaderChainCircuit` + +```rust +pub struct EthBlockHeaderChainInput { + 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 + +## `EthBlockHeaderChainIntermediateAggregationCircuit` + +```rust +pub struct EthBlockHeaderChainIntermediateAggregationInput { + 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 + +## `EthBlockHeaderChainRootAggregationCircuit` + +```rust +pub struct EthBlockHeaderChainRootAggregationInput { + /// See [EthBlockHeaderChainIntermediateAggregationInput] + pub inner: EthBlockHeaderChainIntermediateAggregationInput, + /// Succinct verifying key (generator of KZG trusted setup) should match `inner.snarks` + pub svk: Svk, + prev_acc_indices: Vec>, +} +``` + +This circuit takes two [`EthBlockHeaderChainIntermediateAggregationCircuit`s](#ethblockheaderchainintermediateaggregationcircuit) 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 [`EthBlockHeaderChainIntermediateAggregationCircuit`](#ethblockheaderchainintermediateaggregationcircuit) except that it does do the final Keccaks to form the full Merkle mountain range + +## Passthrough Aggregation Circuit + +This is from [`axiom-eth`](../axiom-eth/src/utils/merkle_aggregation.rs). + +```rust +pub struct InputMerkleAggregation { + pub snarks: Vec, +} +``` + +We will only use this where `snarks` has length 1 and consists of a single snark. In this case it is an `AggregationCircuit` that purely passes through the public instances of the single snark in `snarks`, discarding old accumulators (there is no Merkle root computation because there is only one snark). + +We will use this snark on [`EthBlockHeaderChainRootAggregationCircuit`] or itself if we want multiple rounds of passthrough aggregation. +The public instances are exactly the same as for [`EthBlockHeaderChainRootAggregationCircuit`]. diff --git a/axiom-core/configs/production/core.yml b/axiom-core/configs/production/core.yml new file mode 100644 index 00000000..0f23d9d3 --- /dev/null +++ b/axiom-core/configs/production/core.yml @@ -0,0 +1,8 @@ +# Core configuration used for chains where extra data field has at most 32 bytes +k_at_depth: [23, 22, 19, 21, 22, 19] +max_extra_data_bytes: 32 +params: + node_type: + Evm: 1 + depth: 10 + initial_depth: 7 diff --git a/axiom-core/configs/production/core_historical.yml b/axiom-core/configs/production/core_historical.yml new file mode 100644 index 00000000..768a9723 --- /dev/null +++ b/axiom-core/configs/production/core_historical.yml @@ -0,0 +1,8 @@ +# Core Historical configuration used for chains where extra data field has at most 32 bytes +k_at_depth: [23, 22, 22, 20, 21, 20, 20, 21, 20, 20, 21, 22, 19] +max_extra_data_bytes: 32 +params: + node_type: + Evm: 1 + depth: 17 + initial_depth: 7 diff --git a/axiom-core/configs/tests/mainnet_3.json b/axiom-core/configs/tests/mainnet_3.json new file mode 100644 index 00000000..ea1dd4f6 --- /dev/null +++ b/axiom-core/configs/tests/mainnet_3.json @@ -0,0 +1,52 @@ +[ + { + "rlc": { + "base": { + "k": 16, + "num_advice_per_phase": [ + 17, + 5 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 0, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 1 + }, + "keccak_rows_per_round": 50 + }, + { + "base": [ + [ + 65425, + 65426, + 65425, + 65424, + 65425, + 65424, + 65424, + 65426, + 65426, + 65424, + 65424, + 65425, + 65426, + 65424, + 65426, + 65424 + ], + [ + 65426, + 65426, + 65425, + 65426 + ] + ], + "rlc": [] + } +] \ No newline at end of file diff --git a/axiom-core/configs/tests/mainnet_4_3.json b/axiom-core/configs/tests/mainnet_4_3.json new file mode 100644 index 00000000..aa7e8386 --- /dev/null +++ b/axiom-core/configs/tests/mainnet_4_3.json @@ -0,0 +1,65 @@ +{ + "params": { + "degree": 20, + "num_advice": 53, + "num_lookup_advice": 6, + "num_fixed": 1, + "lookup_bits": 19 + }, + "break_points": [ + [ + 1048564, + 1048566, + 1048565, + 1048564, + 1048566, + 1048564, + 1048565, + 1048565, + 1048566, + 1048564, + 1048566, + 1048564, + 1048565, + 1048565, + 1048566, + 1048564, + 1048565, + 1048566, + 1048564, + 1048565, + 1048566, + 1048564, + 1048564, + 1048566, + 1048565, + 1048564, + 1048565, + 1048564, + 1048566, + 1048565, + 1048564, + 1048566, + 1048566, + 1048566, + 1048565, + 1048566, + 1048566, + 1048565, + 1048565, + 1048566, + 1048564, + 1048565, + 1048566, + 1048564, + 1048564, + 1048566, + 1048565, + 1048564, + 1048566, + 1048565, + 1048566, + 1048566 + ] + ] +} \ No newline at end of file diff --git a/axiom-core/configs/tests/mainnet_5_3_for_evm_0.json b/axiom-core/configs/tests/mainnet_5_3_for_evm_0.json new file mode 100644 index 00000000..f8189914 --- /dev/null +++ b/axiom-core/configs/tests/mainnet_5_3_for_evm_0.json @@ -0,0 +1,16 @@ +{ + "params": { + "degree": 23, + "num_advice": 4, + "num_lookup_advice": 1, + "num_fixed": 1, + "lookup_bits": 22 + }, + "break_points": [ + [ + 8388596, + 8388598, + 8388597 + ] + ] +} \ No newline at end of file diff --git a/axiom-core/configs/tests/mainnet_5_3_root.json b/axiom-core/configs/tests/mainnet_5_3_root.json new file mode 100644 index 00000000..ab1b97ee --- /dev/null +++ b/axiom-core/configs/tests/mainnet_5_3_root.json @@ -0,0 +1,78 @@ +[ + { + "rlc": { + "base": { + "k": 20, + "num_advice_per_phase": [ + 48, + 0 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 6, + 0, + 0 + ], + "lookup_bits": 19, + "num_instance_columns": 1 + }, + "num_rlc_columns": 0 + }, + "keccak_rows_per_round": 50 + }, + { + "base": [ + [ + 1048464, + 1048464, + 1048464, + 1048466, + 1048465, + 1048464, + 1048465, + 1048465, + 1048465, + 1048466, + 1048466, + 1048465, + 1048466, + 1048465, + 1048466, + 1048464, + 1048466, + 1048464, + 1048464, + 1048466, + 1048464, + 1048465, + 1048466, + 1048465, + 1048466, + 1048464, + 1048466, + 1048465, + 1048464, + 1048464, + 1048465, + 1048465, + 1048466, + 1048466, + 1048465, + 1048464, + 1048464, + 1048464, + 1048466, + 1048466, + 1048465, + 1048466, + 1048466, + 1048464, + 1048466, + 1048464, + 1048466 + ], + [] + ], + "rlc": [] + } +] \ No newline at end of file diff --git a/axiom-core/configs/tests/multi_block.json b/axiom-core/configs/tests/multi_block.json new file mode 100644 index 00000000..fb01796c --- /dev/null +++ b/axiom-core/configs/tests/multi_block.json @@ -0,0 +1,21 @@ +{ + "rlc": { + "base": { + "k": 14, + "num_advice_per_phase": [ + 85, + 24 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 0, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 3 + }, + "keccak_rows_per_round": 9 +} \ No newline at end of file diff --git a/axiom-core/data/headers/default_blocks_goerli.json b/axiom-core/data/headers/default_blocks_goerli.json new file mode 100644 index 00000000..00518a11 --- /dev/null +++ b/axiom-core/data/headers/default_blocks_goerli.json @@ -0,0 +1 @@ +["f90201a0ef63eee702e2219e26891b8c72fa973cbe519f4bd78c19032cae103efec5cc86a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347949a4c96c607254304b7dd4fd9e7b8e8eff1b24987a0773ec4e7653f1c526a794e297d370d133f7dec429dc5c0e5942977b4cd2534d5a0b2afdafd93c27329195561ee79935be46524eb6bf5481ceb7cfee2a875a4f2dfa0a1cde4a1783eee23b56b978f02a2c5ebdc14e5dbd37a8b35b8376cb483dedb90b9010000200000a000000041000010800240300000080004000200008080001000100000004000004001000000024900b285000200200008002100000000040020004000000000081020080a00800840000120001000100040000080001200c000100000400000022a000800200000004008000001003000000440000000920008000000000f44200210004008000000002012000a00410160000805002040005080049a000008000828000000408000000802004000000002000a10000400000008400100000211c11804004020080a000400120020c20000001000200926000020000010200018004000000010000001084804400840090c8040000c0011400000048083ef00188401c9c380831f5fab84633a015780a04da2c501a9f653a97d1f5133f2a41b997eda5d44414bf53e724231470f83190e88000000000000000085025a1eb030", "f90202a0cdfc6de4fa65e8834148e2972b4f3fe7fb31e87292279420cc580fea337af6e9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c3dc5292caf0c3350c1be8e5f8c36e10a6b12a6ba0be68bcb1c729dd738041fb6b5cc294f0e981fadba5871a412bc25ee5e37ff53fa0018451d8ca9f462e01ef1d2b60c7120f8328d634542d67d79ec03988523c79d6a069a5c26a65d498f5fb60bb4d16ee7fe014c857eb0a9af211da788491df8f1bb0b90100782dc9cef8923f95df2ca7c8d53c32f7fc9149d7b6ab0b3dee37f55517349d11d0e30dfcd8e4dd90c6d5739e4b5b71ed5be6fcdd7bfebdde876815d2e4a7a77f534ec9c45591e47e7e93ba79b145437856d3881dfdc7bcfa7a843f5bdd77dbccf6aeff16aedfebebe3dfdc16bac8fbade3beb7d12ed856dd72d5c4b5c78a9374bbf5a1345c83d17f9d4ebdc464dc6e0cde13ad056bea51bf7d63ecf079decaf0ef272b8bf58a675a835a68d60f8e6ded972d0dd9ef87cbd20ff4ee151cd0d0a94f7a5f0f2aab1f99f634c4513c6ed6dd956c6b4ad2ab9dfe1f66af5764456f0374bfe2ab9812eab8f27d3594474b1bc9e9aa0ae755dc5fdea2ef8df54b5076218083ef00198401c9c380840166d0a984633a016380a0607768af0a1dcb8872defe2d6df55698bc14541f96fd0c3c304679e88350fa218800000000000000008502192bf7bc", "f90201a00b605392e9cb575f65e8eca04b5922abab689e902d3a84e9e5be6cffb4d6bc9aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ebec795c9c8bbd61ffc14a6662944748f299cacfa00ca3f4178d2000fc1f4fd80444004fc22ab41ab1cba0166a7612daac97324ef8a0e84a8910cbcfb75c40177e9b00e0659ae0c76c00f7c37ee6925f1d99c7effa81a062787c34c84afd1855ff740a0a6042241534c5a9d3e95bb05a90af3f1070bff2b9010074644400818800c019100000801819390010040000000241d011184014120103100295408449080690005e000270018822000403080038000020802001200029121a80202120241ca8c6a00cb88060600100008b0440800054010d8099000800169c410016220046b100120a5c082804230202016a28040100240451028a02040202003000060060405b01802305092503030c011300200868213540213488009b010103480d610724090084014090440520844084900a0228610a2000481100012052020004014142c002000183018844004a40000305148004ef0260902001403926280a1000400400110410040926c1202840014c114820640408405692018083ef001a8401c9c3808353d89384633a016f80a02220fa28956d3d85e17536aab236403c378940caf4281396e0f51fdd0abd610c88000000000000000085023f4a389a", "f90202a0b7b0644d9d638ff691bc8587e309d7b187a3aa0612226e08c1122201b0444b0ba01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944675c7e5baafbffbca748158becba61ef3b0a263a00396dcdc2d575fef611a59c57fcd3b2b63e662e78e4fc9229ad712e2b0b92a3aa0276e529db249a027a49f0bc55db7023fe9134c582a0e4dc3bde8592afe90f803a01a43e8a89b846aef3e634f9f565697ec9797a4c3bcceee77279c2d6cf213037fb9010014a44dc783b01a0899c082b5af5bc73e88d564c7101122249c5101abf419100f556c41e03802992213e9d85c10558b41122de0ef3af4fc496a0f8025d63fe0e306b01aa89631e62cfd0af0b89708617552db82a47c591cb258821fe9e9613ca51783561017aa418f6530ba28d420affd0080a01a0855474e130aaed21c0b4d47caa1c138027b9d45886f0ee08e7dc2b88578fe598bdffbd96b346657f210916cba979d8bd1ab6e8aea86ddfc958b600ca7f5048524b66bab8aef421450e9228c515ca69635fa927bab120880c9a36ea6460f4743c70b94d4118419267d202016c0386d0e7106048049b19b8ceb8c4f40d97a8342c2dd31da2044a9fac1e771ea8083ef001b8401c9c38084011c3aac84633a017b80a097c33781e1172a9c846b0a802360bcf79cdae4df33354a4c62411a0c0ca64614880000000000000000850211b8ca74", "f90201a08bc659a65f400f8c6a631d97070b4c0fbad6ff08584292064aa14620de78dc13a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794e887312c0595a10ac88e32ebb8e9f660ad9ab7f7a0ea7346c2e40fd6468c0921b5a9b4f453b616e3b16fe2b750122947be1d56465aa017f4a27edb59532d3af4937ebdac534099c26afb668649e182fca991e950d0bca01b00c5e3d93447d9e599fb170e9d89c19c0c64daa15deade2e3ad0d0c4cc7ed5b9010054244b8c61a03004510c250ca0017e721859a0170d0d02a8268d003a445857014a00012009afc01540521bba014111e04b31c00c4a8839800240e61c8034234169240000105228284dc3f808022315e133190001845a002470a2866e834e4400807805c0525e04080d8740d011884d5581902545140dc4553770a034e5180840289385030383550006d9214886040520668308514352950a8260244808b088218a00321c4a2a34020b9778e46c78c84856000f830d24e02ad0020a028480122901844012002269144000a0923f0a384551312941e01900108102092610e0a11391b83002203000100a425c3080004804225c864a907e76e0085688501b2b58438083ef001c8401c9c38083fa4cba84633a018780a0d892d2d15eb3d2f4c45975862e9fd5feef3d2d2d65dc3c08f44ea09c66ea4b7d880000000000000000850221bbd79f", "f90202a027a24f79d481146399714de497b2b59d5a49b1253171d4a67d31b6655b914e43a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794388c818ca8b9251b393131c08a736a67ccb19297a0438bd05d75e4fdc5bc23296797b5e114485469a501cf14863c8a5e2a13571538a04d5b55140915df796e38e20b4161ce8eda4ba6c47fc11246d096d595b7f8425da0c98005a46319f4c4c51407707615675f53db6afe0a4623101f2d3258bfa5d484b90100fdefbdace19e7df6a1a096b79b7915fb30d4977d87750f61d377d991dfdbf882f596ec7e5dbee96e8e9a6c1d629d396723fad3d7ce8ffcf5c1a4dfbf78f3ae6753fffc858e75eceefbe7fe7fb34d7dbfa3f19feffe539c6f5e95b96fb9b57dfc1f7cc5282fe6e2dbf7d3df4718f87dfdf793defdfd59d5479574e4f172fe43371e26f372696fd6fdf7ff5a5365e2e2af2ed7fff9cfe1b3dbe4113deff6d39e50fbfab17fdcfefd31bd8dcbe2dfb1edec777f8eefa89bfaeeff7d9e3fdcb9fa55cf593eee3abadbddd25c9df3bffbffedf57cbfebbebb8f7eda3dcde77f7eba7fc7797f7af82380fd16be77b7ff943ffce67efe6ff3bed1f470ed39f777e33dab8083ef001d8401c9c3808401a6354384633a019380a0323dc6ec374f7604527d417ca3f6e0bfac35edb08307e52ac7cc47ccb4e87c388800000000000000008502281e01d9", "f90201a0a752af7104be6e4a4da95857896ea320c4b6930561764ac34a4eb81f4afecb61a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794bc31c592076a72a5f07cf11a28dffb396cac5fa2a0c53306e9a3ee989d941b4cee960b11374aa1e636dfc7157913ed070aab335510a0297d2659fc2d4492e4b7180f4efb67334b725b909dfdf7608fde6911690e87c1a0687ee2baa2600bcea4e04bc09e3417d05e1ce0149bc630c818f87e319c39cf21b9010040240001802000000080001180000330400000010002020080910020000200000010004000008400800140800031c5000210500408803400020000002024000802204000108410082a022049018000282000080041444800000008518100040082010080131308020300840002012804001030070400440000010110100800001200001003220040804a000000000004020b000101821208028000c0805010408a024083411c201401100980101c0040040840000000000202440044008000004850000200801100004000020a002080820000400c01283400000d0b41086000c0103028000000000000000002002808000040404009004202048040800100208083ef001e8401c9c38083321b2e84633a019f80a025fd89821d1a92234a46b1b2fd7048ce6d74178c4dce3b5fdf94d2562cec67e78800000000000000008502626929e8"] \ No newline at end of file diff --git a/axiom-core/data/headers/default_hashes_goerli.json b/axiom-core/data/headers/default_hashes_goerli.json new file mode 100644 index 00000000..7562ca94 --- /dev/null +++ b/axiom-core/data/headers/default_hashes_goerli.json @@ -0,0 +1 @@ +["ef63eee702e2219e26891b8c72fa973cbe519f4bd78c19032cae103efec5cc86", "7dfb9051601adbb35d4ea79205be6b2e2a20adbaaf4fcce666932e156df28714", "0xef0018", "0xef001e", ["0000000000000000000000000000000000000000000000000000000000000000", "ca52e1f08d51cbd109db65625c789e7a514c2d8f06852ec947bdc2219dd5ee40", "06975bda9ac5799eb9091293edeffb982101f20f06ffc809ac9c93c6b2497617", "7dfb9051601adbb35d4ea79205be6b2e2a20adbaaf4fcce666932e156df28714"]] \ No newline at end of file diff --git a/axiom-core/data/headers/mainnet_10_7_for_evm_1.yul b/axiom-core/data/headers/mainnet_10_7_for_evm_1.yul new file mode 100644 index 00000000..4fa2368e --- /dev/null +++ b/axiom-core/data/headers/mainnet_10_7_for_evm_1.yul @@ -0,0 +1,1500 @@ + + 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(0x0, 12714566557495233314129647473762182141380482669824660892726865342775293934986) + + { + let x := calldataload(0x4e0) + mstore(0x500, x) + let y := calldataload(0x500) + mstore(0x520, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x540, keccak256(0x0, 1344)) +{ + let hash := mload(0x540) + mstore(0x560, mod(hash, f_q)) + mstore(0x580, hash) + } + + { + let x := calldataload(0x520) + mstore(0x5a0, x) + let y := calldataload(0x540) + mstore(0x5c0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x560) + mstore(0x5e0, x) + let y := calldataload(0x580) + mstore(0x600, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x620, keccak256(0x580, 160)) +{ + let hash := mload(0x620) + mstore(0x640, mod(hash, f_q)) + mstore(0x660, hash) + } +mstore8(1664, 1) +mstore(0x680, keccak256(0x660, 33)) +{ + let hash := mload(0x680) + mstore(0x6a0, mod(hash, f_q)) + mstore(0x6c0, hash) + } + + { + 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(0x6c0, 224)) +{ + 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) + } + + { + let x := calldataload(0x720) + mstore(0x8c0, x) + let y := calldataload(0x740) + mstore(0x8e0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x900, keccak256(0x7e0, 288)) +{ + let hash := mload(0x900) + mstore(0x920, mod(hash, f_q)) + mstore(0x940, hash) + } +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, keccak256(0x940, 640)) +{ + let hash := mload(0xbc0) + mstore(0xbe0, mod(hash, f_q)) + mstore(0xc00, hash) + } +mstore8(3104, 1) +mstore(0xc20, keccak256(0xc00, 33)) +{ + let hash := mload(0xc20) + mstore(0xc40, mod(hash, f_q)) + mstore(0xc60, hash) + } + + { + let x := calldataload(0x9c0) + mstore(0xc80, x) + let y := calldataload(0x9e0) + mstore(0xca0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0xcc0, keccak256(0xc60, 96)) +{ + let hash := mload(0xcc0) + mstore(0xce0, mod(hash, f_q)) + mstore(0xd00, hash) + } + + { + let x := calldataload(0xa00) + mstore(0xd20, x) + let y := calldataload(0xa20) + mstore(0xd40, 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(3424, x) +let y := mload(0x80) +y := add(y, shl(88, mload(0xa0))) +y := add(y, shl(176, mload(0xc0))) +mstore(3456, 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(3488, x) +let y := mload(0x140) +y := add(y, shl(88, mload(0x160))) +y := add(y, shl(176, mload(0x180))) +mstore(3520, y) + + success := and(validate_ec_point(x, y), success) + } +mstore(0xde0, mulmod(mload(0x920), mload(0x920), f_q)) +mstore(0xe00, mulmod(mload(0xde0), mload(0xde0), f_q)) +mstore(0xe20, mulmod(mload(0xe00), mload(0xe00), f_q)) +mstore(0xe40, mulmod(mload(0xe20), mload(0xe20), f_q)) +mstore(0xe60, mulmod(mload(0xe40), mload(0xe40), f_q)) +mstore(0xe80, mulmod(mload(0xe60), mload(0xe60), f_q)) +mstore(0xea0, mulmod(mload(0xe80), mload(0xe80), f_q)) +mstore(0xec0, mulmod(mload(0xea0), mload(0xea0), f_q)) +mstore(0xee0, mulmod(mload(0xec0), mload(0xec0), f_q)) +mstore(0xf00, mulmod(mload(0xee0), mload(0xee0), f_q)) +mstore(0xf20, mulmod(mload(0xf00), mload(0xf00), f_q)) +mstore(0xf40, mulmod(mload(0xf20), mload(0xf20), f_q)) +mstore(0xf60, mulmod(mload(0xf40), mload(0xf40), f_q)) +mstore(0xf80, mulmod(mload(0xf60), mload(0xf60), f_q)) +mstore(0xfa0, mulmod(mload(0xf80), mload(0xf80), 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, addmod(mload(0x10a0), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q)) +mstore(0x10e0, mulmod(mload(0x10c0), 21888240262557392955334514970720457388010314637169927192662615958087340972065, f_q)) +mstore(0x1100, mulmod(mload(0x10e0), 4506835738822104338668100540817374747935106310012997856968187171738630203507, f_q)) +mstore(0x1120, addmod(mload(0x920), 17381407133017170883578305204439900340613258090403036486730017014837178292110, f_q)) +mstore(0x1140, mulmod(mload(0x10e0), 21710372849001950800533397158415938114909991150039389063546734567764856596059, f_q)) +mstore(0x1160, addmod(mload(0x920), 177870022837324421713008586841336973638373250376645280151469618810951899558, f_q)) +mstore(0x1180, mulmod(mload(0x10e0), 1887003188133998471169152042388914354640772748308168868301418279904560637395, f_q)) +mstore(0x11a0, addmod(mload(0x920), 20001239683705276751077253702868360733907591652107865475396785906671247858222, f_q)) +mstore(0x11c0, mulmod(mload(0x10e0), 2785514556381676080176937710880804108647911392478702105860685610379369825016, f_q)) +mstore(0x11e0, addmod(mload(0x920), 19102728315457599142069468034376470979900453007937332237837518576196438670601, f_q)) +mstore(0x1200, mulmod(mload(0x10e0), 14655294445420895451632927078981340937842238432098198055057679026789553137428, f_q)) +mstore(0x1220, addmod(mload(0x920), 7232948426418379770613478666275934150706125968317836288640525159786255358189, f_q)) +mstore(0x1240, mulmod(mload(0x10e0), 8734126352828345679573237859165904705806588461301144420590422589042130041188, f_q)) +mstore(0x1260, addmod(mload(0x920), 13154116519010929542673167886091370382741775939114889923107781597533678454429, f_q)) +mstore(0x1280, mulmod(mload(0x10e0), 9741553891420464328295280489650144566903017206473301385034033384879943874347, f_q)) +mstore(0x12a0, addmod(mload(0x920), 12146688980418810893951125255607130521645347193942732958664170801695864621270, f_q)) +mstore(0x12c0, mulmod(mload(0x10e0), 1, f_q)) +mstore(0x12e0, addmod(mload(0x920), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q)) +mstore(0x1300, mulmod(mload(0x10e0), 8374374965308410102411073611984011876711565317741801500439755773472076597347, f_q)) +mstore(0x1320, addmod(mload(0x920), 13513867906530865119835332133273263211836799082674232843258448413103731898270, f_q)) +mstore(0x1340, mulmod(mload(0x10e0), 11211301017135681023579411905410872569206244553457844956874280139879520583390, f_q)) +mstore(0x1360, addmod(mload(0x920), 10676941854703594198666993839846402519342119846958189386823924046696287912227, f_q)) +mstore(0x1380, mulmod(mload(0x10e0), 3615478808282855240548287271348143516886772452944084747768312988864436725401, f_q)) +mstore(0x13a0, addmod(mload(0x920), 18272764063556419981698118473909131571661591947471949595929891197711371770216, f_q)) +mstore(0x13c0, mulmod(mload(0x10e0), 1426404432721484388505361748317961535523355871255605456897797744433766488507, f_q)) +mstore(0x13e0, addmod(mload(0x920), 20461838439117790833741043996939313553025008529160428886800406442142042007110, f_q)) +mstore(0x1400, mulmod(mload(0x10e0), 216092043779272773661818549620449970334216366264741118684015851799902419467, f_q)) +mstore(0x1420, addmod(mload(0x920), 21672150828060002448584587195636825118214148034151293225014188334775906076150, f_q)) +mstore(0x1440, mulmod(mload(0x10e0), 12619617507853212586156872920672483948819476989779550311307282715684870266992, f_q)) +mstore(0x1460, addmod(mload(0x920), 9268625363986062636089532824584791139728887410636484032390921470890938228625, f_q)) +mstore(0x1480, mulmod(mload(0x10e0), 18610195890048912503953886742825279624920778288956610528523679659246523534888, f_q)) +mstore(0x14a0, addmod(mload(0x920), 3278046981790362718292519002431995463627586111459423815174524527329284960729, f_q)) +mstore(0x14c0, mulmod(mload(0x10e0), 19032961837237948602743626455740240236231119053033140765040043513661803148152, f_q)) +mstore(0x14e0, addmod(mload(0x920), 2855281034601326619502779289517034852317245347382893578658160672914005347465, f_q)) +mstore(0x1500, mulmod(mload(0x10e0), 14875928112196239563830800280253496262679717528621719058794366823499719730250, f_q)) +mstore(0x1520, addmod(mload(0x920), 7012314759643035658415605465003778825868646871794315284903837363076088765367, f_q)) +mstore(0x1540, mulmod(mload(0x10e0), 915149353520972163646494413843788069594022902357002628455555785223409501882, f_q)) +mstore(0x1560, addmod(mload(0x920), 20973093518318303058599911331413487018954341498059031715242648401352398993735, f_q)) +mstore(0x1580, mulmod(mload(0x10e0), 5522161504810533295870699551020523636289972223872138525048055197429246400245, f_q)) +mstore(0x15a0, addmod(mload(0x920), 16366081367028741926375706194236751452258392176543895818650148989146562095372, f_q)) +mstore(0x15c0, mulmod(mload(0x10e0), 3766081621734395783232337525162072736827576297943013392955872170138036189193, f_q)) +mstore(0x15e0, addmod(mload(0x920), 18122161250104879439014068220095202351720788102473020950742332016437772306424, f_q)) +mstore(0x1600, mulmod(mload(0x10e0), 9100833993744738801214480881117348002768153232283708533639316963648253510584, f_q)) +mstore(0x1620, addmod(mload(0x920), 12787408878094536421031924864139927085780211168132325810058887222927554985033, f_q)) +mstore(0x1640, mulmod(mload(0x10e0), 4245441013247250116003069945606352967193023389718465410501109428393342802981, f_q)) +mstore(0x1660, addmod(mload(0x920), 17642801858592025106243335799650922121355341010697568933197094758182465692636, f_q)) +mstore(0x1680, mulmod(mload(0x10e0), 6132660129994545119218258312491950835441607143741804980633129304664017206141, f_q)) +mstore(0x16a0, addmod(mload(0x920), 15755582741844730103028147432765324253106757256674229363065074881911791289476, f_q)) +mstore(0x16c0, mulmod(mload(0x10e0), 5854133144571823792863860130267644613802765696134002830362054821530146160770, f_q)) +mstore(0x16e0, addmod(mload(0x920), 16034109727267451429382545614989630474745598704282031513336149365045662334847, f_q)) +mstore(0x1700, mulmod(mload(0x10e0), 515148244606945972463850631189471072103916690263705052318085725998468254533, f_q)) +mstore(0x1720, addmod(mload(0x920), 21373094627232329249782555114067804016444447710152329291380118460577340241084, f_q)) +mstore(0x1740, mulmod(mload(0x10e0), 5980488956150442207659150513163747165544364597008566989111579977672498964212, f_q)) +mstore(0x1760, addmod(mload(0x920), 15907753915688833014587255232093527923003999803407467354586624208903309531405, f_q)) +mstore(0x1780, mulmod(mload(0x10e0), 5223738580615264174925218065001555728265216895679471490312087802465486318994, f_q)) +mstore(0x17a0, addmod(mload(0x920), 16664504291224011047321187680255719360283147504736562853386116384110322176623, f_q)) +mstore(0x17c0, mulmod(mload(0x10e0), 14557038802599140430182096396825290815503940951075961210638273254419942783582, f_q)) +mstore(0x17e0, addmod(mload(0x920), 7331204069240134792064309348431984273044423449340073133059930932155865712035, f_q)) +mstore(0x1800, mulmod(mload(0x10e0), 16976236069879939850923145256911338076234942200101755618884183331004076579046, f_q)) +mstore(0x1820, addmod(mload(0x920), 4912006801959335371323260488345937012313422200314278724814020855571731916571, f_q)) +mstore(0x1840, mulmod(mload(0x10e0), 13553911191894110065493137367144919847521088405945523452288398666974237857208, f_q)) +mstore(0x1860, addmod(mload(0x920), 8334331679945165156753268378112355241027275994470510891409805519601570638409, f_q)) +mstore(0x1880, mulmod(mload(0x10e0), 12222687719926148270818604386979005738180875192307070468454582955273533101023, f_q)) +mstore(0x18a0, addmod(mload(0x920), 9665555151913126951427801358278269350367489208108963875243621231302275394594, f_q)) +mstore(0x18c0, mulmod(mload(0x10e0), 9697063347556872083384215826199993067635178715531258559890418744774301211662, f_q)) +mstore(0x18e0, addmod(mload(0x920), 12191179524282403138862189919057282020913185684884775783807785441801507283955, f_q)) +mstore(0x1900, mulmod(mload(0x10e0), 13783318220968413117070077848579881425001701814458176881760898225529300547844, f_q)) +mstore(0x1920, addmod(mload(0x920), 8104924650870862105176327896677393663546662585957857461937305961046507947773, f_q)) +mstore(0x1940, mulmod(mload(0x10e0), 10807735674816066981985242612061336605021639643453679977988966079770672437131, f_q)) +mstore(0x1960, addmod(mload(0x920), 11080507197023208240261163133195938483526724756962354365709238106805136058486, f_q)) +mstore(0x1980, mulmod(mload(0x10e0), 15487660954688013862248478071816391715224351867581977083810729441220383572585, f_q)) +mstore(0x19a0, addmod(mload(0x920), 6400581917151261359997927673440883373324012532834057259887474745355424923032, f_q)) +mstore(0x19c0, mulmod(mload(0x10e0), 12459868075641381822485233712013080087763946065665469821362892189399541605692, f_q)) +mstore(0x19e0, addmod(mload(0x920), 9428374796197893399761172033244195000784418334750564522335311997176266889925, f_q)) +mstore(0x1a00, mulmod(mload(0x10e0), 12562571400845953139885120066983392294851269266041089223701347829190217414825, f_q)) +mstore(0x1a20, addmod(mload(0x920), 9325671470993322082361285678273882793697095134374945119996856357385591080792, f_q)) +mstore(0x1a40, mulmod(mload(0x10e0), 16038300751658239075779628684257016433412502747804121525056508685985277092575, f_q)) +mstore(0x1a60, addmod(mload(0x920), 5849942120181036146466777061000258655135861652611912818641695500590531403042, f_q)) +mstore(0x1a80, mulmod(mload(0x10e0), 17665522928519859765452767154433594409738037332395989540221744312194874941704, f_q)) +mstore(0x1aa0, addmod(mload(0x920), 4222719943319415456793638590823680678810327068020044803476459874380933553913, f_q)) +mstore(0x1ac0, mulmod(mload(0x10e0), 6955697244493336113861667751840378876927906302623587437721024018233754910398, f_q)) +mstore(0x1ae0, addmod(mload(0x920), 14932545627345939108384737993416896211620458097792446905977180168342053585219, f_q)) +mstore(0x1b00, mulmod(mload(0x10e0), 1918679275621049296283934091410967415474987212511681231948800935495808101054, f_q)) +mstore(0x1b20, addmod(mload(0x920), 19969563596218225925962471653846307673073377187904353111749403251080000394563, f_q)) +mstore(0x1b40, mulmod(mload(0x10e0), 13498745591877810872211159461644682954739332524336278910448604883789771736885, f_q)) +mstore(0x1b60, addmod(mload(0x920), 8389497279961464350035246283612592133809031876079755433249599302786036758732, f_q)) +mstore(0x1b80, mulmod(mload(0x10e0), 6604851689411953560355663038203889299997924520355363678860500374111951937637, f_q)) +mstore(0x1ba0, addmod(mload(0x920), 15283391182427321661890742707053385788550439880060670664837703812463856557980, f_q)) +mstore(0x1bc0, mulmod(mload(0x10e0), 20345677989844117909528750049476969581182118546166966482506114734614108237981, f_q)) +mstore(0x1be0, addmod(mload(0x920), 1542564881995157312717655695780305507366245854249067861192089451961700257636, f_q)) +mstore(0x1c00, mulmod(mload(0x10e0), 11244009323710436498447061620026171700033960328162115124806024297270121927878, f_q)) +mstore(0x1c20, addmod(mload(0x920), 10644233548128838723799344125231103388514404072253919218892179889305686567739, f_q)) +mstore(0x1c40, mulmod(mload(0x10e0), 790608022292213379425324383664216541739009722347092850716054055768832299157, f_q)) +mstore(0x1c60, addmod(mload(0x920), 21097634849547061842821081361593058546809354678068941492982150130806976196460, f_q)) +{ + let prod := mload(0x1120) + + prod := mulmod(mload(0x1160), prod, f_q) + mstore(0x1c80, prod) + + prod := mulmod(mload(0x11a0), prod, f_q) + mstore(0x1ca0, prod) + + prod := mulmod(mload(0x11e0), prod, f_q) + mstore(0x1cc0, prod) + + prod := mulmod(mload(0x1220), prod, f_q) + mstore(0x1ce0, prod) + + prod := mulmod(mload(0x1260), prod, f_q) + mstore(0x1d00, prod) + + prod := mulmod(mload(0x12a0), prod, f_q) + mstore(0x1d20, prod) + + prod := mulmod(mload(0x12e0), prod, f_q) + mstore(0x1d40, prod) + + prod := mulmod(mload(0x1320), prod, f_q) + mstore(0x1d60, prod) + + prod := mulmod(mload(0x1360), prod, f_q) + mstore(0x1d80, prod) + + prod := mulmod(mload(0x13a0), prod, f_q) + mstore(0x1da0, prod) + + prod := mulmod(mload(0x13e0), prod, f_q) + mstore(0x1dc0, prod) + + prod := mulmod(mload(0x1420), prod, f_q) + mstore(0x1de0, prod) + + prod := mulmod(mload(0x1460), prod, f_q) + mstore(0x1e00, prod) + + prod := mulmod(mload(0x14a0), prod, f_q) + mstore(0x1e20, prod) + + prod := mulmod(mload(0x14e0), prod, f_q) + mstore(0x1e40, prod) + + prod := mulmod(mload(0x1520), prod, f_q) + mstore(0x1e60, prod) + + prod := mulmod(mload(0x1560), prod, f_q) + mstore(0x1e80, prod) + + prod := mulmod(mload(0x15a0), prod, f_q) + mstore(0x1ea0, prod) + + prod := mulmod(mload(0x15e0), prod, f_q) + mstore(0x1ec0, prod) + + prod := mulmod(mload(0x1620), prod, f_q) + mstore(0x1ee0, prod) + + prod := mulmod(mload(0x1660), prod, f_q) + mstore(0x1f00, prod) + + prod := mulmod(mload(0x16a0), prod, f_q) + mstore(0x1f20, prod) + + prod := mulmod(mload(0x16e0), prod, f_q) + mstore(0x1f40, prod) + + prod := mulmod(mload(0x1720), prod, f_q) + mstore(0x1f60, prod) + + prod := mulmod(mload(0x1760), prod, f_q) + mstore(0x1f80, prod) + + prod := mulmod(mload(0x17a0), prod, f_q) + mstore(0x1fa0, prod) + + prod := mulmod(mload(0x17e0), prod, f_q) + mstore(0x1fc0, prod) + + prod := mulmod(mload(0x1820), prod, f_q) + mstore(0x1fe0, prod) + + prod := mulmod(mload(0x1860), prod, f_q) + mstore(0x2000, prod) + + prod := mulmod(mload(0x18a0), prod, f_q) + mstore(0x2020, prod) + + prod := mulmod(mload(0x18e0), prod, f_q) + mstore(0x2040, prod) + + prod := mulmod(mload(0x1920), prod, f_q) + mstore(0x2060, prod) + + prod := mulmod(mload(0x1960), prod, f_q) + mstore(0x2080, prod) + + prod := mulmod(mload(0x19a0), prod, f_q) + mstore(0x20a0, prod) + + prod := mulmod(mload(0x19e0), prod, f_q) + mstore(0x20c0, prod) + + prod := mulmod(mload(0x1a20), prod, f_q) + mstore(0x20e0, prod) + + prod := mulmod(mload(0x1a60), prod, f_q) + mstore(0x2100, prod) + + prod := mulmod(mload(0x1aa0), prod, f_q) + mstore(0x2120, prod) + + prod := mulmod(mload(0x1ae0), prod, f_q) + mstore(0x2140, prod) + + prod := mulmod(mload(0x1b20), prod, f_q) + mstore(0x2160, prod) + + prod := mulmod(mload(0x1b60), prod, f_q) + mstore(0x2180, prod) + + prod := mulmod(mload(0x1ba0), prod, f_q) + mstore(0x21a0, prod) + + prod := mulmod(mload(0x1be0), prod, f_q) + mstore(0x21c0, prod) + + prod := mulmod(mload(0x1c20), prod, f_q) + mstore(0x21e0, prod) + + prod := mulmod(mload(0x1c60), prod, f_q) + mstore(0x2200, prod) + + prod := mulmod(mload(0x10c0), prod, f_q) + mstore(0x2220, prod) + + } +mstore(0x2260, 32) +mstore(0x2280, 32) +mstore(0x22a0, 32) +mstore(0x22c0, mload(0x2220)) +mstore(0x22e0, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x2300, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x2260, 0xc0, 0x2240, 0x20), 1), success) +{ + + let inv := mload(0x2240) + let v + + v := mload(0x10c0) + mstore(4288, mulmod(mload(0x2200), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1c60) + mstore(7264, mulmod(mload(0x21e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1c20) + mstore(7200, mulmod(mload(0x21c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1be0) + mstore(7136, mulmod(mload(0x21a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1ba0) + mstore(7072, mulmod(mload(0x2180), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1b60) + mstore(7008, mulmod(mload(0x2160), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1b20) + mstore(6944, mulmod(mload(0x2140), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1ae0) + mstore(6880, mulmod(mload(0x2120), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1aa0) + mstore(6816, mulmod(mload(0x2100), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1a60) + mstore(6752, mulmod(mload(0x20e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1a20) + mstore(6688, mulmod(mload(0x20c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x19e0) + mstore(6624, mulmod(mload(0x20a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x19a0) + mstore(6560, mulmod(mload(0x2080), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1960) + mstore(6496, mulmod(mload(0x2060), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1920) + mstore(6432, mulmod(mload(0x2040), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x18e0) + mstore(6368, mulmod(mload(0x2020), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x18a0) + mstore(6304, mulmod(mload(0x2000), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1860) + mstore(6240, mulmod(mload(0x1fe0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1820) + mstore(6176, mulmod(mload(0x1fc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x17e0) + mstore(6112, mulmod(mload(0x1fa0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x17a0) + mstore(6048, mulmod(mload(0x1f80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1760) + mstore(5984, mulmod(mload(0x1f60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1720) + mstore(5920, mulmod(mload(0x1f40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x16e0) + mstore(5856, mulmod(mload(0x1f20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x16a0) + mstore(5792, mulmod(mload(0x1f00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1660) + mstore(5728, mulmod(mload(0x1ee0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1620) + mstore(5664, mulmod(mload(0x1ec0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x15e0) + mstore(5600, mulmod(mload(0x1ea0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x15a0) + mstore(5536, mulmod(mload(0x1e80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1560) + mstore(5472, mulmod(mload(0x1e60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1520) + mstore(5408, mulmod(mload(0x1e40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x14e0) + mstore(5344, mulmod(mload(0x1e20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x14a0) + mstore(5280, mulmod(mload(0x1e00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1460) + mstore(5216, mulmod(mload(0x1de0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1420) + mstore(5152, mulmod(mload(0x1dc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x13e0) + mstore(5088, mulmod(mload(0x1da0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x13a0) + mstore(5024, mulmod(mload(0x1d80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1360) + mstore(4960, mulmod(mload(0x1d60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1320) + mstore(4896, mulmod(mload(0x1d40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x12e0) + mstore(4832, mulmod(mload(0x1d20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x12a0) + mstore(4768, mulmod(mload(0x1d00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1260) + mstore(4704, mulmod(mload(0x1ce0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1220) + mstore(4640, mulmod(mload(0x1cc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x11e0) + mstore(4576, mulmod(mload(0x1ca0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x11a0) + mstore(4512, mulmod(mload(0x1c80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1160) + mstore(4448, mulmod(mload(0x1120), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x1120, inv) + + } +mstore(0x2320, mulmod(mload(0x1100), mload(0x1120), f_q)) +mstore(0x2340, mulmod(mload(0x1140), mload(0x1160), f_q)) +mstore(0x2360, mulmod(mload(0x1180), mload(0x11a0), f_q)) +mstore(0x2380, mulmod(mload(0x11c0), mload(0x11e0), f_q)) +mstore(0x23a0, mulmod(mload(0x1200), mload(0x1220), f_q)) +mstore(0x23c0, mulmod(mload(0x1240), mload(0x1260), f_q)) +mstore(0x23e0, mulmod(mload(0x1280), mload(0x12a0), f_q)) +mstore(0x2400, mulmod(mload(0x12c0), mload(0x12e0), f_q)) +mstore(0x2420, mulmod(mload(0x1300), mload(0x1320), f_q)) +mstore(0x2440, mulmod(mload(0x1340), mload(0x1360), f_q)) +mstore(0x2460, mulmod(mload(0x1380), mload(0x13a0), f_q)) +mstore(0x2480, mulmod(mload(0x13c0), mload(0x13e0), f_q)) +mstore(0x24a0, mulmod(mload(0x1400), mload(0x1420), f_q)) +mstore(0x24c0, mulmod(mload(0x1440), mload(0x1460), f_q)) +mstore(0x24e0, mulmod(mload(0x1480), mload(0x14a0), f_q)) +mstore(0x2500, mulmod(mload(0x14c0), mload(0x14e0), f_q)) +mstore(0x2520, mulmod(mload(0x1500), mload(0x1520), f_q)) +mstore(0x2540, mulmod(mload(0x1540), mload(0x1560), f_q)) +mstore(0x2560, mulmod(mload(0x1580), mload(0x15a0), f_q)) +mstore(0x2580, mulmod(mload(0x15c0), mload(0x15e0), f_q)) +mstore(0x25a0, mulmod(mload(0x1600), mload(0x1620), f_q)) +mstore(0x25c0, mulmod(mload(0x1640), mload(0x1660), f_q)) +mstore(0x25e0, mulmod(mload(0x1680), mload(0x16a0), f_q)) +mstore(0x2600, mulmod(mload(0x16c0), mload(0x16e0), f_q)) +mstore(0x2620, mulmod(mload(0x1700), mload(0x1720), f_q)) +mstore(0x2640, mulmod(mload(0x1740), mload(0x1760), f_q)) +mstore(0x2660, mulmod(mload(0x1780), mload(0x17a0), f_q)) +mstore(0x2680, mulmod(mload(0x17c0), mload(0x17e0), f_q)) +mstore(0x26a0, mulmod(mload(0x1800), mload(0x1820), f_q)) +mstore(0x26c0, mulmod(mload(0x1840), mload(0x1860), f_q)) +mstore(0x26e0, mulmod(mload(0x1880), mload(0x18a0), f_q)) +mstore(0x2700, mulmod(mload(0x18c0), mload(0x18e0), f_q)) +mstore(0x2720, mulmod(mload(0x1900), mload(0x1920), f_q)) +mstore(0x2740, mulmod(mload(0x1940), mload(0x1960), f_q)) +mstore(0x2760, mulmod(mload(0x1980), mload(0x19a0), f_q)) +mstore(0x2780, mulmod(mload(0x19c0), mload(0x19e0), f_q)) +mstore(0x27a0, mulmod(mload(0x1a00), mload(0x1a20), f_q)) +mstore(0x27c0, mulmod(mload(0x1a40), mload(0x1a60), f_q)) +mstore(0x27e0, mulmod(mload(0x1a80), mload(0x1aa0), f_q)) +mstore(0x2800, mulmod(mload(0x1ac0), mload(0x1ae0), f_q)) +mstore(0x2820, mulmod(mload(0x1b00), mload(0x1b20), f_q)) +mstore(0x2840, mulmod(mload(0x1b40), mload(0x1b60), f_q)) +mstore(0x2860, mulmod(mload(0x1b80), mload(0x1ba0), f_q)) +mstore(0x2880, mulmod(mload(0x1bc0), mload(0x1be0), f_q)) +mstore(0x28a0, mulmod(mload(0x1c00), mload(0x1c20), f_q)) +mstore(0x28c0, mulmod(mload(0x1c40), mload(0x1c60), f_q)) +{ + let result := mulmod(mload(0x2400), mload(0x20), f_q) +result := addmod(mulmod(mload(0x2420), mload(0x40), f_q), result, f_q) +result := addmod(mulmod(mload(0x2440), mload(0x60), f_q), result, f_q) +result := addmod(mulmod(mload(0x2460), mload(0x80), f_q), result, f_q) +result := addmod(mulmod(mload(0x2480), mload(0xa0), f_q), result, f_q) +result := addmod(mulmod(mload(0x24a0), mload(0xc0), f_q), result, f_q) +result := addmod(mulmod(mload(0x24c0), mload(0xe0), f_q), result, f_q) +result := addmod(mulmod(mload(0x24e0), mload(0x100), f_q), result, f_q) +result := addmod(mulmod(mload(0x2500), mload(0x120), f_q), result, f_q) +result := addmod(mulmod(mload(0x2520), mload(0x140), f_q), result, f_q) +result := addmod(mulmod(mload(0x2540), mload(0x160), f_q), result, f_q) +result := addmod(mulmod(mload(0x2560), mload(0x180), f_q), result, f_q) +result := addmod(mulmod(mload(0x2580), mload(0x1a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x25a0), mload(0x1c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x25c0), mload(0x1e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x25e0), mload(0x200), f_q), result, f_q) +result := addmod(mulmod(mload(0x2600), mload(0x220), f_q), result, f_q) +result := addmod(mulmod(mload(0x2620), mload(0x240), f_q), result, f_q) +result := addmod(mulmod(mload(0x2640), mload(0x260), f_q), result, f_q) +result := addmod(mulmod(mload(0x2660), mload(0x280), f_q), result, f_q) +result := addmod(mulmod(mload(0x2680), mload(0x2a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x26a0), mload(0x2c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x26c0), mload(0x2e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x26e0), mload(0x300), f_q), result, f_q) +result := addmod(mulmod(mload(0x2700), mload(0x320), f_q), result, f_q) +result := addmod(mulmod(mload(0x2720), mload(0x340), f_q), result, f_q) +result := addmod(mulmod(mload(0x2740), mload(0x360), f_q), result, f_q) +result := addmod(mulmod(mload(0x2760), mload(0x380), f_q), result, f_q) +result := addmod(mulmod(mload(0x2780), mload(0x3a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x27a0), mload(0x3c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x27c0), mload(0x3e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x27e0), mload(0x400), f_q), result, f_q) +result := addmod(mulmod(mload(0x2800), mload(0x420), f_q), result, f_q) +result := addmod(mulmod(mload(0x2820), mload(0x440), f_q), result, f_q) +result := addmod(mulmod(mload(0x2840), mload(0x460), f_q), result, f_q) +result := addmod(mulmod(mload(0x2860), mload(0x480), f_q), result, f_q) +result := addmod(mulmod(mload(0x2880), mload(0x4a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x28a0), mload(0x4c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x28c0), mload(0x4e0), f_q), result, f_q) +mstore(10464, result) + } +mstore(0x2900, mulmod(mload(0x9a0), mload(0x980), f_q)) +mstore(0x2920, addmod(mload(0x960), mload(0x2900), f_q)) +mstore(0x2940, addmod(mload(0x2920), sub(f_q, mload(0x9c0)), f_q)) +mstore(0x2960, mulmod(mload(0x2940), mload(0xa40), f_q)) +mstore(0x2980, mulmod(mload(0x7c0), mload(0x2960), f_q)) +mstore(0x29a0, addmod(1, sub(f_q, mload(0xae0)), f_q)) +mstore(0x29c0, mulmod(mload(0x29a0), mload(0x2400), f_q)) +mstore(0x29e0, addmod(mload(0x2980), mload(0x29c0), f_q)) +mstore(0x2a00, mulmod(mload(0x7c0), mload(0x29e0), f_q)) +mstore(0x2a20, mulmod(mload(0xae0), mload(0xae0), f_q)) +mstore(0x2a40, addmod(mload(0x2a20), sub(f_q, mload(0xae0)), f_q)) +mstore(0x2a60, mulmod(mload(0x2a40), mload(0x2320), f_q)) +mstore(0x2a80, addmod(mload(0x2a00), mload(0x2a60), f_q)) +mstore(0x2aa0, mulmod(mload(0x7c0), mload(0x2a80), f_q)) +mstore(0x2ac0, addmod(1, sub(f_q, mload(0x2320)), f_q)) +mstore(0x2ae0, addmod(mload(0x2340), mload(0x2360), f_q)) +mstore(0x2b00, addmod(mload(0x2ae0), mload(0x2380), f_q)) +mstore(0x2b20, addmod(mload(0x2b00), mload(0x23a0), f_q)) +mstore(0x2b40, addmod(mload(0x2b20), mload(0x23c0), f_q)) +mstore(0x2b60, addmod(mload(0x2b40), mload(0x23e0), f_q)) +mstore(0x2b80, addmod(mload(0x2ac0), sub(f_q, mload(0x2b60)), f_q)) +mstore(0x2ba0, mulmod(mload(0xa80), mload(0x640), f_q)) +mstore(0x2bc0, addmod(mload(0x9e0), mload(0x2ba0), f_q)) +mstore(0x2be0, addmod(mload(0x2bc0), mload(0x6a0), f_q)) +mstore(0x2c00, mulmod(mload(0xaa0), mload(0x640), f_q)) +mstore(0x2c20, addmod(mload(0x960), mload(0x2c00), f_q)) +mstore(0x2c40, addmod(mload(0x2c20), mload(0x6a0), f_q)) +mstore(0x2c60, mulmod(mload(0x2c40), mload(0x2be0), f_q)) +mstore(0x2c80, mulmod(mload(0xac0), mload(0x640), f_q)) +mstore(0x2ca0, addmod(mload(0x28e0), mload(0x2c80), f_q)) +mstore(0x2cc0, addmod(mload(0x2ca0), mload(0x6a0), f_q)) +mstore(0x2ce0, mulmod(mload(0x2cc0), mload(0x2c60), f_q)) +mstore(0x2d00, mulmod(mload(0x2ce0), mload(0xb00), f_q)) +mstore(0x2d20, mulmod(1, mload(0x640), f_q)) +mstore(0x2d40, mulmod(mload(0x920), mload(0x2d20), f_q)) +mstore(0x2d60, addmod(mload(0x9e0), mload(0x2d40), f_q)) +mstore(0x2d80, addmod(mload(0x2d60), mload(0x6a0), f_q)) +mstore(0x2da0, mulmod(4131629893567559867359510883348571134090853742863529169391034518566172092834, mload(0x640), f_q)) +mstore(0x2dc0, mulmod(mload(0x920), mload(0x2da0), f_q)) +mstore(0x2de0, addmod(mload(0x960), mload(0x2dc0), f_q)) +mstore(0x2e00, addmod(mload(0x2de0), mload(0x6a0), f_q)) +mstore(0x2e20, mulmod(mload(0x2e00), mload(0x2d80), f_q)) +mstore(0x2e40, mulmod(8910878055287538404433155982483128285667088683464058436815641868457422632747, mload(0x640), f_q)) +mstore(0x2e60, mulmod(mload(0x920), mload(0x2e40), f_q)) +mstore(0x2e80, addmod(mload(0x28e0), mload(0x2e60), f_q)) +mstore(0x2ea0, addmod(mload(0x2e80), mload(0x6a0), f_q)) +mstore(0x2ec0, mulmod(mload(0x2ea0), mload(0x2e20), f_q)) +mstore(0x2ee0, mulmod(mload(0x2ec0), mload(0xae0), f_q)) +mstore(0x2f00, addmod(mload(0x2d00), sub(f_q, mload(0x2ee0)), f_q)) +mstore(0x2f20, mulmod(mload(0x2f00), mload(0x2b80), f_q)) +mstore(0x2f40, addmod(mload(0x2aa0), mload(0x2f20), f_q)) +mstore(0x2f60, mulmod(mload(0x7c0), mload(0x2f40), f_q)) +mstore(0x2f80, addmod(1, sub(f_q, mload(0xb20)), f_q)) +mstore(0x2fa0, mulmod(mload(0x2f80), mload(0x2400), f_q)) +mstore(0x2fc0, addmod(mload(0x2f60), mload(0x2fa0), f_q)) +mstore(0x2fe0, mulmod(mload(0x7c0), mload(0x2fc0), f_q)) +mstore(0x3000, mulmod(mload(0xb20), mload(0xb20), f_q)) +mstore(0x3020, addmod(mload(0x3000), sub(f_q, mload(0xb20)), f_q)) +mstore(0x3040, mulmod(mload(0x3020), mload(0x2320), f_q)) +mstore(0x3060, addmod(mload(0x2fe0), mload(0x3040), f_q)) +mstore(0x3080, mulmod(mload(0x7c0), mload(0x3060), f_q)) +mstore(0x30a0, addmod(mload(0xb60), mload(0x640), f_q)) +mstore(0x30c0, mulmod(mload(0x30a0), mload(0xb40), f_q)) +mstore(0x30e0, addmod(mload(0xba0), mload(0x6a0), f_q)) +mstore(0x3100, mulmod(mload(0x30e0), mload(0x30c0), f_q)) +mstore(0x3120, mulmod(mload(0x960), mload(0xa20), f_q)) +mstore(0x3140, addmod(mload(0x3120), mload(0x640), f_q)) +mstore(0x3160, mulmod(mload(0x3140), mload(0xb20), f_q)) +mstore(0x3180, addmod(mload(0xa00), mload(0x6a0), f_q)) +mstore(0x31a0, mulmod(mload(0x3180), mload(0x3160), f_q)) +mstore(0x31c0, addmod(mload(0x3100), sub(f_q, mload(0x31a0)), f_q)) +mstore(0x31e0, mulmod(mload(0x31c0), mload(0x2b80), f_q)) +mstore(0x3200, addmod(mload(0x3080), mload(0x31e0), f_q)) +mstore(0x3220, mulmod(mload(0x7c0), mload(0x3200), f_q)) +mstore(0x3240, addmod(mload(0xb60), sub(f_q, mload(0xba0)), f_q)) +mstore(0x3260, mulmod(mload(0x3240), mload(0x2400), f_q)) +mstore(0x3280, addmod(mload(0x3220), mload(0x3260), f_q)) +mstore(0x32a0, mulmod(mload(0x7c0), mload(0x3280), f_q)) +mstore(0x32c0, mulmod(mload(0x3240), mload(0x2b80), f_q)) +mstore(0x32e0, addmod(mload(0xb60), sub(f_q, mload(0xb80)), f_q)) +mstore(0x3300, mulmod(mload(0x32e0), mload(0x32c0), f_q)) +mstore(0x3320, addmod(mload(0x32a0), mload(0x3300), f_q)) +mstore(0x3340, mulmod(mload(0x10a0), mload(0x10a0), f_q)) +mstore(0x3360, mulmod(mload(0x3340), mload(0x10a0), f_q)) +mstore(0x3380, mulmod(mload(0x3360), mload(0x10a0), f_q)) +mstore(0x33a0, mulmod(1, mload(0x10a0), f_q)) +mstore(0x33c0, mulmod(1, mload(0x3340), f_q)) +mstore(0x33e0, mulmod(1, mload(0x3360), f_q)) +mstore(0x3400, mulmod(mload(0x3320), mload(0x10c0), f_q)) +mstore(0x3420, mulmod(mload(0xde0), mload(0x920), f_q)) +mstore(0x3440, mulmod(mload(0x3420), mload(0x920), f_q)) +mstore(0x3460, mulmod(mload(0x920), 1, f_q)) +mstore(0x3480, addmod(mload(0xce0), sub(f_q, mload(0x3460)), f_q)) +mstore(0x34a0, mulmod(mload(0x920), 3615478808282855240548287271348143516886772452944084747768312988864436725401, f_q)) +mstore(0x34c0, addmod(mload(0xce0), sub(f_q, mload(0x34a0)), f_q)) +mstore(0x34e0, mulmod(mload(0x920), 8374374965308410102411073611984011876711565317741801500439755773472076597347, f_q)) +mstore(0x3500, addmod(mload(0xce0), sub(f_q, mload(0x34e0)), f_q)) +mstore(0x3520, mulmod(mload(0x920), 9741553891420464328295280489650144566903017206473301385034033384879943874347, f_q)) +mstore(0x3540, addmod(mload(0xce0), sub(f_q, mload(0x3520)), f_q)) +mstore(0x3560, mulmod(mload(0x920), 11211301017135681023579411905410872569206244553457844956874280139879520583390, f_q)) +mstore(0x3580, addmod(mload(0xce0), sub(f_q, mload(0x3560)), f_q)) +{ + let result := mulmod(mload(0xce0), mulmod(mload(0x3420), 13213688729882003894512633350385593288217014177373218494356903340348818451480, f_q), f_q) +result := addmod(mulmod(mload(0x920), mulmod(mload(0x3420), 8674554141957271327733772394871681800331350223042815849341300846226990044137, f_q), f_q), result, f_q) +mstore(13728, result) + } +{ + let result := mulmod(mload(0xce0), mulmod(mload(0x3420), 8207090019724696496350398458716998472718344609680392612601596849934418295470, f_q), f_q) +result := addmod(mulmod(mload(0x920), mulmod(mload(0x3420), 7391709068497399131897422873231908718558236401035363928063603272120120747483, f_q), f_q), result, f_q) +mstore(13760, result) + } +{ + let result := mulmod(mload(0xce0), mulmod(mload(0x3420), 7391709068497399131897422873231908718558236401035363928063603272120120747483, f_q), f_q) +result := addmod(mulmod(mload(0x920), mulmod(mload(0x3420), 1833147409647494756995474660497533717522217035849797032644829375745951548463, f_q), f_q), result, f_q) +mstore(13792, result) + } +{ + let result := mulmod(mload(0xce0), mulmod(mload(0x3420), 19036273796805830823244991598792794567595348772040298280440552631112242221017, f_q), f_q) +result := addmod(mulmod(mload(0x920), mulmod(mload(0x3420), 21424174760842011600237027652323753233820727276907995465687706728442780288120, f_q), f_q), result, f_q) +mstore(13824, result) + } +mstore(0x3620, mulmod(1, mload(0x3480), f_q)) +mstore(0x3640, mulmod(mload(0x3620), mload(0x3500), f_q)) +mstore(0x3660, mulmod(mload(0x3640), mload(0x3580), f_q)) +mstore(0x3680, mulmod(mload(0x3660), mload(0x34c0), f_q)) +{ + let result := mulmod(mload(0xce0), mulmod(mload(0x920), 13513867906530865119835332133273263211836799082674232843258448413103731898271, f_q), f_q) +result := addmod(mulmod(mload(0x920), mulmod(mload(0x920), 8374374965308410102411073611984011876711565317741801500439755773472076597346, f_q), f_q), result, f_q) +mstore(13984, result) + } +{ + let result := mulmod(mload(0xce0), mulmod(mload(0x920), 8374374965308410102411073611984011876711565317741801500439755773472076597346, f_q), f_q) +result := addmod(mulmod(mload(0x920), mulmod(mload(0x920), 19051316820012004301078067451830414396053685164699990887263679820168364509574, f_q), f_q), result, f_q) +mstore(14016, result) + } +{ + let result := mulmod(mload(0xce0), mulmod(mload(0x920), 12146688980418810893951125255607130521645347193942732958664170801695864621271, f_q), f_q) +result := addmod(mulmod(mload(0x920), mulmod(mload(0x920), 9741553891420464328295280489650144566903017206473301385034033384879943874346, f_q), f_q), result, f_q) +mstore(14048, result) + } +{ + let result := mulmod(mload(0xce0), mulmod(mload(0x920), 9741553891420464328295280489650144566903017206473301385034033384879943874346, f_q), f_q) +result := addmod(mulmod(mload(0x920), mulmod(mload(0x920), 1007427538592118648722042630484239861096428745172156964443610795837813833159, f_q), f_q), result, f_q) +mstore(14080, result) + } +mstore(0x3720, mulmod(mload(0x3620), mload(0x3540), f_q)) +{ + let result := mulmod(mload(0xce0), 1, f_q) +result := addmod(mulmod(mload(0x920), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q), result, f_q) +mstore(14144, result) + } +{ + let prod := mload(0x35a0) + + prod := mulmod(mload(0x35c0), prod, f_q) + mstore(0x3760, prod) + + prod := mulmod(mload(0x35e0), prod, f_q) + mstore(0x3780, prod) + + prod := mulmod(mload(0x3600), prod, f_q) + mstore(0x37a0, prod) + + prod := mulmod(mload(0x36a0), prod, f_q) + mstore(0x37c0, prod) + + prod := mulmod(mload(0x36c0), prod, f_q) + mstore(0x37e0, prod) + + prod := mulmod(mload(0x3640), prod, f_q) + mstore(0x3800, prod) + + prod := mulmod(mload(0x36e0), prod, f_q) + mstore(0x3820, prod) + + prod := mulmod(mload(0x3700), prod, f_q) + mstore(0x3840, prod) + + prod := mulmod(mload(0x3720), prod, f_q) + mstore(0x3860, prod) + + prod := mulmod(mload(0x3740), prod, f_q) + mstore(0x3880, prod) + + prod := mulmod(mload(0x3620), prod, f_q) + mstore(0x38a0, prod) + + } +mstore(0x38e0, 32) +mstore(0x3900, 32) +mstore(0x3920, 32) +mstore(0x3940, mload(0x38a0)) +mstore(0x3960, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x3980, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x38e0, 0xc0, 0x38c0, 0x20), 1), success) +{ + + let inv := mload(0x38c0) + let v + + v := mload(0x3620) + mstore(13856, mulmod(mload(0x3880), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3740) + mstore(14144, mulmod(mload(0x3860), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3720) + mstore(14112, mulmod(mload(0x3840), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3700) + mstore(14080, mulmod(mload(0x3820), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x36e0) + mstore(14048, mulmod(mload(0x3800), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3640) + mstore(13888, mulmod(mload(0x37e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x36c0) + mstore(14016, mulmod(mload(0x37c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x36a0) + mstore(13984, mulmod(mload(0x37a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3600) + mstore(13824, mulmod(mload(0x3780), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x35e0) + mstore(13792, mulmod(mload(0x3760), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x35c0) + mstore(13760, mulmod(mload(0x35a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x35a0, inv) + + } +{ + let result := mload(0x35a0) +result := addmod(mload(0x35c0), result, f_q) +result := addmod(mload(0x35e0), result, f_q) +result := addmod(mload(0x3600), result, f_q) +mstore(14752, result) + } +mstore(0x39c0, mulmod(mload(0x3680), mload(0x3640), f_q)) +{ + let result := mload(0x36a0) +result := addmod(mload(0x36c0), result, f_q) +mstore(14816, result) + } +mstore(0x3a00, mulmod(mload(0x3680), mload(0x3720), f_q)) +{ + let result := mload(0x36e0) +result := addmod(mload(0x3700), result, f_q) +mstore(14880, result) + } +mstore(0x3a40, mulmod(mload(0x3680), mload(0x3620), f_q)) +{ + let result := mload(0x3740) +mstore(14944, result) + } +{ + let prod := mload(0x39a0) + + prod := mulmod(mload(0x39e0), prod, f_q) + mstore(0x3a80, prod) + + prod := mulmod(mload(0x3a20), prod, f_q) + mstore(0x3aa0, prod) + + prod := mulmod(mload(0x3a60), prod, f_q) + mstore(0x3ac0, prod) + + } +mstore(0x3b00, 32) +mstore(0x3b20, 32) +mstore(0x3b40, 32) +mstore(0x3b60, mload(0x3ac0)) +mstore(0x3b80, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x3ba0, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x3b00, 0xc0, 0x3ae0, 0x20), 1), success) +{ + + let inv := mload(0x3ae0) + let v + + v := mload(0x3a60) + mstore(14944, mulmod(mload(0x3aa0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3a20) + mstore(14880, mulmod(mload(0x3a80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x39e0) + mstore(14816, mulmod(mload(0x39a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x39a0, inv) + + } +mstore(0x3bc0, mulmod(mload(0x39c0), mload(0x39e0), f_q)) +mstore(0x3be0, mulmod(mload(0x3a00), mload(0x3a20), f_q)) +mstore(0x3c00, mulmod(mload(0x3a40), mload(0x3a60), f_q)) +mstore(0x3c20, mulmod(mload(0xbe0), mload(0xbe0), f_q)) +mstore(0x3c40, mulmod(mload(0x3c20), mload(0xbe0), f_q)) +mstore(0x3c60, mulmod(mload(0x3c40), mload(0xbe0), f_q)) +mstore(0x3c80, mulmod(mload(0x3c60), mload(0xbe0), f_q)) +mstore(0x3ca0, mulmod(mload(0x3c80), mload(0xbe0), f_q)) +mstore(0x3cc0, mulmod(mload(0x3ca0), mload(0xbe0), f_q)) +mstore(0x3ce0, mulmod(mload(0x3cc0), mload(0xbe0), f_q)) +mstore(0x3d00, mulmod(mload(0x3ce0), mload(0xbe0), f_q)) +mstore(0x3d20, mulmod(mload(0x3d00), mload(0xbe0), f_q)) +mstore(0x3d40, mulmod(mload(0xc40), mload(0xc40), f_q)) +mstore(0x3d60, mulmod(mload(0x3d40), mload(0xc40), f_q)) +mstore(0x3d80, mulmod(mload(0x3d60), mload(0xc40), f_q)) +{ + let result := mulmod(mload(0x960), mload(0x35a0), f_q) +result := addmod(mulmod(mload(0x980), mload(0x35c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x9a0), mload(0x35e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x9c0), mload(0x3600), f_q), result, f_q) +mstore(15776, result) + } +mstore(0x3dc0, mulmod(mload(0x3da0), mload(0x39a0), f_q)) +mstore(0x3de0, mulmod(sub(f_q, mload(0x3dc0)), 1, f_q)) +mstore(0x3e00, mulmod(mload(0x3de0), 1, f_q)) +mstore(0x3e20, mulmod(1, mload(0x39c0), f_q)) +{ + let result := mulmod(mload(0xae0), mload(0x36a0), f_q) +result := addmod(mulmod(mload(0xb00), mload(0x36c0), f_q), result, f_q) +mstore(15936, result) + } +mstore(0x3e60, mulmod(mload(0x3e40), mload(0x3bc0), f_q)) +mstore(0x3e80, mulmod(sub(f_q, mload(0x3e60)), 1, f_q)) +mstore(0x3ea0, mulmod(mload(0x3e20), 1, f_q)) +{ + let result := mulmod(mload(0xb20), mload(0x36a0), f_q) +result := addmod(mulmod(mload(0xb40), mload(0x36c0), f_q), result, f_q) +mstore(16064, result) + } +mstore(0x3ee0, mulmod(mload(0x3ec0), mload(0x3bc0), f_q)) +mstore(0x3f00, mulmod(sub(f_q, mload(0x3ee0)), mload(0xbe0), f_q)) +mstore(0x3f20, mulmod(mload(0x3e20), mload(0xbe0), f_q)) +mstore(0x3f40, addmod(mload(0x3e80), mload(0x3f00), f_q)) +mstore(0x3f60, mulmod(mload(0x3f40), mload(0xc40), f_q)) +mstore(0x3f80, mulmod(mload(0x3ea0), mload(0xc40), f_q)) +mstore(0x3fa0, mulmod(mload(0x3f20), mload(0xc40), f_q)) +mstore(0x3fc0, addmod(mload(0x3e00), mload(0x3f60), f_q)) +mstore(0x3fe0, mulmod(1, mload(0x3a00), f_q)) +{ + let result := mulmod(mload(0xb60), mload(0x36e0), f_q) +result := addmod(mulmod(mload(0xb80), mload(0x3700), f_q), result, f_q) +mstore(16384, result) + } +mstore(0x4020, mulmod(mload(0x4000), mload(0x3be0), f_q)) +mstore(0x4040, mulmod(sub(f_q, mload(0x4020)), 1, f_q)) +mstore(0x4060, mulmod(mload(0x3fe0), 1, f_q)) +mstore(0x4080, mulmod(mload(0x4040), mload(0x3d40), f_q)) +mstore(0x40a0, mulmod(mload(0x4060), mload(0x3d40), f_q)) +mstore(0x40c0, addmod(mload(0x3fc0), mload(0x4080), f_q)) +mstore(0x40e0, mulmod(1, mload(0x3a40), f_q)) +{ + let result := mulmod(mload(0xba0), mload(0x3740), f_q) +mstore(16640, result) + } +mstore(0x4120, mulmod(mload(0x4100), mload(0x3c00), f_q)) +mstore(0x4140, mulmod(sub(f_q, mload(0x4120)), 1, f_q)) +mstore(0x4160, mulmod(mload(0x40e0), 1, f_q)) +{ + let result := mulmod(mload(0x9e0), mload(0x3740), f_q) +mstore(16768, result) + } +mstore(0x41a0, mulmod(mload(0x4180), mload(0x3c00), f_q)) +mstore(0x41c0, mulmod(sub(f_q, mload(0x41a0)), mload(0xbe0), f_q)) +mstore(0x41e0, mulmod(mload(0x40e0), mload(0xbe0), f_q)) +mstore(0x4200, addmod(mload(0x4140), mload(0x41c0), f_q)) +{ + let result := mulmod(mload(0xa00), mload(0x3740), f_q) +mstore(16928, result) + } +mstore(0x4240, mulmod(mload(0x4220), mload(0x3c00), f_q)) +mstore(0x4260, mulmod(sub(f_q, mload(0x4240)), mload(0x3c20), f_q)) +mstore(0x4280, mulmod(mload(0x40e0), mload(0x3c20), f_q)) +mstore(0x42a0, addmod(mload(0x4200), mload(0x4260), f_q)) +{ + let result := mulmod(mload(0xa20), mload(0x3740), f_q) +mstore(17088, result) + } +mstore(0x42e0, mulmod(mload(0x42c0), mload(0x3c00), f_q)) +mstore(0x4300, mulmod(sub(f_q, mload(0x42e0)), mload(0x3c40), f_q)) +mstore(0x4320, mulmod(mload(0x40e0), mload(0x3c40), f_q)) +mstore(0x4340, addmod(mload(0x42a0), mload(0x4300), f_q)) +{ + let result := mulmod(mload(0xa40), mload(0x3740), f_q) +mstore(17248, result) + } +mstore(0x4380, mulmod(mload(0x4360), mload(0x3c00), f_q)) +mstore(0x43a0, mulmod(sub(f_q, mload(0x4380)), mload(0x3c60), f_q)) +mstore(0x43c0, mulmod(mload(0x40e0), mload(0x3c60), f_q)) +mstore(0x43e0, addmod(mload(0x4340), mload(0x43a0), f_q)) +{ + let result := mulmod(mload(0xa80), mload(0x3740), f_q) +mstore(17408, result) + } +mstore(0x4420, mulmod(mload(0x4400), mload(0x3c00), f_q)) +mstore(0x4440, mulmod(sub(f_q, mload(0x4420)), mload(0x3c80), f_q)) +mstore(0x4460, mulmod(mload(0x40e0), mload(0x3c80), f_q)) +mstore(0x4480, addmod(mload(0x43e0), mload(0x4440), f_q)) +{ + let result := mulmod(mload(0xaa0), mload(0x3740), f_q) +mstore(17568, result) + } +mstore(0x44c0, mulmod(mload(0x44a0), mload(0x3c00), f_q)) +mstore(0x44e0, mulmod(sub(f_q, mload(0x44c0)), mload(0x3ca0), f_q)) +mstore(0x4500, mulmod(mload(0x40e0), mload(0x3ca0), f_q)) +mstore(0x4520, addmod(mload(0x4480), mload(0x44e0), f_q)) +{ + let result := mulmod(mload(0xac0), mload(0x3740), f_q) +mstore(17728, result) + } +mstore(0x4560, mulmod(mload(0x4540), mload(0x3c00), f_q)) +mstore(0x4580, mulmod(sub(f_q, mload(0x4560)), mload(0x3cc0), f_q)) +mstore(0x45a0, mulmod(mload(0x40e0), mload(0x3cc0), f_q)) +mstore(0x45c0, addmod(mload(0x4520), mload(0x4580), f_q)) +mstore(0x45e0, mulmod(mload(0x33a0), mload(0x3a40), f_q)) +mstore(0x4600, mulmod(mload(0x33c0), mload(0x3a40), f_q)) +mstore(0x4620, mulmod(mload(0x33e0), mload(0x3a40), f_q)) +{ + let result := mulmod(mload(0x3400), mload(0x3740), f_q) +mstore(17984, result) + } +mstore(0x4660, mulmod(mload(0x4640), mload(0x3c00), f_q)) +mstore(0x4680, mulmod(sub(f_q, mload(0x4660)), mload(0x3ce0), f_q)) +mstore(0x46a0, mulmod(mload(0x40e0), mload(0x3ce0), f_q)) +mstore(0x46c0, mulmod(mload(0x45e0), mload(0x3ce0), f_q)) +mstore(0x46e0, mulmod(mload(0x4600), mload(0x3ce0), f_q)) +mstore(0x4700, mulmod(mload(0x4620), mload(0x3ce0), f_q)) +mstore(0x4720, addmod(mload(0x45c0), mload(0x4680), f_q)) +{ + let result := mulmod(mload(0xa60), mload(0x3740), f_q) +mstore(18240, result) + } +mstore(0x4760, mulmod(mload(0x4740), mload(0x3c00), f_q)) +mstore(0x4780, mulmod(sub(f_q, mload(0x4760)), mload(0x3d00), f_q)) +mstore(0x47a0, mulmod(mload(0x40e0), mload(0x3d00), f_q)) +mstore(0x47c0, addmod(mload(0x4720), mload(0x4780), f_q)) +mstore(0x47e0, mulmod(mload(0x47c0), mload(0x3d60), f_q)) +mstore(0x4800, mulmod(mload(0x4160), mload(0x3d60), f_q)) +mstore(0x4820, mulmod(mload(0x41e0), mload(0x3d60), f_q)) +mstore(0x4840, mulmod(mload(0x4280), mload(0x3d60), f_q)) +mstore(0x4860, mulmod(mload(0x4320), mload(0x3d60), f_q)) +mstore(0x4880, mulmod(mload(0x43c0), mload(0x3d60), f_q)) +mstore(0x48a0, mulmod(mload(0x4460), mload(0x3d60), f_q)) +mstore(0x48c0, mulmod(mload(0x4500), mload(0x3d60), f_q)) +mstore(0x48e0, mulmod(mload(0x45a0), mload(0x3d60), f_q)) +mstore(0x4900, mulmod(mload(0x46a0), mload(0x3d60), f_q)) +mstore(0x4920, mulmod(mload(0x46c0), mload(0x3d60), f_q)) +mstore(0x4940, mulmod(mload(0x46e0), mload(0x3d60), f_q)) +mstore(0x4960, mulmod(mload(0x4700), mload(0x3d60), f_q)) +mstore(0x4980, mulmod(mload(0x47a0), mload(0x3d60), f_q)) +mstore(0x49a0, addmod(mload(0x40c0), mload(0x47e0), f_q)) +mstore(0x49c0, mulmod(1, mload(0x3680), f_q)) +mstore(0x49e0, mulmod(1, mload(0xce0), f_q)) +mstore(0x4a00, 0x0000000000000000000000000000000000000000000000000000000000000001) + mstore(0x4a20, 0x0000000000000000000000000000000000000000000000000000000000000002) +mstore(0x4a40, mload(0x49a0)) +success := and(eq(staticcall(gas(), 0x7, 0x4a00, 0x60, 0x4a00, 0x40), 1), success) +mstore(0x4a60, mload(0x4a00)) + mstore(0x4a80, mload(0x4a20)) +mstore(0x4aa0, mload(0x500)) + mstore(0x4ac0, mload(0x520)) +success := and(eq(staticcall(gas(), 0x6, 0x4a60, 0x80, 0x4a60, 0x40), 1), success) +mstore(0x4ae0, mload(0x6e0)) + mstore(0x4b00, mload(0x700)) +mstore(0x4b20, mload(0x3f80)) +success := and(eq(staticcall(gas(), 0x7, 0x4ae0, 0x60, 0x4ae0, 0x40), 1), success) +mstore(0x4b40, mload(0x4a60)) + mstore(0x4b60, mload(0x4a80)) +mstore(0x4b80, mload(0x4ae0)) + mstore(0x4ba0, mload(0x4b00)) +success := and(eq(staticcall(gas(), 0x6, 0x4b40, 0x80, 0x4b40, 0x40), 1), success) +mstore(0x4bc0, mload(0x720)) + mstore(0x4be0, mload(0x740)) +mstore(0x4c00, mload(0x3fa0)) +success := and(eq(staticcall(gas(), 0x7, 0x4bc0, 0x60, 0x4bc0, 0x40), 1), success) +mstore(0x4c20, mload(0x4b40)) + mstore(0x4c40, mload(0x4b60)) +mstore(0x4c60, mload(0x4bc0)) + mstore(0x4c80, mload(0x4be0)) +success := and(eq(staticcall(gas(), 0x6, 0x4c20, 0x80, 0x4c20, 0x40), 1), success) +mstore(0x4ca0, mload(0x5a0)) + mstore(0x4cc0, mload(0x5c0)) +mstore(0x4ce0, mload(0x40a0)) +success := and(eq(staticcall(gas(), 0x7, 0x4ca0, 0x60, 0x4ca0, 0x40), 1), success) +mstore(0x4d00, mload(0x4c20)) + mstore(0x4d20, mload(0x4c40)) +mstore(0x4d40, mload(0x4ca0)) + mstore(0x4d60, mload(0x4cc0)) +success := and(eq(staticcall(gas(), 0x6, 0x4d00, 0x80, 0x4d00, 0x40), 1), success) +mstore(0x4d80, mload(0x5e0)) + mstore(0x4da0, mload(0x600)) +mstore(0x4dc0, mload(0x4800)) +success := and(eq(staticcall(gas(), 0x7, 0x4d80, 0x60, 0x4d80, 0x40), 1), success) +mstore(0x4de0, mload(0x4d00)) + mstore(0x4e00, mload(0x4d20)) +mstore(0x4e20, mload(0x4d80)) + mstore(0x4e40, mload(0x4da0)) +success := and(eq(staticcall(gas(), 0x6, 0x4de0, 0x80, 0x4de0, 0x40), 1), success) +mstore(0x4e60, 0x0b9a1d90f4d2149e6fd88763df52ee3b7fbd7479c8d3880038b37ab455228ca1) + mstore(0x4e80, 0x0a126c5191f45b3f6db196968d725c39024e122f77faf484cadbf20d453bee9e) +mstore(0x4ea0, mload(0x4820)) +success := and(eq(staticcall(gas(), 0x7, 0x4e60, 0x60, 0x4e60, 0x40), 1), success) +mstore(0x4ec0, mload(0x4de0)) + mstore(0x4ee0, mload(0x4e00)) +mstore(0x4f00, mload(0x4e60)) + mstore(0x4f20, mload(0x4e80)) +success := and(eq(staticcall(gas(), 0x6, 0x4ec0, 0x80, 0x4ec0, 0x40), 1), success) +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)) + mstore(0x4fc0, mload(0x4ee0)) +mstore(0x4fe0, mload(0x4f40)) + mstore(0x5000, mload(0x4f60)) +success := and(eq(staticcall(gas(), 0x6, 0x4fa0, 0x80, 0x4fa0, 0x40), 1), success) +mstore(0x5020, 0x0f030ba7d233a926023772efd048a06115a81f44243d9eb142dcc334045e3984) + mstore(0x5040, 0x122aef3feca33f9fa2b58473490d79bd7740e61c5e48be97fb8e03ffc56c1594) +mstore(0x5060, mload(0x4860)) +success := and(eq(staticcall(gas(), 0x7, 0x5020, 0x60, 0x5020, 0x40), 1), success) +mstore(0x5080, mload(0x4fa0)) + mstore(0x50a0, mload(0x4fc0)) +mstore(0x50c0, mload(0x5020)) + mstore(0x50e0, mload(0x5040)) +success := and(eq(staticcall(gas(), 0x6, 0x5080, 0x80, 0x5080, 0x40), 1), success) +mstore(0x5100, 0x2e2491f99291ea9f817cb8e6e6886faf5c55f365bbc21c797e229330ac7ebe65) + mstore(0x5120, 0x259e1755aa8905122f69471618263dfac184a26ffae23687d515274a74e7a8ac) +mstore(0x5140, mload(0x4880)) +success := and(eq(staticcall(gas(), 0x7, 0x5100, 0x60, 0x5100, 0x40), 1), success) +mstore(0x5160, mload(0x5080)) + mstore(0x5180, mload(0x50a0)) +mstore(0x51a0, mload(0x5100)) + mstore(0x51c0, mload(0x5120)) +success := and(eq(staticcall(gas(), 0x6, 0x5160, 0x80, 0x5160, 0x40), 1), success) +mstore(0x51e0, 0x2f4d2bf2ca4cd42f927d0fabbd31965da8bd92a4a6363692f649113d222ee176) + mstore(0x5200, 0x081f92dc68d325cac733543bfac7639fafdd59604910dd41e876bce399fff3a0) +mstore(0x5220, mload(0x48a0)) +success := and(eq(staticcall(gas(), 0x7, 0x51e0, 0x60, 0x51e0, 0x40), 1), success) +mstore(0x5240, mload(0x5160)) + mstore(0x5260, mload(0x5180)) +mstore(0x5280, mload(0x51e0)) + mstore(0x52a0, mload(0x5200)) +success := and(eq(staticcall(gas(), 0x6, 0x5240, 0x80, 0x5240, 0x40), 1), success) +mstore(0x52c0, 0x0acd7d0e602fca913d344401b686c3516f9b02695d957da91ba5c349eee3759f) + mstore(0x52e0, 0x0e73c1bce2daf77a052c94603561ae6d05bdf5e61adfb8ee4628feca60e0f9c2) +mstore(0x5300, mload(0x48c0)) +success := and(eq(staticcall(gas(), 0x7, 0x52c0, 0x60, 0x52c0, 0x40), 1), success) +mstore(0x5320, mload(0x5240)) + mstore(0x5340, mload(0x5260)) +mstore(0x5360, mload(0x52c0)) + mstore(0x5380, mload(0x52e0)) +success := and(eq(staticcall(gas(), 0x6, 0x5320, 0x80, 0x5320, 0x40), 1), success) +mstore(0x53a0, 0x00f3b507db1425bc54a89078f8954e9ee67eee7b46bccdea7bb3adc9ed9c9248) + mstore(0x53c0, 0x0a204ce583710da1783761b04a91bb79fe0e9de3e21e481e814d9f1c481c3523) +mstore(0x53e0, mload(0x48e0)) +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(0x800)) + mstore(0x54a0, mload(0x820)) +mstore(0x54c0, mload(0x4900)) +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(0x840)) + mstore(0x5580, mload(0x860)) +mstore(0x55a0, mload(0x4920)) +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(0x880)) + mstore(0x5660, mload(0x8a0)) +mstore(0x5680, mload(0x4940)) +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, mload(0x8c0)) + mstore(0x5740, mload(0x8e0)) +mstore(0x5760, mload(0x4960)) +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, mload(0x760)) + mstore(0x5820, mload(0x780)) +mstore(0x5840, mload(0x4980)) +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, mload(0xc80)) + mstore(0x5900, mload(0xca0)) +mstore(0x5920, sub(f_q, mload(0x49c0))) +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, mload(0xd20)) + mstore(0x59e0, mload(0xd40)) +mstore(0x5a00, mload(0x49e0)) +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, mload(0x5a20)) + mstore(0x5ac0, mload(0x5a40)) +mstore(0x5ae0, mload(0xd20)) + mstore(0x5b00, mload(0xd40)) +mstore(0x5b20, mload(0xd60)) + mstore(0x5b40, mload(0xd80)) +mstore(0x5b60, mload(0xda0)) + mstore(0x5b80, mload(0xdc0)) +mstore(0x5ba0, keccak256(0x5aa0, 256)) +mstore(23488, mod(mload(23456), f_q)) +mstore(0x5be0, mulmod(mload(0x5bc0), mload(0x5bc0), f_q)) +mstore(0x5c00, mulmod(1, mload(0x5bc0), f_q)) +mstore(0x5c20, mload(0x5b20)) + mstore(0x5c40, mload(0x5b40)) +mstore(0x5c60, mload(0x5c00)) +success := and(eq(staticcall(gas(), 0x7, 0x5c20, 0x60, 0x5c20, 0x40), 1), success) +mstore(0x5c80, mload(0x5aa0)) + mstore(0x5ca0, mload(0x5ac0)) +mstore(0x5cc0, mload(0x5c20)) + mstore(0x5ce0, mload(0x5c40)) +success := and(eq(staticcall(gas(), 0x6, 0x5c80, 0x80, 0x5c80, 0x40), 1), success) +mstore(0x5d00, mload(0x5b60)) + mstore(0x5d20, mload(0x5b80)) +mstore(0x5d40, mload(0x5c00)) +success := and(eq(staticcall(gas(), 0x7, 0x5d00, 0x60, 0x5d00, 0x40), 1), success) +mstore(0x5d60, mload(0x5ae0)) + mstore(0x5d80, mload(0x5b00)) +mstore(0x5da0, mload(0x5d00)) + mstore(0x5dc0, mload(0x5d20)) +success := and(eq(staticcall(gas(), 0x6, 0x5d60, 0x80, 0x5d60, 0x40), 1), success) +mstore(0x5de0, mload(0x5c80)) + mstore(0x5e00, mload(0x5ca0)) +mstore(0x5e20, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) + mstore(0x5e40, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) + mstore(0x5e60, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) + mstore(0x5e80, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) +mstore(0x5ea0, mload(0x5d60)) + mstore(0x5ec0, mload(0x5d80)) +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) + + if not(success) { revert(0, 0) } + return(0, 0) + + } + } + } \ No newline at end of file diff --git a/axiom-core/data/headers/mainnet_17_7_for_evm_1.yul b/axiom-core/data/headers/mainnet_17_7_for_evm_1.yul new file mode 100644 index 00000000..ddd6ceb5 --- /dev/null +++ b/axiom-core/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-core/data/headers/shasums.txt b/axiom-core/data/headers/shasums.txt new file mode 100644 index 00000000..f293722b --- /dev/null +++ b/axiom-core/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/axiom-core/data/production/v2.0.12.cids b/axiom-core/data/production/v2.0.12.cids new file mode 100644 index 00000000..cc0007ce --- /dev/null +++ b/axiom-core/data/production/v2.0.12.cids @@ -0,0 +1,26 @@ +[ + [ + "{\"node_type\":{\"Leaf\":32},\"depth\":7,\"initial_depth\":7}", + "55c014423ae74968405f8d846426515360183fe27b3080cd905dc7e932cb2e47" + ], + [ + "{\"node_type\":\"Intermediate\",\"depth\":8,\"initial_depth\":7}", + "8946746811c9cb4b00b37a7e864135a8a752040efaf93ef3d231c45818c9402a" + ], + [ + "{\"node_type\":\"Intermediate\",\"depth\":9,\"initial_depth\":7}", + "42e495dd8ef1b1eace1bcb273f405dd07e0837dbc102c78cabb3b613ab948fcd" + ], + [ + "{\"node_type\":\"Root\",\"depth\":10,\"initial_depth\":7}", + "d83322a8dba1df94cf6ffeac309ad1c1d50146740ed77090487254b6443eaa4e" + ], + [ + "{\"node_type\":{\"Evm\":0},\"depth\":10,\"initial_depth\":7}", + "066eb72da7facfce749da885f9ef80281aefb08cd676ec63e6feb09fba9fe246" + ], + [ + "{\"node_type\":{\"Evm\":1},\"depth\":10,\"initial_depth\":7}", + "39cb264c605428fc752e90b6ac1b77427ab06b795419a759e237e283b95f377f" + ] +] \ No newline at end of file diff --git a/axiom-core/data/production/v2.0.12.historical.cids b/axiom-core/data/production/v2.0.12.historical.cids new file mode 100644 index 00000000..8dc7f1c7 --- /dev/null +++ b/axiom-core/data/production/v2.0.12.historical.cids @@ -0,0 +1,54 @@ +[ + [ + "{\"node_type\":{\"Leaf\":32},\"depth\":7,\"initial_depth\":7}", + "55c014423ae74968405f8d846426515360183fe27b3080cd905dc7e932cb2e47" + ], + [ + "{\"node_type\":\"Intermediate\",\"depth\":8,\"initial_depth\":7}", + "8946746811c9cb4b00b37a7e864135a8a752040efaf93ef3d231c45818c9402a" + ], + [ + "{\"node_type\":\"Intermediate\",\"depth\":9,\"initial_depth\":7}", + "42e495dd8ef1b1eace1bcb273f405dd07e0837dbc102c78cabb3b613ab948fcd" + ], + [ + "{\"node_type\":\"Intermediate\",\"depth\":10,\"initial_depth\":7}", + "50bbeab0cc5d9983dcd8bd506ce8a1ee2931dcd9e4f287970e12ba5be905af76" + ], + [ + "{\"node_type\":\"Intermediate\",\"depth\":11,\"initial_depth\":7}", + "6807f95bc3d1da094945acd488094e5ba9247c6b10d8346dc2e22082c6b6ce09" + ], + [ + "{\"node_type\":\"Intermediate\",\"depth\":12,\"initial_depth\":7}", + "144ce7e834ad79d79d2cb6d762e575cc98de81ad888e4c6b67181305f7c8b017" + ], + [ + "{\"node_type\":\"Intermediate\",\"depth\":13,\"initial_depth\":7}", + "ad92f075afd1bc68b9d8c144f60c372c0653e3392cde441ce959411c23d23bad" + ], + [ + "{\"node_type\":\"Intermediate\",\"depth\":14,\"initial_depth\":7}", + "d2bca86ba0249f5af59332822eb2bda50e6504cd89bd171e46745a2a8330771b" + ], + [ + "{\"node_type\":\"Intermediate\",\"depth\":15,\"initial_depth\":7}", + "2fa38814042c6fb803509d9d0d34cd84a90c14b74c917401b0d1138d49bc2bdd" + ], + [ + "{\"node_type\":\"Intermediate\",\"depth\":16,\"initial_depth\":7}", + "943ff9156710c54c0903788aef5d46ef734095228c873192bf9cdc291f8f80c6" + ], + [ + "{\"node_type\":\"Root\",\"depth\":17,\"initial_depth\":7}", + "139e65a6380229a50f55e3af4a7c2d904952bdd652b6aa1aad8db243d9348ce8" + ], + [ + "{\"node_type\":{\"Evm\":0},\"depth\":17,\"initial_depth\":7}", + "458d7b330aac781c7fc0e787c9b17e8eb7e573fbdd7a4689d419e7ecdbff0dc7" + ], + [ + "{\"node_type\":{\"Evm\":1},\"depth\":17,\"initial_depth\":7}", + "0379c723deafac09822de4f36da40a5595331c447a5cc7c342eb839cd199be02" + ] +] \ No newline at end of file diff --git a/axiom-core/src/aggregation/final_merkle.rs b/axiom-core/src/aggregation/final_merkle.rs new file mode 100644 index 00000000..d4715d15 --- /dev/null +++ b/axiom-core/src/aggregation/final_merkle.rs @@ -0,0 +1,184 @@ +//! The root of the aggregation tree. +//! An [EthBlockHeaderChainRootAggregationCircuit] can aggregate either: +//! - two [crate::header_chain::EthBlockHeaderChainCircuit]s (if `max_depth == initial_depth + 1`) or +//! - two [super::intermediate::EthBlockHeaderChainIntermediateAggregationCircuit]s. +//! +//! The difference between Intermediate and Root aggregation circuits is that the Intermediate ones +//! do not have a keccak sub-circuit: all keccaks are delayed until the Root aggregation. +use std::iter; + +use anyhow::{bail, Result}; +use axiom_eth::{ + halo2_base::{ + gates::{GateInstructions, RangeInstructions}, + QuantumCell::Constant, + }, + halo2_proofs::poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG}, + halo2curves::bn256::{Bn256, Fr}, + mpt::MPTChip, + rlc::circuit::builder::RlcCircuitBuilder, + snark_verifier_sdk::halo2::aggregation::{ + aggregate_snarks, SnarkAggregationOutput, Svk, VerifierUniversality, + }, + snark_verifier_sdk::{Snark, SHPLONK}, + utils::{ + build_utils::aggregation::CircuitMetadata, + eth_circuit::EthCircuitInstructions, + keccak::decorator::RlcKeccakCircuitImpl, + snark_verifier::{get_accumulator_indices, NUM_FE_ACCUMULATOR}, + uint_to_bytes_be, + }, +}; +use itertools::Itertools; + +use super::intermediate::{ + join_previous_instances, EthBlockHeaderChainIntermediateAggregationInput, +}; + +/// Same as [super::intermediate::EthBlockHeaderChainIntermediateAggregationCircuit] but uses a Keccak sub-circuit to compute the final merkle mountain range. Specifically, it aggregates two snarks at `max_depth - 1` and then computes the keccaks to get the final merkle mountain root. +pub type EthBlockHeaderChainRootAggregationCircuit = + RlcKeccakCircuitImpl; + +/// The input needed to construct [EthBlockHeaderChainRootAggregationCircuit] +#[derive(Clone, Debug)] +pub struct EthBlockHeaderChainRootAggregationInput { + /// See [EthBlockHeaderChainIntermediateAggregationInput] + pub inner: EthBlockHeaderChainIntermediateAggregationInput, + /// Succinct verifying key (generator of KZG trusted setup) should match `inner.snarks` + pub svk: Svk, + prev_acc_indices: Vec>, +} + +impl EthBlockHeaderChainRootAggregationInput { + /// `snarks` should be exactly two snarks of either + /// - `EthBlockHeaderChainCircuit` if `max_depth == initial_depth + 1` or + /// - `EthBlockHeaderChainIntermediateAggregationCircuit` otherwise + /// + /// We only need the generator `kzg_params.get_g()[0]` to match that of the trusted setup used + /// in the creation of `snarks`. + pub fn new( + snarks: Vec, + num_blocks: u32, + max_depth: usize, + initial_depth: usize, + kzg_params: &ParamsKZG, + ) -> Result { + let svk = kzg_params.get_g()[0].into(); + let prev_acc_indices = get_accumulator_indices(&snarks); + if max_depth == initial_depth + 1 + && prev_acc_indices.iter().any(|indices| !indices.is_empty()) + { + bail!("Snarks to be aggregated must not have accumulators: they should come from EthBlockHeaderChainCircuit"); + } + if max_depth > initial_depth + 1 + && prev_acc_indices.iter().any(|indices| indices.len() != NUM_FE_ACCUMULATOR) + { + bail!("Snarks to be aggregated must all be EthBlockHeaderChainIntermediateAggregationCircuits"); + } + let inner = EthBlockHeaderChainIntermediateAggregationInput::new( + snarks, + num_blocks, + max_depth, + initial_depth, + ); + Ok(Self { inner, svk, prev_acc_indices }) + } +} + +impl EthCircuitInstructions for EthBlockHeaderChainRootAggregationInput { + type FirstPhasePayload = (); + + fn virtual_assign_phase0(&self, builder: &mut RlcCircuitBuilder, mpt: &MPTChip) { + let EthBlockHeaderChainIntermediateAggregationInput { + max_depth, + initial_depth, + num_blocks, + snarks, + } = self.inner.clone(); + + let keccak = mpt.keccak(); + let range = keccak.range(); + let pool = builder.base.pool(0); + let SnarkAggregationOutput { mut previous_instances, accumulator, .. } = + aggregate_snarks::(pool, range, self.svk, snarks, VerifierUniversality::None); + // remove old accumulators + for (prev_instance, acc_indices) in + previous_instances.iter_mut().zip_eq(&self.prev_acc_indices) + { + for i in acc_indices.iter().sorted().rev() { + prev_instance.remove(*i); + } + } + + let ctx = pool.main(); + let num_blocks_minus_one = ctx.load_witness(Fr::from(num_blocks as u64 - 1)); + let new_instances = join_previous_instances( + ctx, + range, + previous_instances.try_into().unwrap(), + num_blocks_minus_one, + max_depth, + initial_depth, + ); + let num_blocks = range.gate().add(ctx, num_blocks_minus_one, Constant(Fr::one())); + + // compute the keccaks that were delayed, to get the `max_depth - initial_depth + 1` biggest merkle mountain ranges + let bits = range.gate().num_to_bits(ctx, num_blocks, max_depth + 1); + // bits is in little endian, we take the top `max_depth - initial_depth + 1` bits + let num_leaves = 1 << (max_depth - initial_depth); + let num_leaves_bits = &bits[initial_depth..]; + let mmr_instances = &new_instances[5..]; + // convert from u128 to bytes + let leaves: Vec<_> = mmr_instances + .chunks(2) + .take(num_leaves) + .map(|hash| -> Vec<_> { + hash.iter() + .flat_map(|hash_u128| { + uint_to_bytes_be(ctx, range, hash_u128, 16).into_iter().map(|x| x.into()) + }) + .collect() + }) + .collect(); + let new_mmr = keccak.merkle_mountain_range(ctx, &leaves, num_leaves_bits); + let new_mmr_len = new_mmr.len(); + debug_assert_eq!(new_mmr_len, max_depth - initial_depth + 1); + // convert from bytes to u128 + let new_mmr = new_mmr + .into_iter() + .zip(num_leaves_bits.iter().rev()) + .flat_map(|((_hash_bytes, hash_u128s), bit)| { + // hash_u128s is in hi-lo form + hash_u128s.map(|hash_u128| range.gate().mul(ctx, hash_u128, *bit)) + }) + .collect_vec(); + + // expose public instances + let assigned_instances = builder.public_instances(); + assert_eq!(assigned_instances.len(), 1); + assigned_instances[0].extend( + iter::empty() + .chain(accumulator) + .chain(new_instances[..5].to_vec()) + .chain(new_mmr) + .chain(mmr_instances[2 * num_leaves..].to_vec()), + ); + } + + fn virtual_assign_phase1( + &self, + _: &mut RlcCircuitBuilder, + _: &MPTChip, + _: Self::FirstPhasePayload, + ) { + // do nothing + } +} + +impl CircuitMetadata for EthBlockHeaderChainRootAggregationInput { + const HAS_ACCUMULATOR: bool = true; + /// The instance format exactly matches that of `EthBlockHeaderChainInput`. + fn num_instance(&self) -> Vec { + vec![NUM_FE_ACCUMULATOR + 2 + 2 + 1 + 2 * (self.inner.max_depth + 1)] + } +} diff --git a/axiom-core/src/aggregation/intermediate.rs b/axiom-core/src/aggregation/intermediate.rs new file mode 100644 index 00000000..4e5ddb26 --- /dev/null +++ b/axiom-core/src/aggregation/intermediate.rs @@ -0,0 +1,271 @@ +//! Intermediate aggregation circuits that aggregate in a binary tree topology: +//! The leaves of the tree are formed by [crate::header_chain::EthBlockHeaderChainCircuit]s, and intermediate notes +//! of the tree are formed by [EthBlockHeaderChainIntermediateAggregationCircuit]s. +//! +//! An [EthBlockHeaderChainIntermediateAggregationCircuit] can aggregate either: +//! - two [crate::header_chain::EthBlockHeaderChainCircuit]s or +//! - two [EthBlockHeaderChainIntermediateAggregationCircuit]s. +//! +//! The root of the aggregation tree will be a [super::final_merkle::EthBlockHeaderChainRootAggregationCircuit]. +//! The difference between Intermediate and Root aggregation circuits is that the Intermediate ones +//! do not have a keccak sub-circuit: all keccaks are delayed until the Root aggregation. +use anyhow::{bail, Result}; +use axiom_eth::{ + halo2_base::{ + gates::{circuit::CircuitBuilderStage, GateInstructions, RangeChip, RangeInstructions}, + utils::ScalarField, + AssignedValue, Context, + QuantumCell::{Constant, Existing, Witness}, + }, + halo2_proofs::{ + halo2curves::bn256::{Bn256, Fr}, + poly::kzg::commitment::ParamsKZG, + }, + snark_verifier_sdk::{ + halo2::aggregation::{AggregationCircuit, VerifierUniversality}, + Snark, SHPLONK, + }, + utils::snark_verifier::{ + get_accumulator_indices, AggregationCircuitParams, NUM_FE_ACCUMULATOR, + }, +}; +use itertools::Itertools; + +use crate::Field; + +/// Newtype to distinguish an aggregation circuit created from [EthBlockHeaderChainIntermediateAggregationInput] +pub struct EthBlockHeaderChainIntermediateAggregationCircuit(pub AggregationCircuit); + +impl EthBlockHeaderChainIntermediateAggregationCircuit { + /// The number of instances NOT INCLUDING the accumulator + pub fn get_num_instance(max_depth: usize, initial_depth: usize) -> usize { + assert!(max_depth >= initial_depth); + 5 + 2 * ((1 << (max_depth - initial_depth)) + initial_depth) + } +} + +/// The input to create an intermediate [AggregationCircuit] that aggregates [crate::header_chain::EthBlockHeaderChainCircuit]s. +/// These are intemediate aggregations because they do not perform additional keccaks. Therefore the public instance format (after excluding accumulators) is +/// different from that of the original [crate::header_chain::EthBlockHeaderChainCircuit]s. +#[derive(Clone, Debug)] +pub struct EthBlockHeaderChainIntermediateAggregationInput { + // aggregation circuit with `instances` the accumulator (two G1 points) for delayed pairing verification + pub num_blocks: u32, + /// `snarks` should be exactly two snarks of either + /// - `EthBlockHeaderChainCircuit` if `max_depth == initial_depth + 1` or + /// - `EthBlockHeaderChainIntermediateAggregationCircuit` (this circuit) otherwise + /// + /// Assumes `num_blocks > 0`. + pub snarks: Vec, + pub max_depth: usize, + 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. +} + +impl EthBlockHeaderChainIntermediateAggregationInput { + /// `snarks` should be exactly two snarks of either + /// - `EthBlockHeaderChainCircuit` if `max_depth == initial_depth + 1` or + /// - `EthBlockHeaderChainAggregationCircuit` otherwise + /// + /// Assumes `num_blocks > 0`. + pub fn new( + snarks: Vec, + num_blocks: u32, + max_depth: usize, + initial_depth: usize, + ) -> Self { + assert_ne!(num_blocks, 0); + assert_eq!(snarks.len(), 2); + assert!(max_depth > initial_depth); + assert!(num_blocks <= 1 << max_depth); + + Self { snarks, num_blocks, max_depth, initial_depth } + } +} + +impl EthBlockHeaderChainIntermediateAggregationInput { + pub fn build( + self, + stage: CircuitBuilderStage, + circuit_params: AggregationCircuitParams, + kzg_params: &ParamsKZG, + ) -> Result { + let num_blocks = self.num_blocks; + let max_depth = self.max_depth; + let initial_depth = self.initial_depth; + log::info!( + "New EthBlockHeaderChainAggregationCircuit | num_blocks: {num_blocks} | max_depth: {max_depth} | initial_depth: {initial_depth}" + ); + let prev_acc_indices = get_accumulator_indices(&self.snarks); + if self.max_depth == self.initial_depth + 1 + && prev_acc_indices.iter().any(|indices| !indices.is_empty()) + { + bail!("Snarks to be aggregated must not have accumulators: they should come from EthBlockHeaderChainCircuit"); + } + if self.max_depth > self.initial_depth + 1 + && prev_acc_indices.iter().any(|indices| indices.len() != NUM_FE_ACCUMULATOR) + { + bail!("Snarks to be aggregated must all be EthBlockHeaderChainIntermediateAggregationCircuits"); + } + let mut circuit = AggregationCircuit::new::( + stage, + circuit_params, + kzg_params, + self.snarks, + VerifierUniversality::None, + ); + let mut prev_instances = circuit.previous_instances().clone(); + // remove old accumulators + for (prev_instance, acc_indices) in prev_instances.iter_mut().zip_eq(prev_acc_indices) { + for i in acc_indices.into_iter().sorted().rev() { + prev_instance.remove(i); + } + } + + let builder = &mut circuit.builder; + // TODO: slight computational overhead from recreating RangeChip; builder should store RangeChip as OnceCell + let range = builder.range_chip(); + let ctx = builder.main(0); + let num_blocks_minus_one = ctx.load_witness(Fr::from(num_blocks as u64 - 1)); + + let new_instances = join_previous_instances::( + ctx, + &range, + prev_instances.try_into().unwrap(), + num_blocks_minus_one, + max_depth, + initial_depth, + ); + if builder.assigned_instances.len() != 1 { + bail!("should only have 1 instance column"); + } + assert_eq!(builder.assigned_instances[0].len(), NUM_FE_ACCUMULATOR); + builder.assigned_instances[0].extend(new_instances); + + Ok(EthBlockHeaderChainIntermediateAggregationCircuit(circuit)) + } +} + +/// Takes the concatenated previous instances from two `EthBlockHeaderChainIntermediateAggregationCircuit`s +/// of max depth `max_depth - 1` and +/// - checks that they form a chain of `max_depth` +/// - updates the merkle mountain range: +/// - stores the latest `2^{max_depth - initial_depth}` roots for keccak later +/// - selects the correct last `initial_depth` roots for the smallest part of the range +/// +/// If `max_depth - 1 == initial_depth`, then the previous instances are from two `EthBlockHeaderChainCircuit`s. +/// +/// Returns the new instances for the depth `max_depth` circuit (without accumulators) +/// +/// ## Assumptions +/// - `prev_instances` are the previous instances **with old accumulators removed**. +pub fn join_previous_instances( + ctx: &mut Context, + range: &RangeChip, + prev_instances: [Vec>; 2], + num_blocks_minus_one: AssignedValue, + max_depth: usize, + initial_depth: usize, +) -> Vec> { + let prev_depth = max_depth - 1; + let num_instance = EthBlockHeaderChainIntermediateAggregationCircuit::get_num_instance( + prev_depth, + initial_depth, + ); + assert_eq!(num_instance, prev_instances[0].len()); + assert_eq!(num_instance, prev_instances[1].len()); + + let [instance0, instance1] = prev_instances; + let mountain_selector = range.is_less_than_safe(ctx, num_blocks_minus_one, 1u64 << prev_depth); + + // join block hashes + let prev_hash = &instance0[..2]; + let intermed_hash0 = &instance0[2..4]; + let intermed_hash1 = &instance1[..2]; + let end_hash = &instance1[2..4]; + for (a, b) in intermed_hash0.iter().zip(intermed_hash1.iter()) { + // a == b || num_blocks <= 2^prev_depth + let mut eq_check = range.gate().is_equal(ctx, *a, *b); + eq_check = range.gate().or(ctx, eq_check, mountain_selector); + range.gate().assert_is_const(ctx, &eq_check, &F::ONE); + } + let end_hash = intermed_hash0 + .iter() + .zip(end_hash.iter()) + .map(|(a, b)| range.gate().select(ctx, *a, *b, mountain_selector)) + .collect_vec(); + + // join & sanitize block numbers + let (start_block_number, intermed_block_num0) = split_u64_into_u32s(ctx, range, instance0[4]); + let (intermed_block_num1, mut end_block_number) = split_u64_into_u32s(ctx, range, instance1[4]); + let num_blocks0_minus_one = range.gate().sub(ctx, intermed_block_num0, start_block_number); + let num_blocks1_minus_one = range.gate().sub(ctx, end_block_number, intermed_block_num1); + range.check_less_than_safe(ctx, num_blocks0_minus_one, 1 << prev_depth); + range.check_less_than_safe(ctx, num_blocks1_minus_one, 1 << prev_depth); + + end_block_number = + range.gate().select(ctx, intermed_block_num0, end_block_number, mountain_selector); + // make sure chains link up + let next_block_num0 = range.gate().add(ctx, intermed_block_num0, Constant(F::ONE)); + let mut eq_check = range.gate().is_equal(ctx, next_block_num0, intermed_block_num1); + eq_check = range.gate().or(ctx, eq_check, mountain_selector); + range.gate().assert_is_const(ctx, &eq_check, &F::ONE); + // if num_blocks > 2^prev_depth, then num_blocks0 must equal 2^prev_depth + let prev_max_blocks = range.gate().pow_of_two()[prev_depth]; + let is_max_depth0 = + range.gate().is_equal(ctx, num_blocks0_minus_one, Constant(prev_max_blocks - F::ONE)); + eq_check = range.gate().or(ctx, is_max_depth0, mountain_selector); + range.gate().assert_is_const(ctx, &eq_check, &F::ONE); + // check number of blocks is correct + let boundary_num_diff = range.gate().sub(ctx, end_block_number, start_block_number); + ctx.constrain_equal(&boundary_num_diff, &num_blocks_minus_one); + // concatenate block numbers + let boundary_block_numbers = range.gate().mul_add( + ctx, + Constant(range.gate().pow_of_two()[32]), + start_block_number, + end_block_number, + ); + + // update merkle roots + let roots0 = &instance0[5..]; + let roots1 = &instance1[5..]; + let cutoff = 2 * (1 << (prev_depth - initial_depth)); + + // join merkle mountain ranges + let mut instances = Vec::with_capacity(num_instance + cutoff); + instances.extend_from_slice(prev_hash); + instances.extend_from_slice(&end_hash); + instances.push(boundary_block_numbers); + instances.extend_from_slice(&roots0[..cutoff]); + instances.extend_from_slice(&roots1[..cutoff]); + instances.extend( + roots0[cutoff..] + .iter() + .zip(roots1[cutoff..].iter()) + .map(|(a, b)| range.gate().select(ctx, *a, *b, mountain_selector)), + ); + + instances +} + +fn split_u64_into_u32s( + ctx: &mut Context, + range: &RangeChip, + num: AssignedValue, +) -> (AssignedValue, AssignedValue) { + let v = num.value().get_lower_64(); + let first = F::from(v >> 32); + let second = F::from(v & u32::MAX as u64); + ctx.assign_region( + [Witness(second), Witness(first), Constant(F::from(1u64 << 32)), Existing(num)], + [0], + ); + let second = ctx.get(-4); + let first = ctx.get(-3); + for limb in [first, second] { + range.range_check(ctx, limb, 32); + } + (first, second) +} diff --git a/axiom-core/src/aggregation/mod.rs b/axiom-core/src/aggregation/mod.rs new file mode 100644 index 00000000..0f4d4082 --- /dev/null +++ b/axiom-core/src/aggregation/mod.rs @@ -0,0 +1,2 @@ +pub mod final_merkle; +pub mod intermediate; diff --git a/axiom-core/src/bin/keygen.rs b/axiom-core/src/bin/keygen.rs new file mode 100644 index 00000000..96fdfee7 --- /dev/null +++ b/axiom-core/src/bin/keygen.rs @@ -0,0 +1,79 @@ +use std::{ + collections::BTreeMap, + env, + fs::{self, File}, + path::PathBuf, +}; + +use anyhow::Context; +use axiom_core::{keygen::RecursiveCoreIntent, types::CoreNodeType}; +use axiom_eth::{ + snark_verifier_sdk::{evm::gen_evm_verifier_shplonk, halo2::aggregation::AggregationCircuit}, + utils::build_utils::keygen::read_srs_from_dir, +}; +use clap::Parser; + +#[derive(Parser, Debug)] +pub struct Cli { + #[arg(long = "srs-dir")] + pub srs_dir: PathBuf, + #[arg(long = "data-dir")] + pub data_dir: Option, + #[arg(long = "intent")] + pub intent_path: PathBuf, + /// Tag for the output circuit IDs files. Defaults to the root circuit ID. We auto-add the .cids extension. + #[arg(short, long = "tag")] + pub tag: Option, +} + +fn main() -> anyhow::Result<()> { + env_logger::try_init().unwrap(); + let cli = Cli::parse(); + let srs_dir = cli.srs_dir; + let data_dir = cli.data_dir.unwrap_or_else(|| { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + PathBuf::from(cargo_manifest_dir).join("data").join("playground") + }); + fs::create_dir_all(&data_dir)?; + // Directly deserializing from yaml doesn't work, but going to json first does?? + let intent_json: serde_json::Value = serde_yaml::from_reader( + File::open(&cli.intent_path) + .with_context(|| format!("Failed to open file {}", cli.intent_path.display()))?, + )?; + let intent: RecursiveCoreIntent = serde_json::from_value(intent_json)?; + let node_type = intent.params.node_type; + let k = intent.k_at_depth[0]; + let mut cid_repo = BTreeMap::new(); + let (proof_node, pk, pinning) = + intent.create_and_serialize_proving_key(&srs_dir, &data_dir, &mut cid_repo)?; + println!("Circuit id: {}", proof_node.circuit_id); + + if matches!(node_type, CoreNodeType::Evm(_)) { + log::debug!("Creating verifier contract"); + let num_instance: Vec = serde_json::from_value(pinning["num_instance"].clone())?; + let solc_path = data_dir.join(format!("{}.sol", proof_node.circuit_id)); + let kzg_params = read_srs_from_dir(&srs_dir, k as u32)?; + gen_evm_verifier_shplonk::( + &kzg_params, + pk.get_vk(), + num_instance, + Some(&solc_path), + ); + println!("Verifier contract written to {}", solc_path.display()); + } + + let tag = cli.tag.unwrap_or_else(|| proof_node.circuit_id.clone()); + + // Why do we need to do this? https://stackoverflow.com/questions/62977485/how-to-serialise-and-deserialise-btreemaps-with-arbitrary-key-types + let cids: Vec<_> = cid_repo + .into_iter() + .map(|(key, cid)| (serde_json::to_string(&key).unwrap(), cid)) + .collect(); + let cid_path = data_dir.join(format!("{tag}.cids")); + let f = File::create(&cid_path).with_context(|| { + format!("Failed to create circuit IDs repository file {}", cid_path.display()) + })?; + serde_json::to_writer_pretty(f, &cids)?; + println!("Wrote circuit IDs repository to: {}", cid_path.display()); + Ok(()) +} diff --git a/axiom-core/src/bin/readme.md b/axiom-core/src/bin/readme.md new file mode 100644 index 00000000..a0e1ef7c --- /dev/null +++ b/axiom-core/src/bin/readme.md @@ -0,0 +1,29 @@ +# Proving and Verifying Key Generation + +To recursively run proving key generation on all circuits in an aggregation tree specified by an intent file, you can install the keygen binary to your path via: + +```bash +cargo install --path axiom-core --force +``` +This builds `axiom-core-keygen` binary in release mode and installs it to your path. +Then run: +```bash +axiom-core-keygen --srs-dir --intent configs/production/core.yml --tag --data-dir +``` +to actually generate the proving keys. + +For faster compile times, you can run the keygen binary directly in dev mode (still with `opt-level=3`) via: +```bash +CARGO_PROFILE_DEV_DEBUG_ASSERTIONS=false cargo run --bin axiom-core-keygen -- --srs-dir --intent configs/production/core.yml --tag --data-dir +``` +Debug assertions needs to be **off** as we use dummy witnesses that do not pass certain debug assertions. + +* A file with the mapping of circuit types to circuit IDs will be output to a `.cids` file as a JSON. +* `` defaults to `` if not specified. + +To only get the raw list of circuit IDs from the `.cids` file, run: + +```bash +jq -r '.[][1]' .cids > .txt +``` +with `jq` installed. diff --git a/axiom-core/src/bin/rename_snark_verifier.sh b/axiom-core/src/bin/rename_snark_verifier.sh new file mode 100644 index 00000000..d1c58d6e --- /dev/null +++ b/axiom-core/src/bin/rename_snark_verifier.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Check if a file name is provided +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +# File to be modified +FILE="$1" + +# The first and third line are empty +# Remove the first and third line from the file +sed '1d;3d' $FILE > "$FILE.0" + +base_name=$(basename $FILE) +if [ $base_name == "39cb264c605428fc752e90b6ac1b77427ab06b795419a759e237e283b95f377f.sol" ]; then + # Replace 'contract Halo2Verifier' with 'contract AxiomV2CoreVerifier' + new_file="AxiomV2CoreVerifier.sol" + sed "s/contract Halo2Verifier/contract AxiomV2CoreVerifier/g" "$FILE.0" > $new_file + rm -f $FILE.0 +elif [ $base_name == "0379c723deafac09822de4f36da40a5595331c447a5cc7c342eb839cd199be02.sol" ]; then + # Replace 'contract Halo2Verifier' with 'contract AxiomV2CoreHistoricalVerifier' + new_file="AxiomV2CoreHistoricalVerifier.sol" + sed "s/contract Halo2Verifier/contract AxiomV2CoreHistoricalVerifier/g" "$FILE.0" > $new_file +else + echo "Unknown file" + exit 1 +fi + +echo "Modifications complete. New file output to $new_file" +rm -f "$FILE.0" + +echo "To diff is:" +diff $FILE $new_file + +echo "Running forge fmt on $new_file" +forge fmt $new_file diff --git a/axiom-core/src/header_chain.rs b/axiom-core/src/header_chain.rs new file mode 100644 index 00000000..2346688f --- /dev/null +++ b/axiom-core/src/header_chain.rs @@ -0,0 +1,170 @@ +use std::{iter, marker::PhantomData}; + +use axiom_eth::{ + block_header::{ + get_block_header_rlp_max_lens_from_extra, get_boundary_block_data, EthBlockHeaderChip, + EthBlockHeaderWitness, + }, + halo2_base::{ + gates::{GateInstructions, RangeInstructions}, + AssignedValue, + QuantumCell::Constant, + }, + mpt::MPTChip, + rlc::circuit::builder::RlcCircuitBuilder, + utils::assign_vec, + utils::{ + build_utils::aggregation::CircuitMetadata, eth_circuit::EthCircuitInstructions, + keccak::decorator::RlcKeccakCircuitImpl, + }, +}; +use itertools::Itertools; + +use crate::Field; + +pub type EthBlockHeaderChainCircuit = RlcKeccakCircuitImpl>; + +/// The input datum for the block header chain circuit. It is used to generate a circuit. +/// +/// The public instances: +/// * prev_hash (hi-lo) +/// * end_hash (hi-lo) +/// * solidityPacked(["uint32", "uint32"], [start_block_number, end_block_number]) (F) +/// * merkle_roots: [HiLo; max_depth + 1] +#[derive(Clone, Debug)] +pub struct EthBlockHeaderChainInput { + /// The private inputs, which are the RLP encodings of the block headers + header_rlp_encodings: Vec>, + num_blocks: u32, // num_blocks in [1, 2 ** max_depth] + max_depth: usize, + /// Configuration parameters of the maximum number of bytes in the extra data field. + /// This is mostly to distinguish between mainnet and Goerli (or other forks). + max_extra_data_bytes: usize, + _marker: PhantomData, +} + +impl EthBlockHeaderChainInput { + /// Handles resizing of each `header_rlp_encodings` to max length and also + /// resizes the number of rlp encodings to 2^max_depth. + pub fn new( + mut header_rlp_encodings: Vec>, + num_blocks: u32, + max_depth: usize, + max_extra_data_bytes: usize, + ) -> Self { + let (header_rlp_max_bytes, _) = + get_block_header_rlp_max_lens_from_extra(max_extra_data_bytes); + // pad to correct length with dummies + let dummy_block_rlp = header_rlp_encodings[0].clone(); + header_rlp_encodings.resize(1 << max_depth, dummy_block_rlp); + for header_rlp in header_rlp_encodings.iter_mut() { + header_rlp.resize(header_rlp_max_bytes, 0u8); + } + Self { + header_rlp_encodings, + num_blocks, + max_depth, + max_extra_data_bytes, + _marker: PhantomData, + } + } +} + +/// Data passed from phase0 to phase1 +#[derive(Clone, Debug)] +pub struct EthBlockHeaderchainWitness { + pub max_extra_data_bytes: usize, + /// The chain of blocks, where the hash of block_chain\[i\] is proved to be the parent hash of block_chain\[i+1\] + pub block_chain: Vec>, + pub num_blocks_minus_one: AssignedValue, + pub indicator: Vec>, +} + +impl EthCircuitInstructions for EthBlockHeaderChainInput { + type FirstPhasePayload = EthBlockHeaderchainWitness; + + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + ) -> Self::FirstPhasePayload { + let chip = EthBlockHeaderChip::new(mpt.rlp, self.max_extra_data_bytes); + let keccak = mpt.keccak(); + + // ======== FIRST PHASE =========== + let ctx = builder.base.main(0); + // ==== 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 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 ==== + let max_len = get_block_header_rlp_max_lens_from_extra(self.max_extra_data_bytes).0; + let block_headers = self + .header_rlp_encodings + .iter() + .map(|header| assign_vec(ctx, header.clone(), max_len)) + .collect_vec(); + let block_chain_witness = + chip.decompose_block_header_chain_phase0(builder, keccak, block_headers); + // All keccaks must be done in FirstPhase, so we compute the merkle mountain range from the RLP decoded witnesses now + let ctx = builder.base.main(0); + let num_leaves_bits = chip.gate().num_to_bits(ctx, num_blocks, self.max_depth + 1); + let block_hashes = block_chain_witness + .iter() + .map(|witness| witness.block_hash.output_bytes.as_ref().to_vec()) + .collect_vec(); + // mountain range in bytes + let mountain_range = keccak.merkle_mountain_range(ctx, &block_hashes, &num_leaves_bits); + let mountain_range = mountain_range + .into_iter() + .zip(num_leaves_bits.into_iter().rev()) + .flat_map(|((_hash_bytes, hash_u128s), bit)| { + // if the bit is 0, then we set the hash root to 0 + hash_u128s.map(|hash_u128| chip.gate().mul(ctx, hash_u128, bit)) + }) + .collect_vec(); + + let indicator = + chip.gate().idx_to_indicator(ctx, num_blocks_minus_one, block_chain_witness.len()); + let (prev_block_hash, end_block_hash, block_numbers) = + get_boundary_block_data(ctx, chip.gate(), &block_chain_witness, &indicator); + let assigned_instances = iter::empty() + .chain(prev_block_hash) + .chain(end_block_hash) + .chain(iter::once(block_numbers)) + .chain(mountain_range) + .collect_vec(); + builder.base.assigned_instances[0] = assigned_instances; + + EthBlockHeaderchainWitness { + max_extra_data_bytes: self.max_extra_data_bytes, + block_chain: block_chain_witness, + num_blocks_minus_one, + indicator, + } + } + + fn virtual_assign_phase1( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + witness: Self::FirstPhasePayload, + ) { + let chip = EthBlockHeaderChip::new(mpt.rlp, witness.max_extra_data_bytes); + let _block_chain_trace = chip.decompose_block_header_chain_phase1( + builder, + witness.block_chain, + Some((witness.num_blocks_minus_one, witness.indicator)), + ); + } +} + +impl CircuitMetadata for EthBlockHeaderChainInput { + const HAS_ACCUMULATOR: bool = false; + fn num_instance(&self) -> Vec { + vec![2 + 2 + 1 + 2 * (self.max_depth + 1)] + } +} diff --git a/axiom-core/src/keygen/mod.rs b/axiom-core/src/keygen/mod.rs new file mode 100644 index 00000000..c613185e --- /dev/null +++ b/axiom-core/src/keygen/mod.rs @@ -0,0 +1,392 @@ +use std::{collections::BTreeMap, path::Path, sync::Arc}; + +use axiom_eth::{ + block_header::GENESIS_BLOCK_RLP, + halo2_base::{ + gates::circuit::CircuitBuilderStage, + utils::halo2::{KeygenCircuitIntent, ProvingKeyGenerator}, + }, + halo2_proofs::{ + plonk::{Circuit, ProvingKey}, + poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG}, + }, + halo2curves::bn256::{Bn256, Fr, G1Affine}, + snark_verifier_sdk::{ + halo2::{ + aggregation::AggregationCircuit, + utils::{ + AggregationDependencyIntent, AggregationDependencyIntentOwned, + KeygenAggregationCircuitIntent, + }, + }, + CircuitExt, Snark, + }, + utils::{ + build_utils::{ + aggregation::get_dummy_aggregation_params, + keygen::{ + compile_agg_dep_to_protocol, get_dummy_rlc_keccak_params, read_srs_from_dir, + write_pk_and_pinning, + }, + pinning::aggregation::{AggTreeId, GenericAggParams, GenericAggPinning}, + }, + merkle_aggregation::keygen::AggIntentMerkle, + DEFAULT_RLC_CACHE_BITS, + }, +}; +use serde::{Deserialize, Serialize}; + +use crate::{ + aggregation::{ + final_merkle::{ + EthBlockHeaderChainRootAggregationCircuit, EthBlockHeaderChainRootAggregationInput, + }, + intermediate::EthBlockHeaderChainIntermediateAggregationInput, + }, + header_chain::{EthBlockHeaderChainCircuit, EthBlockHeaderChainInput}, + types::{ + CoreNodeParams, CoreNodeType, CorePinningIntermediate, CorePinningLeaf, CorePinningRoot, + }, +}; + +/// Recursive intent for a node in the aggregation tree that can construct proving keys for this node and all its children. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct RecursiveCoreIntent { + /// `k_at_depth[i]` is the log2 domain size at depth `i`. So it starts from the current node and goes down the + /// layers of the tree. Our aggregation structure is such that each layer of the tree only has a single circuit type so only one `k` is needed. + pub k_at_depth: Vec, + /// Different chains (e.g., Goerli) can have different maximum number of bytes in the extra data field of the block header. + /// We configure the circuits differently based on this. + pub max_extra_data_bytes: usize, + pub params: CoreNodeParams, +} + +impl RecursiveCoreIntent { + pub fn new(k_at_depth: Vec, max_extra_data_bytes: usize, params: CoreNodeParams) -> Self { + Self { k_at_depth, max_extra_data_bytes, params } + } + /// Each layer of tree has a unique circuit type, so this is the child circuit type. + pub fn child(&self) -> Option { + assert!(!self.k_at_depth.is_empty()); + self.params.child(Some(self.max_extra_data_bytes)).map(|params| Self { + k_at_depth: self.k_at_depth[1..].to_vec(), + max_extra_data_bytes: self.max_extra_data_bytes, + params, + }) + } +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct CoreIntentLeaf { + pub k: u32, + /// Different chains (e.g., Goerli) can have different maximum number of bytes in the extra data field of the block header. + /// We configure the circuits differently based on this. + pub max_extra_data_bytes: usize, + /// The leaf layer of the aggregation starts with max number of block headers equal to 2depth. + pub depth: usize, +} + +#[derive(Clone, Debug)] +pub(crate) struct CoreIntentIntermediate { + pub k: u32, + // This is from bad UX; only svk = kzg_params.get_g()[0] is used + pub kzg_params: Arc>, + // There will be exponential duplication since both children have the same circuit type, but it seems better for clarity + pub to_agg: Vec, + /// There are always two children of the same type, so we only specify the intent for one of them + pub child_intent: AggregationDependencyIntentOwned, + /// The maximum number of block headers in the chain at this level of the tree is 2depth. + pub depth: usize, + /// The leaf layer of the aggregation starts with max number of block headers equal to 2initial_depth. + pub initial_depth: usize, +} + +#[derive(Clone, Debug)] +pub(crate) struct CoreIntentRoot { + pub k: u32, + // This is from bad UX; only svk = kzg_params.get_g()[0] is used + pub(crate) kzg_params: Arc>, + // There will be exponential duplication since both children have the same circuit type, but it seems better for clarity + pub to_agg: Vec, + /// There are always two children of the same type, so we only specify the intent for one of them + pub child_intent: AggregationDependencyIntentOwned, + /// The maximum number of block headers in the chain at this level of the tree is 2depth. + pub depth: usize, + /// The leaf layer of the aggregation starts with max number of block headers equal to 2initial_depth. + pub initial_depth: usize, +} + +/// Passthrough wrapper aggregation. +/// Internal version doesn't need any additional context. +#[derive(Clone, Debug)] +pub(crate) struct CoreIntentEvm { + pub k: u32, + // This is from bad UX; only svk = kzg_params.get_g()[0] is used + pub kzg_params: Arc>, + /// Tree of the single child + pub to_agg: AggTreeId, + /// Wrap single child + pub child_intent: AggregationDependencyIntentOwned, +} + +impl KeygenCircuitIntent for CoreIntentLeaf { + type ConcreteCircuit = EthBlockHeaderChainCircuit; + type Pinning = CorePinningLeaf; + fn get_k(&self) -> u32 { + self.k + } + fn build_keygen_circuit(self) -> Self::ConcreteCircuit { + let dummy_input = EthBlockHeaderChainInput::::new( + vec![GENESIS_BLOCK_RLP.to_vec(); 1 << self.depth], // the resizing of each header RLP is handled by constructor + 1, + self.depth, + self.max_extra_data_bytes, + ); + let circuit_params = get_dummy_rlc_keccak_params(self.k as usize, 8); + let mut circuit = EthBlockHeaderChainCircuit::new_impl( + CircuitBuilderStage::Keygen, + dummy_input, + circuit_params, + DEFAULT_RLC_CACHE_BITS, + ); + circuit.calculate_params(); + circuit + } + fn get_pinning_after_keygen( + self, + kzg_params: &ParamsKZG, + circuit: &Self::ConcreteCircuit, + ) -> Self::Pinning { + let params = circuit.params(); + let break_points = circuit.break_points(); + let num_instance = circuit.num_instance(); + let dk = (kzg_params.get_g()[0], kzg_params.g2(), kzg_params.s_g2()); + CorePinningLeaf { params, break_points, num_instance, dk: dk.into() } + } +} + +impl KeygenAggregationCircuitIntent for CoreIntentIntermediate { + fn intent_of_dependencies(&self) -> Vec { + vec![(&self.child_intent).into(); 2] + } + fn build_keygen_circuit_from_snarks(self, snarks: Vec) -> Self::AggregationCircuit { + assert_eq!(snarks.len(), 2); + + let input = EthBlockHeaderChainIntermediateAggregationInput::new( + snarks, + 1, + self.depth, + self.initial_depth, + ); + let circuit_params = get_dummy_aggregation_params(self.k as usize); + let mut circuit = + input.build(CircuitBuilderStage::Keygen, circuit_params, &self.kzg_params).unwrap(); + circuit.0.calculate_params(Some(20)); + circuit.0 + } +} + +impl KeygenCircuitIntent for CoreIntentIntermediate { + type ConcreteCircuit = AggregationCircuit; + type Pinning = CorePinningIntermediate; + fn get_k(&self) -> u32 { + self.k + } + fn build_keygen_circuit(self) -> Self::ConcreteCircuit { + self.build_keygen_circuit_shplonk() + } + fn get_pinning_after_keygen( + self, + kzg_params: &ParamsKZG, + circuit: &Self::ConcreteCircuit, + ) -> Self::Pinning { + let to_agg = compile_agg_dep_to_protocol(kzg_params, &self.child_intent, false); + let dk = (kzg_params.get_g()[0], kzg_params.g2(), kzg_params.s_g2()); + CorePinningIntermediate { + params: circuit.params(), + to_agg: vec![to_agg; self.to_agg.len()], + break_points: circuit.break_points(), + num_instance: circuit.num_instance(), + dk: dk.into(), + } + } +} + +impl KeygenAggregationCircuitIntent for CoreIntentRoot { + type AggregationCircuit = EthBlockHeaderChainRootAggregationCircuit; + fn intent_of_dependencies(&self) -> Vec { + vec![(&self.child_intent).into(); 2] + } + fn build_keygen_circuit_from_snarks(self, snarks: Vec) -> Self::AggregationCircuit { + assert_eq!(snarks.len(), 2); + + let input = EthBlockHeaderChainRootAggregationInput::new( + snarks, + 1, + self.depth, + self.initial_depth, + &self.kzg_params, + ) + .unwrap(); + // This is aggregation circuit, so set lookup bits to max + let circuit_params = get_dummy_rlc_keccak_params(self.k as usize, self.k as usize - 1); + // This is from bad UX; only svk = kzg_params.get_g()[0] is used + let mut circuit = EthBlockHeaderChainRootAggregationCircuit::new_impl( + CircuitBuilderStage::Keygen, + input, + circuit_params, + 0, // note: rlc is not used + ); + circuit.calculate_params(); + circuit + } +} + +impl KeygenCircuitIntent for CoreIntentRoot { + type ConcreteCircuit = EthBlockHeaderChainRootAggregationCircuit; + type Pinning = CorePinningRoot; + fn get_k(&self) -> u32 { + self.k + } + fn build_keygen_circuit(self) -> Self::ConcreteCircuit { + self.build_keygen_circuit_shplonk() + } + fn get_pinning_after_keygen( + self, + kzg_params: &ParamsKZG, + circuit: &Self::ConcreteCircuit, + ) -> Self::Pinning { + let params = circuit.params(); + let break_points = circuit.break_points(); + let to_agg = compile_agg_dep_to_protocol(kzg_params, &self.child_intent, false); + let dk = (kzg_params.get_g()[0], kzg_params.g2(), kzg_params.s_g2()); + CorePinningRoot { + params, + to_agg: vec![to_agg; self.to_agg.len()], + num_instance: circuit.num_instance(), + break_points, + dk: dk.into(), + } + } +} + +impl From for AggIntentMerkle { + fn from(value: CoreIntentEvm) -> Self { + AggIntentMerkle { + kzg_params: value.kzg_params, + to_agg: vec![value.to_agg], + deps: vec![value.child_intent], + k: value.k, + } + } +} + +impl KeygenCircuitIntent for CoreIntentEvm { + type ConcreteCircuit = AggregationCircuit; + type Pinning = GenericAggPinning; + fn get_k(&self) -> u32 { + self.k + } + fn build_keygen_circuit(self) -> Self::ConcreteCircuit { + AggIntentMerkle::from(self).build_keygen_circuit() + } + fn get_pinning_after_keygen( + self, + kzg_params: &ParamsKZG, + circuit: &Self::ConcreteCircuit, + ) -> Self::Pinning { + AggIntentMerkle::from(self).get_pinning_after_keygen(kzg_params, circuit) + } +} + +impl RecursiveCoreIntent { + /// Recursively creates and serializes proving keys and pinnings. + /// + /// Computes `circuit_id` as the blake3 hash of the halo2 VerifyingKey written to bytes. Writes proving key to `circuit_id.pk`, verifying key to `circuit_id.vk` and pinning to `circuit_id.json` in the `data_dir` directory. + /// + /// Returns the `circuit_id, proving_key, pinning`. + /// * `cid_repo` stores a mapping from the [CoreNodeParams] to the corresponding circuit ID. In an Axiom Core aggregation tree, the [CoreNodeParams] determines the node. + /// * `PARAMS_DIR` **must** be set because the aggregation circuit creation requires reading trusted setup files (this can be removed later). + pub fn create_and_serialize_proving_key( + self, + srs_dir: &Path, + data_dir: &Path, + cid_repo: &mut BTreeMap, + ) -> anyhow::Result<(AggTreeId, ProvingKey, serde_json::Value)> { + // If there is child, do it first + let child = if let Some(child_intent) = self.child() { + let is_aggregation = !matches!(child_intent.params.node_type, CoreNodeType::Leaf(_)); + let (child_id, child_pk, child_pinning) = + child_intent.create_and_serialize_proving_key(srs_dir, data_dir, cid_repo)?; + let num_instance: Vec = + serde_json::from_value(child_pinning["num_instance"].clone())?; + // !! ** ASSERTION: all aggregation circuits in Axiom Core have accumulator in indices 0..12 ** !! + // No aggregation circuits in Axiom Core are universal. + let agg_intent = AggregationDependencyIntentOwned { + vk: child_pk.get_vk().clone(), + num_instance, + accumulator_indices: is_aggregation + .then(|| AggregationCircuit::accumulator_indices().unwrap()), + agg_vk_hash_data: None, + }; + Some((child_id, agg_intent)) + } else { + None + }; + assert!(!self.k_at_depth.is_empty()); + let k = self.k_at_depth[0]; + let kzg_params = Arc::new(read_srs_from_dir(srs_dir, k)?); + let ((pk, pinning), children) = match self.params.node_type { + CoreNodeType::Leaf(max_extra_data_bytes) => { + let intent = + CoreIntentLeaf { k, max_extra_data_bytes, depth: self.params.initial_depth }; + (intent.create_pk_and_pinning(&kzg_params), vec![]) + } + CoreNodeType::Intermediate => { + let (child_id, child_intent) = child.unwrap(); + let to_agg = vec![child_id; 2]; + let intent = CoreIntentIntermediate { + k, + kzg_params: kzg_params.clone(), + to_agg: to_agg.clone(), + child_intent, + depth: self.params.depth, + initial_depth: self.params.initial_depth, + }; + (intent.create_pk_and_pinning(&kzg_params), to_agg) + } + CoreNodeType::Root => { + let (child_id, child_intent) = child.unwrap(); + let to_agg = vec![child_id; 2]; + let intent = CoreIntentRoot { + k, + kzg_params: kzg_params.clone(), + to_agg: to_agg.clone(), + child_intent, + depth: self.params.depth, + initial_depth: self.params.initial_depth, + }; + (intent.create_pk_and_pinning(&kzg_params), to_agg) + } + CoreNodeType::Evm(_) => { + let (child_id, child_intent) = child.unwrap(); + let to_agg = vec![child_id.clone()]; + let intent = CoreIntentEvm { + k, + to_agg: child_id, + child_intent, + kzg_params: kzg_params.clone(), + }; + (intent.create_pk_and_pinning(&kzg_params), to_agg) + } + }; + let circuit_id = write_pk_and_pinning(data_dir, &pk, &pinning)?; + if let Some(old_cid) = cid_repo.insert(self.params, circuit_id.clone()) { + if old_cid != circuit_id { + anyhow::bail!("Different circuit ID for the same node params") + } + } + let tree_id = AggTreeId { circuit_id, children, aggregate_vk_hash: None }; + Ok((tree_id, pk, pinning)) + } +} diff --git a/axiom-core/src/lib.rs b/axiom-core/src/lib.rs new file mode 100644 index 00000000..1449d755 --- /dev/null +++ b/axiom-core/src/lib.rs @@ -0,0 +1,16 @@ +pub use axiom_eth; + +/// Aggregation circuits +pub mod aggregation; +/// Circuit that parses RLP encoded block headers and constrains that the block headers actually form a block chain. +pub mod header_chain; +#[cfg(feature = "keygen")] +/// Intents and utilities for generating proving and verifying keys for production +pub mod keygen; +/// Types for different nodes in Axiom Core aggregation tree +pub mod types; + +#[cfg(test)] +pub mod tests; + +pub use axiom_eth::Field; diff --git a/axiom-core/src/tests/chain_instance.rs b/axiom-core/src/tests/chain_instance.rs new file mode 100644 index 00000000..4c1009fc --- /dev/null +++ b/axiom-core/src/tests/chain_instance.rs @@ -0,0 +1,115 @@ +use axiom_eth::utils::encode_h256_to_hilo; +use ethers_core::types::H256; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::Field; + +#[derive(Serialize, Deserialize)] +pub 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 } + } + 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_hilo::(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_hilo::(hash).hi_lo()) + .collect_vec(); + + [&prev_hash.hi_lo()[..], &end_hash.hi_lo()[..], &[block_numbers], &merkle_mountain_range] + .concat() + } + + /*fn from_instance(instance: &[F]) -> Self { + let prev_hash = decode_f(&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) + }*/ +} + +/* +// OLD, no longer needed except for debugging +let chain_instance = { + // 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| { + EthBlockHeaderChainInstance::from_instance( + &snarks[i].instances[0][instance_start_idx..], + ) + }); + + let mut roots = Vec::with_capacity((1 << (max_depth - initial_depth)) + initial_depth); + let cutoff = 1 << (max_depth - initial_depth - 1); + roots.extend_from_slice(&instance0.merkle_mountain_range[..cutoff]); + roots.extend_from_slice(&instance1.merkle_mountain_range[..cutoff]); + if num_blocks <= 1 << (max_depth - 1) { + assert_eq!( + instance0.end_block_number - instance0.start_block_number, + num_blocks - 1 + ); + roots.extend_from_slice(&instance0.merkle_mountain_range[cutoff..]); + } else { + assert_eq!(instance0.end_hash, instance1.prev_hash); + assert_eq!( + instance0.end_block_number - instance0.start_block_number, + (1 << (max_depth - 1)) - 1 + ); + assert_eq!(instance0.end_block_number, instance1.start_block_number - 1); + assert_eq!( + instance1.end_block_number - instance0.start_block_number, + num_blocks - 1 + ); + roots.extend_from_slice(&instance1.merkle_mountain_range[cutoff..]); + }; + EthBlockHeaderChainInstance { + prev_hash: instance0.prev_hash, + end_hash: if num_blocks <= 1 << (max_depth - 1) { + instance0.end_hash + } else { + instance1.end_hash + }, + start_block_number: instance0.start_block_number, + end_block_number: instance0.start_block_number + num_blocks - 1, + merkle_mountain_range: roots, + } +};*/ + +// RootAggregation +/* // 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); + new_mmr.extend_from_slice( + &inner.chain_instance.merkle_mountain_range[1 << (max_depth - initial_depth)..], + ); + inner.chain_instance.merkle_mountain_range = new_mmr; +*/ diff --git a/axiom-core/src/tests/integration.rs b/axiom-core/src/tests/integration.rs new file mode 100644 index 00000000..fa28be1e --- /dev/null +++ b/axiom-core/src/tests/integration.rs @@ -0,0 +1,403 @@ +use std::{ + fs::File, + path::{Path, PathBuf}, +}; + +use axiom_eth::{ + block_header::get_block_header_extra_bytes, + halo2_base::{gates::circuit::CircuitBuilderStage, utils::fs::gen_srs}, + halo2_proofs::plonk::Circuit, + halo2curves::bn256::Fr, + providers::{ + block::{get_block_rlp, get_blocks}, + setup_provider, + }, + rlc::virtual_region::RlcThreadBreakPoints, + snark_verifier_sdk::{ + evm::{evm_verify, gen_evm_proof_shplonk, gen_evm_verifier_shplonk}, + gen_pk, + halo2::{aggregation::AggregationCircuit, gen_snark_shplonk}, + read_pk, CircuitExt, Snark, + }, + utils::{ + build_utils::pinning::{aggregation::AggregationCircuitPinning, Halo2CircuitPinning}, + get_merkle_mountain_range, + keccak::decorator::RlcKeccakCircuitParams, + merkle_aggregation::InputMerkleAggregation, + snark_verifier::{EnhancedSnark, NUM_FE_ACCUMULATOR}, + DEFAULT_RLC_CACHE_BITS, + }, +}; +use ethers_core::types::Chain; +use itertools::Itertools; +use serde::{de::DeserializeOwned, Serialize}; +use test_log::test; + +use crate::{ + aggregation::{ + final_merkle::{ + EthBlockHeaderChainRootAggregationCircuit, EthBlockHeaderChainRootAggregationInput, + }, + intermediate::EthBlockHeaderChainIntermediateAggregationInput, + }, + header_chain::{EthBlockHeaderChainCircuit, EthBlockHeaderChainInput}, +}; + +use super::chain_instance::EthBlockHeaderChainInstance; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub enum Finality { + /// Produces as many snarks as needed to fit the entire block number range, without any final processing. + None, + /// The block number range must fit within the specified max depth. + /// Produces a single final snark with the starting & ending block numbers, previous and last block hashes, + /// and merkle mountain range as output. + Root, + /// The block number range must fit within the specified max depth. `Evm(round)` performs `round + 1` + /// rounds of SNARK verification on the final `Merkle` circuit + Evm(usize), +} + +fn fname(network: Chain, initial_depth: usize, depth: usize, finality: Finality) -> String { + let prefix = if depth == initial_depth { + format!("{}_{}", network, depth) + } else { + format!("{}_{}_{}", network, depth, initial_depth) + }; + let suffix = match finality { + Finality::None => "".to_string(), + Finality::Root => "_root".to_string(), + Finality::Evm(round) => format!("_for_evm_{round}"), + }; + format!("{}{}", prefix, suffix) +} + +fn read_json(path: impl AsRef) -> T { + serde_json::from_reader(File::open(path).unwrap()).unwrap() +} +fn write_json(path: impl AsRef, value: &T) { + serde_json::to_writer_pretty(File::create(path).unwrap(), value).unwrap(); +} + +type KeccakPinning = (RlcKeccakCircuitParams, RlcThreadBreakPoints); + +/// Does binary tree aggregation with leaf circuit EthBlockHeaderChainCircuit of depth `initial_depth`. +/// aggregates intermediate layers up to depth `max_depth`. For the root aggregation of depth `max_depth` +/// it will either use EthBlockHeaderIntermediateAggregationCircuit if finality is None, or else +/// EthBlockHeaderRootAggregationCircuit. +/// If finality is `Evm(round)`, then it will perform `round + 1` rounds of passthrough SNARK verification +/// using MerkleAggregationCircuit (with a single snark there is no merklelization, it is just passthrough). +/// +/// Proof will be for blocks [start_num, stop_num) +pub fn header_tree_aggregation( + network: Chain, + start_num: usize, + stop_num: usize, + initial_depth: usize, + max_depth: usize, + finality: Finality, +) { + // ===== Initial leaf layer ==== + // get RLP encoded headers + let provider = setup_provider(network); + let blocks = get_blocks(&provider, start_num as u64..stop_num as u64).unwrap(); + let header_rlp_encodings = + blocks.iter().map(|block| get_block_rlp(block.as_ref().unwrap())).collect_vec(); + + // create pkey + let name = fname(network, initial_depth, initial_depth, Finality::None); + let pk_path = PathBuf::from(format!("data/tests/{}.pk", &name)); + let pinning_path = format!("configs/tests/{}.json", &name); + let header_extra_bytes = get_block_header_extra_bytes(network); + let pinning: KeccakPinning = read_json(&pinning_path); + + let params = gen_srs(pinning.0.k() as u32); + let (pk, pinning) = + if let Ok(pk) = read_pk::>(&pk_path, pinning.0.clone()) { + (pk, pinning) + } else { + let first_rlp = header_rlp_encodings[0].clone(); + let input = EthBlockHeaderChainInput::::new( + vec![first_rlp], + 1, + initial_depth, + header_extra_bytes, + ); + let mut circuit = EthBlockHeaderChainCircuit::new_impl( + CircuitBuilderStage::Keygen, + input, + pinning.0, + DEFAULT_RLC_CACHE_BITS, + ); + circuit.calculate_params(); + let pk = gen_pk(¶ms, &circuit, Some(&pk_path)); + let pinning = (circuit.params(), circuit.break_points()); + write_json(pinning_path, &pinning); + (pk, pinning) + }; + let mut snarks: Vec = vec![]; + for start in (start_num..stop_num).step_by(1 << initial_depth) { + let stop = std::cmp::min(start + (1 << initial_depth), stop_num) as usize; + let rlps = header_rlp_encodings[start - start_num..stop - start_num].to_vec(); + let input = EthBlockHeaderChainInput::::new( + rlps, + (stop - start) as u32, + initial_depth, + header_extra_bytes, + ); + let circuit = EthBlockHeaderChainCircuit::new_impl( + CircuitBuilderStage::Prover, + input, + pinning.0.clone(), + DEFAULT_RLC_CACHE_BITS, + ) + .use_break_points(pinning.1.clone()); + let snark_path = format!("data/tests/{}_{}_{}.snark", &name, start, stop); + let snark = gen_snark_shplonk(¶ms, &pk, circuit, Some(snark_path)); + snarks.push(snark); + } + drop(params); + drop(pk); + + // ====== Intermediate layers ====== + let mut last_inter_depth = max_depth - 1; + if finality == Finality::None { + last_inter_depth += 1; + } + for depth in initial_depth + 1..=last_inter_depth { + let prev_snarks = std::mem::take(&mut snarks); + let mut start = start_num; + + let name = fname(network, initial_depth, depth, Finality::None); + let pk_path = PathBuf::from(format!("data/tests/{}.pk", &name)); + let pinning_path = format!("configs/tests/{}.json", &name); + let pinning: AggregationCircuitPinning = read_json(&pinning_path); + let params = gen_srs(pinning.params.degree); + let (pk, pinning) = if let Ok(pk) = read_pk::(&pk_path, pinning.params) + { + (pk, pinning) + } else { + let stop = std::cmp::min(start + (1 << (depth - 1)), stop_num); + let input = EthBlockHeaderChainIntermediateAggregationInput::new( + vec![prev_snarks[0].clone(), prev_snarks[0].clone()], + (stop - start) as u32, + depth, + initial_depth, + ); + let mut circuit = + input.build(CircuitBuilderStage::Keygen, pinning.params, ¶ms).unwrap().0; + circuit.calculate_params(Some(9)); + let pk = gen_pk(¶ms, &circuit, Some(&pk_path)); + let pinning = AggregationCircuitPinning::new(circuit.params(), circuit.break_points()); + write_json(pinning_path, &pinning); + (pk, pinning) + }; + for snark_pair in prev_snarks.into_iter().chunks(2).into_iter() { + let mut snark_pair = snark_pair.collect_vec(); + if snark_pair.len() == 1 { + let first = snark_pair[0].clone(); + snark_pair.push(first); + } + let stop = std::cmp::min(start + (1 << depth), stop_num); + let input = EthBlockHeaderChainIntermediateAggregationInput::new( + snark_pair, + (stop - start) as u32, + depth, + initial_depth, + ); + let circuit = input + .build(CircuitBuilderStage::Prover, pinning.params, ¶ms) + .unwrap() + .0 + .use_break_points(pinning.break_points.clone()); + let snark_path = format!("data/tests/{}_{}_{}.snark", &name, start, stop); + let snark = gen_snark_shplonk(¶ms, &pk, circuit, Some(snark_path)); + snarks.push(snark); + start = stop; + } + } + if finality == Finality::None { + return; + } + // ==== Root layer ==== + let depth = max_depth; + let prev_snarks = std::mem::take(&mut snarks); + let mut start = start_num; + + let name = fname(network, initial_depth, depth, Finality::Root); + let pk_path = PathBuf::from(format!("data/tests/{}.pk", &name)); + let pinning_path = format!("configs/tests/{}.json", &name); + let pinning: KeccakPinning = read_json(&pinning_path); + let params = gen_srs(pinning.0.k() as u32); + let (pk, pinning) = if let Ok(pk) = + read_pk::(&pk_path, pinning.0.clone()) + { + (pk, pinning) + } else { + let stop = std::cmp::min(start + (1 << (depth - 1)), stop_num); + let input = EthBlockHeaderChainRootAggregationInput::new( + vec![prev_snarks[0].clone(), prev_snarks[0].clone()], + (stop - start) as u32, + depth, + initial_depth, + ¶ms, + ) + .unwrap(); + let mut circuit = EthBlockHeaderChainRootAggregationCircuit::new_impl( + CircuitBuilderStage::Keygen, + input, + pinning.0.clone(), + 0, // note: rlc is not used + ); + circuit.calculate_params(); + let pk = gen_pk(¶ms, &circuit, Some(&pk_path)); + let pinning = (circuit.params(), circuit.break_points()); + write_json(pinning_path, &pinning); + (pk, pinning) + }; + for snark_pair in prev_snarks.into_iter().chunks(2).into_iter() { + let mut snark_pair = snark_pair.collect_vec(); + if snark_pair.len() == 1 { + let first = snark_pair[0].clone(); + snark_pair.push(first); + } + let stop = std::cmp::min(start + (1 << depth), stop_num); + let input = EthBlockHeaderChainRootAggregationInput::new( + snark_pair, + (stop - start) as u32, + depth, + initial_depth, + ¶ms, + ) + .unwrap(); + let circuit = EthBlockHeaderChainRootAggregationCircuit::new_impl( + CircuitBuilderStage::Prover, + input, + pinning.0.clone(), + 0, // note: rlc is not used + ) + .use_break_points(pinning.1.clone()); + let snark_path = format!("data/tests/{}_{}_{}.snark", &name, start, stop); + let snark = gen_snark_shplonk(¶ms, &pk, circuit, Some(snark_path)); + snarks.push(snark); + start = stop; + } + drop(params); + drop(pk); + // ==== Passthrough verification to shrink snark size ==== + let mut final_instances = vec![]; + if let Finality::Evm(round) = finality { + for r in 0..=round { + let name = fname(network, initial_depth, depth, Finality::Evm(r)); + let pk_path = PathBuf::from(format!("data/tests/{}.pk", &name)); + let pinning_path = format!("configs/tests/{}.json", &name); + let pinning: AggregationCircuitPinning = read_json(&pinning_path); + let params = gen_srs(pinning.params.degree); + let (pk, pinning) = + if let Ok(pk) = read_pk::(&pk_path, pinning.params) { + (pk, pinning) + } else { + let input = + InputMerkleAggregation::new([EnhancedSnark::new(snarks[0].clone(), None)]); + let mut circuit = + input.build(CircuitBuilderStage::Keygen, pinning.params, ¶ms).unwrap(); + circuit.calculate_params(Some(9)); + let pk = gen_pk(¶ms, &circuit, Some(&pk_path)); + let pinning = + AggregationCircuitPinning::new(circuit.params(), circuit.break_points()); + write_json(pinning_path, &pinning); + (pk, pinning) + }; + let prev_snarks = std::mem::take(&mut snarks); + + if r < round { + let mut start = start_num; + for snark in prev_snarks { + let stop = std::cmp::min(start + (1 << depth), stop_num); + let input = InputMerkleAggregation::new([EnhancedSnark::new(snark, None)]); + let circuit = input + .build(CircuitBuilderStage::Prover, pinning.params, ¶ms) + .unwrap() + .use_break_points(pinning.break_points.clone()); + let snark_path = format!("data/tests/{}_{}_{}.snark", &name, start, stop); + let snark = gen_snark_shplonk(¶ms, &pk, circuit, Some(snark_path)); + snarks.push(snark); + start = stop; + } + } else { + // FINAL ROUND, do evm verification, requires solc installed + let num_instance = NUM_FE_ACCUMULATOR + 5 + 2 * (depth + 1); + let sol_path = PathBuf::from(format!("data/tests/{}.sol", &name)); + let deploy_code = gen_evm_verifier_shplonk::( + ¶ms, + pk.get_vk(), + vec![num_instance], + Some(&sol_path), + ); + for snark in prev_snarks { + let input = InputMerkleAggregation::new([EnhancedSnark::new(snark, None)]); + let circuit = input + .build(CircuitBuilderStage::Prover, pinning.params, ¶ms) + .unwrap() + .use_break_points(pinning.break_points.clone()); + let instances = circuit.instances(); + let proof = gen_evm_proof_shplonk(¶ms, &pk, circuit, instances.clone()); + evm_verify(deploy_code.clone(), instances.clone(), proof); + final_instances.push(instances); + } + } + } + } + // all done! + // check final instances + // if not Finality::Evm, check instances from snarks + if !snarks.is_empty() { + for snark in &snarks { + final_instances.push(snark.instances.clone()); + } + } + let mut start = start_num; + for instances in final_instances { + let stop = std::cmp::min(start + (1 << depth), stop_num); + let blocks = &blocks[start - start_num..stop - start_num]; + let prev_hash = blocks[0].clone().unwrap().parent_hash; + let block_hashes = blocks.iter().map(|b| b.clone().unwrap().hash.unwrap()).collect_vec(); + let end_hash = *block_hashes.last().unwrap(); + let mmr = get_merkle_mountain_range(&block_hashes, depth); + let chain_instance = EthBlockHeaderChainInstance::new( + prev_hash, + end_hash, + start as u32, + stop as u32 - 1, + mmr, + ) + .to_instance(); + // instances has accumulator, remove it + assert_eq!(&instances[0][NUM_FE_ACCUMULATOR..], &chain_instance); + + start = stop; + } +} + +#[test] +fn test_mainnet_header_chain_provider() { + header_tree_aggregation(Chain::Mainnet, 0x765fb3, 0x765fb3 + 7, 3, 3, Finality::None); +} + +#[test] +#[ignore = "requires a lot of memory"] +fn test_mainnet_header_chain_intermediate_aggregation() { + header_tree_aggregation(Chain::Mainnet, 0x765fb3, 0x765fb3 + 11, 3, 4, Finality::None); +} + +#[test] +#[ignore = "requires over 32G memory"] +fn test_mainnet_header_chain_root_aggregation() { + header_tree_aggregation(Chain::Mainnet, 0x765fb3, 0x765fb3 + 11, 3, 5, Finality::Root); +} + +#[test] +#[ignore = "requires over 32G memory and solc installed"] +fn test_mainnet_header_chain_aggregation_for_evm() { + header_tree_aggregation(Chain::Mainnet, 0x765fb3, 0x765fb3 + 11, 3, 5, Finality::Evm(0)); +} diff --git a/axiom-core/src/tests/mod.rs b/axiom-core/src/tests/mod.rs new file mode 100644 index 00000000..d07f84cf --- /dev/null +++ b/axiom-core/src/tests/mod.rs @@ -0,0 +1,103 @@ +use std::fs::File; + +use axiom_eth::{ + block_header::get_block_header_extra_bytes, + halo2_base::{ + gates::circuit::CircuitBuilderStage::{self, Keygen, Mock, Prover}, + utils::fs::gen_srs, + }, + halo2_proofs::{dev::MockProver, plonk::Circuit}, + halo2curves::bn256::Fr, + rlc::circuit::RlcCircuitParams, + snark_verifier_sdk::{gen_pk, halo2::gen_snark_shplonk}, + utils::{keccak::decorator::RlcKeccakCircuitParams, DEFAULT_RLC_CACHE_BITS}, +}; +use ethers_core::types::Chain; +use hex::FromHex; +use itertools::Itertools; +use test_log::test; + +use super::header_chain::*; + +pub mod chain_instance; +pub mod integration; + +fn get_rlc_keccak_params(path: &str) -> RlcKeccakCircuitParams { + serde_json::from_reader(File::open(path).unwrap()).unwrap() +} + +#[allow(dead_code)] +fn get_dummy_rlc_keccak_params(k: usize) -> RlcKeccakCircuitParams { + let mut rlc = RlcCircuitParams::default(); + rlc.base.k = k; + rlc.base.lookup_bits = Some(8); + rlc.base.num_instance_columns = 1; + RlcKeccakCircuitParams { rlc, keccak_rows_per_round: 20 } +} + +fn get_default_goerli_header_chain_circuit( + stage: CircuitBuilderStage, + circuit_params: RlcKeccakCircuitParams, +) -> EthBlockHeaderChainCircuit { + let network = Chain::Goerli; + let max_extra_data_bytes = get_block_header_extra_bytes(network); + let blocks: Vec = + serde_json::from_reader(File::open("data/headers/default_blocks_goerli.json").unwrap()) + .unwrap(); + let header_rlp_encodings = blocks.into_iter().map(|s| Vec::from_hex(s).unwrap()).collect_vec(); + let max_depth = 3; + + let input = + EthBlockHeaderChainInput::new(header_rlp_encodings, 7, max_depth, max_extra_data_bytes); + EthBlockHeaderChainCircuit::new_impl(stage, input, circuit_params, DEFAULT_RLC_CACHE_BITS) +} + +#[test] +pub fn test_multi_goerli_header_mock() { + // let circuit_params = get_dummy_rlc_keccak_params(k); + let circuit_params = get_rlc_keccak_params("configs/tests/multi_block.json"); + let k = circuit_params.k() as u32; + + let mut circuit = get_default_goerli_header_chain_circuit(Mock, circuit_params); + circuit.calculate_params(); + let instances = circuit.instances(); + MockProver::run(k, &circuit, instances).unwrap().assert_satisfied(); + serde_json::to_writer_pretty( + File::create("configs/tests/multi_block.json").unwrap(), + &circuit.params(), + ) + .unwrap(); +} + +#[test] +pub fn test_header_instances_constrained() { + let circuit_params = get_rlc_keccak_params("configs/tests/multi_block.json"); + let k = circuit_params.k() as u32; + + let circuit = get_default_goerli_header_chain_circuit(Mock, circuit_params); + + assert!( + MockProver::run(k, &circuit, vec![vec![]]).unwrap().verify().is_err(), + "instances were not constrained" + ); +} + +#[test] +pub fn test_multi_goerli_header_prover() { + let circuit_params = get_rlc_keccak_params("configs/tests/multi_block.json"); + let k = circuit_params.k() as u32; + + let mut circuit = get_default_goerli_header_chain_circuit(Keygen, circuit_params); + circuit.calculate_params(); + + let params = gen_srs(k); + let pk = gen_pk(¶ms, &circuit, None); + let circuit_params = circuit.params(); + let break_points = circuit.break_points(); + drop(circuit); + + let circuit = get_default_goerli_header_chain_circuit(Prover, circuit_params) + .use_break_points(break_points); + // this does proof verification automatically + gen_snark_shplonk(¶ms, &pk, circuit, None::<&str>); +} diff --git a/axiom-core/src/types.rs b/axiom-core/src/types.rs new file mode 100644 index 00000000..6e60a7f4 --- /dev/null +++ b/axiom-core/src/types.rs @@ -0,0 +1,132 @@ +use axiom_eth::{ + halo2_base::gates::flex_gate::MultiPhaseThreadBreakPoints, + halo2curves::bn256::{Bn256, G1Affine}, + rlc::virtual_region::RlcThreadBreakPoints, + snark_verifier::{pcs::kzg::KzgDecidingKey, verifier::plonk::PlonkProtocol}, + utils::{ + build_utils::pinning::aggregation::{GenericAggParams, GenericAggPinning}, + keccak::decorator::RlcKeccakCircuitParams, + snark_verifier::{AggregationCircuitParams, Base64Bytes}, + }, +}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +/// Circuit parameters by node type +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] +pub struct CoreNodeParams { + /// Type of the node in the aggregation tree. + pub node_type: CoreNodeType, + /// The maximum number of block headers in the chain at this level of the tree is 2depth. + pub depth: usize, + /// The leaf layer of the aggregation starts with max number of block headers equal to 2initial_depth. + pub initial_depth: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] +pub enum CoreNodeType { + /// Leaf(max_extra_data_bytes) + /// + /// Different chains (e.g., Goerli) can have different maximum number of bytes in the extra data field of the block header. + /// We configure the circuits differently based on this. + Leaf(usize), + /// Produces as many snarks as needed to fit the entire block number range, without any final processing. + Intermediate, + /// The block number range must fit within the specified max depth. + /// Produces a single final snark with the starting & ending block numbers, previous and last block hashes, + /// and merkle mountain range as output. + Root, + /// The block number range must fit within the specified max depth. `Evm(round)` performs `round + 1` + /// rounds of SNARK verification on the final `Root` circuit + Evm(usize), +} + +impl CoreNodeParams { + pub fn new(node_type: CoreNodeType, depth: usize, initial_depth: usize) -> Self { + assert!(depth >= initial_depth); + Self { node_type, depth, initial_depth } + } + + pub fn child(&self, max_extra_data_bytes: Option) -> Option { + match self.node_type { + CoreNodeType::Leaf(_) => None, + CoreNodeType::Intermediate | CoreNodeType::Root => { + assert!(self.depth > self.initial_depth); + if self.depth == self.initial_depth + 1 { + Some(Self::new( + CoreNodeType::Leaf( + max_extra_data_bytes.expect("must provide max_extra_data_bytes"), + ), + self.initial_depth, + self.initial_depth, + )) + } else { + Some(Self::new(CoreNodeType::Intermediate, self.depth - 1, self.initial_depth)) + } + } + CoreNodeType::Evm(round) => { + if round == 0 { + let node_type = if self.depth == self.initial_depth { + CoreNodeType::Leaf( + max_extra_data_bytes.expect("must provide max_extra_data_bytes"), + ) + } else { + CoreNodeType::Root + }; + Some(Self::new(node_type, self.depth, self.initial_depth)) + } else { + Some(Self::new(CoreNodeType::Evm(round - 1), self.depth, self.initial_depth)) + } + } + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CorePinningLeaf { + pub num_instance: Vec, + pub params: RlcKeccakCircuitParams, + pub break_points: RlcThreadBreakPoints, + /// g1 generator, g2 generator, s_g2 (s is generator of trusted setup). + /// Together with domain size `2^k`, this commits to the trusted setup used. + /// This is all that's needed to verify the final ecpairing check on the KZG proof. + pub dk: KzgDecidingKey, +} + +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CorePinningIntermediate { + /// Configuration parameters + pub params: AggregationCircuitParams, + /// PlonkProtocol of the children + #[serde_as(as = "Vec")] + pub to_agg: Vec>, + /// Number of instances in each instance column + pub num_instance: Vec, + /// Break points. Should only have phase0, so MultiPhase is extraneous. + pub break_points: MultiPhaseThreadBreakPoints, + /// g1 generator, g2 generator, s_g2 (s is generator of trusted setup). + /// Together with domain size `2^k`, this commits to the trusted setup used. + /// This is all that's needed to verify the final ecpairing check on the KZG proof. + pub dk: KzgDecidingKey, +} + +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CorePinningRoot { + /// Configuration parameters + pub params: RlcKeccakCircuitParams, + /// PlonkProtocol of the children + #[serde_as(as = "Vec")] + pub to_agg: Vec>, + /// Number of instances in each instance column + pub num_instance: Vec, + /// Break points. + pub break_points: RlcThreadBreakPoints, + /// g1 generator, g2 generator, s_g2 (s is generator of trusted setup). + /// Together with domain size `2^k`, this commits to the trusted setup used. + /// This is all that's needed to verify the final ecpairing check on the KZG proof. + pub dk: KzgDecidingKey, +} + +pub type CorePinningEvm = GenericAggPinning; diff --git a/axiom-eth/.env.example b/axiom-eth/.env.example new file mode 100644 index 00000000..76413210 --- /dev/null +++ b/axiom-eth/.env.example @@ -0,0 +1,2 @@ +# need to export for tests to work +export JSON_RPC_URL= \ No newline at end of file diff --git a/axiom-eth/.gitignore b/axiom-eth/.gitignore new file mode 100644 index 00000000..730093f4 --- /dev/null +++ b/axiom-eth/.gitignore @@ -0,0 +1,6 @@ +**/target +Cargo.lock +**/*.rs.bk +*~ +.env +/params \ No newline at end of file diff --git a/axiom-eth/Cargo.toml b/axiom-eth/Cargo.toml new file mode 100644 index 00000000..1c32ac7f --- /dev/null +++ b/axiom-eth/Cargo.toml @@ -0,0 +1,93 @@ +[package] +name = "axiom-eth" +version = "0.4.2" +authors = ["Intrinsic Technologies"] +license = "MIT" +edition = "2021" +repository = "https://github.com/axiom-crypto/axiom-eth" +readme = "README.md" +description = "This crate is the main library for building ZK circuits that prove data about the Ethereum virtual machine (EVM)." +rust-version = "1.73.0" + +[dependencies] +itertools = "0.11" +lazy_static = "1.4.0" +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0", default-features = false } +serde_with = { version = "3.3", features = ["base64"], optional = true } +bincode = { version = "1.3.3" } +rayon = "1.8" +# misc +log = "0.4" +env_logger = "0.10" +ark-std = { version = "0.3.0", features = ["print-trace"], optional = true } +getset = "0.1.2" +thiserror = "1" +anyhow = "1.0" +static_assertions = "1.1.0" +type-map = "0.5.0" + +# halo2 +halo2-base = { workspace = true } +zkevm-hashes = { workspace = true } + +# crypto +rlp = "=0.5.2" +ethers-core = { workspace = true } +# mpt implementation +hasher = { version = "=0.1", features = ["hash-keccak"] } +cita_trie = "=5.0.0" +num-bigint = { version = "0.4" } +sha3 = "0.10.6" +# rand +rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } +rand = "0.8" +rand_chacha = "0.3.1" + +# aggregation +snark-verifier = { workspace = true } +snark-verifier-sdk = { workspace = true } + +# generating circuit inputs from blockchain +ethers-providers = { workspace = true, optional = true } +tokio = { version = "1.28", default-features = false, features = ["rt", "rt-multi-thread", "macros"], optional = true } +futures = { version = "0.3", optional = true } + +# keygen +blake3 = { version = "=1.5", 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" +pprof = { version = "0.11", features = ["criterion", "flamegraph"] } +criterion = "0.4" +criterion-macro = "0.4" +rayon = "1.6.1" +test-case = "3.1.0" +proptest = "1.1.0" +hasher = { version = "0.1", features = ["hash-keccak"] } + +[features] +default = [ + "halo2-axiom", + "jemallocator", + "providers", + "display", + "aggregation", + "keygen", + # "evm" +] +aggregation = ["snark-verifier/loader_halo2", "snark-verifier-sdk/loader_halo2", "providers", "dep:serde_with"] +evm = ["snark-verifier-sdk/loader_evm", "aggregation"] +revm = ["snark-verifier-sdk/revm"] +providers = ["dep:ethers-providers", "dep:tokio", "dep:futures"] +display = ["zkevm-hashes/display", "snark-verifier-sdk/display", "dep:ark-std"] +# EXACTLY one of halo2-pse / halo2-axiom should always be turned on +halo2-pse = ["zkevm-hashes/halo2-pse", "snark-verifier-sdk/halo2-pse"] +halo2-axiom = ["zkevm-hashes/halo2-axiom", "snark-verifier-sdk/halo2-axiom"] +keygen = ["aggregation", "dep:blake3"] +jemallocator = ["halo2-base/jemallocator"] +asm = ["halo2-base/asm"] +profile = ["halo2-base/profile"] diff --git a/axiom-eth/LICENSE b/axiom-eth/LICENSE new file mode 100644 index 00000000..69ec60b5 --- /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 00000000..85f4cefd --- /dev/null +++ b/axiom-eth/README.md @@ -0,0 +1,77 @@ +# `axiom-eth` + +This crate is the main library for building ZK circuits that prove data about the Ethereum virtual machine (EVM). + +## Modules + +### Primitives + +The rest of this crate makes heavy use of the following primitives: + +- [`halo2-base`](https://github.com/axiom-crypto/halo2-lib/tree/main/halo2-base) Specifically the `BaseCircuitBuilder` and `LookupAnyManager`. +- `rlc`: In this crate we introduce a new custom gate, which is a vertical gate (1 advice column, 1 selector column) using 3 rotations. + This is in [`PureRlcConfig`](./src/rlc/circuit/mod.rs). We extend `BaseCircuitBuilder` to support this gate in [`RlcCircuitBuilder`](./src/rlc/circuit/builder.rs). + `RlcCircuitBuilder` is the core circuit builder that is used in the rest of this crate. +- `keccak`: The keccak hash plays a very special role in the EVM, so it is heavily used in chips in this crate. The `keccak` module contains an **adapter** `KeccakChip` that + collects requests for keccak hashes of either fixed length or variable length byte arrays. It is very important to note that the `KeccakChip` does **not** explicitly constrain + the computation of these keccak hashes. The hash digests are returned as **unconstrained** witnesses. The verification of these hashes depends on the concrete `Circuit` + implementation that uses the `KeccakChip`. To that end, we now discuss the `Circuit` implementations in this crate. + +### `Circuit` implementations + +This crate has three main Halo2 `Circuit` trait implementations: + +- `RlcKeccakCircuitImpl`: this is the most direct circuit implementation. It creates a standalone circuit with a `RlcCircuitBuilder` managed sub-circuit as well as a vanilla zkEVM keccak sub-circuit. The keccak calls collected by `KeccakChip` are directly constrained against the output table from the zkEVM keccak sub-circuit. One creates such a circuit by implementing `EthCircuitInstructions`. +- `EthCircuitImpl`: this creates a circuit with a `RlcCircuitBuilder` managed sub-circuit. In addition, it loads a table of keccak hashes (and their inputs) as private witnesses and outputs a Poseidon hash of the table as a public output. The keccak calls collected by `KeccakChip` are dynamically looked up into the table of keccak hashes. One creates such a circuit by implementing `EthCircuitInstructions`. It is important to note that a proof generated by this circuit is **not** fully verified yet: the loaded keccak table must be verified by constraining the Poseidon hash of the table equals the public output of a `KeccakComponentShardCircuit` that actually proves the validity of the table. This must be done by an aggregation circuit. +- `ComponentCircuitImpl`: this is part of the [Component Framework](./src/utils/README.md). See there for more details. Note that the implementation of `EthCircuitImpl` is really a specialized version of `ComponentCircuitImpl` since it uses the same `PromiseLoader, PromiseCollector` pattern. + +### Data structures + +- `rlp`: this module defines `RlpChip`, which heavily uses RLCs to prove the correct encoding/decoding of RLP (Recursive Length Prefix) serialization. +- `mpt`: this module defines `MPTChip`, which uses `rlp` and `keccak` to prove inclusion and exclusion proofs of nodes in a Merkle Patricia Trie (MPT). + +### Ethereum execution layer (EL) chips + +- `block_header`: proves the decomposition of an RLP encoded block header (as bytes) into block header fields. Also computes the block hash by keccak hashing the block header. +- `storage`: proves account proofs into the state trie and storage proofs into the storage trie. +- `transaction`: proves inclusion of an RLP encoded transaction into the transaction trie. Also provides function to extract a specific field from a transaction. +- `receipt`: proves inclusion of an RLP encoded receipt into the receipt trie. Also provides function to extract a specific field from a receipt. +- `solidity`: proves the raw storage slot corresponding to a sequence of Solidity mapping keys. + +## Circuit Builders + +This is an abstract concept that we do not codify into a trait at the top level. +This crate builds heavily upon `halo2-base`, and specifically the use of [virtual region managers](https://github.com/axiom-crypto/halo2-lib/pull/123). Within this framework, circuits are written using circuit builders, which is some collection of virtual region managers. A circuit builder owns a collection of raw Halo2 columns and custom gates, but stores the logic of the circuit itself in some virtual state. The circuit builder may then do auto-configuration or optimizations based on this virtual state before assigning the raw columns and gate selectors into the raw Halo2 circuit. + +Therefore for each **phase** of the Halo2 Challenge API, there are two steps to writing a circuit: + +- Specifying the assignments to virtual regions within the circuit builder +- Assigning the raw columns and gate selectors into the raw Halo2 circuit + +In this crate we make heavy use of Random Linear Combinations (RLCs), which require **two** phases: phase0 and phase1. Phase0 corresponds to the `FirstPhase` struct in raw Halo2, and phase1 corresponds to the `SecondPhase` struct. Because the correct calculations of the virtual assignments in phase1 require a challenge value, phase1 virtual assignments _must_ be done after phase0 +Concretely, this means that one cannot fully pre-populate all virtual regions of your circuit builder ahead of time. Instead, you must specify a function for the virtual assignments to be done once the challenge value is received. + +The above motivates the definitions of the core traits in this module such as [CoreBuilder](./src/utils/component/circuit.rs) and [PromiseBuilder](./src/utils/component/circuit.rs). +These are traits that specify interfaces for variations of the functions: + +- `virtual_assign_phase0` +- `raw_synthesize_phase0` +- `virtual_assign_phase1` +- `raw_synthesize_phase1` + +These four functions are all called in sequence in the `Circuit::synthesize` trait implementation in raw Halo2, with the phase0 columns being commited to between `raw_synthesize_phase0` and `virtual_assign_phase1` so that the challenge values are availabe starting in `virtual_assign_phase1`. (The other technical distinction is that `virtual_assign_*` can be called outside of `Circuit::synthesize` without a `Layouter`, for any precomputation purposes.) + +Most of the time, one circuit builder can be re-used for different circuit implementations: +the `raw_synthesize_*` logic stays the same, and you only need to specify the `virtual_assign_*` instructions to quickly write new circuits. + +### Phase0 Payload + +Results/data from phase0 also needs to be passed to phase1 to "continue the computation". +Ideally one would use Rust closures to auto-infer these data to be passed between phases. However +due to some compiler limitations / possible instability, we have opted to require all data that is +passed from phase0 to phase1 to be explicitly declared in some kind of `Payload` struct. +These should always be viewed as an explicit implementation of a closure. + +## Component Framework + +See [README](./src/utils/README.md). diff --git a/axiom-eth/configs/bench/mpt.json b/axiom-eth/configs/bench/mpt.json new file mode 100644 index 00000000..fbfadc69 --- /dev/null +++ b/axiom-eth/configs/bench/mpt.json @@ -0,0 +1,56 @@ +[ + { + "base": { + "k": 15, + "num_advice_per_phase": [ + 29, + 18 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 2 + }, + { + "base": { + "k": 16, + "num_advice_per_phase": [ + 15, + 9 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 1 + }, + { + "base": { + "k": 17, + "num_advice_per_phase": [ + 8, + 5 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 1 + } +] \ No newline at end of file diff --git a/axiom-eth/configs/bench/storage.json b/axiom-eth/configs/bench/storage.json new file mode 100644 index 00000000..000b2597 --- /dev/null +++ b/axiom-eth/configs/bench/storage.json @@ -0,0 +1,65 @@ +[ + [ + { + "base": { + "k": 15, + "num_advice_per_phase": [ + 92, + 58 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 5 + }, + 1 + ], + [ + { + "base": { + "k": 18, + "num_advice_per_phase": [ + 56, + 39 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 3 + }, + 10 + ], + [ + { + "base": { + "k": 20, + "num_advice_per_phase": [ + 41, + 29 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 3 + }, + 32 + ] +] \ No newline at end of file diff --git a/axiom-eth/configs/bench/transaction.json b/axiom-eth/configs/bench/transaction.json new file mode 100644 index 00000000..dd16b246 --- /dev/null +++ b/axiom-eth/configs/bench/transaction.json @@ -0,0 +1,65 @@ +[ + [ + { + "base": { + "k": 15, + "num_advice_per_phase": [ + 25, + 16 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 2 + }, + 1 + ], + [ + { + "base": { + "k": 17, + "num_advice_per_phase": [ + 47, + 36 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 3 + }, + 10 + ], + [ + { + "base": { + "k": 18, + "num_advice_per_phase": [ + 73, + 57 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 5 + }, + 32 + ] +] \ No newline at end of file diff --git a/axiom-eth/configs/tests/keccak_shard.json b/axiom-eth/configs/tests/keccak_shard.json new file mode 100644 index 00000000..55a8e26a --- /dev/null +++ b/axiom-eth/configs/tests/keccak_shard.json @@ -0,0 +1,32 @@ +[ + { + "k": 18, + "num_unusable_row": 69, + "capacity": 50, + "publish_raw_outputs": false, + "keccak_circuit_params": { + "k": 18, + "rows_per_round": 206 + }, + "base_circuit_params": { + "k": 18, + "num_advice_per_phase": [ + 3 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 0, + 0, + 0 + ], + "lookup_bits": null, + "num_instance_columns": 1 + } + }, + [ + [ + 262034, + 262032 + ] + ] +] \ No newline at end of file diff --git a/axiom-eth/configs/tests/keccak_shard2_merkle.json b/axiom-eth/configs/tests/keccak_shard2_merkle.json new file mode 100644 index 00000000..1135bb18 --- /dev/null +++ b/axiom-eth/configs/tests/keccak_shard2_merkle.json @@ -0,0 +1,39 @@ +{ + "params": { + "degree": 20, + "num_advice": 27, + "num_lookup_advice": 3, + "num_fixed": 1, + "lookup_bits": 19 + }, + "break_points": [ + [ + 1048566, + 1048564, + 1048564, + 1048566, + 1048566, + 1048566, + 1048564, + 1048566, + 1048565, + 1048564, + 1048565, + 1048566, + 1048566, + 1048564, + 1048565, + 1048566, + 1048564, + 1048564, + 1048566, + 1048566, + 1048566, + 1048564, + 1048565, + 1048566, + 1048566, + 1048565 + ] + ] +} \ 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 00000000..b10af31b --- /dev/null +++ b/axiom-eth/configs/tests/one_block.json @@ -0,0 +1,11 @@ +{ + "base": { + "k": 12, + "num_advice_per_phase": [23, 8], + "num_fixed": 1, + "num_lookup_advice_per_phase": [1, 0, 0], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 1 +} diff --git a/axiom-eth/configs/tests/storage.json b/axiom-eth/configs/tests/storage.json new file mode 100644 index 00000000..d90a46da --- /dev/null +++ b/axiom-eth/configs/tests/storage.json @@ -0,0 +1,11 @@ +{ + "base": { + "k": 15, + "num_advice_per_phase": [45, 24], + "num_fixed": 1, + "num_lookup_advice_per_phase": [1, 1, 0], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 3 +} diff --git a/axiom-eth/configs/tests/storage_mapping.json b/axiom-eth/configs/tests/storage_mapping.json new file mode 100644 index 00000000..d90a46da --- /dev/null +++ b/axiom-eth/configs/tests/storage_mapping.json @@ -0,0 +1,11 @@ +{ + "base": { + "k": 15, + "num_advice_per_phase": [45, 24], + "num_fixed": 1, + "num_lookup_advice_per_phase": [1, 1, 0], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 3 +} diff --git a/axiom-eth/configs/tests/transaction.json b/axiom-eth/configs/tests/transaction.json new file mode 100644 index 00000000..efa53e2e --- /dev/null +++ b/axiom-eth/configs/tests/transaction.json @@ -0,0 +1,11 @@ +{ + "base": { + "k": 16, + "num_advice_per_phase": [45, 24], + "num_fixed": 1, + "num_lookup_advice_per_phase": [1, 1, 0], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 3 +} diff --git a/axiom-eth/data/mappings/anvil_dynamic_uint.json b/axiom-eth/data/mappings/anvil_dynamic_uint.json new file mode 100644 index 00000000..52b5a106 --- /dev/null +++ b/axiom-eth/data/mappings/anvil_dynamic_uint.json @@ -0,0 +1,9 @@ +{ + "key": "0100", + "var_len": "1", + "max_var_len": "2", + "test_type": "DynamicArray", + "mapping_slot": "0000000000000000000000000000000000000000000000000000000000000000", + "ground_truth_concat_key": "01000000000000000000000000000000000000000000000000000000000000000000", + "ground_truth_slot": "0d678e31a4b2825b806fe160675cd01dab159802c7f94397ce45ed91b5f3aac6" +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/block_mapping.t.json b/axiom-eth/data/mappings/block_mapping.t.json new file mode 100644 index 00000000..171cc70d --- /dev/null +++ b/axiom-eth/data/mappings/block_mapping.t.json @@ -0,0 +1,42 @@ +{ + "block_number": 14878854, + "query": [ + "1f98431c8ad98523631ae4a59f267346ea31f984", + "0000000000000000000000000000000000000000000000000000000000000005", + [ + [ + [ + "00000000000000000000000000000000000045166c45af0fc6e4cf31d9e14b9a", + "0000000000000000000000000000000000000000000000000000000000000020" + ], + [ + "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "0000000000000000000000000000000000000000000000000000000000000020" + ], + [ + "0000000000000000000000000000000000000000000000000000000000002710", + "0000000000000000000000000000000000000000000000000000000000000020" + ] + + ], + [ + [ + "00000000000000000000000000000000000045166c45af0fc6e4cf31d9e14b9a", + "0000000000000000000000000000000000000000000000000000000000000020" + ], + [ + "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "0000000000000000000000000000000000000000000000000000000000000020" + ], + [ + "0000000000000000000000000000000000000000000000000000000000002710", + "0000000000000000000000000000000000000000000000000000000000000020" + ] + ] + ] + ], + "not_empty": [ + true, + true + ] +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/cryptopunks_balance_of_addr_uint.json b/axiom-eth/data/mappings/cryptopunks_balance_of_addr_uint.json new file mode 100644 index 00000000..af0f2c1a --- /dev/null +++ b/axiom-eth/data/mappings/cryptopunks_balance_of_addr_uint.json @@ -0,0 +1,9 @@ +{ + "key": "000000000000000000000000000000000000dead", + "var_len": "", + "max_var_len": "", + "test_type": "Address", + "mapping_slot": "000000000000000000000000000000000000000000000000000000000000000b", + "ground_truth_concat_key": "000000000000000000000000000000000000000000000000000000000000dead000000000000000000000000000000000000000000000000000000000000000b", + "ground_truth_slot": "44433eeeda1d04bdae79f62169cdb2ab0a6af287fa15706d3fafdbac5fac3415" +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/mapping.t.json b/axiom-eth/data/mappings/mapping.t.json new file mode 100644 index 00000000..5aeb4e4d --- /dev/null +++ b/axiom-eth/data/mappings/mapping.t.json @@ -0,0 +1,42 @@ +{ + "block_response": 14878854, + "account_response": "1f98431c8ad98523631ae4a59f267346ea31f984", + "query": [ + "0000000000000000000000000000000000000000000000000000000000000005", + [ + [ + [ + "00000000000000000000000000000000000045166c45af0fc6e4cf31d9e14b9a", + "0000000000000000000000000000000000000000000000000000000000000020" + ], + [ + "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "0000000000000000000000000000000000000000000000000000000000000020" + ], + [ + "0000000000000000000000000000000000000000000000000000000000002710", + "0000000000000000000000000000000000000000000000000000000000000020" + ] + + ], + [ + [ + "00000000000000000000000000000000000045166c45af0fc6e4cf31d9e14b9a", + "0000000000000000000000000000000000000000000000000000000000000020" + ], + [ + "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "0000000000000000000000000000000000000000000000000000000000000020" + ], + [ + "0000000000000000000000000000000000000000000000000000000000002710", + "0000000000000000000000000000000000000000000000000000000000000020" + ] + ] + ] + ], + "not_empty": [ + true, + true + ] +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/uni_v3_factory_fake.json b/axiom-eth/data/mappings/uni_v3_factory_fake.json new file mode 100644 index 00000000..32d529af --- /dev/null +++ b/axiom-eth/data/mappings/uni_v3_factory_fake.json @@ -0,0 +1,9 @@ +{ + "key": "00000000000000000000000000000000000003e8", + "var_len": "", + "max_var_len": "", + "test_type": "Address", + "mapping_slot": "125b34c9effbe1283a941860855143fc893f1fe5abe557e1626df746d9f19d16", + "ground_truth_concat_key": "00000000000000000000000000000000000000000000000000000000000003e8125b34c9effbe1283a941860855143fc893f1fe5abe557e1626df746d9f19d16", + "ground_truth_slot": "64479ced6d1ebcf1b3db1573104fc23b42e527cfb9bdf09e3085da4adad6fb28" +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/uni_v3_factory_get_pool_addr_addr.json b/axiom-eth/data/mappings/uni_v3_factory_get_pool_addr_addr.json new file mode 100644 index 00000000..c5519323 --- /dev/null +++ b/axiom-eth/data/mappings/uni_v3_factory_get_pool_addr_addr.json @@ -0,0 +1,9 @@ + +{ + "key": "00000000000045166c45af0fc6e4cf31d9e14b9a", + "var_len": "", + "max_var_len": "", + "mapping_slot": "0000000000000000000000000000000000000000000000000000000000000005", + "ground_truth_concat_key": "00000000000000000000000000000000000045166c45af0fc6e4cf31d9e14b9a0000000000000000000000000000000000000000000000000000000000000005", + "ground_truth_slot": "43ae34693ee6dbe9779e719b5579311057a431d9890ba5cceb265b8a21e95990" +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/uni_v3_factory_get_pool_addr_uint.json b/axiom-eth/data/mappings/uni_v3_factory_get_pool_addr_uint.json new file mode 100644 index 00000000..34386c2d --- /dev/null +++ b/axiom-eth/data/mappings/uni_v3_factory_get_pool_addr_uint.json @@ -0,0 +1,8 @@ +{ + "key": "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "var_len": "", + "max_var_len": "", + "mapping_slot": "43ae34693ee6dbe9779e719b5579311057a431d9890ba5cceb265b8a21e95990", + "ground_truth_concat_key": "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc243ae34693ee6dbe9779e719b5579311057a431d9890ba5cceb265b8a21e95990", + "ground_truth_slot": "af01b659268ea8b268c9c7ca07bf047651afe5aecef23fa3e9b9e2a8140a32b7" +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/uni_v3_factory_get_pool_uint_addr.json b/axiom-eth/data/mappings/uni_v3_factory_get_pool_uint_addr.json new file mode 100644 index 00000000..4acee4c8 --- /dev/null +++ b/axiom-eth/data/mappings/uni_v3_factory_get_pool_uint_addr.json @@ -0,0 +1,8 @@ +{ + "key": "0000000000000000000000000000000000000000000000000000000000002710", + "var_len": "", + "max_var_len": "", + "mapping_slot": "af01b659268ea8b268c9c7ca07bf047651afe5aecef23fa3e9b9e2a8140a32b7", + "ground_truth_concat_key": "0000000000000000000000000000000000000000000000000000000000002710af01b659268ea8b268c9c7ca07bf047651afe5aecef23fa3e9b9e2a8140a32b7", + "ground_truth_slot": "125b34c9effbe1283a941860855143fc893f1fe5abe557e1626df746d9f19d16" +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/unisocks_erc20_balance_of_addr_uint.json b/axiom-eth/data/mappings/unisocks_erc20_balance_of_addr_uint.json new file mode 100644 index 00000000..0621991c --- /dev/null +++ b/axiom-eth/data/mappings/unisocks_erc20_balance_of_addr_uint.json @@ -0,0 +1,9 @@ +{ + "key": "b9fddbd225b6c8cc24ce193e5fb95db76d783f2d", + "var_len": "", + "max_var_len": "", + "test_type": "Address", + "mapping_slot": "0000000000000000000000000000000000000000000000000000000000000005", + "ground_truth_concat_key": "000000000000000000000000b9fddbd225b6c8cc24ce193e5fb95db76d783f2d0000000000000000000000000000000000000000000000000000000000000005", + "ground_truth_slot": "751c4389cc6b086faa41a8f84e863555076c3c8323f4d14f98d9e76cbba041fc" +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/unisocks_erc721_balance_of_addr_uint.json b/axiom-eth/data/mappings/unisocks_erc721_balance_of_addr_uint.json new file mode 100644 index 00000000..beb6f9c6 --- /dev/null +++ b/axiom-eth/data/mappings/unisocks_erc721_balance_of_addr_uint.json @@ -0,0 +1,9 @@ +{ + "key": "8688a84fcfd84d8f78020d0fc0b35987cc58911f", + "var_len": "", + "max_var_len": "", + "test_type": "Address", + "mapping_slot": "0000000000000000000000000000000000000000000000000000000000000007", + "ground_truth_concat_key": "0000000000000000000000008688a84fcfd84d8f78020d0fc0b35987cc58911f0000000000000000000000000000000000000000000000000000000000000007", + "ground_truth_slot": "900630798574b2f89ae13e25e9c1a75520672892d1a0b503abcb2bae8f12432f" +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/weth_allowance_addr_addr.json b/axiom-eth/data/mappings/weth_allowance_addr_addr.json new file mode 100644 index 00000000..e7bde531 --- /dev/null +++ b/axiom-eth/data/mappings/weth_allowance_addr_addr.json @@ -0,0 +1,9 @@ +{ + "key": "000000000000006f6502b7f2bbac8c30a3f67e9a", + "var_len": "", + "max_var_len": "", + "test_type": "Address", + "mapping_slot": "0000000000000000000000000000000000000000000000000000000000000004", + "ground_truth_concat_key": "000000000000000000000000000000000000006f6502b7f2bbac8c30a3f67e9a0000000000000000000000000000000000000000000000000000000000000004", + "ground_truth_slot": "1c2339d1c4bfaf58b8f0d2638d9dae656c10932e13c9305c240770ec2cf1f659" +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/weth_allowance_addr_uint.json b/axiom-eth/data/mappings/weth_allowance_addr_uint.json new file mode 100644 index 00000000..389f8b60 --- /dev/null +++ b/axiom-eth/data/mappings/weth_allowance_addr_uint.json @@ -0,0 +1,9 @@ +{ + "key": "01dfeaf2f6b648296b4c9dc4f0c5fddcb0984c0d", + "var_len": "", + "max_var_len": "", + "test_type": "Address", + "mapping_slot": "1c2339d1c4bfaf58b8f0d2638d9dae656c10932e13c9305c240770ec2cf1f659", + "ground_truth_concat_key": "00000000000000000000000001dfeaf2f6b648296b4c9dc4f0c5fddcb0984c0d1c2339d1c4bfaf58b8f0d2638d9dae656c10932e13c9305c240770ec2cf1f659", + "ground_truth_slot": "edb07cd504a790e8a8e63503d585d5c2408101fafac413177b1bdf3e0c3086e3" +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/weth_balance_of_addr_uint.json b/axiom-eth/data/mappings/weth_balance_of_addr_uint.json new file mode 100644 index 00000000..e47e0526 --- /dev/null +++ b/axiom-eth/data/mappings/weth_balance_of_addr_uint.json @@ -0,0 +1,9 @@ +{ + "key": "f04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e", + "var_len": "", + "max_var_len": "", + "test_type": "Address", + "mapping_slot": "0000000000000000000000000000000000000000000000000000000000000003", + "ground_truth_concat_key": "000000000000000000000000f04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e0000000000000000000000000000000000000000000000000000000000000003", + "ground_truth_slot": "1f8193c3f94e8840dc3a6dfc0bc012432d338ef33c4f3e4b3aca0d6d3c5a09b6" +} \ No newline at end of file diff --git a/axiom-eth/data/mappings/weth_balance_of_bytes32_uint.json b/axiom-eth/data/mappings/weth_balance_of_bytes32_uint.json new file mode 100644 index 00000000..8d853009 --- /dev/null +++ b/axiom-eth/data/mappings/weth_balance_of_bytes32_uint.json @@ -0,0 +1,9 @@ +{ + "key": "000000000000000000000000f04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e", + "var_len": "", + "max_var_len": "", + "test_type": "Bytes32", + "mapping_slot": "0000000000000000000000000000000000000000000000000000000000000003", + "ground_truth_concat_key": "000000000000000000000000f04a5cc80b1e94c69b48f5ee68a08cd2f09a7c3e0000000000000000000000000000000000000000000000000000000000000003", + "ground_truth_slot": "1f8193c3f94e8840dc3a6dfc0bc012432d338ef33c4f3e4b3aca0d6d3c5a09b6" +} \ No newline at end of file diff --git a/axiom-eth/data/receipts/task.goerli.json b/axiom-eth/data/receipts/task.goerli.json new file mode 100644 index 00000000..4523150f --- /dev/null +++ b/axiom-eth/data/receipts/task.goerli.json @@ -0,0 +1,93 @@ +{ + "queries": [ + { + "txHash": "0x1320047d683efdf132b7939418a9f0061f9dfd7348a2f347f32745fb46cb6ec6", + "fieldIdx": 0 + }, + { + "txHash": "0x1320047d683efdf132b7939418a9f0061f9dfd7348a2f347f32745fb46cb6ec6", + "fieldIdx": 3, + "logIdx": 0 + }, + { + "txHash": "0x1320047d683efdf132b7939418a9f0061f9dfd7348a2f347f32745fb46cb6ec6", + "fieldIdx": 1 + } + ], + "mmr": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xe8d6970591102bd6e43961d0d4c6a3cef99a18b493cb3bda636ca7ec2409a86e", + "0xaaa2e7f7056a5e9e0275083d1e49b17a7415788c3fe0b7506aeca39386d95825", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xec3c6c5338ca0bddd0c7925aef2835b9ffb2ac4ac2b0d5f5ce38f59f06d387c7", + "0xa01781a978c22545f26063dd0a8f0a7c052b19e1f3706169c1b71d627a7ea13c", + "0xf8d8f6319c2dd17fb421463eb8d0c86881d0afaf8e08e95c49b5a9068727cae7", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x2caa414cfbca54154d3a8815f9e1a6d410f4e1d0f76df14f9eecebb0ea05b59d", + "0x33d65445a72205b92572a182e3867c9c66fca3a086d02dda622122df063b38c8", + "0xdde7f249a13c0fef92a9aa7abd559586732c0398241813253fcf07d956859bce", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xf6bcd969602fef0c36ec84ee377e592ec3b5311534ab3c1efcf0646acd96eead" + ], + "mmrProofs": [ + [ + "0xf7c9fdba1d27d4d66b2085ef041e97c5b9bddf0262f03cb75fff4ae5342da7fc", + "0xf01d543b87c11402616226836455ed3b03a00b9c3feebb478eb9e5a97be07392", + "0x4bd8b09a6fd10f09f920c3aa6aab942d63d55fdd1f093301b5fcb8f7fd59ce8a", + "0x79423f92140f3e6698b8d38394170fcd19c452282674c30835edf7c06dff49ef", + "0x4c39bed02253ac79744c1ff0134c82eecda62341048e9edc7f48caa0b06d2d1e", + "0x28805385c643dfc4f15251f848d32fe536cccc605b6f36312f8f1210c88bc4e5", + "0x3b14201f7ea94e76ec305487b1c5e3ec56715eafbe57a5ea3253b75f3625a772", + "0x22ac80faa27cba53fc2ca042902eb8c55d0b3599294ee004186a962ca8d9cc6f", + "0x73bc39a9b31be50ecdf05fa2b8e1826a72c59abffdbe010f135e0a4089440603", + "0xbb5e3fb8c83f0237baa615239a5372c98c9efd2c3a41d289b6ce134ee7a3bc50", + "0x4be9154bca6cb87ee928b0cc41bf29b1043ba1265fde3b2dad297d4d2b198f29", + "0x464ec85779dcb2a11b64e28a6aada9563b9a24803cf0250bf073768ed75e1c5f", + "0xe76931d3a2a46f459cf169a0e0e6d44af220c98835a9fd406e82ef9b167fffa1", + "0x7faeb24db369709c70ae2c41259d2216bc320e9dc29ee0d7e358b18a375ca113" + ], + [ + "0xf7c9fdba1d27d4d66b2085ef041e97c5b9bddf0262f03cb75fff4ae5342da7fc", + "0xf01d543b87c11402616226836455ed3b03a00b9c3feebb478eb9e5a97be07392", + "0x4bd8b09a6fd10f09f920c3aa6aab942d63d55fdd1f093301b5fcb8f7fd59ce8a", + "0x79423f92140f3e6698b8d38394170fcd19c452282674c30835edf7c06dff49ef", + "0x4c39bed02253ac79744c1ff0134c82eecda62341048e9edc7f48caa0b06d2d1e", + "0x28805385c643dfc4f15251f848d32fe536cccc605b6f36312f8f1210c88bc4e5", + "0x3b14201f7ea94e76ec305487b1c5e3ec56715eafbe57a5ea3253b75f3625a772", + "0x22ac80faa27cba53fc2ca042902eb8c55d0b3599294ee004186a962ca8d9cc6f", + "0x73bc39a9b31be50ecdf05fa2b8e1826a72c59abffdbe010f135e0a4089440603", + "0xbb5e3fb8c83f0237baa615239a5372c98c9efd2c3a41d289b6ce134ee7a3bc50", + "0x4be9154bca6cb87ee928b0cc41bf29b1043ba1265fde3b2dad297d4d2b198f29", + "0x464ec85779dcb2a11b64e28a6aada9563b9a24803cf0250bf073768ed75e1c5f", + "0xe76931d3a2a46f459cf169a0e0e6d44af220c98835a9fd406e82ef9b167fffa1", + "0x7faeb24db369709c70ae2c41259d2216bc320e9dc29ee0d7e358b18a375ca113" + ], + [ + "0xf7c9fdba1d27d4d66b2085ef041e97c5b9bddf0262f03cb75fff4ae5342da7fc", + "0xf01d543b87c11402616226836455ed3b03a00b9c3feebb478eb9e5a97be07392", + "0x4bd8b09a6fd10f09f920c3aa6aab942d63d55fdd1f093301b5fcb8f7fd59ce8a", + "0x79423f92140f3e6698b8d38394170fcd19c452282674c30835edf7c06dff49ef", + "0x4c39bed02253ac79744c1ff0134c82eecda62341048e9edc7f48caa0b06d2d1e", + "0x28805385c643dfc4f15251f848d32fe536cccc605b6f36312f8f1210c88bc4e5", + "0x3b14201f7ea94e76ec305487b1c5e3ec56715eafbe57a5ea3253b75f3625a772", + "0x22ac80faa27cba53fc2ca042902eb8c55d0b3599294ee004186a962ca8d9cc6f", + "0x73bc39a9b31be50ecdf05fa2b8e1826a72c59abffdbe010f135e0a4089440603", + "0xbb5e3fb8c83f0237baa615239a5372c98c9efd2c3a41d289b6ce134ee7a3bc50", + "0x4be9154bca6cb87ee928b0cc41bf29b1043ba1265fde3b2dad297d4d2b198f29", + "0x464ec85779dcb2a11b64e28a6aada9563b9a24803cf0250bf073768ed75e1c5f", + "0xe76931d3a2a46f459cf169a0e0e6d44af220c98835a9fd406e82ef9b167fffa1", + "0x7faeb24db369709c70ae2c41259d2216bc320e9dc29ee0d7e358b18a375ca113" + ] + ] +} diff --git a/axiom-eth/data/receipts/task.mainnet.json b/axiom-eth/data/receipts/task.mainnet.json new file mode 100644 index 00000000..0732b67d --- /dev/null +++ b/axiom-eth/data/receipts/task.mainnet.json @@ -0,0 +1,64 @@ +{ + "queries": [ + { + "txHash": "0x725187f197cbc28766304299191171891b72fa7ccfd1d70255c6296daf5212a7", + "fieldIdx": 3, + "logIdx": 0 + } + ], + "mmr": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xd2fcfe5ff5e4c3509468fa208708f7de5abe4894b16bfe36c4bdb465ae134bdd", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x577956894541f12e359ba6428306edbdc4e16f30eb33aea225412a4c619fbcd3", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xe553f451efe36070afa3e1bec68987919dbdb8b01153ff51b2c8ed222f95595a", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x37e7688219e9ad6573641ecda977fffa02efb76340ccd2f0726832bc9af0b0c0", + "0xd917e847ca54394b03475e17edde6598f4ac6772f3d517eea1e9f8134a8a89c2", + "0xbfed61dc28eb972a55ce01242ce04921e59e35bf83b5b26cbbbec7f23ddabe56", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x4755084f1c80a73395852c7ede586dddb3b8db7828e4ad2cf3259f693ac4f875" + ], + "mmrProofs": [ + [ + "0x0c49a468469a9c0af040376aeecc1a6625ccd188005ada6202c9349e0b2b1c7e", + "0xad2cf9b6a35ec994afd298cfda444dd3bc858bd98a72cae19d6746c4d43a378f", + "0x3860d4943d890e2120af1e764ce10060b2a9d348f6f89e183f155dd1f1c0b3f6", + "0x37a4e5a773a65924ec281ad1bb6f9006530d0a6d2dd557365f75628da046e4e7", + "0x3adc9d22627ff5c298f506e0dfbb83745ba889f747f4888410aa9be05b883beb", + "0x1fe33fae325abc07a8fe265103338699d0e0fae4b740b53a182556809bc45f13", + "0xef7a1b2657b1e90c747536ed1edf90cdb552022841fec94fbe5735fd9b8bf113", + "0x0f62ca6c4928d159b968e69708d7ccc3b704b8d7c4192d29d5513d3c12ea3492", + "0x22f91717e2361a8269b8b7f2c5296ea91d93da651457ad7ffc67e8216e45d61c", + "0x9ea188da4461fbcff96a42e44a4a6526528508b037e884e895bf9ff181a0a631", + "0x42d225f31886410369452490a02a0d54ad311ccec6e2ea617942f3de0e63c2df", + "0x2ea5b5d642306fb61a646a56621f8e17a5c8c29d1bc382cfc1ef5567e5ee6842", + "0x9032d519db6cae76bd6bfba6ce210ce36d208791ee1157413ad0465be74b3770", + "0xa7f4d56dd1fb81828199e77a205c86e2ed8d41e87d2f733c1ae171a04012bed0", + "0xdf69cb8c19b513b888aee79d2dfaa03ef4128f5628e0dae02a3f0348a87c6e5f", + "0x677f0ec9ecef0a39918979673c0e1286818509e8b0b24718d548927e60b38ca7", + "0xddebf1eaa86f2500698cf1de0b1b5c8f1963991a97af381afd27d5c9e6545a49", + "0x3643992767358e54ed59b4c36f7356be744c1c8234f383c79b970088c0e90872", + "0x43062e083f5f40510f30723d9cebb51c4ae67c35d86c5b791a043bae317350e3", + "0x6cddc980f4c3b403d99080c32b3f0a6205e39560b9021d5f233c04d96c23381e", + "0x6a42052cabd8d66a584b8823c6aadf64dd2755321210c66a2b7acd1da5bdeacf", + "0xebf08ca711cbab09109bb51277c545ee43073d8fa8b46c0cbbedd46ce85e73be", + "0x477c055e69de14e3bbfe2af0389e6c3ac28ffb5b0cc8fa26b543ac47857fd646", + "0xf47e6584af17667881494720b50dd063d0900b288438df7a37e2e91440aedd23" + ] + ] +} diff --git a/axiom-eth/data/storage/mainnet_10_evm.yul b/axiom-eth/data/storage/mainnet_10_evm.yul new file mode 100644 index 00000000..10c84bc5 --- /dev/null +++ b/axiom-eth/data/storage/mainnet_10_evm.yul @@ -0,0 +1,1949 @@ + + 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(0x6c0, mod(calldataload(0x6a0), f_q)) +mstore(0x6e0, mod(calldataload(0x6c0), f_q)) +mstore(0x700, mod(calldataload(0x6e0), f_q)) +mstore(0x0, 3070330598423483684997578132342042775302428088284443187127165829370988263872) + + { + let x := calldataload(0x700) + mstore(0x720, x) + let y := calldataload(0x720) + mstore(0x740, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x740) + mstore(0x760, x) + let y := calldataload(0x760) + mstore(0x780, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x780) + mstore(0x7a0, x) + let y := calldataload(0x7a0) + mstore(0x7c0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x7e0, keccak256(0x0, 2016)) +{ + let hash := mload(0x7e0) + mstore(0x800, mod(hash, f_q)) + mstore(0x820, hash) + } + + { + let x := calldataload(0x7c0) + mstore(0x840, x) + let y := calldataload(0x7e0) + mstore(0x860, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x800) + mstore(0x880, x) + let y := calldataload(0x820) + mstore(0x8a0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x8c0, keccak256(0x820, 160)) +{ + let hash := mload(0x8c0) + mstore(0x8e0, mod(hash, f_q)) + mstore(0x900, hash) + } +mstore8(2336, 1) +mstore(0x920, keccak256(0x900, 33)) +{ + let hash := mload(0x920) + mstore(0x940, mod(hash, f_q)) + mstore(0x960, hash) + } + + { + let x := calldataload(0x840) + mstore(0x980, x) + let y := calldataload(0x860) + mstore(0x9a0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x880) + mstore(0x9c0, x) + let y := calldataload(0x8a0) + mstore(0x9e0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x8c0) + mstore(0xa00, x) + let y := calldataload(0x8e0) + mstore(0xa20, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x900) + mstore(0xa40, x) + let y := calldataload(0x920) + mstore(0xa60, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x940) + mstore(0xa80, x) + let y := calldataload(0x960) + mstore(0xaa0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0xac0, keccak256(0x960, 352)) +{ + let hash := mload(0xac0) + mstore(0xae0, mod(hash, f_q)) + mstore(0xb00, hash) + } + + { + let x := calldataload(0x980) + mstore(0xb20, x) + let y := calldataload(0x9a0) + mstore(0xb40, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x9c0) + mstore(0xb60, x) + let y := calldataload(0x9e0) + mstore(0xb80, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0xa00) + mstore(0xba0, x) + let y := calldataload(0xa20) + mstore(0xbc0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0xbe0, keccak256(0xb00, 224)) +{ + let hash := mload(0xbe0) + mstore(0xc00, mod(hash, f_q)) + mstore(0xc20, hash) + } +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, mod(calldataload(0xd00), f_q)) +mstore(0xf20, mod(calldataload(0xd20), f_q)) +mstore(0xf40, mod(calldataload(0xd40), f_q)) +mstore(0xf60, mod(calldataload(0xd60), f_q)) +mstore(0xf80, mod(calldataload(0xd80), f_q)) +mstore(0xfa0, mod(calldataload(0xda0), f_q)) +mstore(0xfc0, mod(calldataload(0xdc0), f_q)) +mstore(0xfe0, mod(calldataload(0xde0), f_q)) +mstore(0x1000, mod(calldataload(0xe00), f_q)) +mstore(0x1020, mod(calldataload(0xe20), f_q)) +mstore(0x1040, keccak256(0xc20, 1056)) +{ + let hash := mload(0x1040) + mstore(0x1060, mod(hash, f_q)) + mstore(0x1080, hash) + } +mstore8(4256, 1) +mstore(0x10a0, keccak256(0x1080, 33)) +{ + let hash := mload(0x10a0) + mstore(0x10c0, mod(hash, f_q)) + mstore(0x10e0, hash) + } + + { + let x := calldataload(0xe40) + mstore(0x1100, x) + let y := calldataload(0xe60) + mstore(0x1120, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x1140, keccak256(0x10e0, 96)) +{ + let hash := mload(0x1140) + mstore(0x1160, mod(hash, f_q)) + mstore(0x1180, hash) + } + + { + let x := calldataload(0xe80) + mstore(0x11a0, x) + let y := calldataload(0xea0) + mstore(0x11c0, 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(4576, x) +let y := mload(0x80) +y := add(y, shl(88, mload(0xa0))) +y := add(y, shl(176, mload(0xc0))) +mstore(4608, 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(4640, x) +let y := mload(0x140) +y := add(y, shl(88, mload(0x160))) +y := add(y, shl(176, mload(0x180))) +mstore(4672, y) + + success := and(validate_ec_point(x, y), success) + } +mstore(0x1260, mulmod(mload(0xc00), mload(0xc00), 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, mulmod(mload(0x13c0), mload(0x13c0), f_q)) +mstore(0x1400, mulmod(mload(0x13e0), mload(0x13e0), f_q)) +mstore(0x1420, mulmod(mload(0x1400), mload(0x1400), f_q)) +mstore(0x1440, mulmod(mload(0x1420), mload(0x1420), f_q)) +mstore(0x1460, mulmod(mload(0x1440), mload(0x1440), f_q)) +mstore(0x1480, mulmod(mload(0x1460), mload(0x1460), f_q)) +mstore(0x14a0, mulmod(mload(0x1480), mload(0x1480), f_q)) +mstore(0x14c0, mulmod(mload(0x14a0), mload(0x14a0), f_q)) +mstore(0x14e0, mulmod(mload(0x14c0), mload(0x14c0), f_q)) +mstore(0x1500, mulmod(mload(0x14e0), mload(0x14e0), f_q)) +mstore(0x1520, mulmod(mload(0x1500), mload(0x1500), f_q)) +mstore(0x1540, mulmod(mload(0x1520), mload(0x1520), f_q)) +mstore(0x1560, addmod(mload(0x1540), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q)) +mstore(0x1580, mulmod(mload(0x1560), 21888241567198334088790460357988866238279339518792980768180410072331574733841, f_q)) +mstore(0x15a0, mulmod(mload(0x1580), 12929131318670223636853686797196826072950305380535537217467769528748593133487, f_q)) +mstore(0x15c0, addmod(mload(0xc00), 8959111553169051585392718948060449015598059019880497126230434657827215362130, f_q)) +mstore(0x15e0, mulmod(mload(0x1580), 14655294445420895451632927078981340937842238432098198055057679026789553137428, f_q)) +mstore(0x1600, addmod(mload(0xc00), 7232948426418379770613478666275934150706125968317836288640525159786255358189, f_q)) +mstore(0x1620, mulmod(mload(0x1580), 12220484078924208264862893648548198807365556694478604924193442790112568454894, f_q)) +mstore(0x1640, addmod(mload(0xc00), 9667758792915066957383512096709076281182807705937429419504761396463240040723, f_q)) +mstore(0x1660, mulmod(mload(0x1580), 8734126352828345679573237859165904705806588461301144420590422589042130041188, f_q)) +mstore(0x1680, addmod(mload(0xc00), 13154116519010929542673167886091370382741775939114889923107781597533678454429, f_q)) +mstore(0x16a0, mulmod(mload(0x1580), 7358966525675286471217089135633860168646304224547606326237275077574224349359, f_q)) +mstore(0x16c0, addmod(mload(0xc00), 14529276346163988751029316609623414919902060175868428017460929109001584146258, f_q)) +mstore(0x16e0, mulmod(mload(0x1580), 9741553891420464328295280489650144566903017206473301385034033384879943874347, f_q)) +mstore(0x1700, addmod(mload(0xc00), 12146688980418810893951125255607130521645347193942732958664170801695864621270, f_q)) +mstore(0x1720, mulmod(mload(0x1580), 17329448237240114492580865744088056414251735686965494637158808787419781175510, f_q)) +mstore(0x1740, addmod(mload(0xc00), 4558794634599160729665540001169218674296628713450539706539395399156027320107, f_q)) +mstore(0x1760, mulmod(mload(0x1580), 1, f_q)) +mstore(0x1780, addmod(mload(0xc00), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q)) +mstore(0x17a0, mulmod(mload(0x1580), 11451405578697956743456240853980216273390554734748796433026540431386972584651, f_q)) +mstore(0x17c0, addmod(mload(0xc00), 10436837293141318478790164891277058815157809665667237910671663755188835910966, f_q)) +mstore(0x17e0, mulmod(mload(0x1580), 8374374965308410102411073611984011876711565317741801500439755773472076597347, f_q)) +mstore(0x1800, addmod(mload(0xc00), 13513867906530865119835332133273263211836799082674232843258448413103731898270, f_q)) +mstore(0x1820, mulmod(mload(0x1580), 21490807004895109926141140246143262403290679459142140821740925192625185504522, f_q)) +mstore(0x1840, addmod(mload(0xc00), 397435866944165296105265499114012685257684941273893521957278993950622991095, f_q)) +mstore(0x1860, mulmod(mload(0x1580), 11211301017135681023579411905410872569206244553457844956874280139879520583390, f_q)) +mstore(0x1880, addmod(mload(0xc00), 10676941854703594198666993839846402519342119846958189386823924046696287912227, f_q)) +mstore(0x18a0, mulmod(mload(0x1580), 18846108080730935585192484934247867403156699586319724728525857970312957475341, f_q)) +mstore(0x18c0, addmod(mload(0xc00), 3042134791108339637053920811009407685391664814096309615172346216262851020276, f_q)) +mstore(0x18e0, mulmod(mload(0x1580), 3615478808282855240548287271348143516886772452944084747768312988864436725401, f_q)) +mstore(0x1900, addmod(mload(0xc00), 18272764063556419981698118473909131571661591947471949595929891197711371770216, f_q)) +mstore(0x1920, mulmod(mload(0x1580), 21451937155080765789602997556105366785934335730087568134349216848800867145453, f_q)) +mstore(0x1940, addmod(mload(0xc00), 436305716758509432643408189151908302614028670328466209348987337774941350164, f_q)) +mstore(0x1960, mulmod(mload(0x1580), 1426404432721484388505361748317961535523355871255605456897797744433766488507, f_q)) +mstore(0x1980, addmod(mload(0xc00), 20461838439117790833741043996939313553025008529160428886800406442142042007110, f_q)) +mstore(0x19a0, mulmod(mload(0x1580), 13982290267294411190096162596630216412723378687553046594730793425118513274800, f_q)) +mstore(0x19c0, addmod(mload(0xc00), 7905952604544864032150243148627058675824985712862987748967410761457295220817, f_q)) +mstore(0x19e0, mulmod(mload(0x1580), 216092043779272773661818549620449970334216366264741118684015851799902419467, f_q)) +mstore(0x1a00, addmod(mload(0xc00), 21672150828060002448584587195636825118214148034151293225014188334775906076150, f_q)) +mstore(0x1a20, mulmod(mload(0x1580), 9537783784440837896026284659246718978615447564543116209283382057778110278482, f_q)) +mstore(0x1a40, addmod(mload(0xc00), 12350459087398437326220121086010556109932916835872918134414822128797698217135, f_q)) +mstore(0x1a60, mulmod(mload(0x1580), 12619617507853212586156872920672483948819476989779550311307282715684870266992, f_q)) +mstore(0x1a80, addmod(mload(0xc00), 9268625363986062636089532824584791139728887410636484032390921470890938228625, f_q)) +mstore(0x1aa0, mulmod(mload(0x1580), 3947443723575973965644279767310964219908423994086470065513888332899718123222, f_q)) +mstore(0x1ac0, addmod(mload(0xc00), 17940799148263301256602125977946310868639940406329564278184315853676090372395, f_q)) +mstore(0x1ae0, mulmod(mload(0x1580), 18610195890048912503953886742825279624920778288956610528523679659246523534888, f_q)) +mstore(0x1b00, addmod(mload(0xc00), 3278046981790362718292519002431995463627586111459423815174524527329284960729, f_q)) +mstore(0x1b20, mulmod(mload(0x1580), 1539082509056298927655194235755440186888826897239928178265486731666142403222, f_q)) +mstore(0x1b40, addmod(mload(0xc00), 20349160362782976294591211509501834901659537503176106165432717454909666092395, f_q)) +mstore(0x1b60, mulmod(mload(0x1580), 19032961837237948602743626455740240236231119053033140765040043513661803148152, f_q)) +mstore(0x1b80, addmod(mload(0xc00), 2855281034601326619502779289517034852317245347382893578658160672914005347465, f_q)) +mstore(0x1ba0, mulmod(mload(0x1580), 4317410353320599552056040796202302907960891408523818766419977508859423800635, f_q)) +mstore(0x1bc0, addmod(mload(0xc00), 17570832518518675670190364949054972180587472991892215577278226677716384694982, f_q)) +mstore(0x1be0, mulmod(mload(0x1580), 14875928112196239563830800280253496262679717528621719058794366823499719730250, f_q)) +mstore(0x1c00, addmod(mload(0xc00), 7012314759643035658415605465003778825868646871794315284903837363076088765367, f_q)) +mstore(0x1c20, mulmod(mload(0x1580), 2366023502186770334390939928726871658997402416352868340984630739442624219298, f_q)) +mstore(0x1c40, addmod(mload(0xc00), 19522219369652504887855465816530403429550961984063166002713573447133184276319, f_q)) +mstore(0x1c60, mulmod(mload(0x1580), 915149353520972163646494413843788069594022902357002628455555785223409501882, f_q)) +mstore(0x1c80, addmod(mload(0xc00), 20973093518318303058599911331413487018954341498059031715242648401352398993735, f_q)) +mstore(0x1ca0, mulmod(mload(0x1580), 14391499717548074167711220639833994904150450341569029103202493919171555826079, f_q)) +mstore(0x1cc0, addmod(mload(0xc00), 7496743154291201054535185105423280184397914058847005240495710267404252669538, f_q)) +mstore(0x1ce0, mulmod(mload(0x1580), 5522161504810533295870699551020523636289972223872138525048055197429246400245, f_q)) +mstore(0x1d00, addmod(mload(0xc00), 16366081367028741926375706194236751452258392176543895818650148989146562095372, f_q)) +mstore(0x1d20, mulmod(mload(0x1580), 10119780362642123194334092174270235809557798114544683654677907882314807212354, f_q)) +mstore(0x1d40, addmod(mload(0xc00), 11768462509197152027912313570987039278990566285871350689020296304261001283263, f_q)) +mstore(0x1d60, mulmod(mload(0x1580), 3766081621734395783232337525162072736827576297943013392955872170138036189193, f_q)) +mstore(0x1d80, addmod(mload(0xc00), 18122161250104879439014068220095202351720788102473020950742332016437772306424, f_q)) +mstore(0x1da0, mulmod(mload(0x1580), 2080322550956715654503104356805349981348621877591103674778333538652571537127, f_q)) +mstore(0x1dc0, addmod(mload(0xc00), 19807920320882559567743301388451925107199742522824930668919870647923236958490, f_q)) +mstore(0x1de0, mulmod(mload(0x1580), 9100833993744738801214480881117348002768153232283708533639316963648253510584, f_q)) +mstore(0x1e00, addmod(mload(0xc00), 12787408878094536421031924864139927085780211168132325810058887222927554985033, f_q)) +mstore(0x1e20, mulmod(mload(0x1580), 11145214675344139457514777444556774698911688619991656085001542609383151586084, f_q)) +mstore(0x1e40, addmod(mload(0xc00), 10743028196495135764731628300700500389636675780424378258696661577192656909533, f_q)) +mstore(0x1e60, mulmod(mload(0x1580), 4245441013247250116003069945606352967193023389718465410501109428393342802981, f_q)) +mstore(0x1e80, addmod(mload(0xc00), 17642801858592025106243335799650922121355341010697568933197094758182465692636, f_q)) +mstore(0x1ea0, mulmod(mload(0x1580), 19228510170961893342195489288913594506775385223367826565223897736323409650249, f_q)) +mstore(0x1ec0, addmod(mload(0xc00), 2659732700877381880050916456343680581772979177048207778474306450252398845368, f_q)) +mstore(0x1ee0, mulmod(mload(0x1580), 6132660129994545119218258312491950835441607143741804980633129304664017206141, f_q)) +mstore(0x1f00, addmod(mload(0xc00), 15755582741844730103028147432765324253106757256674229363065074881911791289476, f_q)) +mstore(0x1f20, mulmod(mload(0x1580), 10094752117139066216691253588991632982053223883646966177987813353508871280747, f_q)) +mstore(0x1f40, addmod(mload(0xc00), 11793490754700209005555152156265642106495140516769068165710390833066937214870, f_q)) +mstore(0x1f60, mulmod(mload(0x1580), 5854133144571823792863860130267644613802765696134002830362054821530146160770, f_q)) +mstore(0x1f80, addmod(mload(0xc00), 16034109727267451429382545614989630474745598704282031513336149365045662334847, f_q)) +mstore(0x1fa0, mulmod(mload(0x1580), 21346203717540287263608402129024479709126363130664317843105498655869866203005, f_q)) +mstore(0x1fc0, addmod(mload(0xc00), 542039154298987958638003616232795379422001269751716500592705530705942292612, f_q)) +mstore(0x1fe0, mulmod(mload(0x1580), 515148244606945972463850631189471072103916690263705052318085725998468254533, f_q)) +mstore(0x2000, addmod(mload(0xc00), 21373094627232329249782555114067804016444447710152329291380118460577340241084, f_q)) +mstore(0x2020, mulmod(mload(0x1580), 13788243025932779125104144225768424453664118806559109014238064020826883170336, f_q)) +mstore(0x2040, addmod(mload(0xc00), 8099999845906496097142261519488850634884245593856925329460140165748925325281, f_q)) +mstore(0x2060, mulmod(mload(0x1580), 5980488956150442207659150513163747165544364597008566989111579977672498964212, f_q)) +mstore(0x2080, addmod(mload(0xc00), 15907753915688833014587255232093527923003999803407467354586624208903309531405, f_q)) +mstore(0x20a0, mulmod(mload(0x1580), 8561696234966975469289029207282849740510759316794581475824569334969644143582, f_q)) +mstore(0x20c0, addmod(mload(0xc00), 13326546636872299752957376537974425348037605083621452867873634851606164352035, f_q)) +mstore(0x20e0, mulmod(mload(0x1580), 5223738580615264174925218065001555728265216895679471490312087802465486318994, f_q)) +mstore(0x2100, addmod(mload(0xc00), 16664504291224011047321187680255719360283147504736562853386116384110322176623, f_q)) +mstore(0x2120, mulmod(mload(0x1580), 3302268277365219249160464068848832456250192077357408622723420445620736662125, f_q)) +mstore(0x2140, addmod(mload(0xc00), 18585974594474055973085941676408442632298172323058625720974783740955071833492, f_q)) +mstore(0x2160, mulmod(mload(0x1580), 14557038802599140430182096396825290815503940951075961210638273254419942783582, f_q)) +mstore(0x2180, addmod(mload(0xc00), 7331204069240134792064309348431984273044423449340073133059930932155865712035, f_q)) +mstore(0x21a0, mulmod(mload(0x1580), 21631349642691366221117117325940229443266870213711402446456178962469345982255, f_q)) +mstore(0x21c0, addmod(mload(0xc00), 256893229147909001129288419317045645281494186704631897242025224106462513362, f_q)) +mstore(0x21e0, mulmod(mload(0x1580), 16976236069879939850923145256911338076234942200101755618884183331004076579046, f_q)) +mstore(0x2200, addmod(mload(0xc00), 4912006801959335371323260488345937012313422200314278724814020855571731916571, f_q)) +mstore(0x2220, mulmod(mload(0x1580), 18106030913818996184930975996483865250387924434749113154514488995517615180373, f_q)) +mstore(0x2240, addmod(mload(0xc00), 3782211958020279037315429748773409838160439965666921189183715191058193315244, f_q)) +mstore(0x2260, mulmod(mload(0x1580), 13553911191894110065493137367144919847521088405945523452288398666974237857208, f_q)) +mstore(0x2280, addmod(mload(0xc00), 8334331679945165156753268378112355241027275994470510891409805519601570638409, f_q)) +mstore(0x22a0, mulmod(mload(0x1580), 15126807493918544618788554261654793824894621953586710625413511093368555507114, f_q)) +mstore(0x22c0, addmod(mload(0xc00), 6761435377920730603457851483602481263653742446829323718284693093207252988503, f_q)) +mstore(0x22e0, mulmod(mload(0x1580), 12222687719926148270818604386979005738180875192307070468454582955273533101023, f_q)) +mstore(0x2300, addmod(mload(0xc00), 9665555151913126951427801358278269350367489208108963875243621231302275394594, f_q)) +mstore(0x2320, mulmod(mload(0x1580), 16714975918200644516413377824646615398811161167186708005951772994925725210988, f_q)) +mstore(0x2340, addmod(mload(0xc00), 5173266953638630705833027920610659689737203233229326337746431191650083284629, f_q)) +mstore(0x2360, mulmod(mload(0x1580), 9697063347556872083384215826199993067635178715531258559890418744774301211662, f_q)) +mstore(0x2380, addmod(mload(0xc00), 12191179524282403138862189919057282020913185684884775783807785441801507283955, f_q)) +mstore(0x23a0, mulmod(mload(0x1580), 8232431451150482057473812611943294635402895089782291356256081195754688638419, f_q)) +mstore(0x23c0, addmod(mload(0xc00), 13655811420688793164772593133313980453145469310633742987442122990821119857198, f_q)) +mstore(0x23e0, mulmod(mload(0x1580), 13783318220968413117070077848579881425001701814458176881760898225529300547844, f_q)) +mstore(0x2400, addmod(mload(0xc00), 8104924650870862105176327896677393663546662585957857461937305961046507947773, f_q)) +mstore(0x2420, mulmod(mload(0x1580), 17070294809977715215077308683367050851120007385961664319144486981376266100228, f_q)) +mstore(0x2440, addmod(mload(0xc00), 4817948061861560007169097061890224237428357014454370024553717205199542395389, f_q)) +mstore(0x2460, mulmod(mload(0x1580), 10807735674816066981985242612061336605021639643453679977988966079770672437131, f_q)) +mstore(0x2480, addmod(mload(0xc00), 11080507197023208240261163133195938483526724756962354365709238106805136058486, f_q)) +mstore(0x24a0, mulmod(mload(0x1580), 10320222094738691136760616041626757686650252828592064883134944032883139493239, f_q)) +mstore(0x24c0, addmod(mload(0xc00), 11568020777100584085485789703630517401898111571823969460563260153692669002378, f_q)) +mstore(0x24e0, mulmod(mload(0x1580), 15487660954688013862248478071816391715224351867581977083810729441220383572585, f_q)) +mstore(0x2500, addmod(mload(0xc00), 6400581917151261359997927673440883373324012532834057259887474745355424923032, f_q)) +mstore(0x2520, mulmod(mload(0x1580), 12896114329936223826328336851156210236116218608332234285980274197209453560736, f_q)) +mstore(0x2540, addmod(mload(0xc00), 8992128541903051395918068894101064852432145792083800057717929989366354934881, f_q)) +{ + let prod := mload(0x15c0) + + prod := mulmod(mload(0x1600), prod, f_q) + mstore(0x2560, prod) + + prod := mulmod(mload(0x1640), prod, f_q) + mstore(0x2580, prod) + + prod := mulmod(mload(0x1680), prod, f_q) + mstore(0x25a0, prod) + + prod := mulmod(mload(0x16c0), prod, f_q) + mstore(0x25c0, prod) + + prod := mulmod(mload(0x1700), prod, f_q) + mstore(0x25e0, prod) + + prod := mulmod(mload(0x1740), prod, f_q) + mstore(0x2600, prod) + + prod := mulmod(mload(0x1780), prod, f_q) + mstore(0x2620, prod) + + prod := mulmod(mload(0x17c0), prod, f_q) + mstore(0x2640, prod) + + prod := mulmod(mload(0x1800), prod, f_q) + mstore(0x2660, prod) + + prod := mulmod(mload(0x1840), prod, f_q) + mstore(0x2680, prod) + + prod := mulmod(mload(0x1880), prod, f_q) + mstore(0x26a0, prod) + + prod := mulmod(mload(0x18c0), prod, f_q) + mstore(0x26c0, prod) + + prod := mulmod(mload(0x1900), prod, f_q) + mstore(0x26e0, prod) + + prod := mulmod(mload(0x1940), prod, f_q) + mstore(0x2700, prod) + + prod := mulmod(mload(0x1980), prod, f_q) + mstore(0x2720, prod) + + prod := mulmod(mload(0x19c0), prod, f_q) + mstore(0x2740, prod) + + prod := mulmod(mload(0x1a00), prod, f_q) + mstore(0x2760, prod) + + prod := mulmod(mload(0x1a40), prod, f_q) + mstore(0x2780, prod) + + prod := mulmod(mload(0x1a80), prod, f_q) + mstore(0x27a0, prod) + + prod := mulmod(mload(0x1ac0), prod, f_q) + mstore(0x27c0, prod) + + prod := mulmod(mload(0x1b00), prod, f_q) + mstore(0x27e0, prod) + + prod := mulmod(mload(0x1b40), prod, f_q) + mstore(0x2800, prod) + + prod := mulmod(mload(0x1b80), prod, f_q) + mstore(0x2820, prod) + + prod := mulmod(mload(0x1bc0), prod, f_q) + mstore(0x2840, prod) + + prod := mulmod(mload(0x1c00), prod, f_q) + mstore(0x2860, prod) + + prod := mulmod(mload(0x1c40), prod, f_q) + mstore(0x2880, prod) + + prod := mulmod(mload(0x1c80), prod, f_q) + mstore(0x28a0, prod) + + prod := mulmod(mload(0x1cc0), prod, f_q) + mstore(0x28c0, prod) + + prod := mulmod(mload(0x1d00), prod, f_q) + mstore(0x28e0, prod) + + prod := mulmod(mload(0x1d40), prod, f_q) + mstore(0x2900, prod) + + prod := mulmod(mload(0x1d80), prod, f_q) + mstore(0x2920, prod) + + prod := mulmod(mload(0x1dc0), prod, f_q) + mstore(0x2940, prod) + + prod := mulmod(mload(0x1e00), prod, f_q) + mstore(0x2960, prod) + + prod := mulmod(mload(0x1e40), prod, f_q) + mstore(0x2980, prod) + + prod := mulmod(mload(0x1e80), prod, f_q) + mstore(0x29a0, prod) + + prod := mulmod(mload(0x1ec0), prod, f_q) + mstore(0x29c0, prod) + + prod := mulmod(mload(0x1f00), prod, f_q) + mstore(0x29e0, prod) + + prod := mulmod(mload(0x1f40), prod, f_q) + mstore(0x2a00, prod) + + prod := mulmod(mload(0x1f80), prod, f_q) + mstore(0x2a20, prod) + + prod := mulmod(mload(0x1fc0), prod, f_q) + mstore(0x2a40, prod) + + prod := mulmod(mload(0x2000), prod, f_q) + mstore(0x2a60, prod) + + prod := mulmod(mload(0x2040), prod, f_q) + mstore(0x2a80, prod) + + prod := mulmod(mload(0x2080), prod, f_q) + mstore(0x2aa0, prod) + + prod := mulmod(mload(0x20c0), prod, f_q) + mstore(0x2ac0, prod) + + prod := mulmod(mload(0x2100), prod, f_q) + mstore(0x2ae0, prod) + + prod := mulmod(mload(0x2140), prod, f_q) + mstore(0x2b00, prod) + + prod := mulmod(mload(0x2180), prod, f_q) + mstore(0x2b20, prod) + + prod := mulmod(mload(0x21c0), prod, f_q) + mstore(0x2b40, prod) + + prod := mulmod(mload(0x2200), prod, f_q) + mstore(0x2b60, prod) + + prod := mulmod(mload(0x2240), prod, f_q) + mstore(0x2b80, prod) + + prod := mulmod(mload(0x2280), prod, f_q) + mstore(0x2ba0, prod) + + prod := mulmod(mload(0x22c0), prod, f_q) + mstore(0x2bc0, prod) + + prod := mulmod(mload(0x2300), prod, f_q) + mstore(0x2be0, prod) + + prod := mulmod(mload(0x2340), prod, f_q) + mstore(0x2c00, prod) + + prod := mulmod(mload(0x2380), prod, f_q) + mstore(0x2c20, prod) + + prod := mulmod(mload(0x23c0), prod, f_q) + mstore(0x2c40, prod) + + prod := mulmod(mload(0x2400), prod, f_q) + mstore(0x2c60, prod) + + prod := mulmod(mload(0x2440), prod, f_q) + mstore(0x2c80, prod) + + prod := mulmod(mload(0x2480), prod, f_q) + mstore(0x2ca0, prod) + + prod := mulmod(mload(0x24c0), prod, f_q) + mstore(0x2cc0, prod) + + prod := mulmod(mload(0x2500), prod, f_q) + mstore(0x2ce0, prod) + + prod := mulmod(mload(0x2540), prod, f_q) + mstore(0x2d00, prod) + + prod := mulmod(mload(0x1560), prod, f_q) + mstore(0x2d20, prod) + + } +mstore(0x2d60, 32) +mstore(0x2d80, 32) +mstore(0x2da0, 32) +mstore(0x2dc0, mload(0x2d20)) +mstore(0x2de0, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x2e00, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x2d60, 0xc0, 0x2d40, 0x20), 1), success) +{ + + let inv := mload(0x2d40) + let v + + v := mload(0x1560) + mstore(5472, mulmod(mload(0x2d00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2540) + mstore(9536, mulmod(mload(0x2ce0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2500) + mstore(9472, mulmod(mload(0x2cc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x24c0) + mstore(9408, mulmod(mload(0x2ca0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2480) + mstore(9344, mulmod(mload(0x2c80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2440) + mstore(9280, mulmod(mload(0x2c60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2400) + mstore(9216, mulmod(mload(0x2c40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x23c0) + mstore(9152, mulmod(mload(0x2c20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2380) + mstore(9088, mulmod(mload(0x2c00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2340) + mstore(9024, mulmod(mload(0x2be0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2300) + mstore(8960, mulmod(mload(0x2bc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x22c0) + mstore(8896, mulmod(mload(0x2ba0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2280) + mstore(8832, mulmod(mload(0x2b80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2240) + mstore(8768, mulmod(mload(0x2b60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2200) + mstore(8704, mulmod(mload(0x2b40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x21c0) + mstore(8640, mulmod(mload(0x2b20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2180) + mstore(8576, mulmod(mload(0x2b00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2140) + mstore(8512, mulmod(mload(0x2ae0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2100) + mstore(8448, mulmod(mload(0x2ac0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x20c0) + mstore(8384, mulmod(mload(0x2aa0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2080) + mstore(8320, mulmod(mload(0x2a80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2040) + mstore(8256, mulmod(mload(0x2a60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x2000) + mstore(8192, mulmod(mload(0x2a40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1fc0) + mstore(8128, mulmod(mload(0x2a20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1f80) + mstore(8064, mulmod(mload(0x2a00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1f40) + mstore(8000, mulmod(mload(0x29e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1f00) + mstore(7936, mulmod(mload(0x29c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1ec0) + mstore(7872, mulmod(mload(0x29a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1e80) + mstore(7808, mulmod(mload(0x2980), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1e40) + mstore(7744, mulmod(mload(0x2960), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1e00) + mstore(7680, mulmod(mload(0x2940), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1dc0) + mstore(7616, mulmod(mload(0x2920), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1d80) + mstore(7552, mulmod(mload(0x2900), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1d40) + mstore(7488, mulmod(mload(0x28e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1d00) + mstore(7424, mulmod(mload(0x28c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1cc0) + mstore(7360, mulmod(mload(0x28a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1c80) + mstore(7296, mulmod(mload(0x2880), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1c40) + mstore(7232, mulmod(mload(0x2860), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1c00) + mstore(7168, mulmod(mload(0x2840), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1bc0) + mstore(7104, mulmod(mload(0x2820), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1b80) + mstore(7040, mulmod(mload(0x2800), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1b40) + mstore(6976, mulmod(mload(0x27e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1b00) + mstore(6912, mulmod(mload(0x27c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1ac0) + mstore(6848, mulmod(mload(0x27a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1a80) + mstore(6784, mulmod(mload(0x2780), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1a40) + mstore(6720, mulmod(mload(0x2760), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1a00) + mstore(6656, mulmod(mload(0x2740), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x19c0) + mstore(6592, mulmod(mload(0x2720), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1980) + mstore(6528, mulmod(mload(0x2700), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1940) + mstore(6464, mulmod(mload(0x26e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1900) + mstore(6400, mulmod(mload(0x26c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x18c0) + mstore(6336, mulmod(mload(0x26a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1880) + mstore(6272, mulmod(mload(0x2680), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1840) + mstore(6208, mulmod(mload(0x2660), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1800) + mstore(6144, mulmod(mload(0x2640), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x17c0) + mstore(6080, mulmod(mload(0x2620), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1780) + mstore(6016, mulmod(mload(0x2600), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1740) + mstore(5952, mulmod(mload(0x25e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1700) + mstore(5888, mulmod(mload(0x25c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x16c0) + mstore(5824, mulmod(mload(0x25a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1680) + mstore(5760, mulmod(mload(0x2580), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1640) + mstore(5696, mulmod(mload(0x2560), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1600) + mstore(5632, mulmod(mload(0x15c0), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x15c0, inv) + + } +mstore(0x2e20, mulmod(mload(0x15a0), mload(0x15c0), f_q)) +mstore(0x2e40, mulmod(mload(0x15e0), mload(0x1600), f_q)) +mstore(0x2e60, mulmod(mload(0x1620), mload(0x1640), f_q)) +mstore(0x2e80, mulmod(mload(0x1660), mload(0x1680), f_q)) +mstore(0x2ea0, mulmod(mload(0x16a0), mload(0x16c0), f_q)) +mstore(0x2ec0, mulmod(mload(0x16e0), mload(0x1700), f_q)) +mstore(0x2ee0, mulmod(mload(0x1720), mload(0x1740), f_q)) +mstore(0x2f00, mulmod(mload(0x1760), mload(0x1780), f_q)) +mstore(0x2f20, mulmod(mload(0x17a0), mload(0x17c0), f_q)) +mstore(0x2f40, mulmod(mload(0x17e0), mload(0x1800), f_q)) +mstore(0x2f60, mulmod(mload(0x1820), mload(0x1840), f_q)) +mstore(0x2f80, mulmod(mload(0x1860), mload(0x1880), f_q)) +mstore(0x2fa0, mulmod(mload(0x18a0), mload(0x18c0), f_q)) +mstore(0x2fc0, mulmod(mload(0x18e0), mload(0x1900), f_q)) +mstore(0x2fe0, mulmod(mload(0x1920), mload(0x1940), f_q)) +mstore(0x3000, mulmod(mload(0x1960), mload(0x1980), f_q)) +mstore(0x3020, mulmod(mload(0x19a0), mload(0x19c0), f_q)) +mstore(0x3040, mulmod(mload(0x19e0), mload(0x1a00), f_q)) +mstore(0x3060, mulmod(mload(0x1a20), mload(0x1a40), f_q)) +mstore(0x3080, mulmod(mload(0x1a60), mload(0x1a80), f_q)) +mstore(0x30a0, mulmod(mload(0x1aa0), mload(0x1ac0), f_q)) +mstore(0x30c0, mulmod(mload(0x1ae0), mload(0x1b00), f_q)) +mstore(0x30e0, mulmod(mload(0x1b20), mload(0x1b40), f_q)) +mstore(0x3100, mulmod(mload(0x1b60), mload(0x1b80), f_q)) +mstore(0x3120, mulmod(mload(0x1ba0), mload(0x1bc0), f_q)) +mstore(0x3140, mulmod(mload(0x1be0), mload(0x1c00), f_q)) +mstore(0x3160, mulmod(mload(0x1c20), mload(0x1c40), f_q)) +mstore(0x3180, mulmod(mload(0x1c60), mload(0x1c80), f_q)) +mstore(0x31a0, mulmod(mload(0x1ca0), mload(0x1cc0), f_q)) +mstore(0x31c0, mulmod(mload(0x1ce0), mload(0x1d00), f_q)) +mstore(0x31e0, mulmod(mload(0x1d20), mload(0x1d40), f_q)) +mstore(0x3200, mulmod(mload(0x1d60), mload(0x1d80), f_q)) +mstore(0x3220, mulmod(mload(0x1da0), mload(0x1dc0), f_q)) +mstore(0x3240, mulmod(mload(0x1de0), mload(0x1e00), f_q)) +mstore(0x3260, mulmod(mload(0x1e20), mload(0x1e40), f_q)) +mstore(0x3280, mulmod(mload(0x1e60), mload(0x1e80), f_q)) +mstore(0x32a0, mulmod(mload(0x1ea0), mload(0x1ec0), f_q)) +mstore(0x32c0, mulmod(mload(0x1ee0), mload(0x1f00), f_q)) +mstore(0x32e0, mulmod(mload(0x1f20), mload(0x1f40), f_q)) +mstore(0x3300, mulmod(mload(0x1f60), mload(0x1f80), f_q)) +mstore(0x3320, mulmod(mload(0x1fa0), mload(0x1fc0), f_q)) +mstore(0x3340, mulmod(mload(0x1fe0), mload(0x2000), f_q)) +mstore(0x3360, mulmod(mload(0x2020), mload(0x2040), f_q)) +mstore(0x3380, mulmod(mload(0x2060), mload(0x2080), f_q)) +mstore(0x33a0, mulmod(mload(0x20a0), mload(0x20c0), f_q)) +mstore(0x33c0, mulmod(mload(0x20e0), mload(0x2100), f_q)) +mstore(0x33e0, mulmod(mload(0x2120), mload(0x2140), f_q)) +mstore(0x3400, mulmod(mload(0x2160), mload(0x2180), f_q)) +mstore(0x3420, mulmod(mload(0x21a0), mload(0x21c0), f_q)) +mstore(0x3440, mulmod(mload(0x21e0), mload(0x2200), f_q)) +mstore(0x3460, mulmod(mload(0x2220), mload(0x2240), f_q)) +mstore(0x3480, mulmod(mload(0x2260), mload(0x2280), f_q)) +mstore(0x34a0, mulmod(mload(0x22a0), mload(0x22c0), f_q)) +mstore(0x34c0, mulmod(mload(0x22e0), mload(0x2300), f_q)) +mstore(0x34e0, mulmod(mload(0x2320), mload(0x2340), f_q)) +mstore(0x3500, mulmod(mload(0x2360), mload(0x2380), f_q)) +mstore(0x3520, mulmod(mload(0x23a0), mload(0x23c0), f_q)) +mstore(0x3540, mulmod(mload(0x23e0), mload(0x2400), f_q)) +mstore(0x3560, mulmod(mload(0x2420), mload(0x2440), f_q)) +mstore(0x3580, mulmod(mload(0x2460), mload(0x2480), f_q)) +mstore(0x35a0, mulmod(mload(0x24a0), mload(0x24c0), f_q)) +mstore(0x35c0, mulmod(mload(0x24e0), mload(0x2500), f_q)) +mstore(0x35e0, mulmod(mload(0x2520), mload(0x2540), f_q)) +{ + let result := mulmod(mload(0x2f00), mload(0x20), f_q) +result := addmod(mulmod(mload(0x2f20), mload(0x40), f_q), result, f_q) +result := addmod(mulmod(mload(0x2f40), mload(0x60), f_q), result, f_q) +result := addmod(mulmod(mload(0x2f60), mload(0x80), f_q), result, f_q) +result := addmod(mulmod(mload(0x2f80), mload(0xa0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2fa0), mload(0xc0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2fc0), mload(0xe0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2fe0), mload(0x100), f_q), result, f_q) +result := addmod(mulmod(mload(0x3000), mload(0x120), f_q), result, f_q) +result := addmod(mulmod(mload(0x3020), mload(0x140), f_q), result, f_q) +result := addmod(mulmod(mload(0x3040), mload(0x160), f_q), result, f_q) +result := addmod(mulmod(mload(0x3060), mload(0x180), f_q), result, f_q) +result := addmod(mulmod(mload(0x3080), mload(0x1a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x30a0), mload(0x1c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x30c0), mload(0x1e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x30e0), mload(0x200), f_q), result, f_q) +result := addmod(mulmod(mload(0x3100), mload(0x220), f_q), result, f_q) +result := addmod(mulmod(mload(0x3120), mload(0x240), f_q), result, f_q) +result := addmod(mulmod(mload(0x3140), mload(0x260), f_q), result, f_q) +result := addmod(mulmod(mload(0x3160), mload(0x280), f_q), result, f_q) +result := addmod(mulmod(mload(0x3180), mload(0x2a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x31a0), mload(0x2c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x31c0), mload(0x2e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x31e0), mload(0x300), f_q), result, f_q) +result := addmod(mulmod(mload(0x3200), mload(0x320), f_q), result, f_q) +result := addmod(mulmod(mload(0x3220), mload(0x340), f_q), result, f_q) +result := addmod(mulmod(mload(0x3240), mload(0x360), f_q), result, f_q) +result := addmod(mulmod(mload(0x3260), mload(0x380), f_q), result, f_q) +result := addmod(mulmod(mload(0x3280), mload(0x3a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x32a0), mload(0x3c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x32c0), mload(0x3e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x32e0), mload(0x400), f_q), result, f_q) +result := addmod(mulmod(mload(0x3300), mload(0x420), f_q), result, f_q) +result := addmod(mulmod(mload(0x3320), mload(0x440), f_q), result, f_q) +result := addmod(mulmod(mload(0x3340), mload(0x460), f_q), result, f_q) +result := addmod(mulmod(mload(0x3360), mload(0x480), f_q), result, f_q) +result := addmod(mulmod(mload(0x3380), mload(0x4a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x33a0), mload(0x4c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x33c0), mload(0x4e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x33e0), mload(0x500), f_q), result, f_q) +result := addmod(mulmod(mload(0x3400), mload(0x520), f_q), result, f_q) +result := addmod(mulmod(mload(0x3420), mload(0x540), f_q), result, f_q) +result := addmod(mulmod(mload(0x3440), mload(0x560), f_q), result, f_q) +result := addmod(mulmod(mload(0x3460), mload(0x580), f_q), result, f_q) +result := addmod(mulmod(mload(0x3480), mload(0x5a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x34a0), mload(0x5c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x34c0), mload(0x5e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x34e0), mload(0x600), f_q), result, f_q) +result := addmod(mulmod(mload(0x3500), mload(0x620), f_q), result, f_q) +result := addmod(mulmod(mload(0x3520), mload(0x640), f_q), result, f_q) +result := addmod(mulmod(mload(0x3540), mload(0x660), f_q), result, f_q) +result := addmod(mulmod(mload(0x3560), mload(0x680), f_q), result, f_q) +result := addmod(mulmod(mload(0x3580), mload(0x6a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x35a0), mload(0x6c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x35c0), mload(0x6e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x35e0), mload(0x700), f_q), result, f_q) +mstore(13824, result) + } +mstore(0x3620, mulmod(mload(0xc80), mload(0xc60), f_q)) +mstore(0x3640, addmod(mload(0xc40), mload(0x3620), f_q)) +mstore(0x3660, addmod(mload(0x3640), sub(f_q, mload(0xca0)), f_q)) +mstore(0x3680, mulmod(mload(0x3660), mload(0xda0), f_q)) +mstore(0x36a0, mulmod(mload(0xae0), mload(0x3680), f_q)) +mstore(0x36c0, mulmod(mload(0xd00), mload(0xce0), f_q)) +mstore(0x36e0, addmod(mload(0xcc0), mload(0x36c0), f_q)) +mstore(0x3700, addmod(mload(0x36e0), sub(f_q, mload(0xd20)), f_q)) +mstore(0x3720, mulmod(mload(0x3700), mload(0xdc0), f_q)) +mstore(0x3740, addmod(mload(0x36a0), mload(0x3720), f_q)) +mstore(0x3760, mulmod(mload(0xae0), mload(0x3740), f_q)) +mstore(0x3780, addmod(1, sub(f_q, mload(0xea0)), f_q)) +mstore(0x37a0, mulmod(mload(0x3780), mload(0x2f00), f_q)) +mstore(0x37c0, addmod(mload(0x3760), mload(0x37a0), f_q)) +mstore(0x37e0, mulmod(mload(0xae0), mload(0x37c0), f_q)) +mstore(0x3800, mulmod(mload(0xf60), mload(0xf60), f_q)) +mstore(0x3820, addmod(mload(0x3800), sub(f_q, mload(0xf60)), f_q)) +mstore(0x3840, mulmod(mload(0x3820), mload(0x2e20), f_q)) +mstore(0x3860, addmod(mload(0x37e0), mload(0x3840), f_q)) +mstore(0x3880, mulmod(mload(0xae0), mload(0x3860), f_q)) +mstore(0x38a0, addmod(mload(0xf00), sub(f_q, mload(0xee0)), f_q)) +mstore(0x38c0, mulmod(mload(0x38a0), mload(0x2f00), f_q)) +mstore(0x38e0, addmod(mload(0x3880), mload(0x38c0), f_q)) +mstore(0x3900, mulmod(mload(0xae0), mload(0x38e0), f_q)) +mstore(0x3920, addmod(mload(0xf60), sub(f_q, mload(0xf40)), f_q)) +mstore(0x3940, mulmod(mload(0x3920), mload(0x2f00), f_q)) +mstore(0x3960, addmod(mload(0x3900), mload(0x3940), f_q)) +mstore(0x3980, mulmod(mload(0xae0), mload(0x3960), f_q)) +mstore(0x39a0, addmod(1, sub(f_q, mload(0x2e20)), f_q)) +mstore(0x39c0, addmod(mload(0x2e40), mload(0x2e60), f_q)) +mstore(0x39e0, addmod(mload(0x39c0), mload(0x2e80), f_q)) +mstore(0x3a00, addmod(mload(0x39e0), mload(0x2ea0), f_q)) +mstore(0x3a20, addmod(mload(0x3a00), mload(0x2ec0), f_q)) +mstore(0x3a40, addmod(mload(0x3a20), mload(0x2ee0), f_q)) +mstore(0x3a60, addmod(mload(0x39a0), sub(f_q, mload(0x3a40)), f_q)) +mstore(0x3a80, mulmod(mload(0xe00), mload(0x8e0), f_q)) +mstore(0x3aa0, addmod(mload(0xd60), mload(0x3a80), f_q)) +mstore(0x3ac0, addmod(mload(0x3aa0), mload(0x940), f_q)) +mstore(0x3ae0, mulmod(mload(0xe20), mload(0x8e0), f_q)) +mstore(0x3b00, addmod(mload(0xc40), mload(0x3ae0), f_q)) +mstore(0x3b20, addmod(mload(0x3b00), mload(0x940), f_q)) +mstore(0x3b40, mulmod(mload(0x3b20), mload(0x3ac0), f_q)) +mstore(0x3b60, mulmod(mload(0x3b40), mload(0xec0), f_q)) +mstore(0x3b80, mulmod(1, mload(0x8e0), f_q)) +mstore(0x3ba0, mulmod(mload(0xc00), mload(0x3b80), f_q)) +mstore(0x3bc0, addmod(mload(0xd60), mload(0x3ba0), f_q)) +mstore(0x3be0, addmod(mload(0x3bc0), mload(0x940), f_q)) +mstore(0x3c00, mulmod(4131629893567559867359510883348571134090853742863529169391034518566172092834, mload(0x8e0), f_q)) +mstore(0x3c20, mulmod(mload(0xc00), mload(0x3c00), f_q)) +mstore(0x3c40, addmod(mload(0xc40), mload(0x3c20), f_q)) +mstore(0x3c60, addmod(mload(0x3c40), mload(0x940), f_q)) +mstore(0x3c80, mulmod(mload(0x3c60), mload(0x3be0), f_q)) +mstore(0x3ca0, mulmod(mload(0x3c80), mload(0xea0), f_q)) +mstore(0x3cc0, addmod(mload(0x3b60), sub(f_q, mload(0x3ca0)), f_q)) +mstore(0x3ce0, mulmod(mload(0x3cc0), mload(0x3a60), f_q)) +mstore(0x3d00, addmod(mload(0x3980), mload(0x3ce0), f_q)) +mstore(0x3d20, mulmod(mload(0xae0), mload(0x3d00), f_q)) +mstore(0x3d40, mulmod(mload(0xe40), mload(0x8e0), f_q)) +mstore(0x3d60, addmod(mload(0xcc0), mload(0x3d40), f_q)) +mstore(0x3d80, addmod(mload(0x3d60), mload(0x940), f_q)) +mstore(0x3da0, mulmod(mload(0xe60), mload(0x8e0), f_q)) +mstore(0x3dc0, addmod(mload(0xd40), mload(0x3da0), f_q)) +mstore(0x3de0, addmod(mload(0x3dc0), mload(0x940), f_q)) +mstore(0x3e00, mulmod(mload(0x3de0), mload(0x3d80), f_q)) +mstore(0x3e20, mulmod(mload(0x3e00), mload(0xf20), f_q)) +mstore(0x3e40, mulmod(8910878055287538404433155982483128285667088683464058436815641868457422632747, mload(0x8e0), f_q)) +mstore(0x3e60, mulmod(mload(0xc00), mload(0x3e40), f_q)) +mstore(0x3e80, addmod(mload(0xcc0), mload(0x3e60), f_q)) +mstore(0x3ea0, addmod(mload(0x3e80), mload(0x940), f_q)) +mstore(0x3ec0, mulmod(11166246659983828508719468090013646171463329086121580628794302409516816350802, mload(0x8e0), f_q)) +mstore(0x3ee0, mulmod(mload(0xc00), mload(0x3ec0), f_q)) +mstore(0x3f00, addmod(mload(0xd40), mload(0x3ee0), f_q)) +mstore(0x3f20, addmod(mload(0x3f00), mload(0x940), f_q)) +mstore(0x3f40, mulmod(mload(0x3f20), mload(0x3ea0), f_q)) +mstore(0x3f60, mulmod(mload(0x3f40), mload(0xf00), f_q)) +mstore(0x3f80, addmod(mload(0x3e20), sub(f_q, mload(0x3f60)), f_q)) +mstore(0x3fa0, mulmod(mload(0x3f80), mload(0x3a60), f_q)) +mstore(0x3fc0, addmod(mload(0x3d20), mload(0x3fa0), f_q)) +mstore(0x3fe0, mulmod(mload(0xae0), mload(0x3fc0), f_q)) +mstore(0x4000, mulmod(mload(0xe80), mload(0x8e0), f_q)) +mstore(0x4020, addmod(mload(0x3600), mload(0x4000), f_q)) +mstore(0x4040, addmod(mload(0x4020), mload(0x940), f_q)) +mstore(0x4060, mulmod(mload(0x4040), mload(0xf80), f_q)) +mstore(0x4080, mulmod(284840088355319032285349970403338060113257071685626700086398481893096618818, mload(0x8e0), f_q)) +mstore(0x40a0, mulmod(mload(0xc00), mload(0x4080), f_q)) +mstore(0x40c0, addmod(mload(0x3600), mload(0x40a0), f_q)) +mstore(0x40e0, addmod(mload(0x40c0), mload(0x940), f_q)) +mstore(0x4100, mulmod(mload(0x40e0), mload(0xf60), f_q)) +mstore(0x4120, addmod(mload(0x4060), sub(f_q, mload(0x4100)), f_q)) +mstore(0x4140, mulmod(mload(0x4120), mload(0x3a60), f_q)) +mstore(0x4160, addmod(mload(0x3fe0), mload(0x4140), f_q)) +mstore(0x4180, mulmod(mload(0xae0), mload(0x4160), f_q)) +mstore(0x41a0, addmod(1, sub(f_q, mload(0xfa0)), f_q)) +mstore(0x41c0, mulmod(mload(0x41a0), mload(0x2f00), f_q)) +mstore(0x41e0, addmod(mload(0x4180), mload(0x41c0), f_q)) +mstore(0x4200, mulmod(mload(0xae0), mload(0x41e0), f_q)) +mstore(0x4220, mulmod(mload(0xfa0), mload(0xfa0), f_q)) +mstore(0x4240, addmod(mload(0x4220), sub(f_q, mload(0xfa0)), f_q)) +mstore(0x4260, mulmod(mload(0x4240), mload(0x2e20), f_q)) +mstore(0x4280, addmod(mload(0x4200), mload(0x4260), f_q)) +mstore(0x42a0, mulmod(mload(0xae0), mload(0x4280), f_q)) +mstore(0x42c0, addmod(mload(0xfe0), mload(0x8e0), f_q)) +mstore(0x42e0, mulmod(mload(0x42c0), mload(0xfc0), f_q)) +mstore(0x4300, addmod(mload(0x1020), mload(0x940), f_q)) +mstore(0x4320, mulmod(mload(0x4300), mload(0x42e0), f_q)) +mstore(0x4340, addmod(mload(0xd40), mload(0x8e0), f_q)) +mstore(0x4360, mulmod(mload(0x4340), mload(0xfa0), f_q)) +mstore(0x4380, addmod(mload(0xd80), mload(0x940), f_q)) +mstore(0x43a0, mulmod(mload(0x4380), mload(0x4360), f_q)) +mstore(0x43c0, addmod(mload(0x4320), sub(f_q, mload(0x43a0)), f_q)) +mstore(0x43e0, mulmod(mload(0x43c0), mload(0x3a60), f_q)) +mstore(0x4400, addmod(mload(0x42a0), mload(0x43e0), f_q)) +mstore(0x4420, mulmod(mload(0xae0), mload(0x4400), f_q)) +mstore(0x4440, addmod(mload(0xfe0), sub(f_q, mload(0x1020)), f_q)) +mstore(0x4460, mulmod(mload(0x4440), mload(0x2f00), f_q)) +mstore(0x4480, addmod(mload(0x4420), mload(0x4460), f_q)) +mstore(0x44a0, mulmod(mload(0xae0), mload(0x4480), f_q)) +mstore(0x44c0, mulmod(mload(0x4440), mload(0x3a60), f_q)) +mstore(0x44e0, addmod(mload(0xfe0), sub(f_q, mload(0x1000)), f_q)) +mstore(0x4500, mulmod(mload(0x44e0), mload(0x44c0), f_q)) +mstore(0x4520, addmod(mload(0x44a0), mload(0x4500), f_q)) +mstore(0x4540, mulmod(mload(0x1540), mload(0x1540), f_q)) +mstore(0x4560, mulmod(mload(0x4540), mload(0x1540), f_q)) +mstore(0x4580, mulmod(1, mload(0x1540), f_q)) +mstore(0x45a0, mulmod(1, mload(0x4540), f_q)) +mstore(0x45c0, mulmod(mload(0x4520), mload(0x1560), f_q)) +mstore(0x45e0, mulmod(mload(0x1260), mload(0xc00), f_q)) +mstore(0x4600, mulmod(mload(0x45e0), mload(0xc00), f_q)) +mstore(0x4620, mulmod(mload(0xc00), 1, f_q)) +mstore(0x4640, addmod(mload(0x1160), sub(f_q, mload(0x4620)), f_q)) +mstore(0x4660, mulmod(mload(0xc00), 8374374965308410102411073611984011876711565317741801500439755773472076597347, f_q)) +mstore(0x4680, addmod(mload(0x1160), sub(f_q, mload(0x4660)), f_q)) +mstore(0x46a0, mulmod(mload(0xc00), 11451405578697956743456240853980216273390554734748796433026540431386972584651, f_q)) +mstore(0x46c0, addmod(mload(0x1160), sub(f_q, mload(0x46a0)), f_q)) +mstore(0x46e0, mulmod(mload(0xc00), 12929131318670223636853686797196826072950305380535537217467769528748593133487, f_q)) +mstore(0x4700, addmod(mload(0x1160), sub(f_q, mload(0x46e0)), f_q)) +mstore(0x4720, mulmod(mload(0xc00), 17329448237240114492580865744088056414251735686965494637158808787419781175510, f_q)) +mstore(0x4740, addmod(mload(0x1160), sub(f_q, mload(0x4720)), f_q)) +mstore(0x4760, mulmod(mload(0xc00), 21490807004895109926141140246143262403290679459142140821740925192625185504522, f_q)) +mstore(0x4780, addmod(mload(0x1160), sub(f_q, mload(0x4760)), f_q)) +{ + let result := mulmod(mload(0x1160), mulmod(mload(0x45e0), 6616149745577394522356295102346368305374051634342887004165528916468992151333, f_q), f_q) +result := addmod(mulmod(mload(0xc00), mulmod(mload(0x45e0), 15272093126261880699890110642910906783174312766073147339532675270106816344284, f_q), f_q), result, f_q) +mstore(18336, result) + } +{ + let result := mulmod(mload(0x1160), mulmod(mload(0x45e0), 530501691302793820034524283154921640443166880847115433758691660016816186416, f_q), f_q) +result := addmod(mulmod(mload(0xc00), mulmod(mload(0x45e0), 6735468303947967792722299167169712601265763928443086612877978228369959138708, f_q), f_q), result, f_q) +mstore(18368, result) + } +{ + let result := mulmod(mload(0x1160), mulmod(mload(0x45e0), 6735468303947967792722299167169712601265763928443086612877978228369959138708, f_q), f_q) +result := addmod(mulmod(mload(0xc00), mulmod(mload(0x45e0), 21402573809525492531235934453699988060841876665026314791644170130242704768864, f_q), f_q), result, f_q) +mstore(18400, result) + } +{ + let result := mulmod(mload(0x1160), mulmod(mload(0x45e0), 21558793644302942916864965630979640748886316167261336210841195936026980690666, f_q), f_q) +result := addmod(mulmod(mload(0xc00), mulmod(mload(0x45e0), 21647881284526053590463969745634050372655996593461286860577821962674562513632, f_q), f_q), result, f_q) +mstore(18432, result) + } +mstore(0x4820, mulmod(1, mload(0x4640), f_q)) +mstore(0x4840, mulmod(mload(0x4820), mload(0x46c0), f_q)) +mstore(0x4860, mulmod(mload(0x4840), mload(0x4680), f_q)) +mstore(0x4880, mulmod(mload(0x4860), mload(0x4780), f_q)) +{ + let result := mulmod(mload(0x1160), 1, f_q) +result := addmod(mulmod(mload(0xc00), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q), result, f_q) +mstore(18592, result) + } +{ + let result := mulmod(mload(0x1160), mulmod(mload(0x1260), 12163000419891990293569405173061573680049742717229898748261573253229795914908, f_q), f_q) +result := addmod(mulmod(mload(0xc00), mulmod(mload(0x1260), 9725242451947284928677000572195701408498621683186135595436630933346012580709, f_q), f_q), result, f_q) +mstore(18624, result) + } +{ + let result := mulmod(mload(0x1160), mulmod(mload(0x1260), 17085049131699056766421998221476555826977441931846378573521510030619952504372, f_q), f_q) +result := addmod(mulmod(mload(0xc00), mulmod(mload(0x1260), 6337000465755888211746305680664882431492568521396101891532798530745714905908, f_q), f_q), result, f_q) +mstore(18656, result) + } +{ + let result := mulmod(mload(0x1160), mulmod(mload(0x1260), 10262058425268217215884133263876699099081481632544093361167483234163265012860, f_q), f_q) +result := addmod(mulmod(mload(0xc00), mulmod(mload(0x1260), 14297308348282218433797077139696728813764374573836158179437870281950912384055, f_q), f_q), result, f_q) +mstore(18688, result) + } +mstore(0x4920, mulmod(mload(0x4840), mload(0x4700), f_q)) +{ + let result := mulmod(mload(0x1160), mulmod(mload(0xc00), 10436837293141318478790164891277058815157809665667237910671663755188835910967, f_q), f_q) +result := addmod(mulmod(mload(0xc00), mulmod(mload(0xc00), 11451405578697956743456240853980216273390554734748796433026540431386972584650, f_q), f_q), result, f_q) +mstore(18752, result) + } +{ + let result := mulmod(mload(0x1160), mulmod(mload(0xc00), 11451405578697956743456240853980216273390554734748796433026540431386972584650, f_q), f_q) +result := addmod(mulmod(mload(0xc00), mulmod(mload(0xc00), 3077030613389546641045167241996204396678989417006994932586784657914895987304, f_q), f_q), result, f_q) +mstore(18784, result) + } +{ + let result := mulmod(mload(0x1160), mulmod(mload(0xc00), 4558794634599160729665540001169218674296628713450539706539395399156027320108, f_q), f_q) +result := addmod(mulmod(mload(0xc00), mulmod(mload(0xc00), 17329448237240114492580865744088056414251735686965494637158808787419781175509, f_q), f_q), result, f_q) +mstore(18816, result) + } +{ + let result := mulmod(mload(0x1160), mulmod(mload(0xc00), 17329448237240114492580865744088056414251735686965494637158808787419781175509, f_q), f_q) +result := addmod(mulmod(mload(0xc00), mulmod(mload(0xc00), 7587894345819650164285585254437911847348718480492193252124775402539837301163, f_q), f_q), result, f_q) +mstore(18848, result) + } +mstore(0x49c0, mulmod(mload(0x4820), mload(0x4740), f_q)) +{ + let prod := mload(0x47a0) + + prod := mulmod(mload(0x47c0), prod, f_q) + mstore(0x49e0, prod) + + prod := mulmod(mload(0x47e0), prod, f_q) + mstore(0x4a00, prod) + + prod := mulmod(mload(0x4800), prod, f_q) + mstore(0x4a20, prod) + + prod := mulmod(mload(0x48a0), prod, f_q) + mstore(0x4a40, prod) + + prod := mulmod(mload(0x4820), prod, f_q) + mstore(0x4a60, prod) + + prod := mulmod(mload(0x48c0), prod, f_q) + mstore(0x4a80, prod) + + prod := mulmod(mload(0x48e0), prod, f_q) + mstore(0x4aa0, prod) + + prod := mulmod(mload(0x4900), prod, f_q) + mstore(0x4ac0, prod) + + prod := mulmod(mload(0x4920), prod, f_q) + mstore(0x4ae0, prod) + + prod := mulmod(mload(0x4940), prod, f_q) + mstore(0x4b00, prod) + + prod := mulmod(mload(0x4960), prod, f_q) + mstore(0x4b20, prod) + + prod := mulmod(mload(0x4840), prod, f_q) + mstore(0x4b40, prod) + + prod := mulmod(mload(0x4980), prod, f_q) + mstore(0x4b60, prod) + + prod := mulmod(mload(0x49a0), prod, f_q) + mstore(0x4b80, prod) + + prod := mulmod(mload(0x49c0), prod, f_q) + mstore(0x4ba0, prod) + + } +mstore(0x4be0, 32) +mstore(0x4c00, 32) +mstore(0x4c20, 32) +mstore(0x4c40, mload(0x4ba0)) +mstore(0x4c60, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x4c80, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x4be0, 0xc0, 0x4bc0, 0x20), 1), success) +{ + + let inv := mload(0x4bc0) + let v + + v := mload(0x49c0) + mstore(18880, mulmod(mload(0x4b80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x49a0) + mstore(18848, mulmod(mload(0x4b60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4980) + mstore(18816, mulmod(mload(0x4b40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4840) + mstore(18496, mulmod(mload(0x4b20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4960) + mstore(18784, mulmod(mload(0x4b00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4940) + mstore(18752, mulmod(mload(0x4ae0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4920) + mstore(18720, mulmod(mload(0x4ac0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4900) + mstore(18688, mulmod(mload(0x4aa0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x48e0) + mstore(18656, mulmod(mload(0x4a80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x48c0) + mstore(18624, mulmod(mload(0x4a60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4820) + mstore(18464, mulmod(mload(0x4a40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x48a0) + mstore(18592, mulmod(mload(0x4a20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4800) + mstore(18432, mulmod(mload(0x4a00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x47e0) + mstore(18400, mulmod(mload(0x49e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x47c0) + mstore(18368, mulmod(mload(0x47a0), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x47a0, inv) + + } +{ + let result := mload(0x47a0) +result := addmod(mload(0x47c0), result, f_q) +result := addmod(mload(0x47e0), result, f_q) +result := addmod(mload(0x4800), result, f_q) +mstore(19616, result) + } +mstore(0x4cc0, mulmod(mload(0x4880), mload(0x4820), f_q)) +{ + let result := mload(0x48a0) +mstore(19680, result) + } +mstore(0x4d00, mulmod(mload(0x4880), mload(0x4920), f_q)) +{ + let result := mload(0x48c0) +result := addmod(mload(0x48e0), result, f_q) +result := addmod(mload(0x4900), result, f_q) +mstore(19744, result) + } +mstore(0x4d40, mulmod(mload(0x4880), mload(0x4840), f_q)) +{ + let result := mload(0x4940) +result := addmod(mload(0x4960), result, f_q) +mstore(19808, result) + } +mstore(0x4d80, mulmod(mload(0x4880), mload(0x49c0), f_q)) +{ + let result := mload(0x4980) +result := addmod(mload(0x49a0), result, f_q) +mstore(19872, result) + } +{ + let prod := mload(0x4ca0) + + prod := mulmod(mload(0x4ce0), prod, f_q) + mstore(0x4dc0, prod) + + prod := mulmod(mload(0x4d20), prod, f_q) + mstore(0x4de0, prod) + + prod := mulmod(mload(0x4d60), prod, f_q) + mstore(0x4e00, prod) + + prod := mulmod(mload(0x4da0), prod, f_q) + mstore(0x4e20, prod) + + } +mstore(0x4e60, 32) +mstore(0x4e80, 32) +mstore(0x4ea0, 32) +mstore(0x4ec0, mload(0x4e20)) +mstore(0x4ee0, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x4f00, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x4e60, 0xc0, 0x4e40, 0x20), 1), success) +{ + + let inv := mload(0x4e40) + let v + + v := mload(0x4da0) + mstore(19872, mulmod(mload(0x4e00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4d60) + mstore(19808, mulmod(mload(0x4de0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4d20) + mstore(19744, mulmod(mload(0x4dc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x4ce0) + mstore(19680, mulmod(mload(0x4ca0), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x4ca0, inv) + + } +mstore(0x4f20, mulmod(mload(0x4cc0), mload(0x4ce0), f_q)) +mstore(0x4f40, mulmod(mload(0x4d00), mload(0x4d20), f_q)) +mstore(0x4f60, mulmod(mload(0x4d40), mload(0x4d60), f_q)) +mstore(0x4f80, mulmod(mload(0x4d80), mload(0x4da0), f_q)) +mstore(0x4fa0, mulmod(mload(0x1060), mload(0x1060), f_q)) +mstore(0x4fc0, mulmod(mload(0x4fa0), mload(0x1060), f_q)) +mstore(0x4fe0, mulmod(mload(0x4fc0), mload(0x1060), f_q)) +mstore(0x5000, mulmod(mload(0x4fe0), mload(0x1060), f_q)) +mstore(0x5020, mulmod(mload(0x5000), mload(0x1060), f_q)) +mstore(0x5040, mulmod(mload(0x5020), mload(0x1060), f_q)) +mstore(0x5060, mulmod(mload(0x5040), mload(0x1060), f_q)) +mstore(0x5080, mulmod(mload(0x5060), mload(0x1060), f_q)) +mstore(0x50a0, mulmod(mload(0x5080), mload(0x1060), f_q)) +mstore(0x50c0, mulmod(mload(0x50a0), mload(0x1060), f_q)) +mstore(0x50e0, mulmod(mload(0x50c0), mload(0x1060), f_q)) +mstore(0x5100, mulmod(mload(0x50e0), mload(0x1060), f_q)) +mstore(0x5120, mulmod(mload(0x10c0), mload(0x10c0), f_q)) +mstore(0x5140, mulmod(mload(0x5120), mload(0x10c0), f_q)) +mstore(0x5160, mulmod(mload(0x5140), mload(0x10c0), f_q)) +mstore(0x5180, mulmod(mload(0x5160), mload(0x10c0), f_q)) +{ + let result := mulmod(mload(0xc40), mload(0x47a0), f_q) +result := addmod(mulmod(mload(0xc60), mload(0x47c0), f_q), result, f_q) +result := addmod(mulmod(mload(0xc80), mload(0x47e0), f_q), result, f_q) +result := addmod(mulmod(mload(0xca0), mload(0x4800), f_q), result, f_q) +mstore(20896, result) + } +mstore(0x51c0, mulmod(mload(0x51a0), mload(0x4ca0), f_q)) +mstore(0x51e0, mulmod(sub(f_q, mload(0x51c0)), 1, f_q)) +{ + let result := mulmod(mload(0xcc0), mload(0x47a0), f_q) +result := addmod(mulmod(mload(0xce0), mload(0x47c0), f_q), result, f_q) +result := addmod(mulmod(mload(0xd00), mload(0x47e0), f_q), result, f_q) +result := addmod(mulmod(mload(0xd20), mload(0x4800), f_q), result, f_q) +mstore(20992, result) + } +mstore(0x5220, mulmod(mload(0x5200), mload(0x4ca0), f_q)) +mstore(0x5240, mulmod(sub(f_q, mload(0x5220)), mload(0x1060), f_q)) +mstore(0x5260, mulmod(1, mload(0x1060), f_q)) +mstore(0x5280, addmod(mload(0x51e0), mload(0x5240), f_q)) +mstore(0x52a0, mulmod(mload(0x5280), 1, f_q)) +mstore(0x52c0, mulmod(mload(0x5260), 1, f_q)) +mstore(0x52e0, mulmod(1, mload(0x4cc0), f_q)) +{ + let result := mulmod(mload(0xd40), mload(0x48a0), f_q) +mstore(21248, result) + } +mstore(0x5320, mulmod(mload(0x5300), mload(0x4f20), f_q)) +mstore(0x5340, mulmod(sub(f_q, mload(0x5320)), 1, f_q)) +mstore(0x5360, mulmod(mload(0x52e0), 1, f_q)) +{ + let result := mulmod(mload(0x1020), mload(0x48a0), f_q) +mstore(21376, result) + } +mstore(0x53a0, mulmod(mload(0x5380), mload(0x4f20), f_q)) +mstore(0x53c0, mulmod(sub(f_q, mload(0x53a0)), mload(0x1060), f_q)) +mstore(0x53e0, mulmod(mload(0x52e0), mload(0x1060), f_q)) +mstore(0x5400, addmod(mload(0x5340), mload(0x53c0), f_q)) +{ + let result := mulmod(mload(0xd60), mload(0x48a0), f_q) +mstore(21536, result) + } +mstore(0x5440, mulmod(mload(0x5420), mload(0x4f20), f_q)) +mstore(0x5460, mulmod(sub(f_q, mload(0x5440)), mload(0x4fa0), f_q)) +mstore(0x5480, mulmod(mload(0x52e0), mload(0x4fa0), f_q)) +mstore(0x54a0, addmod(mload(0x5400), mload(0x5460), f_q)) +{ + let result := mulmod(mload(0xd80), mload(0x48a0), f_q) +mstore(21696, result) + } +mstore(0x54e0, mulmod(mload(0x54c0), mload(0x4f20), f_q)) +mstore(0x5500, mulmod(sub(f_q, mload(0x54e0)), mload(0x4fc0), f_q)) +mstore(0x5520, mulmod(mload(0x52e0), mload(0x4fc0), f_q)) +mstore(0x5540, addmod(mload(0x54a0), mload(0x5500), f_q)) +{ + let result := mulmod(mload(0xda0), mload(0x48a0), f_q) +mstore(21856, result) + } +mstore(0x5580, mulmod(mload(0x5560), mload(0x4f20), f_q)) +mstore(0x55a0, mulmod(sub(f_q, mload(0x5580)), mload(0x4fe0), f_q)) +mstore(0x55c0, mulmod(mload(0x52e0), mload(0x4fe0), f_q)) +mstore(0x55e0, addmod(mload(0x5540), mload(0x55a0), f_q)) +{ + let result := mulmod(mload(0xdc0), mload(0x48a0), f_q) +mstore(22016, result) + } +mstore(0x5620, mulmod(mload(0x5600), mload(0x4f20), f_q)) +mstore(0x5640, mulmod(sub(f_q, mload(0x5620)), mload(0x5000), f_q)) +mstore(0x5660, mulmod(mload(0x52e0), mload(0x5000), f_q)) +mstore(0x5680, addmod(mload(0x55e0), mload(0x5640), f_q)) +{ + let result := mulmod(mload(0xe00), mload(0x48a0), f_q) +mstore(22176, result) + } +mstore(0x56c0, mulmod(mload(0x56a0), mload(0x4f20), f_q)) +mstore(0x56e0, mulmod(sub(f_q, mload(0x56c0)), mload(0x5020), f_q)) +mstore(0x5700, mulmod(mload(0x52e0), mload(0x5020), f_q)) +mstore(0x5720, addmod(mload(0x5680), mload(0x56e0), f_q)) +{ + let result := mulmod(mload(0xe20), mload(0x48a0), f_q) +mstore(22336, result) + } +mstore(0x5760, mulmod(mload(0x5740), mload(0x4f20), f_q)) +mstore(0x5780, mulmod(sub(f_q, mload(0x5760)), mload(0x5040), f_q)) +mstore(0x57a0, mulmod(mload(0x52e0), mload(0x5040), f_q)) +mstore(0x57c0, addmod(mload(0x5720), mload(0x5780), f_q)) +{ + let result := mulmod(mload(0xe40), mload(0x48a0), f_q) +mstore(22496, result) + } +mstore(0x5800, mulmod(mload(0x57e0), mload(0x4f20), f_q)) +mstore(0x5820, mulmod(sub(f_q, mload(0x5800)), mload(0x5060), f_q)) +mstore(0x5840, mulmod(mload(0x52e0), mload(0x5060), f_q)) +mstore(0x5860, addmod(mload(0x57c0), mload(0x5820), f_q)) +{ + let result := mulmod(mload(0xe60), mload(0x48a0), f_q) +mstore(22656, result) + } +mstore(0x58a0, mulmod(mload(0x5880), mload(0x4f20), f_q)) +mstore(0x58c0, mulmod(sub(f_q, mload(0x58a0)), mload(0x5080), f_q)) +mstore(0x58e0, mulmod(mload(0x52e0), mload(0x5080), f_q)) +mstore(0x5900, addmod(mload(0x5860), mload(0x58c0), f_q)) +{ + let result := mulmod(mload(0xe80), mload(0x48a0), f_q) +mstore(22816, result) + } +mstore(0x5940, mulmod(mload(0x5920), mload(0x4f20), f_q)) +mstore(0x5960, mulmod(sub(f_q, mload(0x5940)), mload(0x50a0), f_q)) +mstore(0x5980, mulmod(mload(0x52e0), mload(0x50a0), f_q)) +mstore(0x59a0, addmod(mload(0x5900), mload(0x5960), f_q)) +mstore(0x59c0, mulmod(mload(0x4580), mload(0x4cc0), f_q)) +mstore(0x59e0, mulmod(mload(0x45a0), mload(0x4cc0), f_q)) +{ + let result := mulmod(mload(0x45c0), mload(0x48a0), f_q) +mstore(23040, result) + } +mstore(0x5a20, mulmod(mload(0x5a00), mload(0x4f20), f_q)) +mstore(0x5a40, mulmod(sub(f_q, mload(0x5a20)), mload(0x50c0), f_q)) +mstore(0x5a60, mulmod(mload(0x52e0), mload(0x50c0), f_q)) +mstore(0x5a80, mulmod(mload(0x59c0), mload(0x50c0), f_q)) +mstore(0x5aa0, mulmod(mload(0x59e0), mload(0x50c0), f_q)) +mstore(0x5ac0, addmod(mload(0x59a0), mload(0x5a40), f_q)) +{ + let result := mulmod(mload(0xde0), mload(0x48a0), f_q) +mstore(23264, result) + } +mstore(0x5b00, mulmod(mload(0x5ae0), mload(0x4f20), f_q)) +mstore(0x5b20, mulmod(sub(f_q, mload(0x5b00)), mload(0x50e0), f_q)) +mstore(0x5b40, mulmod(mload(0x52e0), mload(0x50e0), f_q)) +mstore(0x5b60, addmod(mload(0x5ac0), mload(0x5b20), f_q)) +mstore(0x5b80, mulmod(mload(0x5b60), mload(0x10c0), f_q)) +mstore(0x5ba0, mulmod(mload(0x5360), mload(0x10c0), f_q)) +mstore(0x5bc0, mulmod(mload(0x53e0), mload(0x10c0), f_q)) +mstore(0x5be0, mulmod(mload(0x5480), mload(0x10c0), f_q)) +mstore(0x5c00, mulmod(mload(0x5520), mload(0x10c0), f_q)) +mstore(0x5c20, mulmod(mload(0x55c0), mload(0x10c0), f_q)) +mstore(0x5c40, mulmod(mload(0x5660), mload(0x10c0), f_q)) +mstore(0x5c60, mulmod(mload(0x5700), mload(0x10c0), f_q)) +mstore(0x5c80, mulmod(mload(0x57a0), mload(0x10c0), f_q)) +mstore(0x5ca0, mulmod(mload(0x5840), mload(0x10c0), f_q)) +mstore(0x5cc0, mulmod(mload(0x58e0), mload(0x10c0), f_q)) +mstore(0x5ce0, mulmod(mload(0x5980), mload(0x10c0), f_q)) +mstore(0x5d00, mulmod(mload(0x5a60), mload(0x10c0), f_q)) +mstore(0x5d20, mulmod(mload(0x5a80), mload(0x10c0), f_q)) +mstore(0x5d40, mulmod(mload(0x5aa0), mload(0x10c0), f_q)) +mstore(0x5d60, mulmod(mload(0x5b40), mload(0x10c0), f_q)) +mstore(0x5d80, addmod(mload(0x52a0), mload(0x5b80), f_q)) +mstore(0x5da0, mulmod(1, mload(0x4d00), f_q)) +{ + let result := mulmod(mload(0xea0), mload(0x48c0), f_q) +result := addmod(mulmod(mload(0xec0), mload(0x48e0), f_q), result, f_q) +result := addmod(mulmod(mload(0xee0), mload(0x4900), f_q), result, f_q) +mstore(24000, result) + } +mstore(0x5de0, mulmod(mload(0x5dc0), mload(0x4f40), f_q)) +mstore(0x5e00, mulmod(sub(f_q, mload(0x5de0)), 1, f_q)) +mstore(0x5e20, mulmod(mload(0x5da0), 1, f_q)) +{ + let result := mulmod(mload(0xf00), mload(0x48c0), f_q) +result := addmod(mulmod(mload(0xf20), mload(0x48e0), f_q), result, f_q) +result := addmod(mulmod(mload(0xf40), mload(0x4900), f_q), result, f_q) +mstore(24128, result) + } +mstore(0x5e60, mulmod(mload(0x5e40), mload(0x4f40), f_q)) +mstore(0x5e80, mulmod(sub(f_q, mload(0x5e60)), mload(0x1060), f_q)) +mstore(0x5ea0, mulmod(mload(0x5da0), mload(0x1060), f_q)) +mstore(0x5ec0, addmod(mload(0x5e00), mload(0x5e80), f_q)) +mstore(0x5ee0, mulmod(mload(0x5ec0), mload(0x5120), f_q)) +mstore(0x5f00, mulmod(mload(0x5e20), mload(0x5120), f_q)) +mstore(0x5f20, mulmod(mload(0x5ea0), mload(0x5120), f_q)) +mstore(0x5f40, addmod(mload(0x5d80), mload(0x5ee0), f_q)) +mstore(0x5f60, mulmod(1, mload(0x4d40), f_q)) +{ + let result := mulmod(mload(0xf60), mload(0x4940), f_q) +result := addmod(mulmod(mload(0xf80), mload(0x4960), f_q), result, f_q) +mstore(24448, result) + } +mstore(0x5fa0, mulmod(mload(0x5f80), mload(0x4f60), f_q)) +mstore(0x5fc0, mulmod(sub(f_q, mload(0x5fa0)), 1, f_q)) +mstore(0x5fe0, mulmod(mload(0x5f60), 1, f_q)) +{ + let result := mulmod(mload(0xfa0), mload(0x4940), f_q) +result := addmod(mulmod(mload(0xfc0), mload(0x4960), f_q), result, f_q) +mstore(24576, result) + } +mstore(0x6020, mulmod(mload(0x6000), mload(0x4f60), f_q)) +mstore(0x6040, mulmod(sub(f_q, mload(0x6020)), mload(0x1060), f_q)) +mstore(0x6060, mulmod(mload(0x5f60), mload(0x1060), f_q)) +mstore(0x6080, addmod(mload(0x5fc0), mload(0x6040), f_q)) +mstore(0x60a0, mulmod(mload(0x6080), mload(0x5140), f_q)) +mstore(0x60c0, mulmod(mload(0x5fe0), mload(0x5140), f_q)) +mstore(0x60e0, mulmod(mload(0x6060), mload(0x5140), f_q)) +mstore(0x6100, addmod(mload(0x5f40), mload(0x60a0), f_q)) +mstore(0x6120, mulmod(1, mload(0x4d80), f_q)) +{ + let result := mulmod(mload(0xfe0), mload(0x4980), f_q) +result := addmod(mulmod(mload(0x1000), mload(0x49a0), f_q), result, f_q) +mstore(24896, result) + } +mstore(0x6160, mulmod(mload(0x6140), mload(0x4f80), f_q)) +mstore(0x6180, mulmod(sub(f_q, mload(0x6160)), 1, f_q)) +mstore(0x61a0, mulmod(mload(0x6120), 1, f_q)) +mstore(0x61c0, mulmod(mload(0x6180), mload(0x5160), f_q)) +mstore(0x61e0, mulmod(mload(0x61a0), mload(0x5160), f_q)) +mstore(0x6200, addmod(mload(0x6100), mload(0x61c0), f_q)) +mstore(0x6220, mulmod(1, mload(0x4880), f_q)) +mstore(0x6240, mulmod(1, mload(0x1160), f_q)) +mstore(0x6260, 0x0000000000000000000000000000000000000000000000000000000000000001) + mstore(0x6280, 0x0000000000000000000000000000000000000000000000000000000000000002) +mstore(0x62a0, mload(0x6200)) +success := and(eq(staticcall(gas(), 0x7, 0x6260, 0x60, 0x6260, 0x40), 1), success) +mstore(0x62c0, mload(0x6260)) + mstore(0x62e0, mload(0x6280)) +mstore(0x6300, mload(0x720)) + mstore(0x6320, mload(0x740)) +success := and(eq(staticcall(gas(), 0x6, 0x62c0, 0x80, 0x62c0, 0x40), 1), success) +mstore(0x6340, mload(0x760)) + mstore(0x6360, mload(0x780)) +mstore(0x6380, mload(0x52c0)) +success := and(eq(staticcall(gas(), 0x7, 0x6340, 0x60, 0x6340, 0x40), 1), success) +mstore(0x63a0, mload(0x62c0)) + mstore(0x63c0, mload(0x62e0)) +mstore(0x63e0, mload(0x6340)) + mstore(0x6400, mload(0x6360)) +success := and(eq(staticcall(gas(), 0x6, 0x63a0, 0x80, 0x63a0, 0x40), 1), success) +mstore(0x6420, mload(0x7a0)) + mstore(0x6440, mload(0x7c0)) +mstore(0x6460, mload(0x5ba0)) +success := and(eq(staticcall(gas(), 0x7, 0x6420, 0x60, 0x6420, 0x40), 1), success) +mstore(0x6480, mload(0x63a0)) + mstore(0x64a0, mload(0x63c0)) +mstore(0x64c0, mload(0x6420)) + mstore(0x64e0, mload(0x6440)) +success := and(eq(staticcall(gas(), 0x6, 0x6480, 0x80, 0x6480, 0x40), 1), success) +mstore(0x6500, mload(0x880)) + mstore(0x6520, mload(0x8a0)) +mstore(0x6540, mload(0x5bc0)) +success := and(eq(staticcall(gas(), 0x7, 0x6500, 0x60, 0x6500, 0x40), 1), success) +mstore(0x6560, mload(0x6480)) + mstore(0x6580, mload(0x64a0)) +mstore(0x65a0, mload(0x6500)) + mstore(0x65c0, mload(0x6520)) +success := and(eq(staticcall(gas(), 0x6, 0x6560, 0x80, 0x6560, 0x40), 1), success) +mstore(0x65e0, 0x052fef9c6915b7fb3db3f21b29b01aeb9ee6532165bf6fcbd235f7be282d74ed) + mstore(0x6600, 0x2d6f4fffe3096d9bf5e63b02a1d25e23d53fa855763d0cb68f5cf9f95a133868) +mstore(0x6620, mload(0x5be0)) +success := and(eq(staticcall(gas(), 0x7, 0x65e0, 0x60, 0x65e0, 0x40), 1), success) +mstore(0x6640, mload(0x6560)) + mstore(0x6660, mload(0x6580)) +mstore(0x6680, mload(0x65e0)) + mstore(0x66a0, mload(0x6600)) +success := and(eq(staticcall(gas(), 0x6, 0x6640, 0x80, 0x6640, 0x40), 1), success) +mstore(0x66c0, 0x18ab8615a39222924f8bfe2bf3b88612fb692de0efa6e24f90c2ddc05661b634) + mstore(0x66e0, 0x2b7033475410a3db82d02c345da2b6f1246c3cf96ba28ac7d657a1ca08a68262) +mstore(0x6700, mload(0x5c00)) +success := and(eq(staticcall(gas(), 0x7, 0x66c0, 0x60, 0x66c0, 0x40), 1), success) +mstore(0x6720, mload(0x6640)) + mstore(0x6740, mload(0x6660)) +mstore(0x6760, mload(0x66c0)) + mstore(0x6780, mload(0x66e0)) +success := and(eq(staticcall(gas(), 0x6, 0x6720, 0x80, 0x6720, 0x40), 1), success) +mstore(0x67a0, 0x259b90135dc570023ee29da41add0c2e2911e5fd5e7847293c43286256d85c82) + mstore(0x67c0, 0x233538fa1b5e4d2135c173543cd83005995bb2b638f7784a9faebfe1aa002297) +mstore(0x67e0, mload(0x5c20)) +success := and(eq(staticcall(gas(), 0x7, 0x67a0, 0x60, 0x67a0, 0x40), 1), success) +mstore(0x6800, mload(0x6720)) + mstore(0x6820, mload(0x6740)) +mstore(0x6840, mload(0x67a0)) + mstore(0x6860, mload(0x67c0)) +success := and(eq(staticcall(gas(), 0x6, 0x6800, 0x80, 0x6800, 0x40), 1), success) +mstore(0x6880, 0x25cc6cabf373c5cc3cd17f2058ab5f36ba95744bd0a0a83fa6009a3c90bb503f) + mstore(0x68a0, 0x1ff986851274b80031fa7d31fa9fba16a5004556e8598dab4b49c0c5dcdf6aef) +mstore(0x68c0, mload(0x5c40)) +success := and(eq(staticcall(gas(), 0x7, 0x6880, 0x60, 0x6880, 0x40), 1), success) +mstore(0x68e0, mload(0x6800)) + mstore(0x6900, mload(0x6820)) +mstore(0x6920, mload(0x6880)) + mstore(0x6940, mload(0x68a0)) +success := and(eq(staticcall(gas(), 0x6, 0x68e0, 0x80, 0x68e0, 0x40), 1), success) +mstore(0x6960, 0x05c1c4db8c04629723402ae5f05825228810a792dd3fea746683b0d0104ec490) + mstore(0x6980, 0x2b7fe37ad98c0fa894e68e047c33e33327d6c3aa2b9c1c0911f4513d281bebfa) +mstore(0x69a0, mload(0x5c60)) +success := and(eq(staticcall(gas(), 0x7, 0x6960, 0x60, 0x6960, 0x40), 1), success) +mstore(0x69c0, mload(0x68e0)) + mstore(0x69e0, mload(0x6900)) +mstore(0x6a00, mload(0x6960)) + mstore(0x6a20, mload(0x6980)) +success := and(eq(staticcall(gas(), 0x6, 0x69c0, 0x80, 0x69c0, 0x40), 1), success) +mstore(0x6a40, 0x0dfdf63a8514b1155d732148b7f0ff487a5bd48fafc3203597d55c8f608fc615) + mstore(0x6a60, 0x05f8827b24a83d5ab29f2189037355a22fef5fb2a246f9783dfaff1691dca957) +mstore(0x6a80, mload(0x5c80)) +success := and(eq(staticcall(gas(), 0x7, 0x6a40, 0x60, 0x6a40, 0x40), 1), success) +mstore(0x6aa0, mload(0x69c0)) + mstore(0x6ac0, mload(0x69e0)) +mstore(0x6ae0, mload(0x6a40)) + mstore(0x6b00, mload(0x6a60)) +success := and(eq(staticcall(gas(), 0x6, 0x6aa0, 0x80, 0x6aa0, 0x40), 1), success) +mstore(0x6b20, 0x1fac4291d7aa01f5a48c51802981a94695af4ba420f4ae84b09a9e93f43d78a1) + mstore(0x6b40, 0x27fc59f3644ad31b19991dd181eff24eb0c777ac5ca29dd97e408483c20f3a64) +mstore(0x6b60, mload(0x5ca0)) +success := and(eq(staticcall(gas(), 0x7, 0x6b20, 0x60, 0x6b20, 0x40), 1), success) +mstore(0x6b80, mload(0x6aa0)) + mstore(0x6ba0, mload(0x6ac0)) +mstore(0x6bc0, mload(0x6b20)) + mstore(0x6be0, mload(0x6b40)) +success := and(eq(staticcall(gas(), 0x6, 0x6b80, 0x80, 0x6b80, 0x40), 1), success) +mstore(0x6c00, 0x2ef56aa248b578d21a0bc79c35bfa32fc9393563715aaed6c38371f5081c66fc) + mstore(0x6c20, 0x15a7b64da6f594573f55bc7f733510aba338cbacd75152f82452460fc5e475ba) +mstore(0x6c40, mload(0x5cc0)) +success := and(eq(staticcall(gas(), 0x7, 0x6c00, 0x60, 0x6c00, 0x40), 1), success) +mstore(0x6c60, mload(0x6b80)) + mstore(0x6c80, mload(0x6ba0)) +mstore(0x6ca0, mload(0x6c00)) + mstore(0x6cc0, mload(0x6c20)) +success := and(eq(staticcall(gas(), 0x6, 0x6c60, 0x80, 0x6c60, 0x40), 1), success) +mstore(0x6ce0, 0x171d03d85f31d499df4757cc276cd9941a36d4b7841672b9199bb70c9b434143) + mstore(0x6d00, 0x136d1d20f5b04044a94d1c3a6f6c77e296af0cac540bc9b4ae20f951a7b9bfa1) +mstore(0x6d20, mload(0x5ce0)) +success := and(eq(staticcall(gas(), 0x7, 0x6ce0, 0x60, 0x6ce0, 0x40), 1), success) +mstore(0x6d40, mload(0x6c60)) + mstore(0x6d60, mload(0x6c80)) +mstore(0x6d80, mload(0x6ce0)) + mstore(0x6da0, mload(0x6d00)) +success := and(eq(staticcall(gas(), 0x6, 0x6d40, 0x80, 0x6d40, 0x40), 1), success) +mstore(0x6dc0, mload(0xb20)) + mstore(0x6de0, mload(0xb40)) +mstore(0x6e00, mload(0x5d00)) +success := and(eq(staticcall(gas(), 0x7, 0x6dc0, 0x60, 0x6dc0, 0x40), 1), success) +mstore(0x6e20, mload(0x6d40)) + mstore(0x6e40, mload(0x6d60)) +mstore(0x6e60, mload(0x6dc0)) + mstore(0x6e80, mload(0x6de0)) +success := and(eq(staticcall(gas(), 0x6, 0x6e20, 0x80, 0x6e20, 0x40), 1), success) +mstore(0x6ea0, mload(0xb60)) + mstore(0x6ec0, mload(0xb80)) +mstore(0x6ee0, mload(0x5d20)) +success := and(eq(staticcall(gas(), 0x7, 0x6ea0, 0x60, 0x6ea0, 0x40), 1), success) +mstore(0x6f00, mload(0x6e20)) + mstore(0x6f20, mload(0x6e40)) +mstore(0x6f40, mload(0x6ea0)) + mstore(0x6f60, mload(0x6ec0)) +success := and(eq(staticcall(gas(), 0x6, 0x6f00, 0x80, 0x6f00, 0x40), 1), success) +mstore(0x6f80, mload(0xba0)) + mstore(0x6fa0, mload(0xbc0)) +mstore(0x6fc0, mload(0x5d40)) +success := and(eq(staticcall(gas(), 0x7, 0x6f80, 0x60, 0x6f80, 0x40), 1), success) +mstore(0x6fe0, mload(0x6f00)) + mstore(0x7000, mload(0x6f20)) +mstore(0x7020, mload(0x6f80)) + mstore(0x7040, mload(0x6fa0)) +success := and(eq(staticcall(gas(), 0x6, 0x6fe0, 0x80, 0x6fe0, 0x40), 1), success) +mstore(0x7060, mload(0xa80)) + mstore(0x7080, mload(0xaa0)) +mstore(0x70a0, mload(0x5d60)) +success := and(eq(staticcall(gas(), 0x7, 0x7060, 0x60, 0x7060, 0x40), 1), success) +mstore(0x70c0, mload(0x6fe0)) + mstore(0x70e0, mload(0x7000)) +mstore(0x7100, mload(0x7060)) + mstore(0x7120, mload(0x7080)) +success := and(eq(staticcall(gas(), 0x6, 0x70c0, 0x80, 0x70c0, 0x40), 1), success) +mstore(0x7140, mload(0x980)) + mstore(0x7160, mload(0x9a0)) +mstore(0x7180, mload(0x5f00)) +success := and(eq(staticcall(gas(), 0x7, 0x7140, 0x60, 0x7140, 0x40), 1), success) +mstore(0x71a0, mload(0x70c0)) + mstore(0x71c0, mload(0x70e0)) +mstore(0x71e0, mload(0x7140)) + mstore(0x7200, mload(0x7160)) +success := and(eq(staticcall(gas(), 0x6, 0x71a0, 0x80, 0x71a0, 0x40), 1), success) +mstore(0x7220, mload(0x9c0)) + mstore(0x7240, mload(0x9e0)) +mstore(0x7260, mload(0x5f20)) +success := and(eq(staticcall(gas(), 0x7, 0x7220, 0x60, 0x7220, 0x40), 1), success) +mstore(0x7280, mload(0x71a0)) + mstore(0x72a0, mload(0x71c0)) +mstore(0x72c0, mload(0x7220)) + mstore(0x72e0, mload(0x7240)) +success := and(eq(staticcall(gas(), 0x6, 0x7280, 0x80, 0x7280, 0x40), 1), success) +mstore(0x7300, mload(0xa00)) + mstore(0x7320, mload(0xa20)) +mstore(0x7340, mload(0x60c0)) +success := and(eq(staticcall(gas(), 0x7, 0x7300, 0x60, 0x7300, 0x40), 1), success) +mstore(0x7360, mload(0x7280)) + mstore(0x7380, mload(0x72a0)) +mstore(0x73a0, mload(0x7300)) + mstore(0x73c0, mload(0x7320)) +success := and(eq(staticcall(gas(), 0x6, 0x7360, 0x80, 0x7360, 0x40), 1), success) +mstore(0x73e0, mload(0xa40)) + mstore(0x7400, mload(0xa60)) +mstore(0x7420, mload(0x60e0)) +success := and(eq(staticcall(gas(), 0x7, 0x73e0, 0x60, 0x73e0, 0x40), 1), success) +mstore(0x7440, mload(0x7360)) + mstore(0x7460, mload(0x7380)) +mstore(0x7480, mload(0x73e0)) + mstore(0x74a0, mload(0x7400)) +success := and(eq(staticcall(gas(), 0x6, 0x7440, 0x80, 0x7440, 0x40), 1), success) +mstore(0x74c0, mload(0x840)) + mstore(0x74e0, mload(0x860)) +mstore(0x7500, mload(0x61e0)) +success := and(eq(staticcall(gas(), 0x7, 0x74c0, 0x60, 0x74c0, 0x40), 1), success) +mstore(0x7520, mload(0x7440)) + mstore(0x7540, mload(0x7460)) +mstore(0x7560, mload(0x74c0)) + mstore(0x7580, mload(0x74e0)) +success := and(eq(staticcall(gas(), 0x6, 0x7520, 0x80, 0x7520, 0x40), 1), success) +mstore(0x75a0, mload(0x1100)) + mstore(0x75c0, mload(0x1120)) +mstore(0x75e0, sub(f_q, mload(0x6220))) +success := and(eq(staticcall(gas(), 0x7, 0x75a0, 0x60, 0x75a0, 0x40), 1), success) +mstore(0x7600, mload(0x7520)) + mstore(0x7620, mload(0x7540)) +mstore(0x7640, mload(0x75a0)) + mstore(0x7660, mload(0x75c0)) +success := and(eq(staticcall(gas(), 0x6, 0x7600, 0x80, 0x7600, 0x40), 1), success) +mstore(0x7680, mload(0x11a0)) + mstore(0x76a0, mload(0x11c0)) +mstore(0x76c0, mload(0x6240)) +success := and(eq(staticcall(gas(), 0x7, 0x7680, 0x60, 0x7680, 0x40), 1), success) +mstore(0x76e0, mload(0x7600)) + mstore(0x7700, mload(0x7620)) +mstore(0x7720, mload(0x7680)) + mstore(0x7740, mload(0x76a0)) +success := and(eq(staticcall(gas(), 0x6, 0x76e0, 0x80, 0x76e0, 0x40), 1), success) +mstore(0x7760, mload(0x76e0)) + mstore(0x7780, mload(0x7700)) +mstore(0x77a0, mload(0x11a0)) + mstore(0x77c0, mload(0x11c0)) +mstore(0x77e0, mload(0x11e0)) + mstore(0x7800, mload(0x1200)) +mstore(0x7820, mload(0x1220)) + mstore(0x7840, mload(0x1240)) +mstore(0x7860, keccak256(0x7760, 256)) +mstore(30848, mod(mload(30816), f_q)) +mstore(0x78a0, mulmod(mload(0x7880), mload(0x7880), f_q)) +mstore(0x78c0, mulmod(1, mload(0x7880), f_q)) +mstore(0x78e0, mload(0x77e0)) + mstore(0x7900, mload(0x7800)) +mstore(0x7920, mload(0x78c0)) +success := and(eq(staticcall(gas(), 0x7, 0x78e0, 0x60, 0x78e0, 0x40), 1), success) +mstore(0x7940, mload(0x7760)) + mstore(0x7960, mload(0x7780)) +mstore(0x7980, mload(0x78e0)) + mstore(0x79a0, mload(0x7900)) +success := and(eq(staticcall(gas(), 0x6, 0x7940, 0x80, 0x7940, 0x40), 1), success) +mstore(0x79c0, mload(0x7820)) + mstore(0x79e0, mload(0x7840)) +mstore(0x7a00, mload(0x78c0)) +success := and(eq(staticcall(gas(), 0x7, 0x79c0, 0x60, 0x79c0, 0x40), 1), success) +mstore(0x7a20, mload(0x77a0)) + mstore(0x7a40, mload(0x77c0)) +mstore(0x7a60, mload(0x79c0)) + mstore(0x7a80, mload(0x79e0)) +success := and(eq(staticcall(gas(), 0x6, 0x7a20, 0x80, 0x7a20, 0x40), 1), success) +mstore(0x7aa0, mload(0x7940)) + mstore(0x7ac0, mload(0x7960)) +mstore(0x7ae0, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) + mstore(0x7b00, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) + mstore(0x7b20, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) + mstore(0x7b40, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) +mstore(0x7b60, mload(0x7a20)) + mstore(0x7b80, mload(0x7a40)) +mstore(0x7ba0, 0x186282957db913abd99f91db59fe69922e95040603ef44c0bd7aa3adeef8f5ac) + mstore(0x7bc0, 0x17944351223333f260ddc3b4af45191b856689eda9eab5cbcddbbe570ce860d2) + mstore(0x7be0, 0x06d971ff4a7467c3ec596ed6efc674572e32fd6f52b721f97e35b0b3d3546753) + mstore(0x7c00, 0x06ecdb9f9567f59ed2eee36e1e1d58797fd13cc97fafc2910f5e8a12f202fa9a) +success := and(eq(staticcall(gas(), 0x8, 0x7aa0, 0x180, 0x7aa0, 0x20), 1), success) +success := and(eq(mload(0x7aa0), 1), success) + + if not(success) { revert(0, 0) } + return(0, 0) + + } + } + } \ No newline at end of file diff --git a/axiom-eth/data/storage/task.t.json b/axiom-eth/data/storage/task.t.json new file mode 100644 index 00000000..41484098 --- /dev/null +++ b/axiom-eth/data/storage/task.t.json @@ -0,0 +1,16 @@ +{ + "block_number": 16329190, + "address": "0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB", + "slots": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] +} diff --git a/axiom-eth/data/transaction/task.t.json b/axiom-eth/data/transaction/task.t.json new file mode 100644 index 00000000..3f680c91 --- /dev/null +++ b/axiom-eth/data/transaction/task.t.json @@ -0,0 +1,91 @@ +{ + "block_numbers": [ + 12985438, + 12985438 + ], + "queries": [ + [ + 0, + 4 + ], + [ + 0, + 4 + ] + ], + "mmr": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xe81cc62bb288e100856ea7d40af72b844e9dcb9ff8ebed659a475e2635cd4e18", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xb169c87af2d231bc71f910481d6d8315a6fc4edfab212ee003d206b9643339c0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x43062e083f5f40510f30723d9cebb51c4ae67c35d86c5b791a043bae317350e3", + "0x6cddc980f4c3b403d99080c32b3f0a6205e39560b9021d5f233c04d96c23381e", + "0x6a42052cabd8d66a584b8823c6aadf64dd2755321210c66a2b7acd1da5bdeacf", + "0xebf08ca711cbab09109bb51277c545ee43073d8fa8b46c0cbbedd46ce85e73be", + "0x477c055e69de14e3bbfe2af0389e6c3ac28ffb5b0cc8fa26b543ac47857fd646", + "0xf47e6584af17667881494720b50dd063d0900b288438df7a37e2e91440aedd23" + ], + "mmr_list_len": 16525312, + "mmr_proofs": [ + [ + "0xebd5fc0be32c2298e2ee18dac2db5b1508a64741ba7186dd59229ec0620d9d64", + "0x9139f12e0f47241172143fc5d0b0245b5ffbcdf9130da9efb14c155f6036697e", + "0x97f5d40bc9a10e06b5eef7609a3e364f30ab10675d22fbc3304179a381b39b18", + "0xc8c07e6c877f0cd60903d376c1aa911f47c96d3967d989101ed8b873cf6e38de", + "0x96cf53edbe3be783378433c518939a7e0b4e657edb6558b1f6e14edc0a125a18", + "0xfa3448a664e9406ffdc3b53e24f06fcf6b576621f854e421385bd1603ea257ee", + "0x9dffc8cb737d72da73df5e333bb7716cfec51e4b761281c6c7ff4db55689911c", + "0xef3fb7b7274810ec5bc63e7c248ea7dfe26d95abcd8bcb8d97b1f5fb617b8dc8", + "0x6a4d92e38592f280afc95efe5dd178a37c155bfad49759db7a066d597bc804d3", + "0x7db79de6d79e2ff264f4d171243f5038b575b380d31b052dda979e28fae7fc08", + "0x3106ece6d5a3c317f17c9313e7d0a3cd73649662301f50fdcedc67254b3fe153", + "0x902c8cf11e8d5cf14137e632061a52574515a2254fbd3b70cfc85a45f9dbcb4a", + "0xc48c7fe69133ac6f0c2200e600a3c15fe1832577156bc8851a7538403eafadfa", + "0x4434e3730dbe222cb8b98703748da1f07f05564c64ea66fe4765484ea982f5d6", + "0x69d2bc461de5dba21f741bf757d60ec8a92c3f29e417cb99fa76459bc3e86278", + "0xe18396e487f6c0bcd73a2d4c4c8c3583be7edefe59f20b2ce67c7f363b8a856a", + "0xa10b0dd9e041c793d0dbdf615bee9e18c3f6e3b191469bbb8cc9912d5d228050", + "0xa51d50eb9feaaf85b7ddacb99f71886135f1c4f59de3e788a5e29a485d5fdce5", + "0xa46b70512bfe0b85498e28ae8187cfadff9e58680b84ddcde450cd880ea489b1", + "0x33552dfc75e340bca3c698e4fb486ae540d07cf2a845465575cff24d866a161a", + "0x0fec590ac8394abe8477b828bf31b470d95772b3f331ff5be34ba0a899975a17" + ], + [ + "0xebd5fc0be32c2298e2ee18dac2db5b1508a64741ba7186dd59229ec0620d9d64", + "0x9139f12e0f47241172143fc5d0b0245b5ffbcdf9130da9efb14c155f6036697e", + "0x97f5d40bc9a10e06b5eef7609a3e364f30ab10675d22fbc3304179a381b39b18", + "0xc8c07e6c877f0cd60903d376c1aa911f47c96d3967d989101ed8b873cf6e38de", + "0x96cf53edbe3be783378433c518939a7e0b4e657edb6558b1f6e14edc0a125a18", + "0xfa3448a664e9406ffdc3b53e24f06fcf6b576621f854e421385bd1603ea257ee", + "0x9dffc8cb737d72da73df5e333bb7716cfec51e4b761281c6c7ff4db55689911c", + "0xef3fb7b7274810ec5bc63e7c248ea7dfe26d95abcd8bcb8d97b1f5fb617b8dc8", + "0x6a4d92e38592f280afc95efe5dd178a37c155bfad49759db7a066d597bc804d3", + "0x7db79de6d79e2ff264f4d171243f5038b575b380d31b052dda979e28fae7fc08", + "0x3106ece6d5a3c317f17c9313e7d0a3cd73649662301f50fdcedc67254b3fe153", + "0x902c8cf11e8d5cf14137e632061a52574515a2254fbd3b70cfc85a45f9dbcb4a", + "0xc48c7fe69133ac6f0c2200e600a3c15fe1832577156bc8851a7538403eafadfa", + "0x4434e3730dbe222cb8b98703748da1f07f05564c64ea66fe4765484ea982f5d6", + "0x69d2bc461de5dba21f741bf757d60ec8a92c3f29e417cb99fa76459bc3e86278", + "0xe18396e487f6c0bcd73a2d4c4c8c3583be7edefe59f20b2ce67c7f363b8a856a", + "0xa10b0dd9e041c793d0dbdf615bee9e18c3f6e3b191469bbb8cc9912d5d228050", + "0xa51d50eb9feaaf85b7ddacb99f71886135f1c4f59de3e788a5e29a485d5fdce5", + "0xa46b70512bfe0b85498e28ae8187cfadff9e58680b84ddcde450cd880ea489b1", + "0x33552dfc75e340bca3c698e4fb486ae540d07cf2a845465575cff24d866a161a", + "0x0fec590ac8394abe8477b828bf31b470d95772b3f331ff5be34ba0a899975a17" + ] + ] +} \ No newline at end of file diff --git a/axiom-eth/data/transaction/test.calldata b/axiom-eth/data/transaction/test.calldata new file mode 100644 index 00000000..52076d60 --- /dev/null +++ b/axiom-eth/data/transaction/test.calldata @@ -0,0 +1 @@ +0000000000000000000000000000000000000000009d5234faa89b20e42d1f79000000000000000000000000000000000000000000bcb37f8f18d637aebf54520000000000000000000000000000000000000000000005602d08e5a76809da5f00000000000000000000000000000000000000000045ac04dcd79d05c1af4b4e0000000000000000000000000000000000000000000711e31df1da81f0f2197f0000000000000000000000000000000000000000000025d3b755157d6bdaaf6a000000000000000000000000000000000000000000ea8b002f12a954f8c080160000000000000000000000000000000000000000008d071ea1319fc5cd45085e0000000000000000000000000000000000000000000027d125c37b20a0aec4d3000000000000000000000000000000000000000000b18bed65f22252af0ec2e0000000000000000000000000000000000000000000b36386fa863f15e186f84e000000000000000000000000000000000000000000000beb0f0846e511200ad300000000000000000000000000000000eab72f21616dd49262dbf7b9bd110b0e0000000000000000000000000000000072c91fbc6ca206fce206d3dc6158c5f100000000000000000000000000000000000000000000000000000000004c4b720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000012b9d6c09782c7d8da32ea1bddcca65257afec13cf392483d251db9fac5b25b8a21127c3537e4154c09fcd5ca4479ce7bdb2a58a619ba8c2bb69088416a2784ba041eb4569381a820cac1cdad800a7abe62660730a8b951dd6c7922f97562c87e01485103ba7ff9529f6e8b1acd7aa56522e97e0b1a6e0350b0cdb007ea4b7a241db1ce03f4f2a0adba66d8d779fe581867ec097787e70a260ac6816d8850d9cb139896f1a482870f92be1dbd96318830a00ddb9389d1187ddebcff4d061a4ce2011a17ec168f805ff9b5f4d0ce36dad7f246f06d7cfdaf7372b7407dbef3e9c1206d0deae76a22e140e017d98dbf78b4b9c6b7e404812bee22feb262a9fc77c72be50914282e17fd0c38c5c3bd9335f759eb1628bcadbdf5c4cfe8792bb028b108c524291c636a25d67d752f04031e34e915e1d0f9e604ae9a9bf8f2fe6799441ee75c2e84788aed824d6d34ed99e7da92778ebdbcfa8095fa263b0ca51c56f2173196dff6ecd677f8477e2d21a61f0e7f3eec4a7ce6bc32a1fa035980c6433f2a0953670d486d653b511516101823fdf950691e87db43b3beb6b3b49454b2b813a8e278017fe76e4c3b8e87fa002c764e392687d0e3d289b3a2b69b4c032c2f09a504e3142372525498ef94788f9efaad66bfecf54d4c59ae29a034b870f04f0e04f1c6cc86bb95712ffe6442fdb5f80f3c9687103055ac94edbc8f203a59d90e8d34c5160e6e0ac180ef4acf1aa880c66f685c0964354ccdd8b8172fa7ab25218208605fe206588d31875b12ce1e5ed224699dcafa342c4c61297ec5b9af7829ac4b95fb77dc2d49305a94c1da38b42915f6f2b1163240f5ad450dddcca894265c219fa29f98c847e36aecaf143d523e05525e9c1ed66b60c57ac1b8d837b710624706448d490b9cd18f48964c898f926517266dad4abce9ffd34c3852d9ce07ab0df7fcd773dfc0662e0250d52bb6ab57d4d21d6aee326a995009567e57030fd925660e3177520588418514adb685ae17a5b1bfe483d115a5c2f4e46ef29e0ea13d94a7c70f7ad83dd0b4dc85bb12dc9fc456b2bd36d149ab99dac6118a9f1b55f22b5770b2aac92ac4833471567fe1960409a716d24b7b1aafe93dcf1a1512a26e017e823b88b8f3e2db288d4c41d163cd11e78e66b5ecdb86ca27b14c110383cfb341295c098595e37431a470a586fa95b7bc719813d09811f7d6bbea7c05ea7450873f801c08e76ca9bbf9bd610791d7571c63beb7ded2e81236765bc6289d4222054fb9dc0ada90c249fe0b9a04cea5fa2bca0a3c43552be8683ea7ef22de3e11734e981c9ebf640047df3badde897877e1d169be5a8fd43fe8e78cc62ee071ad4ccd1501f823f9144bbdac50d44e00a28c054ef1447ca8882edbc73d1df45295777271362a9ae2a387a68407e48babebdce01d2c203bb93c41d7bea51928a1df7102b715c34684cc7eee4a76a434e0f45305aa540baaa02dce9097700619a344183251a120e6e5100362458b2984c95eb9ad55735512f905cc81f5c418f045731ac454bca784d5fc0958b81443ea884d05d0ac2d2e04281ef17c91be25f958fa89cd51570f007d22a447f49382dd089dca836ebb0ab4f06531ad9214229b36bc7ae779f89b260601ae7081d024bd40822da50332cf64e2cc93744990189930029bd3209ef193ca2a984a72e1df5298154f76db994a3d0696efbb3873298902141f64b37014eeb8fc1ca4e3f5ae21e0ae561b2513bd8db7fa8c02cafc1bc45630910562077a7cef0e9a7d38850854ab95b99594b38899a702492e27090cabfc1ea303e364adac6c94f80d05b6b4246ab09de0f2edb6a0558888a434b9102f7de6ae26acba8655245d733c63623691a5e5d989638199758a7c91fdd642118a450d00077ea6f5e17265c5881f052175012fa64388c4f709acfe9806e9d9107477e5c1512dd0a329829046c3739f6a41a519c0477270e1bfc64cba0425f210ec0482f8820cfaa1d4cb4463238e5609b5bfab8bf42b2e114b25a54712726f11bed90c00447a6ff4cafa0e344fea1ddcbb4faa9b0b1c1b5bd22bb68a31eea12f6af54e77e0d661cf149751ca0cc39711394231f95edc857521bec1581d1a951e14ee20a7a07f19ec254561bcb5abaf72e623c980d727f1dc7381215889462909c9fab77a708a9133b256e5da33a685d02265d6c9c4d4e7565bdd519f56a679073465de3f229701b001f673621e95e4d2e89f55a6eb921e7cf94d32b50756231e1f1198b1d428798eb9417536e15ac7a4e84f10263e0c566de22544fe63ff9b222ef7a3641d5df8178e3ad46b37ac5f2f83bd0abe247558d191bd72b8cf347510e7a736f0074d15695a6f49154ae1ff44423c61cac67a6c3dece44b284ab3e32607ac1865605678a9e5c51691e92dded1165afad14b17367505ce12aba25401059436a5c267141ceb9f1165c14cccdd7feeb69307dee966fbd968214df6498b17cc6596831523c377734ae848962a98719afccaa8a5329d13a32e65a977216905126f7195a1c0434d6b9bb745abbfbc71b1ac81fa98fdab67fc95e9c4c451682afed7e451c97749c7f4d1a830e671261069d8dde464d8ca6ac34841c8cf2690167721825f233501656e732eaafe16d30d0fcfc302d33d760f0f534ba705aa4f0d7b7667f2f4645777cfadbecfa8ce01675704449fc05bd0e390d96563311f11272d3c532192d77ae8ab56ce1e4016a0695e77b9238a7f7a5a9fae12cac66630150f122504eef9f8c6e8d803b3b9947e2954cebe1697b81b6675141ec766a3d60d98fcbfd674965bbe4d8180392c5ea4c815a36dcba7ede7d8755f4a63d5b2d6107ce20b660ca40e6c2ce5bb8448f136858a33ccc571ed97922c31eef9fa7c170573ebb2e499039ff88bea54dbf0a54da8d523ea9d50db83da31b76983e1ac211a39c182a920910a9e056e637341441a88d1657096ea0a6ca986c831d6b83c8912b7f091433cb4f3d69e37aa7cc063d2cf166a475c7d2a4b3daa693581eeee6c0237199009ee172d65cff63fef0e9ccc8fa1dd62e7db9738b231375ea30bc67b28580b933a49583443fe1d28544fcada5f86819d1aeb280886aae5a8bd5e42922dba421435b8ca0d7c092626771d4bf8df4631a34daf6102a19a02920298a3d8192d10f7f42ad2626322b9bd760fdc308cb1c508a0a484bd77c1afe1935ddc742685385ed232e4bba13688ad6e2628ed4b11e4114a66691b47f5c69c7b0088cf2c9e55729cfc3427e98395c13484e97e0af46172b701492778fd8fb6d651751d1c3ab10f348756e84786a45e9ea2f9b2c936249e1c52a884bbb93e0b27d6c0b4222211b2d783054c3e4f13882d4787b83de3ec0889e463b5776beebeb008b025299c525c575bc363bfb90e7894dc803a66697328d1494c8abbfa9ac9598ddea103dad60fefaee6bba77735d9a0f0b3b42284559345871089eef85dcad7d0fad10aafdbdca1f830760f50187e3f0a17b68587afd822045ad57b0e65f95665f99e2c92a83163469451c09f6f0db55a3930efb654a3f4785bb06c7aafd02025c08d17d549fca53609b6dd5f285bc69a927b255f77449d0d3ced871972e9be2732162e8d42e52f42b6e1ae9f162ac43a644de569ed8572b6596653d032c35a139dc0078e09494556f58da64905f614e694f2c9418c61c7350c8bfa044dd6b1f12ced09bddd899cb676b7aaa7458e8db349d136cb6a8b281e9f184116f5ac3a1940ec03bcafb9a4dd060da4115927c09f14cb6719ef75eba0eab5b35e71dfe55bfcc6105fcb5978014311aac78602a7ebd8af131362366c4554956a114a3e54632b4e12a1c1bba3ec49d845aa29b203c1c7d4fb11c2d2b3ddfcb3e6fa89016e14f34820c743b6bb413f987a4bea2dcbfeaf3b39015caa8bafda5323165b28824b8a000ae40fa2090f7c843719eafbde3e30059035f2a013f17fb05d91daea58945eba0e2b73fc9ac1a840e700f3ce06d2100d9cbd1ea3069c62335c890210f70639fb255b12f5e10794900fac273924e224f314def0b43b9eb0201e4072d3ce8bb2fa2a159d169896012ca661b7ec79b21740afe469d69b093570748ade71c8a846ab \ No newline at end of file diff --git a/axiom-eth/data/transaction/test.yul b/axiom-eth/data/transaction/test.yul new file mode 100644 index 00000000..71c402aa --- /dev/null +++ b/axiom-eth/data/transaction/test.yul @@ -0,0 +1,1794 @@ + + 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(0x0, 636471555827657750166888787375234867401705836697401370361760792659418825816) + + { + let x := calldataload(0x240) + mstore(0x260, x) + let y := calldataload(0x260) + mstore(0x280, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x280) + mstore(0x2a0, x) + let y := calldataload(0x2a0) + mstore(0x2c0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x2c0) + mstore(0x2e0, x) + let y := calldataload(0x2e0) + mstore(0x300, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x300) + mstore(0x320, x) + let y := calldataload(0x320) + mstore(0x340, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x340) + mstore(0x360, x) + let y := calldataload(0x360) + mstore(0x380, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x380) + mstore(0x3a0, x) + let y := calldataload(0x3a0) + mstore(0x3c0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x3e0, keccak256(0x0, 992)) +{ + let hash := mload(0x3e0) + mstore(0x400, mod(hash, f_q)) + mstore(0x420, hash) + } + + { + let x := calldataload(0x3c0) + mstore(0x440, x) + let y := calldataload(0x3e0) + mstore(0x460, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x400) + mstore(0x480, x) + let y := calldataload(0x420) + mstore(0x4a0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x4c0, keccak256(0x420, 160)) +{ + let hash := mload(0x4c0) + mstore(0x4e0, mod(hash, f_q)) + mstore(0x500, hash) + } +mstore8(1312, 1) +mstore(0x520, keccak256(0x500, 33)) +{ + let hash := mload(0x520) + mstore(0x540, mod(hash, f_q)) + mstore(0x560, hash) + } + + { + let x := calldataload(0x440) + mstore(0x580, x) + let y := calldataload(0x460) + mstore(0x5a0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x480) + mstore(0x5c0, x) + let y := calldataload(0x4a0) + mstore(0x5e0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x4c0) + mstore(0x600, x) + let y := calldataload(0x4e0) + mstore(0x620, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x500) + mstore(0x640, x) + let y := calldataload(0x520) + mstore(0x660, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x540) + mstore(0x680, x) + let y := calldataload(0x560) + mstore(0x6a0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x580) + mstore(0x6c0, x) + let y := calldataload(0x5a0) + mstore(0x6e0, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x700, keccak256(0x560, 416)) +{ + let hash := mload(0x700) + mstore(0x720, mod(hash, f_q)) + mstore(0x740, hash) + } + + { + let x := calldataload(0x5c0) + mstore(0x760, x) + let y := calldataload(0x5e0) + mstore(0x780, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x600) + mstore(0x7a0, x) + let y := calldataload(0x620) + mstore(0x7c0, y) + success := and(validate_ec_point(x, y), success) + } + + { + let x := calldataload(0x640) + mstore(0x7e0, x) + let y := calldataload(0x660) + mstore(0x800, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x820, keccak256(0x740, 224)) +{ + let hash := mload(0x820) + mstore(0x840, mod(hash, f_q)) + mstore(0x860, hash) + } +mstore(0x880, mod(calldataload(0x680), f_q)) +mstore(0x8a0, mod(calldataload(0x6a0), f_q)) +mstore(0x8c0, mod(calldataload(0x6c0), f_q)) +mstore(0x8e0, mod(calldataload(0x6e0), f_q)) +mstore(0x900, mod(calldataload(0x700), f_q)) +mstore(0x920, mod(calldataload(0x720), f_q)) +mstore(0x940, mod(calldataload(0x740), f_q)) +mstore(0x960, mod(calldataload(0x760), f_q)) +mstore(0x980, mod(calldataload(0x780), f_q)) +mstore(0x9a0, mod(calldataload(0x7a0), f_q)) +mstore(0x9c0, mod(calldataload(0x7c0), f_q)) +mstore(0x9e0, mod(calldataload(0x7e0), f_q)) +mstore(0xa00, mod(calldataload(0x800), f_q)) +mstore(0xa20, mod(calldataload(0x820), f_q)) +mstore(0xa40, mod(calldataload(0x840), f_q)) +mstore(0xa60, mod(calldataload(0x860), f_q)) +mstore(0xa80, mod(calldataload(0x880), f_q)) +mstore(0xaa0, mod(calldataload(0x8a0), f_q)) +mstore(0xac0, 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, mod(calldataload(0xd00), f_q)) +mstore(0xf20, keccak256(0x860, 1728)) +{ + let hash := mload(0xf20) + mstore(0xf40, mod(hash, f_q)) + mstore(0xf60, hash) + } +mstore8(3968, 1) +mstore(0xf80, keccak256(0xf60, 33)) +{ + let hash := mload(0xf80) + mstore(0xfa0, mod(hash, f_q)) + mstore(0xfc0, hash) + } + + { + let x := calldataload(0xd20) + mstore(0xfe0, x) + let y := calldataload(0xd40) + mstore(0x1000, y) + success := and(validate_ec_point(x, y), success) + } +mstore(0x1020, keccak256(0xfc0, 96)) +{ + let hash := mload(0x1020) + mstore(0x1040, mod(hash, f_q)) + mstore(0x1060, hash) + } + + { + let x := calldataload(0xd60) + mstore(0x1080, x) + let y := calldataload(0xd80) + mstore(0x10a0, 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(4288, x) +let y := mload(0x80) +y := add(y, shl(88, mload(0xa0))) +y := add(y, shl(176, mload(0xc0))) +mstore(4320, 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(4352, x) +let y := mload(0x140) +y := add(y, shl(88, mload(0x160))) +y := add(y, shl(176, mload(0x180))) +mstore(4384, y) + + success := and(validate_ec_point(x, y), success) + } +mstore(0x1140, mulmod(mload(0x840), mload(0x840), 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, mulmod(mload(0x13c0), mload(0x13c0), f_q)) +mstore(0x1400, addmod(mload(0x13e0), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q)) +mstore(0x1420, mulmod(mload(0x1400), 21888237653275510688422624196183639687472264873923820041627027729598873448513, f_q)) +mstore(0x1440, mulmod(mload(0x1420), 13225785879531581993054172815365636627224369411478295502904397545373139154045, f_q)) +mstore(0x1460, addmod(mload(0x840), 8662456992307693229192232929891638461323994988937738840793806641202669341572, f_q)) +mstore(0x1480, mulmod(mload(0x1420), 10939663269433627367777756708678102241564365262857670666700619874077960926249, f_q)) +mstore(0x14a0, addmod(mload(0x840), 10948579602405647854468649036579172846983999137558363676997584312497847569368, f_q)) +mstore(0x14c0, mulmod(mload(0x1420), 11016257578652593686382655500910603527869149377564754001549454008164059876499, f_q)) +mstore(0x14e0, addmod(mload(0x840), 10871985293186681535863750244346671560679215022851280342148750178411748619118, f_q)) +mstore(0x1500, mulmod(mload(0x1420), 15402826414547299628414612080036060696555554914079673875872749760617770134879, f_q)) +mstore(0x1520, addmod(mload(0x840), 6485416457291975593831793665221214391992809486336360467825454425958038360738, f_q)) +mstore(0x1540, mulmod(mload(0x1420), 21710372849001950800533397158415938114909991150039389063546734567764856596059, f_q)) +mstore(0x1560, addmod(mload(0x840), 177870022837324421713008586841336973638373250376645280151469618810951899558, f_q)) +mstore(0x1580, mulmod(mload(0x1420), 2785514556381676080176937710880804108647911392478702105860685610379369825016, f_q)) +mstore(0x15a0, addmod(mload(0x840), 19102728315457599142069468034376470979900453007937332237837518576196438670601, f_q)) +mstore(0x15c0, mulmod(mload(0x1420), 8734126352828345679573237859165904705806588461301144420590422589042130041188, f_q)) +mstore(0x15e0, addmod(mload(0x840), 13154116519010929542673167886091370382741775939114889923107781597533678454429, f_q)) +mstore(0x1600, mulmod(mload(0x1420), 1, f_q)) +mstore(0x1620, addmod(mload(0x840), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q)) +mstore(0x1640, mulmod(mload(0x1420), 11211301017135681023579411905410872569206244553457844956874280139879520583390, f_q)) +mstore(0x1660, addmod(mload(0x840), 10676941854703594198666993839846402519342119846958189386823924046696287912227, f_q)) +mstore(0x1680, mulmod(mload(0x1420), 1426404432721484388505361748317961535523355871255605456897797744433766488507, f_q)) +mstore(0x16a0, addmod(mload(0x840), 20461838439117790833741043996939313553025008529160428886800406442142042007110, f_q)) +mstore(0x16c0, mulmod(mload(0x1420), 12619617507853212586156872920672483948819476989779550311307282715684870266992, f_q)) +mstore(0x16e0, addmod(mload(0x840), 9268625363986062636089532824584791139728887410636484032390921470890938228625, f_q)) +mstore(0x1700, mulmod(mload(0x1420), 19032961837237948602743626455740240236231119053033140765040043513661803148152, f_q)) +mstore(0x1720, addmod(mload(0x840), 2855281034601326619502779289517034852317245347382893578658160672914005347465, f_q)) +mstore(0x1740, mulmod(mload(0x1420), 915149353520972163646494413843788069594022902357002628455555785223409501882, f_q)) +mstore(0x1760, addmod(mload(0x840), 20973093518318303058599911331413487018954341498059031715242648401352398993735, f_q)) +mstore(0x1780, mulmod(mload(0x1420), 3766081621734395783232337525162072736827576297943013392955872170138036189193, f_q)) +mstore(0x17a0, addmod(mload(0x840), 18122161250104879439014068220095202351720788102473020950742332016437772306424, f_q)) +mstore(0x17c0, mulmod(mload(0x1420), 4245441013247250116003069945606352967193023389718465410501109428393342802981, f_q)) +mstore(0x17e0, addmod(mload(0x840), 17642801858592025106243335799650922121355341010697568933197094758182465692636, f_q)) +mstore(0x1800, mulmod(mload(0x1420), 5854133144571823792863860130267644613802765696134002830362054821530146160770, f_q)) +mstore(0x1820, addmod(mload(0x840), 16034109727267451429382545614989630474745598704282031513336149365045662334847, f_q)) +mstore(0x1840, mulmod(mload(0x1420), 5980488956150442207659150513163747165544364597008566989111579977672498964212, f_q)) +mstore(0x1860, addmod(mload(0x840), 15907753915688833014587255232093527923003999803407467354586624208903309531405, f_q)) +mstore(0x1880, mulmod(mload(0x1420), 14557038802599140430182096396825290815503940951075961210638273254419942783582, f_q)) +mstore(0x18a0, addmod(mload(0x840), 7331204069240134792064309348431984273044423449340073133059930932155865712035, f_q)) +mstore(0x18c0, mulmod(mload(0x1420), 13553911191894110065493137367144919847521088405945523452288398666974237857208, f_q)) +mstore(0x18e0, addmod(mload(0x840), 8334331679945165156753268378112355241027275994470510891409805519601570638409, f_q)) +mstore(0x1900, mulmod(mload(0x1420), 9697063347556872083384215826199993067635178715531258559890418744774301211662, f_q)) +mstore(0x1920, addmod(mload(0x840), 12191179524282403138862189919057282020913185684884775783807785441801507283955, f_q)) +mstore(0x1940, mulmod(mload(0x1420), 10807735674816066981985242612061336605021639643453679977988966079770672437131, f_q)) +mstore(0x1960, addmod(mload(0x840), 11080507197023208240261163133195938483526724756962354365709238106805136058486, f_q)) +mstore(0x1980, mulmod(mload(0x1420), 12459868075641381822485233712013080087763946065665469821362892189399541605692, f_q)) +mstore(0x19a0, addmod(mload(0x840), 9428374796197893399761172033244195000784418334750564522335311997176266889925, f_q)) +mstore(0x19c0, mulmod(mload(0x1420), 16038300751658239075779628684257016433412502747804121525056508685985277092575, f_q)) +mstore(0x19e0, addmod(mload(0x840), 5849942120181036146466777061000258655135861652611912818641695500590531403042, f_q)) +mstore(0x1a00, mulmod(mload(0x1420), 6955697244493336113861667751840378876927906302623587437721024018233754910398, f_q)) +mstore(0x1a20, addmod(mload(0x840), 14932545627345939108384737993416896211620458097792446905977180168342053585219, f_q)) +mstore(0x1a40, mulmod(mload(0x1420), 13498745591877810872211159461644682954739332524336278910448604883789771736885, f_q)) +mstore(0x1a60, addmod(mload(0x840), 8389497279961464350035246283612592133809031876079755433249599302786036758732, f_q)) +{ + let prod := mload(0x1460) + + prod := mulmod(mload(0x14a0), prod, f_q) + mstore(0x1a80, prod) + + prod := mulmod(mload(0x14e0), prod, f_q) + mstore(0x1aa0, prod) + + prod := mulmod(mload(0x1520), prod, f_q) + mstore(0x1ac0, prod) + + prod := mulmod(mload(0x1560), prod, f_q) + mstore(0x1ae0, prod) + + prod := mulmod(mload(0x15a0), prod, f_q) + mstore(0x1b00, prod) + + prod := mulmod(mload(0x15e0), prod, f_q) + mstore(0x1b20, prod) + + prod := mulmod(mload(0x1620), prod, f_q) + mstore(0x1b40, prod) + + prod := mulmod(mload(0x1660), prod, f_q) + mstore(0x1b60, prod) + + prod := mulmod(mload(0x16a0), prod, f_q) + mstore(0x1b80, prod) + + prod := mulmod(mload(0x16e0), prod, f_q) + mstore(0x1ba0, prod) + + prod := mulmod(mload(0x1720), prod, f_q) + mstore(0x1bc0, prod) + + prod := mulmod(mload(0x1760), prod, f_q) + mstore(0x1be0, prod) + + prod := mulmod(mload(0x17a0), prod, f_q) + mstore(0x1c00, prod) + + prod := mulmod(mload(0x17e0), prod, f_q) + mstore(0x1c20, prod) + + prod := mulmod(mload(0x1820), prod, f_q) + mstore(0x1c40, prod) + + prod := mulmod(mload(0x1860), prod, f_q) + mstore(0x1c60, prod) + + prod := mulmod(mload(0x18a0), prod, f_q) + mstore(0x1c80, prod) + + prod := mulmod(mload(0x18e0), prod, f_q) + mstore(0x1ca0, prod) + + prod := mulmod(mload(0x1920), prod, f_q) + mstore(0x1cc0, prod) + + prod := mulmod(mload(0x1960), prod, f_q) + mstore(0x1ce0, prod) + + prod := mulmod(mload(0x19a0), prod, f_q) + mstore(0x1d00, prod) + + prod := mulmod(mload(0x19e0), prod, f_q) + mstore(0x1d20, prod) + + prod := mulmod(mload(0x1a20), prod, f_q) + mstore(0x1d40, prod) + + prod := mulmod(mload(0x1a60), prod, f_q) + mstore(0x1d60, prod) + + prod := mulmod(mload(0x1400), prod, f_q) + mstore(0x1d80, prod) + + } +mstore(0x1dc0, 32) +mstore(0x1de0, 32) +mstore(0x1e00, 32) +mstore(0x1e20, mload(0x1d80)) +mstore(0x1e40, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x1e60, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x1dc0, 0xc0, 0x1da0, 0x20), 1), success) +{ + + let inv := mload(0x1da0) + let v + + v := mload(0x1400) + mstore(5120, mulmod(mload(0x1d60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1a60) + mstore(6752, mulmod(mload(0x1d40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1a20) + mstore(6688, mulmod(mload(0x1d20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x19e0) + mstore(6624, mulmod(mload(0x1d00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x19a0) + mstore(6560, mulmod(mload(0x1ce0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1960) + mstore(6496, mulmod(mload(0x1cc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1920) + mstore(6432, mulmod(mload(0x1ca0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x18e0) + mstore(6368, mulmod(mload(0x1c80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x18a0) + mstore(6304, mulmod(mload(0x1c60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1860) + mstore(6240, mulmod(mload(0x1c40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1820) + mstore(6176, mulmod(mload(0x1c20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x17e0) + mstore(6112, mulmod(mload(0x1c00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x17a0) + mstore(6048, mulmod(mload(0x1be0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1760) + mstore(5984, mulmod(mload(0x1bc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1720) + mstore(5920, mulmod(mload(0x1ba0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x16e0) + mstore(5856, mulmod(mload(0x1b80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x16a0) + mstore(5792, mulmod(mload(0x1b60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1660) + mstore(5728, mulmod(mload(0x1b40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1620) + mstore(5664, mulmod(mload(0x1b20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x15e0) + mstore(5600, mulmod(mload(0x1b00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x15a0) + mstore(5536, mulmod(mload(0x1ae0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1560) + mstore(5472, mulmod(mload(0x1ac0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x1520) + mstore(5408, mulmod(mload(0x1aa0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x14e0) + mstore(5344, mulmod(mload(0x1a80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x14a0) + mstore(5280, mulmod(mload(0x1460), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x1460, inv) + + } +mstore(0x1e80, mulmod(mload(0x1440), mload(0x1460), f_q)) +mstore(0x1ea0, mulmod(mload(0x1480), mload(0x14a0), f_q)) +mstore(0x1ec0, mulmod(mload(0x14c0), mload(0x14e0), f_q)) +mstore(0x1ee0, mulmod(mload(0x1500), mload(0x1520), f_q)) +mstore(0x1f00, mulmod(mload(0x1540), mload(0x1560), f_q)) +mstore(0x1f20, mulmod(mload(0x1580), mload(0x15a0), f_q)) +mstore(0x1f40, mulmod(mload(0x15c0), mload(0x15e0), f_q)) +mstore(0x1f60, mulmod(mload(0x1600), mload(0x1620), f_q)) +mstore(0x1f80, mulmod(mload(0x1640), mload(0x1660), f_q)) +mstore(0x1fa0, mulmod(mload(0x1680), mload(0x16a0), f_q)) +mstore(0x1fc0, mulmod(mload(0x16c0), mload(0x16e0), f_q)) +mstore(0x1fe0, mulmod(mload(0x1700), mload(0x1720), f_q)) +mstore(0x2000, mulmod(mload(0x1740), mload(0x1760), f_q)) +mstore(0x2020, mulmod(mload(0x1780), mload(0x17a0), f_q)) +mstore(0x2040, mulmod(mload(0x17c0), mload(0x17e0), f_q)) +mstore(0x2060, mulmod(mload(0x1800), mload(0x1820), f_q)) +mstore(0x2080, mulmod(mload(0x1840), mload(0x1860), f_q)) +mstore(0x20a0, mulmod(mload(0x1880), mload(0x18a0), f_q)) +mstore(0x20c0, mulmod(mload(0x18c0), mload(0x18e0), f_q)) +mstore(0x20e0, mulmod(mload(0x1900), mload(0x1920), f_q)) +mstore(0x2100, mulmod(mload(0x1940), mload(0x1960), f_q)) +mstore(0x2120, mulmod(mload(0x1980), mload(0x19a0), f_q)) +mstore(0x2140, mulmod(mload(0x19c0), mload(0x19e0), f_q)) +mstore(0x2160, mulmod(mload(0x1a00), mload(0x1a20), f_q)) +mstore(0x2180, mulmod(mload(0x1a40), mload(0x1a60), f_q)) +{ + let result := mulmod(mload(0x1f60), mload(0x20), f_q) +result := addmod(mulmod(mload(0x1f80), mload(0x40), f_q), result, f_q) +result := addmod(mulmod(mload(0x1fa0), mload(0x60), f_q), result, f_q) +result := addmod(mulmod(mload(0x1fc0), mload(0x80), f_q), result, f_q) +result := addmod(mulmod(mload(0x1fe0), mload(0xa0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2000), mload(0xc0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2020), mload(0xe0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2040), mload(0x100), f_q), result, f_q) +result := addmod(mulmod(mload(0x2060), mload(0x120), f_q), result, f_q) +result := addmod(mulmod(mload(0x2080), mload(0x140), f_q), result, f_q) +result := addmod(mulmod(mload(0x20a0), mload(0x160), f_q), result, f_q) +result := addmod(mulmod(mload(0x20c0), mload(0x180), f_q), result, f_q) +result := addmod(mulmod(mload(0x20e0), mload(0x1a0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2100), mload(0x1c0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2120), mload(0x1e0), f_q), result, f_q) +result := addmod(mulmod(mload(0x2140), mload(0x200), f_q), result, f_q) +result := addmod(mulmod(mload(0x2160), mload(0x220), f_q), result, f_q) +result := addmod(mulmod(mload(0x2180), mload(0x240), f_q), result, f_q) +mstore(8608, result) + } +mstore(0x21c0, mulmod(mload(0x8c0), mload(0x8a0), f_q)) +mstore(0x21e0, addmod(mload(0x880), mload(0x21c0), f_q)) +mstore(0x2200, addmod(mload(0x21e0), sub(f_q, mload(0x8e0)), f_q)) +mstore(0x2220, mulmod(mload(0x2200), mload(0xb60), f_q)) +mstore(0x2240, mulmod(mload(0x720), mload(0x2220), f_q)) +mstore(0x2260, mulmod(mload(0x940), mload(0x920), f_q)) +mstore(0x2280, addmod(mload(0x900), mload(0x2260), f_q)) +mstore(0x22a0, addmod(mload(0x2280), sub(f_q, mload(0x960)), f_q)) +mstore(0x22c0, mulmod(mload(0x22a0), mload(0xb80), f_q)) +mstore(0x22e0, addmod(mload(0x2240), mload(0x22c0), f_q)) +mstore(0x2300, mulmod(mload(0x720), mload(0x22e0), f_q)) +mstore(0x2320, mulmod(mload(0x9c0), mload(0x9a0), f_q)) +mstore(0x2340, addmod(mload(0x980), mload(0x2320), f_q)) +mstore(0x2360, addmod(mload(0x2340), sub(f_q, mload(0x9e0)), f_q)) +mstore(0x2380, mulmod(mload(0x2360), mload(0xba0), f_q)) +mstore(0x23a0, addmod(mload(0x2300), mload(0x2380), f_q)) +mstore(0x23c0, mulmod(mload(0x720), mload(0x23a0), f_q)) +mstore(0x23e0, mulmod(mload(0xa40), mload(0xa20), f_q)) +mstore(0x2400, addmod(mload(0xa00), mload(0x23e0), f_q)) +mstore(0x2420, addmod(mload(0x2400), sub(f_q, mload(0xa60)), f_q)) +mstore(0x2440, mulmod(mload(0x2420), mload(0xbc0), f_q)) +mstore(0x2460, addmod(mload(0x23c0), mload(0x2440), f_q)) +mstore(0x2480, mulmod(mload(0x720), mload(0x2460), f_q)) +mstore(0x24a0, mulmod(mload(0xac0), mload(0xaa0), f_q)) +mstore(0x24c0, addmod(mload(0xa80), mload(0x24a0), f_q)) +mstore(0x24e0, addmod(mload(0x24c0), sub(f_q, mload(0xae0)), f_q)) +mstore(0x2500, mulmod(mload(0x24e0), mload(0xbe0), f_q)) +mstore(0x2520, addmod(mload(0x2480), mload(0x2500), f_q)) +mstore(0x2540, mulmod(mload(0x720), mload(0x2520), f_q)) +mstore(0x2560, addmod(1, sub(f_q, mload(0xd20)), f_q)) +mstore(0x2580, mulmod(mload(0x2560), mload(0x1f60), f_q)) +mstore(0x25a0, addmod(mload(0x2540), mload(0x2580), f_q)) +mstore(0x25c0, mulmod(mload(0x720), mload(0x25a0), f_q)) +mstore(0x25e0, mulmod(mload(0xe40), mload(0xe40), f_q)) +mstore(0x2600, addmod(mload(0x25e0), sub(f_q, mload(0xe40)), f_q)) +mstore(0x2620, mulmod(mload(0x2600), mload(0x1e80), f_q)) +mstore(0x2640, addmod(mload(0x25c0), mload(0x2620), f_q)) +mstore(0x2660, mulmod(mload(0x720), mload(0x2640), f_q)) +mstore(0x2680, addmod(mload(0xd80), sub(f_q, mload(0xd60)), f_q)) +mstore(0x26a0, mulmod(mload(0x2680), mload(0x1f60), f_q)) +mstore(0x26c0, addmod(mload(0x2660), mload(0x26a0), f_q)) +mstore(0x26e0, mulmod(mload(0x720), mload(0x26c0), f_q)) +mstore(0x2700, addmod(mload(0xde0), sub(f_q, mload(0xdc0)), f_q)) +mstore(0x2720, mulmod(mload(0x2700), mload(0x1f60), f_q)) +mstore(0x2740, addmod(mload(0x26e0), mload(0x2720), f_q)) +mstore(0x2760, mulmod(mload(0x720), mload(0x2740), f_q)) +mstore(0x2780, addmod(mload(0xe40), sub(f_q, mload(0xe20)), f_q)) +mstore(0x27a0, mulmod(mload(0x2780), mload(0x1f60), f_q)) +mstore(0x27c0, addmod(mload(0x2760), mload(0x27a0), f_q)) +mstore(0x27e0, mulmod(mload(0x720), mload(0x27c0), f_q)) +mstore(0x2800, addmod(1, sub(f_q, mload(0x1e80)), f_q)) +mstore(0x2820, addmod(mload(0x1ea0), mload(0x1ec0), f_q)) +mstore(0x2840, addmod(mload(0x2820), mload(0x1ee0), f_q)) +mstore(0x2860, addmod(mload(0x2840), mload(0x1f00), f_q)) +mstore(0x2880, addmod(mload(0x2860), mload(0x1f20), f_q)) +mstore(0x28a0, addmod(mload(0x2880), mload(0x1f40), f_q)) +mstore(0x28c0, addmod(mload(0x2800), sub(f_q, mload(0x28a0)), f_q)) +mstore(0x28e0, mulmod(mload(0xc20), mload(0x4e0), f_q)) +mstore(0x2900, addmod(mload(0xb20), mload(0x28e0), f_q)) +mstore(0x2920, addmod(mload(0x2900), mload(0x540), f_q)) +mstore(0x2940, mulmod(mload(0xc40), mload(0x4e0), f_q)) +mstore(0x2960, addmod(mload(0x880), mload(0x2940), f_q)) +mstore(0x2980, addmod(mload(0x2960), mload(0x540), f_q)) +mstore(0x29a0, mulmod(mload(0x2980), mload(0x2920), f_q)) +mstore(0x29c0, mulmod(mload(0x29a0), mload(0xd40), f_q)) +mstore(0x29e0, mulmod(1, mload(0x4e0), f_q)) +mstore(0x2a00, mulmod(mload(0x840), mload(0x29e0), f_q)) +mstore(0x2a20, addmod(mload(0xb20), mload(0x2a00), f_q)) +mstore(0x2a40, addmod(mload(0x2a20), mload(0x540), f_q)) +mstore(0x2a60, mulmod(4131629893567559867359510883348571134090853742863529169391034518566172092834, mload(0x4e0), f_q)) +mstore(0x2a80, mulmod(mload(0x840), mload(0x2a60), f_q)) +mstore(0x2aa0, addmod(mload(0x880), mload(0x2a80), f_q)) +mstore(0x2ac0, addmod(mload(0x2aa0), mload(0x540), f_q)) +mstore(0x2ae0, mulmod(mload(0x2ac0), mload(0x2a40), f_q)) +mstore(0x2b00, mulmod(mload(0x2ae0), mload(0xd20), f_q)) +mstore(0x2b20, addmod(mload(0x29c0), sub(f_q, mload(0x2b00)), f_q)) +mstore(0x2b40, mulmod(mload(0x2b20), mload(0x28c0), f_q)) +mstore(0x2b60, addmod(mload(0x27e0), mload(0x2b40), f_q)) +mstore(0x2b80, mulmod(mload(0x720), mload(0x2b60), f_q)) +mstore(0x2ba0, mulmod(mload(0xc60), mload(0x4e0), f_q)) +mstore(0x2bc0, addmod(mload(0x900), mload(0x2ba0), f_q)) +mstore(0x2be0, addmod(mload(0x2bc0), mload(0x540), f_q)) +mstore(0x2c00, mulmod(mload(0xc80), mload(0x4e0), f_q)) +mstore(0x2c20, addmod(mload(0x980), mload(0x2c00), f_q)) +mstore(0x2c40, addmod(mload(0x2c20), mload(0x540), f_q)) +mstore(0x2c60, mulmod(mload(0x2c40), mload(0x2be0), f_q)) +mstore(0x2c80, mulmod(mload(0x2c60), mload(0xda0), f_q)) +mstore(0x2ca0, mulmod(8910878055287538404433155982483128285667088683464058436815641868457422632747, mload(0x4e0), f_q)) +mstore(0x2cc0, mulmod(mload(0x840), mload(0x2ca0), f_q)) +mstore(0x2ce0, addmod(mload(0x900), mload(0x2cc0), f_q)) +mstore(0x2d00, addmod(mload(0x2ce0), mload(0x540), f_q)) +mstore(0x2d20, mulmod(11166246659983828508719468090013646171463329086121580628794302409516816350802, mload(0x4e0), f_q)) +mstore(0x2d40, mulmod(mload(0x840), mload(0x2d20), f_q)) +mstore(0x2d60, addmod(mload(0x980), mload(0x2d40), f_q)) +mstore(0x2d80, addmod(mload(0x2d60), mload(0x540), f_q)) +mstore(0x2da0, mulmod(mload(0x2d80), mload(0x2d00), f_q)) +mstore(0x2dc0, mulmod(mload(0x2da0), mload(0xd80), f_q)) +mstore(0x2de0, addmod(mload(0x2c80), sub(f_q, mload(0x2dc0)), f_q)) +mstore(0x2e00, mulmod(mload(0x2de0), mload(0x28c0), f_q)) +mstore(0x2e20, addmod(mload(0x2b80), mload(0x2e00), f_q)) +mstore(0x2e40, mulmod(mload(0x720), mload(0x2e20), f_q)) +mstore(0x2e60, mulmod(mload(0xca0), mload(0x4e0), f_q)) +mstore(0x2e80, addmod(mload(0xa00), mload(0x2e60), f_q)) +mstore(0x2ea0, addmod(mload(0x2e80), mload(0x540), f_q)) +mstore(0x2ec0, mulmod(mload(0xcc0), mload(0x4e0), f_q)) +mstore(0x2ee0, addmod(mload(0xa80), mload(0x2ec0), f_q)) +mstore(0x2f00, addmod(mload(0x2ee0), mload(0x540), f_q)) +mstore(0x2f20, mulmod(mload(0x2f00), mload(0x2ea0), f_q)) +mstore(0x2f40, mulmod(mload(0x2f20), mload(0xe00), f_q)) +mstore(0x2f60, mulmod(284840088355319032285349970403338060113257071685626700086398481893096618818, mload(0x4e0), f_q)) +mstore(0x2f80, mulmod(mload(0x840), mload(0x2f60), f_q)) +mstore(0x2fa0, addmod(mload(0xa00), mload(0x2f80), f_q)) +mstore(0x2fc0, addmod(mload(0x2fa0), mload(0x540), f_q)) +mstore(0x2fe0, mulmod(21134065618345176623193549882539580312263652408302468683943992798037078993309, mload(0x4e0), f_q)) +mstore(0x3000, mulmod(mload(0x840), mload(0x2fe0), f_q)) +mstore(0x3020, addmod(mload(0xa80), mload(0x3000), f_q)) +mstore(0x3040, addmod(mload(0x3020), mload(0x540), f_q)) +mstore(0x3060, mulmod(mload(0x3040), mload(0x2fc0), f_q)) +mstore(0x3080, mulmod(mload(0x3060), mload(0xde0), f_q)) +mstore(0x30a0, addmod(mload(0x2f40), sub(f_q, mload(0x3080)), f_q)) +mstore(0x30c0, mulmod(mload(0x30a0), mload(0x28c0), f_q)) +mstore(0x30e0, addmod(mload(0x2e40), mload(0x30c0), f_q)) +mstore(0x3100, mulmod(mload(0x720), mload(0x30e0), f_q)) +mstore(0x3120, mulmod(mload(0xce0), mload(0x4e0), f_q)) +mstore(0x3140, addmod(mload(0xb00), mload(0x3120), f_q)) +mstore(0x3160, addmod(mload(0x3140), mload(0x540), f_q)) +mstore(0x3180, mulmod(mload(0xd00), mload(0x4e0), f_q)) +mstore(0x31a0, addmod(mload(0x21a0), mload(0x3180), f_q)) +mstore(0x31c0, addmod(mload(0x31a0), mload(0x540), f_q)) +mstore(0x31e0, mulmod(mload(0x31c0), mload(0x3160), f_q)) +mstore(0x3200, mulmod(mload(0x31e0), mload(0xe60), f_q)) +mstore(0x3220, mulmod(5625741653535312224677218588085279924365897425605943700675464992185016992283, mload(0x4e0), f_q)) +mstore(0x3240, mulmod(mload(0x840), mload(0x3220), f_q)) +mstore(0x3260, addmod(mload(0xb00), mload(0x3240), f_q)) +mstore(0x3280, addmod(mload(0x3260), mload(0x540), f_q)) +mstore(0x32a0, mulmod(14704729814417906439424896605881467874595262020190401576785074330126828718155, mload(0x4e0), f_q)) +mstore(0x32c0, mulmod(mload(0x840), mload(0x32a0), f_q)) +mstore(0x32e0, addmod(mload(0x21a0), mload(0x32c0), f_q)) +mstore(0x3300, addmod(mload(0x32e0), mload(0x540), f_q)) +mstore(0x3320, mulmod(mload(0x3300), mload(0x3280), f_q)) +mstore(0x3340, mulmod(mload(0x3320), mload(0xe40), f_q)) +mstore(0x3360, addmod(mload(0x3200), sub(f_q, mload(0x3340)), f_q)) +mstore(0x3380, mulmod(mload(0x3360), mload(0x28c0), f_q)) +mstore(0x33a0, addmod(mload(0x3100), mload(0x3380), f_q)) +mstore(0x33c0, mulmod(mload(0x720), mload(0x33a0), f_q)) +mstore(0x33e0, addmod(1, sub(f_q, mload(0xe80)), f_q)) +mstore(0x3400, mulmod(mload(0x33e0), mload(0x1f60), f_q)) +mstore(0x3420, addmod(mload(0x33c0), mload(0x3400), f_q)) +mstore(0x3440, mulmod(mload(0x720), mload(0x3420), f_q)) +mstore(0x3460, mulmod(mload(0xe80), mload(0xe80), f_q)) +mstore(0x3480, addmod(mload(0x3460), sub(f_q, mload(0xe80)), f_q)) +mstore(0x34a0, mulmod(mload(0x3480), mload(0x1e80), f_q)) +mstore(0x34c0, addmod(mload(0x3440), mload(0x34a0), f_q)) +mstore(0x34e0, mulmod(mload(0x720), mload(0x34c0), f_q)) +mstore(0x3500, addmod(mload(0xec0), mload(0x4e0), f_q)) +mstore(0x3520, mulmod(mload(0x3500), mload(0xea0), f_q)) +mstore(0x3540, addmod(mload(0xf00), mload(0x540), f_q)) +mstore(0x3560, mulmod(mload(0x3540), mload(0x3520), f_q)) +mstore(0x3580, addmod(mload(0xb00), mload(0x4e0), f_q)) +mstore(0x35a0, mulmod(mload(0x3580), mload(0xe80), f_q)) +mstore(0x35c0, addmod(mload(0xb40), mload(0x540), f_q)) +mstore(0x35e0, mulmod(mload(0x35c0), mload(0x35a0), f_q)) +mstore(0x3600, addmod(mload(0x3560), sub(f_q, mload(0x35e0)), f_q)) +mstore(0x3620, mulmod(mload(0x3600), mload(0x28c0), f_q)) +mstore(0x3640, addmod(mload(0x34e0), mload(0x3620), f_q)) +mstore(0x3660, mulmod(mload(0x720), mload(0x3640), f_q)) +mstore(0x3680, addmod(mload(0xec0), sub(f_q, mload(0xf00)), f_q)) +mstore(0x36a0, mulmod(mload(0x3680), mload(0x1f60), f_q)) +mstore(0x36c0, addmod(mload(0x3660), mload(0x36a0), f_q)) +mstore(0x36e0, mulmod(mload(0x720), mload(0x36c0), f_q)) +mstore(0x3700, mulmod(mload(0x3680), mload(0x28c0), f_q)) +mstore(0x3720, addmod(mload(0xec0), sub(f_q, mload(0xee0)), f_q)) +mstore(0x3740, mulmod(mload(0x3720), mload(0x3700), f_q)) +mstore(0x3760, addmod(mload(0x36e0), mload(0x3740), f_q)) +mstore(0x3780, mulmod(mload(0x13e0), mload(0x13e0), f_q)) +mstore(0x37a0, mulmod(mload(0x3780), mload(0x13e0), f_q)) +mstore(0x37c0, mulmod(1, mload(0x13e0), f_q)) +mstore(0x37e0, mulmod(1, mload(0x3780), f_q)) +mstore(0x3800, mulmod(mload(0x3760), mload(0x1400), f_q)) +mstore(0x3820, mulmod(mload(0x1140), mload(0x840), f_q)) +mstore(0x3840, mulmod(mload(0x3820), mload(0x840), f_q)) +mstore(0x3860, mulmod(mload(0x840), 1, f_q)) +mstore(0x3880, addmod(mload(0x1040), sub(f_q, mload(0x3860)), f_q)) +mstore(0x38a0, mulmod(mload(0x840), 1426404432721484388505361748317961535523355871255605456897797744433766488507, f_q)) +mstore(0x38c0, addmod(mload(0x1040), sub(f_q, mload(0x38a0)), f_q)) +mstore(0x38e0, mulmod(mload(0x840), 8734126352828345679573237859165904705806588461301144420590422589042130041188, f_q)) +mstore(0x3900, addmod(mload(0x1040), sub(f_q, mload(0x38e0)), f_q)) +mstore(0x3920, mulmod(mload(0x840), 11211301017135681023579411905410872569206244553457844956874280139879520583390, f_q)) +mstore(0x3940, addmod(mload(0x1040), sub(f_q, mload(0x3920)), f_q)) +mstore(0x3960, mulmod(mload(0x840), 12619617507853212586156872920672483948819476989779550311307282715684870266992, f_q)) +mstore(0x3980, addmod(mload(0x1040), sub(f_q, mload(0x3960)), f_q)) +mstore(0x39a0, mulmod(mload(0x840), 13225785879531581993054172815365636627224369411478295502904397545373139154045, f_q)) +mstore(0x39c0, addmod(mload(0x1040), sub(f_q, mload(0x39a0)), f_q)) +{ + let result := mulmod(mload(0x1040), mulmod(mload(0x3820), 3544324119167359571073009690693121464267965232733679586767649244433889388945, f_q), f_q) +result := addmod(mulmod(mload(0x840), mulmod(mload(0x3820), 18343918752671915651173396054564153624280399167682354756930554942141919106672, f_q), f_q), result, f_q) +mstore(14816, result) + } +{ + let result := mulmod(mload(0x1040), mulmod(mload(0x3820), 3860370625838117017501327045244227871206764201116468958063324100051382735289, f_q), f_q) +result := addmod(mulmod(mload(0x840), mulmod(mload(0x3820), 21616901807277407275624036604424346159916096890712898844034238973395610537327, f_q), f_q), result, f_q) +mstore(14848, result) + } +{ + let result := mulmod(mload(0x1040), mulmod(mload(0x3820), 21616901807277407275624036604424346159916096890712898844034238973395610537327, f_q), f_q) +result := addmod(mulmod(mload(0x840), mulmod(mload(0x3820), 889236556954614024749610889108815341999962898269585485843658889664869519176, f_q), f_q), result, f_q) +mstore(14880, result) + } +{ + let result := mulmod(mload(0x1040), mulmod(mload(0x3820), 3209408481237076479025468386201293941554240476766691830436732310949352383503, f_q), f_q) +result := addmod(mulmod(mload(0x840), mulmod(mload(0x3820), 12080394110851700286656425387058292751221637853580771255128961096834426654570, f_q), f_q), result, f_q) +mstore(14912, result) + } +mstore(0x3a60, mulmod(1, mload(0x3880), f_q)) +mstore(0x3a80, mulmod(mload(0x3a60), mload(0x3940), f_q)) +mstore(0x3aa0, mulmod(mload(0x3a80), mload(0x38c0), f_q)) +mstore(0x3ac0, mulmod(mload(0x3aa0), mload(0x3980), f_q)) +{ + let result := mulmod(mload(0x1040), 1, f_q) +result := addmod(mulmod(mload(0x840), 21888242871839275222246405745257275088548364400416034343698204186575808495616, f_q), result, f_q) +mstore(15072, result) + } +{ + let result := mulmod(mload(0x1040), mulmod(mload(0x1140), 8390819244605639573390577733158868133682115698337564550620146375401109684432, f_q), f_q) +result := addmod(mulmod(mload(0x840), mulmod(mload(0x1140), 13497423627233635648855828012098406954866248702078469793078057811174698811185, f_q), f_q), result, f_q) +mstore(15104, result) + } +{ + let result := mulmod(mload(0x1040), mulmod(mload(0x1140), 14389468897523033212448771694851898440525479866834419679925499462425232628530, f_q), f_q) +result := addmod(mulmod(mload(0x840), mulmod(mload(0x1140), 10771624105926513343199793365135253961557027396599172824137553349410803667382, f_q), f_q), result, f_q) +mstore(15136, result) + } +{ + let result := mulmod(mload(0x1040), mulmod(mload(0x1140), 8021781111580269725587432039983408559403601261632071736490564397134126857583, f_q), f_q) +result := addmod(mulmod(mload(0x840), mulmod(mload(0x1140), 13263758384809315129424392494083758423780924407584659157289746760747196496964, f_q), f_q), result, f_q) +mstore(15168, result) + } +mstore(0x3b60, mulmod(mload(0x3a80), mload(0x39c0), f_q)) +{ + let result := mulmod(mload(0x1040), mulmod(mload(0x840), 10676941854703594198666993839846402519342119846958189386823924046696287912228, f_q), f_q) +result := addmod(mulmod(mload(0x840), mulmod(mload(0x840), 11211301017135681023579411905410872569206244553457844956874280139879520583389, f_q), f_q), result, f_q) +mstore(15232, result) + } +{ + let result := mulmod(mload(0x1040), mulmod(mload(0x840), 11211301017135681023579411905410872569206244553457844956874280139879520583389, f_q), f_q) +result := addmod(mulmod(mload(0x840), mulmod(mload(0x840), 9784896584414196635074050157092911033682888682202239499976482395445754094883, f_q), f_q), result, f_q) +mstore(15264, result) + } +{ + let result := mulmod(mload(0x1040), mulmod(mload(0x840), 13154116519010929542673167886091370382741775939114889923107781597533678454430, f_q), f_q) +result := addmod(mulmod(mload(0x840), mulmod(mload(0x840), 8734126352828345679573237859165904705806588461301144420590422589042130041187, f_q), f_q), result, f_q) +mstore(15296, result) + } +{ + let result := mulmod(mload(0x1040), mulmod(mload(0x840), 8734126352828345679573237859165904705806588461301144420590422589042130041187, f_q), f_q) +result := addmod(mulmod(mload(0x840), mulmod(mload(0x840), 5948611796446669599396300148285100597158677068822442314729736978662760216172, f_q), f_q), result, f_q) +mstore(15328, result) + } +mstore(0x3c00, mulmod(mload(0x3a60), mload(0x3900), f_q)) +{ + let prod := mload(0x39e0) + + prod := mulmod(mload(0x3a00), prod, f_q) + mstore(0x3c20, prod) + + prod := mulmod(mload(0x3a20), prod, f_q) + mstore(0x3c40, prod) + + prod := mulmod(mload(0x3a40), prod, f_q) + mstore(0x3c60, prod) + + prod := mulmod(mload(0x3ae0), prod, f_q) + mstore(0x3c80, prod) + + prod := mulmod(mload(0x3a60), prod, f_q) + mstore(0x3ca0, prod) + + prod := mulmod(mload(0x3b00), prod, f_q) + mstore(0x3cc0, prod) + + prod := mulmod(mload(0x3b20), prod, f_q) + mstore(0x3ce0, prod) + + prod := mulmod(mload(0x3b40), prod, f_q) + mstore(0x3d00, prod) + + prod := mulmod(mload(0x3b60), prod, f_q) + mstore(0x3d20, prod) + + prod := mulmod(mload(0x3b80), prod, f_q) + mstore(0x3d40, prod) + + prod := mulmod(mload(0x3ba0), prod, f_q) + mstore(0x3d60, prod) + + prod := mulmod(mload(0x3a80), prod, f_q) + mstore(0x3d80, prod) + + prod := mulmod(mload(0x3bc0), prod, f_q) + mstore(0x3da0, prod) + + prod := mulmod(mload(0x3be0), prod, f_q) + mstore(0x3dc0, prod) + + prod := mulmod(mload(0x3c00), prod, f_q) + mstore(0x3de0, prod) + + } +mstore(0x3e20, 32) +mstore(0x3e40, 32) +mstore(0x3e60, 32) +mstore(0x3e80, mload(0x3de0)) +mstore(0x3ea0, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x3ec0, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x3e20, 0xc0, 0x3e00, 0x20), 1), success) +{ + + let inv := mload(0x3e00) + let v + + v := mload(0x3c00) + mstore(15360, mulmod(mload(0x3dc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3be0) + mstore(15328, mulmod(mload(0x3da0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3bc0) + mstore(15296, mulmod(mload(0x3d80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3a80) + mstore(14976, mulmod(mload(0x3d60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3ba0) + mstore(15264, mulmod(mload(0x3d40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3b80) + mstore(15232, mulmod(mload(0x3d20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3b60) + mstore(15200, mulmod(mload(0x3d00), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3b40) + mstore(15168, mulmod(mload(0x3ce0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3b20) + mstore(15136, mulmod(mload(0x3cc0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3b00) + mstore(15104, mulmod(mload(0x3ca0), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3a60) + mstore(14944, mulmod(mload(0x3c80), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3ae0) + mstore(15072, mulmod(mload(0x3c60), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3a40) + mstore(14912, mulmod(mload(0x3c40), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3a20) + mstore(14880, mulmod(mload(0x3c20), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3a00) + mstore(14848, mulmod(mload(0x39e0), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x39e0, inv) + + } +{ + let result := mload(0x39e0) +result := addmod(mload(0x3a00), result, f_q) +result := addmod(mload(0x3a20), result, f_q) +result := addmod(mload(0x3a40), result, f_q) +mstore(16096, result) + } +mstore(0x3f00, mulmod(mload(0x3ac0), mload(0x3a60), f_q)) +{ + let result := mload(0x3ae0) +mstore(16160, result) + } +mstore(0x3f40, mulmod(mload(0x3ac0), mload(0x3b60), f_q)) +{ + let result := mload(0x3b00) +result := addmod(mload(0x3b20), result, f_q) +result := addmod(mload(0x3b40), result, f_q) +mstore(16224, result) + } +mstore(0x3f80, mulmod(mload(0x3ac0), mload(0x3a80), f_q)) +{ + let result := mload(0x3b80) +result := addmod(mload(0x3ba0), result, f_q) +mstore(16288, result) + } +mstore(0x3fc0, mulmod(mload(0x3ac0), mload(0x3c00), f_q)) +{ + let result := mload(0x3bc0) +result := addmod(mload(0x3be0), result, f_q) +mstore(16352, result) + } +{ + let prod := mload(0x3ee0) + + prod := mulmod(mload(0x3f20), prod, f_q) + mstore(0x4000, prod) + + prod := mulmod(mload(0x3f60), prod, f_q) + mstore(0x4020, prod) + + prod := mulmod(mload(0x3fa0), prod, f_q) + mstore(0x4040, prod) + + prod := mulmod(mload(0x3fe0), prod, f_q) + mstore(0x4060, prod) + + } +mstore(0x40a0, 32) +mstore(0x40c0, 32) +mstore(0x40e0, 32) +mstore(0x4100, mload(0x4060)) +mstore(0x4120, 21888242871839275222246405745257275088548364400416034343698204186575808495615) +mstore(0x4140, 21888242871839275222246405745257275088548364400416034343698204186575808495617) +success := and(eq(staticcall(gas(), 0x5, 0x40a0, 0xc0, 0x4080, 0x20), 1), success) +{ + + let inv := mload(0x4080) + let v + + v := mload(0x3fe0) + mstore(16352, mulmod(mload(0x4040), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3fa0) + mstore(16288, mulmod(mload(0x4020), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3f60) + mstore(16224, mulmod(mload(0x4000), inv, f_q)) + inv := mulmod(v, inv, f_q) + + v := mload(0x3f20) + mstore(16160, mulmod(mload(0x3ee0), inv, f_q)) + inv := mulmod(v, inv, f_q) + mstore(0x3ee0, inv) + + } +mstore(0x4160, mulmod(mload(0x3f00), mload(0x3f20), f_q)) +mstore(0x4180, mulmod(mload(0x3f40), mload(0x3f60), f_q)) +mstore(0x41a0, mulmod(mload(0x3f80), mload(0x3fa0), f_q)) +mstore(0x41c0, mulmod(mload(0x3fc0), mload(0x3fe0), f_q)) +mstore(0x41e0, mulmod(mload(0xf40), mload(0xf40), f_q)) +mstore(0x4200, mulmod(mload(0x41e0), mload(0xf40), f_q)) +mstore(0x4220, mulmod(mload(0x4200), mload(0xf40), f_q)) +mstore(0x4240, mulmod(mload(0x4220), mload(0xf40), f_q)) +mstore(0x4260, mulmod(mload(0x4240), mload(0xf40), f_q)) +mstore(0x4280, mulmod(mload(0x4260), mload(0xf40), f_q)) +mstore(0x42a0, mulmod(mload(0x4280), mload(0xf40), f_q)) +mstore(0x42c0, mulmod(mload(0x42a0), mload(0xf40), f_q)) +mstore(0x42e0, mulmod(mload(0x42c0), mload(0xf40), f_q)) +mstore(0x4300, mulmod(mload(0x42e0), mload(0xf40), f_q)) +mstore(0x4320, mulmod(mload(0x4300), mload(0xf40), f_q)) +mstore(0x4340, mulmod(mload(0x4320), mload(0xf40), f_q)) +mstore(0x4360, mulmod(mload(0x4340), mload(0xf40), f_q)) +mstore(0x4380, mulmod(mload(0x4360), mload(0xf40), f_q)) +mstore(0x43a0, mulmod(mload(0x4380), mload(0xf40), f_q)) +mstore(0x43c0, mulmod(mload(0x43a0), mload(0xf40), f_q)) +mstore(0x43e0, mulmod(mload(0x43c0), mload(0xf40), f_q)) +mstore(0x4400, mulmod(mload(0x43e0), mload(0xf40), f_q)) +mstore(0x4420, mulmod(mload(0xfa0), mload(0xfa0), f_q)) +mstore(0x4440, mulmod(mload(0x4420), mload(0xfa0), f_q)) +mstore(0x4460, mulmod(mload(0x4440), mload(0xfa0), f_q)) +mstore(0x4480, mulmod(mload(0x4460), mload(0xfa0), f_q)) +{ + let result := mulmod(mload(0x880), mload(0x39e0), f_q) +result := addmod(mulmod(mload(0x8a0), mload(0x3a00), f_q), result, f_q) +result := addmod(mulmod(mload(0x8c0), mload(0x3a20), f_q), result, f_q) +result := addmod(mulmod(mload(0x8e0), mload(0x3a40), f_q), result, f_q) +mstore(17568, result) + } +mstore(0x44c0, mulmod(mload(0x44a0), mload(0x3ee0), f_q)) +mstore(0x44e0, mulmod(sub(f_q, mload(0x44c0)), 1, f_q)) +{ + let result := mulmod(mload(0x900), mload(0x39e0), f_q) +result := addmod(mulmod(mload(0x920), mload(0x3a00), f_q), result, f_q) +result := addmod(mulmod(mload(0x940), mload(0x3a20), f_q), result, f_q) +result := addmod(mulmod(mload(0x960), mload(0x3a40), f_q), result, f_q) +mstore(17664, result) + } +mstore(0x4520, mulmod(mload(0x4500), mload(0x3ee0), f_q)) +mstore(0x4540, mulmod(sub(f_q, mload(0x4520)), mload(0xf40), f_q)) +mstore(0x4560, mulmod(1, mload(0xf40), f_q)) +mstore(0x4580, addmod(mload(0x44e0), mload(0x4540), f_q)) +{ + let result := mulmod(mload(0x980), mload(0x39e0), f_q) +result := addmod(mulmod(mload(0x9a0), mload(0x3a00), f_q), result, f_q) +result := addmod(mulmod(mload(0x9c0), mload(0x3a20), f_q), result, f_q) +result := addmod(mulmod(mload(0x9e0), mload(0x3a40), f_q), result, f_q) +mstore(17824, result) + } +mstore(0x45c0, mulmod(mload(0x45a0), mload(0x3ee0), f_q)) +mstore(0x45e0, mulmod(sub(f_q, mload(0x45c0)), mload(0x41e0), f_q)) +mstore(0x4600, mulmod(1, mload(0x41e0), f_q)) +mstore(0x4620, addmod(mload(0x4580), mload(0x45e0), f_q)) +{ + let result := mulmod(mload(0xa00), mload(0x39e0), f_q) +result := addmod(mulmod(mload(0xa20), mload(0x3a00), f_q), result, f_q) +result := addmod(mulmod(mload(0xa40), mload(0x3a20), f_q), result, f_q) +result := addmod(mulmod(mload(0xa60), mload(0x3a40), f_q), result, f_q) +mstore(17984, result) + } +mstore(0x4660, mulmod(mload(0x4640), mload(0x3ee0), f_q)) +mstore(0x4680, mulmod(sub(f_q, mload(0x4660)), mload(0x4200), f_q)) +mstore(0x46a0, mulmod(1, mload(0x4200), f_q)) +mstore(0x46c0, addmod(mload(0x4620), mload(0x4680), f_q)) +{ + let result := mulmod(mload(0xa80), mload(0x39e0), f_q) +result := addmod(mulmod(mload(0xaa0), mload(0x3a00), f_q), result, f_q) +result := addmod(mulmod(mload(0xac0), mload(0x3a20), f_q), result, f_q) +result := addmod(mulmod(mload(0xae0), mload(0x3a40), f_q), result, f_q) +mstore(18144, result) + } +mstore(0x4700, mulmod(mload(0x46e0), mload(0x3ee0), f_q)) +mstore(0x4720, mulmod(sub(f_q, mload(0x4700)), mload(0x4220), f_q)) +mstore(0x4740, mulmod(1, mload(0x4220), f_q)) +mstore(0x4760, addmod(mload(0x46c0), mload(0x4720), f_q)) +mstore(0x4780, mulmod(mload(0x4760), 1, f_q)) +mstore(0x47a0, mulmod(mload(0x4560), 1, f_q)) +mstore(0x47c0, mulmod(mload(0x4600), 1, f_q)) +mstore(0x47e0, mulmod(mload(0x46a0), 1, f_q)) +mstore(0x4800, mulmod(mload(0x4740), 1, f_q)) +mstore(0x4820, mulmod(1, mload(0x3f00), f_q)) +{ + let result := mulmod(mload(0xb00), mload(0x3ae0), f_q) +mstore(18496, result) + } +mstore(0x4860, mulmod(mload(0x4840), mload(0x4160), f_q)) +mstore(0x4880, mulmod(sub(f_q, mload(0x4860)), 1, f_q)) +mstore(0x48a0, mulmod(mload(0x4820), 1, f_q)) +{ + let result := mulmod(mload(0xf00), mload(0x3ae0), f_q) +mstore(18624, result) + } +mstore(0x48e0, mulmod(mload(0x48c0), mload(0x4160), f_q)) +mstore(0x4900, mulmod(sub(f_q, mload(0x48e0)), mload(0xf40), f_q)) +mstore(0x4920, mulmod(mload(0x4820), mload(0xf40), f_q)) +mstore(0x4940, addmod(mload(0x4880), mload(0x4900), f_q)) +{ + let result := mulmod(mload(0xb20), mload(0x3ae0), f_q) +mstore(18784, result) + } +mstore(0x4980, mulmod(mload(0x4960), mload(0x4160), f_q)) +mstore(0x49a0, mulmod(sub(f_q, mload(0x4980)), mload(0x41e0), f_q)) +mstore(0x49c0, mulmod(mload(0x4820), mload(0x41e0), f_q)) +mstore(0x49e0, addmod(mload(0x4940), mload(0x49a0), f_q)) +{ + let result := mulmod(mload(0xb40), mload(0x3ae0), f_q) +mstore(18944, result) + } +mstore(0x4a20, mulmod(mload(0x4a00), mload(0x4160), f_q)) +mstore(0x4a40, mulmod(sub(f_q, mload(0x4a20)), mload(0x4200), f_q)) +mstore(0x4a60, mulmod(mload(0x4820), mload(0x4200), f_q)) +mstore(0x4a80, addmod(mload(0x49e0), mload(0x4a40), f_q)) +{ + let result := mulmod(mload(0xb60), mload(0x3ae0), f_q) +mstore(19104, result) + } +mstore(0x4ac0, mulmod(mload(0x4aa0), mload(0x4160), f_q)) +mstore(0x4ae0, mulmod(sub(f_q, mload(0x4ac0)), mload(0x4220), f_q)) +mstore(0x4b00, mulmod(mload(0x4820), mload(0x4220), f_q)) +mstore(0x4b20, addmod(mload(0x4a80), mload(0x4ae0), f_q)) +{ + let result := mulmod(mload(0xb80), mload(0x3ae0), f_q) +mstore(19264, result) + } +mstore(0x4b60, mulmod(mload(0x4b40), mload(0x4160), f_q)) +mstore(0x4b80, mulmod(sub(f_q, mload(0x4b60)), mload(0x4240), f_q)) +mstore(0x4ba0, mulmod(mload(0x4820), mload(0x4240), f_q)) +mstore(0x4bc0, addmod(mload(0x4b20), mload(0x4b80), f_q)) +{ + let result := mulmod(mload(0xba0), mload(0x3ae0), f_q) +mstore(19424, result) + } +mstore(0x4c00, mulmod(mload(0x4be0), mload(0x4160), f_q)) +mstore(0x4c20, mulmod(sub(f_q, mload(0x4c00)), mload(0x4260), f_q)) +mstore(0x4c40, mulmod(mload(0x4820), mload(0x4260), f_q)) +mstore(0x4c60, addmod(mload(0x4bc0), mload(0x4c20), f_q)) +{ + let result := mulmod(mload(0xbc0), mload(0x3ae0), f_q) +mstore(19584, result) + } +mstore(0x4ca0, mulmod(mload(0x4c80), mload(0x4160), f_q)) +mstore(0x4cc0, mulmod(sub(f_q, mload(0x4ca0)), mload(0x4280), f_q)) +mstore(0x4ce0, mulmod(mload(0x4820), mload(0x4280), f_q)) +mstore(0x4d00, addmod(mload(0x4c60), mload(0x4cc0), f_q)) +{ + let result := mulmod(mload(0xbe0), mload(0x3ae0), f_q) +mstore(19744, result) + } +mstore(0x4d40, mulmod(mload(0x4d20), mload(0x4160), f_q)) +mstore(0x4d60, mulmod(sub(f_q, mload(0x4d40)), mload(0x42a0), f_q)) +mstore(0x4d80, mulmod(mload(0x4820), mload(0x42a0), f_q)) +mstore(0x4da0, addmod(mload(0x4d00), mload(0x4d60), f_q)) +{ + let result := mulmod(mload(0xc20), mload(0x3ae0), f_q) +mstore(19904, result) + } +mstore(0x4de0, mulmod(mload(0x4dc0), mload(0x4160), f_q)) +mstore(0x4e00, mulmod(sub(f_q, mload(0x4de0)), mload(0x42c0), f_q)) +mstore(0x4e20, mulmod(mload(0x4820), mload(0x42c0), f_q)) +mstore(0x4e40, addmod(mload(0x4da0), mload(0x4e00), f_q)) +{ + let result := mulmod(mload(0xc40), mload(0x3ae0), f_q) +mstore(20064, result) + } +mstore(0x4e80, mulmod(mload(0x4e60), mload(0x4160), f_q)) +mstore(0x4ea0, mulmod(sub(f_q, mload(0x4e80)), mload(0x42e0), f_q)) +mstore(0x4ec0, mulmod(mload(0x4820), mload(0x42e0), f_q)) +mstore(0x4ee0, addmod(mload(0x4e40), mload(0x4ea0), f_q)) +{ + let result := mulmod(mload(0xc60), mload(0x3ae0), f_q) +mstore(20224, result) + } +mstore(0x4f20, mulmod(mload(0x4f00), mload(0x4160), f_q)) +mstore(0x4f40, mulmod(sub(f_q, mload(0x4f20)), mload(0x4300), f_q)) +mstore(0x4f60, mulmod(mload(0x4820), mload(0x4300), f_q)) +mstore(0x4f80, addmod(mload(0x4ee0), mload(0x4f40), f_q)) +{ + let result := mulmod(mload(0xc80), mload(0x3ae0), f_q) +mstore(20384, result) + } +mstore(0x4fc0, mulmod(mload(0x4fa0), mload(0x4160), f_q)) +mstore(0x4fe0, mulmod(sub(f_q, mload(0x4fc0)), mload(0x4320), f_q)) +mstore(0x5000, mulmod(mload(0x4820), mload(0x4320), f_q)) +mstore(0x5020, addmod(mload(0x4f80), mload(0x4fe0), f_q)) +{ + let result := mulmod(mload(0xca0), mload(0x3ae0), f_q) +mstore(20544, result) + } +mstore(0x5060, mulmod(mload(0x5040), mload(0x4160), f_q)) +mstore(0x5080, mulmod(sub(f_q, mload(0x5060)), mload(0x4340), f_q)) +mstore(0x50a0, mulmod(mload(0x4820), mload(0x4340), f_q)) +mstore(0x50c0, addmod(mload(0x5020), mload(0x5080), f_q)) +{ + let result := mulmod(mload(0xcc0), mload(0x3ae0), f_q) +mstore(20704, result) + } +mstore(0x5100, mulmod(mload(0x50e0), mload(0x4160), f_q)) +mstore(0x5120, mulmod(sub(f_q, mload(0x5100)), mload(0x4360), f_q)) +mstore(0x5140, mulmod(mload(0x4820), mload(0x4360), f_q)) +mstore(0x5160, addmod(mload(0x50c0), mload(0x5120), f_q)) +{ + let result := mulmod(mload(0xce0), mload(0x3ae0), f_q) +mstore(20864, result) + } +mstore(0x51a0, mulmod(mload(0x5180), mload(0x4160), f_q)) +mstore(0x51c0, mulmod(sub(f_q, mload(0x51a0)), mload(0x4380), f_q)) +mstore(0x51e0, mulmod(mload(0x4820), mload(0x4380), f_q)) +mstore(0x5200, addmod(mload(0x5160), mload(0x51c0), f_q)) +{ + let result := mulmod(mload(0xd00), mload(0x3ae0), f_q) +mstore(21024, result) + } +mstore(0x5240, mulmod(mload(0x5220), mload(0x4160), f_q)) +mstore(0x5260, mulmod(sub(f_q, mload(0x5240)), mload(0x43a0), f_q)) +mstore(0x5280, mulmod(mload(0x4820), mload(0x43a0), f_q)) +mstore(0x52a0, addmod(mload(0x5200), mload(0x5260), f_q)) +mstore(0x52c0, mulmod(mload(0x37c0), mload(0x3f00), f_q)) +mstore(0x52e0, mulmod(mload(0x37e0), mload(0x3f00), f_q)) +{ + let result := mulmod(mload(0x3800), mload(0x3ae0), f_q) +mstore(21248, result) + } +mstore(0x5320, mulmod(mload(0x5300), mload(0x4160), f_q)) +mstore(0x5340, mulmod(sub(f_q, mload(0x5320)), mload(0x43c0), f_q)) +mstore(0x5360, mulmod(mload(0x4820), mload(0x43c0), f_q)) +mstore(0x5380, mulmod(mload(0x52c0), mload(0x43c0), f_q)) +mstore(0x53a0, mulmod(mload(0x52e0), mload(0x43c0), f_q)) +mstore(0x53c0, addmod(mload(0x52a0), mload(0x5340), f_q)) +{ + let result := mulmod(mload(0xc00), mload(0x3ae0), f_q) +mstore(21472, result) + } +mstore(0x5400, mulmod(mload(0x53e0), mload(0x4160), f_q)) +mstore(0x5420, mulmod(sub(f_q, mload(0x5400)), mload(0x43e0), f_q)) +mstore(0x5440, mulmod(mload(0x4820), mload(0x43e0), f_q)) +mstore(0x5460, addmod(mload(0x53c0), mload(0x5420), f_q)) +mstore(0x5480, mulmod(mload(0x5460), mload(0xfa0), f_q)) +mstore(0x54a0, mulmod(mload(0x48a0), mload(0xfa0), f_q)) +mstore(0x54c0, mulmod(mload(0x4920), mload(0xfa0), f_q)) +mstore(0x54e0, mulmod(mload(0x49c0), mload(0xfa0), f_q)) +mstore(0x5500, mulmod(mload(0x4a60), mload(0xfa0), f_q)) +mstore(0x5520, mulmod(mload(0x4b00), mload(0xfa0), f_q)) +mstore(0x5540, mulmod(mload(0x4ba0), mload(0xfa0), f_q)) +mstore(0x5560, mulmod(mload(0x4c40), mload(0xfa0), f_q)) +mstore(0x5580, mulmod(mload(0x4ce0), mload(0xfa0), f_q)) +mstore(0x55a0, mulmod(mload(0x4d80), mload(0xfa0), f_q)) +mstore(0x55c0, mulmod(mload(0x4e20), mload(0xfa0), f_q)) +mstore(0x55e0, mulmod(mload(0x4ec0), mload(0xfa0), f_q)) +mstore(0x5600, mulmod(mload(0x4f60), mload(0xfa0), f_q)) +mstore(0x5620, mulmod(mload(0x5000), mload(0xfa0), f_q)) +mstore(0x5640, mulmod(mload(0x50a0), mload(0xfa0), f_q)) +mstore(0x5660, mulmod(mload(0x5140), mload(0xfa0), f_q)) +mstore(0x5680, mulmod(mload(0x51e0), mload(0xfa0), f_q)) +mstore(0x56a0, mulmod(mload(0x5280), mload(0xfa0), f_q)) +mstore(0x56c0, mulmod(mload(0x5360), mload(0xfa0), f_q)) +mstore(0x56e0, mulmod(mload(0x5380), mload(0xfa0), f_q)) +mstore(0x5700, mulmod(mload(0x53a0), mload(0xfa0), f_q)) +mstore(0x5720, mulmod(mload(0x5440), mload(0xfa0), f_q)) +mstore(0x5740, addmod(mload(0x4780), mload(0x5480), f_q)) +mstore(0x5760, mulmod(1, mload(0x3f40), f_q)) +{ + let result := mulmod(mload(0xd20), mload(0x3b00), f_q) +result := addmod(mulmod(mload(0xd40), mload(0x3b20), f_q), result, f_q) +result := addmod(mulmod(mload(0xd60), mload(0x3b40), f_q), result, f_q) +mstore(22400, result) + } +mstore(0x57a0, mulmod(mload(0x5780), mload(0x4180), f_q)) +mstore(0x57c0, mulmod(sub(f_q, mload(0x57a0)), 1, f_q)) +mstore(0x57e0, mulmod(mload(0x5760), 1, f_q)) +{ + let result := mulmod(mload(0xd80), mload(0x3b00), f_q) +result := addmod(mulmod(mload(0xda0), mload(0x3b20), f_q), result, f_q) +result := addmod(mulmod(mload(0xdc0), mload(0x3b40), f_q), result, f_q) +mstore(22528, result) + } +mstore(0x5820, mulmod(mload(0x5800), mload(0x4180), f_q)) +mstore(0x5840, mulmod(sub(f_q, mload(0x5820)), mload(0xf40), f_q)) +mstore(0x5860, mulmod(mload(0x5760), mload(0xf40), f_q)) +mstore(0x5880, addmod(mload(0x57c0), mload(0x5840), f_q)) +{ + let result := mulmod(mload(0xde0), mload(0x3b00), f_q) +result := addmod(mulmod(mload(0xe00), mload(0x3b20), f_q), result, f_q) +result := addmod(mulmod(mload(0xe20), mload(0x3b40), f_q), result, f_q) +mstore(22688, result) + } +mstore(0x58c0, mulmod(mload(0x58a0), mload(0x4180), f_q)) +mstore(0x58e0, mulmod(sub(f_q, mload(0x58c0)), mload(0x41e0), f_q)) +mstore(0x5900, mulmod(mload(0x5760), mload(0x41e0), f_q)) +mstore(0x5920, addmod(mload(0x5880), mload(0x58e0), f_q)) +mstore(0x5940, mulmod(mload(0x5920), mload(0x4420), f_q)) +mstore(0x5960, mulmod(mload(0x57e0), mload(0x4420), f_q)) +mstore(0x5980, mulmod(mload(0x5860), mload(0x4420), f_q)) +mstore(0x59a0, mulmod(mload(0x5900), mload(0x4420), f_q)) +mstore(0x59c0, addmod(mload(0x5740), mload(0x5940), f_q)) +mstore(0x59e0, mulmod(1, mload(0x3f80), f_q)) +{ + let result := mulmod(mload(0xe40), mload(0x3b80), f_q) +result := addmod(mulmod(mload(0xe60), mload(0x3ba0), f_q), result, f_q) +mstore(23040, result) + } +mstore(0x5a20, mulmod(mload(0x5a00), mload(0x41a0), f_q)) +mstore(0x5a40, mulmod(sub(f_q, mload(0x5a20)), 1, f_q)) +mstore(0x5a60, mulmod(mload(0x59e0), 1, f_q)) +{ + let result := mulmod(mload(0xe80), mload(0x3b80), f_q) +result := addmod(mulmod(mload(0xea0), mload(0x3ba0), f_q), result, f_q) +mstore(23168, result) + } +mstore(0x5aa0, mulmod(mload(0x5a80), mload(0x41a0), f_q)) +mstore(0x5ac0, mulmod(sub(f_q, mload(0x5aa0)), mload(0xf40), f_q)) +mstore(0x5ae0, mulmod(mload(0x59e0), mload(0xf40), f_q)) +mstore(0x5b00, addmod(mload(0x5a40), mload(0x5ac0), f_q)) +mstore(0x5b20, mulmod(mload(0x5b00), mload(0x4440), f_q)) +mstore(0x5b40, mulmod(mload(0x5a60), mload(0x4440), f_q)) +mstore(0x5b60, mulmod(mload(0x5ae0), mload(0x4440), f_q)) +mstore(0x5b80, addmod(mload(0x59c0), mload(0x5b20), f_q)) +mstore(0x5ba0, mulmod(1, mload(0x3fc0), f_q)) +{ + let result := mulmod(mload(0xec0), mload(0x3bc0), f_q) +result := addmod(mulmod(mload(0xee0), mload(0x3be0), f_q), result, f_q) +mstore(23488, result) + } +mstore(0x5be0, mulmod(mload(0x5bc0), mload(0x41c0), f_q)) +mstore(0x5c00, mulmod(sub(f_q, mload(0x5be0)), 1, f_q)) +mstore(0x5c20, mulmod(mload(0x5ba0), 1, f_q)) +mstore(0x5c40, mulmod(mload(0x5c00), mload(0x4460), f_q)) +mstore(0x5c60, mulmod(mload(0x5c20), mload(0x4460), f_q)) +mstore(0x5c80, addmod(mload(0x5b80), mload(0x5c40), f_q)) +mstore(0x5ca0, mulmod(1, mload(0x3ac0), f_q)) +mstore(0x5cc0, mulmod(1, mload(0x1040), f_q)) +mstore(0x5ce0, 0x0000000000000000000000000000000000000000000000000000000000000001) + mstore(0x5d00, 0x0000000000000000000000000000000000000000000000000000000000000002) +mstore(0x5d20, mload(0x5c80)) +success := and(eq(staticcall(gas(), 0x7, 0x5ce0, 0x60, 0x5ce0, 0x40), 1), success) +mstore(0x5d40, mload(0x5ce0)) + mstore(0x5d60, mload(0x5d00)) +mstore(0x5d80, mload(0x260)) + mstore(0x5da0, mload(0x280)) +success := and(eq(staticcall(gas(), 0x6, 0x5d40, 0x80, 0x5d40, 0x40), 1), success) +mstore(0x5dc0, mload(0x2a0)) + mstore(0x5de0, mload(0x2c0)) +mstore(0x5e00, mload(0x47a0)) +success := and(eq(staticcall(gas(), 0x7, 0x5dc0, 0x60, 0x5dc0, 0x40), 1), success) +mstore(0x5e20, mload(0x5d40)) + mstore(0x5e40, mload(0x5d60)) +mstore(0x5e60, mload(0x5dc0)) + mstore(0x5e80, mload(0x5de0)) +success := and(eq(staticcall(gas(), 0x6, 0x5e20, 0x80, 0x5e20, 0x40), 1), success) +mstore(0x5ea0, mload(0x2e0)) + mstore(0x5ec0, mload(0x300)) +mstore(0x5ee0, mload(0x47c0)) +success := and(eq(staticcall(gas(), 0x7, 0x5ea0, 0x60, 0x5ea0, 0x40), 1), success) +mstore(0x5f00, mload(0x5e20)) + mstore(0x5f20, mload(0x5e40)) +mstore(0x5f40, mload(0x5ea0)) + mstore(0x5f60, mload(0x5ec0)) +success := and(eq(staticcall(gas(), 0x6, 0x5f00, 0x80, 0x5f00, 0x40), 1), success) +mstore(0x5f80, mload(0x320)) + mstore(0x5fa0, mload(0x340)) +mstore(0x5fc0, mload(0x47e0)) +success := and(eq(staticcall(gas(), 0x7, 0x5f80, 0x60, 0x5f80, 0x40), 1), success) +mstore(0x5fe0, mload(0x5f00)) + mstore(0x6000, mload(0x5f20)) +mstore(0x6020, mload(0x5f80)) + mstore(0x6040, mload(0x5fa0)) +success := and(eq(staticcall(gas(), 0x6, 0x5fe0, 0x80, 0x5fe0, 0x40), 1), success) +mstore(0x6060, mload(0x360)) + mstore(0x6080, mload(0x380)) +mstore(0x60a0, mload(0x4800)) +success := and(eq(staticcall(gas(), 0x7, 0x6060, 0x60, 0x6060, 0x40), 1), success) +mstore(0x60c0, mload(0x5fe0)) + mstore(0x60e0, mload(0x6000)) +mstore(0x6100, mload(0x6060)) + mstore(0x6120, mload(0x6080)) +success := and(eq(staticcall(gas(), 0x6, 0x60c0, 0x80, 0x60c0, 0x40), 1), success) +mstore(0x6140, mload(0x3a0)) + mstore(0x6160, mload(0x3c0)) +mstore(0x6180, mload(0x54a0)) +success := and(eq(staticcall(gas(), 0x7, 0x6140, 0x60, 0x6140, 0x40), 1), success) +mstore(0x61a0, mload(0x60c0)) + mstore(0x61c0, mload(0x60e0)) +mstore(0x61e0, mload(0x6140)) + mstore(0x6200, mload(0x6160)) +success := and(eq(staticcall(gas(), 0x6, 0x61a0, 0x80, 0x61a0, 0x40), 1), success) +mstore(0x6220, mload(0x480)) + mstore(0x6240, mload(0x4a0)) +mstore(0x6260, mload(0x54c0)) +success := and(eq(staticcall(gas(), 0x7, 0x6220, 0x60, 0x6220, 0x40), 1), success) +mstore(0x6280, mload(0x61a0)) + mstore(0x62a0, mload(0x61c0)) +mstore(0x62c0, mload(0x6220)) + mstore(0x62e0, mload(0x6240)) +success := and(eq(staticcall(gas(), 0x6, 0x6280, 0x80, 0x6280, 0x40), 1), success) +mstore(0x6300, 0x1f224f5998f14bc19a1fc8ab9271b179aa55c5c2188c466029c96038ef96993e) + mstore(0x6320, 0x051ac90c8a88cbae8df335e747936e9567931d8b470dec40c0fc3b4d510e4973) +mstore(0x6340, mload(0x54e0)) +success := and(eq(staticcall(gas(), 0x7, 0x6300, 0x60, 0x6300, 0x40), 1), success) +mstore(0x6360, mload(0x6280)) + mstore(0x6380, mload(0x62a0)) +mstore(0x63a0, mload(0x6300)) + mstore(0x63c0, mload(0x6320)) +success := and(eq(staticcall(gas(), 0x6, 0x6360, 0x80, 0x6360, 0x40), 1), success) +mstore(0x63e0, 0x04528ec7365a2881b7d3c8925570e06bb3b17f04f6a95384ac8ed19a30c12097) + mstore(0x6400, 0x28d1ef470a8a5278ad6d2eb9047ad7e93024113f543b06870f1bbea7177db404) +mstore(0x6420, mload(0x5500)) +success := and(eq(staticcall(gas(), 0x7, 0x63e0, 0x60, 0x63e0, 0x40), 1), success) +mstore(0x6440, mload(0x6360)) + mstore(0x6460, mload(0x6380)) +mstore(0x6480, mload(0x63e0)) + mstore(0x64a0, mload(0x6400)) +success := and(eq(staticcall(gas(), 0x6, 0x6440, 0x80, 0x6440, 0x40), 1), success) +mstore(0x64c0, 0x148da7a45ae1351d24cb90fac1678751b258a5aff7b437f9183860716b066d1e) + mstore(0x64e0, 0x083fe9b9175e0b464d258a01327e37688eea7a94859457f95cfc50edf15e7d37) +mstore(0x6500, mload(0x5520)) +success := and(eq(staticcall(gas(), 0x7, 0x64c0, 0x60, 0x64c0, 0x40), 1), success) +mstore(0x6520, mload(0x6440)) + mstore(0x6540, mload(0x6460)) +mstore(0x6560, mload(0x64c0)) + mstore(0x6580, mload(0x64e0)) +success := and(eq(staticcall(gas(), 0x6, 0x6520, 0x80, 0x6520, 0x40), 1), success) +mstore(0x65a0, 0x2c89ef76f1ff53371da882bd1f56419409140a67f6e43f9cb6b8e3dc290f3d32) + mstore(0x65c0, 0x092b7306ff29f079a954b0e67cd377246de2fda9e67852ac4ac9b239380844d7) +mstore(0x65e0, mload(0x5540)) +success := and(eq(staticcall(gas(), 0x7, 0x65a0, 0x60, 0x65a0, 0x40), 1), success) +mstore(0x6600, mload(0x6520)) + mstore(0x6620, mload(0x6540)) +mstore(0x6640, mload(0x65a0)) + mstore(0x6660, mload(0x65c0)) +success := and(eq(staticcall(gas(), 0x6, 0x6600, 0x80, 0x6600, 0x40), 1), success) +mstore(0x6680, 0x264ba2649108dc04cf91bfe5ce7f9e39718ef3489c3a63af4434ef95d78947d3) + mstore(0x66a0, 0x1f7b8d6de9ff44a2b5ce79cf4d428ae53d24106c5a8b496018d4527c988ccaa8) +mstore(0x66c0, mload(0x5560)) +success := and(eq(staticcall(gas(), 0x7, 0x6680, 0x60, 0x6680, 0x40), 1), success) +mstore(0x66e0, mload(0x6600)) + mstore(0x6700, mload(0x6620)) +mstore(0x6720, mload(0x6680)) + mstore(0x6740, mload(0x66a0)) +success := and(eq(staticcall(gas(), 0x6, 0x66e0, 0x80, 0x66e0, 0x40), 1), success) +mstore(0x6760, 0x01c33b5a6e9a4bd6d333d8cebc27b12269a07577f863423cb21af26f8882fb1b) + mstore(0x6780, 0x13ec8460a262474074812c8d0013ae3348c30cd29d9f2bf0b7544af011c7e86c) +mstore(0x67a0, mload(0x5580)) +success := and(eq(staticcall(gas(), 0x7, 0x6760, 0x60, 0x6760, 0x40), 1), success) +mstore(0x67c0, mload(0x66e0)) + mstore(0x67e0, mload(0x6700)) +mstore(0x6800, mload(0x6760)) + mstore(0x6820, mload(0x6780)) +success := and(eq(staticcall(gas(), 0x6, 0x67c0, 0x80, 0x67c0, 0x40), 1), success) +mstore(0x6840, 0x138ca2ca05c4c86af016dce6765689ad133c465f3ae958265217d6f3bf956096) + mstore(0x6860, 0x27df80ac3bfaa603862f1baee6eb9f3da5e7995e5def2f3f836802ff4abbcfc7) +mstore(0x6880, mload(0x55a0)) +success := and(eq(staticcall(gas(), 0x7, 0x6840, 0x60, 0x6840, 0x40), 1), success) +mstore(0x68a0, mload(0x67c0)) + mstore(0x68c0, mload(0x67e0)) +mstore(0x68e0, mload(0x6840)) + mstore(0x6900, mload(0x6860)) +success := and(eq(staticcall(gas(), 0x6, 0x68a0, 0x80, 0x68a0, 0x40), 1), success) +mstore(0x6920, 0x13e0e546075988f7a28874395078b73892d877975ff57cf00aa6316aa5abd52d) + mstore(0x6940, 0x10c6b7871774e2609ecc62b7cf0a80fa3594d43705cc239619b3ea3bcfd30829) +mstore(0x6960, mload(0x55c0)) +success := and(eq(staticcall(gas(), 0x7, 0x6920, 0x60, 0x6920, 0x40), 1), success) +mstore(0x6980, mload(0x68a0)) + mstore(0x69a0, mload(0x68c0)) +mstore(0x69c0, mload(0x6920)) + mstore(0x69e0, mload(0x6940)) +success := and(eq(staticcall(gas(), 0x6, 0x6980, 0x80, 0x6980, 0x40), 1), success) +mstore(0x6a00, 0x246e7ca9b83c0f5457b0733911aef2997251e5af5f1144016162f307f8a69596) + mstore(0x6a20, 0x02000973e02faa177b17b846e7b1a2ba349c22c20badb7da24299a175d5b43ec) +mstore(0x6a40, mload(0x55e0)) +success := and(eq(staticcall(gas(), 0x7, 0x6a00, 0x60, 0x6a00, 0x40), 1), success) +mstore(0x6a60, mload(0x6980)) + mstore(0x6a80, mload(0x69a0)) +mstore(0x6aa0, mload(0x6a00)) + mstore(0x6ac0, mload(0x6a20)) +success := and(eq(staticcall(gas(), 0x6, 0x6a60, 0x80, 0x6a60, 0x40), 1), success) +mstore(0x6ae0, 0x290ffddf7e57e7249144c7e2a528da0ae6bc012eadcfcf530f101416a002b3f1) + mstore(0x6b00, 0x121773be2cc80e3dbfb20e8d8eb60617a5d2a4bbd5cbef21593ff1da7e7695fd) +mstore(0x6b20, mload(0x5600)) +success := and(eq(staticcall(gas(), 0x7, 0x6ae0, 0x60, 0x6ae0, 0x40), 1), success) +mstore(0x6b40, mload(0x6a60)) + mstore(0x6b60, mload(0x6a80)) +mstore(0x6b80, mload(0x6ae0)) + mstore(0x6ba0, mload(0x6b00)) +success := and(eq(staticcall(gas(), 0x6, 0x6b40, 0x80, 0x6b40, 0x40), 1), success) +mstore(0x6bc0, 0x2ece498dabef6ec6074c536e36548f87384a81b09b17745e1848e3fe41046ed8) + mstore(0x6be0, 0x25742093fb504498c37f80b511cde8e51398711b35ffc1a56b1bf2f155ea1f54) +mstore(0x6c00, mload(0x5620)) +success := and(eq(staticcall(gas(), 0x7, 0x6bc0, 0x60, 0x6bc0, 0x40), 1), success) +mstore(0x6c20, mload(0x6b40)) + mstore(0x6c40, mload(0x6b60)) +mstore(0x6c60, mload(0x6bc0)) + mstore(0x6c80, mload(0x6be0)) +success := and(eq(staticcall(gas(), 0x6, 0x6c20, 0x80, 0x6c20, 0x40), 1), success) +mstore(0x6ca0, 0x1eaf3b78ae89964c57cbb6ffd74ae258c84106fcfacdf0b34cfb7f0517c83d34) + mstore(0x6cc0, 0x03fc9b6fb73e4108bb2725c51e3243cb49d65e30692b1104511477c67a64ff3e) +mstore(0x6ce0, mload(0x5640)) +success := and(eq(staticcall(gas(), 0x7, 0x6ca0, 0x60, 0x6ca0, 0x40), 1), success) +mstore(0x6d00, mload(0x6c20)) + mstore(0x6d20, mload(0x6c40)) +mstore(0x6d40, mload(0x6ca0)) + mstore(0x6d60, mload(0x6cc0)) +success := and(eq(staticcall(gas(), 0x6, 0x6d00, 0x80, 0x6d00, 0x40), 1), success) +mstore(0x6d80, 0x211c7ebb676fb55bf5bf70d25c8099113588fa46f9487b31c80fdd19c99c56b6) + mstore(0x6da0, 0x28c5339caf705e65ce8437725dafa12804573290575791b19eceea6aef10c8a8) +mstore(0x6dc0, mload(0x5660)) +success := and(eq(staticcall(gas(), 0x7, 0x6d80, 0x60, 0x6d80, 0x40), 1), success) +mstore(0x6de0, mload(0x6d00)) + mstore(0x6e00, mload(0x6d20)) +mstore(0x6e20, mload(0x6d80)) + mstore(0x6e40, mload(0x6da0)) +success := and(eq(staticcall(gas(), 0x6, 0x6de0, 0x80, 0x6de0, 0x40), 1), success) +mstore(0x6e60, 0x0ece099bd367af0715b4d47e7bc614bfe9a9f45c51f9969d4b104f4dcb0c279f) + mstore(0x6e80, 0x0c7702bd2b7cafe83d79c152ef77a1661a50c4331f3458377a383a8ac68bb8ae) +mstore(0x6ea0, mload(0x5680)) +success := and(eq(staticcall(gas(), 0x7, 0x6e60, 0x60, 0x6e60, 0x40), 1), success) +mstore(0x6ec0, mload(0x6de0)) + mstore(0x6ee0, mload(0x6e00)) +mstore(0x6f00, mload(0x6e60)) + mstore(0x6f20, mload(0x6e80)) +success := and(eq(staticcall(gas(), 0x6, 0x6ec0, 0x80, 0x6ec0, 0x40), 1), success) +mstore(0x6f40, 0x131587b948c9547bfb0ece7b4031b60b72c7a08bd30b600d57b831f772955941) + mstore(0x6f60, 0x0967910d6f98d32bfa6d7debd908f5b9a3e084269b7116d76d4a0b7bf4fa8758) +mstore(0x6f80, mload(0x56a0)) +success := and(eq(staticcall(gas(), 0x7, 0x6f40, 0x60, 0x6f40, 0x40), 1), success) +mstore(0x6fa0, mload(0x6ec0)) + mstore(0x6fc0, mload(0x6ee0)) +mstore(0x6fe0, mload(0x6f40)) + mstore(0x7000, mload(0x6f60)) +success := and(eq(staticcall(gas(), 0x6, 0x6fa0, 0x80, 0x6fa0, 0x40), 1), success) +mstore(0x7020, mload(0x760)) + mstore(0x7040, mload(0x780)) +mstore(0x7060, mload(0x56c0)) +success := and(eq(staticcall(gas(), 0x7, 0x7020, 0x60, 0x7020, 0x40), 1), success) +mstore(0x7080, mload(0x6fa0)) + mstore(0x70a0, mload(0x6fc0)) +mstore(0x70c0, mload(0x7020)) + mstore(0x70e0, mload(0x7040)) +success := and(eq(staticcall(gas(), 0x6, 0x7080, 0x80, 0x7080, 0x40), 1), success) +mstore(0x7100, mload(0x7a0)) + mstore(0x7120, mload(0x7c0)) +mstore(0x7140, mload(0x56e0)) +success := and(eq(staticcall(gas(), 0x7, 0x7100, 0x60, 0x7100, 0x40), 1), success) +mstore(0x7160, mload(0x7080)) + mstore(0x7180, mload(0x70a0)) +mstore(0x71a0, mload(0x7100)) + mstore(0x71c0, mload(0x7120)) +success := and(eq(staticcall(gas(), 0x6, 0x7160, 0x80, 0x7160, 0x40), 1), success) +mstore(0x71e0, mload(0x7e0)) + mstore(0x7200, mload(0x800)) +mstore(0x7220, mload(0x5700)) +success := and(eq(staticcall(gas(), 0x7, 0x71e0, 0x60, 0x71e0, 0x40), 1), success) +mstore(0x7240, mload(0x7160)) + mstore(0x7260, mload(0x7180)) +mstore(0x7280, mload(0x71e0)) + mstore(0x72a0, mload(0x7200)) +success := and(eq(staticcall(gas(), 0x6, 0x7240, 0x80, 0x7240, 0x40), 1), success) +mstore(0x72c0, mload(0x6c0)) + mstore(0x72e0, mload(0x6e0)) +mstore(0x7300, mload(0x5720)) +success := and(eq(staticcall(gas(), 0x7, 0x72c0, 0x60, 0x72c0, 0x40), 1), success) +mstore(0x7320, mload(0x7240)) + mstore(0x7340, mload(0x7260)) +mstore(0x7360, mload(0x72c0)) + mstore(0x7380, mload(0x72e0)) +success := and(eq(staticcall(gas(), 0x6, 0x7320, 0x80, 0x7320, 0x40), 1), success) +mstore(0x73a0, mload(0x580)) + mstore(0x73c0, mload(0x5a0)) +mstore(0x73e0, mload(0x5960)) +success := and(eq(staticcall(gas(), 0x7, 0x73a0, 0x60, 0x73a0, 0x40), 1), success) +mstore(0x7400, mload(0x7320)) + mstore(0x7420, mload(0x7340)) +mstore(0x7440, mload(0x73a0)) + mstore(0x7460, mload(0x73c0)) +success := and(eq(staticcall(gas(), 0x6, 0x7400, 0x80, 0x7400, 0x40), 1), success) +mstore(0x7480, mload(0x5c0)) + mstore(0x74a0, mload(0x5e0)) +mstore(0x74c0, mload(0x5980)) +success := and(eq(staticcall(gas(), 0x7, 0x7480, 0x60, 0x7480, 0x40), 1), success) +mstore(0x74e0, mload(0x7400)) + mstore(0x7500, mload(0x7420)) +mstore(0x7520, mload(0x7480)) + mstore(0x7540, mload(0x74a0)) +success := and(eq(staticcall(gas(), 0x6, 0x74e0, 0x80, 0x74e0, 0x40), 1), success) +mstore(0x7560, mload(0x600)) + mstore(0x7580, mload(0x620)) +mstore(0x75a0, mload(0x59a0)) +success := and(eq(staticcall(gas(), 0x7, 0x7560, 0x60, 0x7560, 0x40), 1), success) +mstore(0x75c0, mload(0x74e0)) + mstore(0x75e0, mload(0x7500)) +mstore(0x7600, mload(0x7560)) + mstore(0x7620, mload(0x7580)) +success := and(eq(staticcall(gas(), 0x6, 0x75c0, 0x80, 0x75c0, 0x40), 1), success) +mstore(0x7640, mload(0x640)) + mstore(0x7660, mload(0x660)) +mstore(0x7680, mload(0x5b40)) +success := and(eq(staticcall(gas(), 0x7, 0x7640, 0x60, 0x7640, 0x40), 1), success) +mstore(0x76a0, mload(0x75c0)) + mstore(0x76c0, mload(0x75e0)) +mstore(0x76e0, mload(0x7640)) + mstore(0x7700, mload(0x7660)) +success := and(eq(staticcall(gas(), 0x6, 0x76a0, 0x80, 0x76a0, 0x40), 1), success) +mstore(0x7720, mload(0x680)) + mstore(0x7740, mload(0x6a0)) +mstore(0x7760, mload(0x5b60)) +success := and(eq(staticcall(gas(), 0x7, 0x7720, 0x60, 0x7720, 0x40), 1), success) +mstore(0x7780, mload(0x76a0)) + mstore(0x77a0, mload(0x76c0)) +mstore(0x77c0, mload(0x7720)) + mstore(0x77e0, mload(0x7740)) +success := and(eq(staticcall(gas(), 0x6, 0x7780, 0x80, 0x7780, 0x40), 1), success) +mstore(0x7800, mload(0x440)) + mstore(0x7820, mload(0x460)) +mstore(0x7840, mload(0x5c60)) +success := and(eq(staticcall(gas(), 0x7, 0x7800, 0x60, 0x7800, 0x40), 1), success) +mstore(0x7860, mload(0x7780)) + mstore(0x7880, mload(0x77a0)) +mstore(0x78a0, mload(0x7800)) + mstore(0x78c0, mload(0x7820)) +success := and(eq(staticcall(gas(), 0x6, 0x7860, 0x80, 0x7860, 0x40), 1), success) +mstore(0x78e0, mload(0xfe0)) + mstore(0x7900, mload(0x1000)) +mstore(0x7920, sub(f_q, mload(0x5ca0))) +success := and(eq(staticcall(gas(), 0x7, 0x78e0, 0x60, 0x78e0, 0x40), 1), success) +mstore(0x7940, mload(0x7860)) + mstore(0x7960, mload(0x7880)) +mstore(0x7980, mload(0x78e0)) + mstore(0x79a0, mload(0x7900)) +success := and(eq(staticcall(gas(), 0x6, 0x7940, 0x80, 0x7940, 0x40), 1), success) +mstore(0x79c0, mload(0x1080)) + mstore(0x79e0, mload(0x10a0)) +mstore(0x7a00, mload(0x5cc0)) +success := and(eq(staticcall(gas(), 0x7, 0x79c0, 0x60, 0x79c0, 0x40), 1), success) +mstore(0x7a20, mload(0x7940)) + mstore(0x7a40, mload(0x7960)) +mstore(0x7a60, mload(0x79c0)) + mstore(0x7a80, mload(0x79e0)) +success := and(eq(staticcall(gas(), 0x6, 0x7a20, 0x80, 0x7a20, 0x40), 1), success) +mstore(0x7aa0, mload(0x7a20)) + mstore(0x7ac0, mload(0x7a40)) +mstore(0x7ae0, mload(0x1080)) + mstore(0x7b00, mload(0x10a0)) +mstore(0x7b20, mload(0x10c0)) + mstore(0x7b40, mload(0x10e0)) +mstore(0x7b60, mload(0x1100)) + mstore(0x7b80, mload(0x1120)) +mstore(0x7ba0, keccak256(0x7aa0, 256)) +mstore(31680, mod(mload(31648), f_q)) +mstore(0x7be0, mulmod(mload(0x7bc0), mload(0x7bc0), f_q)) +mstore(0x7c00, mulmod(1, mload(0x7bc0), f_q)) +mstore(0x7c20, mload(0x7b20)) + mstore(0x7c40, mload(0x7b40)) +mstore(0x7c60, mload(0x7c00)) +success := and(eq(staticcall(gas(), 0x7, 0x7c20, 0x60, 0x7c20, 0x40), 1), success) +mstore(0x7c80, mload(0x7aa0)) + mstore(0x7ca0, mload(0x7ac0)) +mstore(0x7cc0, mload(0x7c20)) + mstore(0x7ce0, mload(0x7c40)) +success := and(eq(staticcall(gas(), 0x6, 0x7c80, 0x80, 0x7c80, 0x40), 1), success) +mstore(0x7d00, mload(0x7b60)) + mstore(0x7d20, mload(0x7b80)) +mstore(0x7d40, mload(0x7c00)) +success := and(eq(staticcall(gas(), 0x7, 0x7d00, 0x60, 0x7d00, 0x40), 1), success) +mstore(0x7d60, mload(0x7ae0)) + mstore(0x7d80, mload(0x7b00)) +mstore(0x7da0, mload(0x7d00)) + mstore(0x7dc0, mload(0x7d20)) +success := and(eq(staticcall(gas(), 0x6, 0x7d60, 0x80, 0x7d60, 0x40), 1), success) +mstore(0x7de0, mload(0x7c80)) + mstore(0x7e00, mload(0x7ca0)) +mstore(0x7e20, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) + mstore(0x7e40, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) + mstore(0x7e60, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) + mstore(0x7e80, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) +mstore(0x7ea0, mload(0x7d60)) + mstore(0x7ec0, mload(0x7d80)) +mstore(0x7ee0, 0x0181624e80f3d6ae28df7e01eaeab1c0e919877a3b8a6b7fbc69a6817d596ea2) + mstore(0x7f00, 0x1783d30dcb12d259bb89098addf6280fa4b653be7a152542a28f7b926e27e648) + mstore(0x7f20, 0x00ae44489d41a0d179e2dfdc03bddd883b7109f8b6ae316a59e815c1a6b35304) + mstore(0x7f40, 0x0b2147ab62a386bd63e6de1522109b8c9588ab466f5aadfde8c41ca3749423ee) +success := and(eq(staticcall(gas(), 0x8, 0x7de0, 0x180, 0x7de0, 0x20), 1), success) +success := and(eq(mload(0x7de0), 1), success) + + if not(success) { revert(0, 0) } + return(0, 0) + + } + } + } \ No newline at end of file diff --git a/axiom-eth/data/tx_receipts/task.goerli.json b/axiom-eth/data/tx_receipts/task.goerli.json new file mode 100644 index 00000000..5afac6fa --- /dev/null +++ b/axiom-eth/data/tx_receipts/task.goerli.json @@ -0,0 +1,128 @@ +{ + "txQueries": [ + { + "txHash": "0x9ba6df3200fe8d62103ede64a32a2475e4c7f992fe5e8ea7f08a490edf32d48d", + "fieldIdx": 3 + }, + { + "txHash": "0x684dcb11fdc89ee4568b03dc9c165e585c552a3361f6c5813418bc625cb76932", + "fieldIdx": 7 + } + ], + "receiptQueries": [ + { + "txHash": "0x1320047d683efdf132b7939418a9f0061f9dfd7348a2f347f32745fb46cb6ec6", + "fieldIdx": 0, + "logIdx": 0 + }, + { + "txHash": "0x1320047d683efdf132b7939418a9f0061f9dfd7348a2f347f32745fb46cb6ec6", + "fieldIdx": 3, + "logIdx": 0 + } + ], + "mmr": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x9001a1b1b89def70442c874a9056d7bdfd7f241547aa6fe0d11d001770654894", + "0x2caa414cfbca54154d3a8815f9e1a6d410f4e1d0f76df14f9eecebb0ea05b59d", + "0x33d65445a72205b92572a182e3867c9c66fca3a086d02dda622122df063b38c8", + "0xdde7f249a13c0fef92a9aa7abd559586732c0398241813253fcf07d956859bce", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xf6bcd969602fef0c36ec84ee377e592ec3b5311534ab3c1efcf0646acd96eead" + ], + "txMmrProofs": [ + [ + "0xe3b171a6a94d9a3eb8705be2027dd1b7f36155af525d9f1c42e4fc3aeb6415a5", + "0xfd9d4ed5748eb546fcec653502f773e2571ba7aa34ee7e62ff8c475d69bff6ec", + "0x24f4542a34fe0deef6bfe0242023d54436de7e608ba96fdf3dcc8fda20b91f6b", + "0xa9e0c8a16b1a4bff904c80cbb51997564d88fda9cd22da5794a6eec2d16e9bae", + "0x125a3020dedca37a6c42c96c8163390e7320a6dfbcea705b6a8e049f7d64f4ef", + "0x19f311181477e59a986732cc488ee0f52e11bb239831f767172c69badf0f4b0e", + "0x2ca98b1cdde7a16482691c899177f93f908caf04950aef620155b5ce5e26d543", + "0xaad44d06475f108d50b42ff6295acc614063171ad482c005e773367870cc3710", + "0x98ea01437632be717454c461d9510e868a00801d32ca236b1c96fba0b10c58a9", + "0x34626e1dcd0630fc5d9e62df2be2c29c6174e2931d96745718237efdbbb153bf", + "0x6ed4b0b5107fc60f8f45d887a82762585ca552ab893720a4e0e1c47c2d199b54", + "0x88faddf76a438510c102c9b2da1d5a3e0e9a69b5f1b1a9d23c6c94f26471d0a1", + "0xe900295c9ec37bdbeb3abda8bd371a434299bc3edf044020eccb5ef761ff5bc1", + "0xd523c754baccd7d9f7d9a35d43157b2bb1f93ca76815a517e8a91cc888b2701c", + "0x67ed5a40bef84013461f6d1bba4a5327d9e3fdc3a8bb037b620d6bde2a14c013", + "0x664cc20d84db67636e15b015613aa033984035060bb132d1d27bf30eb0354f68", + "0x6cc5504083b2c00e4d8fa153cfc095ab1ea72156678c61fa19658becbd2b622e", + "0xb1749f505305caf01d0096ea232f9d1f9a4fbaa9bdf10b8b50c31ff802ef757d" + ], + [ + "0xbd12800a021e3f70cefe7ac301cbb0c61a7076f13fcd735e3acebabba877b9f2", + "0x91f271ed7f8994119bbe33ad0faf89d9f51e082a4b4202da6f5c3b94ee641874", + "0x0009a588763e0c0fad4f5cfa630c860dec550f71e60e6d95103697b0b4af3713", + "0x6a925ce5509b4f31367775e8cfa2fcd678bcd6f2f6c17228758c38f6f930b894", + "0x4e6fc1a7b0a16583b601eac12869b5cf04095bec54af60b57a8539ba7c997e6d", + "0x0be1bdfd1519c802082e1f432ebcff9de6c6d019981ca53907eb66a7fabbd893", + "0x8a7e2dbe3865a5d51cf0b9b01f55233df9887f8769c7093e48b638912d14099a", + "0xa87b80fda7e258142e56541b3ac39155a7b2b8ebc50b37bb4fb6af0bbbe99b1e", + "0x6f46cbb147939955a6902d02026a2c87b51bb5a5b428060ebed2285a65a1ffde", + "0x9c07be5f69fd6a38195fe1c95c53b90e7ac844b3a08e86cea98181253b2a7566", + "0x0c05fe92c25adbb248c3315c9607dc4c0a5effa547fe8c5b25b59a34892ff53e", + "0x6bdf9c5b572dea2a8b2754adefe9e00d42b59aa970156ec0997eaf91b17f5a7c", + "0xb424aa1d27b48cee75c37d2cd13749f6095c7c86435080e8cfdeca97e13383ce", + "0xec3c6c5338ca0bddd0c7925aef2835b9ffb2ac4ac2b0d5f5ce38f59f06d387c7", + "0xa01781a978c22545f26063dd0a8f0a7c052b19e1f3706169c1b71d627a7ea13c", + "0xf8d8f6319c2dd17fb421463eb8d0c86881d0afaf8e08e95c49b5a9068727cae7" + ] + ], + "receiptMmrProofs": [ + [ + "0xf7c9fdba1d27d4d66b2085ef041e97c5b9bddf0262f03cb75fff4ae5342da7fc", + "0xf01d543b87c11402616226836455ed3b03a00b9c3feebb478eb9e5a97be07392", + "0x4bd8b09a6fd10f09f920c3aa6aab942d63d55fdd1f093301b5fcb8f7fd59ce8a", + "0x79423f92140f3e6698b8d38394170fcd19c452282674c30835edf7c06dff49ef", + "0x4c39bed02253ac79744c1ff0134c82eecda62341048e9edc7f48caa0b06d2d1e", + "0x28805385c643dfc4f15251f848d32fe536cccc605b6f36312f8f1210c88bc4e5", + "0x3b14201f7ea94e76ec305487b1c5e3ec56715eafbe57a5ea3253b75f3625a772", + "0x22ac80faa27cba53fc2ca042902eb8c55d0b3599294ee004186a962ca8d9cc6f", + "0x73bc39a9b31be50ecdf05fa2b8e1826a72c59abffdbe010f135e0a4089440603", + "0xbb5e3fb8c83f0237baa615239a5372c98c9efd2c3a41d289b6ce134ee7a3bc50", + "0x4be9154bca6cb87ee928b0cc41bf29b1043ba1265fde3b2dad297d4d2b198f29", + "0x464ec85779dcb2a11b64e28a6aada9563b9a24803cf0250bf073768ed75e1c5f", + "0xe76931d3a2a46f459cf169a0e0e6d44af220c98835a9fd406e82ef9b167fffa1", + "0x7faeb24db369709c70ae2c41259d2216bc320e9dc29ee0d7e358b18a375ca113", + "0xdf96d84651c4ed2a01bad7d2ff2a465bf334587adcfdeec26ebc5104bff2e4d5", + "0xf8d8f6319c2dd17fb421463eb8d0c86881d0afaf8e08e95c49b5a9068727cae7" + ], + [ + "0xf7c9fdba1d27d4d66b2085ef041e97c5b9bddf0262f03cb75fff4ae5342da7fc", + "0xf01d543b87c11402616226836455ed3b03a00b9c3feebb478eb9e5a97be07392", + "0x4bd8b09a6fd10f09f920c3aa6aab942d63d55fdd1f093301b5fcb8f7fd59ce8a", + "0x79423f92140f3e6698b8d38394170fcd19c452282674c30835edf7c06dff49ef", + "0x4c39bed02253ac79744c1ff0134c82eecda62341048e9edc7f48caa0b06d2d1e", + "0x28805385c643dfc4f15251f848d32fe536cccc605b6f36312f8f1210c88bc4e5", + "0x3b14201f7ea94e76ec305487b1c5e3ec56715eafbe57a5ea3253b75f3625a772", + "0x22ac80faa27cba53fc2ca042902eb8c55d0b3599294ee004186a962ca8d9cc6f", + "0x73bc39a9b31be50ecdf05fa2b8e1826a72c59abffdbe010f135e0a4089440603", + "0xbb5e3fb8c83f0237baa615239a5372c98c9efd2c3a41d289b6ce134ee7a3bc50", + "0x4be9154bca6cb87ee928b0cc41bf29b1043ba1265fde3b2dad297d4d2b198f29", + "0x464ec85779dcb2a11b64e28a6aada9563b9a24803cf0250bf073768ed75e1c5f", + "0xe76931d3a2a46f459cf169a0e0e6d44af220c98835a9fd406e82ef9b167fffa1", + "0x7faeb24db369709c70ae2c41259d2216bc320e9dc29ee0d7e358b18a375ca113", + "0xdf96d84651c4ed2a01bad7d2ff2a465bf334587adcfdeec26ebc5104bff2e4d5", + "0xf8d8f6319c2dd17fb421463eb8d0c86881d0afaf8e08e95c49b5a9068727cae7" + ] + ] +} diff --git a/axiom-eth/data/tx_receipts/task.mainnet.json b/axiom-eth/data/tx_receipts/task.mainnet.json new file mode 100644 index 00000000..3c64456c --- /dev/null +++ b/axiom-eth/data/tx_receipts/task.mainnet.json @@ -0,0 +1,61 @@ +{ + "txQueries": [ + { + "txHash": "0x66c7b4d2bb7cc086f3eeb3b46bca9c0bb825826bbfc43ebc45f1c9eb7e7344d9", + "fieldIdx": 4 + } + ], + "receiptQueries": [], + "mmr": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xe81cc62bb288e100856ea7d40af72b844e9dcb9ff8ebed659a475e2635cd4e18", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xb169c87af2d231bc71f910481d6d8315a6fc4edfab212ee003d206b9643339c0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x43062e083f5f40510f30723d9cebb51c4ae67c35d86c5b791a043bae317350e3", + "0x6cddc980f4c3b403d99080c32b3f0a6205e39560b9021d5f233c04d96c23381e", + "0x6a42052cabd8d66a584b8823c6aadf64dd2755321210c66a2b7acd1da5bdeacf", + "0xebf08ca711cbab09109bb51277c545ee43073d8fa8b46c0cbbedd46ce85e73be", + "0x477c055e69de14e3bbfe2af0389e6c3ac28ffb5b0cc8fa26b543ac47857fd646", + "0xf47e6584af17667881494720b50dd063d0900b288438df7a37e2e91440aedd23" + ], + "txMmrProofs": [ + [ + "0xebd5fc0be32c2298e2ee18dac2db5b1508a64741ba7186dd59229ec0620d9d64", + "0x9139f12e0f47241172143fc5d0b0245b5ffbcdf9130da9efb14c155f6036697e", + "0x97f5d40bc9a10e06b5eef7609a3e364f30ab10675d22fbc3304179a381b39b18", + "0xc8c07e6c877f0cd60903d376c1aa911f47c96d3967d989101ed8b873cf6e38de", + "0x96cf53edbe3be783378433c518939a7e0b4e657edb6558b1f6e14edc0a125a18", + "0xfa3448a664e9406ffdc3b53e24f06fcf6b576621f854e421385bd1603ea257ee", + "0x9dffc8cb737d72da73df5e333bb7716cfec51e4b761281c6c7ff4db55689911c", + "0xef3fb7b7274810ec5bc63e7c248ea7dfe26d95abcd8bcb8d97b1f5fb617b8dc8", + "0x6a4d92e38592f280afc95efe5dd178a37c155bfad49759db7a066d597bc804d3", + "0x7db79de6d79e2ff264f4d171243f5038b575b380d31b052dda979e28fae7fc08", + "0x3106ece6d5a3c317f17c9313e7d0a3cd73649662301f50fdcedc67254b3fe153", + "0x902c8cf11e8d5cf14137e632061a52574515a2254fbd3b70cfc85a45f9dbcb4a", + "0xc48c7fe69133ac6f0c2200e600a3c15fe1832577156bc8851a7538403eafadfa", + "0x4434e3730dbe222cb8b98703748da1f07f05564c64ea66fe4765484ea982f5d6", + "0x69d2bc461de5dba21f741bf757d60ec8a92c3f29e417cb99fa76459bc3e86278", + "0xe18396e487f6c0bcd73a2d4c4c8c3583be7edefe59f20b2ce67c7f363b8a856a", + "0xa10b0dd9e041c793d0dbdf615bee9e18c3f6e3b191469bbb8cc9912d5d228050", + "0xa51d50eb9feaaf85b7ddacb99f71886135f1c4f59de3e788a5e29a485d5fdce5", + "0xa46b70512bfe0b85498e28ae8187cfadff9e58680b84ddcde450cd880ea489b1", + "0x33552dfc75e340bca3c698e4fb486ae540d07cf2a845465575cff24d866a161a", + "0x0fec590ac8394abe8477b828bf31b470d95772b3f331ff5be34ba0a899975a17" + ] + ], + "receiptMmrProofs": [] +} diff --git a/axiom-eth/proptest-regressions/solidity/tests.txt b/axiom-eth/proptest-regressions/solidity/tests.txt new file mode 100644 index 00000000..db6bdc1a --- /dev/null +++ b/axiom-eth/proptest-regressions/solidity/tests.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc a5b59729ad75fa91e62214a81d6430ccdda1e75a56338267c43632989cbf7b64 # shrinks to input = MappingTest { key: [], mapping_slot: [], ground_truth_slot: [41, 13, 236, 217, 84, 139, 98, 168, 214, 3, 69, 169, 136, 56, 111, 200, 75, 166, 188, 149, 72, 64, 8, 246, 54, 47, 147, 22, 14, 243, 229, 99] } diff --git a/axiom-eth/rustfmt.toml b/axiom-eth/rustfmt.toml new file mode 100644 index 00000000..f5a13f37 --- /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/axiom-eth/scripts/input_gen/block.json b/axiom-eth/scripts/input_gen/block.json new file mode 100644 index 00000000..13d4c14f --- /dev/null +++ b/axiom-eth/scripts/input_gen/block.json @@ -0,0 +1,5351 @@ +{ + "baseFeePerGas": "0x52e489b96", + "difficulty": "0x0", + "extraData": "0x496c6c756d696e61746520446d6f63726174697a6520447374726962757465", + "gasLimit": "0x1c9c380", + "gasUsed": "0x156d87f", + "hash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "logsBloom": "0xee6a35b2f381ba14522c6472e8d46529f5b1b1b6ff83afbc7bc31c6f9c323b4a5996f7fbec0d7c2bf4a8fb38cb3df5ed8a77d661cfe37ece3b5a55b554b7ff4f77fd52703b09890caf2b16ffdfff87adacbf6850fe5d3ee1a7253de9f83e3d769e7b6c855fbecffac54ee243a858adbb8a5c7a34923ee45fbac1c7ff051eedf45e6b077ecf46c9b4bdce3eeb116b4e4728058fc1ef699769587cee5eeb5793ecde93390361c2f8926efc64eddbb295e18e5b2ae9cb988acb917b0e16b2aa485fe916e5a789b677495c589910fc9e13777df9aacf37aed537d6050bc708ff7b7e8b72e30a9349be9ac6d57ff367b91e0bcdbf2b8a7a5aef4f36656928f22bf6bb", + "miner": "0xdafea492d9c6733ae3d56b7ed1adb60692c98bc5", + "mixHash": "0x77e70a1ebdeffad090cf2b0c8a126b9a6d5befa12669ff0e5001997e1a326599", + "nonce": "0x0000000000000000", + "number": "0xf929e6", + "parentHash": "0x7ba1ca22552d0795fb0775d4b25a8bc2254d26b24fb967017758c41678bf7ca7", + "receiptsRoot": "0xb2b8e2eea2dc8e9b700851d4c970e2a2ccecb31e5c0a4d0ffa808f82ea57e2ec", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x1db51", + "stateRoot": "0xcd298c3cb7747f8ed148fb53653c304273faa9928c9fdc997b40d9cc0f75cd7b", + "timestamp": "0x63b4a97f", + "totalDifficulty": "0xc70d815d562d3cfa955", + "transactions": [ + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x1529282ea2d39c1a7a78608c0d0cab72a265196d", + "gas": "0x48fcc", + "gasPrice": "0x1273bf9596", + "hash": "0x5763cb4c53899c7cf2192053d450193cfa4f2d32c5f6702665a15fc6b78e77ea", + "input": "0x7ff36ab50000000000000000000000000000000000000000000000000000003611d6f5f600000000000000000000000000000000000000000000000000000000000000800000000000000000000000001529282ea2d39c1a7a78608c0d0cab72a265196d0000000000000000000000000000000000000000000000000000000063b4a9f20000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000028dbe36cde7f6034709225c9999549e23c14481", + "maxFeePerGas": "0x150ae3e361", + "maxPriorityFeePerGas": "0xd4576fa00", + "nonce": "0xadb", + "r": "0xed4fccac8081a31d3a72817a584e5da172678741b0e22d1279fc94bf8ac86e7a", + "s": "0x1aa3b470ed21f438391f071c141897e4408bc6009594fc5ef2c4531caef05b18", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x0", + "type": "0x2", + "v": "0x0", + "value": "0xb1a2bc2ec50000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x7105e81982d12c1ecdfea3a80c92dc3fffab0077", + "gas": "0x7a120", + "gasPrice": "0xa35a87b8c", + "hash": "0x417a8cecdcd94c9d061bbe497133211095bc0237f5388c4d5e453d48f2127881", + "input": "0x7ff36ab500000000000000000000000000000000000000000000328c79ab493cc068929100000000000000000000000000000000000000000000000000000000000000800000000000000000000000007105e81982d12c1ecdfea3a80c92dc3fffab00770000000000000000000000000000000000000000000000000000000063b4a9a20000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000328b4247324732940f47e4ec831b16fc49246fff", + "maxFeePerGas": "0xa35a87b8c", + "maxPriorityFeePerGas": "0xa35a87b8c", + "nonce": "0x7c", + "r": "0x300a01dba05dfd98c404ee60cfc82300981986abeb2bf61d5eb6370684445fbe", + "s": "0x7b1bab5838510d3372b0e53c0c8b02d205cffc0410990364c032fd9d88c7bbb6", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x1", + "type": "0x2", + "v": "0x1", + "value": "0x16345785d8a0000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x70e8a14f94b8a325aa5b3de0d9776d032f91aea0", + "gas": "0xaae60", + "gasPrice": "0x97900ba7c", + "hash": "0x42ff1c14344b5fc60a487697088e67776450d0f1500a1362885b587c2bdb58e3", + "input": "0xfb3bdb410000000000000000000000000000000000000000000000005dd192baab1b71a0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000070e8a14f94b8a325aa5b3de0d9776d032f91aea00000000000000000000000000000000000000000000000000000018579b6cc0d0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007dbafe66b3bc826bfa44ddab70de9e49c97ffeb3", + "maxFeePerGas": "0xa2aff467c", + "maxPriorityFeePerGas": "0x44ab81ee6", + "nonce": "0x128", + "r": "0xda34d398fadb74ded158030ebbdc2a046f6dafdff18c8c8a5c5d645844b19713", + "s": "0x20036343664992b127f0dc144208037d1f55435fb51cd8f0a02439e0ba8ca127", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x2", + "type": "0x2", + "v": "0x1", + "value": "0x16345785d8a0000" + }, + { + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x8a01fa5a77311bbcf29e293d8ecb48707cfdb700", + "gas": "0x200d3", + "gasPrice": "0x8bb2c9700", + "hash": "0x7c36836dde5d8e04c733f61c78fd67cea545baed9c6ca46b0cfc694b9ca2dcfd", + "input": "0x42842e0e0000000000000000000000008a01fa5a77311bbcf29e293d8ecb48707cfdb7000000000000000000000000009e79fc4e6e1de5081323c5d07b95ab11fda42c16000000000000000000000000000000000000000000000000000000000001edec006e6674e68f90e58f96", + "nonce": "0x19fb7", + "r": "0x824f14382a498a5ed9815fd2c144d6558438edf235d3ef9c21451815327cb00c", + "s": "0x1c5d08d25d5ed8826181583b5a6bb234ba8f129e29db6a80c3903b1a838cbb23", + "to": "0x929832b1f1515cf02c9548a0ff454f1b0e216b18", + "transactionIndex": "0x3", + "type": "0x0", + "v": "0x26", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x55c7659371f9731cd95db67711531ae87ccf6cc7", + "gas": "0xc1cd6", + "gasPrice": "0x8ac5a7196", + "hash": "0xa639cb31b75f022d28b22af25c2fae89695c95c63c47c8572734ba9a41631343", + "input": "0xfb3bdb41000000000000000000000000000000000000000000000001155350c65ea8f834000000000000000000000000000000000000000000000000000000000000008000000000000000000000000055c7659371f9731cd95db67711531ae87ccf6cc70000000000000000000000000000000000000000000000000000000063b4a9ea0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007dbafe66b3bc826bfa44ddab70de9e49c97ffeb3", + "maxFeePerGas": "0xa73a0bf9d", + "maxPriorityFeePerGas": "0x37e11d600", + "nonce": "0x9", + "r": "0x9b524d7b322c1597cc28f45d5211146f37729759a9934635bd2e7293a0afbb22", + "s": "0x3e9d943f335004ae9d8f37ce844102e56401ba7fc554a1f91ecfc482cd223d05", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x4", + "type": "0x2", + "v": "0x0", + "value": "0x365cf154d25d771" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xdd22de874fdf682e4b7975ded8b3cc3d585ff29e", + "gas": "0x3e760", + "gasPrice": "0x8ac5a7196", + "hash": "0xe33009ec5d427ac72c7d5a944b3ff7ab5d4c0cb677c769eda0233265c4a4c48e", + "input": "0x791ac9470000000000000000000000000000000000000000000000000004715978d38a00000000000000000000000000000000000000000000000000003c32eba82d1ff300000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000dd22de874fdf682e4b7975ded8b3cc3d585ff29e0000000000000000000000000000000000000000000000000000000063b4a9f20000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b4e92af212c05cfb2434efb6365500c76501e397000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "maxFeePerGas": "0xb437ebf61", + "maxPriorityFeePerGas": "0x37e11d600", + "nonce": "0xbf", + "r": "0xeb61a99df5ca57524bebdc778f734d26854bc1a72c07d7e84f2e7082d36d314f", + "s": "0x318f9bd2e083cda4ebf50c02f736cbf74929679bf73b6b9148ff249b6de7bdae", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x5", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xfb534c27c467515c17e6fdd5fcd179575a67a8d4", + "gas": "0x1de429", + "gasPrice": "0x70b1eeb96", + "hash": "0x12f3c75fa1f4e3c81cb84bbda4223f8d8676aaf9a2a8469f3712db6af91fc352", + "input": "0x7ff36ab50000000000000000000000000000000000000000000000006e86fd75e5f6cdb00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000fb534c27c467515c17e6fdd5fcd179575a67a8d40000000000000000000000000000000000000000000000000000000063b4a9f30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007dbafe66b3bc826bfa44ddab70de9e49c97ffeb3", + "maxFeePerGas": "0x70b1eeb96", + "maxPriorityFeePerGas": "0x1dcd65000", + "nonce": "0x9c", + "r": "0x14f7d843e772915be92978443df6290804176ae02b6a5b799448cd546c92f5c1", + "s": "0x71bccf834ccf4367676041c15b4e113842650f392e50a64574819845587f8f54", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x6", + "type": "0x2", + "v": "0x1", + "value": "0x16345785d8a0000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xdd1c8c22593ec7dd8aacd96f914550e0d3d568fb", + "gas": "0x7a120", + "gasPrice": "0x6fc23ac00", + "hash": "0x88e1effe5d304b57da442c16669fa004b59a810cd80fef9ed0bf05f7491f1e43", + "input": "0x7ff36ab500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000dd1c8c22593ec7dd8aacd96f914550e0d3d568fb0000000000000000000000000000000000000000000000000000000063b4abd30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000283d310d682284ebc24db33a41bb5a01bdd140b", + "maxFeePerGas": "0x6fc23ac00", + "maxPriorityFeePerGas": "0x6fc23ac00", + "nonce": "0x4fd", + "r": "0x9b62ff0bf9f9556e8d2a6e804ed5dbd1cd39ae637781d95032a56c0890daf9c3", + "s": "0x17300b983874be9294e42b50f14fc1872f8ffcb52e2e35eeffa3d42227e3d02d", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x7", + "type": "0x2", + "v": "0x1", + "value": "0x5af3107a4000" + }, + { + "accessList": [ + { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "storageKeys": [ + "0xdf4a7caf27e082ccc4b23ff2d3a45778d7ea6229414021e82478e5857d6ccca5", + "0x245d32d2da911ebe35956892a50b3686a5bbeb9c3bc6e8bd766cbdeada720e9e" + ] + }, + { + "address": "0xd9fae7f9f3202a96ea02025ce65f6ad41eca6ace", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x000000000000000000000000000000000000000000000000000000000000000c" + ] + }, + { + "address": "0xb8ac2383cdf35f77b9bf2605f1378e84ebf6452d", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000016", + "0x57c94f2490324e5d0fda7957d8a66ba1d98db110cdc9603019013536352459c4", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000015", + "0xd48d202b57d147136036f7f838036df3eabe4ca5a6dacf1f96481596226e9bda", + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x9883e6aecca133faf20b75f34adc3b129362784423d0e25bc664830c93740fa2", + "0x0000000000000000000000000000000000000000000000000000000000000017", + "0x61e480029613f88ce61824925bde5fd6d7cdf1a472aa9a282de108871b3bcc0c", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x848cbe31122265aac4d885d8e6c7e098e4e7906f97c5f7f2a96a0d828365b25a", + "0x7e20d63170fdc721b79f6c48eeb88758552e7db01df07e0f71970fca50a47cca", + "0xc33b578e25064bb3c0b9eeb75e8b154264e438d1bedcf84580a92a3b218de92a", + "0x0000000000000000000000000000000000000000000000000000000000000018", + "0x000000000000000000000000000000000000000000000000000000000000000d" + ] + } + ], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xcc8efeb2a5f50c81c5d7403676b198ae094b2f3c", + "gas": "0xf4240", + "gasPrice": "0x52e489b96", + "hash": "0xf075b8ae6253e568f30ba2e98e493efd8d27acf417bb047a5d092ee906392da4", + "input": "0x7fd9fae7f9f3202a96ea02025ce65f6ad41eca6ace018153000000", + "maxFeePerGas": "0x52e489b97", + "maxPriorityFeePerGas": "0x0", + "nonce": "0xd53f", + "r": "0x2f57ab875cb0ade28c2850777336c888775cff529059ff6663e420ceae566dc3", + "s": "0xb57d01d47d120a444e11b0e7563375a166d6612c472e0d0cba0dddcc5b27c27", + "to": "0x00000000500e2fece27a7600435d0c48d64e0c00", + "transactionIndex": "0x8", + "type": "0x2", + "v": "0x1", + "value": "0xff44de268" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe37a010859a480f69c8af3dc8dcf171daee80a01", + "gas": "0x4e882", + "gasPrice": "0x5a57e2f96", + "hash": "0xac6d54dceed8651d0a86600a5b52fdb503cc6e46874af70aa884b0bfdfe2ac92", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b06300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104b858183f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4472b43f300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010314e69551c1d0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000e37a010859a480f69c8af3dc8dcf171daee80a010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000b8ac2383cdf35f77b9bf2605f1378e84ebf6452d00000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x77b43754c", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0xa6f", + "r": "0x5553d37d27718cce036bf14f9421a75e9cf0de23710f021accdb0f9cbce9b041", + "s": "0x1e67e775c1f5e5bb1a96a5d8488d50c9261ee942da2c00216c69227fd5d4b6dc", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x9", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [ + { + "address": "0xb8ac2383cdf35f77b9bf2605f1378e84ebf6452d", + "storageKeys": [ + "0x9883e6aecca133faf20b75f34adc3b129362784423d0e25bc664830c93740fa2", + "0x0000000000000000000000000000000000000000000000000000000000000018", + "0x57c94f2490324e5d0fda7957d8a66ba1d98db110cdc9603019013536352459c4", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x000000000000000000000000000000000000000000000000000000000000000d", + "0x848cbe31122265aac4d885d8e6c7e098e4e7906f97c5f7f2a96a0d828365b25a", + "0x0000000000000000000000000000000000000000000000000000000000000015", + "0x0000000000000000000000000000000000000000000000000000000000000016", + "0xc33b578e25064bb3c0b9eeb75e8b154264e438d1bedcf84580a92a3b218de92a", + "0xd48d202b57d147136036f7f838036df3eabe4ca5a6dacf1f96481596226e9bda", + "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x7e20d63170fdc721b79f6c48eeb88758552e7db01df07e0f71970fca50a47cca", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x61e480029613f88ce61824925bde5fd6d7cdf1a472aa9a282de108871b3bcc0c" + ] + }, + { + "address": "0xd9fae7f9f3202a96ea02025ce65f6ad41eca6ace", + "storageKeys": [ + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ] + }, + { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "storageKeys": [ + "0xdf4a7caf27e082ccc4b23ff2d3a45778d7ea6229414021e82478e5857d6ccca5", + "0x245d32d2da911ebe35956892a50b3686a5bbeb9c3bc6e8bd766cbdeada720e9e" + ] + } + ], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xcc8efeb2a5f50c81c5d7403676b198ae094b2f3c", + "gas": "0x3c96c", + "gasPrice": "0xa8b0e3298", + "hash": "0x45b2b12dbea560d8f45949dd03873ecd75b2aea2579d37ea3412b007c25389b6", + "input": "0x8ed9fae7f9f3202a96ea02025ce65f6ad41eca6aceb8ac2383cdf35f77b9bf2605f1378e84ebf6452d018152ff0000", + "maxFeePerGas": "0xa8b0e3299", + "maxPriorityFeePerGas": "0x55cc59702", + "nonce": "0xd540", + "r": "0x63e894a129d7cd214eec4cbab3460db398da11ac5bcf1380ca740af4da82a701", + "s": "0x10c12011ee83b673333a2d9b03a55eb307be892cd979bb50d363cebf19160c25", + "to": "0x00000000500e2fece27a7600435d0c48d64e0c00", + "transactionIndex": "0xa", + "type": "0x2", + "v": "0x1", + "value": "0x11e29be000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf18d90929e3ec5d3bea4aa16b9451ad3216a7764", + "gas": "0xdef5", + "gasPrice": "0x61cb3c396", + "hash": "0x4b4fa1747e8a7241dcb618f12effdda5ab0b90c93436a5aa383fe83193d94684", + "input": "0x1801fbe500c0ce6af16112124601000000000000000000000000000000000000078376800000000000001e77d6bd6c7c960603961fea106e2cfa570971d6f5b6500e8478", + "maxFeePerGas": "0x10c388d000", + "maxPriorityFeePerGas": "0xee6b2800", + "nonce": "0x37fc", + "r": "0x94b5e5e65f882dc5032fca4f9d50b45ecc04ab99d31f1664f56b2820bdc4b806", + "s": "0x5169ac64f9800b83426fe2bce77b2674036a50b0d2774e81bee7aca418cee431", + "to": "0x4c3bae16c79c30eeb1004fb03c878d89695e3a99", + "transactionIndex": "0xb", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x7eef28976548c33bac91c423501f189cf55a34a8", + "gas": "0x5208", + "gasPrice": "0x613507581", + "hash": "0xf67ca3772876af8a7f0fb13234abc68b39f715fa61b696728e5cd985db755c34", + "input": "0x", + "nonce": "0x243", + "r": "0x5c003e15283224ca86f708e363b21092e4e365574c4ae56f82bafd2c5e0de979", + "s": "0x18dc11166b380c2799f91754ab906acca6d2a60056c7ade62ce38a0d9e954029", + "to": "0xcbd6832ebc203e49e2b771897067fce3c58575ac", + "transactionIndex": "0xc", + "type": "0x0", + "v": "0x26", + "value": "0x242088adc2a06d0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xcfae29f9d5251eb2bbf9ab68f66671c62a95dc75", + "gas": "0x3feef", + "gasPrice": "0x60db88400", + "hash": "0x76185d6e6ccb272b880eb0dc434e6ea12b4bdc569596e7a5496e5353b5e0934c", + "input": "0x7ff36ab500000000000000000000000000000000000000000000000000000469ac3662250000000000000000000000000000000000000000000000000000000000000080000000000000000000000000cfae29f9d5251eb2bbf9ab68f66671c62a95dc750000000000000000000000000000000000000000000000000000000063b4b0630000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000c9be207ea5c9a306f54887d699a7fbc29e976c8", + "maxFeePerGas": "0x60db88400", + "maxPriorityFeePerGas": "0x60db88400", + "nonce": "0x868", + "r": "0xcb67eae271f4d54deb68e29103725c7c446018c74817b74a23e4b944cd8102dc", + "s": "0x7ba2ad2ff070a0c66488797c78389538aaea871bfa8b3cdde554bcf75c104a32", + "to": "0x25553828f22bdd19a20e4f12f052903cb474a335", + "transactionIndex": "0xd", + "type": "0x2", + "v": "0x1", + "value": "0x16345785d8a0000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xb287eac48ab21c5fb1d3723830d60b4c797555b0", + "gas": "0x5208", + "gasPrice": "0x5e1377e16", + "hash": "0xa8dba5ebdaa017152492d95a9895a9fcd1ed2413e01284777edf45e46817ce7a", + "input": "0x", + "maxFeePerGas": "0xb88470359", + "maxPriorityFeePerGas": "0xb2eee280", + "nonce": "0x3e36f", + "r": "0xa7a551db44b62265e51b9da71a2ae0bf1af1babef3816a4e57eaafb6170ed5a4", + "s": "0xcacdfa8539bd743513d661dd96d0f5557284b20b69d30cd2aa97a1b5f4c60dd", + "to": "0xa3f8e6907aee2a51f47a5ba23970ac1496776514", + "transactionIndex": "0xe", + "type": "0x2", + "v": "0x0", + "value": "0xa7b02661660000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x8216874887415e2650d12d53ff53516f04a74fd7", + "gas": "0x5208", + "gasPrice": "0x5e1377e16", + "hash": "0xd54508d329c6d10625b0ed269cf0234e76974c415d891845374dfd0949ee7a21", + "input": "0x", + "maxFeePerGas": "0xb88470359", + "maxPriorityFeePerGas": "0xb2eee280", + "nonce": "0x56ee6", + "r": "0xa4208b00f7bbf461655d3af8a734f4bc3f234c6f7d05fde3588779daa93864ac", + "s": "0x462bbca3a205da074fde2ff90fa640369bed01469e88edbff9dd8b0fffc707be", + "to": "0xd0245c4959331ff01204522f31eb72fac9a0a851", + "transactionIndex": "0xf", + "type": "0x2", + "v": "0x0", + "value": "0x44ec327ca48000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x336950897d07fd083dde9580587120523fb249c4", + "gas": "0x48fe7", + "gasPrice": "0x5e118f996", + "hash": "0xb998a8e3ee983d0d6c60a603f9924e202f998b36e22acbabd4f8b4557a0a059b", + "input": "0x7ff36ab500000000000000000000000000000000000000000000000025baabe2f79bd7d80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000336950897d07fd083dde9580587120523fb249c40000000000000000000000000000000000000000000000000000000063b4a9e60000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007dbafe66b3bc826bfa44ddab70de9e49c97ffeb3", + "maxFeePerGas": "0x7a85f479d", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0x275", + "r": "0x53e61e7e660d2afca77510a610fe864a23e58c3aba2b6388f22c30b4e9ba755c", + "s": "0x23c092a15e2afe3c59dc98e385f4e0757fbbfa7d675396bc11b57a95740d8cd9", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x10", + "type": "0x2", + "v": "0x1", + "value": "0x6a94d74f430000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x6306f9ddbd028b39edbb663bfae9c5407565d408", + "gas": "0x4a72a", + "gasPrice": "0x5e118f996", + "hash": "0xc1e22e451c67e795d4b0526de02f60a639f271ae58f7fa698f8dc2e15d158be8", + "input": "0x7ff36ab50000000000000000000000000000000000000000000000007828c793bdd5a59600000000000000000000000000000000000000000000000000000000000000800000000000000000000000006306f9ddbd028b39edbb663bfae9c5407565d4080000000000000000000000000000000000000000000000000000000063b4a9e60000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007dbafe66b3bc826bfa44ddab70de9e49c97ffeb3", + "maxFeePerGas": "0x7a85f479d", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0xa0", + "r": "0xdca8448ab3eb4fb4d4b3feeea27a5cdfbb82295c0f9a04bfc6ec1daa3c305d0d", + "s": "0x70b3f8242fb4d234991abcde03ffd5d574bc448a611cc5cc18867450dddbf2fb", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x11", + "type": "0x2", + "v": "0x1", + "value": "0x16345785d8a0000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xa26d879a4aa6987f035565c64bd5fa1e282c2d7a", + "gas": "0x4af23", + "gasPrice": "0x5e118f996", + "hash": "0x2d8727a0b10820ca8482dbd716b7ec803885066f513698e729571d0d090f5e49", + "input": "0x7ff36ab50000000000000000000000000000000000000000000000003e11a44ac9e3f42f0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000a26d879a4aa6987f035565c64bd5fa1e282c2d7a0000000000000000000000000000000000000000000000000000000063b4a9eb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007dbafe66b3bc826bfa44ddab70de9e49c97ffeb3", + "maxFeePerGas": "0x7a85f479d", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0x17", + "r": "0xd92dd1bc383b7b7d78c2b449cef7e3aa1cd36c1fb53c124f99e4045dcea33358", + "s": "0x6ed581d80bf2ff93955c62208f52e2dda13fc50d7300a361f1d5f3c13e5d45bb", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x12", + "type": "0x2", + "v": "0x1", + "value": "0xb1a2bc2ec50000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xdfd94d2fb5f07f8f3df5f2a8c01ca1cfb4b4c1c3", + "gas": "0x11b07", + "gasPrice": "0x5e118f996", + "hash": "0x307867c4d3197ea5e708ca70b7b94307d7db2bf50eaf5e44c317155f933190fe", + "input": "0x095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x8783d4761", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0xd", + "r": "0x609bf92a804619b09361b3c6902ff344dfd2626532546e1af1feb6765359eed", + "s": "0x77a2c368b6922a02c58e5bf896186be02146fb8d522f2716b42acec2f6499f3a", + "to": "0x7dbafe66b3bc826bfa44ddab70de9e49c97ffeb3", + "transactionIndex": "0x13", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x36c28fcaf92133c823e526da41c1d2ea6421a290", + "gas": "0x11a81", + "gasPrice": "0x5e118f996", + "hash": "0xd710e7210bfe3dde6711ee7a0d8e8ac20e93474b58d5423bfd10fa2be32127ae", + "input": "0x095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x8783d4761", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0x6ec", + "r": "0x966c67a8ab7e80a2a293d96117d3b2409af057119e7046b1095ba0b03e3e2298", + "s": "0x20273ccdf0a40037bf1d410ec2f5497270eed39e56c2f0376553ab3e4375fab4", + "to": "0x7dbafe66b3bc826bfa44ddab70de9e49c97ffeb3", + "transactionIndex": "0x14", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xafc02fae7b578230f38cb91b203c4d0e07ddf42c", + "gas": "0x46536", + "gasPrice": "0x5e118f996", + "hash": "0xd02e68847fe46311358cf2e791056e5d399ee0a4fab6cc9df88b609a03f512f1", + "input": "0x7ff36ab50000000000000000000000000000000000000000000000000000021f75cc74f40000000000000000000000000000000000000000000000000000000000000080000000000000000000000000afc02fae7b578230f38cb91b203c4d0e07ddf42c0000000000000000000000000000000000000000000000000000000063b4a9ee0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000c9be207ea5c9a306f54887d699a7fbc29e976c8", + "maxFeePerGas": "0x8783d4761", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0xd", + "r": "0xcda5c15b02350c7bb632b1a07f8ccba5145baae9f849abb3d72c980f1df270ff", + "s": "0x78a8f5f5a5158b3de06858cc37b34bb97190d5f331bc0856a6a7908ad14e4190", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x15", + "type": "0x2", + "v": "0x1", + "value": "0xb1a2bc2ec50000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x19f494583c7c933be7b0ee58104ddafac1e8adfa", + "gas": "0x2f92a", + "gasPrice": "0x5e118f996", + "hash": "0x99a4ae821eeb4094c7aec2225f668024060ff1d697b609b5d23b41b46a8fd24b", + "input": "0xa9059cbb0000000000000000000000007bdb4f7e0637d125a73dbe0adacb0fa6aab0af6500000000000000000000000000000000d8ed171d1a7405344ebb16da2ac26aa8", + "maxFeePerGas": "0x2e90edd000", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0x3c0f1", + "r": "0x6b08714a47c0e93701ed4791203eee46ee150319c1e75ee1e7a522e41a64ef2", + "s": "0x6175bcf5ad4f4c1ffc0ec53adc238440c0ded2156fb04ab0cce2901cae12b546", + "to": "0xdbd324b73f6f85bf9013b75c442021303b635ff9", + "transactionIndex": "0x16", + "type": "0x2", + "v": "0x0", + "value": "0x7bfd5d76a94000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf442cb95a7a661d7741d0e2b3cde234ef1f52b8e", + "gas": "0x48e4e", + "gasPrice": "0x5e118f996", + "hash": "0x491cda18089ba2a0c535b2e495eb24d0cc2f57e15c083915ee4aabc515ae189a", + "input": "0x7ff36ab500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000f442cb95a7a661d7741d0e2b3cde234ef1f52b8e0000000000000000000000000000000000000000000000000000000063b4a9f30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007dbafe66b3bc826bfa44ddab70de9e49c97ffeb3", + "maxFeePerGas": "0x8783d4761", + "maxPriorityFeePerGas": "0xb2d05e00", + "nonce": "0x4ae", + "r": "0x234d49f176e9951d6830d7a04a7150e16c541faeae7446d333b1efc24f478e89", + "s": "0x28303b1fda925245bd9c92581f94ab358c3ff54f0e08169ddf1bb4b19687275d", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x17", + "type": "0x2", + "v": "0x0", + "value": "0x30f1f5120c44de0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xc56b682493d335b2c6cc0e974cfed4eda471cb1a", + "gas": "0x360f5", + "gasPrice": "0x5d21dba00", + "hash": "0x88b27ded0460a99a1f56cf8b69be098b18e67c5c85d7692e5fdbe313889387da", + "input": "0xfb0f3ee100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000269bef0518b800000000000000000000000000fabc14944f44fa39d7ae04197030b9f08fd7442e000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000005940ead1a16a8994b1c42755d4cd2a5cb3269f5a0000000000000000000000000000000000000000000000000000000000000043000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a7530000000000000000000000000000000000000000000000000000000063dd85d30000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000a0031f4f4f7361be0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001057acf5f78000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000139c692729000000000000000000000000000aed6189d26cf26ec97af930b521507c17f75cd9b00000000000000000000000000000000000000000000000000000000000000410eef971606c6e31d171be92fd0abf0e8028c6353207b29347625729ece6cbcff3f6eecbcb9b4a0743f6f38441d229756be30cbc7f1c71609f5d00395335e91ab1c00000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x5d21dba00", + "maxPriorityFeePerGas": "0x12a05f200", + "nonce": "0x9c4", + "r": "0x96132f8430b1e9f200fb5ba61791a981d6bb147590086fd66a29958205817634", + "s": "0x7feae8c4c73bc18025b1dca74918de85f5a6e406823f068be0d9a7d86eda11d7", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x18", + "type": "0x2", + "v": "0x1", + "value": "0x28db3066eac000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x308d52b0689f20e1fc2b6bc014fb1cd019b490ec", + "gas": "0x741e", + "gasPrice": "0x5ccd9eb4a", + "hash": "0xaf2b5844258fdbb4f5014ccc2d322b39ab8b6720b33f974b68b23507e4bf5bb9", + "input": "0x095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc450000000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x5ccd9eb4a", + "maxPriorityFeePerGas": "0x5ccd9eb4a", + "nonce": "0x2a", + "r": "0x3d9b02e8eb435da3e738b2ccf7aee538205a7c028fb5c4d8b722d701cc0c2f22", + "s": "0x1505d60b3ce3784d8e6f51b52b8ee1555a0f059590ef9923cddfdd66213879f", + "to": "0x2d3aeba667504b79388a4e1f782f7f5f59baa715", + "transactionIndex": "0x19", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xadce3263b83c5d34ed04774a973d3ab5b56dec58", + "gas": "0x4157a", + "gasPrice": "0x5c713c7a8", + "hash": "0x720f30bd0603d1b28d49bd993679b1862912e97e6dde83b85498054ee166bb3f", + "input": "0x357a150b0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000003b35a5442b8ee0000000000000000000000000000000000000000000000000000000063b4d39b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000adce3263b83c5d34ed04774a973d3ab5b56dec580000000000000000000000000000000000000000000000000000000000000000e14d80f2f48c0aa163cff17b331bd96580818546f17fbc3ffc74e19ec831c2a541b4ec1141ee23c0de7ac40341fb745e474f16d12b384d0d9cee6dda4e5f60fb000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002097852ce1d28128d2f59da1cec4fe1bfb8674e79e4ba6121b71272986e45edea9000000000000000000000000175c7aaccc6c4ad4f89285ee87dbd48b8851a9830000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000063b62f76000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001c067da39adbc3cfada8fa4066a35e45ef99ee41e46ccfd80f3900d84a8546e7fb119702759326301484b52fe0f3f7abd8a2b09531e87695768516fa8904825019b000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000232bff5f46c0000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000ab45f7b8973a1b71366ea909c70b9ff707d78b7d000000000000000000000000000000000000000000000000000000000000008e0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232bff5f46c0008f0b64175062b7c3cb240a006777501feaa602e68e09ad9a6dd3b358155f8714000000000000000000000000f849de01b080adc3a814fabe1e2087475cf2e35400000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000001388000000000000000000000000d823c605807cc5e6bd6fc0d7e4eea50d3e2d66cd00000000000000000000000000000000000000000000000000000000000124f80000000000000000000000002a49859f72d1de53e6dc9883691c67761d5e7ce91f6e66746e657264732e61691f", + "nonce": "0x232", + "r": "0xe7c179f4aa3880d38314d803cc2f9150f52ff3a7d31e98d34ef1032ecb64d9c4", + "s": "0x65295a55bec0e541e3346c3aecd2ebd8c22960285ff6f7f3ae215809d62fb465", + "to": "0x74312363e45dcaba76c59ec49a7aa8a65a67eed3", + "transactionIndex": "0x1a", + "type": "0x0", + "v": "0x25", + "value": "0x232bff5f46c000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x199a6b23463b012ad37129e6a640c96632ed543d", + "gas": "0xc951", + "gasPrice": "0x5c34b9496", + "hash": "0x5fb31cd2dd527de814e9cb99da0f06875d32e8428d0190150829228c91050a07", + "input": "0xa9059cbb00000000000000000000000021096e97e9fa7c2d283e8c4356e2d9e1fe903de70000000000000000000000000000000000000000000000001bc16d674ec80000", + "maxFeePerGas": "0x8d6043e4a", + "maxPriorityFeePerGas": "0x9502f900", + "nonce": "0x160", + "r": "0xd4f53faf1a6b8b9ea1b45116ed8fa55358c26a06442ac3c04854d3ec54bdc165", + "s": "0x4619f75e7be604dee7f8d3c5ae748d29ccb0795b2de389114f74b71e2dae27ac", + "to": "0xf4d2888d29d722226fafa5d9b24f9164c092421e", + "transactionIndex": "0x1b", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xda972b0bd41ca5daba431561d36c208d1da66bb4", + "gas": "0x32ffe", + "gasPrice": "0x5c34b9496", + "hash": "0xe1c7bf290bb88cbe50cbd804f7ba8ff945a832247876ddf195b3e950f1d69960", + "input": "0xab5c0da2000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000da972b0bd41ca5daba431561d36c208d1da66bb4000000000000000000000000da972b0bd41ca5daba431561d36c208d1da66bb400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000d486327dbfe29f600000000000000000000000083e7e4c65e0ea698addb557aaaa9f7bb0f65881b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000cbf", + "maxFeePerGas": "0x985853d82", + "maxPriorityFeePerGas": "0x9502f900", + "nonce": "0x291", + "r": "0x65b22975f661b92773169a581e3ba2c2a0bcbbc04efefecc785d3241e6c5fae8", + "s": "0x1a9656625b9e4ef157883dc25c4f54ef7362784db5d0475277e6ea2f7af58b95", + "to": "0x2b2e8cda09bba9660dca5cb6233787738ad68329", + "transactionIndex": "0x1c", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x70f275bb99e26c8275dab9ac843535f8e130bc87", + "gas": "0x5af81", + "gasPrice": "0x5c34b9496", + "hash": "0x5cf212436d87010da7047f4f70b39462339a023c38f9919aa16901711d168cde", + "input": "0x87201b4100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000134000000000000000000000000000000000000000000000000000000000000014e00000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000c2000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000728b3de78c3bd3e9c970524ca6aae1f8d6a61996000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000063b4a8a40000000000000000000000000000000000000000000000000000000063dd87240000000000000000000000000000000000000000000000000000000000000000360c6ebe00000000000000000000000000000000000000005fa3bd95ae4c6ddf0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000f36c296e87dd2d7adcef251a542561d4bbe07714000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002231e2f1f69000000000000000000000000000000000000000000000000000002231e2f1f690000000000000000000000000000728b3de78c3bd3e9c970524ca6aae1f8d6a61996000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa1c6d5030000000000000000000000000000000000000000000000000000000fa1c6d50300000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e871b540c0000000000000000000000000000000000000000000000000000003e871b540c00000000000000000000000000007caa2d25de293e2329795a7c9990fc39ac35f3e3000000000000000000000000000000000000000000000000000000000000004131139c95c57555b8e604959411b35701e099f035d945e8b2b15dd8f8a0ca81a30dfd82b8dbf62244d8333a8dd4eaf0ae0712ef0cadb5649631d01f064fad87aa1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000389458f93e387fc568ca4568c231a64ffd0456d2000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000063b4a8b10000000000000000000000000000000000000000000000000000000063b4b6c10000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000bc08140cb26035f40000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000f36c296e87dd2d7adcef251a542561d4bbe07714000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001118f178fb4800000000000000000000000000000000000000000000000000001118f178fb48000000000000000000000000000389458f93e387fc568ca4568c231a64ffd0456d20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007d0e36a8180000000000000000000000000000000000000000000000000000007d0e36a8180000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f438daa060000000000000000000000000000000000000000000000000000001f438daa0600000000000000000000000000007caa2d25de293e2329795a7c9990fc39ac35f3e300000000000000000000000000000000000000000000000000000000000000418d801d85d1c2f38162b8ae99a80683132ba3b682ffd4e92d43f01c5cbf935d391d4d86b734c051a2bc1296cd6b97e396d8f58dbb31fe9457a1308a19e555cc661b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000b5e74d76326474d56d4f467ea483476f19904716000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000063b4a8290000000000000000000000000000000000000000000000000000000063dd86a90000000000000000000000000000000000000000000000000000000000000000360c6ebe00000000000000000000000000000000000000000afd46623cd00ae20000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000f36c296e87dd2d7adcef251a542561d4bbe0771400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000114aae65fe230000000000000000000000000000000000000000000000000000114aae65fe23000000000000000000000000000b5e74d76326474d56d4f467ea483476f199047160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e7a02ea010000000000000000000000000000000000000000000000000000007e7a02ea010000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f9e80ba804000000000000000000000000000000000000000000000000000001f9e80ba8040000000000000000000000000007caa2d25de293e2329795a7c9990fc39ac35f3e30000000000000000000000000000000000000000000000000000000000000041cb5a81b350877b73819e32ecc51358ce660c06eb40ff7d726551ddc0fdce08497be42e9b0ca1c79213dc649591671556d8253ff259b85bcb21aba7e6c1bdb07d1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x9502f9000", + "maxPriorityFeePerGas": "0x9502f900", + "nonce": "0x293", + "r": "0xbe2562d8e0779cf0632b38c288a235c52fcb5d2504f7591e8aebc2c2c41e7eb", + "s": "0x6680c7025b6c772c5d196f49e02275a25dd8e61be9d5e3bb6758ebac5347a363", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x1d", + "type": "0x2", + "v": "0x0", + "value": "0x3ad7818917a8000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x6d7108d8d35d8bd9c1b01fd6957cdf646c2fee37", + "gas": "0x12c41", + "gasPrice": "0x5c34b9496", + "hash": "0x69032a099ff19609a0a0beb8606d7a21b057e95475b37cd9b46508a6d953af0e", + "input": "0x6ce5d95702852505832c94f341305656fc21f714bfc64251b2a86a3f6690eec08373b2c600b333e3142fe16b78628f19bb15afddaef437e72d6d7f5c6c20c6801a27fba600000000000000000000000000000000000000000000000000000000001041bd", + "maxFeePerGas": "0x8d6043e4a", + "maxPriorityFeePerGas": "0x9502f900", + "nonce": "0x53", + "r": "0x89f3e86af227395f7118d0cf3d24ced50346e89cffc31dfbff8b681562e8296c", + "s": "0x66e2afb2a090afeabb61c9e57f9f32ab4b9a4a3fb6c71cf1088dde17dc50e504", + "to": "0xf5c9f957705bea56a7e806943f98f7777b995826", + "transactionIndex": "0x1e", + "type": "0x2", + "v": "0x0", + "value": "0xaf6a4d07c8f0000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x0d854a6beee7b9539b09d0db27e6e2b420a4cbe9", + "gas": "0x8617", + "gasPrice": "0x5bc248696", + "hash": "0x3060b7bd17f2748a9f523f02710c26f2d40d44158aab927ce0bd43875bd532d9", + "input": "0xfe1c9ef70000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008abc27acb0db1443cdba7564a9ea7c6eb64eaab90000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000002e2f6e5e148000", + "maxFeePerGas": "0x81447fb00", + "maxPriorityFeePerGas": "0x8ddbeb00", + "nonce": "0x123", + "r": "0x5c4b640b3e99a24d8bc03233339b23ea2a3de54dd7f79215b90c577c4c8f4ab9", + "s": "0x2fd5784ddb54d5d75a13a127cf3fd762549c528c345faafcd23c83083f63b83d", + "to": "0x1a90b3dead0113740266b7f7ea1136e8ed1b48c5", + "transactionIndex": "0x1f", + "type": "0x2", + "v": "0x1", + "value": "0x2e2f6e5e148000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xeea71e3cf712e5cdcbcdf8ef275d5896aa3b6a5e", + "gas": "0x725e3", + "gasPrice": "0x5b54b271d", + "hash": "0x102034609faeef17dd41bd9549637be6ca439de50875c669ccfc1720dac09cf9", + "input": "0x12aa3caf00000000000000000000000053222470cdcfb8081c0e3a50fd106f0d69e63f200000000000000000000000002fdb5d28d01be42e941e7a195a5694d996064283000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000001e0eb9db917ea63b1d5dd0caa456dcfffd704707000000000000000000000000eea71e3cf712e5cdcbcdf8ef275d5896aa3b6a5e00000000000000000000000000000000000000000000005150ae84a8cdf0000000000000000000000000000000000000000000000000000000de7162504c6263000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c30000000000000000000000000000000000000000000000a500008f00005300206ae4071118002dc6c01e0eb9db917ea63b1d5dd0caa456dcfffd70470700000000000000000000000000000000000000000000000000000000000000012fdb5d28d01be42e941e7a195a5694d9960642834101c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200042e1a7d4d0000000000000000000000000000000000000000000000000000000000000000c0611111111254eeb25477b68fb85ed929f73a960582000000000000000000000000000000000000000000000000000000000077e1b5df", + "maxFeePerGas": "0x5e476922a", + "maxPriorityFeePerGas": "0x87028b87", + "nonce": "0x466", + "r": "0x7063ffcc003b69d209a7eafbe70b20d8c055bb34d1c874c2772a146ff2282330", + "s": "0x3c578fe66afda6025d0bbaa0aaa54f6ed87c1a0a52ded6c00a86808cb903284", + "to": "0x1111111254eeb25477b68fb85ed929f73a960582", + "transactionIndex": "0x20", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x1a8ed0d3ad42c9019cc141aace7e5fb6e576b917", + "gas": "0x27100", + "gasPrice": "0x5aa2be857", + "hash": "0x24a48bb3805173872211f14d4ccb799115e5177746fe9d08936c8aa86767c3a7", + "input": "0x46f83b50000000000000000000000000000000000000000000000000000000000000006ca65a1bd6a752b571a5e694fe9f47ceb7d18eb07cd547faf9710d4056d7b8868e00000000000000000000000000000000000000000000000000000000000f9adaa65a1bd6a752b571a5e694fe9f47ceb7d18eb07cd547faf9710d4056d7b8868e", + "nonce": "0x13df5", + "r": "0x35e675637625360fbf9b33436e281b77cf9401229e592c04c5f897e7d5570f41", + "s": "0x1d1e00ebd339704b8310ef981c1be373f4e66155a11b6c30863006420269a1a", + "to": "0x5a54fe5234e811466d5366846283323c954310b2", + "transactionIndex": "0x21", + "type": "0x0", + "v": "0x26", + "value": "0x0" + }, + { + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe93685f3bba03016f02bd1828badd6195988d950", + "gas": "0x1771f4", + "gasPrice": "0x5aa2be857", + "hash": "0x5b093bbf8b2b45e56057c2d6cc97ef9e8c774667a1cb52c6fe48b56d530160e8", + "input": "0x0508941e000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000296f55f8fb28e498b858d0bcda06d955b2cb3f970000000000000000000000000000000000000000000000000000000000126308e5e8d968de9c8d90808f11525eedf20d1ff93dd01e33a6b7b810645bb85f74ef1ffbfa8a6f31a39847ebb832605924459ec539dfa993c59cbc336591e690a65a00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000082779b0c125cba6dc6071b90e19ee010fb6daa240000000000000000000000000000000000000000000000000000000000000ba000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000b4000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000053f851a04139a4bb44cbe15fd6d5d8716e372d6248fe72d99a2ac4e3c9e916baa527045080808080808080a0b0f51de7909653fadedb604e425c3ae1245d466325c8296662c289fa327eeeea80808080808080800000000000000000000000000000000000000000000000000000000000000000000000000000000000000009d0f909cd31b909c902f909c501831086acb901000000004000000000400000000200000000004000000000002000000000000010000000280000002000000000001c000000000000000000100040000000200000004010000000000000000008000000001000000000004000000000000000000000040000000000011080000000000000000000000000400000000010000000000000000400000000000000000000000000000000280000000000008000000000020000004100020000000004000000000020000000000000000000000000000000029002000008000000000000010000000000400000000000000000108010001010800000000000000000000004000010010000000000000000080008000000f908baf9013a94b6cfcf89a7b22988bfc96632ac2a9d6dab60d641e1a034660fc8af304464529f48a778e03d03e4d34bcd5f9b6f0cfbf3cd238c642f7fb901000000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000000000000200000000000000000000000082779b0c125cba6dc6071b90e19ee010fb6daa240000000000000000000000000000000000000000000000000000000005f3a5b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb5000000000000000000000000000000000000000000000000000000000000de760000000000000000000000000000000000000000000000000000000000000000f89b94fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000082779b0c125cba6dc6071b90e19ee010fb6daa24a0000000000000000000000000b6cfcf89a7b22988bfc96632ac2a9d6dab60d641a00000000000000000000000000000000000000000000000000000000005f48fdbf89b94fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a000000000000000000000000082779b0c125cba6dc6071b90e19ee010fb6daa24a000000000000000000000000053bf833a5d6c4dda888f69c22c88c9f356a41614a0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0b7024f8b994b6cfcf89a7b22988bfc96632ac2a9d6dab60d641e1a06939f93e3f21cf1362eb17155b740277de5687dae9a83a85909fd71da95944e7b880000000000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001754bc36365f85894177d36dbe2271a4ddb2ad8304d82628eb921d790e1a0df21c415b78ed2552cc9971249e32a053abce6087a0ae0fbf3f78db5174a3493a0000000000000000000000000000000000000000000000000003134be47c77c4ff8f9944d73adb72bc3dd368966edd0f0b2148401a178e2e1a0b0c632f55f1e1b3b2c3d82f41ee4716bb4c00f0f5d84cdafc141581bb8757a4fb8c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000056000200000000000000000000000000000000000000000000000000000000000320c8000000000000000000000000000000000000000000000000000c60280df1c10082779b0c125cba6dc6071b90e19ee010fb6daa2400000000000000000000f8d994a0cc33dd6f4819d473226257792afe230ec3c67fe1a04e41ee13e03cd5e0446487b524fdc48af6acf26c074dacdbdfb6b574b42c8146b8a0000000000000000000000000000000000000000000000000000000000000006500000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000014000000000000000000000000352d8275aae3e0c2404d9f68f6cee084b5beb3dd0000000000000000000000000000000000000000000000000010c774002d3f34f902da944d73adb72bc3dd368966edd0f0b2148401a178e2e1a0e9bded5f24a4168e4f3bf44e00298c993b22376aad8c58c7dda9718a54cbea82b902a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000254000000000000512d006e352d8275aae3e0c2404d9f68f6cee084b5beb3dd0065296f55f8fb28e498b858d0bcda06d955b2cb3f9700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001754bc363650000000000000000000000000000000000000000000000000000000005f3a5b00000000000000000000000000000000000000000000000000000000000000bb500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de760000000000000000000000000000000000000000000000000000000005f48fdb00000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001482779b0c125cba6dc6071b90e19ee010fb6daa240000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f87994352d8275aae3e0c2404d9f68f6cee084b5beb3dde1a08d3ee0df6a4b7e82a7f20a763f1c6826e6176323e655af64f32318827d2112d4b8400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000512d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "nonce": "0x14c3f", + "r": "0xeafbf4130c395a39e3b32d7a2c0a4f3784a40fcb77dcce4eeb3002b108314efd", + "s": "0x166ac19070e233e65599244cc543ede172584a37cc2f22e0758b3ced3e118870", + "to": "0x902f09715b6303d4173037652fa7377e5b98089e", + "transactionIndex": "0x22", + "type": "0x0", + "v": "0x25", + "value": "0xc60280df1c100" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x33a76a7cba9627805e3c68f3e6f0b34b8c02091e", + "gas": "0x5208", + "gasPrice": "0x5a8792016", + "hash": "0x26cbcc39af3b292d5be1244229ef0869a38f7276a6ec154b2bdeb09db551c798", + "input": "0x", + "maxFeePerGas": "0x77e13d500", + "maxPriorityFeePerGas": "0x7a308480", + "nonce": "0xc", + "r": "0x5df5bba29c045c5272536356187e6b2b98181c3a9d961f10b760ffa0453b2adb", + "s": "0x2059e60781cb4f642ad46362caf3a2a5c991f68a80e0c47662a62cfd799b0ea7", + "to": "0x44c17fefddba2a5bb784f5351217b1768c1ffd8e", + "transactionIndex": "0x23", + "type": "0x2", + "v": "0x0", + "value": "0x2aa1efb94e0000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe6f79f8b46b30f293cfbde50ef787d2fe0610782", + "gas": "0xc350", + "gasPrice": "0x5a58d71d6", + "hash": "0xc037626e46220883a6e02d796a3a8988fcae563e999ab5488fd22ed679b9f374", + "input": "0x", + "maxFeePerGas": "0xe8990a4600", + "maxPriorityFeePerGas": "0x7744d640", + "nonce": "0xff29", + "r": "0x84720b707f16f9156015b2b02c1bfe170c7054b0a6476b45c006a452d717cf39", + "s": "0x149bd94c779a2fd07a985b372007cf9d4e20553644479c4952e929a792d08689", + "to": "0xab17268904a9c61de47e059f3cdaffc125091013", + "transactionIndex": "0x24", + "type": "0x2", + "v": "0x1", + "value": "0xed750badb780f80" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xfe2d290b73101568ae8a4d333d1d684e8358ed8e", + "gas": "0x780c", + "gasPrice": "0x5a57e2f96", + "hash": "0x0ffd3974a06025ebf57ffd9f451c9df3ac0f08a16771c7545c7ceb9388dd526e", + "input": "0xd0e30db0", + "maxFeePerGas": "0x810711b09", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x270", + "r": "0xad7542d74c6f98ff3db2dc8e849e1f4746c8abf502f66248f18a1b964ac28f4a", + "s": "0x23cd068f94a8cc57226ae500af4603937217e76fcc7a612201d167be7a9b289", + "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "transactionIndex": "0x25", + "type": "0x2", + "v": "0x0", + "value": "0x470de4df820000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xc6500367885952e216373ed85fe5c5775d530bd9", + "gas": "0x5208", + "gasPrice": "0x5a57e2f96", + "hash": "0x3ffda281ae60ff13a9ac2679f222e185ba3b51f0efeb33ad8ca5016b17357a58", + "input": "0x", + "maxFeePerGas": "0x810711b09", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x68", + "r": "0x25a3291568c3c4304f5522285a136b8a10fd4c45f8fac7cbb28e6164db3df4a3", + "s": "0x4b055e1d702d0a77dafc48b438b4836d2d54a31d2aac102228d2a7b0e9618860", + "to": "0x0dd045813a2e81d4201bd7397d149ba0820a7cfb", + "transactionIndex": "0x26", + "type": "0x2", + "v": "0x1", + "value": "0x470de4df820000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x8d56f551b44a6da6072a9608d63d664ce67681a5", + "gas": "0xc350", + "gasPrice": "0x5a57e2f96", + "hash": "0x202249ed65903f2706932b5f1bbccea0b77d99283da2428e7eab6de750acf54b", + "input": "0x", + "maxFeePerGas": "0x174876e800", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x2c68d", + "r": "0xe628f1a69081613ef92209311d9e2f9d20a9e3b8d6740cb864b19f3cb2377b84", + "s": "0x7e027b667322ebe765db953e96dcbb8f2e8cd07ce51b8af05a975fe1c9d1f0c", + "to": "0xb7b18576c2a22c49f1172ae068f83f26cc6ca3cc", + "transactionIndex": "0x27", + "type": "0x2", + "v": "0x1", + "value": "0xf024bb00b67800" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x70e03a2ee2e0d02bba11f0a8cddba5805be93a10", + "gas": "0x5208", + "gasPrice": "0x5a57e2f96", + "hash": "0xde72fc3be8acbc323e6dee4d8c833740c1592ca422037621ba83900753773d65", + "input": "0x", + "maxFeePerGas": "0x5b0480f7f", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x24", + "r": "0x31a9d1e2b207bb3e02c656384368369e79a0eba015916e2ca562702fe30e1747", + "s": "0x416fec3cb0856c253640925b5239b737f9bf64b30f0e340c3260ee062279ed0", + "to": "0xb36a84c4d59469c053dae6303ef84a48447e4605", + "transactionIndex": "0x28", + "type": "0x2", + "v": "0x0", + "value": "0x17330fe7f04a3bc" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x22b168ae0855ba7b3d05cfed12ad9ce9258c2162", + "gas": "0x39af3", + "gasPrice": "0x5a57e2f96", + "hash": "0x3bcc9c1a41f75ca4fb7d020833c3e62217585131ca2992b15ea6d654a2424de7", + "input": "0x5f5755290000000000000000000000000000000000000000000000000000000000000080000000000000000000000000582d872a1b094fc48f5de31d3b73f2d9be47def1000000000000000000000000000000000000000000000000000000204855402000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000136f6e65496e6368563546656544796e616d69630000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000582d872a1b094fc48f5de31d3b73f2d9be47def100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002048554020000000000000000000000000000000000000000000000000035b01a20ded293000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000007bcc661fcc95a000000000000000000000000f326e4de8f66a0bdc0970b79e0924e33c79f1915000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a8e449022e0000000000000000000000000000000000000000000000000000002048554020000000000000000000000000000000000000000000000000036296cade981778000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000012000000000000000000000004b62fa30fea125e43780dc425c2be5acb4ba743bab4991fe00000000000000000000000000000000000000000000000081", + "maxFeePerGas": "0x810711b09", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x16c", + "r": "0x3275ad329d2ab6306d5a672c4e178fa8abb9b4f84de5f3a6011d7489d3dc846a", + "s": "0x1517b36f48d1a61b54462c9e27ceaf1ccc73947b60a443de2cdc245fdc125b6a", + "to": "0x881d40237659c251811cec9c364ef91dc08d300c", + "transactionIndex": "0x29", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xdfd5293d8e347dfe59e90efd55b2956a1343963d", + "gas": "0x32918", + "gasPrice": "0x5a57e2f96", + "hash": "0x82d4a0b80970192002fcb5e2631faa9574d70376f621bfcd5065d19289837305", + "input": "0xa9059cbb0000000000000000000000006854610b1e1591e2bc09ca554b9d9c7535db2e9b000000000000000000000000000000000000000000000314fa280b4909ae0000", + "maxFeePerGas": "0x17bfac7c00", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x4ca0b5", + "r": "0xbbba4b3e57d9019ff1b5a20d2197d6bb39844f69fefab0b2401aa05ee1972e30", + "s": "0x64d192ae0fbc1714dc3d9df6b344ad79c64d7627a721258799a6ffff908291be", + "to": "0x31c8eacbffdd875c74b94b077895bd78cf1e64a3", + "transactionIndex": "0x2a", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x28c6c06298d514db089934071355e5743bf21d60", + "gas": "0x32918", + "gasPrice": "0x5a57e2f96", + "hash": "0x9f3301eebab153a19ddbe9c98de537dfb6ae81aa676aac1e9747e740154e320e", + "input": "0xa9059cbb0000000000000000000000008c6f009fc5a04035d11238deeffe55632304dea100000000000000000000000000000000000000000000000000000000070ea400", + "maxFeePerGas": "0x17bfac7c00", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x5486c4", + "r": "0x4a5ca369b089a09d2c2a6349548c8222b556170d0364a99d54fea8e8eb42607", + "s": "0x74a05f13d27abbaa96df3d22bd07c0246d6159d2c43fd99a8b8ec3a84c77f13a", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "transactionIndex": "0x2b", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x21a31ee1afc51d94c2efccaa2092ad1028285549", + "gas": "0x32918", + "gasPrice": "0x5a57e2f96", + "hash": "0x9d41092b1540ea20aea10f36723e0be16e3140ff003652ac912da59607074cfc", + "input": "0x", + "maxFeePerGas": "0x17bfac7c00", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x5061d6", + "r": "0x81986453ca0014ee1ec69cb9b4c86b354d8bad9b620df776727b123d6f295778", + "s": "0x22180777f2c8f627c8fa16fad29eda5161ead71e25f2cac30a71c3bb7d4ff4b3", + "to": "0xb7542735bcad39f7294f981ed0adfe887ca18225", + "transactionIndex": "0x2c", + "type": "0x2", + "v": "0x1", + "value": "0x124cd302d33b800" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x56eddb7aa87536c09ccc2793473599fd21a8b17f", + "gas": "0x32918", + "gasPrice": "0x5a57e2f96", + "hash": "0xd263dfc881e67373c28e8ad57e8de1355da6d9fad27c81087c43cdf7feabde37", + "input": "0x", + "maxFeePerGas": "0x17bfac7c00", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x3e29a6", + "r": "0xee66c15a69bf2ff4b0ddf3e18576a2490de4518a2f7da879615950540b502cd", + "s": "0x51a89acbd0ba7a71e19bc33a6f2b09ff25717b4d095d3c069e6ccf6be898e9d9", + "to": "0xb9e745d48ee5b315f11d05cababcbac1090d4a31", + "transactionIndex": "0x2d", + "type": "0x2", + "v": "0x1", + "value": "0x1091e393c2ee800" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x9696f59e4d72e237be84ffd425dcad154bf96976", + "gas": "0x32918", + "gasPrice": "0x5a57e2f96", + "hash": "0x0e1a35f6d7d68f7dadfcb0048be2117806adb5ec7bac518c2b88760d197bd4ee", + "input": "0x", + "maxFeePerGas": "0x17bfac7c00", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x38f51c", + "r": "0xb62c4aff6e0598daceb6844bbae99f9bdb2cde8ab97806e5846ecee694f041f2", + "s": "0x76f1c4ff50d4bb586fe279a8964dda1ead3e67529ff30d7e15d9c115c13c9c0a", + "to": "0x0f12194998ab749b0e443300c442a3e5204cfedb", + "transactionIndex": "0x2e", + "type": "0x2", + "v": "0x0", + "value": "0x349501a766eb400" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe7e435eaab31452296f24de4a6635293cdba6148", + "gas": "0x262ac", + "gasPrice": "0x5a57e2f96", + "hash": "0xe13a21d78e5ca311c8ef5a21282c77a337173abb3e6434b195cedb14afd18cc8", + "input": "0xfb0f3ee1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020dcd3742c20000000000000000000000000000ba7933402348a902064499ed883c49843eeb7019000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000003f64bced4cc5e47f2262791d4e1caedabac59bb900000000000000000000000000000000000000000000000000000000000011e9000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a8760000000000000000000000000000000000000000000000000000000063dc35750000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000457f33e9d7da74770000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000e35fa931a00000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000001c6bf526340000000000000000000000000000e51518bc4fb1a359a387b92ffd093fba59219cec0000000000000000000000000000000000000000000000000000000000000040972c9e8d6ea15fd6b3b07e7bf8a19acd4209d9e36672d68f31129b45288b5fb0b219f927e990ecb0830c4640685adc37c7a7286790a7c993e3b0b7e58340a487360c6ebe", + "maxFeePerGas": "0x810711b09", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x3d", + "r": "0x3b41296d1fcf5ede64303f79da60390d21047a82e510fe81b45276a1ed9ec0a0", + "s": "0x1fb1b9da81caddd3477aaa402c3c480970ff0fbc9c92bb26e946d0a4ca122917", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x2f", + "type": "0x2", + "v": "0x1", + "value": "0x2386f26fc100000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xbf275a0bcffc645aa329893e788b3b4daaf69fa5", + "gas": "0xc186", + "gasPrice": "0x5a57e2f96", + "hash": "0x6cdb7a54582e7ef4283c3334cd41bcee233135452d9cd2403112661df3cd1ce0", + "input": "0xd0e30db0", + "maxFeePerGas": "0x77b43754c", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x56", + "r": "0x426d2f892c4793060cf04a19d0683b6516a11a60bfe9412c6b7de5f2842a1d8", + "s": "0x24b89994256a2ab9ec08cdad851ab6b1e211d4a0417439193336f62d37ec8a82", + "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "transactionIndex": "0x30", + "type": "0x2", + "v": "0x0", + "value": "0xb1a2bc2ec50000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x0639dda84198dc06f5bc91bddbb62cd2e38c2f9a", + "gas": "0x67fcb", + "gasPrice": "0x5a57e2f96", + "hash": "0x3f6bbf788e4be1791ccb4857db6394a1db812e81c29426e93937ec2876c5d074", + "input": "0x9a1fc3a7000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000001c1d1b77e9855765dfb05165c56bd1982d06f6b8e19fd723a5e5e7245ade76c7bc2bf265de121103d02071188ff856cdb8ec17aae9242bdbe20db49543852431f7000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f929e400000000000000000000000075331ebbe0b00b97cab532384f13c9b479f074ec00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000dab4a563819e8fd93dba3b25bc3495000000000000000000000000eb7088423d7f8c1448ef074fc372bc67efa4de440000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001083bab1f2b700000000000000000000000000000000000000000000000000000000000063b4a5640000000000000000000000000000000000000000000000000000000063bddfe300000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000004043c946c612fd1da6eaaf96271ffafd0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000320000000000000000000000005178b38849eb9200ad681309ee4a6d5b00c23e51000000000000000000000000000000000000000000000000000000000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001b444a07cc20521b042485a75ce6e51bea0b73627a8c65866224c196796e6ee4ca48b280b875b803667493bf286f4ef5dd576dba0718eb203aa6c277b2dc7b97c800000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f929e40000000000000000000000000639dda84198dc06f5bc91bddbb62cd2e38c2f9a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dab4a563819e8fd93dba3b25bc3495000000000000000000000000eb7088423d7f8c1448ef074fc372bc67efa4de440000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001083bab1f2b700000000000000000000000000000000000000000000000000000000000063b4a5650000000000000000000000000000000000000000000000000000000063b4b77d00000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000fe1c4accc687e48f9a41bb8a08508ef100000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001cf770c78524a0fea097a82c4f938e85b5c678d972aa3f539e1687cef76d7be4ad44713b0089a43d8dfb6a1968b8066273d90471094bc098ce4ee6c56c76c85ad1", + "maxFeePerGas": "0x810711b09", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x8b", + "r": "0xff54c5f6c9430f43cafa047863b2951f1fb0a79dbc7f549c5cebd0e6ab14572c", + "s": "0x21578605d63a7ca507cf973ed56616c1e4137e29a972d74b7bae021fcf8bd6bc", + "to": "0x000000000000ad05ccc4f10045630fb830b95127", + "transactionIndex": "0x31", + "type": "0x2", + "v": "0x0", + "value": "0x1083bab1f2b70000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x583066bd66c6e48421202af9867777930d1696d0", + "gas": "0x31fbf", + "gasPrice": "0x5a57e2f96", + "hash": "0xa370800502078fc108ab13c8eafb1807d01c506ab1acbbe3a4d636119cb16a4c", + "input": "0x5f57552900000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006e2255f409800000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000136f6e65496e6368563546656544796e616d69630000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000549020a9cb845220d66d3e9c6d9f9ef61c981102000000000000000000000000000000000000000000000000006d2ba2a4244c000000000000000000000000000000000000000000000008ade5e83ac3d0a346de00000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000f6b34fe53400000000000000000000000000f326e4de8f66a0bdc0970b79e0924e33c79f1915000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c80502b1c50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d2ba2a4244c000000000000000000000000000000000000000000000008ade5e83ac3d0a346de0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000180000000000000003b6d03408c07e1dfede38b1908698988b4202a87e0d7a0f7ab4991fe00000000000000000000000000000000000000000000000055", + "maxFeePerGas": "0x810711b09", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0xd3", + "r": "0xe55a7bf49d1489633c33264723d011873a113f8aa90c50ff9b7a4b501e4aa5ef", + "s": "0x77bdda1c617b7871622113a1f23bafeffb230c7f05b7ed7e3d38900be53e5f91", + "to": "0x881d40237659c251811cec9c364ef91dc08d300c", + "transactionIndex": "0x32", + "type": "0x2", + "v": "0x1", + "value": "0x6e2255f4098000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x49e6ca573ee3802d206f541c517b65a41159e5c4", + "gas": "0x5208", + "gasPrice": "0x5a57e2f96", + "hash": "0x8e51a94f775f8083f56000104cca4a413c1f5e28d6381b264ecfbef2bea5c380", + "input": "0x", + "maxFeePerGas": "0x6ddb3f64c", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x202", + "r": "0x520b16ea95707ed418b2a758322c3ad08ebf1e96f01ddedbcc0c8086a3863728", + "s": "0x50c9d87e94fcffdb8bd77ccb10876c556dc75968a4439785f77299683a0d37e4", + "to": "0xe000fbeedd9bc012b68fe716ad67396ea37e9517", + "transactionIndex": "0x33", + "type": "0x2", + "v": "0x0", + "value": "0x24d6265416a71a9" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xac3c837d67b4f638c0abbf711327cfd33149cf3d", + "gas": "0x379e4", + "gasPrice": "0x5a57e2f96", + "hash": "0x004eedb66678514fe0b9aaf100c08743b48a4b6affa3046b1846a7302bc7f980", + "input": "0xfb0f3ee100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002958d0227a0200000000000000000000000000067ca64e6d0a97bae9d85b6b005a0ccdd876ab896000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c0000000000000000000000000087463f3e25adeeb33bc03b8a7dce6fa60f160c8c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b46cbd0000000000000000000000000000000000000000000000000000000063b5be3d0000000000000000000000000000000000000000000000000000000000000000360c6ebe000000000000000000000000000000000000000010e8b3a4af625f640000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000012605c8119c8000000000000000000000000000000a26b00c1f0df003000390027140000faa71900000000000000000000000000000000000000000000000000372115834d58000000000000000000000000000f5d5751d4f300c3d0adbcadc05f0af4f8baf78800000000000000000000000000000000000000000000000000000000000000419918be69434853027772a2f6aa925c762ee2e8688dc849db72a5cf9f1e906ed62018dfde76ad573126bb09ea0cc768e5ac6c7e951693598dc64db8edf3214d731c00000000000000000000000000000000000000000000000000000000000000360c6ebe332d1229", + "maxFeePerGas": "0x1176592e00", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x7db", + "r": "0xc2b2281e82cd8c3162d34ac3392514f9db5e0bac14caf9ab1c4d6beea31b2e28", + "s": "0x37717e00bf37fbf1b7e5e1fb94017d48ddf80f81c34d6f13f9d1dbbb98dc7a91", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x34", + "type": "0x2", + "v": "0x1", + "value": "0x2df0e742c074000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x51965ad51816c62c58106c1a3e8fa3b8a4af7e07", + "gas": "0x9ace", + "gasPrice": "0x5a57e2f96", + "hash": "0x73a9e0d71ec29d8acd413372d1842f68eff21b47a2a86539fe8a33fe98221125", + "input": "0x2e1a7d4d0000000000000000000000000000000000000000000000000a2f31c826de4000", + "maxFeePerGas": "0x77b43754c", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0xe", + "r": "0x79f7efc3c7cbe14ca34d747d385359714c251de2279e7ecc423987207dd463e", + "s": "0x208a7b97b28e12e35a88fc9bba942fa78e1d02c335a601cee6d77faf4dc94fdb", + "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "transactionIndex": "0x35", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x3fc4bc1bf9a27afe1f364fbe8e95ee64bfe40552", + "gas": "0x2bf74", + "gasPrice": "0x5a57e2f96", + "hash": "0x3b7bb5e9cfeaf425987590db0fdc1d0ae450560ff70a9a5775428131aac9379e", + "input": "0xe7acab24000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000006600000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000324e9e6e602fdf1f7f50decea6cb83fff575020f000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000063b4a8960000000000000000000000000000000000000000000000000000000063b89d160000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000fa44417fcc960aba0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000095c7df9bb4d1cf75b1069570df837917a4b32eb800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066461423d21e300000000000000000000000000000000000000000000000000066461423d21e30000000000000000000000000324e9e6e602fdf1f7f50decea6cb83fff575020f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ec0f343934100000000000000000000000000000000000000000000000000002ec0f343934100000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb03cd0e4d040000000000000000000000000000000000000000000000000000bb03cd0e4d0400000000000000000000000006b8c6e15818c74895c31a1c91390b3d42b33679900000000000000000000000000000000000000000000000000000000000000416db65c59c5c2aaea67a12f0d4a818fbf18a28b910c448641a9d3016b147ca4c6462f05ea0335f3fe12248224c185da8f9b2e53750147473d2c39eceb1195520e1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x649534e00", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x0", + "r": "0x3e0383ac732774115b351f406aedbcb3ed57f393f2832072aa7cfcefc3bc2f6d", + "s": "0x6c9ecc9593620854ec16ef2722535671eb7e125b00e86a1439de82a4d947d417", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x36", + "type": "0x2", + "v": "0x0", + "value": "0x10b2a005d92980" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe739957a1c212d646e08afad3611f7c3ac60bc81", + "gas": "0x5208", + "gasPrice": "0x5a57e2f96", + "hash": "0x9923decf5845730629b3417970224a38c0cd80d765f4efef76c6ab9f2ca0e071", + "input": "0x", + "maxFeePerGas": "0x607fd3421", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x16b", + "r": "0x7d257200b367d200be884aa8c760113ed180fa40ab8e4cdab52541a15e6a037a", + "s": "0x82bd7c10521aaa4fde60e7d1e53afd7fd43c22f71c2b39646955f76523e16a7", + "to": "0xb28554cc9a8220ad3d9a76727de5dfa7d797b381", + "transactionIndex": "0x37", + "type": "0x2", + "v": "0x0", + "value": "0xea93c7abcf006a" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xcad621da75a66c7a8f4ff86d30a2bf981bfc8fdd", + "gas": "0x13880", + "gasPrice": "0x599ba0b55", + "hash": "0xad84a9372116ae52308e3a2556cc1b9f3b2cc6255e7d4be326fbae049538c53e", + "input": "0x", + "maxFeePerGas": "0x9c7652400", + "maxPriorityFeePerGas": "0x6b716fbf", + "nonce": "0xecb81", + "r": "0x1e61223f227eb40a9acde6820ec074aace57ce690c6678e6ae597beeb4cdf897", + "s": "0x131c653824d5ac9ace26a05a9367ee43ac388154705c28424a5a5703674df0a9", + "to": "0x3a677323ea97649e80435be93b85cb77e016143c", + "transactionIndex": "0x38", + "type": "0x2", + "v": "0x0", + "value": "0xb69d414278fdc00" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf16e9b0d03470827a95cdfd0cb8a8a3b46969b91", + "gas": "0x186a0", + "gasPrice": "0x599ba0b55", + "hash": "0xcb7464479e2ec2424266ad76603eb015e0e338d39cc54c90d7a1782f94cc4211", + "input": "0xa9059cbb00000000000000000000000046413066e0bfade93e3acb9d7d9a0080039dc64c0000000000000000000000000000000000000000000000111380cf0ef80c0000", + "maxFeePerGas": "0x9c7652400", + "maxPriorityFeePerGas": "0x6b716fbf", + "nonce": "0x159d53", + "r": "0xc717686808b4f200d2aac9f6a3a372238a8dd59d86778583cc3938f88c1b6518", + "s": "0x1609bca4d38bdc7f0ef145f567a7ff9a52c2a53ddaad40fe740ed9e1f8729651", + "to": "0xf57e7e7c23978c3caec3c3548e3d615c346e79ff", + "transactionIndex": "0x39", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xcad621da75a66c7a8f4ff86d30a2bf981bfc8fdd", + "gas": "0x13880", + "gasPrice": "0x599ba0b55", + "hash": "0xef0c95ab59c501c6ebfee3519a18ee6e0e60a9289dfbfd252ce333dd7e7ea6ab", + "input": "0x", + "maxFeePerGas": "0x9c7652400", + "maxPriorityFeePerGas": "0x6b716fbf", + "nonce": "0xecb82", + "r": "0x8e1183896b01a4e38daeb22ba9f213de1624108a2ed1bcb78d3dd43fc254f6f5", + "s": "0x5e75d73acb6f96a6f41882b65098eeb48ca17e6ed96c6cb58383f27da78dc08e", + "to": "0xa0ffc53dee7999612ad1475da928c936b07de017", + "transactionIndex": "0x3a", + "type": "0x2", + "v": "0x1", + "value": "0x39d01dc7f72800" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xcad621da75a66c7a8f4ff86d30a2bf981bfc8fdd", + "gas": "0x13880", + "gasPrice": "0x599ba0b55", + "hash": "0x2473155be570ac4833d4134cc602c2ccdcdefc1181d1673e8aaad8545495926a", + "input": "0x", + "maxFeePerGas": "0x9c7652400", + "maxPriorityFeePerGas": "0x6b716fbf", + "nonce": "0xecb83", + "r": "0x3c852960a8efc1ed3db6925ecaca7d97258d6e27b38230639506ad294c6a6a90", + "s": "0x3f0d1db01193c45489538d9143b412e2239bdc594af3c95d3f81b3f410b701c2", + "to": "0x8f939545ac9d4496bd7a84f950169d979002cb08", + "transactionIndex": "0x3b", + "type": "0x2", + "v": "0x0", + "value": "0x470de4df820000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe32866870304e028d8771d2a21a2e3be3afcc683", + "gas": "0x50ae0", + "gasPrice": "0x59682f000", + "hash": "0x2af4f469aa86a3a6fabec02079f8a9c9fc8180b096bc8bd840810c464c5468bd", + "input": "0x5c11d79500000000000000000000000000000000000000000000000000c397155523fb26000000000000000000000000000000000000000000000007559eafd0c89c723a00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e32866870304e028d8771d2a21a2e3be3afcc6830000000000000000000000000000000000000000000000000000018579d135ba000000000000000000000000000000000000000000000000000000000000000300000000000000000000000045804880de22913dafe09f4980848ece6ecbaf78000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000feeb4d0f5463b1b04351823c246bdb84c4320cc2", + "maxFeePerGas": "0x59682f000", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x17a", + "r": "0x7211a183f0495421dafcc4b4f80b8ea7c14b9db01f0d9b17f884e84f1bf39f68", + "s": "0x25f6638d16be09ed29fe3930e0537e017a4898767b3648c4621e091fe25bd93f", + "to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", + "transactionIndex": "0x3c", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe3a6fbeeb58fe393e2132e29ba428a849d29efb9", + "gas": "0xdaa9", + "gasPrice": "0x59682f000", + "hash": "0xaba6a440faaa5fabb4a2ef85f40889d448409e0baa9e391d55ea1bc1f2b37d6f", + "input": "0x095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x59682f000", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x4", + "r": "0xcea7c10a26b844422252db166d003b1b418ed8d52fc0e07ae2b5c87fdd97ac49", + "s": "0x733cb40473201d72ba0087a43d6ad5afcf31eabd5e3e0e9a0bdf53b155ee1447", + "to": "0xe6f1966d04cfcb9cd1b1dc4e8256d8b501b11cba", + "transactionIndex": "0x3d", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x3f6cbb6ff4073d96f027b74bad6f82fd3ac8dd86", + "gas": "0x5208", + "gasPrice": "0x59682f000", + "hash": "0xc49c2a3c0782e29b6e25cb3d9d7ca40bfa4ca3f8dc8eda54784c65a3630c2a0d", + "input": "0x", + "maxFeePerGas": "0x59682f000", + "maxPriorityFeePerGas": "0x77359400", + "nonce": "0x0", + "r": "0x7bb90b2444f42e01893abc6104cfd7bf2bcb0b901962acc76a1a88a97302b974", + "s": "0x2858e490f5e4324c9cc5681c1635a02712a3f15b3012dd491d831f0b0a647aa6", + "to": "0x6aa6d1425da4349ab5c448526143013cba761358", + "transactionIndex": "0x3e", + "type": "0x2", + "v": "0x1", + "value": "0x290a83052d9108" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x70da4f0a3eabd72fdff018dc3f8786131115206f", + "gas": "0x5208", + "gasPrice": "0x587c00cd6", + "hash": "0x85361a2bc9293dae2df69f5a19475faa9170252885f59296192a5d5d92d61e5d", + "input": "0x", + "maxFeePerGas": "0xb35606637", + "maxPriorityFeePerGas": "0x59777140", + "nonce": "0x0", + "r": "0xc280682fbb7faa98673abcf613d823924c6bf13f542d9df7f5ce7ca30774bc78", + "s": "0x2d8a9322402c2a8ed9f8e7b783791a654ae28b32d24307511656ff6521ec82c0", + "to": "0xc298ac4d9951d4c2b730f7347a301e3ed8a762e5", + "transactionIndex": "0x3f", + "type": "0x2", + "v": "0x0", + "value": "0x719da7e7d89048" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x16ada39d140699d9a1934f05ddcf78612f342b3e", + "gas": "0xf36e", + "gasPrice": "0x587c00cd6", + "hash": "0xf3412287bd84a6c3b449a6a580bcfe2cdc208c59cfffdf422f0173bd7e025d04", + "input": "0xa9059cbb0000000000000000000000000ed0f180fc70291de898c67fa05f90bd3de9e59a0000000000000000000000000000000000000000000000000018289060790000", + "maxFeePerGas": "0xb35606637", + "maxPriorityFeePerGas": "0x59777140", + "nonce": "0x46", + "r": "0xc6d3d0a7cc160b1b7fa5ee2dd08dfa6a886162fffb8dee68509a448937ea75f3", + "s": "0x31d2cef0b94b824513f6a9e9aadda3b444a780479ff2fbcc6f31497fd19a9c9", + "to": "0x15d4c048f83bd7e37d49ea4c83a07267ec4203da", + "transactionIndex": "0x40", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x91aae0aafd9d2d730111b395c6871f248d7bd728", + "gas": "0x493ee", + "gasPrice": "0x587b0ca96", + "hash": "0x32c5e09796f6bc3bae9aaa2200aa030c28a5d86f5e9ebf45586da8e5d0e75e67", + "input": "0x49c36c070000000000000000000000000a55882f4e1117adc54afbbac4a39079ad52fb890000000000000000000000000000000000000000000000000ace650975e6b9800000000000000000000000000000000000000000180a0a9c5db3b500000000000000000000000000000000000000000000000000181dabab42964b00000000000000000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x5972a453d", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x622b3", + "r": "0x304f85f9c99c1ed0140176a174b3546f53ba9a01e853fc1e0101223c96e1c746", + "s": "0x70918693a9528655b682172767d3b55a606333b87c2be93815e9028fc4f19c1b", + "to": "0x98c3d3183c4b8a650614ad179a1a98be0a8d6b8e", + "transactionIndex": "0x41", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x3a0b433cdf858a29f3f31426d14dd5264700bd77", + "gas": "0x2f118", + "gasPrice": "0x587b0ca96", + "hash": "0x6b977831ea9a8b37cc4a9177653c9bf5c793fe75d9dc95ed9687ba399506eb99", + "input": "0xfb0f3ee10000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000129fd67e0521780000000000000000000000000b89d308480f43f8b1c66a6810ae7a304281a0622000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c00000000000000000000000000459ea67815b4720e55ec7dfd93687c9d2924eb790000000000000000000000000000000000000000000000000000000000000262000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b475a20000000000000000000000000000000000000000000000000000000063c02e800000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000d7badeea0f0a95110000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000007fc16d03b45800000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000d6a11d8b57f00000000000000000000000000e94a165107d59a9fc652240e1ab29972daab379b00000000000000000000000000000000000000000000000000000000000000410f8060f582c45b62177b2b0df989b3c3f63f111fccd7fae24260dfa16d6c342a278811278c774a5553f6fa685268e8d5b0638947d4049154dfc35ab7991932e61c00000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x458", + "r": "0x76f0dac5b736643617e6ed387b03fa8321720ba40af348dcd7246491e28cd960", + "s": "0x5c3d70ab34c4d6e13010b58927da1d1b4c4cd8cca931965225035cfd6b71faa1", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x42", + "type": "0x2", + "v": "0x1", + "value": "0x13f63908942dc00" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x9a9a4813e510fb0eeea07ed117158e261e02bebd", + "gas": "0xda90", + "gasPrice": "0x587b0ca96", + "hash": "0x365b56368963da972a1bcb5cdc4dd9d85ecf28a7cdab61897512bcfe9d1d09e4", + "input": "0x095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x4a", + "r": "0x7e0a38284b407afa4cd033836f25a9f14bcd145c2df80e14658f1e5ab58a4881", + "s": "0x4937521e06abf277ad6aa50338cc9e9b1eb61a9d77deb4fafc463fba21e625d3", + "to": "0x29119ae6c6a85984026cc5a135ce05d5453b7b53", + "transactionIndex": "0x43", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x604600ac12c103d0b2312a5b55723b8c81693f2d", + "gas": "0x39174", + "gasPrice": "0x587b0ca96", + "hash": "0x39feec318ee9d67623d094d0bacde831f45a3b0c3ae3d33c40f1aaafec70e480", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b05700000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f3000000000000000000000000000000000000000000000000001289efd00106a3000000000000000000000000000000000000000000000000000000142a9ce7530000000000000000000000000000000000000000000000000000000000000080000000000000000000000000604600ac12c103d0b2312a5b55723b8c81693f2d0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000024808959f85817683406282e3583c95d13a5956600000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x1e", + "r": "0xb8a89e005877bb86c0d53e4177479edd441d251b5594bcf0116b213d5dd9b166", + "s": "0x4b41e586a3d8703defb8a284102dd6cbeac7066151368224ab4bb1aaca992ed2", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x44", + "type": "0x2", + "v": "0x1", + "value": "0x1289efd00106a3" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe4f8cf094298a636c6bcf8f52de33472a91f8fb5", + "gas": "0x27fd7", + "gasPrice": "0x587b0ca96", + "hash": "0xa03ef85563c625053a3252e95629c5930e36fabb9dd969e1080719ae3a7a0c6d", + "input": "0xfb0f3ee10000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023f4161d2080000000000000000000000000006cab2859480cecd3875a8c0435bf571b18a51040000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c00000000000000000000000000ab45f7b8973a1b71366ea909c70b9ff707d78b7d00000000000000000000000000000000000000000000000000000000000001ea000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a8c10000000000000000000000000000000000000000000000000000000063b5fa340000000000000000000000000000000000000000000000000000000000000000360c6ebe00000000000000000000000000000000000000009c673bfa7eab6c9c0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000fa1c6d5030000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000002263e8a16d0000000000000000000000000002a49859f72d1de53e6dc9883691c67761d5e7ce900000000000000000000000000000000000000000000000000000000000000404551ec843f32ed6e4699603a98bb129d6eb12b6048c4de7748a624436ca878e8d4da8874189eeb6df03914a5e5fbd8e613dfb7893a40f559b48b9e1e07eba4f0360c6ebe", + "maxFeePerGas": "0x6c5eaeb31", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xc99", + "r": "0xc645104bf2fa5f515d741ab2fb02214642ca83c9bc944d77181a27c30a04fbb5", + "s": "0x1b489e0cfb558fd5068574d12a4c582ab2ce1ecc8cc9db88a72d7acfee0f6f31", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x45", + "type": "0x2", + "v": "0x0", + "value": "0x27147114878000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x6a7da4bd988e7b91530ff2a61384d238804b77c8", + "gas": "0xdd6d", + "gasPrice": "0x587b0ca96", + "hash": "0x281be809b66d4377c1866b07dd580e2f7b56364fab284b7b20ea1242fe4bc84f", + "input": "0x095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x70", + "r": "0xac309df11051bd819b0d38e84c3b8eac11d02f02e736b064cb6bf5e4c6d52fae", + "s": "0x5301cdff556a544b9e8264715cc46faaf0a95c340023b1f2fd66956e068f5e52", + "to": "0xfcd76e300b79d05832f12e6ed04d744868bb6264", + "transactionIndex": "0x46", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x9b134a26a5ec87ce1d468c4fd4ce1713494eb9d9", + "gas": "0x1f09d", + "gasPrice": "0x587b0ca96", + "hash": "0x672a692b47ef316dd24f876ac02dacb69b77a140df97ae89bcced4872ba9fd10", + "input": "0x97474f13000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000aafa1614dd8ff777dbd644bf64478339dcb595a939ca8d5abfb2c15e66594295110553dfa1ca9cea79a5afcb10cd233bc2c7e7a6fc1346c6ad0d47eabfc8c466f61a9595326d1dba243fa53c2cacae75cc959fe450a8381c17f37c64a7955a85ab6a34001d84c4ee1dcc0cf779489548e0cd61086022d46eac4bfd57bcff93def6682748d0bd8a457e62260b56dd313da14a5de13a81f99926411e3cdf5e71701fec2378664a4af7faedc90a2437de5c2973c6b46d9e2abfbcf7cda87d47751007a3c1d70446d78fdcca8a3871005f53fe08d0356cb9babb025c7d4ccda51fc8b2fb388bbf1021623ca029377b04d860843f08f86280a2e4ea343ca747763d25f53ee984e0c0ad05c4ff652e91b3656f52aa8de8b5bcdfe52ca8a0c18f617ab6226323dc47f84de1040c0734499dc996d36d4f6f99e0f3eb7d701bb17614b7108", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x26", + "r": "0xba0a688d8cd49f6b81cb7637d6ff33aa8d5e700e4e701a6b3b43ba69f9cd5a34", + "s": "0x48083bd552e49d2c32d4a9b79972bb0301390e6bb173c3321dba958a74f710da", + "to": "0x5940ead1a16a8994b1c42755d4cd2a5cb3269f5a", + "transactionIndex": "0x47", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x5aa5b193aaaa9269f8a7a1fd00af49f017e02a92", + "gas": "0x26c6a", + "gasPrice": "0x587b0ca96", + "hash": "0x99205cf958d30810c1afeb1a4ec59277a5ec47bf72fba70a2741f418dfba0ad3", + "input": "0xfb0f3ee1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b5483ef188c00000000000000000000000000cd87c481d7bedc824e03d01b96a2e103ebacd3e7000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000005940ead1a16a8994b1c42755d4cd2a5cb3269f5a000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a8970000000000000000000000000000000000000000000000000000000063dd87170000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000eb00daa2c62b01ab0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000b365d82e94000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000000000000004122eb0d38f6abe36abde95fd2152b9b326b076bfa920be4cbaf3abbc58ec959814519c67726fd2e16ad4ef6309d5bb46a897fa41d113a57bd113184fedc7f91c31c00000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x61", + "r": "0x8cd7f7cd6233e20f13599cd000a60ddd02dcbb151e4ea3336cbbc6c22a910bd7", + "s": "0x18189c772157aaed1a5b4e1592dae2965ccf6c99fcaa9d89c6737de3af4aae53", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x48", + "type": "0x2", + "v": "0x1", + "value": "0x1c07e9c7472000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x75bceb8ff952bc38405e791ef1df753cb797bd4b", + "gas": "0xd276", + "gasPrice": "0x587b0ca96", + "hash": "0x915cf2c80bce6c664a2a77c59bbc404937e3ac90cecce50e3d954e1acb9917ef", + "input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x9d", + "r": "0x8dfe402c102f23055b82b98d0978cf35f3b078faec855e6e95f5103907671ea7", + "s": "0x35ce5a7c8bb91bf8bb2c1bfdbf6deeb76ba2b7ec8a511a1bf019248529a54460", + "to": "0xb76fbbb30e31f2c3bdaa2466cfb1cfe39b220d06", + "transactionIndex": "0x49", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xa3a56dd2be92d2251f313a4387d111317a564080", + "gas": "0xed6b", + "gasPrice": "0x587b0ca96", + "hash": "0xa14f42e07f3bf8964f6a086d0857d96880ae34e43b16eeadbed582680b800f7c", + "input": "0xfd9f1e10000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a3a56dd2be92d2251f313a4387d111317a564080000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a3840000000000000000000000000000000000000000000000000000000063dd82040000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000e21d6c984625918a0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000ab45f7b8973a1b71366ea909c70b9ff707d78b7d000000000000000000000000000000000000000000000000000000000000017e000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e19f7a6ac8000000000000000000000000000000000000000000000000000003e19f7a6ac8000000000000000000000000000a3a56dd2be92d2251f313a4387d111317a5640800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b0028e44b0000000000000000000000000000000000000000000000000000001b0028e44b0000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003b66c05ca50000000000000000000000000000000000000000000000000000003b66c05ca50000000000000000000000000002a49859f72d1de53e6dc9883691c67761d5e7ce9360c6ebe", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x736", + "r": "0x672c205290b5b75deaf26e0349e311186ce40bf8454f30fe3e79483335ca09a0", + "s": "0x431465f9db992bafc701b83ec82715965200e5c862598699c39c6b938e13f4d1", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x4a", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xfbc32049de2dfd2900e7cd0ea0d243a42b5db1bb", + "gas": "0x14696", + "gasPrice": "0x587b0ca96", + "hash": "0x1345087a5858527d350460e87423b2068942d69e3f4927e470b3e60b2f24b33e", + "input": "0x62fb4e01000000000000000000000000fbc32049de2dfd2900e7cd0ea0d243a42b5db1bb000000000000000000000000000000000000000000000000a40f8801b73872810000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001cdb5069b377040000000000000000000000000000000000000000000000000000000063b4a98a000000000000000000000000000000000000000000000000000000000000001b4835b7dfb06ec9054ed6f03426745c669507a2d1ef10abed7d8b42660278c15e09c6c7300d7dbb5c28af52d75dfcd7484186a8fb815ebeff1c65158537cafb09", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x3", + "r": "0xf0b749d1387f0c7f23afc4969be1b39debf9939ee13fecbbcbf8e278cb50d124", + "s": "0x75a42aedbb8523f7a54dae52434909ffe85067146700cf3d141c4a32d549e748", + "to": "0xe7b0ce0526fbe3969035a145c9e9691d4d9d216c", + "transactionIndex": "0x4b", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x9e667903c73884064fc7bce6be6b9967644262eb", + "gas": "0x1d1db", + "gasPrice": "0x587b0ca96", + "hash": "0x6792e068a34aca68ab59b9e7f85587b73126ef32f83185f13bb22c5806e851eb", + "input": "0xfde5f54800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000f3a00fe578f776dbcb8647b67fcf03080644b73ecc5c65d67e461ebc89d35d043aa54fa3e0a6da095ec4795ec812dae2133e6cf33fe63643a47eec39357cda79698ff2b78dd86e44d6b19f454bd0463a841b890b011965f98860c19298b1319216c219791eacd94403a1ff42b24af0cfd39599f4552873214e43325e6e76223590370f00289d252a06a916da43eaae9bf61dca21cee2fb3bb7c1d7a63a0025ae0ee7d64e7c47079e95ea5a5e1e724e0b79d7cd8d8a26264351c47fb32c050071e0f65ca4061b3dfda487419f654b3a2730b800d703bce2cef235791db43356a0b30217752c30aa17f8b90703df5ea2e767d677912f085a930e4f74a5574f5ea11f9df1052106d9fb594ce11fe29dcb5dd1971ae06382bb92b7b010257a4b259eacc157781c1f7f3e82b98f643b093717bc9c58e257796efe0f2658f9fc4d62e5386795a3a72f08bd3cbd2d6c0b422eea451a76d542289ffce60aa239df65026731fc79b57bc044517f8714fc5d7d5f69db2a219e7e32d83b86372d328c345a8ee58d4ddbd2e991e13a9532fa3e31ac0884cd31f3f692d88bf88de2d981938126131f74fee1dac41b74e71e2cd6d3375ce37503b1f935f235aa9063e98095bee0f8a23b1c7dcbccfa5ce06631afd0b433c330e5c10623e8051e42d17758bfbaa47", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x0", + "r": "0x8e1610ff5e4de5c434d9a631ff01b970d15a56e389c654d41c43a4da2a7f6337", + "s": "0x6629a6f9631887cf40660f09ae19362bf037cb83ab8d954946cdc9e81d870c09", + "to": "0x77e2545d1d63856e22ce82e3d6f2a3b2077232bf", + "transactionIndex": "0x4c", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xdcf37d8aa17142f053aaa7dc56025ab00d897a19", + "gas": "0x19986", + "gasPrice": "0x587b0ca96", + "hash": "0x2fdb9cbbece5fe0604c8a68782efce5f3d5bf4aba69251c377d6651e76471edd", + "input": "0xefef39a10000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xa86", + "r": "0x1361076394fd3692ace0df6e2c66c1ee48ce62d57feb2ed29fa7d259212ab95e", + "s": "0x68f5882b167f6e464041aec277f86b29588f4e85aeae361e887b3f12d00706e9", + "to": "0xf330c09ad7b581a723d23079a316d5f3c5f1fe28", + "transactionIndex": "0x4d", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x03d2c287fa0a29a80c05497e42b83204481d4be1", + "gas": "0x8caf", + "gasPrice": "0x587b0ca96", + "hash": "0xa63857d3994f6cb074bb82dba58264ede2d593a79b411304e41e7956cc6cbc61", + "input": "0x2e1a7d4d000000000000000000000000000000000000000000000000058d15e176280000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x20f", + "r": "0xd246ed3f9d489cc01c09b3f39a3acff73e34cb31c4bd0093096daea787c626ec", + "s": "0x8ecda10d537e69e7cdc298752a8c7f6da208a3f31e16e43dd914f6d8a9734d1", + "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "transactionIndex": "0x4e", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xfcef3429049dedce74547c4a36a624fc94814003", + "gas": "0x146a3", + "gasPrice": "0x587b0ca96", + "hash": "0x903a7ef0e17cfd7903954d82d27a07963f559105fb3000768713d73d181e96b7", + "input": "0x62fb4e01000000000000000000000000fcef3429049dedce74547c4a36a624fc94814003000000000000000000000000000000000000000000000000a961bc0fb50b642b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001dcae8e82085560000000000000000000000000000000000000000000000000000000063b4a983000000000000000000000000000000000000000000000000000000000000001bbbce0476a3b664f377d9894abdfc325871f3be46554fb3d7bd36e9406d7dcfea5f32265ad9f16c15f31dac1e88ccdd3a813dfb7840cb8b02f268a44bdd27d288", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x7", + "r": "0x366324b9606124c538f55a485bf0546d5fcf9c1c79583c2f5a1425022e73b8e5", + "s": "0x7b837e1bdd2e46ccbe62f89c11ee93dc6b80d679853ad5a860a219ee031887bd", + "to": "0xe7b0ce0526fbe3969035a145c9e9691d4d9d216c", + "transactionIndex": "0x4f", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x1cdf33604e2306c198033a8cca3ca3e7e313ac97", + "gas": "0x3abaa", + "gasPrice": "0x587b0ca96", + "hash": "0x159aaaaf3dc3968d4884fb84c6a16c554329b81d42c73f0402efc389161640c9", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b0570000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000c4f3995c67000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000023c346000000000000000000000000000000000000000000000000000000000063b4b4ef000000000000000000000000000000000000000000000000000000000000001c9331214e8be6bc80c9c83534cb64791714507a3bd99b02569f337ad06cd0cd53223f4d1dc00261f537691aae423fe0921651b2e1855a4ad60707f1f47ff489550000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e404e45aaf000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000001f400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000023c3460000000000000000000000000000000000000000000000000006d6e0f426530006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c00000000000000000000000000000000000000000000000006d6e0f4265300060000000000000000000000001cdf33604e2306c198033a8cca3ca3e7e313ac9700000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x30", + "r": "0xcd69715bb4f3f8801111926e61c325e8f8d7d2e0a9c55f3c7e4e847cf878a561", + "s": "0x2a595e3e17fd1ad0f203d102515c6e02929e07c4499d0126fd0bd516f5e4ee6a", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x50", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x05f708722e5d9a3e3bd9360056cf3ac109fb4a81", + "gas": "0x1f084", + "gasPrice": "0x587b0ca96", + "hash": "0x389bc623761ff7989c67320a96518cc6c6d37e09952a1d84c5f80a63e3775c85", + "input": "0x97474f13000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000a667caffaebb0415aa026a19f47e3698a8eca17f9a62b578657a749d30c8756f6fdbd00261c7d4a431d185ef0ea831185fdd07ca86df8acc6b287de34d437bcaeb2407a35d51f0594ab1870eab5a8d300ea28259b6fa02b0dd59f53aa74293a3f38b8ed27ae6fec92a86a363c95b83828a25b1508034519c19ae5e3d2dab6b4cbf0f780b2d897ba9d29946890826db259e20a10e45debf922a99d3d105a0fc67a50437164b01f5a78bd929922811f5df575d88d598111dd210b333b290e7dd50869146f238e165a6d51e581d0ebbe1b85ebdf622112d2b52fa1be884042fcd40947b0b8d2a21824d71e8cfd3998599bd3c0bb08b220b3fa85c13c97ca36a45c0f53ee984e0c0ad05c4ff652e91b3656f52aa8de8b5bcdfe52ca8a0c18f617ab6226323dc47f84de1040c0734499dc996d36d4f6f99e0f3eb7d701bb17614b7108", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x169", + "r": "0x8c7d1683cd28156dda333110d0de04cf05430703b6258bcf6e211e00feccdc7e", + "s": "0x7045a1484cebcde53d0d1850d7a40c2948d58e48b925a8ed70a7a71cc0530cd9", + "to": "0x5940ead1a16a8994b1c42755d4cd2a5cb3269f5a", + "transactionIndex": "0x51", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x20ffc9220ccc458b509db342b12f40f48cf007c2", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x513a41fda2f07adbc7c855fe9dd67be4a998ea3d076b375a1414eeda62cf75d2", + "input": "0x", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x1", + "r": "0xb298e11097c93fce43b0554cee39867cf69c0e22a15c5843e77debcfab38a761", + "s": "0x5322825d49847d621f45ca6920e8a1583ddf7d4d1fba77b5d009c636b4b4f7fe", + "to": "0xd7bb921219805feaf1a918ba5458900a9d8773ec", + "transactionIndex": "0x52", + "type": "0x2", + "v": "0x1", + "value": "0x70ccdb4eb6578" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x1cdb49962c20db63a6e0717e43fc6c93293e756d", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x3d4b49bd5eb7ffd31cbb60662d4bf72929b9cf0f131fb000626d4fc6b9dd6bba", + "input": "0x", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x52", + "r": "0x72e75c069930ec5a7dfd21fb69ba3a1352cc7c27996b26a780ab6e1acc359eea", + "s": "0x5437902b08f88a8fef34a8455645aa73a9d7a8c622044e2db6e677bed4abcd71", + "to": "0xe1d3c8d69e5177073856f6b553bef5e64b0d8402", + "transactionIndex": "0x53", + "type": "0x2", + "v": "0x0", + "value": "0x1f5eb2e211a61f3" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x57e283b1bfff55471b6c78203a08acc761dc6ed4", + "gas": "0xed77", + "gasPrice": "0x587b0ca96", + "hash": "0xb53f0d14b08942dd1ac9a5fee6e9beb75c7b3b9dd70395145c69a225039a5dc8", + "input": "0xfd9f1e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000057e283b1bfff55471b6c78203a08acc761dc6ed4000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000063b4a0e60000000000000000000000000000000000000000000000000000000063b5f2660000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000a42a4ec67da388fc0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000c23a563a26afff06e945ace77173e1568f288ce5000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000073e5c2027eb5800000000000000000000000000000000000000000000000000073e5c2027eb580000000000000000000000000057e283b1bfff55471b6c78203a08acc761dc6ed40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033828f1d8da6000000000000000000000000000000000000000000000000000033828f1d8da6000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009a87ad58a8f200000000000000000000000000000000000000000000000000009a87ad58a8f2000000000000000000000000000f5d5751d4f300c3d0adbcadc05f0af4f8baf788360c6ebe", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x521", + "r": "0x2a1a49e2414be96272f028eb92d7e486caaf1718e4be8cbe492853954637fe0c", + "s": "0x731b1612f1d72489b4b09b985dc16a2bc312400ee0aa3cd83cce9952d27c119b", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x54", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xbb773aab6cc2e47215e07f97740529376c044b2f", + "gas": "0x8caf", + "gasPrice": "0x587b0ca96", + "hash": "0x782accaf89e95e2c7ebfda2912ea940c302dff28d0a40652c1e3b8b3c54426dc", + "input": "0x2e1a7d4d0000000000000000000000000000000000000000000000000106e69ba1610000", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x7", + "r": "0x3a1570b85dfd6cb3b03d33bb3c86932851152a879f33875e8fed8dd3c575f2cb", + "s": "0x3b5f6f8bf8dfe0e5d5f43ce944c7c361228c22f98f55edf4066339c486f05ef8", + "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "transactionIndex": "0x55", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x69818fd8b109f687ec6fcf21cc08510bae7d9bfa", + "gas": "0x340d6", + "gasPrice": "0x587b0ca96", + "hash": "0x45f0cbe49ba7469a24c7321246bae383a4248fd8f227a44f3856e692a618853f", + "input": "0xfb0f3ee10000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020af59ebef0000000000000000000000000000f167b212b1c73c6a8e11fcb03780e33cdb550b9e000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000009c21c678852c3d6cf72bf48f7fd56dc801d6104200000000000000000000000000000000000000000000000000000000000009ed000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a8ea00000000000000000000000000000000000000000000000000000000642b4fea0000000000000000000000000000000000000000000000000000000000000000360c6ebe000000000000000000000000000000000000000012dac7c63335038d0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000e35fa931a0000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000001f438daa06000000000000000000000000000a661eb6e85ca7f5f2677574cc2ba3cd9b9409454000000000000000000000000000000000000000000000000000000000000004115d59e33c6cff7ab70b25adeecf0d07da8f1b37e5315b411f53da789ca20fae130ca282b8580b50b659b450c8df9c7de0082fb689bbb091bdae09646a56b33131b00000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x9c", + "r": "0x73011c2dd3cedac7a8cb0a5620e27b62481d40a9726a619acbf1c46a404fda2", + "s": "0x153e0745800f9c43f340ca18d78c2e8066fa85ee0f714da7ae3d1e9cefd2d179", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x56", + "type": "0x2", + "v": "0x1", + "value": "0x2386f26fc10000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x6c9e01d2502c8944235a376b3d2104c9657fe781", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0xb4618dec163c12cdea0f2a97f80cb143dd1901d56a942a79eaa2f10d4ebee269", + "input": "0x", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x11", + "r": "0x456e717bd252ba5de0da6a42220e7560f53feb6b1feb4ad05e9527c08e27785f", + "s": "0x3fc339e2229e6c0145197f6ef74e72a92f7cf6f3712c226e9d510bd5ae380ac2", + "to": "0x190b0678dd07c3db9663ba03a2d192acc2064b97", + "transactionIndex": "0x57", + "type": "0x2", + "v": "0x0", + "value": "0xde0b6b3a7640000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x9e91bb2764cd4ad01342791b6634b04a1bc70c4f", + "gas": "0x146a3", + "gasPrice": "0x587b0ca96", + "hash": "0x1391ffe77d7d77b6b17631dd1a6717731cc904bcb2d0b5e872fc9f4f2e2522ea", + "input": "0x62fb4e010000000000000000000000009e91bb2764cd4ad01342791b6634b04a1bc70c4f000000000000000000000000000000000000000000000000a46c357304cccccc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001cebac19bd3fe40000000000000000000000000000000000000000000000000000000063b4a998000000000000000000000000000000000000000000000000000000000000001c6a312d8315e92fe99ea0c769b3be11b2ea819984bfe9b52c91218a49e23789593a87cb41aeaa947f4a67789af5fa26d24b77df9fecc599d64bbd333ef5ea297e", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x4", + "r": "0x552e981f50ee574b520baa939b21a4bd2faa10a89f3ab48488fda04bee66b218", + "s": "0x253a5b8c37fadc384fd7eca108e3bba8b883a9836869bb5799f325ac2ec723dd", + "to": "0xe7b0ce0526fbe3969035a145c9e9691d4d9d216c", + "transactionIndex": "0x58", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xabe62273ae29777d771417e4893325666ee86c78", + "gas": "0xdd89", + "gasPrice": "0x587b0ca96", + "hash": "0x0586384a72a245e120bd4554afe783d284b33897dfe9485e1ad0fdb03699fe3f", + "input": "0x095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x374", + "r": "0x71da620f19899b55484ad85a93c9fb5b5f281ebf84b07298a9431d73e21dc534", + "s": "0x2d007bbf204214d23b95fa7e8620f69c767f00c75ce16ffc7ba410cf65cb965e", + "to": "0xb8ac2383cdf35f77b9bf2605f1378e84ebf6452d", + "transactionIndex": "0x59", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x585a08e5a84ded4050d127dc01d4ae7e659f1a4f", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x303f7a0714477204cfff5019197f79fcbbfdb99c5d50cb1b414c7e26577d942d", + "input": "0x", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x1", + "r": "0x859eec06feb17eb83309dad465c819c48402b58b686afc22bf9bc36a30c11e88", + "s": "0x25ea010882f8fbdaff15b444ee952921216a72f1f30bf031ac5f2a60fdd0dae", + "to": "0xfa1447894078408eb8ce42e4c71d0ac54eb1950e", + "transactionIndex": "0x5a", + "type": "0x2", + "v": "0x1", + "value": "0x26f6c28fba68e" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x6b0321c33e56feaa911db0732a5738581e64342e", + "gas": "0x11de4", + "gasPrice": "0x587b0ca96", + "hash": "0x7b648f73b59803c51a5dd953c069a08d538a628bbfb6bf9b055249e55c02af61", + "input": "0xa9059cbb00000000000000000000000028a5e1fe9705bc9cf91684ae2559b28088db114600000000000000000000000000000000000000000000000000000000055d4a80", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x6d3", + "r": "0x1ee3cdbf83ae88fc5af396ef7f9356e52447ac7a2e4f54112b6d91dd61263672", + "s": "0x4c84913e373240dfb07acd4d7248a2d56d7e7ba5f9839db8dc82e1664a033307", + "to": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "transactionIndex": "0x5b", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x86bb14375c4e38b031973220517e28c658dfa64f", + "gas": "0x295ce", + "gasPrice": "0x587b0ca96", + "hash": "0xcc9786dd6d18638787b1fa3849abaa33e8cd2f77c739b3711832daaf8bfa8f23", + "input": "0xfb0f3ee1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a7d8e7f838000000000000000000000000000ac29965fd4e37d7c1099474be7e36d93f859f889000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000009c21c678852c3d6cf72bf48f7fd56dc801d6104200000000000000000000000000000000000000000000000000000000000003cb000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a5600000000000000000000000000000000000000000000000000000000063dd36610000000000000000000000000000000000000000000000000000000000000000360c6ebe00000000000000000000000000000000000000005326b8e35b6b49960000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000012795f58d50000000000000000000000000000000a26b00c1f0df003000390027140000faa71900000000000000000000000000000000000000000000000000028a49e903b000000000000000000000000000a661eb6e85ca7f5f2677574cc2ba3cd9b94094540000000000000000000000000000000000000000000000000000000000000041010dbd29ace219ed350336d879a840367ee27b94c2c7941a68f6c2bdf798a360795fe8c4b6f4b9828dd0772fc2f891c61ed1de1448f18e6d4d517fce2743c2361c00000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x58", + "r": "0x88c834265053c94285a2026a6073773206a08671fbf0661913bd95cb918c26b9", + "s": "0x78f2163539921757e4def7f07b977eb0807c9e01fc22a6715af413767c4c5f0e", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x5c", + "type": "0x2", + "v": "0x1", + "value": "0x2e2f6e5e148000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x9be00408939d5a57957b3c8085aa71c8766483a3", + "gas": "0x1d07c", + "gasPrice": "0x587b0ca96", + "hash": "0x1e323e87d321f582707b1547e3134929009852ca4835def3ff26a6353e08df0c", + "input": "0x798bac8d0000000000000000000000003194b8684e4381f38d274adfabb196fb7895b6a8000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000016345785d8a0000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x46", + "r": "0xb48670b350423891e0387f2187c50954b4b20868117e5e2c1577e3f754edd160", + "s": "0x15f266e442fb03fe962a2d73028ee75e1f2e06d54808179aa721a68ff29a064d", + "to": "0xcda72070e455bb31c7690a170224ce43623d0b6f", + "transactionIndex": "0x5d", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x60941977b20b93b014f6755f6c6e74b16635b4a4", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x79c5b1fa08d3cf72ccc4973456850b06260090d2581126384c83476bccac1dfb", + "input": "0x", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x2", + "r": "0x59ca32ad3a5e9db00e6cf9684b5a3425b4654dbf13d2fb312b1fe4b37cea60e3", + "s": "0x525919dac42ec8c3c5df4d200943a907713dfc485c8794379380ad1b39e341f5", + "to": "0x64b77167b65c9e5df541d01ff2d94a84ec72d1d6", + "transactionIndex": "0x5e", + "type": "0x2", + "v": "0x0", + "value": "0x22acd34fbc0819" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x8d37019a56489b0c8828f09eab6d6937f52223a7", + "gas": "0xf79b", + "gasPrice": "0x587b0ca96", + "hash": "0x40bcd2dcbdb5538e8308eff3fd311a14f3288cefbd261afe44e936aee7795dd1", + "input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x12", + "r": "0xb770af4ad85c81637df34d8a40f92c035c494aa3045de3a0c1ab1b685731be79", + "s": "0x6c073922d59f1d13ce5fa02228586642e4c9ee6626c7492d9631184d7e1ba4b4", + "to": "0x5940ead1a16a8994b1c42755d4cd2a5cb3269f5a", + "transactionIndex": "0x5f", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x1e77c839610d2c36406223976a2dceac825faf03", + "gas": "0x3be60", + "gasPrice": "0x587b0ca96", + "hash": "0x2e7c0e19cd5ac2b7ce45feeb06ceef1f6f588652672aa2df59d9019aa7e11cd8", + "input": "0x7af61775000000000000000000000000fe8c6d19365453d26af321d0e8c910428c23873f000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000009c6000000000000000000000000000000000000000000000000000000000000171d000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000005c283d41039410000000000000000000000000000000000000000000000000006194049f30f720000000000000000000000000000000000000000000000000000000000000000000410eed9611bc8beb2ef98d494e68aba07d9518253fcc080304e8b95b11618749c17dd0eb497155774b1fecce1a6c59dce13b6b6a9a68bc0bbf13539eea238205981c00000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x11e", + "r": "0xd4a884d93729c159a1c2bf53856536e627f27c645bea139f0229309fd19b3c13", + "s": "0x66026df8914a4f69d9a7aef8da5685d80a8423969c4ecd4e481554d5cb5708bc", + "to": "0xc3503192343eae4b435e4a1211c5d28bf6f6a696", + "transactionIndex": "0x60", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x00ffc9f986eaec82cafdc2ae5350c48b51a3acdf", + "gas": "0xed77", + "gasPrice": "0x587b0ca96", + "hash": "0xde75e5494114ea2f05688deae925ae3701e222a7dc765e52d7a86751b8e264a9", + "input": "0xfd9f1e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000ffc9f986eaec82cafdc2ae5350c48b51a3acdf000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b46f3a0000000000000000000000000000000000000000000000000000000063b5c0b90000000000000000000000000000000000000000000000000000000000000000360c6ebe000000000000000000000000000000000000000066549250c89e89cd0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b852c6b5892256c264cc2c888ea462189154d8d700000000000000000000000000000000000000000000000000000000000019f900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015b1c797b121200000000000000000000000000000000000000000000000000015b1c797b121200000000000000000000000000000ffc9f986eaec82cafdc2ae5350c48b51a3acdf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000961a26b1c2a00000000000000000000000000000000000000000000000000000961a26b1c2a0000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012c344d63854000000000000000000000000000000000000000000000000000012c344d638540000000000000000000000000006bf609583cd3cfa2393c535ca8c3f49fd23e00b3360c6ebe", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x15a", + "r": "0x2b04d35eb751857101c774982107ebbea1fa6dbb37e4a6c732856d95d5980b64", + "s": "0x69960f7e1c57ead3e77de951bd3aa003297d979086c1a86d635e07c52957ea7f", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x61", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xd7a7e8177555dc7bd9900109f60a75bf3d24eb71", + "gas": "0xa410", + "gasPrice": "0x587b0ca96", + "hash": "0xd24a983698d0e7aac2dcc38552d8dc485603ca916143b13c6fcded1ce94db778", + "input": "0x", + "maxFeePerGas": "0x68a2922f8", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x0", + "r": "0xc7fe86e0b898a1ee8847d2b96a21d98c424a78a01683b6bf42b53dfbbff0de1f", + "s": "0x67b5c19b35cd2d378ee59c813b778a7bbce3900dc87cd49731d46a591336b6ff", + "to": "0x84a7cbdbbbdfb4f765d1d99efade356c59f968f2", + "transactionIndex": "0x62", + "type": "0x2", + "v": "0x1", + "value": "0xad71d131cbf080" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf129f20bd4f4374aa12d7c488addfd2009125aae", + "gas": "0xb64f", + "gasPrice": "0x587b0ca96", + "hash": "0xffd4070fefea406d09340d73e663a46dedcdfd2f2f4de443373592c9088329fa", + "input": "0x095ea7b30000000000000000000000008967ba97f39334c9e6f8e34b8a3d7556306af568ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x64f", + "r": "0xfd2a4d90b5dfe4205bc2325a5828eb5febd7fde4e1b7d3f9458ab9b221e3c72b", + "s": "0xa78bf5f858a6f60227372d5bd8cdbb4add4356c77dba18047a9f91e071bc32", + "to": "0x38029c62dfa30d9fd3cadf4c64e9b2ab21dbda17", + "transactionIndex": "0x63", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x0c4e9599fdddd24ff72bf1d95ccb4229085f9ac5", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x426f87f5c1d8b555ed38bde616f98a72ca67b9c9faf8c73124985755167af2be", + "input": "0x", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x11", + "r": "0x80b2583695d7fef9b92b95a9629cd6c4d8f351a76622669b89c4267abf42853c", + "s": "0x32b22c9bb48362b6e4a2aea6952ce9d9b92f3741bd68920064cd975dc93d9880", + "to": "0x71632e7f930b7f4b7d8c86feef22b6f76f40de0e", + "transactionIndex": "0x64", + "type": "0x2", + "v": "0x1", + "value": "0xf43fc2c04ee0000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xae68a4ab5228229391225283c899e88e4741c62a", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0xa1b1883b7921525771888edecf47c9e379a278e6c97ca03a29ec646f3175ec01", + "input": "0x", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x491", + "r": "0xefb9c225f7509e753985a32bf046fa0f647d4176c23b694715344e4cb425c431", + "s": "0x6069084312f41a5c5579664b41ad8492cfb984e38755dbcef68149bb365b44c", + "to": "0xefaf25c982a3d990813c76364e991fe31a557b7a", + "transactionIndex": "0x65", + "type": "0x2", + "v": "0x1", + "value": "0x2386f26fc10000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xed63d7de023f952c9abd240a0e62728914fe3c37", + "gas": "0x17c66", + "gasPrice": "0x587b0ca96", + "hash": "0x24f33555832278221f3597b95286df940fa4d18f686ed33cc19dbf12397adb01", + "input": "0xfd9f1e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000ed63d7de023f952c9abd240a0e62728914fe3c37000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063a5044f0000000000000000000000000000000000000000000000000000000063cb3ec90000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000bdd2997b7648948d0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000002c88aa0956bc9813505d73575f653f69ada6092300000000000000000000000000000000000000000000000000000000000006cd000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000324b08cc9a040000000000000000000000000000000000000000000000000000324b08cc9a040000000000000000000000000000ed63d7de023f952c9abd240a0e62728914fe3c37000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000000000a26b00c1f0df003000390027140000faa71900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d48c89a602000000000000000000000000000000000000000000000000000003d48c89a602000000000000000000000000000025025e203f892f50c357bc0703eaf258879a8def000000000000000000000000ed63d7de023f952c9abd240a0e62728914fe3c37000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063a260490000000000000000000000000000000000000000000000000000000063cb3ec90000000000000000000000000000000000000000000000000000000000000000360c6ebe000000000000000000000000000000000000000081dda39ad1c7e4030000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000002c88aa0956bc9813505d73575f653f69ada6092300000000000000000000000000000000000000000000000000000000000006cd0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003bb91a72f6e4c0000000000000000000000000000000000000000000000000003bb91a72f6e4c000000000000000000000000000ed63d7de023f952c9abd240a0e62728914fe3c3700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a5e27eef13e00000000000000000000000000000000000000000000000000001a5e27eef13e0000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048c66e375226000000000000000000000000000000000000000000000000000048c66e37522600000000000000000000000000025025e203f892f50c357bc0703eaf258879a8def360c6ebe", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x67", + "r": "0x8657f7d55455b109bf1699710235d18f884e6f14705152e37ee8d4eef7099ef2", + "s": "0x39a6bdaac4c2c077b1bbef681014f215069cce5b41e197165e4e145e78468b5f", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x66", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xab799c5d55675d45f4371e161f893cdef4e5d3e5", + "gas": "0xb4bb", + "gasPrice": "0x587b0ca96", + "hash": "0x0992b1044397cba350f76d0ae159e92a7a507a727285eda59b30cf0650bffafb", + "input": "0xf14fcbc8dfd86ea8b451468c8e5c9d7efef39d43e8bbe9e5a0ccb256c251170d1c415db2", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x5a1", + "r": "0xcf90215db3b8d96ee2d5beb865216bebd91cda9cee8ce88ca17ff7dbbeb00333", + "s": "0x58bc1719f9fee0917f6d4519dd8b600239497442cec2de550a53d0f03bd36f46", + "to": "0x283af0b28c62c092c9727f1ee09c02ca627eb7f5", + "transactionIndex": "0x67", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x0693e11cb9708c60bedd1527bd5c3b245e4ec6bf", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x92ef02fb482ea7793676c588e7b8e5c3cc362030a1e8b03bac6eabf75d2f5eee", + "input": "0x", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x3", + "r": "0xab5982c32f573370c355727eac348b1cfbba8265471c2093a204f4a7bb817435", + "s": "0x7120c4d64fc33b10890605cbc6231e2d00e132c78ab363df420184bbed6e5ace", + "to": "0xfc483244d832ffa24ee2a89b3494649200f083c1", + "transactionIndex": "0x68", + "type": "0x2", + "v": "0x0", + "value": "0x2aefdb795ca7b82" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x83bbed4de7691ef006d94f6c9341699ca14621cb", + "gas": "0x5a3b5", + "gasPrice": "0x587b0ca96", + "hash": "0xb51a71eb3020073fad184163dc55d27bc66fe2ac93dae6966dc28b704bec9fd8", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b063000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000e4472b43f300000000000000000000000000000000000000000000000000041d0feb94a950000000000000000000000000000000000000000000000000002b90faee939a5d000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b4e92af212c05cfb2434efb6365500c76501e397000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c000000000000000000000000000000000000000000000000002b90faee939a5d00000000000000000000000083bbed4de7691ef006d94f6c9341699ca14621cb00000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x964", + "r": "0x391b13a6012408cf99aeb738d695638e1b1823af6edeb8f62edded34dc04d", + "s": "0x2903ee3d7ba9bae489bf08c396c0273af29b8fb34d3530610f71c18d70dd1a09", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x69", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xad37ae8c1b04a09e8d5b4a5784ab6b2ea621037d", + "gas": "0x8caf", + "gasPrice": "0x587b0ca96", + "hash": "0xb5574ebf2efe04d0537490c65f094925cbb6de3890b1acd59430d9cf8239938a", + "input": "0x2e1a7d4d000000000000000000000000000000000000000000000000001b5b1bf4c54000", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x49", + "r": "0xe92f989e9f9977757afac5c5a5a9f5885c57cf8a9f979794958a427d9fd868b8", + "s": "0x4119c9cf946bfaeb8fbb67754a6671196e004c73f583f33145dc8b03fce0809e", + "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "transactionIndex": "0x6a", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x199dbe321aff5f6a12268cc56a563830d2b03754", + "gas": "0x2bf08", + "gasPrice": "0x587b0ca96", + "hash": "0x80d62212d31284627a58641742fa4f19d8f9131946e6b9392a32802869536717", + "input": "0x9ff054df0000000000000000000000000000000000000000000000000000000000000190", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x0", + "r": "0x57f76bb956b7b7d7809a611f6c66192bf0b1cecff206ab2d7c116f03b446166a", + "s": "0x4d8c4db3d3a00ea1b10a81f149bf33efb0bd578a62730b98f59d1b238ec4fdef", + "to": "0x06450dee7fd2fb8e39061434babcfc05599a6fb8", + "transactionIndex": "0x6b", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xd217626d506193850982a80e2fba34cfbf7b4bda", + "gas": "0x1ed23", + "gasPrice": "0x587b0ca96", + "hash": "0xebfd734a1b479ed4854214a2741822c95b5c393a3f953a93a76ee39a55f3866e", + "input": "0x97474f130000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000009e0294a5f9f59b6a21abd59014765f6bf234977d993ef585ffc570a8e178c5c585ed2d850a7645602b854060131d1b7549eecaff95cb509f858217d0fb8d7b5726e2e659a7d71ccaee1266bd776ab628cea7ec36faa38b716b8034c9ee261b1570f062d22599348ff9383a4fc955bd35b7b1386535d2b49ffb7bb95f736fb50d69de8104af87aa4c79bdd6ce0c7cd8877d4971efa5af7564f4ea041520127f93a47b021a4777be5499a8c65a41a3de39b4ed245ee4d9324f27c0fe7d109f19fd0620a89dcf1fc4b6d12ba2e74b0e5dc4c6d44fce16ecbbe2bfb775dacaa78602cef333fe964d7943dd3aabc81aad08fa24629cac366ca0567c88e87c2c2f3c8c796f26ab1ce7cea9049f7b512f22819c14af9c3dfd7f07352fd8f783c4c70d17a", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x118", + "r": "0xb219ca043a13e2766febb5da904e82353b23e09767e461882ca45dbf75878a0a", + "s": "0x10842bde8c91e50aaf879cc64e424665ea0ab135c84dafa40dbeee1b419044a6", + "to": "0x5940ead1a16a8994b1c42755d4cd2a5cb3269f5a", + "transactionIndex": "0x6c", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x0de4818fa57179897306b519abb15fb70b915abe", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x66b8ba0eee63448a759a832ff503fd0c50dc86ab3c615400041624564e0f179f", + "input": "0x", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x108", + "r": "0x49e36a987655a49603040464c80cb70c50dcf2d5ea45e1e5dd5a8bbb31f727f9", + "s": "0x435fe5547fbb6992885e1c0ebf0c186cf716383144bfc9896359cbf3f27821ee", + "to": "0x411d6c8065ebac8ecc54bb5ab63ef158a8f138cb", + "transactionIndex": "0x6d", + "type": "0x2", + "v": "0x0", + "value": "0x2386f26fc10000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x7b5094d709eb6294e9f52d109befd542e2a81fb3", + "gas": "0x12cda", + "gasPrice": "0x587b0ca96", + "hash": "0x5e6bdf804550f7640a85e96ab98ee47b49fed50890ef8146de71e8e1713a8a42", + "input": "0x7ff48afb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000153dbf7edec3e0000000000000000000000000007b5094d709eb6294e9f52d109befd542e2a81fb30000000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xd0", + "r": "0x759586e35bbe158e9503510ad48f9418962fac2319ae7079723bd2d4a20feee2", + "s": "0x63360db017b8a67b051e31c51e9627ffa3ea26a98a4c8a0eac1e34a230938f2e", + "to": "0xff1f2b4adb9df6fc8eafecdcbf96a2b351680455", + "transactionIndex": "0x6e", + "type": "0x2", + "v": "0x1", + "value": "0x153dbf7edec3e000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xb6f7c85405a9a010e2166fde19b02040d36f6e61", + "gas": "0x2bd24", + "gasPrice": "0x587b0ca96", + "hash": "0xca81e77163ce5d87a91d04718191c3ef80411674188f9afd732d4e52de5f4b08", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b057000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000c4f3995c67000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000004190ab000000000000000000000000000000000000000000000000000000000063b4b477000000000000000000000000000000000000000000000000000000000000001b9ccc32e98a537fec7b262f23e9aab15829eabf5141f5c3a44598484f5eba03eb7bd344dd48c967b7fb7571ee476c0276982f26fc67d482005ef2ed6e7c83d7cf0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4472b43f30000000000000000000000000000000000000000000000000000000029b927000000000000000000000000000000000000000000000000cc20294638a00c3dc10000000000000000000000000000000000000000000000000000000000000080000000000000000000000000b6f7c85405a9a010e2166fde19b02040d36f6e610000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000038d9eb07a7b8df7d86f440a4a5c4a4c1a27e1a0800000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x12", + "r": "0x973e70c8b72548e54e5bc006e6aa26c411976f115429d59a0370c4f4851e616f", + "s": "0x3d449548d2c499fc574fca32a2b152d40e8ccb0c024694bd7f235241ac1a0a7c", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x6f", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x84d14b6290f5cfc915eca489b639c96e81f4b4b4", + "gas": "0x1bea4", + "gasPrice": "0x587b0ca96", + "hash": "0x501b3b29f9da0968c1782a4906b03b2dbd40a5724f94e4c069a68f890963faad", + "input": "0x26c858a40000000000000000000000001b5c7d1216045a4024f16d83354ba8b5ff4574e400000000000000000000000000000000000000000000000000000000445a00f0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000084d14b6290f5cfc915eca489b639c96e81f4b4b400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x46", + "r": "0x3ce2198c0850f615d0eada8e067c194a96c56c6d01025a446dd6022e8a9a5746", + "s": "0x38bbd0ccc03b4fb145f4d53d253de19cf47ef9e483df8a1d461fd363b74fe337", + "to": "0x44e94034afce2dd3cd5eb62528f239686fc8f162", + "transactionIndex": "0x70", + "type": "0x2", + "v": "0x1", + "value": "0x310706e1e68000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x35135671c2e46d4a16dbffaf2348e06c7041e664", + "gas": "0x177df", + "gasPrice": "0x587b0ca96", + "hash": "0x3536e6c04669ecc6c2b1a38f16c07ff39645619422fbbf0cec67327982d9a141", + "input": "0x0d1d7ae50000000000000000000000000000000000000000000000000000000000000005", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x29f", + "r": "0xee89b1e7df7de69d2fa65b57aedcd01a77fdc4f115f693ce6fc764ed0617b786", + "s": "0x6f3e7449e5e63975ca348efd5a1535c0fca560ca500dbfa31c6d5c9376b449e0", + "to": "0xd7b343ed7e0c5a325651c53669814a3da418e9bb", + "transactionIndex": "0x71", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x953dc196a1055c0270429335b29fb2ca66128536", + "gas": "0xafee", + "gasPrice": "0x587b0ca96", + "hash": "0xe76a93158cec6e8d40678edb97bba45e7ffd8ce4f0169b997b419cd3ea9d43f9", + "input": "0xd0e30db0", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x1", + "r": "0xaebf6074761746c653a81bf6b44681a54bc694a64c6999e087e143bcf4d44814", + "s": "0x7c820eb1878a4bd7e4bd3eaf3dd4d578d301e22852c156d4737c259a42d8d33b", + "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "transactionIndex": "0x72", + "type": "0x2", + "v": "0x0", + "value": "0xd529ae9e860000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x0ef42517c82798d2110e046b38b7c6e2588d6108", + "gas": "0x2c120", + "gasPrice": "0x587b0ca96", + "hash": "0x6dd57b07aa80e0e1784feb7196c9a51ad44a5486dda1d490976371319302dc2d", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b05700000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f300000000000000000000000000000000000000000000000001224dc4127b41e40000000000000000000000000000000000000000000000e9107fcbf2929388ce00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000ef42517c82798d2110e046b38b7c6e2588d61080000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000f21661d0d1d76d3ecb8e1b9f1c923dbfffae409700000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x29", + "r": "0x4426f3e89d01744fad29496da80d44aa2828841e6830015f6f220328996b3676", + "s": "0xebfa87d4c449825099109e8bd3b275f09fdc5b1bc3572f16f802f380c983517", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x73", + "type": "0x2", + "v": "0x0", + "value": "0x1224dc4127b41e4" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xbc15fafbbb77f411e125d31ba8084426ce674c16", + "gas": "0x71547", + "gasPrice": "0x587b0ca96", + "hash": "0x381e6ca4f448faaddd92050078897d8db31baa205909611d904f0dced3abb1dd", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b063000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000e442712a670000000000000000000000000000000000000000000000000001eb208c2dc00000000000000000000000000000000000000000000000000000b2d1673139bf5c0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000bc15fafbbb77f411e125d31ba8084426ce674c160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000080fe7f635b8f6ac71507bbfcbe90e6bdf8b0fc9200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000412210e8a00000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x4f", + "r": "0x4185dc742387ab69157e8eff37b058c263a33e44576ed8821848c95b7ec92ff7", + "s": "0x92c7a090bd7e3c8e25f4b9f181722f87e0d8301614de68f397a33b27b5d5ba5", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x74", + "type": "0x2", + "v": "0x0", + "value": "0xb2d1673139bf5c" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe867813a5725930deed3db1ff3cd33a0cdc07e76", + "gas": "0x1f09c", + "gasPrice": "0x587b0ca96", + "hash": "0x92e54cf49349ff844098bfcb8ebe33f2fe27d6d3fa2217a5dfb10862673b4ce8", + "input": "0x97474f13000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000a04319c95a6e9334d28de43be5e161f22fa7fe081f5766c46defd1e6b089347aa58b9ecf70755423a1078660a9eeaaec30ece61b1329cf79bad0ea007acb3a205dc3b8b4175f8d41d9a2afbdfe299075e4f6ffbe22221420130f885f2de1aaa6f6acd6e5769723aacd5bf51f960c6390cff6b727b1f06338cd9754ea0fdb393a5c3f25aa89635af950e1167b2c3701bdba2abee4661f32ee2b403892f0e651552a6e34cb0b332f0fa9353522fb56db53d368cbedd448a50bd96ca239197a787f294ccaa6202e565835db6c0b0beb9733874b3c790cfbe39d84307da03035a174e0d45c9ee37ef5b35e6e4d03b877ee19e443ca3ee221e1c2d33479308146876d7e323da1ab8dfed0ed2819f527bfc2b6876230184eff301bf57e9bcd9e2f9222f26323dc47f84de1040c0734499dc996d36d4f6f99e0f3eb7d701bb17614b7108", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x0", + "r": "0xe4ecc77d773c436eb7abb382be7330ddaac50929f6d0c9219174cea910eb60b2", + "s": "0x5fb703b78c63efe41ee3a5713100f5c3d9ccdf91ddf1622e98dd1a05df4e3d7", + "to": "0x5940ead1a16a8994b1c42755d4cd2a5cb3269f5a", + "transactionIndex": "0x75", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xc90b2e4b0d3fad61c71b74001c7f856a2974071e", + "gas": "0x3bbd5", + "gasPrice": "0x587b0ca96", + "hash": "0x492ea0727c78fb8365b9086e9283faffede341aa384b23db7f26027a5cf56960", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b06300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f3000000000000000000000000000000000000000000000000008e1bc9bf04000000000000000000000000000000000000000000000000000036903924e4d12c880000000000000000000000000000000000000000000000000000000000000080000000000000000000000000c90b2e4b0d3fad61c71b74001c7f856a2974071e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007dbafe66b3bc826bfa44ddab70de9e49c97ffeb300000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x89", + "r": "0x833795bb136c9cd75886244f1ce380885b65f19138a4edec4054331cf2845e2", + "s": "0x27c5cd5e6a3f715bfe9486325b85306a934dfccce0a6e7a655787602d4530fbe", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x76", + "type": "0x2", + "v": "0x0", + "value": "0x8e1bc9bf040000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x93d9eec82e0858b073159ba3fbf1361145a0ad84", + "gas": "0xb448", + "gasPrice": "0x587b0ca96", + "hash": "0xf0b04a143cd86416e00bcf4f0e86d58231b064edfc3786ac71085c50300bb154", + "input": "0xa22cb46500000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a80000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x12b", + "r": "0xfcbf06d5f5b1d4f3c313639e0948966d156d7682a1cfc40c685998629dbe8f3b", + "s": "0x2f75654442df1d9597dea7816589e863298514fc88c76835a8fe9916ba633c92", + "to": "0x0a8d311b99ddaa9ebb45fd606eb0a1533004f26b", + "transactionIndex": "0x77", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xfee73f83466aaf63a56527535e5fe6dd440b0dbc", + "gas": "0x29aae", + "gasPrice": "0x587b0ca96", + "hash": "0xb454ba4ba7563a08358d687d54ef930cf7ef2a53900bbe8b9b4ea8782c7549a8", + "input": "0xfb0f3ee10000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f990ec7ff800000000000000000000000000022485b2751592d6228ec5efac56f3367af9d975000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000005940ead1a16a8994b1c42755d4cd2a5cb3269f5a00000000000000000000000000000000000000000000000000000000000000b7000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a76a0000000000000000000000000000000000000000000000000000000063b5f8620000000000000000000000000000000000000000000000000000000000000000360c6ebe00000000000000000000000000000000000000009af5147b792f3f9c0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001057acf5f78000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000002dc24ab0b5000000000000000000000000000aed6189d26cf26ec97af930b521507c17f75cd9b00000000000000000000000000000000000000000000000000000000000000412d58831659646c1969bea463dc4f77b331655488be6c9afc67f0816e7572974873c766703ef7adb184716c92ab44cd8cac485de39a8654546066630da5156deb1c00000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x2c", + "r": "0xaef221868c6f1ebb997cea9b8dc6f3b0399748ebd339baef54c100659db33e28", + "s": "0x435756d58a932871af0069332bc85526cf057321f3a123a6b14aed1586c1d9c3", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x78", + "type": "0x2", + "v": "0x0", + "value": "0x28db3066eac000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xfc5f696ddef6e74ec79d6953d52c0eb4567dd692", + "gas": "0xc8a5", + "gasPrice": "0x587b0ca96", + "hash": "0xec7df6ab78b7ca15b4c9a2fb362d7906edc3348eb543941f64c16e27bda4eb08", + "input": "0xa22cb46500000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a80000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x2c3", + "r": "0x65e0f8b8a125d757e29d7584da7d445a298a6488249850c3ec9dfd270b670b7e", + "s": "0x26069e8dcfcae38d093c30ad58b203a7d930f04da0e7c03cc2c6cfff8b7ea874", + "to": "0x4f49149ac66129b56996fd0a043a9be63f85e1e2", + "transactionIndex": "0x79", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x6f9968393601c83ab5b639f8457aa0c2a41124c2", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0xc57e58560412627013d6be5a82923b6044ab88d454ee824710db00b35f9d51d6", + "input": "0x", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x3d", + "r": "0xee534892bb3e601ca9ddc328c9e2eda6db5811652983abfb01d2ae65e5e208bb", + "s": "0x6e0cd78235e316abfbd8f49d36a3e12eb76e8a60273c2199ef324dec9c43f365", + "to": "0x55dcc5fc3fbcc90ea95999c41268d8910c6606bc", + "transactionIndex": "0x7a", + "type": "0x2", + "v": "0x0", + "value": "0x354a6ba7a18000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf8f54bfb0717d8812ed903395d3ed0376a921676", + "gas": "0x1724b", + "gasPrice": "0x587b0ca96", + "hash": "0xf248bced943385afff494bc293985a828088f9c4be84e5907c8df9ec740b7ff3", + "input": "0xa9059cbb0000000000000000000000008038f0ece82475f64d074c2411f4fdd0f36e5b8b000000000000000000000000000000000000000000000000000000003b9aca00", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x77", + "r": "0xd31c9695f9c744d2cedf291c3891404b53b861c707d313f27ef865f3fac9f6d2", + "s": "0x5327aef915cad316afa77977210966090794b2b992f63e989cfebbac1eef01ae", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "transactionIndex": "0x7b", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xc8c0a64a3e1969eb2189dad7f13183dc3131f75c", + "gas": "0xb642", + "gasPrice": "0x587b0ca96", + "hash": "0x8ae704c987a0eba9316dc67418b14878f55989a38d42afee2681c86fed410b6e", + "input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xff", + "r": "0xff5db7ccb14ab5d428cc4051f26a8ce6215d27b87aad746a9497b6d13d90d64b", + "s": "0x74327873bbabbd079c337ef800dc2581c90d03aea02909ec84a3dbba453c2adf", + "to": "0xeca22c0fd4ac62d81a5633eb547e6797c9579462", + "transactionIndex": "0x7c", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x08e3545a481ef8ad30d1e38e2557a77da31e825d", + "gas": "0xb472", + "gasPrice": "0x587b0ca96", + "hash": "0x15a306429f9bb848e8431f8997f271423af3e6799eb376d9c73f1c47d72f373b", + "input": "0x095ea7b3000000000000000000000000001d763c42751edc67686eee9efa84492443444400000000000000000000000000000000b48e51940c76a45816e51f0000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x12", + "r": "0xfb9cf23eb26542ca8ed982eb193d0f3673d7d73b26da6ff38a3ed7a34fee692e", + "s": "0xaf2eaba009e1507ea2d6e75f128ce420bf537180abe635e6ef5e362f9bb3757", + "to": "0x6b175474e89094c44da98b954eedeac495271d0f", + "transactionIndex": "0x7d", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x08e3545a481ef8ad30d1e38e2557a77da31e825d", + "gas": "0xbf01", + "gasPrice": "0x587b0ca96", + "hash": "0x3524c6a931a966838e7318060d7cea55fe9ae3f42170ae1d383bd0660681fc80", + "input": "0x095ea7b30000000000000000000000005427fefa711eff984124bfbb1ab6fbf5e3da1820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x763177aae", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x13", + "r": "0xa6d656e37159449dfebb90f843e6cf1b837d8659148ad9c0c0784c82082ae2ee", + "s": "0x274512e6628e2aa80bd8ef1fe4223b5f42cc1c7389c9cef04280050d88e1d7a", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "transactionIndex": "0x7e", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x08e3545a481ef8ad30d1e38e2557a77da31e825d", + "gas": "0xb472", + "gasPrice": "0x587b0ca96", + "hash": "0x49468ff7f6c8a828ffecb1d6ba13c33f9824330c24997595d3c44ec3a9c1a647", + "input": "0x095ea7b3000000000000000000000000001d763c42751edc67686eee9efa84492443444400000000000000000000000000000000b48e51940c76a45816e51f0000000000", + "maxFeePerGas": "0x66dd16a24", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x14", + "r": "0xccb06eaf5071b0b350392c2829323011a03df379d5dc52e8b644bf40e75202c0", + "s": "0x1507de00d2f8ffe947b5ca1fcd0d00173eed6e6413f5a71710e53c9b94ef385a", + "to": "0x6b175474e89094c44da98b954eedeac495271d0f", + "transactionIndex": "0x7f", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x08e3545a481ef8ad30d1e38e2557a77da31e825d", + "gas": "0xb472", + "gasPrice": "0x587b0ca96", + "hash": "0x55de8bc8ecdeee72714344a49968da7ed8a18a7412a9dfd67af8dd129912a3a5", + "input": "0x095ea7b3000000000000000000000000001d763c42751edc67686eee9efa84492443444400000000000000000000000000000000b48e51940c76a45816e51f0000000000", + "maxFeePerGas": "0x900962842", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x15", + "r": "0xa80b93c9e80c3436f803edbdfc63ba9f7141cdb0a798c07bcee7e915cef781de", + "s": "0x5fe829e449de9ce0ba4d50da8d12dad98a45f1ed7bec52e86b8901878f9dfe84", + "to": "0x6b175474e89094c44da98b954eedeac495271d0f", + "transactionIndex": "0x80", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x08e3545a481ef8ad30d1e38e2557a77da31e825d", + "gas": "0xbf01", + "gasPrice": "0x587b0ca96", + "hash": "0x4607d80c3415db7f633434c950a70ad22adf5857ef94832a3954653963eb1188", + "input": "0x095ea7b30000000000000000000000005427fefa711eff984124bfbb1ab6fbf5e3da1820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x8497fe4f7", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x16", + "r": "0xb6803b42a20c9834b3e2f5f3066df97a793da3f3830080cc100ff706f341ad54", + "s": "0x7c17e847de651de5ad855589c765995ff49adb55ffcdd2743588764cff4d6e9", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "transactionIndex": "0x81", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x08e3545a481ef8ad30d1e38e2557a77da31e825d", + "gas": "0xd21a", + "gasPrice": "0x5d34c225c", + "hash": "0xe91040ef64e8805a0ec0d9cf11e0d156f370ee4ad0a8cacc14b9b35fc4397416", + "input": "0x095ea7b3000000000000000000000000eff92a263d31888d860bd50809a8d171709b7b1cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x5d34c225c", + "maxPriorityFeePerGas": "0x5d34c225c", + "nonce": "0x17", + "r": "0x7c7f52bf09ba2126a2333c6f06450c05bbfbae8d56b39657dfdc7bd779ff970e", + "s": "0x73d361c72b4b9e8b0bcecf82de55b0701395068b84b2cecf0f2311a17832fb0e", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "transactionIndex": "0x82", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x08e3545a481ef8ad30d1e38e2557a77da31e825d", + "gas": "0xb472", + "gasPrice": "0x587b0ca96", + "hash": "0x7ecdf3684564ca36113606be7db4f6acbc7ead6e8f07bde2492714604d200f0c", + "input": "0x095ea7b3000000000000000000000000001d763c42751edc67686eee9efa84492443444400000000000000000000000000000000b48e51940c76a45816e51f0000000000", + "maxFeePerGas": "0x8364600fd", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x18", + "r": "0x8d59c365301dce6de99d7f4b777278de628ef3639113b92ade47f4bc77ec5785", + "s": "0x57799efa0cb0a329766a19860e90388087467f18511950be7def370952de56f1", + "to": "0x6b175474e89094c44da98b954eedeac495271d0f", + "transactionIndex": "0x83", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x08e3545a481ef8ad30d1e38e2557a77da31e825d", + "gas": "0xb472", + "gasPrice": "0x587b0ca96", + "hash": "0x68269d30c75fc37857f927883a9d25071d105a20e835e7cc3a9e58039b58c791", + "input": "0x095ea7b3000000000000000000000000001d763c42751edc67686eee9efa84492443444400000000000000000000000000000000b48e51940c76a45816e51f0000000000", + "maxFeePerGas": "0x7fcc17535", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x19", + "r": "0xa6c0856f79638cd13efda3a93e81656a61d03d8fcb3878e5073a54790ccecae", + "s": "0x161e3f4a638e1d74cfc2fc7a834471f84ab16a874c9384bdac7556c30ac7954e", + "to": "0x6b175474e89094c44da98b954eedeac495271d0f", + "transactionIndex": "0x84", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x5d7af4ba88ec2acbcf67905c8c8f9a88910b4e89", + "gas": "0xda1e", + "gasPrice": "0x587b0ca96", + "hash": "0xd6e88864d7f2fe3393f8e970e0b5fbd81006649e8d8a61c382e88601575d99bb", + "input": "0x095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x81", + "r": "0x1d777c7715b6173cdc6e8016205c3c45fb78313f98dd6be6aff8ae8869f4373b", + "s": "0x5833760223f981180b46563e5b442d8aeac47fb6498f14771a099162b375bcbb", + "to": "0x80fe7f635b8f6ac71507bbfcbe90e6bdf8b0fc92", + "transactionIndex": "0x85", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x98e82f23d3d68d242634d7a7d005c3349f748bfb", + "gas": "0x11de4", + "gasPrice": "0x587b0ca96", + "hash": "0xdd37b813eb728c0c27a43e9b9435f186a6ee145007149ed70b69d7983410a66d", + "input": "0xa9059cbb00000000000000000000000098f6dd5b1123f04ee72b66b724b16695f4986bb50000000000000000000000000000000000000000000000000000000203b30b00", + "maxFeePerGas": "0x7443a4838", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x0", + "r": "0xfe717d8c1e94e0fb1065d4838f4c6663b869c7f75f339ff2d83dc258bef75ef3", + "s": "0x33c1c4b5654a130b8107f818f8e4ba4a9d9b5bc451fa5a6113d288d2216fa065", + "to": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "transactionIndex": "0x86", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x4857c82686f0fef01ac970c4a358556136a84df2", + "gas": "0xc91d", + "gasPrice": "0x587b0ca96", + "hash": "0x937f57f2fd7baae7f419a7e4e38f5ebee6c38d2718fe9aa3feb016279e7a22a8", + "input": "0xa22cb46500000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a80000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xf7", + "r": "0x1026605a4070d68b130d7208810bc3835d93340090b2b8547aa9e8b2877351b0", + "s": "0x77503c2de531a26813bd8fc8b2722d9c628537487bcff359d87cf6c9a4b16aa8", + "to": "0x3d2da3509aa854bc5330c310e21c8756b2bed007", + "transactionIndex": "0x87", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x9313db84c0ab22f14553bcfda7e2798338c0a996", + "gas": "0x18b08", + "gasPrice": "0x587b0ca96", + "hash": "0x4313cf047ac2c5ee603b4db33092c3dbc99cfe4dbad16957145227ef9a52a84e", + "input": "0x32389b7100000000000000000000000000000000000000000000000000000000000000400000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000004b96f6a496cb6275b455e951ce42125fcde5aa6000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b189789434a4728f45b88b009bee5f4b339e3e8800000000000000000000000000000000000000000000000000000000000007af00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b189789434a4728f45b88b009bee5f4b339e3e880000000000000000000000000000000000000000000000000000000000000b1800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b189789434a4728f45b88b009bee5f4b339e3e8800000000000000000000000000000000000000000000000000000000000007b90000000000000000000000000000000000000000000000000000000000000001360c6ebe", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x12b", + "r": "0xa6ccd185b9524c8ffde5a5cd31775ec8c7130093fb4746b47b96757c482bc255", + "s": "0x107aae98d94f6121e230f93aeeecaadf58de62c7bb84380450ecedf8fc160fb9", + "to": "0x0000000000c2d145a2526bd8c716263bfebe1a72", + "transactionIndex": "0x88", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xa595a48687c2039271faf84c92acab62c4ad2ac1", + "gas": "0x2d763", + "gasPrice": "0x587b0ca96", + "hash": "0x15ae484438a9be8f3937f8e071398033bc802f378698acb3b7e70ed76ae3173e", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b06300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f3000000000000000000000000000000000000000000000000007aea28c0f073140000000000000000000000000000000000000000000000000000046d180bebff0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000a595a48687c2039271faf84c92acab62c4ad2ac10000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000d4df22556e07148e591b4c7b4f555a17188cf5cf00000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xb", + "r": "0xf6402bb0a768946d0307fcff2372468d2f37acc649e61095307a09ceff0a80a7", + "s": "0x76de154e38b119cd1b1a5c5ffad2eddbd3031fb05a0da107d08026fd36ae74b9", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x89", + "type": "0x2", + "v": "0x0", + "value": "0x7aea28c0f07314" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x0653e299ee741808f12427277fc8a062d9c718e1", + "gas": "0x6c1a6", + "gasPrice": "0x587b0ca96", + "hash": "0x2fefd9bbc9ed973ce85d0b38b832b0cb8c6adac7cc76fe0221b5619251dcdcba", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b04b00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f30000000000000000000000000000000000000000000000003782dace9d9000000000000000000000000000000000000000000000000001b1bc73402512e251ee00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000653e299ee741808f12427277fc8a062d9c718e10000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000038029c62dfa30d9fd3cadf4c64e9b2ab21dbda1700000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x22f8", + "r": "0x4c514c001af30a22b2078328317b19603742e512e8f0a2b5e80383e1c529752f", + "s": "0x34cbb02ee1ad0a0c151b868e5891ee0eecc20b7a716bba0b23b3e083b6de703", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x8a", + "type": "0x2", + "v": "0x1", + "value": "0x3782dace9d900000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x85073d1906f91c808dd48ca53cf3a70f008bba63", + "gas": "0x5bcc4", + "gasPrice": "0x587b0ca96", + "hash": "0xef1ba5cea6b16a3ecffecb21cd3304f3290cd54299aae4665667e662f0a88ad4", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b05700000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000104b858183f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000077e387959851d20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4472b43f30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000171c98d5d6bf18491acbbf000000000000000000000000000000000000000000000000000000000000008000000000000000000000000085073d1906f91c808dd48ca53cf3a70f008bba630000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000003139059d0b23e88c5ba7e17cdc21cd6a860cb8be00000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x4", + "r": "0xc19a750d278c66611b265a49fa93bf364e63bc93e8c1e5e2f820f0a3d6a716a4", + "s": "0x5a4ceab80f75d91b33332e761b7c6925afb5d918725862ff7b5ece4efa01d6be", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x8b", + "type": "0x2", + "v": "0x1", + "value": "0x77e387959851d2" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x227f524488ff31f4035b0b88f8f915a29edab468", + "gas": "0x533f7", + "gasPrice": "0x587b0ca96", + "hash": "0xc3f1a074005434ca5fc3a2a8c0336d319fbc872ffca8f9602f6e8309a7858623", + "input": "0x21938f7500000000000000000000000000000000000000000000006c564c3945f7c033ae0000000000000000000000000000000000000000000000000983cdbb1988db8a00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000227f524488ff31f4035b0b88f8f915a29edab468000000000000000000000000000000000000000000000000000000000000000200000000000000000000000038029c62dfa30d9fd3cadf4c64e9b2ab21dbda17000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000001a75f7db182ce7fca969f029e1ef573f7aee9cb5", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x17e", + "r": "0x2d80e196e576add56d2950205bd6ae78980ad7bff587303d76a451781c341fed", + "s": "0x38309b03fbe39ef33a691ae7863a001c535e059d7b52d7050ba4299b4283b117", + "to": "0x8967ba97f39334c9e6f8e34b8a3d7556306af568", + "transactionIndex": "0x8c", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x2ea1c224b40a996e8cf33983cc2c4b819da21de8", + "gas": "0x74655", + "gasPrice": "0x587b0ca96", + "hash": "0xbe7098fb34a07fc9acf701a3a83f641b441efef4e8f3675d1e835217b0c9e334", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b057000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000e4472b43f300000000000000000000000000000000000000000000000000000073df3c21130000000000000000000000000000000000000000000000000106ffa40a5f9620000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000028dbe36cde7f6034709225c9999549e23c14481000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c0000000000000000000000000000000000000000000000000106ffa40a5f96200000000000000000000000002ea1c224b40a996e8cf33983cc2c4b819da21de800000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x43f", + "r": "0x2709ded91af97db12e8cb65891afa27eecae6498fad6a0e0d579838b16e55608", + "s": "0x54dc0955e99c3e2306844a92cf38b67ad6ec298ab378eb4d3c3bc7f9b2997802", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x8d", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xd537e74c257e93f3a37d42d83e764651eee5253f", + "gas": "0x34c68", + "gasPrice": "0x587b0ca96", + "hash": "0x98aadfcca920a3f8a6d56ad08c478c8d236b10ccbd373a8badd17d4478eff269", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4acdf00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f300000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000e439685714ea0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000d537e74c257e93f3a37d42d83e764651eee5253f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000b8ac2383cdf35f77b9bf2605f1378e84ebf6452d00000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x6e", + "r": "0x4cbb4d0a51419fa0207919ad09a1a4129c373c6db3f9989673c084df4b267c85", + "s": "0x5762e65bbca5ec2f1f3bda18d895b9320cbf46fa84cc49b174a7365594647c94", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x8e", + "type": "0x2", + "v": "0x0", + "value": "0xb1a2bc2ec50000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xc5d8f59b6102fdf0fe3c041301fac53a53010f6e", + "gas": "0x2d1c3", + "gasPrice": "0x587b0ca96", + "hash": "0xdcc247f0adb506cff4a4e4f8821b5642396e2f1271dcd86727a23767d96e6dd8", + "input": "0xe7acab24000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000005a00000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000bf32569aefb2b39aa86f057d34f68edb867cd781000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000063b4a5440000000000000000000000000000000000000000000000000000000063b4b34a0000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000735b9310eee0381f0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c8d334863fc00000000000000000000000000000000000000000000000000003c8d334863fc000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000004f49149ac66129b56996fd0a043a9be63f85e1e2571b6de53f247027dde6a18704d37142fb762d63f2fb780631bf066b0c1c12fa00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000000000000bf32569aefb2b39aa86f057d34f68edb867cd7810000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018387ae9c198000000000000000000000000000000000000000000000000000018387ae9c198000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000000000000000041122ad38d4ca568371fd79a24fe9f43ba81f3e9a269c52b92c85058800f3f1a5923dd8058bb6e7610683ea7c7644b9af380b3f9f977df212aa5658b2aeb353be51b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000c12fd41de61139abb805a4429c21732dbe3b3e1a3e8894ef5dba0ef4c2a9ca9f3c4d4156c2ca2c7c162e3b8d7a4e7eb1c23bb951a9c75d8ffb581bfbf7c93cce69dca1c6ab5bbf4bbe8f08e0c05eaae3d9ce8cacbf573e0de7e99813cb6d90e999ffbf55e12ae74230459d699f5f3dbea7585c239ede3534427072c1b2f231c7a2e3a8b896e5a8fb56eb50f92fe801878e2d7f2573909d7a6b0bcce7431e9910b84df134d235ab698801106362cc5ef89800fea524adc3f34980d15c6ec7e6b76bfed714e5f597d4373930b15cf424343ebfc6d1c01de4d4243091881c02ff9373d23b79a48a2ce331356bd1b9c57b37b0aae92c04580774055a4f38a5d23e5086137898a979a07057eb12577e71269cf56019cd28398f775c6bc61bbd33b9a50a8bbf8738fd5d57e23d2084047a52d0a0a013d2be1144b042e339bf01d3bc0d7f7217f874d9ed1316bae64f11dbbbdd9bcc27a5efaca23c19e3c706fad152afb8d3eaf1f187ef8c389ee6b14bf487f0b2a3a0d9224426831d281ec0093a01d6a360c6ebe", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xa", + "r": "0x6986b1bc2f3a2ae9c9605df640cef74b193996d83c5ebdd317e8d6b9a4388854", + "s": "0x33a047250762e690c69b42e74f5f805c51c54632635c2427a39f84e8d1a39e4f", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x8f", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x509c69d030e9788076dcaa8f1e271567dfff47f5", + "gas": "0x10dc5", + "gasPrice": "0x587b0ca96", + "hash": "0xb33733f534c8c75bf02aeeb885390babc5012cc7ae14b04972f031777f25d0d4", + "input": "0xefd0cbf90000000000000000000000000000000000000000000000000000000000000002", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x4e", + "r": "0xf760625df8e887e0629d2755fb0e38ca9d5625ae365bfb606e1fbe4433c55707", + "s": "0x6d4d655ccbdf21d64b7a8908c72ec9f3850a89078dfd889a476395a02a4aacd9", + "to": "0x79b19a55022585ed0ceba3890495e5fcd0b76a72", + "transactionIndex": "0x90", + "type": "0x2", + "v": "0x1", + "value": "0x1f161421c8e0000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x91ff59d038c28edd0ec8ca7a667ee26d225d26a1", + "gas": "0x14372", + "gasPrice": "0x587b0ca96", + "hash": "0x254706b90f7eafe7472d7063cd247499a5d64de40e8ad28b27896056aafd20a1", + "input": "0x42842e0e00000000000000000000000091ff59d038c28edd0ec8ca7a667ee26d225d26a1000000000000000000000000f84408070b4de16d6bb0ab2fd8b19d37e3fd142200000000000000000000000000000000000000000000000000000000000000d2360c6ebe", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xfe", + "r": "0x5fdc54aa869e1628c8c7eb7953fb1705d3ddd04e78dc436afa7ecc24b321bbe", + "s": "0x55f38f82aced54f6219c106a157de381d58592865432018a575c153b98744bd8", + "to": "0x7bdb0a896efacdd130e764f426e555d1ebb52f54", + "transactionIndex": "0x91", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x6c5400ca7ad13bee0ec044ed6a233bf19a1351da", + "gas": "0x10dfc", + "gasPrice": "0x587b0ca96", + "hash": "0x2cffe468c2f1eab7f71df04b9678b65b0d31e97faed508c7e86481798a8aac86", + "input": "0x42842e0e0000000000000000000000006c5400ca7ad13bee0ec044ed6a233bf19a1351da000000000000000000000000aeaf3c302c445ac4833c6cef435fe5c4cd8adfab000000000000000000000000000000000000000000000000000000000000100a360c6ebe", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xb0", + "r": "0x48815316f3427bb89864912cc951993bb1de33785b782fe29312b83f4865e207", + "s": "0x11fef784e2f2493fb87b2597f5bb01fcda325aa86ba04a35df1f96934714d33b", + "to": "0x6661c87764adf7fffa3c7922fa6edfa2fd62ccfc", + "transactionIndex": "0x92", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xc7f9acac47e760ca5e3824630ee0d964af1f7769", + "gas": "0x1f084", + "gasPrice": "0x587b0ca96", + "hash": "0xcfec640f3f56f8b0600113398d8ba68ac4a91ecb6fcb641758b2f0c91e5c2028", + "input": "0x97474f13000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000aae1fe5ca3c302987938a7d88b1763109bbe8e6e8c21c3e80a7a10e7f5402a6c2d668f15462615b97fcea854b937cd9e3ad15ccf2b0d694e3991e0070861eaaedcfa0afbf290458fc308632f15feec4a16e46a152a03611ad46fe88ec2cf45a0c2df505e7bbcdf7d7ce08555e63e6b5f6ffc488af507ba0b615623cfbdc554e13b9ac3e2c82306c47cfa2c6bb10038bcd19aeac135ed026a0c20c36a17a62ac58fec2378664a4af7faedc90a2437de5c2973c6b46d9e2abfbcf7cda87d47751007a3c1d70446d78fdcca8a3871005f53fe08d0356cb9babb025c7d4ccda51fc8b2fb388bbf1021623ca029377b04d860843f08f86280a2e4ea343ca747763d25f53ee984e0c0ad05c4ff652e91b3656f52aa8de8b5bcdfe52ca8a0c18f617ab6226323dc47f84de1040c0734499dc996d36d4f6f99e0f3eb7d701bb17614b7108", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x13c", + "r": "0x9b3f8a073888d05536a805f9471235a89bc23b7fa7e47d99745055d3ebe17997", + "s": "0x6e01690deb9d25483b6be2e9a2e089948024e497b3fac94fa3c33973b23b6c8f", + "to": "0x5940ead1a16a8994b1c42755d4cd2a5cb3269f5a", + "transactionIndex": "0x93", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x44b2254f9032ead70010c618b660d3c7e87cf3dd", + "gas": "0x1065b", + "gasPrice": "0x587b0ca96", + "hash": "0x27db0cc04b4eb04b48f69f3b3fad7b2117db35591b1f177c29c36605dec20369", + "input": "0x20a325d0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001187", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xc3", + "r": "0x5e95e46585ff0b606680b1b21797d927ab9210e17fcdfa0fdbff1092f82313f", + "s": "0x79048a408d54cfd57f6465d5ffe5c0d7c2807866dbabd97e2872859319ac3ff9", + "to": "0x5954ab967bc958940b7eb73ee84797dc8a2afbb9", + "transactionIndex": "0x94", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x20026e07ff3c7a71caedb228d074766c34966b04", + "gas": "0x2d7f9", + "gasPrice": "0x587b0ca96", + "hash": "0x89acffec14327c2012aa4fe0c8b54e9980b2e9799ce8825e3f2ff25daaf965fd", + "input": "0xfb0f3ee100000000000000000000000000000000000000000000000000000000000000200000000000000000000000006fdec0a887a306dc8bad780bf569d266bf408411000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000072ad0377962ecb076766e57b5e8c6189449ad05f000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c00000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000063b0b6b30000000000000000000000000000000000000000000000000000000063b4ab2e0000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000f0e5fba89a09922d0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001c6bf526340000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000004f94ae6af8000000000000000000000000000c35be11967d522f81ccb3eb053a3ec0a212e5e6b0000000000000000000000000000000000000000000000000000000000000041640edbd023439ac5e47636687266c6d1487706ddbf5e998659b5283c5d96bb080d35eaa096929b1a6280b23a374fd46c1dc18553142a69af064b0610d1d47d121c00000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xc0", + "r": "0x9023c1467b52e538ff382e1eeeabf6a737157d4c2d32cc83040f05539b958b81", + "s": "0x517c9147dfe85edf2811ed66f75b08598ae54b42b7109d113d9a959463fc6440", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x95", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xc8d2f3ff5d8feac92b77b792b47c11eb27369eda", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0xbbc79ed58638e6d776e97be4f6f986a2f86007ad971d2dde8acd5d3b2cb36a71", + "input": "0x", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x6b3", + "r": "0x3181ebdd7f4fd8351cc6ff431a858561cd5e38abfe32a2bc596203388f6e0792", + "s": "0x9452eaa8543f2a9c3cfb00d46de7f54a03c1885dae15d061bdf05a2debe3a25", + "to": "0x6bfa6b948af76082f3f2b59a225e2be147c5c2e7", + "transactionIndex": "0x96", + "type": "0x2", + "v": "0x0", + "value": "0x1bc16d674ec80000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x2924c331bb2442333673b603d5a84d8d579de1d0", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x0962e7a59bbc1e860396ef7bd7ce58b500a0d0caff9374d38823172b05c68bd3", + "input": "0x", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x8a", + "r": "0x1a988b0fac2045229c67f772634ffd295cc25203c9660f3963d891e09ee1f4e2", + "s": "0x1fcf51b080ede144ac3a78286b8b5918369cf9de28bcff9c068d78e941ededcc", + "to": "0x222603d968f4db15565d3dc9bf5fd0419b69c7b6", + "transactionIndex": "0x97", + "type": "0x2", + "v": "0x1", + "value": "0x2386f26fc10000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xc8bb2e85bdd46eaa273d88765895490ff3c617d0", + "gas": "0xb640", + "gasPrice": "0x587b0ca96", + "hash": "0x3305908a8d4dba770e3e16c6bd79c7c2827aff62d195ce7429d0c7056132d520", + "input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xfb", + "r": "0xcf7afa0ee2b6ceb321e183d707be8e66545c9e1bc72f9934d0de21eda0e2c71e", + "s": "0x31517e441fc0395dc41d940c564fb78e56fad880a51b781bbc7312dd0f81ba68", + "to": "0x3ceb6868bfbf99f6b76fe5bb37343c075677c698", + "transactionIndex": "0x98", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x12612d04bccfce3c5e68968efb8259c4d3807591", + "gas": "0x177df", + "gasPrice": "0x587b0ca96", + "hash": "0xb3365d439897d6e7d21048f3674b5dc5754f273962fed08e73551a77f4805c2b", + "input": "0x0d1d7ae50000000000000000000000000000000000000000000000000000000000000005", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x3e", + "r": "0x1489f4da0145bf6b8ffc9e3f44e7eb29480d782ccc1dd720d6630c65a7ada20b", + "s": "0x4f93503fe6c49249a6e6f96f9f7ccb49993dbfaa2c2cfba9cb39ed53a6bc5f26", + "to": "0xd7b343ed7e0c5a325651c53669814a3da418e9bb", + "transactionIndex": "0x99", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x93d9eec82e0858b073159ba3fbf1361145a0ad84", + "gas": "0xb430", + "gasPrice": "0x587b0ca96", + "hash": "0x9bfd9f66e913c064cb72fdd27876c9da3c93219ad0471e473434b10b89cd24b6", + "input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x12c", + "r": "0xbd291b880422d85c4f577c4c7aba30b0447123f909208b5d459d1f06bc680cbf", + "s": "0xd8d73f196ec4ac8c3a1926d39187976fdde3f0196fd78a3490fd0b1060a3aa3", + "to": "0x0a8d311b99ddaa9ebb45fd606eb0a1533004f26b", + "transactionIndex": "0x9a", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x20d1299605c8842dd07fe7e419d0606000ee4b35", + "gas": "0x30117", + "gasPrice": "0x587b0ca96", + "hash": "0x513868116485410b4e89275ce58ccae371c6c3a460cacde77716419d650389ef", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b06f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000e45023b4df0000000000000000000000002de509bf0014ddf697b220be628213034d320ece000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000058d15e1762800000000000000000000000000000000000000000000000035f9f6aafef01ed5d7d5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c000000000000000000000000000000000000000000000000058d15e17628000000000000000000000000000020d1299605c8842dd07fe7e419d0606000ee4b3500000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x1fe", + "r": "0x9dc45ababad12bd4d41a996a260cecb1fccfc67b2be08c0edd80134810fe40d9", + "s": "0x5887276a6d0d692e0328bd92c61f4c29c4596f8b5676c943ec07790c9d30a040", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0x9b", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x154ea7a524d6e1e1dcfc88483725765cbfb52ef5", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0xc822437e34e5a9e96328829e997df779b49aa8dba4e2d9773cb64069cdaa32a5", + "input": "0x", + "maxFeePerGas": "0x62609f1ad", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x8", + "r": "0x3d9070d5975d946fbe170f6b949ae9162f1118ad3fe358f526ffa49392c285a", + "s": "0x50e777762af9248ff398c5df41550e74b19361bd6212d0bed867c1a1f860d2c9", + "to": "0xa308f07ae50dfd734de66d53041af33c28935a16", + "transactionIndex": "0x9c", + "type": "0x2", + "v": "0x0", + "value": "0x1254fdf9c9c1c00" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x5e5f374666b2cc34a767b259eefe5eeab8603b43", + "gas": "0x28915", + "gasPrice": "0x587b0ca96", + "hash": "0x9af3f0b83578f3471a3eabd9b7beffe420ac322a1934857647d224158b4ff2fe", + "input": "0xfb0f3ee10000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023f4161d2080000000000000000000000000006cab2859480cecd3875a8c0435bf571b18a51040000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c00000000000000000000000000ab45f7b8973a1b71366ea909c70b9ff707d78b7d00000000000000000000000000000000000000000000000000000000000001e8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a8c00000000000000000000000000000000000000000000000000000000063b5fa340000000000000000000000000000000000000000000000000000000000000000360c6ebe00000000000000000000000000000000000000001acdf58d7355b0160000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000fa1c6d5030000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000002263e8a16d0000000000000000000000000002a49859f72d1de53e6dc9883691c67761d5e7ce9000000000000000000000000000000000000000000000000000000000000004061298822d378fa3ca316fb313535c884dc3e497d021fdf969a600e38d28430ec0547797224a23053888f003b6f22734d0d68853530581aacd655dee893475439360c6ebe", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x6dd", + "r": "0x551873e2c787bb2a0d059293f6d1e3d622e94714d8ea90a2bc98490da1654fdf", + "s": "0x255d4cc6a6111b2d6a2dfca02ccc4d1c01142450a4c01af47bc2b55de0bcc466", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0x9d", + "type": "0x2", + "v": "0x0", + "value": "0x27147114878000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe701a35273c57791009a5d0b2de9b9b8c1fceaea", + "gas": "0xb517", + "gasPrice": "0x587b0ca96", + "hash": "0xbd403c1a3f049048e695f34f08bb984e346afc4287c2419a751842c9a08e10f7", + "input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x327", + "r": "0x8e76c4f7026ead2cb84e31fcaffae0e01db215d53ba821f29e9dfa5b387747e8", + "s": "0x4fe0237da9903a6aa4413f59112c57dc9808ea4fe327354fd2d3dbe3e9aba56f", + "to": "0x942bc2d3e7a589fe5bd4a5c6ef9727dfd82f5c8a", + "transactionIndex": "0x9e", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x655533fae22bdde1d73bf7abe97c76f41ffff54d", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0xd222fb3894f701c508ebb898001594ce8cd3567992d190c11e196a20b5ba9c19", + "input": "0x", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x52c", + "r": "0x2a2012afebebded7a85aaa72f045c87fb6e89f9d6da520614949a17873ad31af", + "s": "0x31324cb001437e18e87d273bc03b7ab5686c9b74d8f5a4a64975e4d945dc6153", + "to": "0xa69bcb6b7f3082895496bca8371525dbe6f7a2a5", + "transactionIndex": "0x9f", + "type": "0x2", + "v": "0x1", + "value": "0xeac515e4138cb" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x632236c6d692555d80ed136c24379bb00bda0308", + "gas": "0x6d22", + "gasPrice": "0x587b0ca96", + "hash": "0xb699de75f6c19ae08f274f4fc422e3225854ef88e7c27a1d3f816cdd70cb1ce1", + "input": "0xd0e30db0", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x10", + "r": "0x6b0031194887a091f9a1cd5ef3805db4ddc6826ed8960f9bc556df5727db08e8", + "s": "0x55d8ebf1ecc51c653d273c1a0f4a01e86a2620d6606dc9e5646a934ebf0b6cc", + "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "transactionIndex": "0xa0", + "type": "0x2", + "v": "0x0", + "value": "0x1941f05b73360000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x59e1cdf908933ce0f3296b64e726160af1b63320", + "gas": "0x351c7", + "gasPrice": "0x587b0ca96", + "hash": "0xb9a4d65e56e64dbea978c2b6a430e766b33dac5dc00f38d291a80ea8b038a48e", + "input": "0xb682e859000000000000000000000000000000000000000000000000000000000000004000000000000000000000000059e1cdf908933ce0f3296b64e726160af1b6332000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000001bed00000000000000000000000000000000000000000000000000000000000015650000000000000000000000000000000000000000000000000000000000001bd10000000000000000000000000000000000000000000000000000000000001d0f0000000000000000000000000000000000000000000000000000000000001cff0000000000000000000000000000000000000000000000000000000000001bdf", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x32e", + "r": "0x519e9823b4663b7561549d57cd8dca778b33d948f3668e60a3d9a7591ce2a8a7", + "s": "0x1ca581dbed9d25f9bc16cb05270a49f678601eb241cf53ee9b7c2b53ec75519", + "to": "0x5954ab967bc958940b7eb73ee84797dc8a2afbb9", + "transactionIndex": "0xa1", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x89887e4f9eef506dda99f4706ab9c24b847d40ae", + "gas": "0x2e96d", + "gasPrice": "0x587b0ca96", + "hash": "0x0a444840aa595c24117be133ba79d405b1c19f5a157505b884310465f1e7a032", + "input": "0xe7acab24000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000006600000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000014c2e3f8cfe13dccd72e0957ef093d4356f09a08000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000063b440b00000000000000000000000000000000000000000000000000000000063b5922a0000000000000000000000000000000000000000000000000000000000000000360c6ebe00000000000000000000000000000000000000005cd150a9e7a6fa220000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eff0ff9884980000000000000000000000000000000000000000000000000000eff0ff98849800000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000942bc2d3e7a589fe5bd4a5c6ef9727dfd82f5c8a4b2fd8bb6900335a7ee2dd2e81309715ec81dd03de6313bcf79357a91a69a48d0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000300000000000000000000000014c2e3f8cfe13dccd72e0957ef093d4356f09a080000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005ff9ffd69b7000000000000000000000000000000000000000000000000000005ff9ffd69b70000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011fedff83d25000000000000000000000000000000000000000000000000000011fedff83d250000000000000000000000000006c093fe8bc59e1e0cae2ec10f0b717d3d182056b0000000000000000000000000000000000000000000000000000000000000041d44ff50c1ebac9124e6f6ca290cb39e1bb62d6245d3b6b249e1c20c60b232623035dbd73896ca508d59fe53314892930882ff65cfd9a5eec776d0ed328624ea91b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000766c00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000f73bb09cb29b1c2662c86eb0faa371b58b2d8e85e2b4b1276d5a66fb96738ed2174015729220721cb2d6724cca326779c265d719591e3091bea18d459f5d60d166202e7b2d0b7386f11fe1f16aeb9a289e03e0a7bf293c4ce43fa5cbff7af17350ce15fa44d04e54cb95b52d6123821e9e36c1fa03c7d68e66b76b620066975982ec7b671db7c901e278f1a7826bb1a5fa9f1d1ff5d2eeae8eb916a08ec9e17f5cb53569013f84ad1e42153ea7571ebdb5892ac04affde5e7fd45226479839e5955a7d0689205622a6e77c0471a93e09a8386f38ffeea45e19c97ee02f6812c767ba6177c48b95d235bb802f551ff1060852a12cd600c68873a95cd0f219937f30c5dd4cd9c99ccc046b10c100c43a9e4476fadfc7126d10dc17449418fee40f606a846632d6dcc13ad94a9f7834b801b6584331c2e72c6610f665d890f563a8b17e904e14fab785c4a8eebd28a01b0edfc26fdae1e7d6f2016b6bd75d5e2393867877ca46a7b3400a077f01db031414c7e2cc1e673b31d40762ab57afefbc23de1e9b7d1647f30640c672a9d9427c1a7aed0c1ede4a422a4f7830f252d6d5f287cc8982adf8e5114e8077ac4940ed1ffbc9e58aad2353e10d1310112544ba95825e0a18793f403c4fbe88ec6281c05885eb440f6fdb1ee803892f33b2fcf7498360c6ebe", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x162", + "r": "0xf394922d62c9680d7ca5f942d203cf5a57cf0d008f9719545e7edf0102281c6", + "s": "0x4960418b28d26cf3a75a70552d2ab60a22d7218d61346f37579ca51a351f9224", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0xa2", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x63d52f4ceba0b5516a2b4f5a55b03be2fd1d8cdd", + "gas": "0x26c6a", + "gasPrice": "0x587b0ca96", + "hash": "0x7a53b05fee8d39bee7ab43933e24f68f6d563cd4e42fc78f35e9aa061c186d8e", + "input": "0xfb0f3ee1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b5483ef188c00000000000000000000000000cd87c481d7bedc824e03d01b96a2e103ebacd3e7000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000005940ead1a16a8994b1c42755d4cd2a5cb3269f5a000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a8970000000000000000000000000000000000000000000000000000000063dd87170000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000eb00daa2c62b01ab0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000b365d82e94000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000000000000004122eb0d38f6abe36abde95fd2152b9b326b076bfa920be4cbaf3abbc58ec959814519c67726fd2e16ad4ef6309d5bb46a897fa41d113a57bd113184fedc7f91c31c00000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x138", + "r": "0xf2161ebbb51a70ae5e21001176abfe21bf150d23dea681ba87b99bf9041cc93a", + "s": "0x18ed9c114d63ccb2793ff9574a22af2d7f6d636169c12266a330380ee7fb0546", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0xa3", + "type": "0x2", + "v": "0x1", + "value": "0x1c07e9c7472000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x67f9704f97ccb33d57840b0a4550498af3a6d1fe", + "gas": "0x3ed87", + "gasPrice": "0x587b0ca96", + "hash": "0x2a00c028eacd9bdd2f057ea523abab2b9d35e7acb66c43b289f1c33ada9293b8", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b06f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000e4472b43f3000000000000000000000000000000000000000000224042c1300d227a44cab50000000000000000000000000000000000000000000000000054dded44e63528000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000d34a2051ac17bdd44d76aa84ea7fcf8419130e55000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c0000000000000000000000000000000000000000000000000054dded44e6352800000000000000000000000067f9704f97ccb33d57840b0a4550498af3a6d1fe00000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xa", + "r": "0x8c2f02ff06ba3643de408c2893b70f68c20dfca6f5e4dbfb0439c4a10c881709", + "s": "0x3421994e21645c9c82e720d27750b79a033a950cf02af73789f9bc36b14d9788", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0xa4", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x9fbfeec58de8f127711ad83ee741cde85537af52", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x076a49c9ade62d6529af55c2ef619508ba3c0353a3b133935aff6f326fc370c0", + "input": "0x", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x1e", + "r": "0x42af3549de943648cb3587b2eae2ed74c332e18ce1b174563b271de11f01a6fb", + "s": "0x55fcf1446efa505a01c0d568048bb20d14186ff5ff2097799a98170c238b915d", + "to": "0xf663ff7b6b661b05bfedf445c26163f0233d0360", + "transactionIndex": "0xa5", + "type": "0x2", + "v": "0x0", + "value": "0x13b7b21280e000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x939f196bd1d1b9b819acee08b7e96308a051bdc0", + "gas": "0x28915", + "gasPrice": "0x587b0ca96", + "hash": "0xc3b0c35f99f03c2b9146aa0bff103c7949cdbcbefa2cc4b0744519c3f55c570e", + "input": "0xfb0f3ee10000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023f4161d2080000000000000000000000000006cab2859480cecd3875a8c0435bf571b18a51040000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c00000000000000000000000000ab45f7b8973a1b71366ea909c70b9ff707d78b7d00000000000000000000000000000000000000000000000000000000000001e8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a8c00000000000000000000000000000000000000000000000000000000063b5fa340000000000000000000000000000000000000000000000000000000000000000360c6ebe00000000000000000000000000000000000000001acdf58d7355b0160000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000fa1c6d5030000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000002263e8a16d0000000000000000000000000002a49859f72d1de53e6dc9883691c67761d5e7ce9000000000000000000000000000000000000000000000000000000000000004061298822d378fa3ca316fb313535c884dc3e497d021fdf969a600e38d28430ec0547797224a23053888f003b6f22734d0d68853530581aacd655dee893475439360c6ebe", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x119", + "r": "0x9b8fb7185e10971995c75bc0066c6947abb53d8fc3e912a445692818eaae4473", + "s": "0x29dac33823eb610e7890d80481f3e05e5a9fbe4ac9cdcc4d08b400bdc50d3909", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0xa6", + "type": "0x2", + "v": "0x0", + "value": "0x27147114878000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xb1ce7113e6ac88e2b1284c2cfe26dd048a0ca25d", + "gas": "0xb442", + "gasPrice": "0x587b0ca96", + "hash": "0x6ec2fe38185e767e9d9a8a280838a7770743841414ada4171172418f9f7b7950", + "input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x146", + "r": "0x112e614300f6aa828f47ff82d4232c221a267cda5010aa1c2bca0cd98a87a199", + "s": "0x565b39b26fbed2f07f46aa81540e1aaa3a573e25668de3c578e750fc27a916f2", + "to": "0x5150b29a431ece5eb0e62085535b8aac8df193be", + "transactionIndex": "0xa7", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x8bc169aeb63660aa8424aeef1a96004ef9d3dfd6", + "gas": "0x12ddb", + "gasPrice": "0x587b0ca96", + "hash": "0x1471875645f64756e9fd325c0b587c4245dbfe2498a938b555f6b0867eb57aa8", + "input": "0xc95d571d0000000000000000000000002d9d9050723c7942e2e889e482f57d50512bebfc00000000000000000000000000000000000000000000000000000000434b90f0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000063b4afec0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000920901569cfac9d7c6e211eb01a6390a30783e5d000000000000000000000000000000000000000000000000000000000000002b762d48455a4459466d38324d6135694c79563856546e634e4c506d474770756557536452545053794f4159000000000000000000000000000000000000000000", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xe", + "r": "0x134bd92b86bf5a220723f6fe8c85e33f44bdd7457bece259465b2d3925db1102", + "s": "0x123954247ad39e6f3db8aeeee2e11784620b2c22888468c2372c82cdd0361647", + "to": "0xa46f952645d4deec07a7cd98d1ec9ec888d4b61e", + "transactionIndex": "0xa8", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x2966fd8c2673b4004b008394b7b3452d29deda92", + "gas": "0x15f90", + "gasPrice": "0x587b0ca96", + "hash": "0xde2439393ce5815a96d468a0f844804112c10802bc3af6534e564f0c7e6ae96d", + "input": "0x", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x5", + "r": "0x5d6ebf0f0df68f2ff36444170fd06b9e497b7e9020dfd4cb33bf840cd8694277", + "s": "0x317cc728df099de4beebeeda9c22666a5c8556144aa28ea7b0e61860c3e14404", + "to": "0x2966fd8c2673b4004b008394b7b3452d29deda92", + "transactionIndex": "0xa9", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xc261fe36dda39c420dffc523be69487966953ed9", + "gas": "0x7529", + "gasPrice": "0x587b0ca96", + "hash": "0x97e09396302dac9dd2ade13d8eb90649c6a2429df14d7a52562f77afa1ea85f2", + "input": "0x5b34b966360c6ebe", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x107", + "r": "0x56bbb54cdd25e73eb1bdf1d69bad43508724cf54fe81652fa4776cc631d485ab", + "s": "0x388e482af5d14940ecc488c6261973e694ac6873cf115d0662a3b61c815a88ed", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0xaa", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x8094c8b3c398cbc2fba975447e5b55d758e54369", + "gas": "0xb421", + "gasPrice": "0x587b0ca96", + "hash": "0xc7ab574734c34809f71c6e387a7e5323d228aa11dd591d2cea968be785f85e31", + "input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x12e", + "r": "0xac87f6cd4ea1422e6fc8a0f722b316c7ed2c7f33edb72aa554197f5e62ebba58", + "s": "0x2f7cb572cb02b04069408c3d4851754c6671c8abc4300a4c378388b721d2b7c5", + "to": "0xab45f7b8973a1b71366ea909c70b9ff707d78b7d", + "transactionIndex": "0xab", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x580c84556123981a1c93eb8a95713662d6743d44", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0xaab0850c8498155c6922eb68d9ce04d2f40949652a6c4d787421e1c5ca78955f", + "input": "0x", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x15", + "r": "0x345d4502e952915833194b74e3e6ea87cf50e88e6a5bd7121a306d557780c8b", + "s": "0x4bf93c49b28ec6adc3901e7ff8c8bcb5a4c3a8b760fdda61e6a309052bb301df", + "to": "0x4b3c7b112b3684abf7225c1b0b9073185f18c9f3", + "transactionIndex": "0xac", + "type": "0x2", + "v": "0x1", + "value": "0x6a94d74f430000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xa94e18ff6dc8b0c901995be1c4c5834fb694ccd5", + "gas": "0xb450", + "gasPrice": "0x587b0ca96", + "hash": "0xe552de2cee545d95be5590096f4650e03935a3bf648ac348815383f055f9013b", + "input": "0xa22cb46500000000000000000000000000000000000111abe46ff893f3b2fdf1f759a8a80000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x122", + "r": "0x3e68029de9e25b2c07124c44ecc0a02997dd5b2a3ba218f4956d15dbfe7bcdb5", + "s": "0x537967268c4d960a44e45cb1e970566a9be3afa04c731d68cf1592e3aa78231d", + "to": "0x466cfcd0525189b573e794f554b8a751279213ac", + "transactionIndex": "0xad", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x6e54b841ae5946e865299fd1b84f5d3caaae6d7a", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x44a4a999a7274598c9936b43621c0b3a029fe87246f71fbf41db88d8d0e485de", + "input": "0x", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x1", + "r": "0x2c770f611c1f4290095fef8d142d3bc6fb36871ab419c5c084da8e5e00aa68ee", + "s": "0x4890c971942c691da0207ebfe26163b39302258bdefcde7d34695f34f16c6742", + "to": "0xd7bb921219805feaf1a918ba5458900a9d8773ec", + "transactionIndex": "0xae", + "type": "0x2", + "v": "0x1", + "value": "0x8107c42101160" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xc06575e966a4ffad5f683c0c340bc39c9a2810cc", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x4074f3d476c651880c9feb77706f1feca09c92bf2448be4878566bec10d7fcfa", + "input": "0x", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x9", + "r": "0xa08ee26ca786b8e32ab5f78bd01d7b527cb63d8332ac677fecbee6b1aa025b30", + "s": "0x5b82184af89a1cded843c50247750b3273753473c766b3be1dc3d03ccb7c70c5", + "to": "0x7a421b75b63f85344ffaa519c239affbcb14a0af", + "transactionIndex": "0xaf", + "type": "0x2", + "v": "0x1", + "value": "0x71afd498d0000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x7cf702a502717718f3ca6a4f1588010a2d4a034a", + "gas": "0x183ac", + "gasPrice": "0x587b0ca96", + "hash": "0x50b00b81737f586a5958c1d63cf594df9df1bc4be293a282fba148e52aeea796", + "input": "0xa9059cbb000000000000000000000000174b4d2294f00de33f356c2dce15c30778b47f1200000000000000000000000000000000000000000000000000000000371140cc", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x19", + "r": "0xf820cef433cfffe1925914cd7244b3edcf55ff383b617e68bcb7c38f2be52aa6", + "s": "0x22a8a2aa5a8b735f09a1879bbc8a02fd15e0f19ae6f80faac49bdb5f8063ad22", + "to": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "transactionIndex": "0xb0", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x25d366116f0804f318e92b16b5f1684bef3f984e", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0xc8b509252536e846fcc42ac8badf3bd1bfb542c204e58c47d5ed4434caa81800", + "input": "0x", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xf", + "r": "0x27c0830b7d0a84157eb63523fb4d1381df55de1530e05d4d168bc46fad08374", + "s": "0x4580ffb39cda62195e6c065db23f09519c85179ca0e7514f89de10079b5ce0a1", + "to": "0x09732e2d1e891505cafea0acd5029112e50f14dc", + "transactionIndex": "0xb1", + "type": "0x2", + "v": "0x1", + "value": "0x2386f26fc10000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf83a6fc4394ce4127b5e5aed9d5100e151e4e4d0", + "gas": "0xb464", + "gasPrice": "0x587b0ca96", + "hash": "0xfcf22bbf9949b58c3790466f2523abd956cdcfdbe1aa1da817afd4c62401014e", + "input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x7", + "r": "0x90ff1a46bcd3646f3a3cadf74f2754636ca14b47a92bcbaa020a9b7a57303350", + "s": "0x191a7b1b8cd4ab87ae8fedc02c15074fd0e3fb1c6f88416a2ee6493653233005", + "to": "0x0616a2ef54bad0b37dce41c8d8e35cce17a926f3", + "transactionIndex": "0xb2", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x58d8f9f1a18d49eb900839977c449b1d0d257140", + "gas": "0x32d9d", + "gasPrice": "0x587b0ca96", + "hash": "0x14115097480bc0635837e2efcdfb4c9c9daa848ab359cf415114ad0f9ccf1043", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4adf300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e404e45aaf000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000d31a59c85ae9d8edefec411d448f90841571b89c0000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000058d8f9f1a18d49eb900839977c449b1d0d2571400000000000000000000000000000000000000000000000000058d15e176280000000000000000000000000000000000000000000000000000000000088a86b39000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0xbf", + "r": "0xdc4c6a1e924d45763bb73c8e773f04f716561f5009b6f3e1095aea637f613695", + "s": "0x5536f4ffee8add96580094d26c64f643f56c1e7c3d737ffa85d79dd21637c08b", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0xb3", + "type": "0x2", + "v": "0x1", + "value": "0x58d15e17628000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x3909c6b1aca64b06a993ed5232c54cdd902a35f5", + "gas": "0xb419", + "gasPrice": "0x587b0ca96", + "hash": "0xb536006049a0c9b592efe452b4c31197f253bc8e081a582880398c22b3c34432", + "input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x10f", + "r": "0xa4341f744cf92508b26451830889bf85247c59fcd3f31e22b8386ede86cce393", + "s": "0x1feebc27904736e24270c720461986ef148580e248760a8efed323f33d13d5cd", + "to": "0x8c3fb10693b228e8b976ff33ce88f97ce2ea9563", + "transactionIndex": "0xb4", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf5d6eddc4e945063057a019c739be3bec0ca4c23", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x669471185e5bdaa5dc0df184bc24bc2887463ee90604daf811bdf07e068836e4", + "input": "0x", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x9", + "r": "0xe5a0bd2a339bdab5d0f45bbdfa44ecdccd8a4d0eafd1ae6e92862b4bb4cc459e", + "s": "0x6ad01542dd995e8e9589b578deac54fc64f13021c3d89dae17809ac558f43f60", + "to": "0xdb03c81542cb2ce24917bb180b4734de90f8ed2d", + "transactionIndex": "0xb5", + "type": "0x2", + "v": "0x1", + "value": "0x6a94d74f430000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xdf52a39371de2958c0a04e51f1b2a56a58c29483", + "gas": "0x249f0", + "gasPrice": "0x587b0ca96", + "hash": "0xb7da0b22906f56a7737d6c4f14cc7963c3447daa099002181989c831de55dc04", + "input": "0x6a6278420000000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x27", + "r": "0xa414004ee5a20e75940984fb10ad86b9a0b1e6a9d38e7270ee010128e29ea146", + "s": "0x48f9ebe405c0bfc5ab362b594161d0dba4e050724865b8610c07b22dc4817c9f", + "to": "0x99027c41f74b38862f53bda999881d8389fc6a92", + "transactionIndex": "0xb6", + "type": "0x2", + "v": "0x0", + "value": "0x5af3107a4000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x85f7bbe0727bfc07b15bd2bcc4592930f08ebd8c", + "gas": "0x2f596", + "gasPrice": "0x587b0ca96", + "hash": "0x0ba1467082c393e8cb4f2189e822c954b6b9b7535491eb5ae15f38ef4b7e2a52", + "input": "0xfb0f3ee100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001459b307daa20000000000000000000000000002d42284687e6b0d1f57c4937be3090ce2c510afe000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000004f49149ac66129b56996fd0a043a9be63f85e1e20000000000000000000000000000000000000000000000000000000000000d86000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a8bc0000000000000000000000000000000000000000000000000000000063dd48c70000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000d28a03852413f94d0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000859500338e0000000000000000000000000000000a26b00c1f0df003000390027140000faa71900000000000000000000000000000000000000000000000000000000000000417601086304457d636232b82194e512d0f485e816a7cef3ca7dea913c0b98680e124492bbb4444f394b4d1544c92bf4d334dfc37555d162592d683c815b95d3f11b00000000000000000000000000000000000000000000000000000000000000360c6ebe332d1229", + "maxFeePerGas": "0x799326258", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x73", + "r": "0x41a132e4ed85242bf7bc3b40e01f252c7616cbac9be6ae53d5c22b5ae5555387", + "s": "0x3a4ba111b39b501ade4b4d4f94ca80c24179763202e4eb0f98085e135e7fb469", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0xb7", + "type": "0x2", + "v": "0x0", + "value": "0x14df48080e30000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x490325e7beb56ae132007893ce3d545441c3ea4d", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0xac93a0d5bca0ba1c44f5a4ef5997bbeb68a0afeaad7ebf051610532b880ea32a", + "input": "0x", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x85", + "r": "0x5ed1ca3480e3557e6a5dacda56d3f34eeafc71fa7b4eabc8ebf6d4145941aadf", + "s": "0x10070af7060919aa393213ad970dcda49dcdd48f0e08fe3e8b9b714355434bd9", + "to": "0xe0d9b2c84df209e36a7acbca1b5a47075d13c62d", + "transactionIndex": "0xb8", + "type": "0x2", + "v": "0x1", + "value": "0x4acd7980122c00" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x6701d0dd7d2a0a0d84a088620583f82723c518aa", + "gas": "0x17c96", + "gasPrice": "0x587b0ca96", + "hash": "0x4d3eda8a281638acfe0e7104f5e0d50d622d2781a54301b208f76b340aeab4ad", + "input": "0xfd9f1e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000004c00000000000000000000000006701d0dd7d2a0a0d84a088620583f82723c518aa000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b36e6b0000000000000000000000000000000000000000000000000000000063dbf79d0000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000603ca2cd6e98c3d10000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000231d3559aa848bf10366fb9868590f01d34bf2400000000000000000000000000000000000000000000000000000000000000bb9000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000185a09172f7f5000000000000000000000000000000000000000000000000000185a09172f7f50000000000000000000000000006701d0dd7d2a0a0d84a088620583f82723c518aa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a87cc94679100000000000000000000000000000000000000000000000000000a87cc9467910000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000150f9928cf220000000000000000000000000000000000000000000000000000150f9928cf22000000000000000000000000000ff59b4b3d52728f78020414c720164a17c08f4990000000000000000000000006701d0dd7d2a0a0d84a088620583f82723c518aa000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b3191d0000000000000000000000000000000000000000000000000000000063dbf79d0000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000a785a38c736c95270000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000231d3559aa848bf10366fb9868590f01d34bf2400000000000000000000000000000000000000000000000000000000000000bb900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000019a2a959b938900000000000000000000000000000000000000000000000000019a2a959b93890000000000000000000000000006701d0dd7d2a0a0d84a088620583f82723c518aa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b15e85e269500000000000000000000000000000000000000000000000000000b15e85e26950000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000162bd0bc4d2a0000000000000000000000000000000000000000000000000000162bd0bc4d2a000000000000000000000000000ff59b4b3d52728f78020414c720164a17c08f499360c6ebe", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x64d", + "r": "0x3e3dfccb7480ebef6d60a9643364fe27788362461e1efad6e235c9f204d6c2f3", + "s": "0x1bfba83495cdeeb9e030f2a6df7a754b7f3bc288a3afbae0606f685b618e3a08", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0xb9", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x802fa804b97ccef82331243feb6ba56c9b4281e0", + "gas": "0x18403", + "gasPrice": "0x587b0ca96", + "hash": "0x47f5ec7d2102955bcfe6b0307878339883e5416a76234dc98891ca852094aa99", + "input": "0x26c858a400000000000000000000000075af518b1eaf05deba2e339d9c2df4a8064e19e7000000000000000000000000000000000000000000000000000000004bb9f8f0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000802fa804b97ccef82331243feb6ba56c9b4281e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x119", + "r": "0xe00b5063105e8feddaf772b65f83c6bce99fecb4b2d7f68df4cf8df60e2e4b77", + "s": "0x7e7a085e54b5e21134550925bbc7324cf369df45e9ab8140ee5443e6e3528188", + "to": "0x44e94034afce2dd3cd5eb62528f239686fc8f162", + "transactionIndex": "0xba", + "type": "0x2", + "v": "0x0", + "value": "0x8b44313b320000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xdc6784896b6dce26a8cfce64efa5bee202ac1139", + "gas": "0xe63a", + "gasPrice": "0x587b0ca96", + "hash": "0x9a14269f6d42098a40d2952ccf42b2f0eb933da74a8c8ffa2c0e4e7a1b4ac724", + "input": "0xfd9f1e10000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000dc6784896b6dce26a8cfce64efa5bee202ac1139000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b484140000000000000000000000000000000000000000000000000000000063dd62940000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000c68b017e041b865a0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000004f49149ac66129b56996fd0a043a9be63f85e1e2000000000000000000000000000000000000000000000000000000000000072800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001da8dc139df700000000000000000000000000000000000000000000000000001da8dc139df7000000000000000000000000000dc6784896b6dce26a8cfce64efa5bee202ac1139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c2b04f45b1000000000000000000000000000000000000000000000000000000c2b04f45b10000000000000000000000000000000a26b00c1f0df003000390027140000faa719360c6ebe", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x15", + "r": "0x9e483c6fd965830c2c7dcc254ca55462b8287db15b578b38ad4d91046da75c0e", + "s": "0x628c882cdca1805e71dba14812efc4cc7fe2d8411e3150eb518e55d01ccbbb60", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0xbb", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x5a4d38f276f3c69ab03f1e3ae26598d64cb92b55", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0x34cc08e7282e48d4cfd3fa60b600543fb4174e68d22d98f8f5da6f472e208b2a", + "input": "0x", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x77", + "r": "0x7c38769d3823bb9e20f8f94e2ec9e0353e43e802637467f8ffa91009e8fbc39d", + "s": "0x4a0a5e36abe1f693ee62e3dfa3019347ddf1de9a9cb589febbd32a040a5600a4", + "to": "0x7cec53b3b799f4226261c3f0f9e42a09dc89db49", + "transactionIndex": "0xbc", + "type": "0x2", + "v": "0x0", + "value": "0x2a303fe4b530000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x53e17e794c576819b5e9c26be2c3d3ebd404a679", + "gas": "0x209f9", + "gasPrice": "0x587b0ca96", + "hash": "0xa6cbabfab7782c924302715275a5afcd47c11615e399eb47e4ade74464125cf8", + "input": "0xfa2b068f000000000000000000000000103d09ba077372df32ea5090daeb6156ada94e2e000000000000000000000000000000000000000000000000000000004718d8f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000053e17e794c576819b5e9c26be2c3d3ebd404a6790000000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x69ccf3474", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x9e", + "r": "0x27bfdbec7672e50de58e7004bddf7432ecdb050db0bdeb4851b556a9dff32174", + "s": "0x59f0c72b913094bdd735bb40c542b617df86e20707e9032f6a4367ce6886d427", + "to": "0x44e94034afce2dd3cd5eb62528f239686fc8f162", + "transactionIndex": "0xbd", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf0c4685bfe1ba6fc0f861c11a2d003208c334b9c", + "gas": "0x2805a", + "gasPrice": "0x587b0ca96", + "hash": "0x674e2b1d99252814b09c2fdba07b8b1b290f99a100da295e1c49b57d38dbcd60", + "input": "0xfb0f3ee1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c725365c4d000000000000000000000000000ec6aefe1d661ad80a359533e04daf05101c09c2f000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c00000000000000000000000000a7ee407497b2aeb43580cabe2b04026b5419d1dc0000000000000000000000000000000000000000000000000000000000001f0900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006348ac54000000000000000000000000000000000000000000000000000000006435fa540000000000000000000000000000000000000000000000000000000000000000360c6ebe000000000000000000000000000000000000000005bc5204372ba03d0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000002ee5547f090000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000005dcaa8fe120000000000000000000000000004e0843e8daa53406121588feebf0cde0f1fcefc1000000000000000000000000000000000000000000000000000000000000004173b3587f14fa757d4c81d0459dc01986345a79b73012cbdb26d85ac43d84045e51bf68f54acfc1b295eb554a810bfe6ee3f97fcc012ae7868ed96c342358820d1b00000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x5ebaf70f9", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x64", + "r": "0x98ae40fcef9c60de97728274f499824d47083b06fa7b8e189ee496501f006d00", + "s": "0x150c18a0cc5731f1321b57ca43a99f8426dff6e1de3749feab5b7468f011b68c", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0xbe", + "type": "0x2", + "v": "0x0", + "value": "0x753d533d968000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x9df3ce6c15ed3485893ed6851cccc20e4fa61b7f", + "gas": "0x5208", + "gasPrice": "0x587b0ca96", + "hash": "0xb2dbd3bc8ca2fdff7d423737c50b03fdcd5b17808622567647c2280577ef5bc3", + "input": "0x", + "maxFeePerGas": "0x69ccf3474", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x406", + "r": "0x35a2942e45102148a07bf96f60f0fc24ce7a732efa14cf63eb905bb124e8e729", + "s": "0x2ac81f78deab1075f54d1388deea15599a7000bc136b837c45885e4fb9f31ef4", + "to": "0x20b429182c1fedcc03e3b349d1e5d0b95617bee9", + "transactionIndex": "0xbf", + "type": "0x2", + "v": "0x0", + "value": "0x55e74c3437ab0000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xa3505a34ff0156daa7a8c4e4d39be748572a2f58", + "gas": "0x16e09", + "gasPrice": "0x587b0ca96", + "hash": "0x7bfc04ce4906a72cb03ce1a21a2320291b0f394b020b48ec606be43aa98a2fc8", + "input": "0x781137fa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030b8000000000000000000000000a3505a34ff0156daa7a8c4e4d39be748572a2f58", + "maxFeePerGas": "0x71dc3d372", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x17", + "r": "0x7a8e9e757a69cb1e80ed289e83c8ca33dc756b97c079145cc59e3623b4f65238", + "s": "0x2519c504441d282f9aaa5e2effcbf8fcd971e91b5a34f05de798bc66bc457710", + "to": "0x5ee84583f67d5ecea5420dbb42b462896e7f8d06", + "transactionIndex": "0xc0", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x476ec61a27431dcf120f72b7a9f241ce61daa44c", + "gas": "0x11476", + "gasPrice": "0x581bae996", + "hash": "0xd883fca6f6bb44018c70f470765a476dc358bb10b1cc1c67853cf9383925fb51", + "input": "0xa9059cbb00000000000000000000000028c6c06298d514db089934071355e5743bf21d600000000000000000000000000000000000000000000000a398fa194cbe449c00", + "maxFeePerGas": "0x684ee1800", + "maxPriorityFeePerGas": "0x53724e00", + "nonce": "0x21a", + "r": "0xebd52d77d932d6cd1e83a3fef7febe34feeac0d1482f035e8c0b5fa6c4ee4789", + "s": "0x5e65eb469802c0787a862187c0f6c8a6a974f4a680cf0338cc5ffc7355d2769e", + "to": "0xb62132e35a6c13ee1ee0f84dc5d40bad8d815206", + "transactionIndex": "0xc1", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xb4391e178b04ae02ef4d42ac97b931455f2dfaaa", + "gas": "0x1f698", + "gasPrice": "0x581bae996", + "hash": "0xb1622035fee09c3a1a13526b359e2459c36c343d59e87b7e16493f75fcc20202", + "input": "0xa9059cbb00000000000000000000000028c6c06298d514db089934071355e5743bf21d60000000000000000000000000000000000000000000000000a4ace61d17568301", + "maxFeePerGas": "0x684ee1800", + "maxPriorityFeePerGas": "0x53724e00", + "nonce": "0x1b", + "r": "0x88b798d5533563fb34afe41868bd7ab6a408a7fc52331de53b7e6e773be035a", + "s": "0x37c98a7dde76898d2c0a2ac9be0d14d5652a289dfe0ca74b2a01ef16567735f4", + "to": "0x45804880de22913dafe09f4980848ece6ecbaf78", + "transactionIndex": "0xc2", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe2e0383131e9e9b2cd8416b6dcc03c430964c7b0", + "gas": "0x11052", + "gasPrice": "0x581bae996", + "hash": "0xa8c0a4223795aff8e7f69a69d3b399c9de55e3b1b65a367e2afc8e830a56b880", + "input": "0xa9059cbb00000000000000000000000028c6c06298d514db089934071355e5743bf21d600000000000000000000000000000000000000000000000008df0389cd51e9000", + "maxFeePerGas": "0x684ee1800", + "maxPriorityFeePerGas": "0x53724e00", + "nonce": "0x7", + "r": "0x4d2330148cd26cace1a29094d80a769b07d03107ce791a13f674f3940ad283d", + "s": "0x2ccd6fb60690202fcdb2129d8fe0d148f26177a963a54c6ab9e11b535a81c484", + "to": "0x4a220e6096b25eadb88358cb44068a3248254675", + "transactionIndex": "0xc3", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x73f9b272abda7a97cb1b237d85f9a7236edb6f16", + "gas": "0x13268", + "gasPrice": "0x581bae996", + "hash": "0x58cf822ad38659f1751e4da303f5a6d5d993e33ce3b20ec4f5ec27298804a00f", + "input": "0xa9059cbb00000000000000000000000028c6c06298d514db089934071355e5743bf21d6000000000000000000000000000000000000000000000000000001e7184e8ff80", + "maxFeePerGas": "0x684ee1800", + "maxPriorityFeePerGas": "0x53724e00", + "nonce": "0xfb6e", + "r": "0x926b0f11ce4478fb41fac9d242023e9242007ce7afdd0ba7f1e29239effa219d", + "s": "0x723cc2cf037ef94fac4424b1604c2b2ac3d8c3d67dedee4e9d34f2a3b5dcc0a9", + "to": "0x607f4c5bb672230e8672085532f7e901544a7375", + "transactionIndex": "0xc4", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf8ab22d217f36f3e4c067442e0bbce128f468ac7", + "gas": "0x10b78", + "gasPrice": "0x581bae996", + "hash": "0xd70a74448d1de486f789ca01b97baba1fd2c961f7b8835696f80b720d6d895a0", + "input": "0xa9059cbb00000000000000000000000028c6c06298d514db089934071355e5743bf21d60000000000000000000000000000000000000000000000ed2b525841adfc00000", + "maxFeePerGas": "0x684ee1800", + "maxPriorityFeePerGas": "0x53724e00", + "nonce": "0x40c", + "r": "0x7dc4996fa66070224e56232bcec1059c136c6578624e383fac761b950546edce", + "s": "0x3bc3e0d69fa7d97b20fc654fffb976963c0573624f5aa2a70c7e943859619a4f", + "to": "0x3845badade8e6dff049820680d1f14bd3903a5d0", + "transactionIndex": "0xc5", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x161936397fcc717cb357a65074833f1fc690a467", + "gas": "0x10fbe", + "gasPrice": "0x581bae996", + "hash": "0x5908554a96cfc1ceeeadaefef9a3a9cee610e58fec6d27ac5687fcb181267f0a", + "input": "0xa9059cbb00000000000000000000000028c6c06298d514db089934071355e5743bf21d600000000000000000000000000000000000000000000ac0db698068112d000000", + "maxFeePerGas": "0x684ee1800", + "maxPriorityFeePerGas": "0x53724e00", + "nonce": "0x5", + "r": "0xed8a2bc870a4870d94ca86adcccc4f6ed8207754a7e710f30c35147d335f4a7d", + "s": "0x3ef637d8345483c882000c576084e0ab34986e20b09dd164ca8eaecadbcf9042", + "to": "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", + "transactionIndex": "0xc6", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x5b9ebac40fe201ef5023c334915c265e060b5ec5", + "gas": "0x318d8", + "gasPrice": "0x581bae996", + "hash": "0x0a1e8dcafb9f180f713ecd2e06ce80df2e91e7ca71afd1b72d4eb13b505e6398", + "input": "0xa9059cbb00000000000000000000000028c6c06298d514db089934071355e5743bf21d60000000000000000000000000000000000000000000000077858c50b1d8f64000", + "maxFeePerGas": "0x684ee1800", + "maxPriorityFeePerGas": "0x53724e00", + "nonce": "0x1f", + "r": "0x5b1571361f0ea1e5fe52f213e4375484600511b6a334356ad29113bf8e783a", + "s": "0x2100fafd8c8b67ec8f7b2ffccfd0eb649a73a01a4645453959f4d5d9765ff800", + "to": "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f", + "transactionIndex": "0xc7", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x19ad19519bc5f05d8e2d8f56f29aa40d7d9421c9", + "gas": "0x11ee0", + "gasPrice": "0x581bae996", + "hash": "0xa148db1fb15c0a23022cb66a006521b6332974a33076975bd4211919bb30534e", + "input": "0xa9059cbb00000000000000000000000028c6c06298d514db089934071355e5743bf21d600000000000000000000000000000000000000000000000000000000055f428a2", + "maxFeePerGas": "0x684ee1800", + "maxPriorityFeePerGas": "0x53724e00", + "nonce": "0x1d", + "r": "0x28505b6cc0a6db7c7d7cfe999de02da7585840e64d949f291d8ef5e3151fe2fc", + "s": "0x28e2bc9675ce71fc3a47691eb50dd8c4c682a1ad2ed2382f7b79851f28913081", + "to": "0x476c5e26a75bd202a9683ffd34359c0cc15be0ff", + "transactionIndex": "0xc8", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xa60b79e30409249009d42e33442f569715557a53", + "gas": "0x11ee0", + "gasPrice": "0x581bae996", + "hash": "0x3a2c30cb59f3a7ae0f4a819bc24e3d0538ee810c2f97d098f2a55a5d8f96ca74", + "input": "0xa9059cbb00000000000000000000000028c6c06298d514db089934071355e5743bf21d60000000000000000000000000000000000000000000000000000000000b6bfa7c", + "maxFeePerGas": "0x684ee1800", + "maxPriorityFeePerGas": "0x53724e00", + "nonce": "0x1", + "r": "0x936198a7166bd507fe6266940402ae3a5540d7c90b43fe12a81a63d91c832dcb", + "s": "0x3715844b7e988813135829d09796aba9e6104549b1e8531cf7b6442ad41dbd80", + "to": "0x476c5e26a75bd202a9683ffd34359c0cc15be0ff", + "transactionIndex": "0xc9", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x3d14fe7ceed6f3c79be4d72cd139027a437c2fc2", + "gas": "0x5208", + "gasPrice": "0x57a44f6f1", + "hash": "0x6a9c2a7a5a10b3440df37c9034e9611d8ed8c98885735e4cc56145542019405c", + "input": "0x", + "maxFeePerGas": "0x88cfda0a5", + "maxPriorityFeePerGas": "0x4bfc5b5b", + "nonce": "0x22", + "r": "0x4fbc53a25de348bf6964f029c3cb243ea508f05e95af296e751b10537f892c9c", + "s": "0x583620e9095e0168585dd2d3387d459d971e737a88e04ac7ffe9a09dbe2404ab", + "to": "0x35b97d257023a4ad74a3fcb55776e123554e0231", + "transactionIndex": "0xca", + "type": "0x2", + "v": "0x0", + "value": "0x2386f26fc10000" + }, + { + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x000f422887ea7d370ff31173fd3b46c8f66a5b1c", + "gas": "0xc350", + "gasPrice": "0x5787488f1", + "hash": "0x2614c1a5b9014b6dc66359af95319272ae48904ace32b69ad53c50fa7e4db2f3", + "input": "0x", + "nonce": "0x836f7", + "r": "0x8e4a97acc28960869cdd2fe90d84b3e4dfe5e91d29ba97080b3b2a9a30223069", + "s": "0x1f2824e9d5544b391fa67b14a5b5c907fc8f8d4215bea7097a6b13b0fedc6fc5", + "to": "0xde8266bf77d65a02e4cc5bf5e526e4c2140aabc1", + "transactionIndex": "0xcb", + "type": "0x0", + "v": "0x26", + "value": "0x4628d9760451e24" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xa7335b93acef9a799e4c2a8dc559292f222d055c", + "gas": "0xa479", + "gasPrice": "0x56d76ec96", + "hash": "0x9122feda7707c46b8fbe97a79d462ff9e431f15c64debc47c8f70e10bd265405", + "input": "0x", + "maxFeePerGas": "0x5d5b14100", + "maxPriorityFeePerGas": "0x3f2e5100", + "nonce": "0x63", + "r": "0xc0f08a8f9e26c0435fed9865d229ad045ac3f2f41d4ba806cfeec9d7c897a97d", + "s": "0x1006485d27c4f93026956598d1cde0ae8a465ab0e92231c1d6713295acd2ccec", + "to": "0x8aa3c42d71306fcf311dd17a680c9c0f900701e7", + "transactionIndex": "0xcc", + "type": "0x2", + "v": "0x0", + "value": "0x180d6b5af4744c00" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x6239247d1df6cb01d84282e6faf1b3e4f4bfb6f3", + "gas": "0x22d04", + "gasPrice": "0x56a7bfc16", + "hash": "0xe2022ef318909b25f82eb6f24cf0cd3cbf3351cc11882ee4c2653d35bb83eeef", + "input": "0xa0712d680000000000000000000000000000000000000000000000000000000000000002", + "maxFeePerGas": "0x773594000", + "maxPriorityFeePerGas": "0x3c336080", + "nonce": "0x17b", + "r": "0xe67007ead8af41f9fe4b1ed4dd413013d27d78a67c8ede3e4547225518b533dc", + "s": "0x1063da1bc78a4852e647a8f37be5980472f01c56050737958ca4a548529e7d18", + "to": "0x068306bdd1d3b4c9969becadecd85c7c2a978464", + "transactionIndex": "0xcd", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xbc856dad4b3715057699dbf6ff8a372a663456b6", + "gas": "0x1e339", + "gasPrice": "0x569e36596", + "hash": "0xffb3151dfabb3a65898af94d338e972ec8368c1f64d0419bbf67d1ac0c92b087", + "input": "0xb6219f36000000000000000000000000bc856dad4b3715057699dbf6ff8a372a663456b600000000000000000000000018a428ce0c31584a21cf8cefdb8849d8013e199400000000000000000000000000000000000000000000000000000000000014d6", + "maxFeePerGas": "0xa311735c4", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x39c", + "r": "0x6f891d6141f3ea95e53353f1eeddc37cd57f4fb9e0fa591ba0ba006f0d5c712d", + "s": "0x1c21e95fa84aad87bb849532419ff2f0a57b4103da9a0e1941d246ad5fe09297", + "to": "0xbd5fb504d4482ef4366dfa0c0edfb85ed50a9bbb", + "transactionIndex": "0xce", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x92e41fabf502420dc858b3eba0e6609aee586586", + "gas": "0x3e286", + "gasPrice": "0x569e36596", + "hash": "0x525d6010ae639a2e9c12a66acdb20dae38ff5670c577dbe1b5009ae40adfa6f8", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b04b00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f300000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000252ee69c36b4000000000000000000000000000000000000000000000000000000000000008000000000000000000000000092e41fabf502420dc858b3eba0e6609aee5865860000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000001ed5bf73281d956f331a1164981728af1a499fbe00000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x63f7a789e", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0xe", + "r": "0x24c2ce1fb2e810b439755315a855b41f95b335d7d2f14c73192dc13a1f5033c5", + "s": "0x30f6839f56ec354d14c5746b15cd1fa78cf52f21b43b2ab783ce385d547adcfa", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0xcf", + "type": "0x2", + "v": "0x0", + "value": "0x2386f26fc100000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x80c67432656d59144ceff962e8faf8926599bcf8", + "gas": "0x186a0", + "gasPrice": "0x569e36596", + "hash": "0xbea6d57cbb680990436e3a06b93362ca9720cfb2f38e74e230c74a8784c68df7", + "input": "0x", + "maxFeePerGas": "0x29e8d60800", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x229c5", + "r": "0x366c14efe08688dc3ba1da507af03361b27e0d6e38e239cda1ad1169210beae5", + "s": "0x841ab3425e05a427ffd96facd1fa396986c5e204b927ac6a3cc6c859e4ed203", + "to": "0x1a918a8386f75f382e2a1b2e10b807c39728caf2", + "transactionIndex": "0xd0", + "type": "0x2", + "v": "0x1", + "value": "0x214babb0411e010" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xa6337c4faf2efe8e820e8a2ed341152afe703e14", + "gas": "0x39013", + "gasPrice": "0x569e36596", + "hash": "0x633e650a99e490401a7d3611207f74410e9db929b2e2a255acb78e41cf6eceb6", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b04b00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f300000000000000000000000000000000000000000000000003782dace9d90000000000000000000000000000000000000000006ebd81e86d974cbcf5948286fb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000a6337c4faf2efe8e820e8a2ed341152afe703e140000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000038e3b07d607def629b4f3f46a0082006fdd6cda200000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x63f7a789e", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0xe5", + "r": "0x8c1021a8f5e54b453bb4ae3ce4ec3e4374cb5fcd57ae093a892c93c0927be296", + "s": "0x6d63950ff3d602708c5cdd8e5d3d1031400920faf0eb715326c32b37d638c726", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0xd1", + "type": "0x2", + "v": "0x1", + "value": "0x3782dace9d90000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf434c4ea48ba2076e24d85d74e68c01d946e5cab", + "gas": "0x13128", + "gasPrice": "0x569e36596", + "hash": "0x0cb50fed167994c37f163669f7124014ed9c85f43382cc43c5d60bc611113d9f", + "input": "0x45682a0b000000000000000000000000b7bc86cb0183af5853274ae4e20d36de387c4a64", + "maxFeePerGas": "0x9bfeab164", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x78", + "r": "0x771fa36509876348e80270c06fd5cd68ec66ff0871d4e727f52a851f9d72f77a", + "s": "0x448a8d80ab9594bbf6b1ebf8157d38e1bd150a06ded0f05e6a9906d0810ecc96", + "to": "0xcd51b81ac1572707b7f3051aa97a31e2afb27d45", + "transactionIndex": "0xd2", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xdf45ffbb24485f9ca62579d60ebe8ed45c6784e1", + "gas": "0x3d090", + "gasPrice": "0x569e36596", + "hash": "0x4c1361c54b670b677c73ae2e5b18345a9c929f9ac5f62ff615dcd5c493337a79", + "input": "0xd29dff12000000000000000000000000317cdb7b43898e4da7908daf7e34f638ad711f6f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7", + "maxFeePerGas": "0x9c7652400", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x0", + "r": "0x117e4cbb2e6d2d54d006c7aa9a06ddf0dd428550ed7871ca60a554d85a3cb337", + "s": "0x1d298cb865bd174fbde228c62da49e3e592199ad1e4629d4f67c5b1881214dc4", + "to": "0x881d4032abe4188e2237efcd27ab435e81fc6bb1", + "transactionIndex": "0xd3", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xee0201e07db325a5d732aeb9b9fba7b5b8e92c2a", + "gas": "0x34386", + "gasPrice": "0x569e36596", + "hash": "0x50a08f0ab757c19026e1d4ab0c3d6ea9aae8ef24204469486e0d4255b5381956", + "input": "0xa0712d680000000000000000000000000000000000000000000000000000000000000002", + "maxFeePerGas": "0xa420f97b2", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x150f", + "r": "0x8a2f93c4b56fc538ce7519b8ccb1b3eff8dbcade472284e4df428be87ecb7be5", + "s": "0x19e352175541ee458cb3aad860c7eaac72e687429d2825692e4f6549441f5595", + "to": "0x068306bdd1d3b4c9969becadecd85c7c2a978464", + "transactionIndex": "0xd4", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x7820723eab5449771d8e5fedb047d87f77e09122", + "gas": "0x2e030", + "gasPrice": "0x569e36596", + "hash": "0x7423a081493fa6b1fa7434a4340063d327f1bbf4a31c53d6e413fb9f9c3c07f7", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b06300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e404e45aaf000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000bd3de9a069648c84d27d74d701c9fa3253098b150000000000000000000000000000000000000000000000000000000000000bb80000000000000000000000007820723eab5449771d8e5fedb047d87f77e0912200000000000000000000000000000000000000000000000000470de4df8200000000000000000000000000000000000000000000000000b16b47881a868dc325000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x598b5bfe7", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x18", + "r": "0xf8114a590f601f4f558abecbb6ec7e534c3101ea624b6f9c2a20a01a8b33ab81", + "s": "0x79e144931fbbb65f11f7da56ca09995bbcf5f429d0d6a993985dbf745aa90d4c", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0xd5", + "type": "0x2", + "v": "0x0", + "value": "0x470de4df820000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x04cbe75d7c6a1e36157625f48c2a23e7f894ac02", + "gas": "0x1d4c0", + "gasPrice": "0x569e36596", + "hash": "0x22df334ef5370c1b19d6a602751af7576d21bbe5645f67e5416b8dcddf5b19ec", + "input": "0xa9059cbb000000000000000000000000935f64b44b5c48a1539c4ada5161d27ace4205b50000000000000000000000000000000000000000000000000000000025cbea7a", + "maxFeePerGas": "0xb0876b9e2", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x8", + "r": "0x947c4df0e0a47b7ab06022eba11169b07db367e45ccd9d5c2d8a466ffe987a48", + "s": "0x78533c899dc6fcbf8bc1d7200c8b6c81b27140a89db447697369e76214ce39e1", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "transactionIndex": "0xd6", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x14cbbfb0f5d52c90df9f2133f38260e92289b165", + "gas": "0xda8f", + "gasPrice": "0x569e36596", + "hash": "0x4b6ca936eb88c9c12545a84f76bf0001d514c7c0df0d1ef9ea70206eac2888f9", + "input": "0x095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x63f7a789e", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0xa9", + "r": "0x40f00da82666829990abf04978a2fcf50a41a9f7a3771917d9666990e4a8d06c", + "s": "0x744d1f90032fa889c393f960b927e95138583af068ad584da952fc9f500e0bbb", + "to": "0x38e3b07d607def629b4f3f46a0082006fdd6cda2", + "transactionIndex": "0xd7", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf38af9b3c66c8cb4c109714c318493349913e71a", + "gas": "0x5208", + "gasPrice": "0x569e36596", + "hash": "0x68d24a83391fb6858bd2de1fccd515e5f5658a8c1ae7d9d6784572f0b729efa0", + "input": "0x", + "maxFeePerGas": "0x598b5bfe7", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0xe", + "r": "0x52eb24a76e40d582ac332f1ce9ccb78e6cebc251cf53bd583a21007cbf0149b7", + "s": "0x6b7f1b713f4c75432a08269ce91f6713ba773b1c0a4031c048c1b1c8788ec5c6", + "to": "0xde7cf8aaeb587b129c2d7f9d78b9f081c9f2e210", + "transactionIndex": "0xd8", + "type": "0x2", + "v": "0x0", + "value": "0x2386f26fc10000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xaccb213ed2d5402e8c2f0fa413c879038a16a378", + "gas": "0x1d4c0", + "gasPrice": "0x569e36596", + "hash": "0x38bcf6896898325ffdf51572535907def745289422666bdc2d4d7e8a3b04cdd6", + "input": "0xa9059cbb000000000000000000000000935f64b44b5c48a1539c4ada5161d27ace4205b5000000000000000000000000000000000000000000000000000000000bf50b44", + "maxFeePerGas": "0xb1783bef7", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x7", + "r": "0xda5ae86f58583a8fb12d99a7c4c8db6024c84990075cca50bbe410151ed146dd", + "s": "0x27a3f2c48970424e0bcfe639d94f40c6a44c5e8e42225d7b4e10ce63d9b5d51f", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "transactionIndex": "0xd9", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x67d395133d41e89b581dda148a34b1b2dd035e06", + "gas": "0x2aa71", + "gasPrice": "0x569e36596", + "hash": "0x02c39bc34b3d857df99927189bfb172d2231e72acd8e6c28ce534aa8c62853e5", + "input": "0x21938f7500000000000000000000000000000000000000000577654f515885e38b5d000000000000000000000000000000000000000000000000000000865ff88bce42f000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000067d395133d41e89b581dda148a34b1b2dd035e06000000000000000000000000000000000000000000000000000000000000000200000000000000000000000024da31e7bb182cb2cabfef1d88db19c2ae1f5572000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000001a75f7db182ce7fca969f029e1ef573f7aee9cb5", + "maxFeePerGas": "0x598b5bfe7", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0xd", + "r": "0x48da7de43423c0bb8d38c55de3c9d4e3ef16fc56694f250aa105c0fe6a816846", + "s": "0x7787317e3e8591dee2b4e6f76c0b5172021b2cf263a56776dfaa6e7656e39f7d", + "to": "0x8967ba97f39334c9e6f8e34b8a3d7556306af568", + "transactionIndex": "0xda", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xd739abd0184b54ba0372c5b1b7e866cdbad39822", + "gas": "0x7ccef", + "gasPrice": "0x569e36596", + "hash": "0x8feaca9bb55e8ace7153ecaaf222fc0d39f54332a55cc20b710c7c2b75d88376", + "input": "0x791ac94700000000000000000000000000000000000000000000010e6943603df869f93600000000000000000000000000000000000000000000000002841e2524b311da00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000d739abd0184b54ba0372c5b1b7e866cdbad398220000000000000000000000000000000000000000000000000000000063b4b4b30000000000000000000000000000000000000000000000000000000000000002000000000000000000000000672fd90c122416e9b061d48443c39b8d1d726759000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "maxFeePerGas": "0x5ccd9eb4a", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x289", + "r": "0xea9a56eb60c44dcd28029166ab64d6ffbdb7f704713aa0536982b69001e752fb", + "s": "0x47d900a54e80bdc4fc2a145cab2d79640ddb228302bf307692dd060f2a702b01", + "to": "0x25553828f22bdd19a20e4f12f052903cb474a335", + "transactionIndex": "0xdb", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x3a2d76c5268321706a851c250bd073d1842e254d", + "gas": "0xc6ce", + "gasPrice": "0x563406100", + "hash": "0x31a964741fd79dfa939187a82ec9253dd52eaec122c89bcd5a0a070f690f17c8", + "input": "0xf14fcbc89266e5a538c1b7244602d4f68c8016d8e236934d363f627e60597b95404ec9e0", + "maxFeePerGas": "0x563406100", + "maxPriorityFeePerGas": "0x43f30500", + "nonce": "0x6", + "r": "0x79d8260071e5c7c8ae0eaccc66e7c2f0a908bc18480aa3507ec443859b79f751", + "s": "0x20a79ad17432849be509b9b3b261e12e0ab440a6b5ead2e1f591d8a13302c94e", + "to": "0x283af0b28c62c092c9727f1ee09c02ca627eb7f5", + "transactionIndex": "0xdc", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x3d5c0bbc2d5a6782355889850fbcc21c0c8400f7", + "gas": "0xe347", + "gasPrice": "0x55ef84902", + "hash": "0x392bdcf5f18aa2da1b74f034bfe7e78948f70dda9039e932fbb9da2f08b95bc3", + "input": "0x2d9a56f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000001002db68c4000000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000003d5c0bbc2d5a6782355889850fbcc21c0c8400f7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038c86dd4fe386bb27f8d00000000000000000000000000000000000000000000000000000000011e6a3506a000000a4000000a4000000a4000000a400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000a4bf15fcd8000000000000000000000000303389f541ff2d620e42832f180a08e767b28e10000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000242cc2878d0063b8e6e1000000000000003d5c0bbc2d5a6782355889850fbcc21c0c8400f70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x55ef84902", + "maxPriorityFeePerGas": "0x552d0e0b", + "nonce": "0x1a", + "r": "0x6c2b3d81e3fb14c7bb1cf9a99fd93015678b588a2524447b4c33a388fe26d194", + "s": "0x11f71618f8349d1e61bfb7c372db1f228c8db780d31d193ce9e54c4accdb9fa1", + "to": "0x1111111254eeb25477b68fb85ed929f73a960582", + "transactionIndex": "0xdd", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x831e5312c278b827192c1dd6d28647df80661304", + "gas": "0x2d444", + "gasPrice": "0x55ae82600", + "hash": "0xc3f4554869f62bc2f5099c1de636f63516b2ea0d9670c7d8334479015f570ccb", + "input": "0xfb0f3ee100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ae42b7123ba000000000000000000000000000f299305457b76eafaab23eee4ebdce70a8c0f47d000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c0000000000000000000000000075afa588cc4e97736bf51e3bb542252fbd6b879f0000000000000000000000000000000000000000000000000000000000001744000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a6480000000000000000000000000000000000000000000000000000000063b5f7c80000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000c7ce8979d0a3f0ad0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000004d72fc081a8000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000007beb2cd9c4000000000000000000000000000063b630879ced8940842c4949af7c7c34580943b0000000000000000000000000000000000000000000000000006c6dc73e8b800000000000000000000000000bec7d9558401b43e78e625644c38bc2db1a845a60000000000000000000000000000000000000000000000000000000000000041a9b25e41dae7c34cc53541b59033d3b435193381f57d73c3ae77a36c661ae86b4c0a43f2318b0cee110fe8ad20094662b74970dca81bcd67fc4d6e17697019201c00000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x55ae82600", + "maxPriorityFeePerGas": "0x59682f00", + "nonce": "0x1c7", + "r": "0xb05fa6f06438ca94d062458e75155aa50df8b629880beb44df11a530c84e381d", + "s": "0x4462b0c17e5c5427e202d03e608af5fb66cbbc2a5238c18a9a9f8426f72b6f4d", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0xde", + "type": "0x2", + "v": "0x0", + "value": "0xc19f7614424000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf87acd73088f39c8fcaa77a036d9bdf3671cf0db", + "gas": "0x2076e", + "gasPrice": "0x5586774e4", + "hash": "0xf32b7f73b0b59b4a02597615c3c32ce55b3c51d6981e73ac162ca13a94653e91", + "input": "0x0502b1c5000000000000000000000000e796d6ca1ceb1b022ece5296226bf784110031cd0000000000000000000000000000000000000000000009569d03a2d73ab8380000000000000000000000000000000000000000000000000001f7f8382d4e3ba30000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000180000000000000003b6d0340187094a90a0fc1f0cf51ef408cf88338a456561be26b9977", + "maxFeePerGas": "0x5586774e4", + "maxPriorityFeePerGas": "0x552d0e0b", + "nonce": "0x1e9e", + "r": "0xc9e6ca696fb7dfbf8bfe94565bb0f4cc9faf53ef1d9a3e082dff670d7d64a27f", + "s": "0x339378bb739eb4bdcf2b95dcf3af055fe6ed440b9b1b0ea818d9beb778f0ea3b", + "to": "0x1111111254eeb25477b68fb85ed929f73a960582", + "transactionIndex": "0xdf", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x68fb35511c653c5b4eb86ea273ac767f7c005cea", + "gas": "0x13cb7", + "gasPrice": "0x5456604f7", + "hash": "0xbe3b3375bb41f2cb4abae795e32180439493730c738a9a0c3a2349fc2241e4f1", + "input": "0xa9059cbb0000000000000000000000007a5c4b87454208215c160e76082fce4d04308a72000000000000000000000000000000000000000000000a968163f0a57b400000", + "maxFeePerGas": "0x5456604f7", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x187", + "r": "0x7321d9b131ccd0ebf0a076e674ec9f2330426a8d502fc9a39ad87d91f2e1d973", + "s": "0x156269df50ba5ffa5410217dd5f6e5300b2330d45f45730470029d3fc96982d8", + "to": "0x30f271c9e86d2b7d00a6376cd96a1cfbd5f0b9b3", + "transactionIndex": "0xe0", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x07f22284bf9df656d06041ae313a921a9f19b997", + "gas": "0x5208", + "gasPrice": "0x54310a200", + "hash": "0x2bbfdd42348716da58d3b7e9c2667c6dec9973f1d457a9aa97ec17e3b1f9bc37", + "input": "0x", + "nonce": "0x0", + "r": "0x11046052c19bae635456cb6ddbd3874e186b8f0f046e7bbd3c78712fafbff10d", + "s": "0x7b3c310b1de04e4ac588a747c5a15c00337988a14d5faf3912006e8956d9b566", + "to": "0x6dfc34609a05bc22319fa4cce1d1e2929548c0d7", + "transactionIndex": "0xe1", + "type": "0x0", + "v": "0x25", + "value": "0x12c71fe4a935c00" + }, + { + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x94a87f7400af33046c2af07d785041275325f893", + "gas": "0x5208", + "gasPrice": "0x54310a200", + "hash": "0x05e225d0a7cfdf36897925e8114dcd0f2109af5b038914264065b576a4955b51", + "input": "0x", + "nonce": "0x5", + "r": "0x1769562ef51ea8e31dd01eca3acc2f60af6d9d4bc7fede5d2ead47dfbba8e29e", + "s": "0x195822654c8b520319012ccea85c9ce7c1161b0a9830498e1ae6bf3c6d8813d5", + "to": "0x6dfc34609a05bc22319fa4cce1d1e2929548c0d7", + "transactionIndex": "0xe2", + "type": "0x0", + "v": "0x26", + "value": "0x16848d59328e1800" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf3e7ab9fa7d86c836e837c847432cc73711fe8c8", + "gas": "0x5208", + "gasPrice": "0x5430dec21", + "hash": "0x972199e829cb791d19dc2172132146118d461a85653c46ce073ddbf91d9328e2", + "input": "0x", + "maxFeePerGas": "0x62085fdb3", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x6", + "r": "0x3e2e679e0fdf3407ea4940c15aefae0ba22dba266ace007a03aea740a9cead9b", + "s": "0x2b45e3d46124e2833d906f5a6b721a84ac9628b1c2ed05faa50a25edb3a6f318", + "to": "0x489c8e7a07d1afe4bc50e8e59f2f9f387c85ff67", + "transactionIndex": "0xe3", + "type": "0x2", + "v": "0x0", + "value": "0x2386f26fc10000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x67884402074494830f1f67d8432f8afc3ecbafde", + "gas": "0x5208", + "gasPrice": "0x5430dec21", + "hash": "0xb5a344fcdf3f543832a2135461fbb5fd89b2971678d8f344a2c1ca85921b1851", + "input": "0x", + "maxFeePerGas": "0x5ca8edb60", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x20", + "r": "0xa6b5e49f7d778588880c37087a31f2de4f8d101a7b098281afaa9da5978b2f85", + "s": "0x603f98a455df6dfbc2668c31cde3c74d28dd14949f6578408718c150806ac734", + "to": "0x39bfa21b8be214d841c668d026677a3d84809600", + "transactionIndex": "0xe4", + "type": "0x2", + "v": "0x0", + "value": "0x2e7bd4243dc000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x4df74ce4556034f6818b52090e0be45dcc9f793b", + "gas": "0x2f639", + "gasPrice": "0x5430dec21", + "hash": "0x01a721d3a7e45aaa0f1f2e16bfcff3a185f441659635dcb87328c5e8e4da6374", + "input": "0xfb0f3ee1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e8d1c5b8b5000000000000000000000000000fb20add8c2437cc3995e9cbc5800989c68b978aa000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c00000000000000000000000000201675fbfaaac3a51371e4c31ff73ac14cee2a5a0000000000000000000000000000000000000000000000000000000000001f71000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a66c0000000000000000000000000000000000000000000000000000000063b4b47c0000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000051fc14d9584501d0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000388f27d8d30000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000b4fd4c4f7000000000000000000000000000079a920f4f0d142264f147d5840248a8c0ca346d8000000000000000000000000000000000000000000000000000000000000004122fa1d494b829c43e2cf201874396b1f9c84efd6d38fa011ccdbcca795be3f370978e8ee8024e39721074a98100f5a945ec8f73fefec4eac8b3a94818389187f1b00000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x618a4ff29", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x4d0", + "r": "0x102007b7762b92ea8841d2ec43126e04ef385cee0abf5a2f7046101b23cf890f", + "s": "0x3dfa4f29c98a07c27f9772edf3bf88786a87cdb4adbe85f10047ab28c97e52c5", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0xe5", + "type": "0x2", + "v": "0x1", + "value": "0x8d65e39e0f8000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x92412a57638946dfefbd94c263014658171fc42e", + "gas": "0xdd82", + "gasPrice": "0x5430dec21", + "hash": "0x0877b4c7d17b38b792eed2d6fd3c40e768bcf02b1b0db3f730bf8e02e025435d", + "input": "0x095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "maxFeePerGas": "0x618a4ff29", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x82", + "r": "0x7fc44ed896fb4633325e17967f2e1cf093d5efe4ef1df0673f20f5e0dddd0eea", + "s": "0x6cbc26b6653a4a440926fb30346c3362cc64cb5c1b18a7b66e50e658d79a11c6", + "to": "0x61b8d8a3e7e5a2c697f2e77bd7f2fcb27182e869", + "transactionIndex": "0xe6", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xc546194fb601070e46ab4da1f55c468f9f0e167f", + "gas": "0x4c5b2", + "gasPrice": "0x5430dec21", + "hash": "0xcf3d985c126de3671a1166cb65a9706cd61d1cd0f824eef6768603b97e912031", + "input": "0xa9059cbb000000000000000000000000954e051e48b217d1cfe563fb27f819de77b79f3000000000000000000000000000000000000000000001a78429bce3279a9c0000", + "maxFeePerGas": "0x6c92703ec", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x740", + "r": "0xc45e9b18aa72705796a0a492d9687d32f65b863b01cd698af6a156b919214c78", + "s": "0x7a7ebb2f19ae0b26336338e3de0c74423fac01f95108533d3ad3d7857ca162fa", + "to": "0x54cb643ab007f47882e8120a8c57b639005c2688", + "transactionIndex": "0xe7", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x6887246668a3b87f54deb3b94ba47a6f63f32985", + "gas": "0x52b18", + "gasPrice": "0x5430dec21", + "hash": "0x9bf6fcee5537c29473afb8c171857e0a2051a653c7e39d9dfa435c39bfcecab9", + "input": "0xd0f8934400038f6f11000048000006000000000000000000000000000000000000030000000063b4a92e0000f929ad00000d0000000063b4a93d0000f929ad00001a0000000063b4a93d0000f929ae0000070000000063b4a94c0000f929ae0000170000000063b4a94c0000f929b0789cecbc755cd4ddf6303a3374b7a4a4024a48080c295dd2a080a484521222283d84a080223284847c11445a3a0529094110909046429046c419f27e0e9c732ebc17661ef5797ff7bc9ff7acfff6acd97baf1d6bed955f10381c097ee81848222b1d087e1200e7955da354f4b3f29b09d989e7fa5e39b2e97ac71a56013ec3ab5e1301420934c3875b9b9413547e4213839f4d1a10ac995de1d293e65f50f5c65660acfd95f18f01000d5ec6445c881b8fb36ea8c65cf9bee49082eb0ff6ce1cae89653173465ca635858156e1b86f8b4e9babef027ce737a8a582953ee4953999cf4bded99e95a236f944edcd80660228a0e4a922d3689243cef1fed1782baffe59525dec42318fd2d546cfe665f1758752c0f2bea80cdbcc65c7f7e7da9146f4b37ead65ed150ee55eeebd673681b3b7583141a0f78816dcfdd32070c9877bbc09eb15907f139a18cea115cfa32fac9521e80a2bb7ea4fb19d3f3cb14f7d334d33e94e7f269fe1dcdd8f3ba3315c081a548482350b60e8fa8b000d44a35444839ffae79f3d2ae82f7fd326dbaff97cfea2ed038fbe87ca95367cc1001728cd7a87bc5e6fdeb40b019a98cebb9d512d7045a463d99ccb5d13c05d2f02411c90105bd9fdd54c4dd4c2038a2cd5b068b47c4cfcbd66064bd9a46ad46d3ec32a2098c8c21641345b8be66e80cd4fc228584606c8afa3e8aa44343516702216bbe1b43fad85181aead003e6e1067e62568180e7f3b91abb265c178150539f625e92c3f8a9af6c340992908d0712de4d5a85e2e14d38e7df1dc6e775d0599e927cbadd6faf994bac774f037cd51df730bed83aa351877c22f84d967faaece058260e7b14ddafd0f7ff0380a1c09e46ad6d7752ee5d0f75e90bf16f46b044676b1f335d9765db5e0cd15d0af8110d98bbab0d1725e097743ffde44d9819a84872d52f16f6997df3ac7d5e1ea97da62c08648fb00d849476ec5fb8b818189cd50847a9119b27eecb9cbedbf977fd123e397b4f6081d259b550c028aaafc29a9e2ed8edfd85f92f97398a54b5e506ac61af5817ce2eb1e0fa737403948db0e485d66b1475b7c948a6ed78bb3394aa7a0661990c957356b29e8d9765401805488c3cffb1fdc970640ae0646464e47aee249e45ab56824b997c32ecc35dcf601518cade109d234bdd93184e1f7ac24e3d932723d8a63026d310c05a8066774ebcddffdcbc23cc2cfbfffdc3919ffc8cddd5de50226f243c08f44e51217c4fdd700ae757e8bbf7b24e6e907dc74f006336f1fee63a6b194939d94994792ae53c03b19b51a3241f26c1887a7de86e27184d7774007e8c12aba5cd9e2230584a3e645cb11ddfc80eb4b35cbbc8866c3d769f31d7a96f0cfc11317e94a668fe71e49d3beed6a1ea6c59259d8366fc23dbf9a7b709ddf887e1e4dbd2b9e48d2b020001378c752dae886ac8e4c380a681cb7a759b9b1aeb0a7122512a5d8394b0320093a7dd1bba60df22816f5a58e63a83713da5bb405f563890c867f0463aff4d957f49884e741202b929020436a7c7993e06f4b32c8290f178a2af9d2725665b576a05c5a19fc7a3eca22d01a2d9b4a658c3cd6c6cd80bcff68bbb9490e253b243d96e8c3562f13fee0e3c7b05022d22e603b112ae05926bc2023188a4e140bbe0889332ed2757560b7d8ff3bb6cc40d4d7eb0f2204584b8169a3d736ccd5dbe29ba46e03ba1ab1fdbb0b99e05e4bfda43acdcf1c4f45c29706292a045d39f52f283785f97cd4e5b776ff3a3c1fe2a845384f28649ad35a95ffaa6d292a0f3b20890da2b6577766545ee927bb7a96d42105141d903a7b42473f7857e13d1184ecb4480e6fabd1587da4a8ad5a2017bb6225c46a5094f1bd6355f5858a46e79e74f0252102813f18afa1f5b1b50d2059fd5f5725ac548ac58247071b964eb33945be5e2032b97c7337d55749830fbc7c372a32111bd1fb4291a39947ace8286a0ae27ac01c2c5104ece01fc43cfe2e38c75d4292e9bd92cad49d5f86a63f03149f0acd9b3f91faac5d217df3cc4a7020cca16633e549c7afc43dc71f9e7936a3f6bff787faba677b2d322535fbdc93ba841604724d83e00e7e5fe4d50bb2c0d0fc8e19e9212b49130a8511b968e3575cdd25a37845580314b0b4a1b0e5341459fcfe0b1aeedc026aadc9794271a9cd05e268266f37f17c05cf5498f179cb11eeda10154835816677afd29212200f9a65f02e31bb9afc3a6a77023ecdba83b5358ea91dce2f801e13dc4ca98ba304058b36230c1897ff4b47fa3ff236332b14f5eb6d6f4fde91bf75edc5141b6d9e182400e083b5850e1475350800e2efcf91afd44bd73168384a41b894290aca8dfdbedd21015ba153fb9e5751814883b13ccf0ea07c5de437513a9301da19bf7ef892d6a05b55bbee64b95733e7dd70f50789e4d45b7e97169e9c9f064dc0baf50db738fbecf14bb7334eaed5998b99c2307811e2242c8f60f17838f183e3e413efb5df2929c0fd343e32a3a051b6172431d585a6989d95954cbc6d27d110a056af0aa02db2e34445bb3be7daa0ef1c2589f19e3bcd8562763faec1bc17b27f3d300cff9d4a4c76455e1dba5d2899565f37d77f174afa47be86f49712ff532acb41983406188870158cefbd460f65f81c71a6ad5771aabf37bb911ad2f522499878eb2acc3d278e97cd44f20843553a6c55ae1db157728407596a8832aacba0579031aff38b7f199e904154ed0148e539ef3e6d80643ac39001da1785f0edf69212aaa2cfbfa2543ea9e40ca4ebdd0dde75b9aba433b6396df0f0c3e8283ada14e84279247453dc13a47fb9affca3afdfa29929e1b764eff30f8d03d64e059e51351453cd2c52183f59468aed59187ec06b24d1aa345f2f6997abc165d243d56746ce02b549d2d8bb3a3d18c7fe4a141f7500d928212ab4d704608179aae5fdf8eea6f0511a5a01b1f0ac4ec50bc8f6eaf9dbb54faccfe94c67a0516622ef83645f01679836a49952a9d2e60b8475241531e41b20b7adeb0ba94419a2fd682efbbba50fb69cbefbdb75ecc1a0883028941cab47f1cd8eb06f04a72565656d64761690ad1a16e80b3fa55fbb5969042614d8a36e3f00a485a85710ea7669fe88d9028d0fbcdad22132a9f77ac4fef2db28120ffa68aeb308993c9b1fa117243f929cf30dfca21fab4a393367f097f0cc0fe103f8e1a0d9646d31f1d407ee5cf091d3fb9665d66183f6dbd71e77b90b0e4f6b8fcf46f8fffcf63fa95f98f0134a62e5a6d178db18016fe787d22808f0667ca97f062e117480d9e453acb2d57d38641b1c4e248ef2576abdeb7529480815a9a7c2e206f455a4f313feefd63fa6e719b8207635a0c6b75a0a1d2edea57fbaa04f1be2a317d177e1ceb1ea34ae47aa99de2c0192f2cd9d819ea41e8869a551299a37b394500dbee692b433105514f1fe6ed46ec8d4a11f1d1ce9be371e40302534206d6e5cc00b381a9ed6430b9e71df1f5be8fb3f1254dca7409ac08aa4ec9b6d392cf3f7a7cdb572ae9f669d5c1852b73eadaa6f47249e80e3335bf13143ea35488792f44b51d2961df33038302ef101462c1132cb1b1e94ce43eef32372304347383fd08206adb5905f1b4954b80027f8eb43752d8dfad1de25153d496d99f3c1980c5bca49a7b3f019f1dc122bbffb09107923c310f2461a6ffcd870d6c14832b026cd2c39695ef7fc87957d49c18e9366c3623cbb3682eb0bcabac7f6af43677f70ce0e5903fe204102a647a5dc379df796a7c06730d32572fee4e46952b4bf94a2504844982c4240c84745f3c30825398e14f5b2bde91bb259b605e95da148edbd67ceb6a6208abc0486b85b989a2b93668e40604cdbdc640333c3a001fe63b71bb69ce7a62eb1de2f124c46451bf3508a3390e030704c1c4c5c0c30663fdd604e8f8121f155243f001cace1ad2cddc28f1b2ef51ca550dc5b328dda71a5ab5f750e27508519e1f5afc155d1e9478d3fa377f8207814098698214655bd42161966352a67ae2d4cd94914162a5655d85b69caf087a410bf06ee7023f16f907985d0fd95ac06b2c415d2265e1dfd583c1743e7bb5bc32f2d964c23ab57a36624f2ba877b67bab66fab7ce434492fdc2de896bfda05e3e2dd81f989285a7289b0fd17a92f6ad47c9229c68fc272e8b2a6de32ea17d2e0f7938153afa8c553e4fd3526339f7e4f418ad99e8f9795e656cfa5191132b434fff2c719e2fd7fd9d627b1759439685b8a3cfc387aef74d5594030af9f80cc2b1fca67bafd33db0c36936cc89fa784a024b33a93435dc2ba2e18da63d9bb27dffd8a48ee32ecd335ee9e27237e81b97f11c5f3f7903fc0f4adff9793ff10f4fcd09131b2c2a0e704381f58aeb8d2b73cdd02f52ceaca2d18c0c4b9855fa4ff0485a7b63a5439b7c9c7f004489b197ced0452f122eddbcfb7c6beeeba0d0b5e4b57e5796f776ea9f4c230d36f6ddd7e2bfeabefefc9a214f2622b0cf832764c8f9ca071d76dc7a94b106b0b49334bafe2280f1360d4143cab9466919bacd87c4166f2f6f88d9763da0522759c3b48624dfe404e8468d5ebbad3296f8c3c5048d5335a4d9390968dd97c0631386baf2839ddc4920501622c3bf6b5fb297fac0e1f9b85920fd440a9cfc656e95f8cfa1e1d38f0d8e7985ce664bede8cc2861fb776078f72a0e9bdfb655ee41b918d3efb550a0192badfebe04c1cd7cd7e18c9f0a066104eedfbb1fac765c4a5859b3f159896d073c1934982d1f5b5ed4175cbd2ff8a8269481e882614a913d8e32e547398eeba2cb20500fa22b80b4fa40918cb905e7cdb6ac571858d17cd154a6cbdf6f44846bf5f6620851cb2c9ee806acdc4a5632abf230159f0a647dd4f902d7e1bb5df5bd4cd486335f5b224fa6d936365596f3c40575b6134c6ce24a83448071f987482cf29141dd1c5289ce8c546eb0e54680dcb590ba0091bafbcb49efd9008c9850152c7abbc86a7612cb77061951137c4d03696c24dbdd43f66ea93a6593203001128c6bbebf282c230d38a5738b2f4fc9194fe126a2c93e13ef586e8b6f24b0f2b4420587232e7ab237a576e7aecda7e8dc70d4a61bd7314f18c40f427506201008170d1e9df446a79561438105dd488baaebbe6c6af3e151e10651354f4c631e5432cdc91232cd3164f4656402d65e172a9f60f46d842dd46185bc8c268109165cc2efaec69c7156e337c83130ded78af0f7ef639d369cfb56644efbb5d4f020eb4b9c19f80bb6024623e4c7dcc77515683ef9b51b16485d0bff5ceebec6676bcc98e8b42228e0f0606ad087e39595dbc29df739b5c28f3507baf5362865994d28e1aa890b7ab4c0adc74ff8075560af27c39fc916ee2af59cd69f4fcb9f815f21cc5b1698a13d9509c2d140e25c0e0093ee9f1d7eab0dbc012c53aabfce62a1336cf89ad77b2829e4e9443eac027b2aed72153ad1c87ca411bf8ccd4437fff69ccf98b1e4153cd67af6ed1ad4ddd350fad7f15976d1992c68228bd868baff9a6ef21b1e5534f46366fc4fc720d0ad61e918f815fa8ebb4ba8a84375c6e045a4fbba4f4816eaf5fdef8e4140264e46a582208f4040eb57e37f7b5cf84e36d2206620d06b1080335ddb7df26c61209026e4e45b4d514fda9e3db8b16459370d8a8e5eacd90ed0bd06e13faf26db1e3933371671f3b8e395011f132ac00661a2706bfdbf809a5c70f8894198632fda1f0561c083223738ba728ad0517cb8f1abec645925fd121d19871be8d8e9d89d47037f725eb5d77f35ecf41784acaba4f4ef46a1897fe5cfe864cf31808e746674738a003a74cf82a92e2cca65948d4ccea6875427244edbc92a6fca75edc560cd6455f40344d4cfb76261e91284fd5d5f15343634e56a853c20fcdf45f090e7f3d6956c1640a050c403ff9e3f7469ff4375160116d4b8a6da693d4a33c7b3d7d4f71014bdf9791544b9fa5ccdf0b181254f0f7d4027bc237dd0bbebd2a87abb5fe4cfd6b62b5db36fc302af374a7c7cf9cee4d558f0811399ea9f5943ee7f9235d478b8f51b5943ef7f65fc63006dd690cd7447bf3b2053c6a6cd92bb6d6ac7ec40981eb650d9c3faed2e3056391a998e7fa51de4cc9f67cf75bbfa5a7e8870f83c7d75beea03f08b60edd033cbc9de84e8b3863438278ce26889cd1c25743c95603ed4f9502d49bdb7eed4d5d9d3890b456fb6012b4c614e8d967489ad44c4f398ba062dbc8f1fc55a10538f445ba2ef6873d9230f29862523f02e0b815c739a301b1c3b3663ebbea77884d929578e510cf32fe9740f49b93dfaf42c6074c76e693753410386de5d06559f3c7db15ce2b9e89aa9e9298b946ea097bbe16d9f200bf3a73721b4b917d901edcc6c2fffb4d7d52a2c1f915d52c6790660b5e997ed36d101e6df6287b9a01920900de24620f690e6bfd382d8e222fb9ab216713b55aa26c726b757e97e764fc3602240d0ce5b7c66c4889c8c12f63c51bbe7b52a3939525541834632e9f1ba8ca7e235800efec3eaef57751fd1d8f59dfea002eba4867a989947f374c75ee9d2b2df5c5cdbf7cc0540fcf667c2fd3804573d957606274b85caca4aeaf436532ffe5a2c7c1916d40d5f898202187746bbb12bc36e7866aa5a8763ba8c97ea985a93c4c7b63a3ee9c926e1d1ef03c890a7264be91da3238b392fbb292f35d6ddbcc69b9fd767b355fc1c58183094da371761813476a04088caca494ecb4cacc4aac3a7303f4b0249608004733d0f757e35cdef8dbdc19d87eab66083cfd58a00dcf46e1cbc2b577a257d836ad28a30af73fee8b54b99053f5f2e1b066db0d6bf03ccc3e9473b5999895513e45e2e5bb1183fded4b126e2fd449fe7ba399e9a65e000027d43cc121f44a4825de0119f5e2b4dcd489ee65130e016082eae4ea830d9859507cd2e668e1d9efc5755536ca4d21143fdabea8f0eaf172f97275ce4b412c4c9dd73571a2fa3e10e10140810ad1fc3acebedc02cfebcd569d75b3d9c20567ea38730d93d75724a46cbbc1af0b161d9b09920bac5f4a070be28f1d3d2554ff03989e4689e55903d48be81a1e7405ad1ff1d398e7f2aadba7e65fc6300adb4229952361eb3f114891e4d1248edce5f6cd7f7aa7db5d23e58e73a4ab746d8942df6c861d0da23453f2360230679e5ebf9a8f0cb29f3c47e3fe7d4989dbfeff624a297564923dce215d8db18651cf9ca1fb7be79586ddc2427d5bf8e4961e41a3378895d1d201487be05de2238ee066bcd8b11266eebdf395db37683a951f5d1058c810b4105209013e256209833fedfbcf2da8d96d1ce445e79256b0913faf9a7ade1e8298a10acd7fc386920104c0438e77161d99f991566b2aeec341207a11a0963da295f0a708770724aabedf56f03620984861dd209f4a2dfef7b7c9bc87e6933419c0201dc3fcbf417f0555697dc04815a11cddc074eeadb137047e6892bfec9c4e38fc823f32b1e241083e903cec0caad605a44471ef2e398e25722f1321f89ee7ce48b1b893b0f91bf55133ad98bb72b2c02f8cb2f977eaa519379db75ea6c525dff7008d226b6d7db5da4e4f4b0a33b28243e79b32476d03e99fbf476cd9a4744057f5cacfef9f310adeac8dd8b3ea77899b1f7c5cc41f8164c36020f77e25c5b4d6df123890a8be97663feecc6d8f2f918617f5c9417bdb01fd0732f6a375694970bbb6ac3089c6120d831b2d126bcacc5de765bad46bd071fd060f9eacfb55c024d28d45b8e6e2f5e116f9c1a11cac3c11d98def4cfc11d8d38e03cdabf83f33e1c6efd06e7b5fecaf8c7005ace6b78b298cfc41c35b75cc833a1be1e8758b28a98687ce1df3ee433176172656dc890e349ac6f93595cb13065c7833cb610e2d6e2a5c1e08baa557297e5cfe17e459b5d2c024c8c710728cd54664b5f7847b237b546d16c46a7a1ceeab192322567fb5255370d705ef2ebc0e27017e0b90e2f083e4f284494bae7de9d9b7fda8b50400733d4f0202be2f281d4efaa861fa701a0cc433844cbd4e056a252ba559ebd86f2291c69fd1f59254be23214c6d58d56ad05239f62208072e8e3efcf07efc34be0ce3da5d71f3ec9b9231726e6cfed5bae3c28a37a3bae75ff1e63a27115cbc39df3ab0f4fbcb61761dc66da290175e39d89eabc2719506d8f32ae04f6b9272d02b41733d126884dec7de8337575be1241eb50268e23faac58542dd633c8a461e703c0d4f86d921773dbc600aa1845603042c668a675d7542e245486380c4f74ea06c53f235d18552dff8e749990d29c698a6ac6b7d96bf890df2eaa29a659b1f03f15e90283ffc4dd7024d235a0b859dfdf666cb83e28d6e926e72703c2687e828d8b8503c10083b031f17e6b823f8a74b16867a28c80b3e85cccf833bc643c4abc2e1f4afa5974bfa37435b1e895a1428358f4e62c51e2af3aa37417b218fac8a19c0004c2f4e771bcb1e519e6f36e4b285ef22c42e6e1e5c23cfd643e7a4e99e81c664dafceefafea95cba3d4413d69d28a17b6fa342ce7eecc0de32011922c0e837ad78bcc2cd6de7adf3243b8a7b6c46ae7bd1dee22caa49c2f78e3d11ebad1cd91cf955e15699b911c8290f990ebcddd3e608cf929f7c59b7bc6894e57c0469c5f6f9e0639a0a3cf9dbb76b72aa064e5b5b8174f576c051947ca40d6eee988f1673abce46eb264e21875bd04a768573936ce721196717c5cb0da0d0821ab1af4e0151dc0de665601dfc0254e45aa1964e6af94c6ecfc74d2c70f7bb320e641bd5057de715a4d2a97cf7426b44f7e7c581bf7d188dd1b2ac1a2a1fb818341399fa94be95a2609edde33890009ad12bceac52c43c8eb9d19c6461e855d5cd0474ebdf252ca9558220c74bc6d00dff9971c8c62349362dc4604c3c22ff52649adbeb3e3b63c50a35569715bea240741a29190a701e0af07c60573323cbf1732075d34535f9bf1df7594495e8be1efe30ac13ccdbb5c256057013993fc96741bcdbedc408347c7f447e2d2bdd9763f9115a430c153b695cf3d8de86f6799eda2ea8c8f03bd890a8fb769618c863c346e10309acc965f4adcf95fbac6309458a3f3d2a0954b94bfba677f737f1014f8fcda7f7ad790656a0a97b375749299d1387f21abc4775b44ae347bebaa29f6758033d92361ddc51bf945b57e65e31bc613467ae3871a852ba2d357282f69e118601f4e7ca475f94d2f118ea7f83a1458c4ec1a17d95c2479dbac59c82c96bb30f6eaba736889d7e6cb27a65d0c718b3600b3aeec88b6f72d6a0b86ee94a9d1d44cfe644eb1cfee6dca536d66f7db5eaa8b83c0979160e5fdd49840c85715b8e7aaea70e837e715cbe66fe2dc741ffb5f265ede83954fad9230199c488aa4a19a958228072a6ac1176ea253ced05d0d749c75e46aa2f3b61e67254381dbf6851cf34a5ddcf28f56650ab95b5e2bbca170c4d761e00f58fe714129216b05b87aed0296e584b1abce53ce08b3b88ac26832a3463979dadecbc3f2540b31292e0799820c070a083ef4f84cc172481b0bb594c56f640a92dd208937781f49d249513dddcc7f158a5c9240fe0afe18f86fa6e049f0cf63fa95f98f81ffdc4cc1bfb83e28708b93a79c65f31422b2206e253a775184e3b40a1b22a5254216e6989ff691870eb0cff0fd74db827d47ce344c65b75dc8f9a6d079a4f62b5bc1d7b1821773b7dfce1e5898227f8785d976b8f51b16269afad4a3e31f03682d4c374f21934f0c0370a2b0e588cd6d5bebca9b1acc9c5a1792080c6fb24e895589db26c07c993fd7d0d4eadb6242cec43db3d779ab3e7ae56598699ef4fc1dee5841f416a68b506071251e4789d1b2d2fd8b8fd4e9306fe8c4b061dc7ff559b3d349e783ef3660bb7133d6b316c9348247338ae12020a796cad8d693e318a8f3f862fee626b93b08fc08090ef58fff3bcee388ebf137cee3d3af8c7f0ca03d8ffbd4c5fad5d660c286d7ea8a4b848fafd6e726b05fa2858952b24323e304fafd346ffdc030273785ba201575739f43013730d80d971b724d4afb52b14fb6143dfaf3c08bfd7a97a5ed4d44aa00a56a9ecfc505b251b6d9b4e51d0721a5a4cc0cdff700e0ada1a53a82f862ab38f7952df174a7b568124dbaca4381d19cc8a181924b79970fd883e6401f307a7c62b6ff35a59123552e1261d610fdce92b5db3f9ffdf8523ca31595427744bbfc8d24859380857141d13f6a415d06e5bf5e357f3f01c3ec66c09754fc01893a1094e63077b8c94988797ec7b6347bf4078d8dd73c596c06031505aaee96c5d97150c0db51d3212e84eddd97bd110ede2159788c9811799cd2241dfc49371f29bd6027e09093f1c1015ba8942af41594b1de3c34fbd2ba5bb1fb565b95aa594d7d158bf3c171101e78609a66fff271a0dbeee3bc8028b703dde300bfca74122aeed5e7727e3534a3479aa69d848a8e97fe12537af4b7dcd8472d0f265e1842e4116f9fbb38168489dbf4a31adeb2383b5f04582757507983f3e11319f14b46c0565c7daf1501666cd6e4959afe52f6d34c2a00108b260d9958bde64d36f25867f2d453cdecbc8208d55091c669896cf7b149ec73fb713399fdb8d923d05f8dd8f866867af506343eed67e94cf55da20e79b96e8b323717cccf031201a445cf4aa425d5f543ed1821622f0ca7db5f294d9ea9f735fbec76fe422bb5bb2060e0ed1b2141f08450ad9db06f50559dfe22a64be815891d9cbdef0d4a4939eed9fb8afd812e8dd1db7c82628fce778771699d4604e8aa2d7dcbac15f7e865a6db378addb99eae6fdf2e4fc60c0d836d15050aafe28d0317d21df49a3f27a62c3fd4a11df6a06869d27efcc923871e4e501fa2156e7ccb7d9f16fe03bd1e96077f360c23340e877a96a6e4256b8c1669c2ac6277501a19d864d4bb1efc918133504062a895d0d9d4d089a70bdf3fa5a55a62f84ad2568031abaf6b7541b69d84d70c70efdde8cc6e41d353477f5949bf71ef0f9b64176f4aa5f706bde9fe6a7f4e69a5e89f55965f74fe4fa8b284021b0492bb164fc6af2c7f3260ed0d800fe7375148fbd0132eb46f3cb8e56858960588b79e058617e5954883a654a35c72e082212f63edf11fad89660af4cd8c674e1c8810bdff840f760cfdcaf8c700da07b64fa2b6b3a0213081f3751ad48050e9a52d2b0d96b1a2a517d7a8e99484b3fa5389a7daed55bd0ab555145784c1ced9b79b5a6c1f3a8bb1c988dfeec10fde729545ffc0f6dd27feacf1c867855ae94e79490ab4e6da0f3e1eae0c6eeeb275cff8ab54ec9f0153701826c9333bac1dca84cf8b173db85764f5432f32e8f783bb2fbc637dfda61c042e44825f0b1ff02517000faeb2a47c44fd496151376e993d0bebadfb889161f0fd4a7e169efc0ab0fc7bb62ab5a24b6a76c58484b5fad937fb9f89d6e8909785c8f4877810e1eb0c4dbb8c5f8e3b995caa9e658fffc566d2f61562c4bab5ee8ac567ee1f850cad897e7505cd9ad101ba43b14067367190a29be2f77700b46f3404f17db99dcdc2a7473592c9a9b235b825336b64d792736bab81b6d9e43c8fe03c20fef30128156ef2b6aab1789a1cd12ef5d5abe5a7e4f78e7716ac7ba162f79e101df090c13f79c8e54f78a8ef70eb377808cd337d74fc63002d0f2d66646dd06c27dc92b9db59594cd2e7da4f494890a48c4c26c6c81c0f26f1fca25da121d28b49bc96714dd525fc27297b8143f235ec8fddb25abd0fde0dce03c5f468261001def3a54e8c58c46cc7f5804bb0a6528560ebc196afcc09b487723b52eff063a6039aa025c5ed33331f1e765ad6a6ea7d886c62bf1f4edafc4e985c32acd97133ffc2fe734274a014199d94d984ee6983b4bc6f8702e9be7a42f2293b98e6561f14b1ddb70773eba86eceadebf51364a4a64f82cced01dd9c171b12c5f41bcd4fe5da457e4ae4516d68d6dcfa5e3926ccd6d12a314932f5affaaa3f7a6621d60f544580afe914bd33c8b2b9072d1e45756b709907379684fb3ffb779f21b388ed2487ca00e75e13fda4173d9bdbf65591304c2e5ecc4aa63d58e6a39fd63d2d9e1fb7e98994ff952aa3853655665eb7dfa753f5b9523a5b5fe0c05e4286751b704ba39eabe47bc7574298c59b3b8024341950b5b013269b9c9fa2a20fb5fb5468cf8d69744b72335e835f3a94eaeabe2644f8af0c229c08ebda59bd6cbfd4ca179d1be735138b3128028e092a8b277eb1bbcf51508fe47d64bef894d027ef6ed508faa0f2561bdda3b9b78c011958aca1fdb7cebc79c8f7c5f46338b97a6420fc1b81da858f00049b7e83c299fc0517389278c4fcfc8c22af3d2fd0bf53cd81300ced8e0b250481059160be0008f4a02c20b2181e3ac2ffe6093965aa9f78a97bdb935c8b42cdcf2f834b6698d761a0f2c6501a3674ef36aa800b5857c7e912aace7634e8723951ebda1ad19f175077d7601601864bc6d87eb0b10c15489e3b1b93d343b86cc76c5f943fec2e27f5f9743f93463de0cd3e73b3bba831d1ac532ea52a422f0d8b2ee8bd2357de85eb3e8d6913933e1ff6e3ad90039d0d3fe0af2abbedb59db9c4233a32e616bcb9986b7de91f425550c604b1b663e05060d0f52ee618cd52a0aed2d2199db673f26a0153ac53fd5df2e60d8e832eb78a2700b2bbae77cccf15ec88d02b1ae830fc646b87e8c665d8b9bcdf1596e0b0369642ec134b72209f492efcd502d0df49d88002a2c641a59377f1d6676bee46dc64a3937828463eed18f6cc570ec39d76fbecd60a60b0728d4286f4c30d57f5d3787b4d2cf842dd77b352e6a0296d52993396723853fb454f02fbc4e2c569ffd5a2a75a3afe6b66ed5497269167d25830e6626c172fa1f42782058a0074fda1404dd94ca2e8a9e501bfadeb203bad612e85f87bf95aa435cee731926ae7f0fb7881bbe4b45fea107294dcfe7edad3e743547d9f2f2c1a16ae48d6d41bd1bc212e2dd9dff903598c6542777c8a40b9fc97aa82935df6203c07116e31340fe73fde711fb5e7ddd91d9558d1db61718dedf3780d6bb31d9ea23ff9cc9afc7f10fa98ec0252d35dbca2b1d7b8abf52518201d24cb1cac8451eaaec552dfa536ac6fbe33bf77501e24799044d0d17cfc373ccad34059f10f0fcf1c1e68b299aa717e86da6d96f8cb73eedb6a765faad0517b0260a5af5c7028e9be09f995c4a0e3400408114a6adf0abd3423d5315b49337a412fa7becb19146155c77dd6aca7630e810f5ce52f88cacca5399fe22195fe3a89fbdecfa09d6497762c6df8f54b1097996ba07d194e7420c365e0c77dcfe06f4a0c82026b965eb7d81780a7853e3595d62e0f245c74da8421fe93fc6bb41ad819c9955480363f3df3dd5946ae32a54020bc51eb6cc288f2e2b24307aeee8db33f6b30175540384e481c87402cdd83dc204f72d0b1421c56818319e944ac8766f3d0041230d039ca8fb8b6960ca334dd32237bdbbd08ae9bfe88a95a294e4697a478e47da1985cb24da2c59b7b789bd70daa9bc757e08b7be4eb6dfd909158a732a7bdfa9e18490c9f9d3c23cce9a8c3f8264b7e334f24e65615c93d6301cc5b9b18b33ace87f15a21c3b63ba238c0a96ad92218ebcf9533943847c263448be6a1db6cb735af5f768f36aa4edaf519693952abd7cec6d3993523a4361159ad2f835b978b81b03a62175fe814b92815e998d7b53a9285903748352c81de3d8c177cdc77cf4f5110dead4c51182b4d9f8c95d17a24abf121f543fdb96a9c28f5bb4ff76a3b455719c2b98ef89260d7aef75e2e0d9ef683742dc4a4a83078aee31e593f99ecf359683b188712b2c6c012e7dc96836f7844f0117138d529d122cff11833bea00d76ed5a677faa7864ffb0ad0523fd30d436486b1f334dcf357f1b2539727faa9f36d5077130b07a3212acde73520cd34dd63ea284bf774a2b6f1336867c107f8525bfbcf735c232e7c8f701971f0ef1a6d9ab7e9a1454b4b753b6873dbf7ee948c24fcf7d8f9f6d5d6aac9db4b1faa619cb5ebb16ef8f94662f09651ac763cb47ea52460d14c1de48a7b57f1c3c8caf4b32b0e6b1dfbe55ba251cb9989ee527e83175c4759927387e17607699e1b9c82b1fbf13179d7faaf788eb54985d384e3f85b0dda1217dea47fd8f3855ede42381795d5bf986574cf3de8cc6379644ba52940706368edc5f3d22e55a26b8e3edc83bdabe60b18fd57966b847f847b4c6794e9287d3f8b94b8d9ff3f635855777bd8e7ce8c5a9772a254a21b979acb3e98e9027f9256b3347040835a0d2cf18b8d6c9512416800393a5a4500eae61988bd24892bb779267f9003093a54eac75fd07b0689a7d4485c7c3db4585078b669ba3a48fbaaf1665c2117b2f2dca40295307eacfef61e5a4a2c483ec7a50265461b0eedaa0c2e38c9e4339bc047700aaf561954f70a094dfd8a3e9a83f5424b7d8820a0f71fd81f2fcb055eccb51e141042f51863e705846509e0f2644f1094abc31f9800860f4892fe9a135c3ebde814189d7d84ae3447bf1560d9563cc051b572c8dcb4386011ea8e1aa75fc98e3fa03eeba74194d9dc4e1b8829279a740c4f7e4f97b239dcd209033c23110e27f5077010e06e0f4d82ae5827a2fbdd5ee933e5d23be27b8141488f500637579bc2a1644011301d2266eadba40b869385e33465d52fce8dd10b2fa11bab6476cb42e155d0b521b0698efb19da9b8156c523410a12de80b9f2a4fee0b5c4d4fd814d48bf05837f9f8e22f652c949196737f6bb7f98d8c05a533aeeff4796711d6d1b771f917c776e0806ef7afe08f81ff662c9c04ff3ca65f99ff18f8cfcd58f88beb1301b0ee717f2ba52d65f3079e35405f30d5e5137e174a47acd3a4514e09445b513302ac1e44f73fa53777b6b6aba5b09b7a7fbf53713f37cc74a98d72d489f994e534c9beb9781091c50ecf3ba1f4c6ca56e0d111afc02f075ce9ae1ed14a7cd359ed9bd6e3567b6e336daac4c4bf762bce678502feefb25ab93d597c59ae9ba991c476c90d4a5c721599a3a58044f2385f7b9cf510e0ae26c82009b8729661c87c48576e11d9e92dc0c9cf7cb5f527125b48109742f25f65fe0719677888c4ff96f9ff45f86f99ffff7499ffd89a8cbb32ba6ffafd4795f9f39f3cdddf51e68f37fe22463abae7ffce32ffc93dcd6ca1c25faafaf98d32ff0e74641c6efcb7cc1f35fc1f50e66f2ed47676d21c79f1e23bca4d0a0d7506e654ce5eeacb0e420a855e71e6b8788e00973ee578e506add1851822ef1097f8aaba8f9dab0e220dcf7e3a04912e4507b91c8426c9ff8ef0fe11def88dd0e4c9a2ea98f18f01b4a149522d8c919f2908a056d255cf96efab577e9becf22afd6df5042f64109665af1c5fbc5a6bfc56f0f47a5cc3dabcfee7eb6111808c37d60a7106343b672afade2afa5ad5edf1ac6f0dc10ecad4f76a05eaf7564747d24f7311046051d977acd110232e7e07782fc832378c7346277ce02ea3390b7b0c2f7df7fcbc7285a26ac3fb1bc4b4f717f795358a03658d34e37f97b286c532697db82d248babdf2ae47af34652dda22f7c3dd2589d540e0af47d6fd32858138ebdee64fcc4bbfa7e3b761bb8e00c7ff0edd79b4c9348a1eeb700e3d6ba2227a2668bfde6cb0b0fbc68e78d7670874483ec9e10da7f31e9d5a3fcf0ff040000ffffe235a619", + "maxFeePerGas": "0xa1b3a1e3d", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x6f3a5", + "r": "0x4b62001217ccafbcacc8af2a7d1eeb82f226b848f66ea64ad349978513419e19", + "s": "0x53f9c9fb17df1be018722f0ecf31fcd80bc1821954df454671c2eeb4b6b125e", + "to": "0x5e4e65926ba27467555eb562121fac00d24e9dd2", + "transactionIndex": "0xe8", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xf60be42d83a6c8452734d4b25e42f6823814283e", + "gas": "0x3df7a", + "gasPrice": "0x5430dec21", + "hash": "0xbaa21a3154e7853016e585b30dc7824e3c679d507ea5679b6dfd7bcdd9c36a7f", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b05700000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f3000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000008277f442ebb13117a80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000f60be42d83a6c8452734d4b25e42f6823814283e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000672fd90c122416e9b061d48443c39b8d1d72675900000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x571e04672", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x178", + "r": "0xfde7fc7f144109cd9c14e7cf20ed729eb4101d9738d3e1e1fcf2a34a6ed55ab0", + "s": "0x34a8edc58941bdd7f6a0db8aed087344c3342d2440cd5d202caa5260e55d1942", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0xe9", + "type": "0x2", + "v": "0x0", + "value": "0x16345785d8a0000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x8c8d7c46219d9205f056f28fee5950ad564d7465", + "gas": "0xfde8", + "gasPrice": "0x5430dec21", + "hash": "0xc4253a24fe97ac1c0ff0eaff78ce80bc5b4f1052d9fd48266b547f702b404cd2", + "input": "0x23b872dd000000000000000000000000326b8a8d17c3b4aaaa924e53452e893e1d44e7440000000000000000000000008c8d7c46219d9205f056f28fee5950ad564d74650000000000000000000000000000000000000000000000000000000008baa650", + "maxFeePerGas": "0x90547950d", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x2f194", + "r": "0xbb3ac7ede7ff9f4afe06f67f543c8cf6e2a6ab76b6b875f6fccb66f4e7fc5fcc", + "s": "0x163acfc10986bc528634a648c67c7cd682e7f5a6cedf2a4cca85812b434a43ea", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "transactionIndex": "0xea", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xa15207145c4d7b338f4887808ae56c997e415388", + "gas": "0x5208", + "gasPrice": "0x5430dec21", + "hash": "0xdd0eea34565c6bb88e9a7e6d128e54daef12a300a4ae012124a9503731118472", + "input": "0x", + "maxFeePerGas": "0x571e04672", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0xe2", + "r": "0x97557adee89ae3df79c6accb1d83b179a8803638b31b1fb78f017b40f410d0df", + "s": "0x6dff0670d315edb7834e1d1d9be55fa4e92158dc06266c015a4219e458430061", + "to": "0x037502f5affc785f1b2d1e7ae91bba6451d406e1", + "transactionIndex": "0xeb", + "type": "0x2", + "v": "0x1", + "value": "0xea8a4aa495453" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xd7b22609754498a49303be6a0a1444acff6afbc3", + "gas": "0x5208", + "gasPrice": "0x5430dec21", + "hash": "0x0c2fcd1fc143279d6205ca259bcf9c9c075c78e601c8d7e0b74b37b9023bc40b", + "input": "0x", + "maxFeePerGas": "0x571e04672", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x61", + "r": "0x97392e2e018c1111aa6bafa7dc2dee206a929cde460d0b8f02b5d13c672d50bb", + "s": "0x472559ad5b28c46be0cf67d7809334eca88ab4364c56448630dbca0fc5056b83", + "to": "0xcb5efad06d7ca61393b203cf7dd28e8d2495ce84", + "transactionIndex": "0xec", + "type": "0x2", + "v": "0x1", + "value": "0x2386f26fc10000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe1a791875d140f400bce22b271dfc19d83cad6cd", + "gas": "0x38dc9", + "gasPrice": "0x5430dec21", + "hash": "0x530154420712dc8c0b38d30c13c767111ca25a1253d99cba65e8cba08fe726f7", + "input": "0xe3b910b40000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000011dfebaa498651c000000000000000000000000000000000000000000000000000000000001dfb80000000000000000000000000000000000000000000000000000000000000041122d5be2b8f627d2259dac1dde84e2cf93c395f66fa3dfcf3a17980192a1e6467774192547874e68c30df11774be6c64673827f4efac8c244df8175cbcf522d71b00000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x571e04672", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x4f", + "r": "0xaa8a135305b13abc352042d34cb0cb28f50fe41ffe899999bcfadb8303eab03a", + "s": "0x28439644fb03a35bc7b4ed30a2fbb383cd37134fefe5fc03938f73fd8d5c3a4c", + "to": "0xf83a789d591964f491895fab433f137aed742f50", + "transactionIndex": "0xed", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x6abff23081f39cf02b9a3e5a28de52c5b2686035", + "gas": "0x10e2b", + "gasPrice": "0x5430dec21", + "hash": "0xbb2efb717511439c14599cddc1736b13aab6165212af21604273bd1260ffffe9", + "input": "0xa9059cbb00000000000000000000000021c96ce3f54fdc1fd9846a07d615d61040236b0e0000000000000000000000000000000000000000000000000000002da282a800", + "maxFeePerGas": "0x571e04672", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x27", + "r": "0x58348dc0d9f9a8d4fc80c2174567667ee89577a14daad8f9cf6e1dff7a42c323", + "s": "0x5e202baf842b98d11d778312adb871c649f49c3165cb66c7c17018d0785189e6", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "transactionIndex": "0xee", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xe6c5d10a8c87c3ecd22a17b129a6ec1eff5c98fa", + "gas": "0x39e8e", + "gasPrice": "0x5430dec21", + "hash": "0x3926c5d8676dc7d1a721ace3a95fc7401473cd8a6100a95734b0b5e3b830c5fd", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b06300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f300000000000000000000000000000000000000000000000000ce0eb154f90000000000000000000000000000000000000000000000000000000120a1bc7661090000000000000000000000000000000000000000000000000000000000000080000000000000000000000000e6c5d10a8c87c3ecd22a17b129a6ec1eff5c98fa0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000b8ac2383cdf35f77b9bf2605f1378e84ebf6452d00000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x571e04672", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x8b", + "r": "0xc71717795098f177c7ab2577f55c2d3dac54225a7f187bfa82d9d48e44d2d46a", + "s": "0x17736a8f739b2b56de41133eb6211c0c24dc00a31520518bc37caa1ca5b5b679", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0xef", + "type": "0x2", + "v": "0x1", + "value": "0xce0eb154f90000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xc12a275adfeaf8fe44bd0d2fe6dec0bb811e2215", + "gas": "0x3e462", + "gasPrice": "0x5430dec21", + "hash": "0x2f315f76a76c74e66ae176aff0cb6ec83624a82850d0574a12ab4f7cbf206b83", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b07b00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4472b43f3000000000000000000000000000000000000000000000000009c51c4521e0000000000000000000000000000000000000000000000000000000013a229bf62e10000000000000000000000000000000000000000000000000000000000000080000000000000000000000000c12a275adfeaf8fe44bd0d2fe6dec0bb811e22150000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000080632faaccc8d7a8b6d1fc626490b0f4e380302000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x5a60471d5", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x70", + "r": "0x7c475861b4ad1465b005f099dad2e2ffdfeb88b85666c2151fc30975a711320f", + "s": "0x187e60863355dd6b0d1064cfe0119918fb8ddbb0cc1c05f126b76b199a1941ab", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0xf0", + "type": "0x2", + "v": "0x0", + "value": "0x9c51c4521e0000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xcc9ec80acb6ca0190a89c7825e14317eb5cb43d9", + "gas": "0x8caf", + "gasPrice": "0x5430dec21", + "hash": "0xdac17554007e8345174e6f56386db4fcbad45743cef9e3ca247b2ddfb40cbd39", + "input": "0x2e1a7d4d00000000000000000000000000000000000000000000000000310706e1e68000", + "maxFeePerGas": "0x5a60471d5", + "maxPriorityFeePerGas": "0x14c5508b", + "nonce": "0x6bf", + "r": "0x32280066bf0d246eac31ad4099544745519c92c874c01ccdaee29d18a99c9bd9", + "s": "0xdd73f9330ea96f95383af76b163ca0e2300339864d71b62777beec361034b72", + "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "transactionIndex": "0xf1", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x2c3f38462d1c3f794d5367fe24d0465789332a95", + "gas": "0x10f99", + "gasPrice": "0x5415b6b96", + "hash": "0xbd7185af285d0e49c1fcf23910b56a3824f4067eb8c75abaa7d429503c3e5e7a", + "input": "0xa9059cbb0000000000000000000000002ab996653984f03640127915d05d34e41fd093610000000000000000000000000000000000000000000000000000000002ebae40", + "maxFeePerGas": "0x5b6b483b4", + "maxPriorityFeePerGas": "0x1312d000", + "nonce": "0x202", + "r": "0x215217d5d9b2dc577dba1fd938527919369418b7f74be62ffa265ca1b2d19c5", + "s": "0x3d9e2f55aeb15b6d87ad3e48992a523e16037bca19a53ed2b67b80a9eaa1d616", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "transactionIndex": "0xf2", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x0da90deaa61e8c2b6ab82023e72bedc0be82c4f1", + "gas": "0x28c71", + "gasPrice": "0x53ed530d9", + "hash": "0x17e00e5a726c3d91ddf165b5d534e01b7017472c4f057fa3f7d5da2d6eccd331", + "input": "0xfb0f3ee10000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023f4161d2080000000000000000000000000006cab2859480cecd3875a8c0435bf571b18a51040000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c00000000000000000000000000ab45f7b8973a1b71366ea909c70b9ff707d78b7d00000000000000000000000000000000000000000000000000000000000001e1000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000063b4a8bd0000000000000000000000000000000000000000000000000000000063b5fa340000000000000000000000000000000000000000000000000000000000000000360c6ebe00000000000000000000000000000000000000006e5e3562fb6de2cc0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000fa1c6d5030000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000002263e8a16d0000000000000000000000000002a49859f72d1de53e6dc9883691c67761d5e7ce9000000000000000000000000000000000000000000000000000000000000004075ca7bea20f9d837e6fa936234697f0162e94e1b2cc368398185a98988b3fa920f2c938e8283316ca0ffcc702f15a548b5d03a6f83cf080e05d6d574c6a2edaa360c6ebe", + "maxFeePerGas": "0x53ed530d9", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x21b", + "r": "0x79525434d5d4960535646a596843b4dc99f44605487ce03584a665e329997a55", + "s": "0x1a011ea8c84299769107784a97c72e0ff70070f9cad6df00a657c94ddf6ab296", + "to": "0x00000000006c3852cbef3e08e8df289169ede581", + "transactionIndex": "0xf3", + "type": "0x2", + "v": "0x1", + "value": "0x27147114878000" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xb099a3c3043b262518d9b6cd4d5c9f589b15e2ad", + "gas": "0x1039f", + "gasPrice": "0x53ed530d9", + "hash": "0x9079eb3c4a49d5fbf4a9aa33ceefdbb6054e0d2cfa284583813d2f71c50d88a8", + "input": "0x42842e0e000000000000000000000000b099a3c3043b262518d9b6cd4d5c9f589b15e2ad000000000000000000000000be31e9f1778efdca94f531f22f09cd75f64f60ea0000000000000000000000000000000000000000000000000000000000000cc1360c6ebe", + "maxFeePerGas": "0x53ed530d9", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x5ac", + "r": "0xfe0ccee2cbccfb4a0ae816553a96a3da457d816cd285a86d58c933da5f81b4be", + "s": "0x20b60037482a23a682ce32dcb1858499488e7bc3b40821f9d049bd246220ed2d", + "to": "0x9048de699869385756939a7bb0a22b6d6cb63a83", + "transactionIndex": "0xf4", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x13554a6bcb8e6647f8f8f1abd59f88d5ca9840f0", + "gas": "0x3d63c", + "gasPrice": "0x53ed530d9", + "hash": "0x2820f186e43603b114435a509fbf9a854cce2080e79a2d75191b0390208bb576", + "input": "0xa44bbb15000000000000000000000000000000000000000000000000000000000000002000000000000000000000000013554a6bcb8e6647f8f8f1abd59f88d5ca9840f0000000000000000000000000000000000000000000000000000000000000a4b1000000000000000000000000000000000000000000000000000000000a19c5e500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x53ed530d9", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x77", + "r": "0xb4fed1f873870a631fb25ac5b7fcf2f807c86b42d4ba36550d450fb60dda107b", + "s": "0x151230319afb88c5dd30886ecf913ac6f59cf8994cd81a2fb50a1d547da11a48", + "to": "0xc30141b657f4216252dc59af2e7cdb9d8792e1b0", + "transactionIndex": "0xf5", + "type": "0x2", + "v": "0x1", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xdf205931e9df00f8a2b53ca2d7b31427bc0adaf5", + "gas": "0xb42f", + "gasPrice": "0x53ed530d9", + "hash": "0x04333fe8de51b5872f495925cffce95e8202985bc713cad2c70054aca22c8d9b", + "input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001", + "maxFeePerGas": "0x53ed530d9", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x3", + "r": "0x3f5d03d3f7b7b662df144d715c1cc4dd676ee6814646f4a77434a2d0919e609e", + "s": "0x283aefd8fe9ddb1442f4dfd5975d39d2c5c062e91c11056ec5647c34958975a6", + "to": "0x3d0830aa84dae5bb64ea091a943dfbeb0719ec52", + "transactionIndex": "0xf6", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xbffcc846748eb23ce0cff6c79cb416399bf776b2", + "gas": "0x10e3d", + "gasPrice": "0x53d1ac100", + "hash": "0xdb58e0cbcab27febd10f8dd9d7505185292034c19e683ccce41398e3e60414a6", + "input": "0xa9059cbb00000000000000000000000075e89d5979e4f6fba9f97c104c2f0afb3f1dcb8800000000000000000000000000000000000000000000000000000000a11fe89f", + "nonce": "0x0", + "r": "0x85f5ebdc1fce7d932bebc6295f6bf9cfcd02220c63bc9c96d924905b656ea804", + "s": "0x7a8f07925a465cf5854bf13358b9acc0f802207f5bdea310b68438d211c1464", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "transactionIndex": "0xf7", + "type": "0x0", + "v": "0x26", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x0c4ade9ac2255b61ee4b5fb40bf596faff4d3fcd", + "gas": "0x1080c", + "gasPrice": "0x53a345d96", + "hash": "0x61f7d9a0161d5769a16da7d3a96b3ef411dbea79b034029ad26df4147f517e0f", + "input": "0xf242432a0000000000000000000000000c4ade9ac2255b61ee4b5fb40bf596faff4d3fcd000000000000000000000000374670a2f40043987fd31a89f3d4acf9b7d4ae7f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000360c6ebe", + "maxFeePerGas": "0x6622683a5", + "maxPriorityFeePerGas": "0xbebc200", + "nonce": "0xbd", + "r": "0x9142d472382b8daee25fc0c4f4e12539766ca144d1eebeef0520d7c02515380d", + "s": "0x670812c970452096ebcc70c48f93bc3da4be075ed81c68f6261b9676e104b81f", + "to": "0x8887ce34f6f1a4de4e8eb2a9195eeb261c413365", + "transactionIndex": "0xf8", + "type": "0x2", + "v": "0x0", + "value": "0x0" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0x8f6173d171dfd23be773b7f206a18033b8a49225", + "gas": "0x5a5ea", + "gasPrice": "0x52f6859f9", + "hash": "0x72958422d45528129e7534d4c2194a75d53425d24badce4aabe000679893a8d0", + "input": "0x5ae401dc0000000000000000000000000000000000000000000000000000000063b4b06f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000e442712a67000000000000000000000000000000000000000000000047a18dbf223e8d3600000000000000000000000000000000000000000000000000035f1858a27de95600000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f6173d171dfd23be773b7f206a18033b8a492250000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007b32e70e8d73ac87c1b342e063528b2930b15ceb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000412210e8a00000000000000000000000000000000000000000000000000000000", + "maxFeePerGas": "0x52f6859f9", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonce": "0x1e", + "r": "0x9eb195ceee6014122312edaed6189ee6d24066e3eccc9e843c943abdf10a2bbb", + "s": "0xf0147c9a20f19089c2523cc2baff3b50abc59b91d65edbaf0f7a622fba20868", + "to": "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", + "transactionIndex": "0xf9", + "type": "0x2", + "v": "0x1", + "value": "0x35f1858a27de956" + }, + { + "accessList": [], + "blockHash": "0xeaa53f3fbfe912c45af96f4a1a34e3cb1de8e9ac1b6fe8d8b1c9eadad976eda9", + "blockNumber": "0xf929e6", + "chainId": "0x1", + "from": "0xdafea492d9c6733ae3d56b7ed1adb60692c98bc5", + "gas": "0x523f", + "gasPrice": "0x52e489b96", + "hash": "0xb0355c49a963443ab85fe4012ba6e457885693d4f8821efa3dd2eaac1f9e15d7", + "input": "0x", + "maxFeePerGas": "0x52e489b96", + "maxPriorityFeePerGas": "0x0", + "nonce": "0x27775", + "r": "0xdb2b231a64d02f491e1273011e7caf62c9e65f68ca66eb99f73a7d7e8808cae6", + "s": "0x7a24558a37c10a6f5d838536ea3e26b221dd0d135a5a5b427174a2b603c17c12", + "to": "0xebec795c9c8bbd61ffc14a6662944748f299cacf", + "transactionIndex": "0xfa", + "type": "0x2", + "v": "0x0", + "value": "0xc92537df4b8adf" + } + ], + "transactionsRoot": "0xc6afe3338e2336c3287f84522389c8f212a5b01c2990af7d1efa863bba0f336d", + "uncles": [] +} diff --git a/axiom-eth/scripts/input_gen/default_storage_pf.json b/axiom-eth/scripts/input_gen/default_storage_pf.json new file mode 100644 index 00000000..e4f30636 --- /dev/null +++ b/axiom-eth/scripts/input_gen/default_storage_pf.json @@ -0,0 +1,30 @@ +{ + "accountProof": [ + "0xf90211a060e19ca247b669cf9a0f165959e3037d80da9f2fee9eefe48c99ab0ce811417fa00ebef623ae4ac23e2b6fbd8f956736ede09bf1468b1b8a3e40b13afa39ba76d6a07f4af2f952469e13743c7996ab627055354e540b461e150d291ca56aee1988d9a0d3db84705d023dbb0c0cae8547ea55ab61ae623f5cfa31ce7ba832444816cbdda0de227f0bf6a4e70e319e397b740b0c101f4bfe9eb63f27dc3f1611a5bc5b7c20a02aa977a93e679dc53ea05aef306126a90fa6e2c59192a0c0444c324ad65a96c8a051e1e43c8bca6d25bff214af73e35ff07d9b8b50d60a066dbe0780ac43ceff0da0f1075d00e02ad2557c5c8c537a8ca63b21c4e50a30cbaa479c772bfe12ce5be1a08e4ba1141b491bcdfd436f4cc511a6e54bb524e0b7826ca359f9dfbd1fd563e0a0b1b2251e5e06a61b1302449e908713b5d6b7b8b28891f68413c0bb06b23f2283a0053e02fafd96ef7ac744d356f92807a1866988ae6c29007f875c5ecc142d76eba0e02aa50b6a60b80cb49142c7bbdd03d6577a563ac23653fbc9a14c52e4a1c6a6a00e64da12afd83529c4b06e6b9d161a38a75610100b2dcf4e11d3ed4be0e848dea029e446e1d604751cf3e2ec22d9451649cc692839c7c4e968c247462b75e94341a0ab273ffef80e47759170e72d19152f7b20ed496874cf8fc0307a2ead1ccbfae9a0f5dfa81519d6c9629840e3871bcc4a5b50c418987e0fd3c103741ae00273c1be80", + "0xf90211a0e49565e64b53b58771150e938af657ad12819be289077edc9fd11c9f3c82b0c4a0900ecfcf12ad2a5d84c55e6df81f71916b4d380e3bbdc1e9a0045d7acac25851a0b6533662691b2959b6d5b8d9a45d48c75273e5fc9149b672f31be2c91273ffe0a066d4625e1118ab7b934ee556c2d00a479b7893f135e65912bbd2b292220e2e20a08806cf74b4b0d3db71668514ba99c7b3082640e3e7f2b68313aa453bcabddddca0e3220ab5255b17857f1e4638e335938e5f22c13f4fa3fa9fa7b16b08082f4909a0b1743d6be7e674f022ed2dbbd0ec6508aa169cbdb24d565158f7c352d5111ae8a04ebf0a3b8deeae6f3aa7211d721eae8aaf4893d9136efbb54c349c61d9f98d36a0620f23e7d097d24d4a0e8a6e07232f3f09b2a4aea7c5d043a371140ff08b9d78a0e3aeedc29175cdf3a1ab5c60cae9e81fa0553cd2b3ebe83a94633a308b906687a069631754c5a1e4067f13bbdf0e754d2a8e3fd57fba914dcfbda4d8038e920484a023bbeab920d4b2713c24130d92586c0eaa7d845491242e3bbf92197b8bb79175a0aa4dc5f839dcb200697652b29f4b52e6c9817d8e034ffb3e46497f8461b37e9ba0973d5e50ebf533886e853e1c893e68ef369e3597141e78f67034c1372c22a6b5a0485f9e2cf81151b7b4f44c5df9f63929e4789c0e2fca3b92dc1497123653a165a045d9ca4c5fa108ebc5a38e67357c43f7a359cbd4da6e3d886552ce2b2cd21e2f80", + "0xf90211a01cd9a934f943e7a1c22cc83b6a6345de557269a9010407bdfd77822de5ff5bdca03ff868e450c7cfc3ddb469e4a90a0367ca05189f56bd5cccda2f1ca68c56ec3ba038cebf2f5cfa70f5c628fa8aa9b2bf787c1fef3bf3a7695da85f7737aae4d9fda088f64b159d52fcccceb492c0a9e020c40b9c1d088ac4a1758d7fe0a798154fc4a0e7d1522405a0e078ddaf0201e8ef2f99a46722873bcf19c97db9a43a371f6811a027e9b8acc1cbd81377fe4a0fee8699297cdb8a3cc824a9270736138c22e16dfba04c75a21271aaf90db632bbd5158606c6e36fb14447bd74b422be3daa41cfb219a0b192c539b58afdca537dacfb6a736b2df76b2c6aa712c0c8c8f985aae2e5a4fda06b00e7eb71d559967983f965feede4ac7ea0b85ab351f93762c164ad3fcd54c9a0c1d310a07d7fa602e171b3c68dae679130043335c8404309d03f2cbbc36dedcba0ea5f92fd7809927e61e7eba1b602a3439b1576885239d5f325bc5c05f36d029ca007ae5e8dc5a0832f8a36fb5a1ce62ddcc87f190d728a9b6a2f4477a13ce07b7da04944a3af24300af4073eebc780f21737fb17d5e0b47f68957426f05e4f06debca0dd64869f02d8eb7265d9599397ae653ab649c83569ae941abfd3f9620c1ec77ba0092dc1775177d0190d194d84336d09ca0dab9128f0b0505ded9fcad5c3b06e4ca0f970cef932942bb54b21f692b557d559cdda964d878f9e908bf021e4d1e3ad9c80", + "0xf90211a0e441914ab4c2f95c69f40064f1431e7923ea711704b2791e2451a051879ae8aea0970462075405319bbae14759d221354b1ab84917ce2f6bf372bf2665283c7ecda0fc9d1a9dacaafe7b3b0463c5b2912bb6c324a03afaeffdd0409ea581e9ea58a2a0a9b3316c10718793afd6645d72a33b83dd345f733632543234c478e84de524b9a070aaed48cfab83a9ff159cf9ecd19f672e9fb3a6548e2a9f385b89d2d4cce220a0f3bf95ff67bb602d7b682a390ee979f98ba9f30e0cc46a77a4d096c580e765b0a0496ce23250c9d14cf85067f750c57fe1c72b06e6c854c7262c820d09a4d50158a024f901ec8f68471c9dd54e9c5b91fe8ae9e7488b1b252dc2abfbb3e7f1abfc8fa0b7383acc35dd51331bc5cdecf47d9c35c6d6c5660dab80b91f646d02c058320ba0dba73ab666641ff1513d1f46ec46ce5cd6edf6cbecd836b25825c14c040a08a1a013edbb15b1e4f6a496a3b55d3d3ec1e09a33c3ef340ef1773734a10d5b63d2b6a0c7cf0e41d22a4631634fbc12eb6514456166f2d2085318d09596d9d35ce89fc0a04231abfdf6312161a21562ec37b17e302827234b4e7488431c8fa666b563e6e3a002da5404d0a867bab82a426e77fc0d00012f94bf9ee9dc2fdc5312cba31d9349a0ddadc6579a8b1ae828b229c82d0b7993ef5b50f6c4c826b99dda4c4c0a6e5d39a07332a9c4790f8c849cc1556932e0e555bc23133de49639b846365af5671eae9680", + "0xf90211a0a3e93dafa27095db13bafbdf4c714fc89b2fb45d5716ee5694c8296ed090c096a0da99a1217fc5aa21888eb2f845a0598690f6ac8d9cc1d48c294f8a83e75b8eaca01630f3af331668ebffe0a0a93899b94bf8161cba96050c111985aacfb062c727a0ecfd28061ab2f63e98dedf70ebd3ca104c09f5693da53fe06e01ad6d4e404ca1a0969d7cb533fac5d3c40b3ac7ccb31ed2f11edaab6df4c1fb101ca20f5c7b756da00065275af4349b8296593480173b24de236ec8944e5ff6b4c63682e4eed00a91a0a3ec50593073d617e7ae4cdb22192197c34577857fb532684127bf3b7774702ea0e339fc34ed27c2070926b6fe029c51630fb99381e21813b998933aa6575a4322a0fa9160dec06cfc92b1b153364b3e55ea525607574eab19beff8fe1cc5f080241a0dcc1c29cc19a10987ddc4ba2644cc9ca73b64d8c7cd25df5959765035bb07e8da03362cfe25bb8ea8fbcda1687cbff36550f9762ce2d3ddc217114f43d73169af1a017a3e64263dcd5a4b5143963d87eec7c717d19b4e47aeb4b91c18039c62d502fa08e828e8179f4713d681859e0bb3cc6ae2de23815cc540f0cf6e49636c5717656a0b2b37445339eae0cc6e4723e9b29aab07b85599bcc59f536c8288d7fee89c29ea05b6f8f7424de64476b2da9f16d8d0d534daddf6e7caefcc0fd86acf4565f6d09a04c182072acfba90c9f69cbc939b9de5bc4271c5947a5202485626ae352d8886e80", + "0xf90211a0270f956b324726c987eaa3cded56b9bef8aaf385458daab7acdd70bfd203ee28a031fdda7c3b21ef9eab72b3e64b7fd3bec6b79a200697f7f4b5156a478e7833fba08dccb69de56beabb4084d50f35fc9cd45f7127ca254b529b13c625838ee15a33a0fad147b7a08030aa316015d13d0cc8a3dbadd3c107dfa7853b5dfffac9a5a738a0e1523f7ed99d4de8166ef34bbbf14403751241963378a4bcbe5b4a1aeae0462ea090b012cd85d1fe14241a1f58c8b02715f6b2d7835e9e5b61422ae05124034536a0113d75e61d8f40e30c4ef41974d81a99978a257af9f187086cd3e768ab20b2caa0a3ca2c0ba374ac77bb2e852dafc5169bfddacca5955a7dc43e8cb6d1d1ea9fbaa0f32d3b24f5495371dfbe423e74889e1d08028517dba2a8bab415cdb09a70ce07a0e5e71a4b10aca494fefe50259e497ba4d64d65f719c170bc9dca2a8178e9e8a3a03795fbe7c7fb69733db3e70e04ac1a297181214f0fca67c15480b7adf95cf52ba0e647bb44f3f3150774d478476b7977f7c2e1c2484746c46263ff74e7c5a9078aa07d7d448016779bd028116dea371aebe74036ed8356bcc07ce457a5b7bdd737dfa0100b0edbd15981e7f97fb293dee6a2fa18df5b9399caf20535158f5009d3c7f1a0a13439769cf90bcd6cd9d311e20f2a87ec616354314e3e1b4b2ff083ff4fc5dca0468329217298d1ffa8e41eab4678d02c7f589eca3c935f0dd191bdf44646947080", + "0xf90111a00e3484808a216730502daa7ab011fdcc6eb6df9dfcf65a97a170223b71fb0ac080a085a500be60f07ff9d75dcda7c1a9149a15219218188153b9b0227fa4bc523d75a062174165b4c2793eac2d9de515dbe3c7d33fee8514682cf5725b0ffe36dfdf6e80808080a0228d682f2312e51f484d2a3c3e53e987536e5a08afdc5bdd91345eff0c6c07f48080a0a478b8506e6cffc9dddb38a334a15a112d2ed155c2eb02438a4a329821ddf0dea0d37bd3a8c6197e501c6259a6bb50da18900c253f06eabec3b4c17633214c450da076eca457a853dfbfca74edb837bbd53865c0ba85734cece65f06242a6d6fe25c80a03aefcdb44bcd162364bafeddae1b7f8e2c7575e1c1b43032056650e17aab470480", + "0xf8669d373d3b2842e26222b7332b138c965d6e98a2f70add6324f24cc764fa8eb846f8440180a057a2a1260d6af839abe3d4dbf62c48ca62e828f80fdbd9ff251ffeffa3480afba0a633dd6234bb961a983a1a1e6d22088dfbbd623dede31449808b7b1eda575b7e" + ], + "address": "0x01d5b501c1fc0121e1411970fb79c322737025c2", + "balance": "0x0", + "codeHash": "0xa633dd6234bb961a983a1a1e6d22088dfbbd623dede31449808b7b1eda575b7e", + "nonce": "0x1", + "storageHash": "0x57a2a1260d6af839abe3d4dbf62c48ca62e828f80fdbd9ff251ffeffa3480afb", + "storageProof": [ + { + "key": "000000000000000000000000000000000000000000000000000000000000000", + "proof": [ + "0xf90211a06f30d8c7edb012ee1d11ed6eb219bbface0502beaa944aae0a96a068af07bea9a0ae91d8009e5e5bd1e8d1f092fb7d6b189776dfc8412603ab753023f10419f186a027970d6387470ee9883be5a7a967fcc83c0355c40cc176385023e9cb8658ab7ba06198612b0f178dd9729bf7b7ac160ac205d09b52cc0a8b972d8ed220e2fcffb6a01bf8c5394cfb68c4aa05f3279086dedc12410ab0f3afdda1dc9c339c3c53120aa03018ad314bf4d4771623caecf7cf6b742dea9315d4adc154b383ca34b02fe06ea0f7c8d64cfa7a3d737f214fad36a61ee503bf1081b408a3013ee14203daa27e5fa0f1e680bb2905978fd4cd33385f5e1276ae4ebb6f893d1e07b64e710f70f0fd03a05ff84043dc694c7b969c54dfa48c4d386ceb89844ce6599fa846b4a37b03e67da08628764106004398d8f9fe9229fd6e18a343f80f77e76d26e5482cadec7e92f7a0e86b65d214264e29b7997750dec182ff51649f6b41a74f567537cee0633996a7a0052670ee68ac8db29917ecd73f4fc65ae8a328c010c0696ccab147a0b214ed86a0aae1cd43111371ca8b7b584138c42da2271aea2288c6a3f632d9b6b00ca8d362a0b1b5ef6384834b7de3dcbf8e8a9f62092d9f3642409c11fac6be27387e231a21a05205938a12aba0709a661d32c7601c5ac1d8333d8e09db7fd428416fb79ff7d9a028f184496f858f948e70a6720f3c20607438a51e34a794eaa114ae452147643880", + "0xf90211a0da2ee74a3433e41a54c436059349a94eb287112ce581a4f90759a7f767f788c2a04974d6368ef4a3b032c6671a953fea9677f7c23fc115f03a929fa342d7119c88a095de670f470354bd22347c25ce58354e4c69d05fbd3b733a0816476193ac1287a018a3969149a6234c77646ee2e1e1bbc5f6fb03842253126011bb454ed5d639eda0de76d5d4745d56e3a9351bf19475659cd469f28e50db33caf6e081130b0b22eaa0fd20e59cc159ed27855fc985bb7122bd903eb8ce429311797182643bfe6364ffa0cfce5661364928c82c210321d31466ce49b305e5ed14eb7fa288d8ee22f92272a066cecf29a3ae48be07db81f097da7475fa88a2d4bff630264b008af48be588efa026d643832d8462852c2213a7e83fe51efcee7c8d2e33dc2f5fc38abaadbac6b6a0a9befedee744073da2635c3df305db94cc2fc376aed80c680c554b8e3fbcacffa0d1ebd3e912a6909b7fc67a03d921e58ff4041c43dd3b557f90ff8093aaf1143aa0b97838e162c6549d8c692da1d390ca104b1b975ab4a40a7b234b98138d686bd5a0cd6565cba64a3e757caa6eb99b6b3a0278434d91c8e8f537887735cc4b65256da023b0f617c8cfe4e60625b8f068ce0abed0eedb7b15ba51864317ca0fec82fd56a0a3ab05a01528af79ddf460580ae5458075196cc130fd54bcf3d8dc9961704459a04a6393c1ac8d1ef04f66e9aebb4bc52bae8b0466fd3fb84474addabd9fe81df580", + "0xf90211a0a814c5553331aed76ae1e1d44e734f45ecccb358cc75975673d5c4f3045ead7ca0bb3240067e3e2573687aed40eda66563c22fac0ea37c71eb142ba876119e7696a0e95f6413239fdb7439cc5305f432962227079c9fb042a3a2b2c3572fa14bddb5a0628431ebf93127386fd629c8db6efdd2c3c412b8e8e8d0ba1ab4614a8467897ba010f3a72ca2f926c1538b121ece249b11ec4dbbf61e9c552e92de6fc6ac1a40aaa05a0579e074e28913b6d620135c1e56a53e855711aa9af51103b8ce4ff23606a5a088d0f9bb6f1556abac1e34bbff0f0683824da3938901cbcf175467bcce20022ca0dbc1eb943d1cdefa27410814c4f389538bbd8fe77d6ebb0d02b836347574a6dda09b1e220456cca9d3233b6d160ddddeaab717b7aaa1d6505c5e5cad4bd62c5e96a08a546093d3c31a0ded3d533363c8d370875ffe5869c41ea0c6ea44cb57f6630ea02df600fe90d101f3f2ac2c45cbdac0638f94334c924424371330f7cadbbd7887a05db73ec8a8f80527aaec0351810a4497fb4d31ef5b78a9ec19969d5baa19cfb8a056305f8e780799bd27af3c4f38de9e54bf48728bb66ec203c33e1613e343bc3da0968275164418ee568032c4bc47f9946a32783ecfb2fa66f7e452b8bbf19fdcc6a0221a3ea9467880798a8dd3939fac790388659d9541f6fbeae05ca05f974cc6b6a0447202a32f7d3fe8008cb158d467585f81d1e60a5e7db5b139dc6d05753c2e6980", + "0xf90131a09f58330763510d6a725ab407f6fdffe95a8d03fd5c16370b349af1722d0056a2a0e65079755b35e2a5bf1367f656a90c31da5066dbf7abdb416eb42377fe3aefc980a0351e6032190c302db7d81e4d942d0708b7fb5cddb5df1add2b848ba3e42aa3ce80a01b8c7d26d12fac92ac3de7f3145ff3b49ca751a635947513d462f3c2dad70b5880a027e7628067a5b1199746cf96fd895732ffaa09d9b78844926c65f78697eb40b98080a0944dcec9b1631af70321ac68a7950d5090592e13f19a98d594008a0a51d6e74f8080a0639455d8f01dd40909241e6df698eebe602efb79ae9607b61ff76a722e8392d0a085f210523dc633c8e15504f97353995b46238e50c1f1d71ea638cd14da1698dea0d48823a7920ed2c5e67524b5f5e999575b2d31a2546e75b83be791114e0d494c80", + "0xf69f20ecd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639594f0e3b9aada6d89ddeb34aab7e9cd1744cf90d82f" + ], + "value": "0xf0e3b9aada6d89ddeb34aab7e9cd1744cf90d82f" + } + ] +} diff --git a/axiom-eth/scripts/input_gen/empty_storage_pf.json b/axiom-eth/scripts/input_gen/empty_storage_pf.json new file mode 100644 index 00000000..9979ea73 --- /dev/null +++ b/axiom-eth/scripts/input_gen/empty_storage_pf.json @@ -0,0 +1,25 @@ +{ + "accountProof": [ + "0xf90211a09e1a6ffa921727604af012d8ddff1e61292bb8a07cf843985648b6d6745486bea039267463db8cf4788611e0e6dc8f4a15959700fd7ab7480b4cd799a65e4a5ca9a0a6ba7d2e833fa39ef74f1c515ea5f0bc7dd7333465e5f3ee10f82c1643344049a08394c3d6b2b4063c0eb77e6b048164b23bcb5356101b3375c76217250df239dda06c315c1a9e9372e446cec3c1923cfca25bc319024c3fd068077ded7c57964d09a0d4f915dc90ea3380ed2d07985e7a4e3ee665db8d7ac3a70eee8d6b4577731951a03b21ca8e7f3c50f0db54b585c200c09c89f46d716a08bd8735394c3aa3b207eda0bf4b24566ce7b81847dfff7dd8af046c175f2eb2f5cae4d865748f71302e9bf0a0a61820108cde1a33c5e993a6e15ee8292605cb5efdae0d183cbbfc54bf3bb293a0913bc1da86b9333cf074ff3fa343d763e82f3d3c8d8ce81e6def8906141b3e51a0103719aa9e6070f622c27234e2fb70525e76804faa77b936900b811149205465a0acd9199e81382b974c2c4c4afee675ee6c1dbc6ab399a183a740cc6268db8aeaa095bbd96e08c238ee4950c243f7d355e919518caf9d6ca7634ef66335e1c1be03a0f91aa5ba1cf0f645c54f7f509ee055e112984be29d3d758d11802126fcf53041a02c553f8a9b836530292923bfc27ceb0f42d767e71832aee7705bab3c747a5f80a0ca3d1604084618dcb770ff87df1d59f833d2c6adf057fa29e425258692b5e02f80", + "0xf90211a003394cfb9e1c194c8fc031f6c55da758f838d793bce4b2de0579c06e0d6df1e4a0606ba857bdea832abe43b60b1a3e24d215378e126945e0878a36d27b98da891ca099430b112456210ffbd73f21f8f76134ad0d923c1ff988e14e98e7076458070aa09225637bae7fb4f3faa847370ed0f40eda1ed67e6af7ec94388f87cf018c266ca08302446d22127db44a4c72624b8659e689c403f75980375e0ed39d9634dee97ea013e18a6c9ad2cf18fb51be5baa08bb80b9f5ad4bc9f0dd0972cee11d0874d72aa043560749e3c67dde381f49557ebf31ae7228a1e2fb28ec91532329809d77db9aa0f6fbcbe1ff4c9321ef2dd9c01ec3dbb5328a1c2bc75d018b972e2b13569a5838a042656b44401b37437b217c763d9c3efa0d00066d6975b569eb0ac5be7b178325a08e87750e6ab64af8f2291dfded32dc1cfc6af781f6a4b3585f792676b6582d23a02630b3e3e42d1f87c10c85235bfdb593dc5b5b1918e56525d051ef2b3d682e6ca07bfd50cbce109d71b40fa5cba7d251cce183051afe8b40a80539505ead3cfffda07fec64e3385d48567ab423d0f94b13496129cbdb104bf237dccafc6a328cd4c1a06f9e547e4b0902b550667f20d54cef2c9aa15491d202dcb88df06275c59e0051a00fb66d35b5f71a4503182b85df82249dcc6672471fb2cc224d39b37b9380cef9a0177ce4305cd3e1441093756e84457deaf50a143ebfceed8269185eff8b43383780", + "0xf90211a091e5862ea58db1745f55296af00831cbebd152a0c34fe67583cff1266458647da0975e9f0c07e93b5077078cdb3dd918d7b0abf1ae197e45aed54b5bcabac7a26da00c33771f0de09aaebdf8a138eaf023273f39b9bd71d4422be1852b5508e5af5ca06b57931a4f97032ccc17e73e58227a3ebb4eccc6195223ea716fe8eb016dd0eba0940cca2fe8f35424ccdd4cc54e20e0e7116d4158e735da8a8031746f7106aca8a0563638a07a3274c0ae9cb490dbee3c1967fed1e3873b17da105a27447bab58cfa06c9c1e68380e91bce6637224b43d714ae588a8bdd1bc53f6d5997ac3184114c4a0f76b54d70f433a5adfdf14028fc1f77120645d31cc8829251263a6b5f8c084aca01cc4565d1490a949e0d969577087e5f64b7f6f8740818093e42787d2132808f0a0d02c85c89f6b8804c5b05dd48743f8fe429ff1d78b184a051daba280b7535467a0f663e59322f10c6f874f9a87843ec36395188beb64beaa25ae763b811763112ca09baf2ff7670c5339fb2034f1dea434d34d4fd96369a1a90116eaabacce287e80a0b9b19b8a821b23a2544d5b59b5dcd2164cb9aedac32580241ccda07cc74048cda0a916ee6224bff693ac28a2f3b7c28a82893c6f6d9ed6ae40537ce50ad00f921da08346078a3e3c05cc8e0b5d19d306cf825526a5703ebea610fe7845845e27e4eca0b1a71853390a2f482ca843fcc39102608d62d6e02643780315dc24e5c8a37fc680", + "0xf90211a022533babedbe51e141ef2071500091d2fb2db200f8315f73bfa09cca7062bad0a0a92524dbbc5e5882e791b9fa5a6298a88955367f0eb8a8ae7960051ea528cb88a0cc40cd30364c9b7c791a1f653d312807e90b6451f559d6ed4e7845247a29e77ba05089ed245e3d1a2b51bb131cf30ecdc3bc22b05b6d415ba329e763acfd1bf7fea0839751437155cae550f2fac889d8c12124150626d92c960f648a635d7d2871bfa02ab4b61e86ceea35efb62aa4145ad0032e8de270b7374032b7b6a2c430ecf666a07e9c665062b64eba84b82cc0212cf71f6db673df3e0b4603c8976963211f4903a0a6efc3e7d9e3c77781e0a58e3dc15bcae6280e498207a428b50477fcef533526a09fe3c62fcf625b80d85cd5b2ab1a9f9873730c421e6eddecc9a8f2941cff1881a0318a7c31c6c11c893fe68ee5fe238abb1b5b8184328dd362113c2a17ef33c236a00ac64f0a7f1234cb01a44660ca25c704d2c95b8c23659ee4ff8311aac05e2d36a0ae914832630f8c1acbb746b6b1dbd5e2124362c70f48188f35b4f1c15c2b1cfaa0821b42665926905e9356b80fe1557a541167c8b66fc13ef3988201656504ddeaa0f7b757866ed33463a5726b2f5434ccbf9c27dbc4fa14b3711405b975996ac7cba059ff74c6fffed5e934fcefd98a01b6179486fdff6bf4b438b61262e3c1e04fb6a08331d4eb99dac92e6b1406d6fc1e1195372f9e3eca5cf01457762f6a59fd05a780", + "0xf90211a0e569b9913ce619ccd100bb4f4f6e0e27508c2255ee086af7f5d3fe9949a35af6a064e6a9708a47c608e2d2ea66c43101a02ceb86b1030d19da8ba08773d6b9d162a087bdf3a6b076843f2399422414177a6bd98ca43d8dfbd84bb485807f862ebc70a0432f0d4660a7079ee31cbb276de54d2b44ef7761acbcd87f1bacc2472bde6b69a025008896b6264734ae47cf1a337db5831d9dbaf4db865442e5ca86dd50ef75e2a088fca5b09967a58c67e54f996cb60f755dd50f56ef9e30f170318c418405b274a072bf772936d2869a2201d435c12b4f5126a67e6b02b1fa82170aa7b59a0fef0ea08f42fdf6cb500b44b4827ed1b9f793649730ba5e2092dea30acce4b1cda1360ba0dcc56188e766ad31dc496f120acd6eac62a0cb75d96c902ccae9c03a85ac55b0a06baf5e378e51e08d70e36147d4afa0932c6593d6f796d3cb1b770b6e45d5deeaa0d125697914c5fd3aafdb463db8c44ab4301be1dc94a8490fc79f78afbbe465b8a01b2353b73d0b53935e0c6b66f76ca510437876550a4b5a2f9b1e70881d54a804a0d539d6b28740955b2e51b0e72e9c845730a4b4af53c0c5e68ca25703e4fb516aa0f60efed6d4924d93860b04f850b1d468a10bc11250bd59920e87b2e104e58a9fa027323f5428333054913094fc99ec1ddee68011ed8486282803b54b9d33ebe882a0b717930686065acfcfa26d26ffaa7292c00c7d95544fd9e33aa3135ffdfc7a9280", + "0xf90211a0452083769a54fad677e929dd77fab33135c4afd4be6b4e0544515824c1ed5ef9a02b627a98969342594a740f368d1d2c04774f743ad4840c1e7cc2fa293670b745a0ce3230152fc6b3a9f0904c34090cbac3742e29494dd5e7a087fd9d7ed23278d8a0c109454e027e6ad62261268ca60a8393aed870a0a3f717d2c6f3cecc94adb85aa0cc916def1b999af566a0a83bd5c645365fe5563097e048c6b5c1fa1b22a2bf7da0366673465256afd697828dee6dd61c51efd1bf5dcf256f12f25a40c59bcd8ed3a05417eb53efc7f807949562a014818939b810d397a28a4b3b5418af8b18be969ea00dc079a1386f7f622973502b2a143334483d1ea8fedce6033c1856690fdda0cfa002d9dcb6ff89b82909bcb2b930842ea48ce142f96e7cbc83fc398dc0acd8f2c2a0fbdc374f6e6dbd2e1e095472153bd643047cdd1d013ac263f43fb516417eacada0e6301d02d858da65a69824787e79317cdb220c0be911884186b841e1fca14f26a0e9fa5809fa0f0ff6d198f82f0c61fc0dbca1b1f2d790eeffc9c769dbdf74365da0746ddce6d4653ab890c7f1f974cb892285c3413a6560e4d8d1c35a8241a5ed29a0ef2314361e2b81eb496104960174a8cf9da04e2a6818b258623a52a426a30131a07e4b3e3287706c3ea576e2352c15b747824ab02e79d7f356efa99cff3d164250a0c39983684fc51b19ea9543ebec03def9fd68b3099f1f6382bd58c78cca5fc48c80", + "0xf90151a08a1672ebb05aac6b892d2b57a8c23b39324073871abaad6816fdc2f1ef560fd580808080a05ba6a0e56884ee3c77e559582852cc9f7ed0424763e933a9ce16ad92a54cdd6e80a0d5fb0a3096322f5d1a8ded61f781b24249e3696e3da4d500f7fc864fb5d8e505a0f15f3ba96a351bf987fa5fa3b19060e9b8c6ab4f66253889a38f1ef8ffaae67ba030df0fc6401936032434e2322e991a5c84c04db3718d556944727d0fa6cc3e7da0f2641fc664af7f7c48df51c5af237b036e742615dbedb948f88903b3f1e9f1a280a0362fc8b7b56aa11e5dd3aa8c73ff04fb8c9dcfd2c4afe9adf835d644a5dd3be3a02d0823f444d656496f0298e1683a327c5aaf4356a72b45a4aa4af8ea9c8f4191a03e691bdb9b12a1f2cfb56b69d36df9d060e0aa1c880e691320e745e8beb83833a0972b828871ad39acb5d36be111bc1ed630f522eb5c34fca4bf33c1f2c326ea6f80", + "0xf8518080a0b9adb40116cae4b932b3a669200795bb505f27ea16b99a62ae862384da3803d980a04240542417f20dfcd2c8c95b5f95a6262b2bd2f99cffde2247ccd73322620303808080808080808080808080", + "0xf8709d20e8d9174f6325cb58d1667ea6512ebd61aacc0be4d9dc9ff4eea7603cb850f84e8210c28827bd73dc2bb0f8a5a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + ], + "address": "0xd5df13dc9a72319ddfbd226fece1e438aa920d55", + "balance": "0x27bd73dc2bb0f8a5", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "nonce": "0x10c2", + "storageHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "storageProof": [ + { + "key": "0", + "proof": [], + "value": "0x0" + } + ] +} diff --git a/axiom-eth/scripts/input_gen/headers b/axiom-eth/scripts/input_gen/headers new file mode 120000 index 00000000..19e36941 --- /dev/null +++ b/axiom-eth/scripts/input_gen/headers @@ -0,0 +1 @@ +../../data/headers \ No newline at end of file diff --git a/axiom-eth/scripts/input_gen/noninclusion_branch_pf.json b/axiom-eth/scripts/input_gen/noninclusion_branch_pf.json new file mode 100644 index 00000000..d3c7be6e --- /dev/null +++ b/axiom-eth/scripts/input_gen/noninclusion_branch_pf.json @@ -0,0 +1,29 @@ +{ + "accountProof": [ + "0xf90211a060e19ca247b669cf9a0f165959e3037d80da9f2fee9eefe48c99ab0ce811417fa00ebef623ae4ac23e2b6fbd8f956736ede09bf1468b1b8a3e40b13afa39ba76d6a07f4af2f952469e13743c7996ab627055354e540b461e150d291ca56aee1988d9a0d3db84705d023dbb0c0cae8547ea55ab61ae623f5cfa31ce7ba832444816cbdda0de227f0bf6a4e70e319e397b740b0c101f4bfe9eb63f27dc3f1611a5bc5b7c20a02aa977a93e679dc53ea05aef306126a90fa6e2c59192a0c0444c324ad65a96c8a051e1e43c8bca6d25bff214af73e35ff07d9b8b50d60a066dbe0780ac43ceff0da0f1075d00e02ad2557c5c8c537a8ca63b21c4e50a30cbaa479c772bfe12ce5be1a08e4ba1141b491bcdfd436f4cc511a6e54bb524e0b7826ca359f9dfbd1fd563e0a0b1b2251e5e06a61b1302449e908713b5d6b7b8b28891f68413c0bb06b23f2283a0053e02fafd96ef7ac744d356f92807a1866988ae6c29007f875c5ecc142d76eba0e02aa50b6a60b80cb49142c7bbdd03d6577a563ac23653fbc9a14c52e4a1c6a6a00e64da12afd83529c4b06e6b9d161a38a75610100b2dcf4e11d3ed4be0e848dea029e446e1d604751cf3e2ec22d9451649cc692839c7c4e968c247462b75e94341a0ab273ffef80e47759170e72d19152f7b20ed496874cf8fc0307a2ead1ccbfae9a0f5dfa81519d6c9629840e3871bcc4a5b50c418987e0fd3c103741ae00273c1be80", + "0xf90211a0e49565e64b53b58771150e938af657ad12819be289077edc9fd11c9f3c82b0c4a0900ecfcf12ad2a5d84c55e6df81f71916b4d380e3bbdc1e9a0045d7acac25851a0b6533662691b2959b6d5b8d9a45d48c75273e5fc9149b672f31be2c91273ffe0a066d4625e1118ab7b934ee556c2d00a479b7893f135e65912bbd2b292220e2e20a08806cf74b4b0d3db71668514ba99c7b3082640e3e7f2b68313aa453bcabddddca0e3220ab5255b17857f1e4638e335938e5f22c13f4fa3fa9fa7b16b08082f4909a0b1743d6be7e674f022ed2dbbd0ec6508aa169cbdb24d565158f7c352d5111ae8a04ebf0a3b8deeae6f3aa7211d721eae8aaf4893d9136efbb54c349c61d9f98d36a0620f23e7d097d24d4a0e8a6e07232f3f09b2a4aea7c5d043a371140ff08b9d78a0e3aeedc29175cdf3a1ab5c60cae9e81fa0553cd2b3ebe83a94633a308b906687a069631754c5a1e4067f13bbdf0e754d2a8e3fd57fba914dcfbda4d8038e920484a023bbeab920d4b2713c24130d92586c0eaa7d845491242e3bbf92197b8bb79175a0aa4dc5f839dcb200697652b29f4b52e6c9817d8e034ffb3e46497f8461b37e9ba0973d5e50ebf533886e853e1c893e68ef369e3597141e78f67034c1372c22a6b5a0485f9e2cf81151b7b4f44c5df9f63929e4789c0e2fca3b92dc1497123653a165a045d9ca4c5fa108ebc5a38e67357c43f7a359cbd4da6e3d886552ce2b2cd21e2f80", + "0xf90211a01cd9a934f943e7a1c22cc83b6a6345de557269a9010407bdfd77822de5ff5bdca03ff868e450c7cfc3ddb469e4a90a0367ca05189f56bd5cccda2f1ca68c56ec3ba038cebf2f5cfa70f5c628fa8aa9b2bf787c1fef3bf3a7695da85f7737aae4d9fda088f64b159d52fcccceb492c0a9e020c40b9c1d088ac4a1758d7fe0a798154fc4a0e7d1522405a0e078ddaf0201e8ef2f99a46722873bcf19c97db9a43a371f6811a027e9b8acc1cbd81377fe4a0fee8699297cdb8a3cc824a9270736138c22e16dfba04c75a21271aaf90db632bbd5158606c6e36fb14447bd74b422be3daa41cfb219a0b192c539b58afdca537dacfb6a736b2df76b2c6aa712c0c8c8f985aae2e5a4fda06b00e7eb71d559967983f965feede4ac7ea0b85ab351f93762c164ad3fcd54c9a0c1d310a07d7fa602e171b3c68dae679130043335c8404309d03f2cbbc36dedcba0ea5f92fd7809927e61e7eba1b602a3439b1576885239d5f325bc5c05f36d029ca007ae5e8dc5a0832f8a36fb5a1ce62ddcc87f190d728a9b6a2f4477a13ce07b7da04944a3af24300af4073eebc780f21737fb17d5e0b47f68957426f05e4f06debca0dd64869f02d8eb7265d9599397ae653ab649c83569ae941abfd3f9620c1ec77ba0092dc1775177d0190d194d84336d09ca0dab9128f0b0505ded9fcad5c3b06e4ca0f970cef932942bb54b21f692b557d559cdda964d878f9e908bf021e4d1e3ad9c80", + "0xf90211a0e441914ab4c2f95c69f40064f1431e7923ea711704b2791e2451a051879ae8aea0970462075405319bbae14759d221354b1ab84917ce2f6bf372bf2665283c7ecda0fc9d1a9dacaafe7b3b0463c5b2912bb6c324a03afaeffdd0409ea581e9ea58a2a0a9b3316c10718793afd6645d72a33b83dd345f733632543234c478e84de524b9a070aaed48cfab83a9ff159cf9ecd19f672e9fb3a6548e2a9f385b89d2d4cce220a0f3bf95ff67bb602d7b682a390ee979f98ba9f30e0cc46a77a4d096c580e765b0a0496ce23250c9d14cf85067f750c57fe1c72b06e6c854c7262c820d09a4d50158a024f901ec8f68471c9dd54e9c5b91fe8ae9e7488b1b252dc2abfbb3e7f1abfc8fa0b7383acc35dd51331bc5cdecf47d9c35c6d6c5660dab80b91f646d02c058320ba0dba73ab666641ff1513d1f46ec46ce5cd6edf6cbecd836b25825c14c040a08a1a013edbb15b1e4f6a496a3b55d3d3ec1e09a33c3ef340ef1773734a10d5b63d2b6a0c7cf0e41d22a4631634fbc12eb6514456166f2d2085318d09596d9d35ce89fc0a04231abfdf6312161a21562ec37b17e302827234b4e7488431c8fa666b563e6e3a002da5404d0a867bab82a426e77fc0d00012f94bf9ee9dc2fdc5312cba31d9349a0ddadc6579a8b1ae828b229c82d0b7993ef5b50f6c4c826b99dda4c4c0a6e5d39a07332a9c4790f8c849cc1556932e0e555bc23133de49639b846365af5671eae9680", + "0xf90211a0a3e93dafa27095db13bafbdf4c714fc89b2fb45d5716ee5694c8296ed090c096a0da99a1217fc5aa21888eb2f845a0598690f6ac8d9cc1d48c294f8a83e75b8eaca01630f3af331668ebffe0a0a93899b94bf8161cba96050c111985aacfb062c727a0ecfd28061ab2f63e98dedf70ebd3ca104c09f5693da53fe06e01ad6d4e404ca1a0969d7cb533fac5d3c40b3ac7ccb31ed2f11edaab6df4c1fb101ca20f5c7b756da00065275af4349b8296593480173b24de236ec8944e5ff6b4c63682e4eed00a91a0a3ec50593073d617e7ae4cdb22192197c34577857fb532684127bf3b7774702ea0e339fc34ed27c2070926b6fe029c51630fb99381e21813b998933aa6575a4322a0fa9160dec06cfc92b1b153364b3e55ea525607574eab19beff8fe1cc5f080241a0dcc1c29cc19a10987ddc4ba2644cc9ca73b64d8c7cd25df5959765035bb07e8da03362cfe25bb8ea8fbcda1687cbff36550f9762ce2d3ddc217114f43d73169af1a017a3e64263dcd5a4b5143963d87eec7c717d19b4e47aeb4b91c18039c62d502fa08e828e8179f4713d681859e0bb3cc6ae2de23815cc540f0cf6e49636c5717656a0b2b37445339eae0cc6e4723e9b29aab07b85599bcc59f536c8288d7fee89c29ea05b6f8f7424de64476b2da9f16d8d0d534daddf6e7caefcc0fd86acf4565f6d09a04c182072acfba90c9f69cbc939b9de5bc4271c5947a5202485626ae352d8886e80", + "0xf90211a0270f956b324726c987eaa3cded56b9bef8aaf385458daab7acdd70bfd203ee28a031fdda7c3b21ef9eab72b3e64b7fd3bec6b79a200697f7f4b5156a478e7833fba08dccb69de56beabb4084d50f35fc9cd45f7127ca254b529b13c625838ee15a33a0fad147b7a08030aa316015d13d0cc8a3dbadd3c107dfa7853b5dfffac9a5a738a0e1523f7ed99d4de8166ef34bbbf14403751241963378a4bcbe5b4a1aeae0462ea090b012cd85d1fe14241a1f58c8b02715f6b2d7835e9e5b61422ae05124034536a0113d75e61d8f40e30c4ef41974d81a99978a257af9f187086cd3e768ab20b2caa0a3ca2c0ba374ac77bb2e852dafc5169bfddacca5955a7dc43e8cb6d1d1ea9fbaa0f32d3b24f5495371dfbe423e74889e1d08028517dba2a8bab415cdb09a70ce07a0e5e71a4b10aca494fefe50259e497ba4d64d65f719c170bc9dca2a8178e9e8a3a03795fbe7c7fb69733db3e70e04ac1a297181214f0fca67c15480b7adf95cf52ba0e647bb44f3f3150774d478476b7977f7c2e1c2484746c46263ff74e7c5a9078aa07d7d448016779bd028116dea371aebe74036ed8356bcc07ce457a5b7bdd737dfa0100b0edbd15981e7f97fb293dee6a2fa18df5b9399caf20535158f5009d3c7f1a0a13439769cf90bcd6cd9d311e20f2a87ec616354314e3e1b4b2ff083ff4fc5dca0468329217298d1ffa8e41eab4678d02c7f589eca3c935f0dd191bdf44646947080", + "0xf90111a00e3484808a216730502daa7ab011fdcc6eb6df9dfcf65a97a170223b71fb0ac080a085a500be60f07ff9d75dcda7c1a9149a15219218188153b9b0227fa4bc523d75a062174165b4c2793eac2d9de515dbe3c7d33fee8514682cf5725b0ffe36dfdf6e80808080a0228d682f2312e51f484d2a3c3e53e987536e5a08afdc5bdd91345eff0c6c07f48080a0a478b8506e6cffc9dddb38a334a15a112d2ed155c2eb02438a4a329821ddf0dea0d37bd3a8c6197e501c6259a6bb50da18900c253f06eabec3b4c17633214c450da076eca457a853dfbfca74edb837bbd53865c0ba85734cece65f06242a6d6fe25c80a03aefcdb44bcd162364bafeddae1b7f8e2c7575e1c1b43032056650e17aab470480", + "0xf8669d373d3b2842e26222b7332b138c965d6e98a2f70add6324f24cc764fa8eb846f8440180a057a2a1260d6af839abe3d4dbf62c48ca62e828f80fdbd9ff251ffeffa3480afba0a633dd6234bb961a983a1a1e6d22088dfbbd623dede31449808b7b1eda575b7e" + ], + "address": "0x01d5b501c1fc0121e1411970fb79c322737025c2", + "balance": "0x0", + "codeHash": "0xa633dd6234bb961a983a1a1e6d22088dfbbd623dede31449808b7b1eda575b7e", + "nonce": "0x1", + "storageHash": "0x57a2a1260d6af839abe3d4dbf62c48ca62e828f80fdbd9ff251ffeffa3480afb", + "storageProof": [ + { + "key": "000000000000000000000000000000000000000000000000000000000000002", + "proof": [ + "0xf90211a06f30d8c7edb012ee1d11ed6eb219bbface0502beaa944aae0a96a068af07bea9a0ae91d8009e5e5bd1e8d1f092fb7d6b189776dfc8412603ab753023f10419f186a027970d6387470ee9883be5a7a967fcc83c0355c40cc176385023e9cb8658ab7ba06198612b0f178dd9729bf7b7ac160ac205d09b52cc0a8b972d8ed220e2fcffb6a01bf8c5394cfb68c4aa05f3279086dedc12410ab0f3afdda1dc9c339c3c53120aa03018ad314bf4d4771623caecf7cf6b742dea9315d4adc154b383ca34b02fe06ea0f7c8d64cfa7a3d737f214fad36a61ee503bf1081b408a3013ee14203daa27e5fa0f1e680bb2905978fd4cd33385f5e1276ae4ebb6f893d1e07b64e710f70f0fd03a05ff84043dc694c7b969c54dfa48c4d386ceb89844ce6599fa846b4a37b03e67da08628764106004398d8f9fe9229fd6e18a343f80f77e76d26e5482cadec7e92f7a0e86b65d214264e29b7997750dec182ff51649f6b41a74f567537cee0633996a7a0052670ee68ac8db29917ecd73f4fc65ae8a328c010c0696ccab147a0b214ed86a0aae1cd43111371ca8b7b584138c42da2271aea2288c6a3f632d9b6b00ca8d362a0b1b5ef6384834b7de3dcbf8e8a9f62092d9f3642409c11fac6be27387e231a21a05205938a12aba0709a661d32c7601c5ac1d8333d8e09db7fd428416fb79ff7d9a028f184496f858f948e70a6720f3c20607438a51e34a794eaa114ae452147643880", + "0xf90211a0d9161edb870a153effb69af29b1d077995d51ee02cd83b0c903b847744aef99ba0298563396927b4a9f85ad4b776ff6177a6232fea407a1a849594850b02791b55a07ac6503052ee5b21af5f0ba0cbff06acedc996b6a85e8d8b66b9dea302147c7ba0c88c0d3b63388307ea00825435c0254b79e1596edbfe8033ec84aa6c1e580d77a0e0bf1e80d87786fc40130d5aff63f8cd1511f3dd8a99460383ffce6192a64aa2a0ffbf142caae161668825126fcce99a9e0e3431a7972fa0dd4cea759b78e07bf4a034b107b91bbdc2429e2b5bb50ebf6e9f7dede61d19f1198eb438a80cda0211b6a08890dff5554fbb81a73e3435bbc70d7f0bb560fb9c754f9ab9f39e6642b177d9a06c9e34f2cea4d2a73b814ea4a50fc6a182cd6805aaf32c296c6f5dbd61ff026ba03ce4230ae22b99625337c1c495d2202953a6108e7485f6d17079dd76a0a6d831a0e2784d1683f1a8c154fbfdee1e342b5dde7d6cb82e636a91822025623340383da0494252f3c099370327ab10212247a428b1515b8cacd305a10cec11ac6bfca82aa05e246c244fc5f05b7f378ee0d9000252251f6b1b442c1f6fa02493a873465a9fa0c5d90256293f792b36455963c323dfbb64daf943be1525968dc8f16cde09265aa0bf80e246df15a01461e414a62c4744f6b6514fde197e8f9ea53ccbea6700c933a049b3fab65e4dd47663091532f81972a9b81ce25024d8f695235f0f085b8e5f4b80", + "0xf90211a0abe1f4e4b15b44e1becab67192f462ff12c2f8c60234f01901c3837ccc5cf51da0599e1cbcd3f60b0d968eb76e2d4c1aff9aaa1747d270da2b90506a805364a3bca0f2dcccffc88225b216f648c7045fe77a5594ac411ac0c11d1ecbfb3deb6d2ec9a01f5e312ea7bbc3caaf9414382f348e5745a992a1b6aab1547385709a2b53d170a0a7b31dfb222a8b01da4de45652d06754919f983b75d97bcb4b06b0ef154bb441a0fa803afcd584156838eb6e2625695be1f99ff47632163bb0e7ac7cbffdcc7efda0e8391b2ad9fc3aac95e47d81572c78db02d60827a85d004b52b1cdbc8310f56aa0cfb50c78f77940cb68a20144af8ec41c58b8494033591bf8c28b98dfbd15eb11a0e2e84e94979c9820a993e70a2beb67861799da713ce6bdbc8304e79f0b5efba1a0418ea5dcfd54d4095f0b6b692c52f4486c92da468337f05b8cd2d4f13cb70eeca0338841c5e8c469d324c00e54d16e5365d56df3d8445feccc76393d97b272af5aa04733a5aadc766ed6b7a481b0d6e400ac3939b3a0d9b23bb765088a3b6b30a279a00957481bd4eab5b4e643a68b87f71441bca01f2e595f4b2e476b1579362e0395a0335a3ed483b7efa468d5a61d273d2523053a3ea2f24a224e81ea54bcad8f2b78a0d1a844a960220e817304b590769f9ff8adc5b63302c0c3c5307a73707cdd37dda06e007047e4d8654475c5d9f96164bd33b329d8a21af5d9b80e7e67ceac2ccb2e80", + "0xf87180808080a01e7f2d908dac7a0c10c06e663320cf6f630f8264e415a5675d737b191bc6b49080a0dbf92b7a948174c0375f1859b1bcdea637bc07f10bc1eaea8c3dd857164c98e080808080808080a07773670dbb7657802229a0b5640e52f64489a424fa5e50b451535519f82907758080" + ], + "value": "0x0" + } + ] +} diff --git a/axiom-eth/scripts/input_gen/noninclusion_extension_pf.json b/axiom-eth/scripts/input_gen/noninclusion_extension_pf.json new file mode 100644 index 00000000..9c22bc55 --- /dev/null +++ b/axiom-eth/scripts/input_gen/noninclusion_extension_pf.json @@ -0,0 +1,30 @@ +{ + "accountProof": [ + "0xf90211a07e7b1087466cfb1a8bfa4139544bcaf3c65d3b81c9e1db25b04bd6145e82bf7ba014a53a6fe1569c7021cdde0d28cbd886cf302777ed13703ca8fa7dd1c3f09a40a00535535b8ebe519a77816fbce759548cf53a85d5abe64da9f1bd5a00985cfea2a0ac1d4cf8ccfe115f09006e572248182a41001751675d94d145009c974f1cf582a0668edbb419a09d1e0e8362afdc781ec30733eb0c1b77bc4fda834429c0ae0501a09152f7aacab7c9c02e21d273a64da2f6a93f98ea15751dc665b1f427fe830af1a08ce1643479bfc1a4f7f75985eef67ddfb94870ce049e98413c22160705308394a0fd73396f0bacfe1c2f37315bdf3e13a1ce1647948f42baac09b36d97c56b2a30a0d9dcd16254d90306ce2ceee1fb430ded03506ffdfb82c535a5b8343fddbe6f35a099117017666a3437e0f3fadad780cad3c4c898afafa84c96c146031987736175a0456b925266e2e9c2d95f2313a50b06d93ddc266111fea6c63d3610503268e598a0906d69f111a09d503b156958e2cb1d672500331ce19593dcdc167efaf49274cba089584c1ed794fc4e8faa1a8e906796278e864937ce2872ea8803ec5055826b62a04bb7fda6d2089d9f3160d3effe67a8d76292d7c2c5cf90898402ef116196f1a5a02d7a03da7f9557e75590b2870a7416ca508fa997d576df85edbb204ed4d6fe82a0119145ee74a216f525462f3c67ab19856c53aaa505e00ebbbcc4a5181fb92f5d80", + "0xf90211a0c782ea051b6f82a0fdfda49e5ac8612a57aa3ac89dbe623228ff4b8b5fb80895a02b4821df5d899cb16e52d4626b20d4f3c12954f007d09e48ef09722dbe952c0ca0dee2bdff86c53920a164647a130904665e5f8015a324756f137a6687c8f4fbe5a0b5b0dbb791a14f00e7183bdc0aa994df158b94ac857dbe309b6a546220c719e3a0484053ab71502ff8609332a51d3a63639c81486215b91bb77f3bfc355e56f56ca0c43b11ce41d4e8b585405d9a62c4bd7fe40a7547477341d4199c16e9725568f5a0eb77efc95ab9086e6fe96657bdb85c7bfce5b906fea951f9c09aee897963e494a020ff758de8ef4e8bcc8d5601b722a7c080b11788d7c513a66e14834e0bcc5922a0204c25188bd92ced5cada8c66b3158f585dc04eb2376eea70a874d3fc3581d42a0c22b9b27b4b5659725f86df0f2eb252c9d9a1f6f41bfec1dcb7d39078ffa6f61a0cca1cada1ed76da767d3dbcbf52c900fc2fc3b4e4e08ec88517bf18ce4cf82f0a0e1aaba22f4285471e3b834914bc5da960abf6a2a930c20cadaedb2c686749f0ca08f2c1bb3c25a2181c4dd26b7d5b1e3259444078a70dbe2c8779e6dcc74b5697da074dd82f848d1a49c36fc4f0549d17265ab7f25344dadce2a2bbdb6e795e34749a01a3ecdfa4f55d8705d08494f96f6f799d35ecf44296bbaf97c7f4d2dc52ff468a0ddb405c1a3f7a8d8ff02149ac33ccbb693388f988b3b34f0812c19e5bee00e3f80", + "0xf90211a0f4e12f7d741cf2074f04a1fb2d7e364b312eaad8ed3472d1dcd8861f1dbf73bfa04aee72a3ade49ffb33a24359e8fa34c499eee4dfadf58b26fb4c9b2d58588e76a0061222150b57a569e5e3526e2bb29a2c55869434d2146fabcdd420d91f43c16ca0d03d6ab678ed1e4a439a4ce457e4bfbcb2a177aeffb5bdd5beed19f7003ad782a0e5f39e5557937ce49845b1d356f6eddc319f0e8f69fd84f2bf93ac5237abfe86a01addc25d8e909396d126806ec3da6b4e5e7bd59e65ec3c396ace7971793a8346a0882e8d9e6b57b51a7b0a8c556fd800bf1566cc4dd98088c272d8ade93c0a000aa0eef805a17be2968b2a63f1546b98abb46181101c5355f0349ea691ea1349f937a04f384b4e2fe6b411b64883b55efe611e9fae84226ffc0c44decbc30a3e8686fba0dd8115ebfe6376afe049db1bba5e73b4939aeb354298b49161eed218e9888d17a0978a6addeffd96793e29aa36b9ed9b80c02f2815b1310fc9c9617d4a45c210f6a0be0e87caad4fc15f91bb228b1926ec6db56c62c849ed8cd3ee2121db7d82ad5ba09ddbdadf6e7b0fe9d48000d435fa29ef85a448b2e31301a555d827676aa41a71a02f406139332ae3b9035de9bd289f1d54dda8fa619571d1f32f634170493b71f8a0b3b452fd0185ac47c905236acd89150a81a82d8bcd60382e6f776d0aa7e48df4a05bf5c911885a923ca46c1235bc2273692446cfee348c5a4cc304a94b004d58b780", + "0xf90211a091e8fb9b61c63616d9348e1d7bdf1ee9336e4ed97449888baa63f84ae69694cba0281142920b1810a18e767dc5abcc11aa86ad4a843607b74f8de80c6cd5e6234ca040f7467e208f1d3fa58df5883d63d21434bd4b2ee25951e06589f01742ec5f8da03a0edb739cc785e68c206416e6597e4ef7b4932f78b559ad86f4216dc0d934f3a014d0219056754bc700eddb5d906ee64bd76fccb74ca544a3039cbd762736e26ea0d8912bd7d0a256f70e72125fa7b72f11bdba2b95e4e278e7a57cf3e4422f203ba0ccacb42794d9ddc9254b213863f158ee4f15b3b7eddcad7c5bb8ecf8c17996fba0683d7db36e7dccae54d0cf371a129d6177df5711f5afe5ca92c3c647ef744e21a000e2a094208d7b14ad2fb11fda28d1994afeea5cab9f89a30c291af42b4a8bd2a0ce952d87c02eec55290f66e78166a4af47ad509f76398e29027d0a8a8ed64819a0613095d78c942e23802c377e915772985c10018afcd5e9ff2d4fd4fb9b35d061a0e2dbdb53579d829aaaa7da8e6f50b7bd0ba25dcae3e0a51af9f0930efc5ce617a0831863b2f21eb1d039919e1bd604adae0c4e0ab296f0b71cb39c153aacae13fda04538bf43e55c2a62a8972aeee27a042727d6e69c6e79d3e2f449a8929e72cce4a00c263331c84e51e5a7aca2decab472d4e4b064d6480807aa8bae145843944255a03912994fe7c2097d6222f243e31d7581e855af07da1b63230bf39357d333951080", + "0xf90211a0c526180429b8eec6eba7de835a7141bed64d6c563e6c30c4ff8bb05f1689b72ea0544fc732c1496d2c14a5fd3f98ce5c57c7ee1b3f3ecc5c0349a146050e6752ffa0b0749562099f6cf894657327a9ef92f8c60236361f36c7a2cf45fd6aae945953a0e612fd7eef9097f0b70f8e29e8cadb1aa99c63cb6dd6e36b3e9e44423effb215a0e6194f1333d42c2b8ea6e1e89eb852b62aeb3f4237602bc0c19cfd8e332b0b64a0ade9d3a4b50bd45959080519092e26876bfe0b49c93ba9840a18a9217714b5cfa08a69ec6f1ac3b8dd8254f4920f245f111b739a9d3fa776a31e2ef4568c5bfd66a0c38f65d074fa4b47c702d43da7eda6b878cc9c61e82c13e6b722d2faa990ec90a045bcdaac83ff155e36a23021b563fe4e3f3b73cc5bf68ea5b5034e5dcb715aa7a00975fae22114b798631f9e19ffb9ec4af56b1db2025cae45f4d669ccef6f56f4a05a34e0e52fb4dce93071dc137c5659b00855e54c0c376cb92f281c32db37c2b9a02c13e6a0651180ba12fcda105af2e919d732fbf57b09f2a4e71763b589fb7539a06a12fdd944ac1641a6ba0187f732a60d37347f142ca546522532a6a7612ac524a0919212944bf6f6ea6f5d4f80000c48e3024e1135728532358a2d9af458e27f2da0d07f429ad43b6835d97950203f8f6a2f73e553e358a243284ae237a645e5d217a02ca2c2c5eb70355a4aed661ce2a26d8e8ddf41fa583202b69151c8043ecf7e5880", + "0xf90211a0fe802cc20f884ad71df355f4e87178de529967845cc71ea50c1e84f15b8d7fc4a022c2ebb12274555b20dbf3860c90fc92b85ca5781479e92cc1984a17ba92958aa0832ffab76e6dd0615a2d8894faad599016c8ce9408e9ea082798df7541f59697a00ee266c73038cbba8e23f92fb584b03a0c2435968c3d5aa1e3a650f63b0a8808a0e474e7851ce650ca7b8a23a7f052f85f081550c0d4753aef638a8382e8e3addaa05e40547e9cf3258e69d7371e7109f131928275d7907750b91b689d838ad20692a03acee740dd3779c048b0a0eaad21d279af81479eac2355bc35086bfcbb294e40a08906f316514866c885e39c6078175940971835f617d40018b329efdabca2ca39a07f05b1c8946224e64c6b0f08538d9a8aaeb8e028c058329723994e92e57fed01a0fa0a92403bcf280b011b69e11f72c81913a84e2cfa0e73505e3ba85b45c6d198a0b9e1da6493acffd539c1fb56a45681ae4b2bcb8f9c77708e6de8bf244e755abba07b547a69e6c8ff3ccb45718f84a706bf1c2f238822977917c6fa58a1db67f7dea07549fa146ce97d39a62845e395d8f45840cea1b7f948deef25ab56e025709cd8a0e1c00f534afba3ecfe1bde3065fc227f5e3a630e621033457239cbd27ee38798a02f20e595c6fa4f443ebf75b38ea4b10ca3666d748931dce34e836fc8b2549faba0f0e5bb157ec4ee62deda7137045891e3fbb23dd22ca5fa8a4e3387ed59b5ea4580", + "0xf8f18080a0b561e85842111223038fd7ef285abf8af348d3f49fdb2a4a6a1976f0a077506980a060e1c6c38ccdc96efaef7dbe161b9e612b4013014e5cc660a0b4cd224024e01f8080a094be361a9ee84da5a699b77e9c999dde4850a31a0dd019130a390613481c36e4a06b9e989cf29f77bc45584c8ad68b1f94c543baee8a1b0f110f528682817b0d9580a092b5bf9282278998908f57a9dd4ff8cd9976eb5362c1f0a7fefddb763c76d5d3a06405ec9caf9e5c8413866d4b68bb46812d2bfdef2c873afc80ab87beb6554de2a03e719b8a8c9fa923e3bfa639c832967256a3532ef23e4a307204510ca8c2cff180808080", + "0xf8709d3f8c7fab57471a2a41387f9b0d0eab229457c5024bd6cfb72dd7bba2feb850f84e018a012df5f56180f1e41a90a0c138c0edb743c4874f25abde4e8e22ef5a24ae96167ef179eaecdb773880588aa0e2e7a7524a98ce629ee406c15c51a683e4167f0b74ea230566ddece7ae9d6f0b" + ], + "address": "0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb", + "balance": "0x12df5f56180f1e41a90", + "codeHash": "0xe2e7a7524a98ce629ee406c15c51a683e4167f0b74ea230566ddece7ae9d6f0b", + "nonce": "0x1", + "storageHash": "0xc138c0edb743c4874f25abde4e8e22ef5a24ae96167ef179eaecdb773880588a", + "storageProof": [ + { + "key": "000000000000000000000000000000000000000000000000000000000000005", + "proof": [ + "0xf90211a09c49ef4d3886df2cf19d4766cc0550a4c6ced0ca1604c50f9774e6d1ac918994a0c0ffb3040badce135bfb0a9576409b0e17fd5e70b5d469e49885ed3dc4f10c95a03c96efbfd1bf3b4eae69fc94923779f127955b680d24b0cd412c5aae1dfefa6ba06b0a882bad84bf68f4a0726315bc743deeb5351c15d319ad7bc24f9a2583c354a063be165783f2fd4e51cc56ca0df474870fe5d9f3c4cfad40380d55145582b9b5a0467022b4c0c840e92124c90e5494d6fa2267fb9bd841d6897ec8e98e2fcad316a01630afbeecc644836aeb862fd1f5dce0fe77421539e5353a2ee42b7aa01cdae1a025d78166548b9a5a14c0be8c9a30dd37a3899bd0e526004e561fc9d6e3d65764a036234e7ee53c02a38a20b1fcfcc579532bc1ab59bb4458769eccb2ee1dc44c13a0c5fba91fd627287a09430d784f0c6b7b47c3bdd7a4df85d1a56a6427c8e8aec4a09f1c3670db55c4902b215b1dcd279b8b73ea99312373eda10b979073042ee9fba03d42cbee9bd9cd3b08de25fe408e7e61ac2376eea72fa97d2faede5697900b3aa04ee2e3b31c13d549d7466d70549a358e933be380b77561a6f97abc04fb5a3c5fa04d7e341718727d64a9aab1cbc672239006e29f7b2b51cafc97ca8401e46aba50a0f5a4b13fb3ee5be77644b37dd163f28d21b42615d1ffb9114d14f2434e0b97ffa0083eeccef9c3244bb3a0f18c6decb4acbfd481dc061d423c9a67e35bd51effd580", + "0xf90211a0894d2c39df92a680d448a7a42d5697485cd68fceb37be572e40ff92a72be1147a09fb196ae567aeedc39c34a65722e84bef2ef2f1f165d1611764871d2a633257ea0e5d88e30c06dec47879cb799f0f91c625ee38911430d01873494ee98da87ef78a08a16153b48e46d49c2f2458195d901e508f50deacdd7420aa667670da62bb2c3a0c48d540a12643b8d0c7032ad473485540452c1ad9ef3040648cf3f14021aab9aa08456e36f8bed1ca96888340f20de7334f3ccb3dc309d961a23a8c9dcf6ad53a7a0c1a923dbef965ddbe146a581d8692a00d59bc8be59822bfc0f8c8d6ab5d275e4a0d158cfba3d5ec62aa14e970b414906874b5d53462851ec2ac2a94af14e40cf7ca0d864922fe0b95d584d6c33f2987b081ce0f30e7e889c44012fbf728596590ee1a0b97b9f1a72bc44724c6777576c0079cd67ad2b8241192bb0928183477c1bb4cca0c51b912a5071bb0a0110339074a58d6f01297d724ff91cb32bd0933954666ff3a08f34f965c13c1b3faba7a787491b4666c96c9ed041dfe63850914dae44c9d77ea01505e3e8c5ef4ca62778ed5005d501eade250e4c1b1fc79800cef934409defb9a0f1848664a726cb0a93c6fd20598995b463e2161cdc604cadd3c6076c7d8e17f5a061e6e085d193c7389fcde7f19b1b6f56a6e8bd246be76919bc0cb353a0e4d804a0c13855412616df07d487e90f5432fcf49c80eba718db49ba6936cf131c41532980", + "0xf90211a06e0fd77166c3a514b8027e42cc0563c3382e81b8e2f179fcfd26fb535c7c3767a051b9699e072b33399da19e9ce0cbfaa6f24f18ca3456804b67b8fc5c13d07979a01ac19df3a9b066a99072e5737edcf6da3a15c3b8d6e2ae611aab0095b0b0a62ea0b299ff456e4c0796ec46f5c07ed2bf0fc59b68ad1b0a49b9e8362f7e3da02380a0e8515851b32845f6dbad962bc0368f709d0578444167d3d18329a16834a99af6a0d46880a2fdbb93b6937696811c9de0d01b6fd007adb7032525964af54eaf2175a0ba9d6350a82abb2408cadd1e4f86fb85ffadbc5799a2c60b9cfa538d089c22a1a03c932d7fab204c01437b2f571b45bcfbc9787402d534ed57899dfa495690c1efa08ea1a1681a3205786514c623d75b74428a596f909239e5e0226a14263ff9e47ba00dd07d09a310f21025fe477c7e4bb3cebfba80589cdef9fc324082d76c29cb18a02a0b7491c54f5c120463ee12d637c661e3a1473366388fee7b2f7a539764a8a9a045ba7397664fcd3822c1b94978c09a702460b7a90c48fbb531e3d11b1404df10a039cce67bbd0cf88d931e1754ff284fa318c99be33fc1661e92d744042ecf7c87a01d81fa1e67ee5058ebbcc96b9291502f7efb29076435d50dd4334c8f7f538e94a0e80f4e81ae92400bde69d1b34702ff67d435c2169eba57dc4d1337f2f8d457cfa06630c8824187446e973471564692a81e9683bf3dc24b167c0898b1347167468680", + "0xf90131a0e5f6ce352b2bd67bb51b668619aecd1fc80e1b670c6cb522ff312824a6d65b0fa0f4e315a2cd4e798e701a3655d98d7505e5fbd27c83bcd1366b54e49c92e3b91780a0a6364e8b93b3bfc0a48a2d9fd60c881a90d830790cb5834a76cda9bed34c50b18080a064f9884de5a3be8ece3ca12bca0b7cdd445582a79d3dc80404ff38f8b43c4628a0f0797c5fcb313f4daaf978f03553062fad52cd94b99e038d257d2a2478ae3d4880a076a8856506e4c04fecfa22883ae67f5f1292818e941b1b701df45942ca0bc38280a067d0869505806a3393a1940833df6c928486cb1fd4feb5aa9bb9e905cf80a8af8080a0735c987cbd2b42c998fb17abd45577783eb95ffb6abde58990cdb45a4a74eaf6a0e7286d952e66b5a3b71bfc9a56821003daec3ed2c78454309c60bce6c2d883ef80", + "0xf69f20d60bcf7eb19222827fdcd2f831264b411083bb70f3751542847af476571e95946e1271abcc021805f5fb2cc1724d312c5c3350af" + ], + "value": "0x0" + } + ] +} diff --git a/axiom-eth/scripts/input_gen/noninclusion_extension_pf2.json b/axiom-eth/scripts/input_gen/noninclusion_extension_pf2.json new file mode 100644 index 00000000..3dc9434c --- /dev/null +++ b/axiom-eth/scripts/input_gen/noninclusion_extension_pf2.json @@ -0,0 +1,28 @@ +{ + "address": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", + "balance": "0x96918416033373f44f12c", + "codeHash": "0x6a5d24750f78441e56fec050dc52fe8e911976485b7472faac7464a176a67caa", + "nonce": "0x3", + "storageHash": "0x8d7250d4dfa9ef030b1151e78dea9336d14bb78f2bce5a9d10e3270d605b11a6", + "accountProof": [ + "0xf90211a06875525241ed00974ff8f285d34cf893d590cb081b06ccc8a58becaafbc61586a01b8224bf9e335e42ee25ac1f580f0e675333440fab002754ff309807c6739f42a07eba4b6c8bb368c032a824c5e198f89c81840c7a7fd3e980eb6198c9733b09a5a0a94bfe5ee1cbcff11436d6bdb7a88664489329d5884c505bf244df1edec218e0a0f72a31f473419d4e8fb3f14cda0e574675d59664d9957f15f2aba9c674016f30a0b75e6cf09853e23027b38c892a913be14f28cb9eccb7ebad419cc0bac97f2c76a02fbe507c717acd450d370d3810459eb6b21b99b772b1ebc6ce2d6fc675489d00a0766bd9b7509e7050b87ffc562c64afc6c410cc2c9c7957607f86366fe2b1bc34a06f9c87d701284a345eafa31af48ad089d299d6db29bc41080454b8b4c7a7f509a0ffd2f17a5ba5b3703565bc7c6814a887f9dae4963a7aeb4097c3417667ec2474a0f4acf51081bec49ce14fe4de9f953bbe88331a82888f20975f1e3d7dc832b9aea087c3e94f2cd280568d7b02542d3a1abd4e1236b725f80b375f3d373dd4fa0052a06096434bc0d93cd92458f013f3ed83d2763936adb7f2534183d3d3d8ace601b4a01bcdc447840bb0250efb7517bd2ffd7da493b7b5859855b746533f18ffb454cba0b92b452de3be6e9dedee3beda9ce094dedb9ed176d4efa1047260031ed1b77a0a079e1cc06c10332ef983eedf3de13cb341e7b1a04f4695cd5ad59b2bc42db569980", + "0xf90211a09ba45dc6079684b768fe08a715666ac68520c62c313b2c7b157d5f0c03db6304a05d481b0002593012fd24d2164b755081287e0317385052a8762c1fb520bf0d23a0eb4e543b156fff69b7de8536bf0c9b540240374bb254db4103ca6d43d2e096eba04ce9ebd41461ab69ad714f78db5c9b83f62bc27d14c42209ad07522e6d7f3a19a021a3c256e39ca1890a9515e2c5ba926c2d8d98d3efa4ef7cd2c733518779a9b8a068c7fe4002c065d3084149cf05a61cf50fdf5737b2517a7ed29132200c8288e4a01d3d30246b8290e8983ecc61dfd4f503336f25a52254935e91367ea18bd4e136a0fd77007483e80764d390c9bdae7269e2060c68bc8f7d58a1b30dff07e83a7c3aa062cb759f5b4a51dafab908375897267c40ae4998095b808b5b4b89d4d0ade4eea0fd7b9deba9bf7a39a910e7eebfc100e96734015b6b0723d425bccef537be6a20a06aba685b3ce9c9a6a8d3886b7dbcae0a0b7e6113889915bd2fb0931cd94d7e38a0cffc3a6628b5ea012f29687a52cefb2aad9c6f17b6ac237e34892eb23e65d98da0ada0992f24ccbdfa0de3f32c4f3926cde0493538911f2ced6174120e774654a7a04d98289fa6b82300868a9ed731d0755a973c549c5ea91fa2036ffad70516c702a0e9acae2f3d411d0f6c6d5c912a77af5743d7b8e43e143b97ce57f60a8c498a0ca0c73576eab13deab8cef0a30d024cfb7362d0deac40e8a5b3ad49bb6d015eef7080", + "0xf90211a0281b05ef5553277eb4d42cf6fb4aea6c72a098fce901c6843923ff16dd32fddea0f7105b967bd8f4e4ee05d26b56a6049ad3f49188e0db01393197d9c1a4f39a4ca02478359fc762b583ea59b3b8f34663817e5feb17f334ea43f3de9de335d39d12a0a6fec39fcc228dd5502f0e19c6950535ee9666c8ceda5910392fd8f776ae05d6a0a4f627369c564ab043d337342f3774d1b2b55e52cc4725feb884e22f92734b42a07e702c82be23b07c4c2f893c6b49ff2f7ba23fb33948b8efb1954bc1cf8a68d0a0cdc4f3f16b43aedf844b281ec87822564a15808b48684e12dae0bb4de1b2c76ba09063615b516ae857873276644ead39c1bf175f33e9d7d8bf0d653e264e46173ea03b9728af6ea0b3eb408e5ea3fc09e0e1f5ab24f5e3a8b359661777875aa2efb0a069b6e0f2a4bceaf2f862cb20603cb823ab8356094121c16dca89c9bacfb80355a02a9d3d9c2fa293c6ce954015f33c16b884d02d88bafda1e8a0b323073742cd65a0c3a40b1295b99f81304424040aa2019300a5ed51cf47943578276553aba5ededa0537baa0251dd9abdb5f6b9c87b2062c5016ed54d81dd413ff2ffe5451e67dd56a01c110675f445f35562112c9e7aadf60f0456395f31dac8f9e523eba5ea65e855a0027c5d9ba14e140cac36dc0261e3e3d1c2da5eddf6d113d766b661abe7be3be5a0abb10ae31008d2d3c69baecd628749a364d931d0e42579776d1714c54daa3e8b80", + "0xf90211a0d976f65468f55c49ee686c24ff2b18f5103d3d209a6db684dd37508696f8b847a0d92ff004e27aba9aba3621bd82f7471429b1b59648a6ea18c4eca765976914d6a02558819a7abb626efcf86110212b685c810705f67307de5c0288ed37009f4925a0ab220639050926bacac78f5a40ffecb92220d74ed5ebaf342937074167c78d5ca0d0e2322b9b5117a9256db21732261ad1cd42933d2838e39209b0725f2f22de7da091c5b7b4e040dc9d82d60286148a904cbc76dcead181b9130b58376d3bc30e93a0a5ec0e3d7ff3ae87fa323fe2fda207501868bab3e4181652d71075deb67e4b42a07a9dbfac3a7774ce342e97a98379919060e837790bcddc5d1d3d897db77f80aea015ce6fd88cad4396f4ce597007be3363a95f8e317a0604e0c789cbbce11c3ac3a09b3266040079422303a42c069ceb94362cfd4fef697ee9eefc86ee20f11ed50da0e0fdbb2e74546da02f137bd6bd8220c09787b82f6e76bc505bc37da246d004a9a0981952f9c255a7d01a316732e2cc32c18d62a1f748d7fd5466ecb72f35a075b7a0ed2ad26e9cbee479798b32fef4bf1413cf485b5d2f57ee69ca94655517e78848a01f287e9ca4e72dbb04088f45fc70664b6a7677cc86f4c135edd0c0d86b4cc323a018edae04c38d66bfb0a6513b9a6d6ecf73e112a4f66890cc4982654591e14ddda01f6bf8f8a7c6ede4045460d6f9b7d55189b664380462cb9ab94bbe6d7f71f61a80", + "0xf89180a07639f948c5a444fc90516106d6cb55402e17598f7fe9aa3ef1118f1293c0775d80a0a902464a86381c46240735ca8f986f5137034514a8b528976ff25f588008e89980808080a0c1fe8e7dee6bc282c2891f54412767a17411dbd8eec5531e3d3d33440db7e1eb80808080a00251bc5b7345f1723de51c7c5f4c52433f1d2ce5ef6f49173f9b041ce6940cb2808080", + "0xf8729e330c9f91acda96a5453d2bd6e46294f79f072835244746ff001b6b20e5b2b851f84f038b096918416033373f44f12ca08d7250d4dfa9ef030b1151e78dea9336d14bb78f2bce5a9d10e3270d605b11a6a06a5d24750f78441e56fec050dc52fe8e911976485b7472faac7464a176a67caa" + ], + "storageProof": [ + { + "key": "d1e5be1e03a9eaadc3b681ff116bae5b35f636dae177083008cf9075f00cfa8d", + "proof": [ + "0xf90211a0f05b0c4d8599338b3421224540ab9c4d9626bc597d5fa7ed2a6071554b47a6dfa06fec4c7cc47051b44b91007482da61c91a2e4ee5c6aba946dc85694cafd9e12da0125f12fcd3e7c3e755e0a813e4442663f1ef4f9f86c12c95d634670041119b64a0c13fe7c559a518551b418b9e8ff778356cd41c2a1f9d35c72a3b79a468181e4da0633ab7b1b2a25503b4cb7b58388be724b544817eae189437d75fd185fdf08633a03df7b7ca249e67d8d89ed34e58ae30a72fe4c4a0d50c19fefd4d72d56401a867a0cbe06a4a38f4b05e3552e6bfcefb14e56bce58c5062530f516a64a1bbbf70a75a062e55a51133f76af32690afcfff97d4772085b6030f31364716465bf2161dd17a0c7b6e1c1bfde75f27be5d3c98271069a7c4b82e78870cf91ab5556a5df16aa59a00b7afda86207aaf250acf7692e9b624149965bf663c00c739dc3bb5ec7902c7aa052e5dfffe1d1a7cf631a34bc723e6af3e2c47ee36e7433635c67061985be8010a040ea6b2cf42b7149eb6d47e634935258f6210944b1d176e4f0510bc0a89844d3a0ffd4ff032808737c37dc710969fd4859fc1dc529cbae90e30932f52fb3df93cba05be6bbb19f6a69d6b7d8a5c73d1f6015f880faaf965be2f4606a5d0ffc78ee92a02bb96d39f5de2916e0d2cdcaed05cb1578bbaacea4361fff1716603226e5e9f1a03fbc494221fbadfc7356bf7044782af5efb668ae7e7c9ea97bec82d2cefb562f80", + "0xf90211a01d2617c9d9cc778a01eedc9763e9f47539c4c7bbdf0967ca198efda6d2537aeda0ac66dbead9bbbd89986ad411b93a9e958eff7e640cfe35dc5f60838692b4ea16a070b0e165d0c45583e2717912ba7e7b79ded65520afdb98af230c4be8397d729ea0d5ca3761921753160848ac1e9a00ce5bdc5d35b8c3caf08ebc2acd4e8ba2c3cba059dccd6fc739aefb37617051d87b397f75e407fd14a26137bb771a17ff9a0bd2a0b7a0212fb2e901ac369023d45a87d9586a6c5ba8d2287f3bb58295913e076e56a02c3aea153c85a68b319478f3e8cf5dfe779a6dd51c6fc03460f1ce61d1c62f01a098b7a22c454cf96a43b313d87bbd85517fb53b3d95983d60791bdbed5dea8225a091544c0c8238fd39b12f19d4b744f5d826fbbafd6bbb9adeb176ff21298c7873a0cb007dd98fb91c7f483660f0302179085323ea2ee78444da3fa140acb63d6c5ba0e72654923c72648e18488a814e269c09cb2db1958b0d3d1e9f22feabb4bf18d5a02e8e004b322376e2b012bbd79f554171bd6db5b44271905409d02ae052c8c475a0c15564cb8ae8d115d58963137cce2fc8169cc0e60a57d7974ac02b7fa08dbd3ea06d10ea979253f8a8f8d14eb3af6346424d537d2d923be989ab7aea562820c967a0bab8515a1a7ce668dc9827f40b126689d9c01a910627470f1090c18636c974c1a0d14fe2c4cb72557941fe1003e4a41794dd81acab9b4d2a71fa4080e54418eae780", + "0xf90211a06b83275f9cf83dd9b1dc71420d1e3c7c58d1f73b97a0fc154ac717fe40663567a098e259a9513192dd29e6b388fbfae6f2a0f95f0dbd9906a19ec479bcdd88e307a08428d1d7af6a3567a730491257d7cf0b5c85473daab46ba277f526401dda498aa087bb09ccc970e185ff75a69099159de0cb6470c11487cc7aea34732d7d39e41da0e1ed27369b2b97ecad8cba15ade8301b07634c47c42077e801ff161a95dc31a7a001907f198c00cc5e26554e2faffa8022642ea38a522bce1cabddf1df5cd1965ca075a6a35c8b056e497f1f722a9b340c4a21c3a9ee39bbb19e94155d902fff2d95a0d54f3a4bba0494b7e0e13a1ec3c8b57f86e55931c97b366a9ad0e6cbe7182c71a0687b1ab830ee6cccf63529ab0b2bd04a88611773a1e56d815ec759ad31bc0cc4a0912f21ca281e7eb18c194271de7858a451bd17833f48122a6fc212542f9d532aa0c151193cb0d1578db9a6eb283fa899f7d80d38f52d9dc25402f0471cd1c80a00a030d84df7230606b70e5086a540bf23a84137bc50abe4ea8920a53145ffcc4491a0f10a5014826e7920cb30b94ac67b312e07a8eea02a397632d01f0ba1915970f2a0400cd101d8464b1228d00d66dc09145f1ab1e372da516e12225b8f0142d70fd2a08d85d1638509f1cb3adf3d858c5bc7936c33f848bfc218350daa8ead0c7ca108a0922603ffefc60101f53b24b6c1e87fc23112e7380b9f7bc66d4f660845e1ad6280", + "0xf9011180a09d7f732a8b8b77e8521023b103c3d0bc49407e0e087b3d2a352eb5f29476c25fa0e74ff0fb8b92e95dc334b60293704e7ab7f6435daaa53c5c529e53f693bac950a02d2485c8a30c2d72a731cfb7cd0e0f9c89d6ea67bd3287e76e387cfd790257b7a01cbc6483b948a8d65dcf8142167aeab689195935ac52e96cffa2e253bc7fd0a18080808080a03b4a4656fe4deb37341501c5e89517c55cd8579132744f00c778fc638ed7f47080a0850a8b2f7cf933f8b8cc7f7caf720adafdd3d6fdf3d79743b7adbd4b47a7154780a0568e11b290ec02d80cee7242426ebe6b050db362d869d0a254cd16afe42dfc6aa0e176348f1651d1660d976f62d4eb8b893ef88749cc8ac7bb271510197529f1aa80", + "0xea9f206cdd33f710b60e97af0ef88584affcef1a8179b6d037941782931180d6e58988c2648418dbf37955" + ], + "value": "0x0" + } + ] +} diff --git a/axiom-eth/scripts/input_gen/query_test.sh b/axiom-eth/scripts/input_gen/query_test.sh new file mode 100755 index 00000000..1246671f --- /dev/null +++ b/axiom-eth/scripts/input_gen/query_test.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +ID_FILE="./INFURA_ID" +INFURA_ID=$(cat "$ID_FILE") + +curl -X POST --header "Content-Type: application/json" --data '{"id":1, "jsonrpc": "2.0", "method":"eth_getBlockByNumber","params": ["0xf929e6", true]}' https://mainnet.infura.io/v3/"$INFURA_ID" | node -r fs -e 'console.log(JSON.stringify(JSON.parse(fs.readFileSync("/dev/stdin", "utf-8"))["result"], null, 4));' > block.json +curl -X POST --header "Content-Type: application/json" --data @query_test_storage.json https://mainnet.infura.io/v3/"$INFURA_ID" | node -r fs -e 'console.log(JSON.stringify(JSON.parse(fs.readFileSync("/dev/stdin", "utf-8"))["result"], null, 4));' > acct_storage_pf.json 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 00000000..891f3b50 --- /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": [ + "0xd5Df13DC9A72319dDfbd226Fece1E438aA920D55", + ["0x0"], + "0x103ec96" + ] +} diff --git a/axiom-eth/scripts/input_gen/serialize_blocks.py b/axiom-eth/scripts/input_gen/serialize_blocks.py new file mode 100644 index 00000000..2703c149 --- /dev/null +++ b/axiom-eth/scripts/input_gen/serialize_blocks.py @@ -0,0 +1,142 @@ +import argparse +import json +import pprint +import copy + +import mpt +import rlp +import sha3 +from web3 import Web3, HTTPProvider +from mpt import MerklePatriciaTrie + +def keccak256(x): + k = sha3.keccak_256() + k.update(bytearray.fromhex(x)) + return k.hexdigest() + +def get_block_rlp(block): + block_list = [ + bytearray(block['parentHash']), + bytearray(block['sha3Uncles']), + bytearray.fromhex(block['miner'][2:]), + bytearray(block['stateRoot']), + bytearray(block['transactionsRoot']), + bytearray(block['receiptsRoot']), + bytearray(block['logsBloom']), + block['difficulty'], + block['number'], + block['gasLimit'], + block['gasUsed'], + block['timestamp'], + bytearray(block['extraData']), + bytearray(block['mixHash']), + bytearray(block['nonce']), + block['baseFeePerGas'] + ] + rlp_block = rlp.encode(block_list).hex() + return rlp_block + +def get_block_rlp_list(block_numbers): + with open('INFURA_ID', 'r') as f: + infura_id = f.read() + infura = Web3(HTTPProvider("https://mainnet.infura.io/v3/{}".format(infura_id))) + blocks = [] + block_hashes = [] + for block_number in block_numbers: + block = infura.eth.get_block(block_number) + block_rlp = get_block_rlp(block) + blocks.append(block_rlp) + if block_number == block_numbers[0]: + block_hashes.append(block['parentHash'].hex()[2:]) + block_hashes.append(block['hash'].hex()[2:]) + return (blocks, block_hashes) + +def concat(a, b): + be = bytearray.fromhex(a) + bytearray.fromhex(b) + return be.hex() + +def hash_tree_root(leaves): + if len(leaves) == 1: + return leaves[0] + depth = len(leaves).bit_length() - 1 + assert(1 << depth == len(leaves)) + hashes = [] + for x in range(1 << (depth-1)): + hash = keccak256(concat(leaves[2 * x], leaves[2*x+1])) + hashes.append(hash) + for d in range(depth - 2, -1, -1): + for x in range(1 << d): + hashes[x] = keccak256(concat(hashes[2*x], hashes[2*x+1])) + return hashes[0] + +def merkle_mountain_range(leaves, max_depth): + num_leaves = len(leaves) + assert(num_leaves <= (1 << max_depth)) + startidx = 0 + hashes = [] + if num_leaves == (1 << max_depth): + hashes.append(hash_tree_root(leaves)) + else: + hashes.append(bytearray([0] * 32).hex()) + for d in range(max_depth - 1, -1, -1): + if (num_leaves >> d) & 1 == 1: + hashes.append(hash_tree_root(leaves[startidx:startidx + (1<> i + proof.append(hashes[side ^ 1]) + for x in range(0, 1 << (depth-i), 2): + hashes[x//2] = keccak256(concat(hashes[x], hashes[x+1])) + return proof, hashes[0] + +def check_merkle_proof(hash, root, proof, side): + curr = copy.deepcopy(hash) + for i in range(len(proof)): + if side & 1 == 0: + curr = keccak256(concat(curr, proof[i])) + else: + curr = keccak256(concat(proof[i], curr)) + side = side >> 1 + assert(curr == root) + +def create_block_proof(lastBlockNumber, side): + with open('INFURA_ID', 'r') as f: + infura_id = f.read() + infura = Web3(HTTPProvider("https://goerli.infura.io/v3/{}".format(infura_id))) + block_hashes = [] + for block_number in range(lastBlockNumber - 1024 + 1, lastBlockNumber + 1): + block = infura.eth.get_block(block_number) + block_hashes.append(block['hash'].hex()[2:]) + proof, root = create_merkle_proof(block_hashes, side) + check_merkle_proof(block_hashes[side], root, proof, side) + print("publicHash: ", '0x' + block_hashes[1023]) + print("blockHash: ", '0x' + block_hashes[side]) + proof = ['0x' + x for x in proof] + print("merkleProof:") + print(proof) + print("side: ", side) + +def main(): + start_block_number = 0xef0018 + max_depth = 3 + num_blocks = 7 + (blocks, block_hashes) = get_block_rlp_list([x for x in range(start_block_number, start_block_number + num_blocks)]) + + mmr = merkle_mountain_range(block_hashes[1:], max_depth) + + end_block_number = start_block_number + num_blocks - 1 + with open('headers/{:06x}_{}.json'.format(end_block_number, num_blocks), 'w') as f: + f.write(json.dumps(blocks)) + with open('headers/{:06x}_{}_instances.json'.format(end_block_number, num_blocks), 'w') as f: + f.write(json.dumps([block_hashes[0], block_hashes[len(block_hashes) - 1], hex(start_block_number), hex(end_block_number), mmr])) + +if __name__ == '__main__': + main() diff --git a/axiom-eth/scripts/input_gen/small_val_storage_pf.json b/axiom-eth/scripts/input_gen/small_val_storage_pf.json new file mode 100644 index 00000000..96a0b7f0 --- /dev/null +++ b/axiom-eth/scripts/input_gen/small_val_storage_pf.json @@ -0,0 +1,25 @@ +{ + "accountProof": [ + ], + "address": "0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb", + "balance": "0x12df5f56180f1e41a90", + "codeHash": "0xe2e7a7524a98ce629ee406c15c51a683e4167f0b74ea230566ddece7ae9d6f0b", + "nonce": "0x1", + "storageHash": "0xc138c0edb743c4874f25abde4e8e22ef5a24ae96167ef179eaecdb773880588a", + "storageProof": [ + { + "key": "015130eac76c1a0c44f4cd1dcd859cd8dfad11d8b97bedfbd5b2574864aec982", + "proof": [ + "0xf90211a061acf60be385db534720c3a90cb12b926a5feb4aa8915b9967efd6cb941a6bd2a047e5cce4b1ccd6d16215eae416486c25124bed878b0de607ce325dda1f1ff8a6a08fff7aa4dd54c636d97a8d4bdb8632607d8ee37b62008230a45fbb01ee7d06b0a095387a22f5dbdfa96473ea3da9d09e9811bac8c77219415c4cdd64d1c4f13eb7a02baf58e7d6ef54088f9263339c861536ac214f4fb37f4f2fa2131897e9f873efa0d47dcdbd16d1063d8265132cccf6d855ac465abf6ba3918e491e72fd198c35dea0756c8cb86bc2190fa0445a355d3634d151c0694051f3a9c2baf441b58e3d46d1a0ddcaab2008871bfbc3e37e792e1a5f2cf3762b216b63fa5ebcde02161a124e36a0c97aea09ed7dcba2074f35c5b3f6feaf6d4b4530e5f98fcf6d49021aa7a9bbe0a0eaa4be5178b745a6b368f99c8397a7916088be619924099a48e9caad7b10bf9ea0c8182bb15a64f2d00de328fc48ba33c8d904c0abe8a724cf990c7266019411aea0bc964e6778c173341cc1ba19378e01ad6f92aa34a15e779e22b58e3424271c96a05f45cfa8177cad4fa7febe7d92bea593651b0dee03426cafcefd639989ac2689a073ebd753cfc68fc7c63b92eef438e4963a2c5d9ef26d69f3bc092954ff042ac5a063b091b66f32fdface075fd018e6a43c6a18c5e45c0f36018766b9fd359e3f07a0622edead43b56de895f373d1023d453a7f28fe9b053d3d43f38e81d8bbcc33cc80", + "0xf90211a0d3a6f796f683b9500a144cffbec831a1cd0f36fa179d495df2ca739950eced36a0aa6265cdd5fdf1f235483206cc564a7e6368402819bfb59fae1af54215501ce8a08daf46ff598319c6f8669f4fb24bf457aec4a548e67bb34464b8806145f09732a084bc9452c3c31282800fedabc79f26b4ba5ff870b1501e4e15a0445c109baa62a0e26425c50bb9cddf78b5ea820af5235b2af83b54399804153d31b56e008ba40ca06ced50532f5303138b053cda8bfa07e8be5b5a281f598e463739663cad60b7f6a0db2121deace8995ab60128cba5a5bf414ffdac28e4dc90b26690f593d93d98fea04c6fe3b62eaaf990df14fefcd15b7822c0c9b6a3bfb6c97fbae4898fd8cb8751a03e63d266e6bdcf2f6b68b09f51208dce30b8eccca11cacd8d4d817f5cd35ebdca09f20131570ebabd82ec6f025ec3161893c2860bbf66fc612eb9dd529e63d12dfa006366ecc6e9adec67eb87a740dec9de832bebd24fb35eb639660b36624136718a04ee5aa74f2b65c5b8f9addab8b4efe241dcc94cb00862e8eab3f2064de570993a028639797a44fb9006dacbd8c9516a7b4c2c0b4f5fcfdee58e6d43822d7bcfb98a003d75afe382d531d98d8ba7096b7df7c16a882fe674b5f198dbb0be78133837fa0d696f1f254fb0f631fd54455fdf6bd3ac97a1e1e7ffb56531f9b59a000543b02a04d4f0d1386215b2d7e64fcd2bcb8288c169faef6f2597570ffd01a9e348e9e9680", + "0xf90211a03ef27e62add278aab0d21cc70ad297e32faf1013d12a945ab541b54c218e332aa0780c1b88c39ffd54b8437d392e1cfe174245eae8b5f57ba1465f5c4bdc009eaba021c6cb03221a1ddb9479231b06eec98e0cc29757c6b1f66c075161d19d28d899a0dd77dfa5f6dd39de8be556a74938a32ada4164cd3d74c9cbda70654b5eea48a7a081c39c7fb4b8f4fec9492de412cba484fa94fc5af022c8ea4ef2b74a3d5eba50a0be7769cb5d2c11c9f0e27b77d9253cc1c58fa93a6bc323d41b91518db6a1b4f7a0a822a61d22a729d9b7eb8a732fc7aa7df47d8b93962c61c036624f8919bcf936a0fabbc2b3ff72ab5f33ff8e1b15b7290f7ea596cf7d8df41f8ccc672829981779a088d974885759c175d317f33bed84ad1fad92117ff49eebe4822445f6c6432ec2a0a8f0c9342ec02038d206a397eb79ae89db9d50ffde50ec5ea1eab473b1ed65aaa0d4cbc8faf94ec2d78a86b447459671136d442df37b9a0334dea626dbee21a7bca0ebb21e8a0693a521e11574dbc2b64c0b60e1ff03d275ae9015fd6f77e2ffb348a0c42ac066fe48e16c989a92cbc8abb771492ec1214773acae9b479201996d79faa00d0aa5e650dd52161db61f3e8daf3a98b5807b253adba4aa330a1b2597016ddca04dca1357f7c38d9fa3be494ba30930d17e5e2b259376cc0729cbb18e28522d13a0f47755047d48aed62cc054b9444230a02e3b4365a423a86da7d1651d30bed5b480", + "0xf90211a0eda0f1dcdef84dfdac07fda9d761613e383566a81ba5b13c160a0061bed49005a03c3d8334c3d7ce552a401fe7f608e40693452c37b0ac9c854eab22e433932088a0588d16eba3c636da30d34b0702eb7b6f5fb378ff7bd4b957bb5cab7bbda42b2aa0a23edbab1becfbb4f1f901b345fb360f65bc7f3477cdd9d593a34fc3b40aabe8a0c7816adbe5240f04a86ba81deb8d0740be0950eabb9f074f1b57c27e36e0b301a05d1058132f99bb51cd44d1b22a7145a2c8b3cafc65ebd3f3e5c5d10652d0cb50a0e0f5e3576473f62725a2a94c0be6742af15eb0cb0e620941b83bf252eb0c9284a00c9a49e594f73e3b4dcf2c8f4acc07c7ae263de5b9c1b1917f8b0981694c1a88a05918db6ccef5fc33ee95423a35d593f7463b8ddacf6ed599be4475d41aed78dfa06405ecfdea3ad15e04a21514c6d43647e88b7aafabceb43b54f121b552ef1453a0cbd382e1c28d0abd466fd6a13acf2229afe27546ac12965baecca343346ac606a06252e48391dc4c31c8aae394fbbba75381346f6dcaba952525d636fa8260e14da0943e5af9e9e84896217ce85a58f0c453f6fb3b09825d3af9e9039ac8f06ee347a07d07568a760de38e6623bad03ea0941e6abcbce99cf809b1da3de034481c7f6ca0864d94bff09139683ae4674b43a8ceedd058134a77b1e6fbcc22d7a821483304a0d5066909c2b6b5a95d927ae60fc7cb37bb5192acbfb72f070479ef84e7a8d88b80", + "0xf90211a0feb174d439e0dc86af9c6391161126cbb3041f4c3d65977e80f0a2f617df88a3a05f3b90bc51ced0a787645952db0965085988b103ef9d70c9b7a13b419f2a8497a001137732eed9526bc1233f5e7a6094a7260ca40305bf09249c5be92ce05a43b0a0949136e6c758730ac0a0d1ce5142b5090ef330e355f39203170e1d5c9a263d17a0dfa05eb61765de1d31a696008e783d08193dd044264d5ae3b90c916ba012a445a08e1c09b54baf884acb1b789503a82a50019231d5faf8e333e9a8305d70a07306a0bbb1be9c91b93b94f7a62c05bc22350c1221c858d0a7e69055acb15ab075cb6fa0ca8ac36ca096caa295ffca544c34943a899a9fe01a38777e7f51eccf3db140e3a0ea412b9806cff2f55c13b23cf1e8b496e652274656ec5ad33484030151edd2c5a04b4ecf27d14b52daed5d42449fd34b3a17cbbdc6a5d045807c9ebe36907ea6e5a05e82e7ab805f9deb9e1f161efcfbb4790b2e0d1c5ae8c4dcf8135f327baaddc8a0b33244a04adb6a5c63933311d2aea2004dd0dcb088ab1f32deb096d28f70fd22a0e62a290a9cab845ef11adc0de7c6b8fef91a362a11b65c33d8478ae6eaf6d095a0cbf4b6962195e1b78ac9d18e3182272f52cd3bcf99c757e278c345d7d99dbb50a0e90c8d491802dd1744921b54aadd18c27889349dbe26f39c022adba06d04f070a079e011dd3e741eba34865f99e613dbaae1ed633091ea728d350fba5dd826774280", + "0xf9011180a04f0bc36ba74e6466f88f99ebda9aaf5e3b0d0ccb1babefde75e1794354c3872c80a081039b3c1c6f623d87e0bfc2c167b94e9396b4531a4c96b1a530889fa1f614f8a09cc8165d6cfc1cb51c22bb8d227f0405788a4fe1882972d69792abc7012c10e2a0dfd3faa8be2e0b5a889b142c3215d5f33475a0ba82fc1b1a0b0f729145bc713680a0a12352bfaa48f2cf6836747939970bedbd57c4bb41be21e8bca51e707f914f17a044e81f28fcc6dbff10ed896e61739345198a5209f18c606a0b8acf94125a3cd6a062453197ff143d336268ceac5a85c51ad3e550703939676d34647c35acb37e4a8080a02dd35da72afddfa9855b7a5a934486dcac55fcf982be124df02066f1482834d280808080", + "0xe48200aca0011e0fd69b0d105be03f26707e488aec6ea7a3439dd73c22c95eb780cd7b766c", + "0xf84f80808080a0f02444e4dd7a61d2a14f061f39ff92ca158bfff48da37120a4c9c54155cf6a3ede9c300012866d2bb9d91c572671af93a9cfaaf8295e4838e1b8f6d739e0018080808080808080808080" + ], + "value": "0x01" + } + ] +} diff --git a/axiom-eth/src/beacon/data_gen/beacon_api.rs b/axiom-eth/src/beacon/data_gen/beacon_api.rs new file mode 100644 index 00000000..bd18bfc6 --- /dev/null +++ b/axiom-eth/src/beacon/data_gen/beacon_api.rs @@ -0,0 +1,75 @@ +use beacon_api_client::{mainnet::Client, StateId}; +use itertools::Itertools; +use ssz_rs::prelude::*; +use std::{env::var, fs::File, io::Write}; +use url::Url; + +pub const MAINNET_URL: &str = "https://eth2-beacon-mainnet.nodereal.io/v1/"; + +pub fn setup_url() -> String { + let nodereal_id = var("NODEREAL_ID").expect("NODEREAL_ID environmental variable not set"); + MAINNET_URL.to_owned() + &nodereal_id + "/" +} + +pub async fn get_beacon_state_components(slot: u64) { + let url = setup_url(); + let client = Client::new(Url::parse(&url).unwrap()); + let mut response = client.get_state(StateId::Slot(slot)).await.unwrap(); + let gt = hex::encode(response.genesis_time.hash_tree_root().expect("bad")); + let gvr = hex::encode(response.genesis_validators_root); + let slo = hex::encode(response.slot.hash_tree_root().expect("bad")); + let fork = hex::encode(response.fork.hash_tree_root().expect("bad")); + let lbh = hex::encode(response.latest_block_header.hash_tree_root().expect("bad")); + let br = hex::encode(response.block_roots.hash_tree_root().unwrap()); + let sr = hex::encode(response.state_roots.hash_tree_root().unwrap()); + let hr = hex::encode(response.historical_roots.hash_tree_root().unwrap()); + let ed = hex::encode(response.eth1_data.hash_tree_root().unwrap()); + let edv = hex::encode(response.eth1_data_votes.hash_tree_root().unwrap()); + let edi = hex::encode(response.eth1_deposit_index.hash_tree_root().unwrap()); + let v = hex::encode(response.validators.hash_tree_root().unwrap()); + let b = hex::encode(response.balances.hash_tree_root().unwrap()); + let rm = hex::encode(response.randao_mixes.hash_tree_root().unwrap()); + let sl = hex::encode(response.slashings.hash_tree_root().unwrap()); + let pep = hex::encode(response.previous_epoch_attestations.hash_tree_root().unwrap()); + let cep = hex::encode(response.current_epoch_attestations.hash_tree_root().unwrap()); + let jb = hex::encode(response.justification_bits.hash_tree_root().unwrap()); + let pjc = hex::encode(response.previous_justified_checkpoint.hash_tree_root().unwrap()); + let cjc = hex::encode(response.current_justified_checkpoint.hash_tree_root().unwrap()); + let fc = hex::encode(response.finalized_checkpoint.hash_tree_root().unwrap()); + let roots = [ + gt, gvr, slo, fork, lbh, br, sr, hr, ed, edv, edi, v, b, rm, sl, pep, cep, jb, pjc, cjc, fc, + ]; + let byte_roots = roots.iter().map(|v| hex::decode(v).unwrap()).collect_vec(); + let byte_roots = + byte_roots.into_iter().map(|v| Vector::::try_from(v).unwrap()).collect_vec(); + let mut byte_roots = Vector::, 21>::try_from(byte_roots).unwrap(); + println!("{:?}", hex::encode(byte_roots.hash_tree_root().unwrap())); + println!("{:?}", hex::encode(response.hash_tree_root().unwrap())); + let new_response = client.get_state_root(StateId::Slot(slot)).await.unwrap(); + println!("{:?}", hex::encode(new_response)); + let mut components = + File::create("src/beacon/data_gen/cached_computations/beacon_state_components.json") + .unwrap(); + let obj = serde_json::to_string_pretty(&roots).unwrap(); + let _ = components.write_all(obj.as_bytes()); +} + +pub async fn get_all_validators(slot: u64) { + let url = setup_url(); + let client = Client::new(Url::parse(&url).unwrap()); + let response = client.get_validators(StateId::Slot(slot), &[], &[]).await.unwrap(); + let mut file = File::create("src/beacon/data_gen/cached_computations/validators.json").unwrap(); + let response = response.into_iter().map(|r| r.validator).collect_vec(); + let obj = serde_json::to_string_pretty(&response).unwrap(); + let _ = file.write_all(obj.as_bytes()); +} + +pub async fn get_all_balances(slot: u64) { + let url = setup_url(); + let client = Client::new(Url::parse(&url).unwrap()); + let response = client.get_balances(StateId::Slot(slot), &[]).await.unwrap(); + let mut file = File::create("src/beacon/data_gen/cached_computations/balances.json").unwrap(); + let response = response.into_iter().map(|r| r.balance).collect_vec(); + let obj = serde_json::to_string_pretty(&response).unwrap(); + let _ = file.write_all(obj.as_bytes()); +} diff --git a/axiom-eth/src/beacon/data_gen/cached_computations/beacon_state_components.json b/axiom-eth/src/beacon/data_gen/cached_computations/beacon_state_components.json new file mode 100644 index 00000000..2b301583 --- /dev/null +++ b/axiom-eth/src/beacon/data_gen/cached_computations/beacon_state_components.json @@ -0,0 +1,23 @@ +[ + "5730c65f00000000000000000000000000000000000000000000000000000000", + "4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95", + "583d240000000000000000000000000000000000000000000000000000000000", + "db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "e69b76639ab8c65c3b3e6f8c2994cf47c68955bbf3b11ee09504273cc3a520c5", + "dcc63e70631d48ba78ff98b9525f469cef6498ed66629b328d3c334dd2f61678", + "90186bd32e6ecc71be0da5e4dda073b86519dd5bf385d6158eb568bde822afcd", + "bc0b224a72c40f878c9a423ab4595f6e5b06c8f85b81af04cd8c226592bbe88c", + "e0b6b5e2f3cf3d89b3e06cea94c189a6f47360a200ce86f76563124d07f5471a", + "67c2f91cda45d2ee15ef5e66f924d121dd294f028fcd8f25f8f85f29b3d3bb5f", + "04d3030000000000000000000000000000000000000000000000000000000000", + "e8cd2d3c1d619a21f551a7f6ff4b6ae801aca61de539fe4abf7157512841f9b7", + "14a62b8bcb6c0ab52171708ea8e1ba38f0eb37663cb86654a44cb40fd62eae2b", + "31f959d35ab20a59f65f1cad7ba25347b96091e8b2c032342660c02d060a4937", + "3c3440c99aa5966b6bc829f7973ffb7910dbcccca0ad56fcff0c372ddd4cb81a", + "912ad21c179a58c1399c71b32ab2248d38dc0b097fc75f5a6f434ecf0b22074d", + "245ae6942070d3836affcb6fcf2a11c412275f726fa5cf47fa9807d0173f454a", + "0f00000000000000000000000000000000000000000000000000000000000000", + "4298e0fd6e0c64c6fc1215118274b55397d54b050acb0997628c45d3ad2ec9d4", + "6f4af54f7e56ce0ecd4037400d18013144c68b7bde195f653201a0d6d7a7b7f2", + "4298e0fd6e0c64c6fc1215118274b55397d54b050acb0997628c45d3ad2ec9d4" +] \ No newline at end of file diff --git a/axiom-eth/src/beacon/data_gen/cached_computations/zeroes.json b/axiom-eth/src/beacon/data_gen/cached_computations/zeroes.json new file mode 100644 index 00000000..8299b826 --- /dev/null +++ b/axiom-eth/src/beacon/data_gen/cached_computations/zeroes.json @@ -0,0 +1,42 @@ +[ + "0000000000000000000000000000000000000000000000000000000000000000", + "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "c78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "d88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "df6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "b58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "d49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "f893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "cddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "feb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "e71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "b5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7", + "c6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff", + "1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5", + "2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d", + "328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", + "bfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", + "55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", + "f7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76", + "ad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f" +] \ No newline at end of file diff --git a/axiom-eth/src/beacon/data_gen/mod.rs b/axiom-eth/src/beacon/data_gen/mod.rs new file mode 100644 index 00000000..9c8d8ccc --- /dev/null +++ b/axiom-eth/src/beacon/data_gen/mod.rs @@ -0,0 +1,427 @@ +use crate::Field; +use halo2_base::{ + gates::RangeChip, halo2_proofs::halo2curves::bn256::Fr, utils::ScalarField, Context, +}; +use itertools::Itertools; +use serde_json::{from_str, Map, Value}; +use ssz_rs::prelude::*; + +use crate::{ + beacon::types::SszUint64, + providers::from_hex, + sha256::sha256, + ssz::{ + types::{SszBasicType, SszBasicTypeVector, SszStruct}, + SszChip, + }, +}; + +pub mod beacon_api; +#[cfg(test)] +pub mod test; + +use self::beacon_api::{get_all_balances, get_all_validators, get_beacon_state_components}; + +use super::types::Validator; + +pub const SLOT: u64 = 2375000; +pub const NUM_VALIDATORS: usize = 250399; +pub const TREE_DEPTH: usize = 20; +pub const BALANCE_TREE_DEPTH: usize = TREE_DEPTH - 2; + +#[derive(PartialEq, Eq, Debug, Default, Clone, SimpleSerialize)] +pub struct TestValidator { + pub public_key: Vector, + pub withdrawal_credentials: Vector, + pub effective_balance: u64, + pub slashed: bool, + pub activation_eligibility_epoch: u64, + pub activation_epoch: u64, + pub exit_epoch: u64, + pub withdrawable_epoch: u64, +} + +#[derive(PartialEq, Eq, Debug, Default, Clone, SimpleSerialize)] +pub struct TestValidatorInfo { + pub bls_pub_key: Vector, + pub withdrawal_creds: Vector, +} + +#[derive(PartialEq, Eq, Debug, Default, Clone, SimpleSerialize)] +pub struct TestPair { + pub first: u64, + pub second: u64, +} + +#[derive(Debug, Clone)] +pub struct SszPair { + pub first: SszUint64, + pub second: SszUint64, +} + +impl SszStruct for SszPair { + fn hash_root(&self, ctx: &mut Context, ssz: &SszChip) -> crate::ssz::types::Chunk { + let first_root = self.first.hash_root(ctx, ssz); + let second_root = self.second.hash_root(ctx, ssz); + ssz.merkleize(ctx, [first_root, second_root].to_vec()) + } +} + +pub fn get_validator_from_json(val: Value) -> TestValidator { + let bls: String = serde_json::from_value(val["pubkey"].clone()).unwrap(); + let bls = Vector::::try_from(from_hex(&bls[2..])).unwrap(); + let wc: String = serde_json::from_value(val["withdrawal_credentials"].clone()).unwrap(); + let wc = Vector::::try_from(from_hex(&wc[2..])).unwrap(); + let eb: String = serde_json::from_value(val["effective_balance"].clone()).unwrap(); + let eb = from_str::(&eb).unwrap(); + let sl: bool = serde_json::from_value(val["slashed"].clone()).unwrap(); + let aee: String = serde_json::from_value(val["activation_eligibility_epoch"].clone()).unwrap(); + let aee = from_str::(&aee).unwrap(); + let ae: String = serde_json::from_value(val["activation_epoch"].clone()).unwrap(); + let ae = from_str::(&ae).unwrap(); + let we: String = serde_json::from_value(val["withdrawable_epoch"].clone()).unwrap(); + let we = from_str::(&we).unwrap(); + let ee: String = serde_json::from_value(val["exit_epoch"].clone()).unwrap(); + let ee = from_str::(&ee).unwrap(); + TestValidator { + public_key: bls, + withdrawal_credentials: wc, + effective_balance: eb, + slashed: sl, + activation_eligibility_epoch: aee, + activation_epoch: ae, + exit_epoch: ee, + withdrawable_epoch: we, + } +} + +pub fn get_validator_assigned_from_json( + ctx: &mut Context, + range: &RangeChip, + val: Value, +) -> Validator { + let bls: String = serde_json::from_value(val["pubkey"].clone()).unwrap(); + let bls = from_hex(&bls[2..]); + let bls = bls.into_iter().map(|b| b as u64).collect_vec(); + let wc: String = serde_json::from_value(val["withdrawal_credentials"].clone()).unwrap(); + let wc = from_hex(&wc[2..]); + let wc = wc.into_iter().map(|w| w as u64).collect_vec(); + let eb: String = serde_json::from_value(val["effective_balance"].clone()).unwrap(); + let eb = from_str::(&eb).unwrap(); + let sl: bool = serde_json::from_value(val["slashed"].clone()).unwrap(); + let aee: String = serde_json::from_value(val["activation_eligibility_epoch"].clone()).unwrap(); + let aee = from_str::(&aee).unwrap(); + let ae: String = serde_json::from_value(val["activation_epoch"].clone()).unwrap(); + let ae = from_str::(&ae).unwrap(); + let we: String = serde_json::from_value(val["withdrawable_epoch"].clone()).unwrap(); + let we = from_str::(&we).unwrap(); + let ee: String = serde_json::from_value(val["exit_epoch"].clone()).unwrap(); + let ee = from_str::(&ee).unwrap(); + let bls = SszBasicTypeVector::new_from_ints(ctx, &range, bls, 8); + let wc = SszBasicTypeVector::new_from_ints(ctx, &range, wc, 8); + let eb = SszBasicType::new_from_int(ctx, &range, eb, 64); + let eb = SszUint64::from(eb); + let sl = SszBasicType::new_from_int(ctx, &range, sl as u64, 1); + let aee = SszBasicType::new_from_int(ctx, &range, aee, 64); + let aee = SszUint64::from(aee); + let ae = SszBasicType::new_from_int(ctx, &range, ae, 64); + let ae = SszUint64::from(ae); + let we = SszBasicType::new_from_int(ctx, &range, we, 64); + let we = SszUint64::from(we); + let ee = SszBasicType::new_from_int(ctx, &range, ee, 64); + let ee = SszUint64::from(ee); + Validator::from(bls, wc, eb, sl, aee, ae, ee, we) +} + +pub async fn get_all_data(slot: u64) { + get_beacon_state_components(slot).await; + get_all_validators(slot).await; + get_all_balances(slot).await; +} + +pub fn get_roots_and_zeroes() -> (Vec>, Vec>) { + let roots_str = + std::fs::read_to_string("src/beacon/data_gen/cached_computations/roots.json").unwrap(); + let roots_vec: serde_json::Value = serde_json::from_str(roots_str.as_str()).unwrap(); + let roots_vec: Vec = serde_json::from_value(roots_vec).unwrap(); + let roots_vec: Vec> = roots_vec.iter().map(|val| from_hex(&val)).collect(); + let zeroes_str = + std::fs::read_to_string("src/beacon/data_gen/cached_computations/zeroes.json").unwrap(); + let zeroes_vec: serde_json::Value = serde_json::from_str(zeroes_str.as_str()).unwrap(); + let zeroes_vec: Vec = serde_json::from_value(zeroes_vec).unwrap(); + let zeroes_vec: Vec> = zeroes_vec.iter().map(|val| from_hex(&val)).collect(); + (roots_vec, zeroes_vec) +} + +pub fn get_balance_roots_and_zeroes() -> (Vec>, Vec>) { + let roots_str = + std::fs::read_to_string("src/beacon/data_gen/cached_computations/balance_roots.json") + .unwrap(); + let roots_vec: serde_json::Value = serde_json::from_str(roots_str.as_str()).unwrap(); + let roots_vec: Vec = serde_json::from_value(roots_vec).unwrap(); + let roots_vec: Vec> = roots_vec.iter().map(|val| from_hex(&val)).collect(); + let zeroes_str = + std::fs::read_to_string("src/beacon/data_gen/cached_computations/zeroes.json").unwrap(); + let zeroes_vec: serde_json::Value = serde_json::from_str(zeroes_str.as_str()).unwrap(); + let zeroes_vec: Vec = serde_json::from_value(zeroes_vec).unwrap(); + let zeroes_vec: Vec> = zeroes_vec.iter().map(|val| from_hex(&val)).collect(); + (roots_vec, zeroes_vec) +} + +pub fn get_validator_proof(idx: usize) -> Map { + let (roots, zeroes) = get_roots_and_zeroes(); + assert!(idx < roots.len() / 2); + let base_len = 40 - TREE_DEPTH; + let mut base_path = vec![vec![0; 32]; base_len + 1]; + let mut len: Vec = vec![0; 32]; + let mut len_int = NUM_VALIDATORS; + for i in 0..32 { + len[i] = (len_int % 256) as u8; + len_int /= 256; + } + base_path[0] = len; + let mut base_dir = vec![0; base_len + 1]; + let mut root = roots[1].clone(); + for i in 0..base_len { + base_path[base_len - i] = zeroes[TREE_DEPTH + i].clone(); + let mut root_clone = root.clone(); + root_clone.append(&mut base_path[base_len - i].clone()); + root = sha256(root_clone).to_vec(); + } + let mut root_clone = root.clone(); + root_clone.append(&mut base_path[0].clone()); + root = sha256(root_clone).to_vec(); + let mut new_dir = vec![0; TREE_DEPTH]; + let mut dir_idx = idx; + for i in 0..TREE_DEPTH { + new_dir[40 - base_len - 1 - i] = dir_idx % 2; + dir_idx /= 2; + } + let mut roots_idx = 1; + let mut new_path = Vec::new(); + for i in 0..TREE_DEPTH { + roots_idx = roots_idx * 2 + new_dir[i]; + new_path.push(roots[roots_idx ^ 1].clone()); + } + base_path.append(&mut new_path); + base_dir.append(&mut new_dir); + let val = roots[roots_idx].clone(); + let mut map = Map::new(); + let root = hex::encode(root); + let val = hex::encode(val); + let proof = base_path.iter().map(|p| hex::encode(p)).collect_vec(); + map.insert("directions".to_owned(), base_dir.into()); + map.insert("val".to_owned(), val.into()); + map.insert("root_bytes".to_owned(), root.into()); + map.insert("proof".to_owned(), proof.into()); + map +} + +pub fn get_balance_proof(idx: usize) -> Map { + let (roots, zeroes) = get_balance_roots_and_zeroes(); + assert!(idx < roots.len() / 2); + let base_len = 40 - TREE_DEPTH; + let mut base_path = vec![vec![0; 32]; base_len + 1]; + let mut len: Vec = vec![0; 32]; + let mut len_int = NUM_VALIDATORS; + for i in 0..32 { + len[i] = (len_int % 256) as u8; + len_int /= 256; + } + base_path[0] = len; + let mut base_dir = vec![0; base_len + 1]; + let mut root = roots[1].clone(); + for i in 0..base_len { + base_path[base_len - i] = zeroes[BALANCE_TREE_DEPTH + i].clone(); + let mut root_clone = root.clone(); + root_clone.append(&mut base_path[base_len - i].clone()); + root = sha256(root_clone).to_vec(); + } + let mut root_clone = root.clone(); + root_clone.append(&mut base_path[0].clone()); + root = sha256(root_clone).to_vec(); + let mut new_dir = vec![0; BALANCE_TREE_DEPTH]; + let mut dir_idx = idx / 4; + for i in 0..BALANCE_TREE_DEPTH { + new_dir[BALANCE_TREE_DEPTH - 1 - i] = dir_idx % 2; + dir_idx /= 2; + } + let mut roots_idx = 1; + let mut new_path = Vec::new(); + for i in 0..BALANCE_TREE_DEPTH { + roots_idx = roots_idx * 2 + new_dir[i]; + new_path.push(roots[roots_idx ^ 1].clone()); + } + base_path.append(&mut new_path); + base_dir.append(&mut new_dir); + let val = roots[roots_idx].clone(); + let mut map = Map::new(); + let root = hex::encode(root); + let val = hex::encode(val); + let proof = base_path.iter().map(|p| hex::encode(p)).collect_vec(); + map.insert("directions".to_owned(), base_dir.into()); + map.insert("val".to_owned(), val.into()); + map.insert("root_bytes".to_owned(), root.into()); + map.insert("proof".to_owned(), proof.into()); + map +} + +pub fn get_validator_list_proof() -> (Vec>, Vec, Vec) { + let roots_str = std::fs::read_to_string( + "src/beacon/data_gen/cached_computations/beacon_state_components.json", + ) + .unwrap(); + let roots_vec: serde_json::Value = serde_json::from_str(roots_str.as_str()).unwrap(); + let roots_vec: Vec = serde_json::from_value(roots_vec).unwrap(); + let mut roots_vec: Vec> = roots_vec.iter().map(|val| from_hex(&val)).collect(); + roots_vec.resize(32, vec![0 as u8; 32]); + let mut roots = vec![vec![]; 32]; + roots.append(&mut roots_vec); + for i in 1..32 { + let idx = 32 - i; + let mut root_concat = roots[2 * idx].clone(); + root_concat.append(&mut roots[2 * idx + 1].clone()); + let new_root = sha256(root_concat).to_vec(); + roots[idx] = new_root; + } + let list_proof = vec![ + roots[3].clone(), + roots[4].clone(), + roots[11].clone(), + roots[20].clone(), + roots[42].clone(), + ]; + let list_dir: Vec = vec![0, 1, 0, 1, 1]; + let list_root = roots[1].clone(); + (list_proof, list_root, list_dir) +} + +pub fn get_balance_list_proof() -> (Vec>, Vec, Vec) { + let roots_str = std::fs::read_to_string( + "src/beacon/data_gen/cached_computations/beacon_state_components.json", + ) + .unwrap(); + let roots_vec: serde_json::Value = serde_json::from_str(roots_str.as_str()).unwrap(); + let roots_vec: Vec = serde_json::from_value(roots_vec).unwrap(); + let mut roots_vec: Vec> = roots_vec.iter().map(|val| from_hex(&val)).collect(); + roots_vec.resize(32, vec![0 as u8; 32]); + let mut roots = vec![vec![]; 32]; + roots.append(&mut roots_vec); + for i in 1..32 { + let idx = 32 - i; + let mut root_concat = roots[2 * idx].clone(); + root_concat.append(&mut roots[2 * idx + 1].clone()); + let new_root = sha256(root_concat).to_vec(); + roots[idx] = new_root; + } + let list_proof = vec![ + roots[3].clone(), + roots[4].clone(), + roots[10].clone(), + roots[23].clone(), + roots[45].clone(), + ]; + let list_dir: Vec = vec![0, 1, 1, 0, 0]; + let list_root = roots[1].clone(); + (list_proof, list_root, list_dir) +} + +pub fn get_validator_into_beacon(idx: usize) -> Map { + let mut map = get_validator_proof(idx); + let (list_proof, list_root, mut list_dir) = get_validator_list_proof(); + map["root_bytes"] = hex::encode(&list_root).into(); + let mut dirs: Vec = serde_json::from_value(map["directions"].clone()).unwrap(); + let mut proof: Vec = serde_json::from_value(map["proof"].clone()).unwrap(); + let mut list_proof = list_proof.into_iter().map(|p| hex::encode(&p)).collect_vec(); + list_dir.append(&mut dirs); + list_proof.append(&mut proof); + map["directions"] = list_dir.into(); + map["proof"] = list_proof.into(); + map +} + +pub fn get_balance_into_beacon(idx: usize) -> Map { + let bal_str = + std::fs::read_to_string("src/beacon/data_gen/cached_computations/balances.json").unwrap(); + let vec_val: serde_json::Value = serde_json::from_str(bal_str.as_str()).unwrap(); + let vec_val: Vec = serde_json::from_value(vec_val).unwrap(); + let val = vec_val[idx].clone(); + let balance: u64 = serde_json::from_value(val).unwrap(); + let mut map = get_balance_proof(idx); + let (list_proof, list_root, mut list_dir) = get_balance_list_proof(); + map["root_bytes"] = hex::encode(&list_root).into(); + let mut dirs: Vec = serde_json::from_value(map["directions"].clone()).unwrap(); + let mut proof: Vec = serde_json::from_value(map["proof"].clone()).unwrap(); + let mut list_proof = list_proof.into_iter().map(|p| hex::encode(&p)).collect_vec(); + list_dir.append(&mut dirs); + list_proof.append(&mut proof); + map["directions"] = list_dir.into(); + map["proof"] = list_proof.into(); + let mut new_map = Map::new(); + new_map.insert("proof".to_owned(), Value::Object(map)); + new_map.insert("idx".to_owned(), Value::Number(idx.into())); + new_map.insert("balance".to_owned(), Value::Number(balance.into())); + new_map +} + +pub fn get_validator_info_into_beacon(idx: usize) -> Map { + let val_str = + std::fs::read_to_string("src/beacon/data_gen/cached_computations/validators.json").unwrap(); + let vec_val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let vec_val: Vec = serde_json::from_value(vec_val).unwrap(); + let pow2 = 8; + let mut root_vec = vec![Vec::new(); 8]; + let val = vec_val[idx].clone(); + let bls: String = serde_json::from_value(val["pubkey"].clone()).unwrap(); + let bls_string = bls.clone(); + let mut bls = Vector::::try_from(from_hex(&bls[2..])).unwrap(); + let wc: String = serde_json::from_value(val["withdrawal_credentials"].clone()).unwrap(); + let wc_string = wc.clone(); + let mut wc = Vector::::try_from(from_hex(&wc[2..])).unwrap(); + let eb: String = serde_json::from_value(val["effective_balance"].clone()).unwrap(); + let mut eb = from_str::(&eb).unwrap(); + let mut sl: bool = serde_json::from_value(val["slashed"].clone()).unwrap(); + let aee: String = serde_json::from_value(val["activation_eligibility_epoch"].clone()).unwrap(); + let mut aee = from_str::(&aee).unwrap(); + let ae: String = serde_json::from_value(val["activation_epoch"].clone()).unwrap(); + let mut ae = from_str::(&ae).unwrap(); + let we: String = serde_json::from_value(val["withdrawable_epoch"].clone()).unwrap(); + let mut we = from_str::(&we).unwrap(); + let ee: String = serde_json::from_value(val["exit_epoch"].clone()).unwrap(); + let mut ee = from_str::(&ee).unwrap(); + let bls = hex::encode(bls.hash_tree_root().unwrap()); + let wc = hex::encode(wc.hash_tree_root().unwrap()); + let eb = hex::encode(eb.hash_tree_root().unwrap()); + let sl = hex::encode(sl.hash_tree_root().unwrap()); + let aee = hex::encode(aee.hash_tree_root().unwrap()); + let ae = hex::encode(ae.hash_tree_root().unwrap()); + let we = hex::encode(we.hash_tree_root().unwrap()); + let ee = hex::encode(ee.hash_tree_root().unwrap()); + let app = vec![bls, wc, eb, sl, aee, ae, we, ee]; + let mut app = app.into_iter().map(|s| from_hex(&s)).collect_vec(); + root_vec.append(&mut app); + for i in 1..pow2 { + let idx = pow2 - i; + let mut root_concat = root_vec[2 * idx].clone(); + root_concat.append(&mut root_vec[2 * idx + 1].clone()); + let new_root = sha256(root_concat).to_vec(); + root_vec[idx] = new_root; + } + let root_vec = root_vec.iter().map(|r| hex::encode(r)).collect_vec(); + let mut map = get_validator_into_beacon(idx); + let mut dirs: Vec = serde_json::from_value(map["directions"].clone()).unwrap(); + let mut proof: Vec = serde_json::from_value(map["proof"].clone()).unwrap(); + proof.push(root_vec[3].clone()); + proof.push(root_vec[5].clone()); + dirs.append(&mut vec![0, 0]); + map["directions"] = dirs.into(); + map["proof"] = proof.into(); + map["val"] = serde_json::Value::String(root_vec[4].clone()); + let mut new_map = Map::new(); + new_map.insert("idx".to_owned(), serde_json::Value::Number(idx.into())); + new_map.insert("public_key".to_owned(), serde_json::Value::String(bls_string)); + new_map.insert("withdrawal_credentials".to_owned(), serde_json::Value::String(wc_string)); + let proof = Value::Object(map); + new_map.insert("proof".to_owned(), proof); + new_map +} diff --git a/axiom-eth/src/beacon/data_gen/test.rs b/axiom-eth/src/beacon/data_gen/test.rs new file mode 100644 index 00000000..098f525a --- /dev/null +++ b/axiom-eth/src/beacon/data_gen/test.rs @@ -0,0 +1,346 @@ +use std::{ + fs::File, + io::{BufWriter, Write}, +}; + +use halo2_base::halo2_proofs::halo2curves::bn256::Fr; +use itertools::Itertools; +use serde_json::{from_str, Value}; +use ssz_rs::{List, Merkleized, Vector}; + +use crate::{ + beacon::{ + data_gen::{ + from_hex, get_all_balances, get_all_data, get_all_validators, + get_beacon_state_components, get_roots_and_zeroes, get_validator_assigned_from_json, + get_validator_from_json, SszPair, TestPair, TestValidator, + }, + types::SszUint64, + }, + rlc::circuit::builder::RlcCircuitBuilder, + sha256::{sha256, Sha256Chip}, + ssz::{ + types::{SszBasicType, SszBasicTypeVector, SszList, SszStruct, SszVector}, + SszChip, + }, +}; + +use super::{get_validator_into_beacon, get_validator_proof, BALANCE_TREE_DEPTH, TREE_DEPTH}; + +#[test] +pub fn get_bls_root() { + let val_str = + std::fs::read_to_string("src/beacon/tests/generated_tests/validator.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let bls: String = serde_json::from_value(val["pubkey"].clone()).unwrap(); + let mut bls = Vector::::try_from(from_hex(&bls[2..])).unwrap(); + let root = bls.hash_tree_root().expect("bad"); + println!("{:?}", hex::encode(root)); +} + +#[test] +pub fn get_bls_root_assigned() { + let val_str = + std::fs::read_to_string("src/beacon/tests/generated_tests/validator.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let bls: String = serde_json::from_value(val["pubkey"].clone()).unwrap(); + let bls = from_hex(&bls[2..]); + let bls = bls.into_iter().map(|b| b as u64).collect_vec(); + let mut builder = RlcCircuitBuilder::::new(false, 10).use_lookup_bits(8); + let range = builder.range_chip(); + let ctx = builder.base.main(0); + let bls = SszBasicTypeVector::new_from_ints(ctx, &range, bls, 8); + let ssz = SszChip::new(None, &range, Sha256Chip::new(&range)); + println!("{:?}", bls.hash_root(ctx, &ssz)); +} + +#[test] +pub fn get_eb_root() { + let val_str = + std::fs::read_to_string("src/beacon/tests/generated_tests/validator.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let eb: String = serde_json::from_value(val["effective_balance"].clone()).unwrap(); + let mut eb = from_str::(&eb).unwrap(); + let root = eb.hash_tree_root().expect("bad"); + println!("{:?}", hex::encode(root)); +} + +#[test] +pub fn get_eb_root_assigned() { + let val_str = + std::fs::read_to_string("src/beacon/tests/generated_tests/validator.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let eb: String = serde_json::from_value(val["effective_balance"].clone()).unwrap(); + let eb = from_str::(&eb).unwrap(); + let mut builder = RlcCircuitBuilder::::new(false, 10).use_lookup_bits(8); + let range = builder.range_chip(); + let ctx = builder.base.main(0); + let eb = SszBasicType::new_from_int(ctx, &range, eb, 64); + let eb = SszUint64::from(eb); + let ssz = SszChip::new(None, &range, Sha256Chip::new(&range)); + println!("{:?}", eb.hash_root(ctx, &ssz)); +} + +#[test] +pub fn get_sl_root() { + let val_str = + std::fs::read_to_string("src/beacon/tests/generated_tests/validator.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let mut sl: bool = serde_json::from_value(val["slashed"].clone()).unwrap(); + let root = sl.hash_tree_root().expect("bad"); + println!("{:?}", hex::encode(root)); +} + +#[test] +pub fn get_sl_root_assigned() { + let val_str = + std::fs::read_to_string("src/beacon/tests/generated_tests/validator.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let sl: bool = serde_json::from_value(val["slashed"].clone()).unwrap(); + let mut builder = RlcCircuitBuilder::::new(false, 10).use_lookup_bits(8); + let range = builder.range_chip(); + let ctx = builder.base.main(0); + let sl = SszBasicType::new_from_int(ctx, &range, sl as u64, 1); + let ssz = SszChip::new(None, &range, Sha256Chip::new(&range)); + println!("{:?}", sl.hash_root(ctx, &ssz)); +} + +#[test] +pub fn get_pair_root() { + let val_str = std::fs::read_to_string("src/beacon/tests/generated_tests/pair.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let first: u64 = serde_json::from_value(val["first"].clone()).unwrap(); + let second: u64 = serde_json::from_value(val["second"].clone()).unwrap(); + let mut val = TestPair { first, second }; + let root = val.hash_tree_root().expect("bad"); + println!("{:?}", hex::encode(root)); +} + +#[test] +pub fn get_pair_root_assigned() { + let val_str = std::fs::read_to_string("src/beacon/tests/generated_tests/pair.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let first: u64 = serde_json::from_value(val["first"].clone()).unwrap(); + let second: u64 = serde_json::from_value(val["second"].clone()).unwrap(); + let mut builder = RlcCircuitBuilder::new(false, 10).use_lookup_bits(8); + let range = builder.range_chip(); + let ctx = builder.base.main(0); + let first = SszBasicType::new_from_int(ctx, &range, first, 64); + let first: SszUint64 = SszUint64::from(first); + let second = SszBasicType::new_from_int(ctx, &range, second, 64); + let second = SszUint64::from(second); + let val = SszPair { first, second }; + let ssz = SszChip::new(None, &range, Sha256Chip::new(&range)); + println!("{:?}", val.hash_root(ctx, &ssz)); +} + +#[test] +pub fn get_validator_vec_root() { + let val_str = + std::fs::read_to_string("src/beacon/tests/generated_tests/validator.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let val = get_validator_from_json(val); + let val_vec = vec![val.clone(), val.clone(), val]; + let mut val_vec = Vector::::try_from(val_vec).unwrap(); + let root = val_vec.hash_tree_root().expect("bad"); + println!("{:?}", hex::encode(root)); +} + +#[test] +pub fn get_validator_vec_root_assigned() { + let val_str = + std::fs::read_to_string("src/beacon/tests/generated_tests/validator.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let mut builder = RlcCircuitBuilder::new(false, 10).use_lookup_bits(8); + let range = builder.range_chip(); + let ctx = builder.base.main(0); + let val = get_validator_assigned_from_json(ctx, &range, val); + let ssz = SszChip::new(None, &range, Sha256Chip::new(&range)); + let val_vec = vec![val.clone(), val.clone(), val]; + let val_vec = SszVector { values: val_vec }; + println!("{:?}", val_vec.hash_root(ctx, &ssz)); +} + +#[test] +pub fn get_validator_list_root() { + let val_str = + std::fs::read_to_string("src/beacon/tests/generated_tests/validator.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let val = get_validator_from_json(val); + let val_vec = vec![val.clone(), val.clone()]; + let mut val_vec = List::::try_from(val_vec).unwrap(); + let root = val_vec.hash_tree_root().expect("bad"); + println!("{:?}", hex::encode(root)); +} + +#[test] +pub fn get_validator_list_root_assigned() { + let val_str = + std::fs::read_to_string("src/beacon/tests/generated_tests/validator.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let mut builder = RlcCircuitBuilder::new(false, 10).use_lookup_bits(8); + let range = builder.range_chip(); + let ctx = builder.base.main(0); + let val = get_validator_assigned_from_json(ctx, &range, val); + let ssz = SszChip::new(None, &range, Sha256Chip::new(&range)); + let val_vec = vec![val.clone(), val.clone(), val]; + let two = ctx.load_witness(Fr::from(2)); + let val_vec = SszList { values: val_vec, len: two }; + println!("{:?}", val_vec.hash_root(ctx, &ssz)); +} + +#[test] +pub fn get_validator_root() { + let val_str = + std::fs::read_to_string("src/beacon/tests/generated_tests/validator.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let mut val = get_validator_from_json(val); + let root = val.hash_tree_root().expect("bad"); + println!("{:?}", hex::encode(root)); +} + +#[test] +pub fn get_validator_root_assigned() { + let val_str = + std::fs::read_to_string("src/beacon/tests/generated_tests/validator.json").unwrap(); + let val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let mut builder = RlcCircuitBuilder::new(false, 10).use_lookup_bits(8); + let range = builder.range_chip(); + let ctx = builder.base.main(0); + let val = get_validator_assigned_from_json(ctx, &range, val); + let ssz = SszChip::new(None, &range, Sha256Chip::new(&range)); + println!("{:?}", val.hash_root(ctx, &ssz)); +} + +#[tokio::test] +pub async fn test_beacon_state() { + get_beacon_state_components(2375000).await; +} + +#[tokio::test] +pub async fn test_validators() { + get_all_validators(2375000).await; +} + +#[tokio::test] +pub async fn test_balances() { + get_all_balances(2375000).await; +} + +#[tokio::test] +pub async fn test_beacon_state_and_validators_and_balances() { + get_all_data(2375000).await; +} + +#[test] +pub fn get_all_validators_root() { + let val_str = + std::fs::read_to_string("src/beacon/data_gen/cached_computations/validators.json").unwrap(); + let vec_val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let vec_val: Vec = serde_json::from_value(vec_val).unwrap(); + let len = vec_val.len(); + let base: usize = 2; + let pow2 = base.pow(TREE_DEPTH as u32); + let total_depth = 40; + let mut root_vec = vec![Vec::new(); pow2]; + let mut z_roots = vec![vec![0; 32]]; + for i in 0..len { + let val = vec_val[i].clone(); + let mut val = get_validator_from_json(val); + let root = val.hash_tree_root().expect("bad"); + root_vec.push(from_hex(&hex::encode(root))); + } + for _ in len..pow2 { + let zeroes: Vec = vec![0; 32]; + root_vec.push(zeroes); + } + for i in 1..pow2 { + let idx = pow2 - i; + let mut root_concat = root_vec[2 * idx].clone(); + root_concat.append(&mut root_vec[2 * idx + 1].clone()); + let new_root = sha256(root_concat).to_vec(); + root_vec[idx] = new_root; + } + for i in 1..total_depth { + let mut z_root = z_roots[i - 1].clone(); + z_root.append(&mut z_roots[i - 1].clone()); + z_roots.push(sha256(z_root).to_vec()); + } + let mut roots = File::create("src/beacon/data_gen/cached_computations/roots.json") + .expect("Unable to create file"); + let root_vec = root_vec.iter().map(|r| hex::encode(r)).collect_vec(); + let roots_str = serde_json::to_string_pretty(&root_vec).unwrap(); + let _ = roots.write_all(roots_str.as_bytes()); + let mut zeroes = File::create("src/beacon/data_gen/cached_computations/zeroes.json") + .expect("Unable to create file"); + let z_roots = z_roots.iter().map(|r| hex::encode(r)).collect_vec(); + let z_str = serde_json::to_string_pretty(&z_roots).unwrap(); + let _ = zeroes.write_all(z_str.as_bytes()); +} + +#[test] +pub fn get_all_balances_root() { + let val_str = + std::fs::read_to_string("src/beacon/data_gen/cached_computations/balances.json").unwrap(); + let vec_val: serde_json::Value = serde_json::from_str(val_str.as_str()).unwrap(); + let mut vec_val: Vec = serde_json::from_value(vec_val).unwrap(); + let len = vec_val.len(); + vec_val.resize(4 * ((len + 3) / 4), 0); + let base: usize = 2; + let pow2 = base.pow(BALANCE_TREE_DEPTH as u32); + let mut root_vec = vec![Vec::new(); pow2]; + for i in 0..(len + 3) / 4 { + let mut val = Vector::::try_from(vec![ + vec_val[4 * i], + vec_val[4 * i + 1], + vec_val[4 * i + 2], + vec_val[4 * i + 3], + ]) + .unwrap(); + let root = val.hash_tree_root().expect("bad"); + root_vec.push(from_hex(&hex::encode(root))); + } + for _ in (len + 3) / 4..pow2 { + let zeroes: Vec = vec![0; 32]; + root_vec.push(zeroes); + } + for i in 1..pow2 { + let idx = pow2 - i; + let mut root_concat = root_vec[2 * idx].clone(); + root_concat.append(&mut root_vec[2 * idx + 1].clone()); + let new_root = sha256(root_concat).to_vec(); + root_vec[idx] = new_root; + } + let mut roots = File::create("src/beacon/data_gen/cached_computations/balance_roots.json") + .expect("Unable to create file"); + let root_vec = root_vec.iter().map(|r| hex::encode(r)).collect_vec(); + let roots_str = serde_json::to_string_pretty(&root_vec).unwrap(); + let _ = roots.write_all(roots_str.as_bytes()); +} + +#[test] +pub fn test_get_roots_and_zeroes() { + let (r, z) = get_roots_and_zeroes(); + println!("{:?}", r.len()); + println!("{:?}", z.len()); +} + +#[test] +pub fn get_validator_into_beacon1() { + let map = get_validator_into_beacon(1); + let proof = Value::Object(map); + let file = File::create("src/ssz/tests/merkle_proof/real_beacon_proof.json").unwrap(); + let mut writer = BufWriter::new(file); + let _ = serde_json::to_writer_pretty(&mut writer, &proof); + let _ = writer.flush(); +} + +#[test] +pub fn get_validator1() { + let map = get_validator_proof(1); + let proof = Value::Object(map); + let file = File::create("src/ssz/tests/merkle_proof/real_proof.json").unwrap(); + let mut writer = BufWriter::new(file); + let _ = serde_json::to_writer_pretty(&mut writer, &proof); + let _ = writer.flush(); +} diff --git a/axiom-eth/src/beacon/mod.rs b/axiom-eth/src/beacon/mod.rs new file mode 100644 index 00000000..b6271b6d --- /dev/null +++ b/axiom-eth/src/beacon/mod.rs @@ -0,0 +1,286 @@ +//! Merkle Patricia Trie (MPT) inclusion & exclusion proofs in ZK. +//! +//! See https://hackmd.io/@axiom/ry35GZ4l3 for a technical walkthrough of circuit structure and logic +use crate::Field; +use crate::{ + rlc::chip::RlcChip, + ssz::{self, types::SszStruct, SSZInclusionWitness, SszChip}, +}; +use halo2_base::{ + gates::{GateChip, GateInstructions, RangeChip, RangeInstructions}, + AssignedValue, Context, + QuantumCell::Constant, +}; +use itertools::Itertools; + +use self::ssz::SSZInputAssigned; +use self::{ssz::types::Chunk, types::ValidatorInfo}; + +use self::types::{Gwei, Validator}; + +pub mod data_gen; +#[cfg(test)] +pub mod tests; +pub mod types; + +pub const BEACON_STATE_FIELDS: usize = 25; +pub const VALIDATOR_LIST_IDX: usize = 11; +pub const BALANCE_LIST_IDX: usize = 12; +pub const VALIDATOR_REGISTRY_LIMIT: u64 = 1_099_511_627_776; +pub const VALIDATOR_REGISTRY_LIMIT_BITS: usize = 40; +pub const INFO_ADDITIONAL_BITS: usize = 2; +pub const VALIDATOR_LIST_PATH_BITS: usize = 5; +pub const VALIDATOR_LIST_PATH: [usize; 5] = [0, 1, 0, 1, 1]; +pub const BALANCE_LIST_PATH: [usize; 5] = [0, 1, 1, 0, 0]; +pub const VALIDATOR_DEPTH: usize = 46; +pub const BALANCE_DEPTH: usize = 44; + +#[derive(Clone, Debug)] +pub struct BeaconChip<'range, F: Field> { + pub ssz: &'range SszChip<'range, F>, +} + +impl<'range, F: Field> BeaconChip<'range, F> { + pub fn new(ssz: &'range SszChip) -> Self { + Self { ssz } + } + + pub fn gate(&self) -> &GateChip { + self.ssz.gate() + } + + pub fn range(&self) -> &RangeChip { + self.ssz.range + } + + pub fn rlc(&self) -> &RlcChip { + self.ssz.rlc.as_ref().expect("RlcChip should be constructed and used only in SecondPhase") + } + + pub fn validator_hash_root(self, ctx: &mut Context, validator: Validator) -> Chunk { + validator.hash_root(ctx, self.ssz) + } + + pub fn verify_validator_inclusion( + self, + ctx: &mut Context, + validator: &Validator, + proof: SSZInputAssigned, + ) -> SSZInclusionWitness { + // let validator_hash = validator.hash_root(ctx, self.ssz, sha); + // let witness = self.ssz.verify_inclusion_proof(ctx, sha, proof); + // for (hash_byte, val_byte) in validator_hash.iter().zip(witness.val.iter()) { + // ctx.constrain_equal(hash_byte, val_byte); + // } + self.ssz.verify_struct_inclusion(ctx, proof, validator) + } + + pub fn verify_validator_info_from_validator( + self, + ctx: &mut Context, + proof: SSZInputAssigned, + info: &ValidatorInfo, + ) -> SSZInclusionWitness { + let zero = ctx.load_zero(); + let witness = self.ssz.verify_struct_field_inclusion(ctx, zero, 4, proof, info); + witness + } + + pub fn verify_validator_info_from_beacon_block_root( + self, + ctx: &mut Context, + idx: AssignedValue, + info: &ValidatorInfo, + proof: SSZInputAssigned, + ) -> (AssignedValue, SSZInclusionWitness) { + let zero = ctx.load_zero(); + let new_idx = self.gate().mul(ctx, Constant(F::from(4)), idx); + let (list_len, witness) = self.verify_struct_from_beacon_block_root( + ctx, + new_idx, + info, + proof, + VALIDATOR_LIST_PATH.to_vec(), + Some(VALIDATOR_DEPTH + INFO_ADDITIONAL_BITS), + ); + ctx.constrain_equal(&witness.directions[VALIDATOR_DEPTH], &zero); + ctx.constrain_equal(&witness.directions[VALIDATOR_DEPTH + 1], &zero); + self.range().check_less_than(ctx, idx, list_len, VALIDATOR_REGISTRY_LIMIT_BITS + 1); + (list_len, witness) + } + + pub fn verify_validator_field_hash_root( + self, + ctx: &mut Context, + field_num: AssignedValue, + proof: SSZInputAssigned, + ) -> (AssignedValue, SSZInclusionWitness) { + let witness = self.ssz.verify_field_hash(ctx, field_num, 8, proof); + (field_num, witness) + } + /// The root of the proof is beacon_root, maybe this isn't even necessary(?). + /// verify_validator_inclusion seems to be sufficient due to cryptography or something + pub fn verify_validator_from_beacon_block_root( + self, + ctx: &mut Context, + idx: AssignedValue, + validator: &Validator, + proof: SSZInputAssigned, + ) -> (AssignedValue, SSZInclusionWitness) { + let (list_len, witness) = self.verify_struct_from_beacon_block_root( + ctx, + idx, + validator, + proof, + VALIDATOR_LIST_PATH.to_vec(), + Some(VALIDATOR_DEPTH), + ); + self.range().check_less_than(ctx, idx, list_len, VALIDATOR_REGISTRY_LIMIT_BITS + 1); + (list_len, witness) + } + + /// /// The root of the proof is beacon_root, maybe this isn't even necessary(?). + /// verify_validator_inclusion seems to be sufficient due to cryptography or something + pub fn verify_validator_hash_from_beacon_block_root( + &self, + ctx: &mut Context, + idx: AssignedValue, + proof: SSZInputAssigned, + ) -> (AssignedValue, SSZInclusionWitness) { + let (list_len, witness) = self.verify_hash_from_beacon_block_root( + ctx, + idx, + proof, + VALIDATOR_LIST_PATH.to_vec(), + Some(VALIDATOR_DEPTH), + ); + self.range().check_less_than(ctx, idx, list_len, VALIDATOR_REGISTRY_LIMIT_BITS + 1); + (list_len, witness) + } + + /// The root of the proof is beacon_root, maybe this isn't even necessary(?). + /// verify_validator_inclusion seems to be sufficient due to cryptography or something + /// There is a bit of redundancy here not gonna lie + pub fn verify_balance_from_beacon_block_root( + &self, + ctx: &mut Context, + idx: AssignedValue, + idx_div_4: AssignedValue, + idx_mod_4: AssignedValue, + balance: &Gwei, + proof: SSZInputAssigned, + ) -> (AssignedValue, AssignedValue, SSZInclusionWitness) { + self.range().check_less_than_safe(ctx, idx_mod_4, 4); + let (len, witness) = self.verify_hash_from_beacon_block_root( + ctx, + idx_div_4, + proof, + BALANCE_LIST_PATH.to_vec(), + Some(BALANCE_DEPTH), + ); + let balance_val = balance.val().value(); + let chunks = witness.val.chunks(8); + let balances = chunks.map(|v| v.to_vec()).collect_vec(); + let indicator = self.gate().idx_to_indicator(ctx, idx_mod_4, 4); + for i in 0..8 { + let mut bytes = Vec::new(); + for j in 0..4 { + bytes.push(balances[j][i]); + } + let chosen_byte = self.gate().select_by_indicator(ctx, bytes, indicator.clone()); + ctx.constrain_equal(&balance_val[i], &chosen_byte); + } + let mut match_idx = self.gate().mul(ctx, idx_div_4, Constant(F::from(4))); + match_idx = self.gate().add(ctx, match_idx, idx_mod_4); + ctx.constrain_equal(&idx, &match_idx); + self.range().check_less_than(ctx, idx_div_4, len, VALIDATOR_REGISTRY_LIMIT_BITS + 1); + self.range().check_less_than(ctx, idx, len, VALIDATOR_REGISTRY_LIMIT_BITS + 1); + (idx, len, witness) + } + + /// Passes back len in case struct is a weird basic type (which means its vals are packed). + /// Verifies any struct in a list within the block_root. + /// If the values get packed together (a list of basic types), then extra post processing must be done. + /// See the balance function above + pub fn verify_struct_from_beacon_block_root( + &self, + ctx: &mut Context, + idx: AssignedValue, + ssz_struct: &dyn SszStruct, + proof: SSZInputAssigned, + list_path: Vec, + desired_depth: Option, + ) -> (AssignedValue, SSZInclusionWitness) { + let (list_len, witness) = + self.verify_hash_from_beacon_block_root(ctx, idx, proof, list_path, desired_depth); + let struct_root = ssz_struct.hash_root(ctx, self.ssz); + for i in 0..32 { + ctx.constrain_equal(&witness.val[i], &struct_root[i]); + } + (list_len, witness) + } + + /// MUST CONSTRAIN THE IDX IS VALID AFTERWARDS, HENCE WHY LIST_LEN IS RETURNED + pub fn verify_hash_from_beacon_block_root( + &self, + ctx: &mut Context, + idx: AssignedValue, + proof: SSZInputAssigned, + list_path: Vec, + desired_depth: Option, + ) -> (AssignedValue, SSZInclusionWitness) { + let list_path_bits = list_path.len(); + let max_depth = proof.proof.len(); + // list_depth describes the number of nodes past the the fixed directions + let list_depth = max_depth - list_path_bits - 1; + let zero = ctx.load_zero(); + let depth = proof.depth; + if let Some(_depth) = desired_depth { + let good_depth = ctx.load_constant(F::from(_depth as u64)); + ctx.constrain_equal(&good_depth, &depth); + } + // proof needs at least list_path + 1 nodes in order to be valid + let bad_depth = self.range().is_less_than_safe(ctx, depth, (list_path_bits + 1) as u64); + ctx.constrain_equal(&bad_depth, &zero); + // Constrain that we are actually looking in the correct list + for i in 0..list_path_bits { + let list_bit = ctx.load_constant(F::from(list_path[i] as u64)); + ctx.constrain_equal(&proof.directions[i], &list_bit); + } + // Constrain that we are going down the list values path + ctx.constrain_equal(&proof.directions[list_path_bits], &zero); + // Verify ssz_struct + let witness = self.ssz.verify_inclusion_proof(ctx, proof); + let fixed_bits_witness_plus_one = ctx.load_constant(F::from((list_path_bits + 2) as u64)); + let depth_adjusted = self.gate().sub(ctx, depth, fixed_bits_witness_plus_one); + let depth_adjusted_indicator = + self.gate().idx_to_indicator(ctx, depth_adjusted, list_depth); + // creates an indicator for all slots less than depth + let mut pre_depth = vec![zero; list_depth]; + pre_depth[list_depth - 1] = depth_adjusted_indicator[list_depth - 1]; + for i in 1..list_depth { + pre_depth[list_depth - 1 - i] = self.gate().add( + ctx, + pre_depth[list_depth - i], + depth_adjusted_indicator[list_depth - 1 - i], + ); + } + // constrains that the validator in question is at the claimed index within the list + let mut cum_idx = zero; + for i in 0..list_depth { + let mut new_val = + self.gate().add(ctx, witness.directions[i + list_path_bits + 1], cum_idx); + new_val = self.gate().mul(ctx, new_val, pre_depth[i]); + cum_idx = self.gate().add(ctx, new_val, cum_idx); + } + ctx.constrain_equal(&cum_idx, &idx); + // recall that the `list_path_bits`th node in the proof contains the length of the list + let mut list_len = zero; + let base = ctx.load_constant(F::from(256)); + for i in 0..32 { + list_len = self.gate().mul(ctx, list_len, base); + list_len = self.gate().add(ctx, list_len, witness.proof[list_path_bits][31 - i]); + } + (list_len, witness) + } +} diff --git a/axiom-eth/src/beacon/tests/balance_idx.json b/axiom-eth/src/beacon/tests/balance_idx.json new file mode 100644 index 00000000..34e80c4f --- /dev/null +++ b/axiom-eth/src/beacon/tests/balance_idx.json @@ -0,0 +1,3 @@ +{ + "idx": 1234 +} \ No newline at end of file diff --git a/axiom-eth/src/beacon/tests/balance_test.json b/axiom-eth/src/beacon/tests/balance_test.json new file mode 100644 index 00000000..1fc555aa --- /dev/null +++ b/axiom-eth/src/beacon/tests/balance_test.json @@ -0,0 +1,100 @@ +{ + "balance": 34456818396, + "idx": 1234, + "proof": { + "directions": [ + 0, + 1, + 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, + 1, + 0, + 0, + 1, + 1, + 0, + 1, + 0, + 0 + ], + "proof": [ + "c9242002acfdc02f45fe2197f073648c73c9a33a1452f6727ff499a537541b94", + "0aecf74c9bcd7832df78a3cc117be981e7c8eed81b11148a3de1435ebbd30daa", + "020203cf622c80bc5bd03100f35dc2cfb4d03af5e4342304db740ebfb1a9abe6", + "7d47f11373031d976b75248e77c6e36868496bae42207ee8e1d54bb43c410645", + "31f959d35ab20a59f65f1cad7ba25347b96091e8b2c032342660c02d060a4937", + "1fd2030000000000000000000000000000000000000000000000000000000000", + "55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", + "bfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", + "328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", + "2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d", + "1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5", + "c6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff", + "985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7", + "b5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "e71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "feb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "cddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "f893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "4fc35d1c91120034c5f2bb86b91e4b007745ade4550961267c81d9f935ef56ff", + "165862060354ae9c240d244cd61ed9593f8e2d0edb0a75d99ecde9db13adf489", + "b3569cd9670e3e96500df10229c0b99cab823602d6d9553ea3931466d9bf6d2f", + "14c9882049bac9308ef070c734678e962802cc9a830f88a8f9b23f4b419a0aee", + "ade9aea1622ff768f4d971aa359b897343c9a0e4315fd5d26bbb2b86e01c24d4", + "5477b0e39691a7d500074659f96a5454969ee2c964919f6f901ac9e6405bec2b", + "3118ac87dad59ddde5fa7fc32629d5be29ded163110c046adee09271ad3c6c41", + "31807b6c877e83ab521b53585c332ef319f99c94f24f0604d811cfe576ce2033", + "ecca8e75401f1407a20d62631bf418607204a5652c1d20159cbcbc66aaf41ee3", + "ff9fda1ac85ee3ad4277c69039aea63f089be618a14fb753d5ed2344467c7ce0", + "580672ba1e27521f0aa09564615a5e4d8cf7cd0de2d62c0b8770885e6eca07bc", + "f0e7013738613f3e6da5bd2bd9c9a2a9d40aeea27a6fd1257f06887da40207c3", + "3be9030b68edbf0f2ae536dd46248150d3ad277659c02cb7ae596ea37a4ff07c", + "e773ce2a47f6d9bc585605a0513ff46ba565f2415ca2f35d7df89355a0feef73", + "8f5395a44988a4c62b54c04cd0d205a47e8b6f14497f888e14df61a3d772c7ab", + "b7ddf70408000000b5e55b060800000015c4950608000000a01a420508000000" + ], + "root_bytes": "5b59dc93be660001b7744993091cb9017abde79adc96713fe890b659da46a6c8", + "val": "47fa250608000000c29c3a0408000000dc52c90508000000b7a4a60408000000" + } +} \ No newline at end of file diff --git a/axiom-eth/src/beacon/tests/balance_test_sandbox.json b/axiom-eth/src/beacon/tests/balance_test_sandbox.json new file mode 100644 index 00000000..9a50dd1c --- /dev/null +++ b/axiom-eth/src/beacon/tests/balance_test_sandbox.json @@ -0,0 +1,100 @@ +{ + "balance": 33494377856, + "idx": 100002, + "proof": { + "directions": [ + 0, + 1, + 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, + 1, + 1, + 0, + 0, + 0, + 0, + 1, + 1, + 0, + 1, + 0, + 1, + 0, + 0, + 0 + ], + "proof": [ + "c9242002acfdc02f45fe2197f073648c73c9a33a1452f6727ff499a537541b94", + "0aecf74c9bcd7832df78a3cc117be981e7c8eed81b11148a3de1435ebbd30daa", + "020203cf622c80bc5bd03100f35dc2cfb4d03af5e4342304db740ebfb1a9abe6", + "7d47f11373031d976b75248e77c6e36868496bae42207ee8e1d54bb43c410645", + "31f959d35ab20a59f65f1cad7ba25347b96091e8b2c032342660c02d060a4937", + "1fd2030000000000000000000000000000000000000000000000000000000000", + "55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", + "bfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", + "328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", + "2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d", + "1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5", + "c6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff", + "985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7", + "b5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "e71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "feb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "cddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "f893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "4fc35d1c91120034c5f2bb86b91e4b007745ade4550961267c81d9f935ef56ff", + "fc3221c6b67e17077fd292b65b5f2b835d8fbafbe140f8a142f3c848f312d850", + "164ae715c19243973e576730be7187faa0373ea93bde62b51c0825d3624b0ee7", + "ef0f8fce99448be53083abe4f386bea98d92c8b725806072fe3e0c26680df271", + "f60af362875e671d2c2cc93b1ef70e6df879fae66cf65f05be86ba933cde1970", + "d8fb941564b927b02b36c568d37cc66ed95a38f46f26af97cccaee6d5d9c68e1", + "ada28531e31e2beab3e5d07d9c0d7715cadd3bdfc3c836b3349233297bcc3372", + "627687df79cb1e6f6c1bb848c51983b79b1fdcb0414ba8e14d9f83ab0175412d", + "761c399d622d3b726e50d421df5e14a14e3513c5731ce35a7b18a9a9d4a340e3", + "ce95d1e9c664f2203fbb72e3459b6a6319fbf987fb3b3bb38ee572ccea73006d", + "e71a075073b2dc36c43a9c2a0e51e331fe58504acfb2ccf1c5d098b11e708d90", + "fd61e187fd5c7f6c2a99633abe524f2eb38ff0a050519fa0b9fcd6845cdc2de1", + "9e3dc7e4444fd095e1fe2498e4856d339e6defd2c494c5d1c0083c2560932d3b", + "07dd5ca7b0ae9fbf73f12f4e0596ae117101e0b7aaac0075da7e9dce0a976d7c", + "14c1a9ff30e0d34f93bc046f1ffa4647a1398968a5f7a2c09091093b1fc33ae3", + "f4df26cd0700000074b2cdcd070000009e1548cd070000009721d1cd07000000" + ], + "root_bytes": "5b59dc93be660001b7744993091cb9017abde79adc96713fe890b659da46a6c8", + "val": "088feecd0700000084bd6fcd0700000080a56bcc070000000dc79ccc07000000" + } +} \ No newline at end of file diff --git a/axiom-eth/src/beacon/tests/generated_tests/pair.json b/axiom-eth/src/beacon/tests/generated_tests/pair.json new file mode 100644 index 00000000..1e6b4084 --- /dev/null +++ b/axiom-eth/src/beacon/tests/generated_tests/pair.json @@ -0,0 +1,4 @@ +{ + "first": 1234, + "second": 12345 +} \ No newline at end of file diff --git a/axiom-eth/src/beacon/tests/generated_tests/validator.json b/axiom-eth/src/beacon/tests/generated_tests/validator.json new file mode 100644 index 00000000..79eb47dd --- /dev/null +++ b/axiom-eth/src/beacon/tests/generated_tests/validator.json @@ -0,0 +1,10 @@ +{ + "pubkey": "0xa2dc4fe34d4a934885578a52bbf33dd02367216b3472eb398b7fa3bc92909e29ac547cbcd813649650e419dfd797f46f", + "withdrawal_credentials": "0x003a058dc0b094fd2525966f9273a2c95b10bd84fe3e3227516a96e49335de76", + "effective_balance": "32000000000", + "slashed": false, + "activation_eligibility_epoch": "74210", + "activation_epoch": "18446744073709551615", + "exit_epoch": "18446744073709551615", + "withdrawable_epoch": "18446744073709551615" +} \ No newline at end of file diff --git a/axiom-eth/src/beacon/tests/info_idx.json b/axiom-eth/src/beacon/tests/info_idx.json new file mode 100644 index 00000000..8aa0c0a6 --- /dev/null +++ b/axiom-eth/src/beacon/tests/info_idx.json @@ -0,0 +1,3 @@ +{ + "idx": 100000 +} \ No newline at end of file diff --git a/axiom-eth/src/beacon/tests/info_test.json b/axiom-eth/src/beacon/tests/info_test.json new file mode 100644 index 00000000..d65dfa9e --- /dev/null +++ b/axiom-eth/src/beacon/tests/info_test.json @@ -0,0 +1,109 @@ +{ + "idx": 100000, + "proof": { + "directions": [ + 0, + 1, + 0, + 1, + 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, + 1, + 1, + 0, + 0, + 0, + 0, + 1, + 1, + 0, + 1, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "proof": [ + "c9242002acfdc02f45fe2197f073648c73c9a33a1452f6727ff499a537541b94", + "0aecf74c9bcd7832df78a3cc117be981e7c8eed81b11148a3de1435ebbd30daa", + "e03a9fe84575c25ab0fc8981b7e6adcdd67a15e52c4b95f64b548e532eea1606", + "96d20167d9625b7724b0b1cca28bfc5fffe6f03c80cdb411ffe2078330de5b52", + "04d3030000000000000000000000000000000000000000000000000000000000", + "1fd2030000000000000000000000000000000000000000000000000000000000", + "ad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f", + "f7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76", + "55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", + "bfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", + "328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", + "2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d", + "1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5", + "c6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff", + "985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7", + "b5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "e71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "feb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "cddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "f893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "1364984a688be918803722cc46da0483f33b02105d3b19c72e06cdc5e3d1bb31", + "e1518af615472210df2177339c4f51959b532d45008eda56c047e99246aa67e6", + "db2bcb2a0633bcf7b63708959a2a2a0fa3370fc63d6ea780e5306b73dc6f32ca", + "6c3abd9b1a9b8038ba789bc2d0add5ef282f5b90e22644be334371291917fb3e", + "52d641f3508244e195633deb51ae1a5ed439ba70ad1428571a813cb24c6f0a11", + "9c871d3c655960d20ec54f034f45d6828ed4807503db11b1ecad4cbec84f2bb0", + "ae210aa5919e13e4cb1d4f73d14318b6f3dcc1d22a26723349a7cb6403f333be", + "08a22a48b2886d6f6cf6cba9cec60228500e32025037c74eb22bac71b612bf45", + "5871483f8166b15a877ed71f32b448b5ec0d3e20cc099c7462e431c261dfcc23", + "fcaa065576f2517e9fe2738298f814142251a03ffa022e544285536575e145dd", + "a706fb83c07578db67bef8d6e9509481def6d0646d313526a19b1f509b5635e0", + "8635c0b697faefabe515d3451cdf744e37ce647853f8ea5d4524bf265434eda1", + "7f1a2fecccd31decaa3a908741fe499612b2c83b7e05e0a36369bb29ad641101", + "58455582195855e40d8456b87c960ecd698fb9970bf7b8c4ca076aff599623f2", + "e19fbe0a2ab7e7be465cf12e73dc3bcaa64502c4908d05f291c44a5d3957cb36", + "d13103514c3e260657d7d5e3bd825366bffc55e88274d1cd8ac3ea381637a334", + "079e600d5b64a2aa76bad4929f84ea7c5d01938f211e595d4221537014c65e70", + "9f80913a700727837ec8d566b1e2888ea58d58d12cb3c4260825b1bf77eec81f", + "7661cadf9aee4c79a90471141d22e1796f33e60eb4d5737629d81a631b0b1b40", + "19327cb9763c96e00332bde93bdbb1032c4b796dda73e515c8c5f7ede9a419be" + ], + "root_bytes": "5b59dc93be660001b7744993091cb9017abde79adc96713fe890b659da46a6c8", + "val": "bfde0d4e461fca90282fb1ac7ccb5ef75302eef04d45428e9488d79c65680003" + }, + "public_key": "0xa410d9d4dd37589027f858e99848f4d27400373e035ca52d98672dcffe91629523d8c7c17009572d6f9ccb0b523083bd", + "withdrawal_credentials": "0x001197afeeedb87615cdff5c4acdf18dfc24b80bdd30b9c8e8efa5f5ee6a715c" +} \ No newline at end of file diff --git a/axiom-eth/src/beacon/tests/info_test_sandbox.json b/axiom-eth/src/beacon/tests/info_test_sandbox.json new file mode 100644 index 00000000..9a4f00fa --- /dev/null +++ b/axiom-eth/src/beacon/tests/info_test_sandbox.json @@ -0,0 +1,109 @@ +{ + "idx": 100000, + "proof": { + "directions": [ + 0, + 1, + 0, + 1, + 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, + 1, + 1, + 0, + 0, + 0, + 0, + 1, + 1, + 0, + 1, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "proof": [ + "c9242002acfdc02f45fe2197f073648c73c9a33a1452f6727ff499a537541b94", + "0aecf74c9bcd7832df78a3cc117be981e7c8eed81b11148a3de1435ebbd30daa", + "e03a9fe84575c25ab0fc8981b7e6adcdd67a15e52c4b95f64b548e532eea1606", + "96d20167d9625b7724b0b1cca28bfc5fffe6f03c80cdb411ffe2078330de5b52", + "04d3030000000000000000000000000000000000000000000000000000000000", + "1fd2030000000000000000000000000000000000000000000000000000000000", + "ad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f", + "f7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76", + "55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", + "bfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", + "328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", + "2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d", + "1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5", + "c6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff", + "985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7", + "b5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "e71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "feb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "cddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "f893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "1364984a688be918803722cc46da0483f33b02105d3b19c72e06cdc5e3d1bb31", + "e1518af615472210df2177339c4f51959b532d45008eda56c047e99246aa67e6", + "db2bcb2a0633bcf7b63708959a2a2a0fa3370fc63d6ea780e5306b73dc6f32ca", + "6c3abd9b1a9b8038ba789bc2d0add5ef282f5b90e22644be334371291917fb3e", + "52d641f3508244e195633deb51ae1a5ed439ba70ad1428571a813cb24c6f0a11", + "9c871d3c655960d20ec54f034f45d6828ed4807503db11b1ecad4cbec84f2bb0", + "ae210aa5919e13e4cb1d4f73d14318b6f3dcc1d22a26723349a7cb6403f333be", + "08a22a48b2886d6f6cf6cba9cec60228500e32025037c74eb22bac71b612bf45", + "5871483f8166b15a877ed71f32b448b5ec0d3e20cc099c7462e431c261dfcc23", + "fcaa065576f2517e9fe2738298f814142251a03ffa022e544285536575e145dd", + "a706fb83c07578db67bef8d6e9509481def6d0646d313526a19b1f509b5635e0", + "8635c0b697faefabe515d3451cdf744e37ce647853f8ea5d4524bf265434eda1", + "7f1a2fecccd31decaa3a908741fe499612b2c83b7e05e0a36369bb29ad641101", + "58455582195855e40d8456b87c960ecd698fb9970bf7b8c4ca076aff599623f2", + "e19fbe0a2ab7e7be465cf12e73dc3bcaa64502c4908d05f291c44a5d3957cb36", + "d13103514c3e260657d7d5e3bd825366bffc55e88274d1cd8ac3ea381637a334", + "079e600d5b64a2aa76bad4929f84ea7c5d01938f211e595d4221537014c65e70", + "9f80913a700727837ec8d566b1e2888ea58d58d12cb3c4260825b1bf77eec81f", + "7661cadf9aee4c79a90471141d22e1796f33e60eb4d5737629d81a631b0b1b40", + "19327cb9763c96e00332bde93bdbb1032c4b796dda73e515c8c5f7ede9a419be" + ], + "root_bytes": "5b59dc93be660001b7744993091cb9017abde79adc96713fe890b659da46a6c8", + "val": "bfde0d4e461fca90282fb1ac7ccb5ef75302eef04d45428e9488d79c65680003" + }, + "public_key": "0xa410d9d4dd37589027f858e99848f4d27400373e035ca52d98672dcffe91629523d8c7c17009572d6f9ccb0b523083bd", + "withdrawal_credentials": "0x001197afeeedb87615cdff5c4acdf18dfc24b80bdd30b9c8e8efa5f5ee6a715c" +} \ No newline at end of file diff --git a/axiom-eth/src/beacon/tests/mod.rs b/axiom-eth/src/beacon/tests/mod.rs new file mode 100644 index 00000000..98a08992 --- /dev/null +++ b/axiom-eth/src/beacon/tests/mod.rs @@ -0,0 +1,244 @@ + +use halo2_base::halo2_proofs::halo2curves::bn256::Fr; +use serde_json::Value; + +use super::{ + data_gen::{get_balance_into_beacon, get_validator_info_into_beacon}, + types::SszUint64, + *, +}; +use crate::{ + providers::from_hex, + rlc::circuit::{builder::RlcCircuitBuilder, instructions::RlcCircuitInstructions}, + sha256::Sha256Chip, + ssz::{ + tests::test_mock_circuit, + types::{SszBasicType, SszBasicTypeVector}, + SSZInput, + }, +}; +use std::{ + fs::File, + io::{BufWriter, Write}, + marker::PhantomData, +}; + +#[derive(Clone, Debug)] +pub struct ValidatorInfoCircuit { + pub idx: usize, + pub proof: SSZInput, // public and private inputs + pub bls_pubkey: Vec, + pub withdrawal_creds: Vec, + pub max_depth: usize, + _marker: PhantomData, +} + +impl ValidatorInfoCircuit { + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "providers")] + pub fn from_input( + idx: usize, + proof: SSZInput, + bls_pubkey: Vec, + withdrawal_creds: Vec, + ) -> Self { + assert!(bls_pubkey.len() == 48); + assert!(withdrawal_creds.len() == 32); + let max_depth = proof.max_depth; + assert!(max_depth == 48); + Self { idx, proof, bls_pubkey, withdrawal_creds, max_depth, _marker: PhantomData } + } +} + +impl RlcCircuitInstructions for ValidatorInfoCircuit { + type FirstPhasePayload = (); + fn generate_witnesses_phase0( + &self, + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + ) -> Self::FirstPhasePayload { + let sha = Sha256Chip::new(range); + let ssz = SszChip::new(None, &range, sha); + let ctx = builder.base.main(0); + let proof = self.proof.clone().assign(ctx); + let bls_pubkey = self.bls_pubkey.iter().map(|b| *b as u64).collect_vec(); + let withdrawal_creds = self.withdrawal_creds.iter().map(|b| *b as u64).collect_vec(); + let bls_pubkey = SszBasicTypeVector::new_from_ints(ctx, &range, bls_pubkey, 8); + let withdrawal_creds = SszBasicTypeVector::new_from_ints(ctx, &range, withdrawal_creds, 8); + let info = ValidatorInfo::from(bls_pubkey, withdrawal_creds); + let beacon = BeaconChip::new(&ssz); + let idx = ctx.load_witness(F::from(self.idx as u64)); + let _witness = beacon.verify_validator_info_from_beacon_block_root(ctx, idx, &info, proof); + } + fn generate_witnesses_phase1( + _builder: &mut RlcCircuitBuilder, + _range: &RangeChip, + _rlc: &RlcChip, + _payload: Self::FirstPhasePayload, + ) { + } +} + +#[derive(Clone, Debug)] +pub struct BalanceCircuit { + pub idx: usize, + pub proof: SSZInput, // public and private inputs + pub balance: u64, + pub max_depth: usize, + _marker: PhantomData, +} + +impl BalanceCircuit { + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "providers")] + pub fn from_input(idx: usize, proof: SSZInput, balance: u64) -> Self { + let max_depth = proof.max_depth; + assert!(max_depth == 44); + Self { idx, proof, balance, max_depth, _marker: PhantomData } + } +} + +impl RlcCircuitInstructions for BalanceCircuit { + type FirstPhasePayload = (); + fn generate_witnesses_phase0( + &self, + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + ) -> Self::FirstPhasePayload { + let sha = Sha256Chip::new(range); + let ssz = SszChip::new(None, &range, sha); + let ctx = builder.base.main(0); + let proof = self.proof.clone().assign(ctx); + let balance = SszBasicType::new_from_int(ctx, &range, self.balance, 64); + let balance = SszUint64::from(balance); + let beacon = BeaconChip::new(&ssz); + let idx_div_4 = ctx.load_witness(F::from(self.idx as u64 / 4)); + let idx_mod_4 = ctx.load_witness(F::from(self.idx as u64 % 4)); + let idx = ctx.load_witness(F::from(self.idx as u64)); + let (_idx, _len, _witness) = beacon + .verify_balance_from_beacon_block_root(ctx, idx, idx_div_4, idx_mod_4, &balance, proof); + } + fn generate_witnesses_phase1( + _builder: &mut RlcCircuitBuilder, + _range: &RangeChip, + _rlc: &RlcChip, + _payload: Self::FirstPhasePayload, + ) { + } +} + +fn get_test_info_circuit( + idx: usize, + proof: SSZInput, + bls_pubkey: Vec, + withdrawal_creds: Vec, +) -> ValidatorInfoCircuit { + ValidatorInfoCircuit::from_input(idx, proof, bls_pubkey, withdrawal_creds) +} + +fn get_test_balance_circuit(idx: usize, proof: SSZInput, balance: u64) -> BalanceCircuit { + BalanceCircuit::from_input(idx, proof, balance) +} + +pub fn test_info_valid_input_json(path: String) -> bool { + let pf_str = std::fs::read_to_string(path).unwrap(); + let overall_pf: serde_json::Value = serde_json::from_str(pf_str.as_str()).unwrap(); + let idx: usize = serde_json::from_value(overall_pf["idx"].clone()).unwrap(); + let bls: String = serde_json::from_value(overall_pf["public_key"].clone()).unwrap(); + let bls = Vec::::try_from(from_hex(&bls[2..])).unwrap(); + let wc: String = serde_json::from_value(overall_pf["withdrawal_credentials"].clone()).unwrap(); + let wc = Vec::::try_from(from_hex(&wc[2..])).unwrap(); + let pf = overall_pf["proof"].clone(); + let val: String = serde_json::from_value(pf["val"].clone()).unwrap(); + let val = from_hex(&val); + let root_bytes: String = serde_json::from_value(pf["root_bytes"].clone()).unwrap(); + let root_bytes = from_hex(&root_bytes); + let pf_strs: Vec = serde_json::from_value(pf["proof"].clone()).unwrap(); + let proof: Vec> = pf_strs.into_iter().map(|pf| from_hex(&pf)).collect(); + let directions: Vec = serde_json::from_value(pf["directions"].clone()).unwrap(); + let depth = proof.len(); + let proof = SSZInput { root_bytes, val, proof, directions, depth: 48, max_depth: depth }; + let input = get_test_info_circuit(idx, proof, bls, wc); + test_mock_circuit(input) +} + +pub fn test_balance_valid_input_json(path: String) -> bool { + let pf_str = std::fs::read_to_string(path).unwrap(); + let overall_pf: serde_json::Value = serde_json::from_str(pf_str.as_str()).unwrap(); + let idx: usize = serde_json::from_value(overall_pf["idx"].clone()).unwrap(); + let balance: u64 = serde_json::from_value(overall_pf["balance"].clone()).unwrap(); + let pf = overall_pf["proof"].clone(); + let val: String = serde_json::from_value(pf["val"].clone()).unwrap(); + let val = from_hex(&val); + let root_bytes: String = serde_json::from_value(pf["root_bytes"].clone()).unwrap(); + let root_bytes = from_hex(&root_bytes); + let pf_strs: Vec = serde_json::from_value(pf["proof"].clone()).unwrap(); + let proof: Vec> = pf_strs.into_iter().map(|pf| from_hex(&pf)).collect(); + let directions: Vec = serde_json::from_value(pf["directions"].clone()).unwrap(); + let depth = proof.len(); + let proof = SSZInput { root_bytes, val, proof, directions, depth: 44, max_depth: depth }; + let input = get_test_balance_circuit(idx, proof, balance); + test_mock_circuit(input) +} + +#[test] +pub fn test_select_validator_info_proof() -> Result<(), Box> { + let idx = std::fs::read_to_string("src/beacon/tests/info_idx.json").unwrap(); + let idx: serde_json::Value = serde_json::from_str(idx.as_str()).unwrap(); + let idx: usize = serde_json::from_value(idx["idx"].clone()).unwrap(); + gen_data_and_test_select_validator_info_proof(idx) +} + +pub fn gen_data_and_test_select_validator_info_proof( + idx: usize, +) -> Result<(), Box> { + let map = get_validator_info_into_beacon(idx); + let proof = Value::Object(map); + let file = File::create("src/beacon/tests/info_test.json").unwrap(); + let mut writer = BufWriter::new(file); + let _ = serde_json::to_writer_pretty(&mut writer, &proof); + let _ = writer.flush(); + match test_info_valid_input_json("src/beacon/tests/info_test.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_mock_validator_info_proof() -> Result<(), Box> { + match test_info_valid_input_json("src/beacon/tests/info_test_sandbox.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_select_balance_proof() -> Result<(), Box> { + let idx = std::fs::read_to_string("src/beacon/tests/balance_idx.json").unwrap(); + let idx: serde_json::Value = serde_json::from_str(idx.as_str()).unwrap(); + let idx: usize = serde_json::from_value(idx["idx"].clone()).unwrap(); + gen_data_and_test_select_balance_proof(idx) +} + +pub fn gen_data_and_test_select_balance_proof( + idx: usize, +) -> Result<(), Box> { + let map = get_balance_into_beacon(idx); + let proof = Value::Object(map); + let file = File::create("src/beacon/tests/balance_test.json").unwrap(); + let mut writer = BufWriter::new(file); + let _ = serde_json::to_writer_pretty(&mut writer, &proof); + let _ = writer.flush(); + match test_balance_valid_input_json("src/beacon/tests/balance_test.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_mock_balance_proof() -> Result<(), Box> { + match test_balance_valid_input_json("src/beacon/tests/balance_test_sandbox.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} diff --git a/axiom-eth/src/beacon/tests/readme.md b/axiom-eth/src/beacon/tests/readme.md new file mode 100644 index 00000000..6db8f554 --- /dev/null +++ b/axiom-eth/src/beacon/tests/readme.md @@ -0,0 +1,9 @@ +Run the following to generate the data necessary for tests (too large to store in git): + +``` +cargo t test_beacon_state_and_validators_and_balances +cargo t get_all_validators_root +cargo t get_all_balances_root +``` + +You will need a [nodereal](nodereal.io) API key set to `NODEREAL_ID` env var. diff --git a/axiom-eth/src/beacon/types.rs b/axiom-eth/src/beacon/types.rs new file mode 100644 index 00000000..b7271e2c --- /dev/null +++ b/axiom-eth/src/beacon/types.rs @@ -0,0 +1,157 @@ +use crate::Field; +use halo2_base::{gates::range::RangeChip, utils::ScalarField, Context}; + +use crate::{ + mpt::AssignedBytes, + ssz::{ + types::SszStruct, + types::{Chunk, SszBasicType, SszBasicTypeVector}, + SszChip, + }, +}; + +pub type Gwei = SszUint64; +pub type Epoch = SszUint64; +/// Wrapper for SszBasicType for uint64 +#[derive(Debug, Clone)] +pub struct SszUint64 { + val: SszBasicType, +} + +impl SszUint64 { + pub fn from(val: SszBasicType) -> Self { + Self { val } + } + pub fn from_int(ctx: &mut Context, range: &RangeChip, val: u64) -> Self { + let val = SszBasicType::new_from_int(ctx, range, val, 64); + Self { val } + } + pub fn new(ctx: &mut Context, range: &mut RangeChip, value: AssignedBytes) -> Self { + assert!(value.len() == 8); + let val = SszBasicType::new(ctx, range, value, 64); + Self { val } + } + pub fn val(&self) -> &SszBasicType { + &self.val + } +} + +impl SszStruct for SszUint64 { + fn hash_root(&self, ctx: &mut Context, ssz: &SszChip) -> Chunk { + return self.val.hash_root(ctx, ssz); + } +} + +#[derive(Debug, Clone)] +pub struct Validator { + bls_pub_key: SszBasicTypeVector, + withdrawal_creds: SszBasicTypeVector, + effective_balance: Gwei, + slashed: SszBasicType, + activation_eligibility_epoch: Epoch, + activation_epoch: Epoch, + exit_epoch: Epoch, + withdrawable_epoch: Epoch, +} + +impl SszStruct for Validator { + fn hash_root(&self, ctx: &mut Context, ssz: &SszChip) -> Chunk { + let ae_root = ssz.basic_type_hash_tree_root(ctx, &self.activation_epoch.val); + let aee_root = ssz.basic_type_hash_tree_root(ctx, &self.activation_eligibility_epoch.val); + let ee_root = ssz.basic_type_hash_tree_root(ctx, &self.exit_epoch.val); + let we_root = ssz.basic_type_hash_tree_root(ctx, &self.withdrawable_epoch.val); + let bls_root = ssz.basic_type_vector_hash_tree_root(ctx, &self.bls_pub_key); + let wc_root = ssz.basic_type_vector_hash_tree_root(ctx, &self.withdrawal_creds); + let s_root = ssz.basic_type_hash_tree_root(ctx, &self.slashed); + let eb_root = ssz.basic_type_hash_tree_root(ctx, &self.effective_balance.val); + let chunks = vec![bls_root, wc_root, eb_root, s_root, aee_root, ae_root, ee_root, we_root]; + ssz.merkleize(ctx, chunks) + } +} + +impl Validator { + pub fn from( + bls_pub_key: SszBasicTypeVector, + withdrawal_creds: SszBasicTypeVector, + effective_balance: Gwei, + slashed: SszBasicType, + activation_eligibility_epoch: Epoch, + activation_epoch: Epoch, + exit_epoch: Epoch, + withdrawable_epoch: Epoch, + ) -> Self { + assert!(bls_pub_key.int_bit_size() == 8); + assert!(withdrawal_creds.int_bit_size() == 8); + assert!(slashed.int_bit_size() == 1); + assert!(bls_pub_key.values().len() == 48); + assert!(withdrawal_creds.values().len() == 32); + Self { + bls_pub_key, + withdrawal_creds, + effective_balance, + slashed, + activation_eligibility_epoch, + activation_epoch, + exit_epoch, + withdrawable_epoch, + } + } + pub fn bls_pub_key(self) -> SszBasicTypeVector { + self.bls_pub_key + } + pub fn withdrawal_creds(self) -> SszBasicTypeVector { + self.withdrawal_creds + } + pub fn effective_balance(self) -> Gwei { + self.effective_balance + } + pub fn slashed(self) -> SszBasicType { + self.slashed + } + pub fn activation_eligibility_epoch(self) -> Epoch { + self.activation_eligibility_epoch + } + pub fn activation_epoch(self) -> Epoch { + self.activation_epoch + } + pub fn exit_epoch(self) -> Epoch { + self.exit_epoch + } + pub fn withdrawable_epoch(self) -> Epoch { + self.withdrawable_epoch + } +} + +#[derive(Debug, Clone)] +pub struct ValidatorInfo { + bls_pubkey: SszBasicTypeVector, + withdrawal_creds: SszBasicTypeVector, +} + +impl SszStruct for ValidatorInfo { + fn hash_root(&self, ctx: &mut Context, ssz: &SszChip) -> Chunk { + let bls_root = ssz.basic_type_vector_hash_tree_root(ctx, &self.bls_pubkey); + let wc_root = ssz.basic_type_vector_hash_tree_root(ctx, &self.withdrawal_creds); + let chunks = vec![bls_root, wc_root]; + ssz.merkleize(ctx, chunks) + } +} + +impl ValidatorInfo { + pub fn from( + bls_pubkey: SszBasicTypeVector, + withdrawal_creds: SszBasicTypeVector, + ) -> Self { + assert!(bls_pubkey.int_bit_size() == 8); + assert!(withdrawal_creds.int_bit_size() == 8); + assert!(bls_pubkey.values().len() == 48); + assert!(withdrawal_creds.values().len() == 32); + Self { bls_pubkey, withdrawal_creds } + } + pub fn bls_pub_key(self) -> SszBasicTypeVector { + self.bls_pubkey + } + pub fn withdrawal_creds(self) -> SszBasicTypeVector { + self.withdrawal_creds + } +} diff --git a/axiom-eth/src/block_header/mod.rs b/axiom-eth/src/block_header/mod.rs new file mode 100644 index 00000000..3693da6e --- /dev/null +++ b/axiom-eth/src/block_header/mod.rs @@ -0,0 +1,578 @@ +use crate::Field; +use crate::{ + keccak::{types::KeccakVarLenQuery, KeccakChip}, + rlc::{ + chip::RlcChip, + circuit::builder::{RlcCircuitBuilder, RlcContextPair}, + types::RlcTrace, + }, + rlp::{ + evaluate_byte_array, max_rlp_encoding_len, + types::{RlpArrayWitness, RlpFieldTrace, RlpFieldWitness}, + RlpChip, + }, + utils::{bytes_be_to_u128, AssignedH256}, +}; +use core::iter::once; +use ethers_core::types::Chain; +use halo2_base::{ + gates::{ + flex_gate::threads::parallelize_core, GateChip, GateInstructions, RangeChip, + RangeInstructions, + }, + safe_types::{left_pad_var_array_to_fixed, FixLenBytes, SafeTypeChip}, + AssignedValue, Context, + QuantumCell::{Constant, Existing}, +}; +use itertools::Itertools; + +#[cfg(test)] +mod tests; + +// extra data max byte length is different for different networks +pub const MAINNET_EXTRA_DATA_MAX_BYTES: usize = 32; +pub const GOERLI_EXTRA_DATA_MAX_BYTES: usize = 300; + +/// This is the minimum possible RLP byte length of a block header *at any block* (including pre EIPs) +pub const BLOCK_HEADER_RLP_MIN_BYTES: usize = 479; + +pub const MIN_NUM_BLOCK_HEADER_FIELDS: usize = 15; +pub const NUM_BLOCK_HEADER_FIELDS: usize = 20; +pub const MAINNET_HEADER_FIELDS_MAX_BYTES: [usize; NUM_BLOCK_HEADER_FIELDS] = [ + 32, // parent_hash + 32, // ommers_hash + 20, // coinbase [beneficiary] + 32, // state_root + 32, // txs_root + 32, // receipts_root + 256, // logs_bloom + 7, // difficulty + 4, // number + 4, // gas_limit + 4, // gas_used + 4, // timestamp + MAINNET_EXTRA_DATA_MAX_BYTES, // extradata + 32, // mix_hash or prev_randao + 8, // nonce + 32, // base_fee_per_gas + 32, // withdrawals_root + 8, // data_gas_used + 8, // excess_data_gas + 32, // parent_beacon_block_root +]; +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, true, 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[BLOCK_NUMBER_INDEX]; +pub const STATE_ROOT_INDEX: usize = 3; +pub const TX_ROOT_INDEX: usize = 4; +pub const RECEIPT_ROOT_INDEX: usize = 5; +pub const LOGS_BLOOM_INDEX: usize = 6; +pub const BLOCK_NUMBER_INDEX: usize = 8; +pub const EXTRA_DATA_INDEX: usize = 12; +pub const WITHDRAWALS_ROOT_INDEX: usize = 16; + +/** +| 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, <=32 | <= 33 | <= 264 | +| withdrawalsRoot (post-4895) | 256 bits | 32 | 33 | 264 | +| blobGasUsed (post-4844) | 64 bits | <= 8 | <= 9 | <= 72 | +| excessBlobGas (post-4844) | 64 bits | <= 8 | <= 9 | <= 72 | +| parentBeaconBlockRoot (post-4788) | 256 bits | 32 | 33 | 264 | +*/ +#[allow(dead_code)] +#[derive(Clone, Debug)] +pub struct EthBlockHeaderTrace { + // pub rlp_trace: RlcTrace, + pub parent_hash: RlpFieldTrace, + pub ommers_hash: RlpFieldTrace, + pub beneficiary: RlpFieldTrace, + pub state_root: RlpFieldTrace, + pub transactions_root: RlpFieldTrace, + pub receipts_root: RlpFieldTrace, + pub logs_bloom: RlpFieldTrace, + pub difficulty: RlpFieldTrace, + pub number: RlpFieldTrace, + pub gas_limit: RlpFieldTrace, + pub gas_used: RlpFieldTrace, + pub timestamp: RlpFieldTrace, + pub extra_data: RlpFieldTrace, + pub mix_hash: RlpFieldTrace, + pub nonce: RlpFieldTrace, + pub basefee: RlpFieldTrace, // this is 0 (or undefined) for pre-EIP1559 (London) blocks + pub withdrawals_root: RlpFieldTrace, // this is 0 (or undefined) for pre-EIP4895 (Shapella) blocks (before block number 1681338455) + // the user will have to separately determine whether the block is EIP1559 or not + pub block_hash: KeccakVarLenQuery, + + // pub prefix: AssignedValue, + pub len_trace: RlcTrace, +} + +#[derive(Clone, Debug)] +pub struct EthBlockHeaderWitness { + pub rlp_witness: RlpArrayWitness, + pub block_hash: KeccakVarLenQuery, +} + +impl EthBlockHeaderWitness { + /// Returns block number as bytes4 (left padded with zeros, big endian) + pub fn get_number_fixed( + &self, + ctx: &mut Context, + gate: &impl GateInstructions, + ) -> FixLenBytes { + let block_num_bytes = &self.get_number().field_cells; + let block_num_len = self.get_number().field_len; + SafeTypeChip::unsafe_to_fix_len_bytes( + left_pad_var_array_to_fixed( + ctx, + gate, + block_num_bytes, + block_num_len, + BLOCK_NUMBER_MAX_BYTES, + ) + .try_into() + .unwrap(), + ) + } + /// Returns block number as a field element + pub fn get_number_value( + &self, + ctx: &mut Context, + gate: &impl GateInstructions, + ) -> AssignedValue { + let block_num_bytes = &self.get_number().field_cells; + let block_num_len = self.get_number().field_len; + evaluate_byte_array(ctx, gate, block_num_bytes, block_num_len) + } + pub fn get_block_hash_hi_lo(&self) -> AssignedH256 { + self.block_hash.hi_lo() + } + 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) + } + /// Returns the number of fields in the block header + pub fn get_list_len(&self) -> AssignedValue { + self.rlp_witness.list_len.unwrap() + } +} + +pub struct EthBlockHeaderChip<'chip, F: Field> { + pub rlp: RlpChip<'chip, F>, + pub max_extra_data_bytes: usize, +} + +impl<'chip, F: Field> EthBlockHeaderChip<'chip, F> { + pub fn new(rlp: RlpChip<'chip, F>, max_extra_data_bytes: usize) -> Self { + Self { rlp, max_extra_data_bytes } + } + + pub fn new_from_network(rlp: RlpChip<'chip, F>, chain: Chain) -> Self { + let max_extra_data_bytes = get_block_header_extra_bytes(chain); + Self { rlp, max_extra_data_bytes } + } + + pub fn gate(&self) -> &GateChip { + self.rlp.gate() + } + + pub fn range(&self) -> &RangeChip { + self.rlp.range() + } + + pub fn rlc(&self) -> &RlcChip { + self.rlp.rlc() + } + + pub fn rlp(&self) -> &RlpChip { + &self.rlp + } + + /// Takes the variable length RLP encoded block header, padded with 0s to the maximum possible block header RLP length, and outputs the decomposition into block header fields. + /// + /// This function _will_ range check that `block_header` consists of bytes (8 bits). + /// + /// In addition, the keccak block hash of the block is calculated. + /// + /// This is the preparation step that computes the witnesses. This MUST be done in `FirstPhase`. + /// The accompanying `decompose_block_header_phase1` must be called in `SecondPhase` to constrain the RLCs associated to the RLP decoding. + pub fn decompose_block_header_phase0( + &self, + ctx: &mut Context, // ctx_gate in FirstPhase + keccak: &KeccakChip, + block_header: &[AssignedValue], + ) -> EthBlockHeaderWitness { + let (max_len, max_field_lens) = + get_block_header_rlp_max_lens_from_extra(self.max_extra_data_bytes); + assert_eq!(block_header.len(), max_len); + // range check that block_header consists of bytes + for b in block_header { + self.range().range_check(ctx, *b, 8); + } + + let rlp_witness = self.rlp().decompose_rlp_array_phase0( + ctx, + block_header.to_vec(), + &max_field_lens, + true, + ); // `is_variable_len = true` because RLP can have >=15 fields, depending on which EIPs are active at that block + + let block_hash = keccak.keccak_var_len( + ctx, + rlp_witness.rlp_array.clone(), // this is `block_header_assigned` + rlp_witness.rlp_len, + BLOCK_HEADER_RLP_MIN_BYTES, + ); + EthBlockHeaderWitness { rlp_witness, block_hash } + } + + /// Takes the variable length RLP encoded block header, padded with 0s to the maximum possible block header RLP length, and outputs the decomposition into block header fields. + /// + /// In addition, the keccak block hash of the block is calculated. + /// + /// This is the finalization step that constrains RLC concatenations. + /// This should be called after `decompose_block_header_phase0`. + /// This MUST be done in `SecondPhase`. + pub fn decompose_block_header_phase1( + &self, + ctx: RlcContextPair, + witness: EthBlockHeaderWitness, + ) -> EthBlockHeaderTrace { + let trace = self.rlp().decompose_rlp_array_phase1(ctx, witness.rlp_witness, true); + let block_hash = witness.block_hash; + + // Base fee per unit gas only after London + let [parent_hash, ommers_hash, beneficiary, state_root, transactions_root, receipts_root, logs_bloom, difficulty, number, gas_limit, gas_used, timestamp, extra_data, mix_hash, nonce, basefee, withdrawals_root, ..]: [RlpFieldTrace; NUM_BLOCK_HEADER_FIELDS] = + trace.field_trace.try_into().unwrap(); + EthBlockHeaderTrace { + parent_hash, + ommers_hash, + beneficiary, + state_root, + transactions_root, + receipts_root, + logs_bloom, + difficulty, + number, + gas_limit, + gas_used, + timestamp, + extra_data, + mix_hash, + nonce, + basefee, + withdrawals_root, + block_hash, + len_trace: trace.len_trace, + } + } + + /// Makes multiple calls to `decompose_block_header_phase0` in parallel threads. Should be called in FirstPhase. + pub fn decompose_block_headers_phase0( + &self, + builder: &mut RlcCircuitBuilder, + keccak: &KeccakChip, + block_headers: Vec>>, + ) -> Vec> { + parallelize_core(builder.base.pool(0), block_headers, |ctx, block_header| { + self.decompose_block_header_phase0(ctx, keccak, &block_header) + }) + } + + /// Makes multiple calls to `decompose_block_header_phase1` in parallel threads. Should be called in SecondPhase. + pub fn decompose_block_headers_phase1( + &self, + builder: &mut RlcCircuitBuilder, + witnesses: Vec>, + ) -> Vec> { + assert!(!witnesses.is_empty()); + // `load_rlc_cache` no longer called here: it should be called globally when `RlcCircuitBuilder` is constructed + builder.parallelize_phase1(witnesses, |(ctx_gate, ctx_rlc), witness| { + self.decompose_block_header_phase1((ctx_gate, ctx_rlc), witness) + }) + } + + /// Takes a list of (purported) RLP encoded block headers and + /// decomposes each header into it's fields. + /// `headers[0]` is the earliest block. + /// + /// This is the preparation step that computes the witnesses. This MUST be done in `FirstPhase`. + /// The accompanying `decompose_block_header_chain_phase1` must be called in `SecondPhase` to constrain the RLCs associated to the RLP decoding. + pub fn decompose_block_header_chain_phase0( + &self, + builder: &mut RlcCircuitBuilder, + keccak: &KeccakChip, + block_headers: Vec>>, + ) -> Vec> { + self.decompose_block_headers_phase0(builder, keccak, block_headers) + } + + /// Takes a list of `2^max_depth` (purported) RLP encoded block headers. + /// Decomposes each header into it's fields. + /// `headers[0]` is the earliest block + /// + /// - If `num_blocks_minus_one = (num_blocks_minus_one, indicator)` is not None, then the circuit checks that the first `num_blocks := num_blocks_minus_one + 1` block headers form a chain: meaning that the parent hash of block i + 1 equals the hash of block i. + /// - `indicator` is a vector with index `i` equal to `i == num_blocks - 1 ? 1 : 0`. + /// - Otherwise if `num_blocks` is None, the circuit checks that all `headers` form a hash chain. + /// + /// Assumes that `0 <= num_blocks_minus_one < 2^max_depth`. + /// + /// This is the finalization step that constrains RLC concatenations. In this step the hash chain is actually constrained. + /// This should be called after `decompose_block_header_chain_phase0`. + /// This MUST be done in `SecondPhase`. + pub fn decompose_block_header_chain_phase1( + &self, + builder: &mut RlcCircuitBuilder, + witnesses: Vec>, + num_blocks_minus_one: Option<(AssignedValue, Vec>)>, + ) -> Vec> { + assert!(!witnesses.is_empty()); + let traces = self.decompose_block_headers_phase1(builder, witnesses); + let (ctx_gate, ctx_rlc) = builder.rlc_ctx_pair(); + let thirty_two = F::from(32); + // record for each idx whether hash of headers[idx] is in headers[idx + 1] + if let Some((num_blocks_minus_one, indicator)) = num_blocks_minus_one { + let mut hash_checks = Vec::with_capacity(traces.len() - 1); + for idx in 0..traces.len() - 1 { + let block_hash_bytes = traces[idx].block_hash.output_bytes.as_ref().iter().copied(); + let block_hash = self.rlc().compute_rlc_fixed_len(ctx_rlc, block_hash_bytes); + let hash_check = self.gate().is_equal( + ctx_gate, + block_hash.rlc_val, + traces[idx + 1].parent_hash.field_trace.rlc_val, + ); + hash_checks.push(hash_check); + self.gate().assert_is_const( + ctx_gate, + &traces[idx + 1].parent_hash.field_trace.len, + &thirty_two, + ); + } + let hash_check_sums = + self.gate().partial_sums(ctx_gate, hash_checks.iter().copied()).collect_vec(); + let hash_check_sum = self.gate().select_by_indicator( + ctx_gate, + once(Constant(F::ZERO)).chain(hash_check_sums.into_iter().map(Existing)), + indicator, + ); + ctx_gate.constrain_equal(&hash_check_sum, &num_blocks_minus_one); + } else { + for idx in 0..traces.len() - 1 { + let block_hash_bytes = traces[idx].block_hash.output_bytes.as_ref().iter().copied(); + let block_hash = self.rlc().compute_rlc_fixed_len(ctx_rlc, block_hash_bytes); + ctx_gate.constrain_equal( + &block_hash.rlc_val, + &traces[idx + 1].parent_hash.field_trace.rlc_val, + ); + self.gate().assert_is_const( + ctx_gate, + &traces[idx + 1].parent_hash.field_trace.len, + &thirty_two, + ); + } + } + + traces + } +} + +/// Given a block header chain `chain` of fixed length `2^max_depth`, returns +/// ``` +/// (prev_block_hash, end_block_hash, start_block_number || end_block_number) +/// ``` +/// where +/// ``` +/// prev_block_hash = chain[0].parent_hash, +/// end_block_hash = chain[num_blocks_minus_one].block_hash, +/// start_block_number = chain[0].number, +/// end_block_number = chain[num_blocks_minus_one].number +/// ``` +/// The hashes are H256 that are represented as two u128 +/// (we need them in 128 bits to fit in Bn254 scalar field). +/// +/// The numbers are left padded by zeros to be exactly 4 bytes (u32); the two padded numbers are concatenated together to a u64. +/// +/// `indicator` is the indicator for `num_blocks_minus_one`, where `indicator[i] = (i == end_block_number - start_block_number ? 1 : 0)`. +/// +/// This function should be called in `FirstPhase`. +pub fn get_boundary_block_data( + ctx: &mut Context, // ctx_gate in FirstPhase + gate: &impl GateInstructions, + chain: &[EthBlockHeaderWitness], + indicator: &[AssignedValue], +) -> ([AssignedValue; 2], [AssignedValue; 2], AssignedValue) { + let parent_hash_bytes = SafeTypeChip::unsafe_to_fix_len_bytes_vec( + chain[0].get_parent_hash().field_cells.clone(), + 32, + ); + let prev_block_hash: [_; 2] = + bytes_be_to_u128(ctx, gate, parent_hash_bytes.bytes()).try_into().unwrap(); + let end_block_hash: [_; 2] = [0, 1].map(|idx| { + gate.select_by_indicator( + ctx, + chain.iter().map(|header| header.block_hash.hi_lo()[idx]), + indicator.iter().copied(), + ) + }); + + // start_block_number || end_block_number + let block_numbers = { + debug_assert_eq!(chain[0].get_number().max_field_len, BLOCK_NUMBER_MAX_BYTES); + let start_block_number_bytes = chain[0].get_number_fixed(ctx, gate); + let end_block_number_bytes = { + // TODO: is there a way to do this without so many selects + let bytes: [_; BLOCK_NUMBER_MAX_BYTES] = core::array::from_fn(|i| i).map(|idx| { + gate.select_by_indicator( + ctx, + chain.iter().map(|header| header.get_number().field_cells[idx]), + indicator.iter().copied(), + ) + }); + let len = gate.select_by_indicator( + ctx, + chain.iter().map(|header| header.get_number().field_len), + indicator.iter().copied(), + ); + let var_bytes = SafeTypeChip::unsafe_to_var_len_bytes(bytes, len); + var_bytes.left_pad_to_fixed(ctx, gate) + }; + let block_numbers_bytes = + [start_block_number_bytes.into_bytes(), end_block_number_bytes.into_bytes()].concat(); + let [block_numbers]: [_; 1] = + bytes_be_to_u128(ctx, gate, &block_numbers_bytes).try_into().unwrap(); + block_numbers + }; + + (prev_block_hash, end_block_hash, block_numbers) +} + +pub fn get_block_header_rlp_max_lens(chain: Chain) -> (usize, [usize; NUM_BLOCK_HEADER_FIELDS]) { + get_block_header_rlp_max_lens_from_chain_id(chain as u64) +} + +pub fn get_block_header_rlp_max_lens_from_extra( + max_extra_data_bytes: usize, +) -> (usize, [usize; NUM_BLOCK_HEADER_FIELDS]) { + let mut field_lens = [0usize; NUM_BLOCK_HEADER_FIELDS]; + for (a, b) in field_lens.iter_mut().zip_eq(MAINNET_HEADER_FIELDS_MAX_BYTES.iter()) { + *a = *b; + } + field_lens[EXTRA_DATA_INDEX] = max_extra_data_bytes; + let mut list_payload_len = 0; + for &field_len in &field_lens { + list_payload_len += max_rlp_encoding_len(field_len); + } + let rlp_len = max_rlp_encoding_len(list_payload_len); + (rlp_len, field_lens) +} + +pub fn get_block_header_extra_bytes(chain: Chain) -> usize { + get_block_header_extra_bytes_from_chain_id(chain as u64) +} + +pub fn get_block_header_rlp_max_lens_from_chain_id( + chain_id: u64, +) -> (usize, [usize; NUM_BLOCK_HEADER_FIELDS]) { + let max_extra_data_bytes = get_block_header_extra_bytes_from_chain_id(chain_id); + get_block_header_rlp_max_lens_from_extra(max_extra_data_bytes) +} + +pub fn get_block_header_extra_bytes_from_chain_id(chain_id: u64) -> usize { + match chain_id { + 5 => GOERLI_EXTRA_DATA_MAX_BYTES, + _ => MAINNET_EXTRA_DATA_MAX_BYTES, // for now everything besides Goerli assumed to be mainnet equivalent + } +} + +/// RLP of block number 0 on mainnet +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, +]; diff --git a/axiom-eth/src/block_header/tests.rs b/axiom-eth/src/block_header/tests.rs new file mode 100644 index 00000000..3b837dfc --- /dev/null +++ b/axiom-eth/src/block_header/tests.rs @@ -0,0 +1,192 @@ +use crate::{ + mpt::MPTChip, + rlc::{circuit::RlcCircuitParams, tests::get_rlc_params}, + utils::{ + assign_vec, + eth_circuit::{create_circuit, EthCircuitImpl, EthCircuitInstructions}, + }, +}; + +use super::*; +use ark_std::{end_timer, start_timer}; +use halo2_base::{ + gates::circuit::CircuitBuilderStage, + halo2_proofs::{ + dev::MockProver, + halo2curves::bn256::{Bn256, Fr}, + plonk::*, + poly::kzg::commitment::ParamsKZG, + }, + utils::testing::{check_proof_with_instances, gen_proof_with_instances}, +}; +use hex::FromHex; +use rand_core::OsRng; +use std::marker::PhantomData; +use test_log::test; + +/// 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) +const MAINNET_EXTRA_DATA_RLP_MAX_BYTES: usize = max_rlp_encoding_len(MAINNET_EXTRA_DATA_MAX_BYTES); +const MAINNET_BLOCK_HEADER_RLP_MAX_BYTES: usize = + 1 + 2 + (515 + MAINNET_EXTRA_DATA_RLP_MAX_BYTES + 33 + 33 + 9 * 2 + 33); // 33 + 33 is for basefee, withdrawals_root, 9*2 for eip-4844, 33 for eip-4788 + +#[test] +fn test_block_header_rlp_max_lens() { + let from_chain = get_block_header_rlp_max_lens_from_chain_id(1); + assert_eq!(from_chain.0, MAINNET_BLOCK_HEADER_RLP_MAX_BYTES); + assert_eq!(from_chain.1, MAINNET_HEADER_FIELDS_MAX_BYTES); +} + +#[derive(Clone)] +struct HeaderTest { + headers: Vec>, + _marker: PhantomData, +} + +struct HeaderWitness { + chain: Vec>, +} + +impl EthCircuitInstructions for HeaderTest { + type FirstPhasePayload = HeaderWitness; + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + ) -> Self::FirstPhasePayload { + let chip = EthBlockHeaderChip::new_from_network(mpt.rlp, Chain::Mainnet); + let ctx = builder.base.main(0); + let inputs = self + .headers + .iter() + .map(|header| assign_vec(ctx, header.clone(), MAINNET_BLOCK_HEADER_RLP_MAX_BYTES)) + .collect_vec(); + let chain = chip.decompose_block_header_chain_phase0(builder, mpt.keccak, inputs); + HeaderWitness { chain } + } + + fn virtual_assign_phase1( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + witness: Self::FirstPhasePayload, + ) { + let chip = EthBlockHeaderChip::new_from_network(mpt.rlp, Chain::Mainnet); + chip.decompose_block_header_chain_phase1(builder, witness.chain, None); + } +} + +fn block_header_test_circuit( + stage: CircuitBuilderStage, + inputs: Vec>, + params: RlcCircuitParams, +) -> EthCircuitImpl> { + let test = HeaderTest { headers: inputs, _marker: PhantomData }; + let mut circuit = create_circuit(stage, params, test); + circuit.mock_fulfill_keccak_promises(None); + if !stage.witness_gen_only() { + circuit.calculate_params(); + } + circuit +} + +#[test] +pub fn test_one_mainnet_header_mock() { + let params = get_rlc_params("configs/tests/one_block.json"); + let k = params.base.k as u32; + let input_hex = "f90201a0d7519abd494a823b2c9c28908eaf250fe4a6287d747f1cc53a5a193b6533a549a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944675c7e5baafbffbca748158becba61ef3b0a263a025000d51f040ee5c473fed74eda9ace87d55a35187b11bcde6f5176025c395bfa0a5800a6de6d28d7425ff72714af2af769b9f8f9e1baf56fb42f793fbb40fde07a056e1062a3dc63791e8a8496837606b14062da70ee69178cea97d6eeb5047550cb9010000236420014dc00423903000840002280080282100004704018340c0241c20011211400426000f900001d8088000011006020002ce98bc00c0000020c9a02040000688040200348c3a0082b81402002814922008085d008008200802802c4000130000101703124801400400018008a6108002020420144011200070020bc0202681810804221304004800088600300000040463614a000e200201c00611c0008e800b014081608010a0218a0b410010082000428209080200f50260a00840006700100f40a000000400000448301008c4a00341040e343500800d06250020010215200c008018002c88350404000bc5000a8000210c00724a0d0a4010210a448083eee2468401c9c3808343107884633899e780a07980d8d1f15474c9185e4d1cef5f207167735009daad2eb6af6da37ffba213c28800000000000000008501e08469e60000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + let mut input_bytes: Vec = Vec::from_hex(input_hex).unwrap(); + input_bytes.resize(MAINNET_BLOCK_HEADER_RLP_MAX_BYTES, 0); + + let circuit = + block_header_test_circuit::(CircuitBuilderStage::Mock, vec![input_bytes], params); + let instances = circuit.instances(); + MockProver::run(k, &circuit, instances).unwrap().assert_satisfied(); +} + +#[test] +pub fn test_one_mainnet_header_before_london_mock() { + let params = get_rlc_params("configs/tests/one_block.json"); + let k = params.base.k as u32; + let input_hex = "f90221a0b8b861952bca93c10bc7c38f9ef5c4e047beae539cfe46fa456c78893d916927a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940501b62d81a3f072f1d393d2f74013bab8d36d5ca01fd1d6a626d5d72d433b776c0c348f0cab03d13c68ba39ca4a6d6f109032de34a0418c7fdf567a5989a727ea0fe6054008ecf4953aaf56c28f7f197f6e443f05c0a05f79bcb9839eb480350b541377d04c5088fc4bab6952ed27cb94c70dd6736d73b9010081029040054830208119a218064a503c384490dc2014a414e3148820851856c05008e643a88a4a0002242e1a702d8a516244220a18cd0121a13a20882930000e471369c142ad4323475013088accb068824a002cc35021640860a448405a904001094c200a6081d0420feb02802c2e090a121403213d2640c100503510300364e43020f55943142815080595b145040045890021412545119b9002891cfe41011a704100ca97641210002a3b22c10f24853849048420100465c361880421593000021022c90800008800750e546464068cc40290108c48741899114af9c52801403da6800c02000c6ea270992068b45618c46f1254d7601d4411104e41d00a0787074abe0f14de3383765fdd837a121d8379cbd7845cda8ef39fde830203088f5061726974792d457468657265756d86312e33332e30826c69a09d41f9f64af4ebd672dec132507a12a4c85c1a514f47969dbd9c2b5e9d7d214e882b8a10229542325400000000000000000000"; + let mut input_bytes: Vec = Vec::from_hex(input_hex).unwrap(); + input_bytes.resize(MAINNET_BLOCK_HEADER_RLP_MAX_BYTES, 0); + + let circuit = + block_header_test_circuit::(CircuitBuilderStage::Mock, vec![input_bytes], params); + let instances = circuit.instances(); + MockProver::run(k, &circuit, instances).unwrap().assert_satisfied(); +} + +#[test] +pub fn test_one_mainnet_header_withdrawals_mock() { + let params = get_rlc_params("configs/tests/one_block.json"); + let k = params.base.k as u32; + let input_hex = "f90222a0d7519abd494a823b2c9c28908eaf250fe4a6287d747f1cc53a5a193b6533a549a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944675c7e5baafbffbca748158becba61ef3b0a263a025000d51f040ee5c473fed74eda9ace87d55a35187b11bcde6f5176025c395bfa0a5800a6de6d28d7425ff72714af2af769b9f8f9e1baf56fb42f793fbb40fde07a056e1062a3dc63791e8a8496837606b14062da70ee69178cea97d6eeb5047550cb9010000236420014dc00423903000840002280080282100004704018340c0241c20011211400426000f900001d8088000011006020002ce98bc00c0000020c9a02040000688040200348c3a0082b81402002814922008085d008008200802802c4000130000101703124801400400018008a6108002020420144011200070020bc0202681810804221304004800088600300000040463614a000e200201c00611c0008e800b014081608010a0218a0b410010082000428209080200f50260a00840006700100f40a000000400000448301008c4a00341040e343500800d06250020010215200c008018002c88350404000bc5000a8000210c00724a0d0a4010210a448083eee2468401c9c3808343107884633899e780a07980d8d1f15474c9185e4d1cef5f207167735009daad2eb6af6da37ffba213c28800000000000000008501e08469e6a0f7519abd494a823b2c9c28908eaf250fe4a6287d747f1cc53a5a193b6533a549"; + let mut input_bytes: Vec = Vec::from_hex(input_hex).unwrap(); + input_bytes.resize(MAINNET_BLOCK_HEADER_RLP_MAX_BYTES, 0); + + let circuit = + block_header_test_circuit::(CircuitBuilderStage::Mock, vec![input_bytes], params); + let instances = circuit.instances(); + MockProver::run(k, &circuit, instances).unwrap().assert_satisfied(); +} + +#[test] +pub fn test_one_mainnet_header_fake_cancun_mock() { + let params = get_rlc_params("configs/tests/one_block.json"); + let k = params.base.k as u32; + let input_hex = "f90249a0d7519abd494a823b2c9c28908eaf250fe4a6287d747f1cc53a5a193b6533a549a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944675c7e5baafbffbca748158becba61ef3b0a263a025000d51f040ee5c473fed74eda9ace87d55a35187b11bcde6f5176025c395bfa0a5800a6de6d28d7425ff72714af2af769b9f8f9e1baf56fb42f793fbb40fde07a056e1062a3dc63791e8a8496837606b14062da70ee69178cea97d6eeb5047550cb9010000236420014dc00423903000840002280080282100004704018340c0241c20011211400426000f900001d8088000011006020002ce98bc00c0000020c9a02040000688040200348c3a0082b81402002814922008085d008008200802802c4000130000101703124801400400018008a6108002020420144011200070020bc0202681810804221304004800088600300000040463614a000e200201c00611c0008e800b014081608010a0218a0b410010082000428209080200f50260a00840006700100f40a000000400000448301008c4a00341040e343500800d06250020010215200c008018002c88350404000bc5000a8000210c00724a0d0a4010210a448083eee2468401c9c3808343107884633899e780a07980d8d1f15474c9185e4d1cef5f207167735009daad2eb6af6da37ffba213c28800000000000000008501e08469e6a0f7519abd494a823b2c9c28908eaf250fe4a6287d747f1cc53a5a193b6533a549820123820456a07980d8d1f15474c9185e4d1cef5f207167735009daad2eb6af6da37ffba213c2"; + let mut input_bytes: Vec = Vec::from_hex(input_hex).unwrap(); + input_bytes.resize(MAINNET_BLOCK_HEADER_RLP_MAX_BYTES, 0); + + let circuit = + block_header_test_circuit::(CircuitBuilderStage::Mock, vec![input_bytes], params); + let instances = circuit.instances(); + MockProver::run(k, &circuit, instances).unwrap().assert_satisfied(); +} + +#[test] +pub fn test_one_mainnet_header_prover() -> Result<(), Box> { + let config_params = get_rlc_params("configs/tests/one_block.json"); + let k = config_params.base.k as u32; + let input_hex = "f90222a0d7519abd494a823b2c9c28908eaf250fe4a6287d747f1cc53a5a193b6533a549a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944675c7e5baafbffbca748158becba61ef3b0a263a025000d51f040ee5c473fed74eda9ace87d55a35187b11bcde6f5176025c395bfa0a5800a6de6d28d7425ff72714af2af769b9f8f9e1baf56fb42f793fbb40fde07a056e1062a3dc63791e8a8496837606b14062da70ee69178cea97d6eeb5047550cb9010000236420014dc00423903000840002280080282100004704018340c0241c20011211400426000f900001d8088000011006020002ce98bc00c0000020c9a02040000688040200348c3a0082b81402002814922008085d008008200802802c4000130000101703124801400400018008a6108002020420144011200070020bc0202681810804221304004800088600300000040463614a000e200201c00611c0008e800b014081608010a0218a0b410010082000428209080200f50260a00840006700100f40a000000400000448301008c4a00341040e343500800d06250020010215200c008018002c88350404000bc5000a8000210c00724a0d0a4010210a448083eee2468401c9c3808343107884633899e780a07980d8d1f15474c9185e4d1cef5f207167735009daad2eb6af6da37ffba213c28800000000000000008501e08469e6a0f7519abd494a823b2c9c28908eaf250fe4a6287d747f1cc53a5a193b6533a549"; + let mut input_bytes: Vec = Vec::from_hex(input_hex).unwrap(); + input_bytes.resize(MAINNET_BLOCK_HEADER_RLP_MAX_BYTES, 0); + + let mut rng = OsRng; + let params = ParamsKZG::::setup(k, &mut rng); + let circuit = block_header_test_circuit::( + CircuitBuilderStage::Keygen, + vec![input_bytes.clone()], + config_params, + ); + //circuit.config(k as usize, Some(109)); + let vk_time = start_timer!(|| "vk gen"); + let vk = keygen_vk(¶ms, &circuit).unwrap(); + end_timer!(vk_time); + let pk_time = start_timer!(|| "pk gen"); + let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); + end_timer!(pk_time); + let break_points = circuit.break_points(); + let config_params = circuit.params().rlc; + + let pf_time = start_timer!(|| "proof gen"); + let circuit = block_header_test_circuit::( + CircuitBuilderStage::Prover, + vec![input_bytes], + config_params, + ) + .use_break_points(break_points); + let instances = circuit.instances(); + assert_eq!(instances.len(), 1); + let proof = gen_proof_with_instances(¶ms, &pk, circuit, &[&instances[0]]); + end_timer!(pf_time); + + let verify_time = start_timer!(|| "verify"); + check_proof_with_instances(¶ms, pk.get_vk(), &proof, &[&instances[0]], true); + end_timer!(verify_time); + + Ok(()) +} diff --git a/axiom-eth/src/keccak/README.md b/axiom-eth/src/keccak/README.md new file mode 100644 index 00000000..0df68831 --- /dev/null +++ b/axiom-eth/src/keccak/README.md @@ -0,0 +1,12 @@ +# Keccak Chip + +This module provides adapters and trait implementations to enable the [`KeccakComponentShardCircuit`](https://github.com/axiom-crypto/halo2-lib/tree/main/hashes/zkevm/src/keccak) to fall under the [Component Framework](../utils/README.md). + +The keccak component circuit outputs a virtual table of `(key, value)` pairs where `key` is of type `Vec` and `value` is of type `H256`. We represent the `H256` in hi-lo form as two field elements. The complication is in the `key`, which is a variable length byte array of possibly arbitrary length. +To handle this in ZK, we must instead encode `(key,value)` as a different virtual table with fixed length keys but where a single logical `(key,value)` can take up multiple rows in the new fixed length key table. + +For keccak, the format of the fixed length key table is specified in [zkEVM hashes](https://github.com/axiom-crypto/halo2-lib/tree/main/hashes/zkevm/src/keccak). +What is provided in the [promise](./promise.rs) submodule is a way to perform promise calls into the keccak component circuit. +Promise calls are done as follows: the caller circuit loaded the fixed length key virtual table as private witnesses and computes the commitment to the table to exactly match the output commitment computation of the keccak component circuit. +Then it creates a raw Halo2 table where it RLCs the entries of the fixed length key table in a way that encodes the variable lengths. +Finally for each keccak promise call, the variable length input bytes are first packed into field elements in a way that matches the packing done in the virtual table. Then the packed field elements are RLCed together with the variable length. This RLC value is dynamically looked up against the raw Halo2 table to verify the promise call. diff --git a/axiom-eth/src/keccak/component_shim.rs b/axiom-eth/src/keccak/component_shim.rs new file mode 100644 index 00000000..7c5121ea --- /dev/null +++ b/axiom-eth/src/keccak/component_shim.rs @@ -0,0 +1,85 @@ +use std::{any::Any, collections::HashMap, iter}; + +use anyhow::{anyhow, bail, Result}; +use ethers_core::{types::H256, utils::keccak256}; +use zkevm_hashes::keccak::{ + component::{ + circuit::shard::KeccakComponentShardCircuit, + output::{calculate_circuit_outputs_commit, multi_inputs_to_circuit_outputs}, + }, + vanilla::keccak_packed_multi::get_num_keccak_f, +}; + +use crate::{ + halo2_proofs::plonk::Circuit, + utils::{ + component::{ + types::ComponentPublicInstances, ComponentCircuit, ComponentPromiseResult, + ComponentPromiseResultsInMerkle, GroupedPromiseCalls, GroupedPromiseResults, + LogicalResult, PromiseShardMetadata, + }, + encode_h256_to_hilo, + }, + Field, +}; + +use super::types::{ComponentTypeKeccak, CoreInputKeccak, KeccakLogicalInput, KeccakVirtualOutput}; + +impl ComponentCircuit for KeccakComponentShardCircuit { + fn clear_witnesses(&self) { + self.base_circuit_builder().borrow_mut().clear(); + self.hasher().borrow_mut().clear(); + } + /// No promise calls + fn compute_promise_calls(&self) -> Result { + Ok(HashMap::new()) + } + /// The `input` should be of type [CoreInputKeccak]. + /// As a special case, we allow the input to have used capacity less than the configured capacity because the Keccak component circuit knows to automatically pad the input. + fn feed_input(&self, input: Box) -> Result<()> { + let typed_input = + input.downcast::().map_err(|_| anyhow!("invalid input type"))?; + let params_cap = self.params().capacity(); + let input_cap = + typed_input.iter().map(|input| get_num_keccak_f(input.len())).sum::(); + if input_cap > params_cap { + bail!("Input capacity {input_cap} > configured capacity {params_cap}"); + } + let mut inputs = *typed_input; + // resize so the capacity of `inputs` is exactly `params_cap` + inputs.extend(iter::repeat(vec![]).take(params_cap - input_cap)); + *self.inputs().borrow_mut() = inputs; + Ok(()) + } + fn compute_outputs(&self) -> Result> { + let capacity = self.params().capacity(); + // This is the same as the `instances()` implementation + let vt = multi_inputs_to_circuit_outputs::(&self.inputs().borrow(), capacity); + let output_commit_val = calculate_circuit_outputs_commit(&vt); + let pr: Vec> = self + .inputs() + .borrow() + .iter() + .map(|bytes| { + let output = H256(keccak256(bytes)); + LogicalResult::>::new( + KeccakLogicalInput::new(bytes.clone()), + KeccakVirtualOutput::new(encode_h256_to_hilo(&output)), + ) + .into() + }) + .collect(); + + Ok(ComponentPromiseResultsInMerkle::::new( + vec![PromiseShardMetadata { commit: output_commit_val, capacity }], + vec![(0, pr)], + )) + } + fn get_public_instances(&self) -> ComponentPublicInstances { + unreachable!("keccak does not follow ComponentPublicInstances") + } + /// Promise results are ignored since this component makes no promise calls. + fn fulfill_promise_results(&self, _: &GroupedPromiseResults) -> anyhow::Result<()> { + Ok(()) + } +} diff --git a/axiom-eth/src/keccak/mod.rs b/axiom-eth/src/keccak/mod.rs new file mode 100644 index 00000000..7a2346e7 --- /dev/null +++ b/axiom-eth/src/keccak/mod.rs @@ -0,0 +1,269 @@ +//! Firstly, the structs and functions in this module **DO NOT** constrain the computation of the keccak hash function. +//! Instead, they are meant to constrain the correctness of keccak hashes on a collection of variable length byte arrays +//! when given a commitment to a lookup table of keccak hashes from an external keccak "coprocessor" circuit. +//! +use core::iter::once; + +use getset::Getters; +use halo2_base::{ + gates::{GateChip, GateInstructions, RangeChip, RangeInstructions}, + safe_types::{SafeBytes32, SafeTypeChip}, + utils::{bit_length, ScalarField}, + AssignedValue, Context, + QuantumCell::Constant, +}; +use itertools::Itertools; +use zkevm_hashes::keccak::vanilla::param::NUM_BYTES_TO_SQUEEZE; + +use crate::{ + keccak::promise::KeccakVarLenCall, + utils::{ + bytes_be_to_u128, component::promise_collector::PromiseCaller, u128s_to_bytes_be, + AssignedH256, + }, + Field, +}; + +use self::{ + promise::KeccakFixLenCall, + types::{ComponentTypeKeccak, KeccakFixedLenQuery, KeccakVarLenQuery}, +}; + +mod component_shim; +/// Keccak Promise Loader +pub mod promise; +#[cfg(test)] +mod tests; +/// Types +pub mod types; + +/// Thread-safe manager that collects all keccak queries and then constrains that the input, outputs +/// are correct with respect to an externally provided table of | encoding(input) | keccak(input) |. +#[derive(Clone, Debug, Getters)] +pub struct KeccakChip { + #[getset(get = "pub")] + promise_caller: PromiseCaller, + #[getset(get = "pub")] + range: RangeChip, +} + +impl KeccakChip { + pub fn new(range: RangeChip, promise_collector: PromiseCaller) -> Self { + Self::new_with_promise_collector(range, promise_collector) + } + pub fn new_with_promise_collector( + range: RangeChip, + promise_collector: PromiseCaller, + ) -> Self { + Self { range, promise_caller: promise_collector } + } + pub fn gate(&self) -> &GateChip { + &self.range.gate + } + + /// Takes a byte vector of known fixed length and computes the keccak digest of `bytes`. + /// - Returns `(output_bytes, output_hi, output_lo)`. + /// - This function only computes witnesses for output bytes. + /// + /// # Assumptions + /// - `input` elements have been range checked to be bytes + /// - This assumption is rather **unsafe** and assumes the user is careful. + // TODO: use SafeByte + pub fn keccak_fixed_len( + &self, + ctx: &mut Context, + input: Vec>, + ) -> KeccakFixedLenQuery { + let [output_hi, output_lo] = { + let len = input.len(); + let output = self + .promise_caller + .call::, ComponentTypeKeccak>( + ctx, + KeccakFixLenCall::new(SafeTypeChip::unsafe_to_fix_len_bytes_vec( + input.clone(), + len, + )), + ) + .unwrap(); + output.hash.hi_lo() + }; + + // Decompose hi-lo into bytes (with range check). Right now we always provide the bytes for backwards compatibility. + // In the future we may create them on demand. + let output_bytes = u128s_to_bytes_be(ctx, self.range(), &[output_hi, output_lo]); + // no good way to transmute from Vec to SafeBytes32 + let output_raw: Vec> = + output_bytes.into_iter().map(|b| b.into()).collect(); + let output_bytes = SafeTypeChip::unsafe_to_safe_type(output_raw); + + KeccakFixedLenQuery { input_assigned: input, output_bytes, output_hi, output_lo } + } + + /// Takes a fixed length byte vector and computes the keccak digest of `bytes[..len]`. + /// - Returns `(output_bytes, output_hi, output_lo)`. + /// - This function only computes witnesses for output bytes. + /// + /// Constrains `min_len <= len <= bytes.len()`. + /// + /// # Assumptions + /// - `input` elements have been range checked to be bytes + /// - This assumption is rather **unsafe** and assumes the user is careful. + // TODO: use SafeByte + pub fn keccak_var_len( + &self, + ctx: &mut Context, + input: Vec>, + len: AssignedValue, + min_len: usize, + ) -> KeccakVarLenQuery { + let bytes = get_bytes(&input); + let max_len = input.len(); + + let range = self.range(); + range.check_less_than_safe(ctx, len, (max_len + 1) as u64); + if min_len != 0 { + range.check_less_than( + ctx, + Constant(F::from((min_len - 1) as u64)), + len, + bit_length((max_len + 1) as u64), + ); + } + let num_bytes = len.value().get_lower_64() as usize; + debug_assert!(bytes.len() >= num_bytes); + + let [output_hi, output_lo] = { + let output = self + .promise_caller + .call::, ComponentTypeKeccak>( + ctx, + KeccakVarLenCall::new( + SafeTypeChip::unsafe_to_var_len_bytes_vec(input.clone(), len, max_len), + min_len, + ), + ) + .unwrap(); + output.hash.hi_lo() + }; + + // Decompose hi-lo into bytes (with range check). Right now we always provide the bytes for backwards compatibility. + // In the future we may create them on demand. + let output_bytes = u128s_to_bytes_be(ctx, self.range(), &[output_hi, output_lo]); + + KeccakVarLenQuery { + min_bytes: min_len, + length: len, + input_assigned: input, + output_bytes: output_bytes.try_into().unwrap(), + output_hi, + output_lo, + } + } + + /// Computes the keccak merkle root of a tree with leaves `leaves`. + /// + /// 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. + /// + /// # Warning + /// - This implementation currently has no domain separation between hashing leaves versus hashing inner nodes + pub fn merkle_tree_root( + &self, + ctx: &mut Context, + leaves: &[impl AsRef<[AssignedValue]>], + ) -> (SafeBytes32, AssignedH256) { + let depth = leaves.len().ilog2() as usize; + debug_assert_eq!(1 << depth, leaves.len()); + assert_ne!(depth, 0, "Merkle root of a single leaf is ill-defined"); + + // bottom layer hashes + let mut hashes = leaves + .chunks(2) + .map(|pair| { + let leaves_concat = [pair[0].as_ref(), pair[1].as_ref()].concat(); + self.keccak_fixed_len(ctx, leaves_concat) + }) + .collect_vec(); + debug_assert_eq!(hashes.len(), 1 << (depth - 1)); + for d in (0..depth - 1).rev() { + for i in 0..(1 << d) { + let leaves_concat = + [2 * i, 2 * i + 1].map(|idx| hashes[idx].output_bytes.as_ref()).concat(); + hashes[i] = self.keccak_fixed_len(ctx, leaves_concat); + } + } + (hashes[0].output_bytes.clone(), [hashes[0].output_hi, hashes[0].output_lo]) + } + + /// Computes a keccak merkle mountain range of a tree with leaves `leaves`. + /// + /// Assumptions: + /// - Each element of `leaves` is a slice of assigned byte values of fixed length `NUM_BYTES_TO_SQUEEZE = 32`. + /// - `num_leaves_bits` is the little endian bit representation of `num_leaves` + /// - `leaves.len()` is a power of two (i.e., we have a full binary tree), but `leaves[num_leaves..]` can be arbitrary dummy leaves. + /// - 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 mountain range associated with `leaves[..num_leaves]` + /// as a length `log_2(leaves.len()) + 1` vector of byte arrays. + /// The mountain range is ordered with the largest mountain first. For example, if `num_leaves = leaves.len()` then the first mountain is the merkle root of the full tree. + /// For `i` where `(num_leaves >> i) & 1 == 0`, the value of the corresponding peak should be considered UNDEFINED. + /// + /// The merkle root of the tree with leaves `leaves[..num_leaves]` can be recovered by successively hashing the elements in the merkle mountain range, in reverse order, corresponding to indices + /// where `num_leaves` has a 1 bit. + pub fn merkle_mountain_range( + &self, + ctx: &mut Context, + leaves: &[Vec>], + num_leaves_bits: &[AssignedValue], + ) -> Vec<(SafeBytes32, AssignedH256)> { + let max_depth = leaves.len().ilog2() as usize; + assert_eq!(leaves.len(), 1 << max_depth); + assert_eq!(num_leaves_bits.len(), max_depth + 1); + + // start_idx[i] = (num_leaves >> i) << i + // below we will want to select `leaves[start_idx[depth+1]..start_idx[depth+1] + 2^depth] for depth = max_depth - 1, ..., 0 + // we do this with a barrel-shifter, by shifting `leaves` left by 2^i or 0 depending on the bit in `num_leaves_bits` + // we skip the first shift by 2^max_depth because if num_leaves == 2^max_depth then all these subsequent peaks are undefined + let mut shift_leaves = leaves.to_vec(); + once(self.merkle_tree_root(ctx, leaves)) + .chain(num_leaves_bits.iter().enumerate().rev().skip(1).map(|(depth, &sel)| { + // no need to shift if we're at the end + if depth != 0 { + let peak = self.merkle_tree_root(ctx, &shift_leaves[..(1usize << depth)]); + // shift left by sel == 1 ? 2^depth : 0 + for i in 0..1 << depth { + debug_assert_eq!(shift_leaves[i].len(), NUM_BYTES_TO_SQUEEZE); + for j in 0..shift_leaves[i].len() { + shift_leaves[i][j] = self.gate().select( + ctx, + shift_leaves[i + (1 << depth)][j], + shift_leaves[i][j], + sel, + ); + } + } + peak + } else { + let leaf_bytes = + SafeTypeChip::unsafe_to_fix_len_bytes_vec(shift_leaves[0].clone(), 32) + .into_bytes(); + let hi_lo: [_; 2] = + bytes_be_to_u128(ctx, self.gate(), &leaf_bytes).try_into().unwrap(); + let bytes = SafeBytes32::try_from(leaf_bytes).unwrap(); + (bytes, hi_lo) + } + })) + .collect() + } +} + +// convert field values to u8: +pub fn get_bytes(bytes: &[impl AsRef>]) -> Vec { + // TODO: if we really wanted to optimize, we can pre-compute a HashMap containing just `F::from(byte as u64)` for each byte. I think the cost of hashing is still cheaper than performing the Montgomery reduction + bytes.iter().map(|b| b.as_ref().value().get_lower_64() as u8).collect_vec() +} diff --git a/axiom-eth/src/keccak/promise.rs b/axiom-eth/src/keccak/promise.rs new file mode 100644 index 00000000..4bc8646d --- /dev/null +++ b/axiom-eth/src/keccak/promise.rs @@ -0,0 +1,343 @@ +use std::{any::Any, marker::PhantomData}; + +use getset::Getters; +use halo2_base::{ + gates::{circuit::builder::BaseCircuitBuilder, GateInstructions, RangeChip, RangeInstructions}, + safe_types::{FixLenBytesVec, VarLenBytesVec}, + utils::bit_length, + AssignedValue, Context, + QuantumCell::Constant, +}; +use itertools::Itertools; +use num_bigint::BigUint; +use snark_verifier::util::hash::Poseidon; +use snark_verifier_sdk::NativeLoader; +use zkevm_hashes::keccak::{ + component::{ + encode::{format_input, num_word_per_witness, pack_native_input}, + param::{POSEIDON_RATE, POSEIDON_T}, + }, + vanilla::{ + keccak_packed_multi::get_num_keccak_f, + param::{NUM_BITS_PER_WORD, NUM_BYTES_TO_ABSORB}, + }, +}; + +use crate::{ + rlc::chip::RlcChip, + utils::component::{ + promise_loader::comp_loader::ComponentCommiter, + types::Flatten, + utils::{create_hasher, into_key, native_poseidon_hasher, try_from_key}, + ComponentCircuit, ComponentType, ComponentTypeId, LogicalInputValue, PromiseCallWitness, + TypelessLogicalInput, + }, + Field, +}; + +use super::types::{ + ComponentTypeKeccak, KeccakLogicalInput, KeccakVirtualInput, KeccakVirtualOutput, + OutputKeccakShard, NUM_WITNESS_PER_KECCAK_F, +}; + +/// Keccak promise call for fixed-length inputs. +#[derive(Clone, Debug, Getters)] +pub struct KeccakFixLenCall { + #[getset(get = "pub")] + bytes: FixLenBytesVec, +} +impl KeccakFixLenCall { + pub fn new(bytes: FixLenBytesVec) -> Self { + Self { bytes } + } + pub fn to_logical_input(&self) -> KeccakLogicalInput { + let bytes_vec = self + .bytes + .bytes() + .iter() + .map(|b| b.as_ref().value().get_lower_64() as u8) + .collect_vec(); + KeccakLogicalInput::new(bytes_vec) + } +} + +impl PromiseCallWitness for KeccakFixLenCall { + fn get_component_type_id(&self) -> ComponentTypeId { + ComponentTypeKeccak::::get_type_id() + } + fn get_capacity(&self) -> usize { + get_num_keccak_f(self.bytes.len()) + } + fn to_rlc( + &self, + (gate_ctx, rlc_ctx): (&mut Context, &mut Context), + range_chip: &RangeChip, + rlc_chip: &RlcChip, + ) -> AssignedValue { + let len = self.bytes.len(); + // NOTE: we pack with len + 1 instead of len for domain separation so the RLC can distinguish an empty input with is_final = 1 vs 0 + let len_p1 = gate_ctx.load_constant(F::from((len + 1) as u64)); + let packed_input = + format_input(gate_ctx, &range_chip.gate, self.bytes.bytes(), len_p1).concat().concat(); + let rlc_fixed_trace = rlc_chip.compute_rlc_fixed_len(rlc_ctx, packed_input); + rlc_fixed_trace.rlc_val + } + fn to_typeless_logical_input(&self) -> TypelessLogicalInput { + into_key(self.to_logical_input()) + } + fn get_mock_output(&self) -> Flatten { + let bytes_vec = self + .bytes + .bytes() + .iter() + .map(|b| b.as_ref().value().get_lower_64() as u8) + .collect_vec(); + let logical_input = KeccakLogicalInput::new(bytes_vec); + let output_val = logical_input.compute_output(); + output_val.into() + } + fn as_any(&self) -> &dyn Any { + self + } +} + +/// Keccak promise call for fixed-length inputs. +#[derive(Clone, Debug, Getters)] +pub struct KeccakVarLenCall { + #[getset(get = "pub")] + bytes: VarLenBytesVec, + min_len: usize, +} +impl KeccakVarLenCall { + pub fn new(bytes: VarLenBytesVec, min_len: usize) -> Self { + Self { bytes, min_len } + } + pub fn to_logical_input(&self) -> KeccakLogicalInput { + let len = self.bytes.len().value().get_lower_64() as usize; + let bytes_vec = self.bytes.bytes()[..len] + .iter() + .map(|b| b.as_ref().value().get_lower_64() as u8) + .collect_vec(); + KeccakLogicalInput::new(bytes_vec) + } + /// Returns `num_keccak_f - 1`, where + /// `num_keccak_f = bytes.len() / NUM_BYTES_TO_ABSORB + 1` is the true + /// number of `keccak_f` permutations necessary for variable input length `bytes.len()`. + pub fn num_keccak_f_m1( + &self, + gate_ctx: &mut Context, + range_chip: &RangeChip, + ) -> AssignedValue { + let max_len = self.bytes.max_len(); + let num_bits = bit_length(max_len as u64); + let len = *self.bytes.len(); + let (num_keccak_f_m1, _) = + range_chip.div_mod(gate_ctx, len, BigUint::from(NUM_BYTES_TO_ABSORB), num_bits); + num_keccak_f_m1 + } +} + +impl PromiseCallWitness for KeccakVarLenCall { + fn get_component_type_id(&self) -> ComponentTypeId { + ComponentTypeKeccak::::get_type_id() + } + fn get_capacity(&self) -> usize { + get_num_keccak_f(self.bytes.len().value().get_lower_64() as usize) + } + fn to_rlc( + &self, + (gate_ctx, rlc_ctx): (&mut Context, &mut Context), + range_chip: &RangeChip, + rlc_chip: &RlcChip, + ) -> AssignedValue { + let bytes = self.bytes.ensure_0_padding(gate_ctx, &range_chip.gate); + let num_keccak_f_m1 = self.num_keccak_f_m1(gate_ctx, range_chip); + + let len = bytes.len(); + let len_p1 = range_chip.gate.inc(gate_ctx, *len); + + let num_keccak_f = range_chip.gate.inc(gate_ctx, num_keccak_f_m1); + let packed_input = format_input(gate_ctx, &range_chip.gate, bytes.bytes(), len_p1); + let packed_input = packed_input.into_iter().flatten().flatten(); + + let rlc_len = range_chip.gate.mul( + gate_ctx, + Constant(F::from(NUM_WITNESS_PER_KECCAK_F as u64)), + num_keccak_f, + ); + let rlc_trace = rlc_chip.compute_rlc_with_min_len( + (gate_ctx, rlc_ctx), + &range_chip.gate, + packed_input, + rlc_len, + get_num_keccak_f(self.min_len) * NUM_WITNESS_PER_KECCAK_F, + ); + rlc_trace.rlc_val + } + fn to_typeless_logical_input(&self) -> TypelessLogicalInput { + into_key(self.to_logical_input()) + } + fn get_mock_output(&self) -> Flatten { + let len = self.bytes.len().value().get_lower_64() as usize; + let bytes_vec = self.bytes.bytes()[..len] + .iter() + .map(|b| b.as_ref().value().get_lower_64() as u8) + .collect_vec(); + let logical_input: KeccakLogicalInput = KeccakLogicalInput::new(bytes_vec); + let output_val: as ComponentType>::OutputValue = + logical_input.compute_output(); + output_val.into() + } + fn as_any(&self) -> &dyn Any { + self + } +} + +fn get_dummy_key(native_poseidon: &mut Poseidon) -> F { + native_poseidon.clear(); + // copied from encode_native_input, but save re-creating Spec above + let witnesses_per_keccak_f = pack_native_input(&[]); + for witnesses in witnesses_per_keccak_f { + for absorbing in witnesses.chunks(POSEIDON_RATE) { + // To avoid absorbing witnesses crossing keccak_fs together, pad 0s to make sure absorb.len() == RATE. + let mut padded_absorb = [F::ZERO; POSEIDON_RATE]; + padded_absorb[..absorbing.len()].copy_from_slice(absorbing); + native_poseidon.update(&padded_absorb); + } + } + native_poseidon.squeeze() +} + +/// KeccakComponentCommiter implements the commitment computation in KeccakComponentShardCircuit which uses a legacy way to compute commitment. +pub struct KeccakComponentCommiter(PhantomData); + +impl ComponentCommiter for KeccakComponentCommiter { + /// This must match the commitment computation in [zkevm_hashes::keccak::component::circuit::shard::encode_inputs_from_keccak_fs] and + /// [zkevm_hashes::keccak::component::circuit::shard::KeccakComponentShardCircuit::publish_outputs] with `publish_raw_outputs = false`. + fn compute_commitment( + builder: &mut BaseCircuitBuilder, + witness_virtual_rows: &[(Flatten>, Flatten>)], + ) -> AssignedValue { + let range_chip = &builder.range_chip(); + let ctx = builder.main(0); + + let mut hasher = create_hasher::(); + hasher.initialize_consts(ctx, &range_chip.gate); + let dummy_key = { + let mut native_poseidon = Poseidon::from_spec(&NativeLoader, hasher.spec().clone()); + get_dummy_key(&mut native_poseidon) + }; + let dummy_input = ctx.load_constant(dummy_key); + let parsed_virtual_rows: Vec<(KeccakVirtualInput<_>, KeccakVirtualOutput<_>)> = + witness_virtual_rows + .iter() + .map(|(v_i, v_o)| { + (v_i.clone().try_into().unwrap(), v_o.clone().try_into().unwrap()) + }) + .collect_vec(); + // Constraint is_final of each virtual row + let mut remaining_keccak_f = ctx.load_zero(); + for (v_i, _) in &parsed_virtual_rows { + let (_, length_placeholder) = range_chip.div_mod( + ctx, + v_i.packed_input[0], + BigUint::from(1u128 << NUM_BITS_PER_WORD), + NUM_BITS_PER_WORD * num_word_per_witness::(), + ); + // num_keccak_f = length / NUM_BYTES_TO_ABSORB + 1 + // num_keccak_f_dec = num_keccak_f - 1 + let (num_keccak_f_dec, _) = range_chip.div_mod( + ctx, + length_placeholder, + BigUint::from(NUM_BYTES_TO_ABSORB), + NUM_BITS_PER_WORD, + ); + let remaining_keccak_f_is_zero = range_chip.gate.is_zero(ctx, remaining_keccak_f); + let remaining_keccak_f_dec = range_chip.gate.dec(ctx, remaining_keccak_f); + remaining_keccak_f = range_chip.gate.select( + ctx, + num_keccak_f_dec, + remaining_keccak_f_dec, + remaining_keccak_f_is_zero, + ); + let is_final = range_chip.gate.is_zero(ctx, remaining_keccak_f); + ctx.constrain_equal(&is_final, &v_i.is_final); + } + + let mut inputs_to_poseidon = Vec::with_capacity(parsed_virtual_rows.len()); + let mut virtual_outputs = Vec::with_capacity(parsed_virtual_rows.len()); + for (v_i, v_o) in parsed_virtual_rows { + inputs_to_poseidon.push(v_i.into()); + virtual_outputs.push(v_o); + } + let poseidon_results = + hasher.hash_compact_chunk_inputs(ctx, &range_chip.gate, &inputs_to_poseidon); + let keccak_outputs = poseidon_results + .into_iter() + .zip_eq(virtual_outputs) + .map(|(po, vo)| { + let key = range_chip.gate.select(ctx, po.hash(), dummy_input, po.is_final()); + vec![key, vo.hash.lo(), vo.hash.hi()] + }) + .concat(); + hasher.hash_fix_len_array(ctx, &range_chip.gate, &keccak_outputs) + } + + /// This code path is currently never used, but it should still be consistent with + /// `self.compute_commitment`. + /// + /// We do not do input validation of `is_final` in this function and just assume it is correct. + fn compute_native_commitment(witness_virtual_rows: &[(Flatten, Flatten)]) -> F { + let mut hasher = native_poseidon_hasher(); + let dummy_key = get_dummy_key(&mut hasher); + hasher.clear(); + let keccak_outputs: Vec<_> = witness_virtual_rows + .iter() + .flat_map(|(v_i, v_o)| { + let (v_i, v_o): (KeccakVirtualInput<_>, KeccakVirtualOutput<_>) = + (v_i.clone().try_into().unwrap(), v_o.clone().try_into().unwrap()); + hasher.update(&v_i.packed_input); + let key = if v_i.is_final == F::ONE { + let key = hasher.squeeze(); + hasher.clear(); + key + } else { + dummy_key + }; + let [hi, lo] = v_o.hash.hi_lo(); + [key, lo, hi] + }) + .collect(); + hasher.clear(); + hasher.update(&keccak_outputs); + hasher.squeeze() + } +} + +/// A helper function to fulfill keccak promises for a Component for testing. +pub fn generate_keccak_shards_from_calls( + comp_circuit: &dyn ComponentCircuit, + capacity: usize, +) -> anyhow::Result { + let calls = comp_circuit.compute_promise_calls()?; + let keccak_type_id = ComponentTypeKeccak::::get_type_id(); + let keccak_calls = calls.get(&keccak_type_id).ok_or(anyhow::anyhow!("no keccak calls"))?; + let mut used_capacity = 0; + let responses = keccak_calls + .iter() + .map(|call| { + let li = try_from_key::(&call.logical_input).unwrap(); + used_capacity += >::get_capacity(&li); + (li.bytes.clone().into(), None) + }) + .collect_vec(); + log::info!("Keccak used capacity: {}", used_capacity); + if used_capacity > capacity { + return Err(anyhow::anyhow!( + "used capacity {} exceeds capacity {}", + used_capacity, + capacity + )); + } + Ok(OutputKeccakShard { responses, capacity }) +} diff --git a/axiom-eth/src/keccak/tests.rs b/axiom-eth/src/keccak/tests.rs new file mode 100644 index 00000000..cf4c42dd --- /dev/null +++ b/axiom-eth/src/keccak/tests.rs @@ -0,0 +1,186 @@ +use crate::{ + keccak::types::{ComponentTypeKeccak, KeccakVirtualInput, KeccakVirtualOutput}, + rlc::circuit::builder::RlcCircuitBuilder, + utils::component::{ + promise_loader::{comp_loader::ComponentCommiter, flatten_witness_to_rlc}, + types::{FixLenLogical, Flatten}, + utils::{into_key, load_logical_value}, + ComponentCircuit, ComponentType, LogicalResult, PromiseCallWitness, + }, +}; + +use halo2_base::{ + gates::GateInstructions, halo2_proofs::halo2curves::bn256::Fr, safe_types::SafeTypeChip, + AssignedValue, Context, +}; +use itertools::Itertools; +use snark_verifier_sdk::CircuitExt; +use zkevm_hashes::keccak::{ + component::circuit::shard::{KeccakComponentShardCircuit, KeccakComponentShardCircuitParams}, + vanilla::param::NUM_BYTES_TO_ABSORB, +}; + +use super::{ + promise::{KeccakComponentCommiter, KeccakFixLenCall, KeccakVarLenCall}, + types::KeccakLogicalInput, +}; + +fn verify_rlc_consistency( + logical_input: KeccakLogicalInput, + f: impl Fn(&mut Context) -> Box>, +) { + let output = logical_input.compute_output(); + + let mut builder = RlcCircuitBuilder::::new(false, 32); + builder.set_k(18); + builder.set_lookup_bits(8); + // Mock gamma for testing. + builder.gamma = Some(Fr::from([1, 5, 7, 8])); + let range_chip = &builder.range_chip(); + let rlc_chip = builder.rlc_chip(&range_chip.gate); + let (gate_ctx, rlc_ctx) = builder.rlc_ctx_pair(); + + let assigned_output: KeccakVirtualOutput> = + load_logical_value(gate_ctx, &output); + let call = f(gate_ctx); + + let key = into_key(logical_input.clone()); + assert_eq!(&call.to_typeless_logical_input(), &key); + + let lr = + LogicalResult::>::new(logical_input.clone(), output.clone()); + let vrs_from_results = ComponentTypeKeccak::::logical_result_to_virtual_rows(&lr); + let assigned_vrs_from_results: Vec<(KeccakVirtualInput<_>, KeccakVirtualOutput<_>)> = + vrs_from_results + .into_iter() + .map(|(input, output)| { + (load_logical_value(gate_ctx, &input), load_logical_value(gate_ctx, &output)) + }) + .collect_vec(); + let rlc_from_results = ComponentTypeKeccak::::rlc_virtual_rows( + (gate_ctx, rlc_ctx), + range_chip, + &rlc_chip, + &assigned_vrs_from_results, + ); + + let mut rlc_from_call = call.to_rlc((gate_ctx, rlc_ctx), range_chip, &rlc_chip); + let output_rlc = flatten_witness_to_rlc(rlc_ctx, &rlc_chip, &assigned_output.into()); + let output_multiplier = rlc_chip.rlc_pow_fixed( + gate_ctx, + &range_chip.gate, + KeccakVirtualOutput::::get_num_fields(), + ); + rlc_from_call = range_chip.gate.mul_add(gate_ctx, rlc_from_call, output_multiplier, output_rlc); + + assert_eq!(rlc_from_results.last().unwrap().value(), rlc_from_call.value()); +} + +#[test] +fn test_rlc_consistency() { + let raw_bytes: [u8; 135] = [1; NUM_BYTES_TO_ABSORB - 1]; + let logical_input: KeccakLogicalInput = KeccakLogicalInput::new(raw_bytes.to_vec()); + // Fix-len + verify_rlc_consistency(logical_input.clone(), |gate_ctx| { + let assigned_raw_bytes = + gate_ctx.assign_witnesses(raw_bytes.into_iter().map(|b| Fr::from(b as u64))); + let fix_len_call = KeccakFixLenCall::new(SafeTypeChip::unsafe_to_fix_len_bytes_vec( + assigned_raw_bytes, + raw_bytes.len(), + )); + Box::new(fix_len_call) + }); + // Var-len + verify_rlc_consistency(logical_input, |gate_ctx| { + let max_len = NUM_BYTES_TO_ABSORB; + let len = gate_ctx.load_witness(Fr::from(raw_bytes.len() as u64)); + let var_len_bytes = vec![1; max_len]; + + let assigned_var_len_bytes = + gate_ctx.assign_witnesses(var_len_bytes.into_iter().map(|b| Fr::from(b as u64))); + let var_len_call = KeccakVarLenCall::new( + SafeTypeChip::unsafe_to_var_len_bytes_vec(assigned_var_len_bytes, len, max_len), + 10, + ); + Box::new(var_len_call) + }); +} + +// Test compute outputs against `instances()` implementation +#[test] +fn test_compute_outputs_commit_keccak() { + let k: usize = 15; + let num_unusable_row: usize = 109; + let capacity: usize = 10; + let publish_raw_outputs: bool = false; + + let inputs = vec![ + (0u8..200).collect::>(), + vec![], + (0u8..1).collect::>(), + (0u8..135).collect::>(), + (0u8..136).collect::>(), + (0u8..200).collect::>(), + ]; + // used capacity = 9 + let mut padded_inputs = inputs.clone(); + padded_inputs.push(vec![]); + + let params = + KeccakComponentShardCircuitParams::new(k, num_unusable_row, capacity, publish_raw_outputs); + let circuit = KeccakComponentShardCircuit::::new(vec![], params, false); + circuit.feed_input(Box::new(inputs)).unwrap(); + let commit = circuit.instances()[0][0]; + + let res = circuit.compute_outputs().unwrap(); + assert_eq!(res.leaves()[0].commit, commit); + assert_eq!( + res.shards()[0].1.iter().map(|(i, _o)| i.clone()).collect_vec(), + padded_inputs + .into_iter() + .map(|bytes| into_key(KeccakLogicalInput::new(bytes))) + .collect_vec() + ); +} + +/// Test `compute_native_commitment` against the custom `compute_outputs` implementation +#[test] +fn test_compute_native_commit_keccak() { + let k: usize = 15; + let num_unusable_row: usize = 109; + let capacity: usize = 10; + let publish_raw_outputs: bool = false; + + let inputs = vec![ + (0u8..200).collect::>(), + vec![], + (0u8..1).collect::>(), + (0u8..135).collect::>(), + (0u8..136).collect::>(), + (0u8..200).collect::>(), + ]; + // used capacity = 9 + let mut padded_inputs = inputs.clone(); + padded_inputs.push(vec![]); + + let params = + KeccakComponentShardCircuitParams::new(k, num_unusable_row, capacity, publish_raw_outputs); + let circuit = KeccakComponentShardCircuit::::new(vec![], params, false); + circuit.feed_input(Box::new(inputs)).unwrap(); + + let res = circuit.compute_outputs().unwrap(); + let commit = res.leaves()[0].commit; + + let vt = padded_inputs + .into_iter() + .flat_map(|bytes| { + let logical_input = KeccakLogicalInput::new(bytes); + let output = logical_input.compute_output(); + let lr = LogicalResult::>::new(logical_input, output); + ComponentTypeKeccak::::logical_result_to_virtual_rows(&lr) + }) + .map(|(v_i, v_o)| (Flatten::from(v_i), Flatten::from(v_o))) + .collect_vec(); + let commit2 = KeccakComponentCommiter::compute_native_commitment(&vt); + assert_eq!(commit, commit2); +} diff --git a/axiom-eth/src/keccak/types.rs b/axiom-eth/src/keccak/types.rs new file mode 100644 index 00000000..437a0d14 --- /dev/null +++ b/axiom-eth/src/keccak/types.rs @@ -0,0 +1,394 @@ +use std::{marker::PhantomData, sync::RwLock}; + +use anyhow::anyhow; + +use ethers_core::{ + types::{Bytes, H256}, + utils::keccak256, +}; +use halo2_base::{ + gates::{GateInstructions, RangeChip}, + poseidon::hasher::PoseidonCompactChunkInput, + safe_types::{SafeBytes32, SafeTypeChip}, + utils::ScalarField, + AssignedValue, Context, +}; +use itertools::Itertools; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; + +use type_map::concurrent::TypeMap; +use zkevm_hashes::keccak::{ + component::{encode::pack_native_input, output::KeccakCircuitOutput, param::POSEIDON_RATE}, + vanilla::keccak_packed_multi::get_num_keccak_f, +}; + +use crate::{ + rlc::chip::RlcChip, + utils::{ + component::{ + types::{FixLenLogical, Flatten}, + ComponentType, ComponentTypeId, LogicalInputValue, LogicalResult, + }, + hilo::HiLo, + AssignedH256, + }, + Field, +}; + +use super::promise::KeccakComponentCommiter; + +#[derive(Clone, Debug)] +pub struct KeccakFixedLenQuery { + /// Input in bytes + pub input_assigned: Vec>, + /// The hash digest, in bytes + // For backwards compatbility we always compute this; we can consider computing it on-demand in the future + pub output_bytes: SafeBytes32, + /// The hash digest, hi 128 bits (range checked by lookup table) + pub output_hi: AssignedValue, + /// The hash digest, lo 128 bits (range checked by lookup table) + pub output_lo: AssignedValue, +} + +impl KeccakFixedLenQuery { + pub fn hi_lo(&self) -> AssignedH256 { + [self.output_hi, self.output_lo] + } +} + +#[derive(Clone, Debug)] +pub struct KeccakVarLenQuery { + pub min_bytes: usize, + // pub max_bytes: usize, // equal to input_assigned.len() + // pub num_bytes: usize, + /// Actual length of input + pub length: AssignedValue, + pub input_assigned: Vec>, + /// The hash digest, in bytes + // For backwards compatbility we always compute this; we can consider computing it on-demand in the future + pub output_bytes: SafeBytes32, + /// The hash digest, hi 128 bits (range checked by lookup table) + pub output_hi: AssignedValue, + /// The hash digest, lo 128 bits (range checked by lookup table) + pub output_lo: AssignedValue, +} + +impl KeccakVarLenQuery { + pub fn hi_lo(&self) -> AssignedH256 { + [self.output_hi, self.output_lo] + } +} + +/// The core logical input to the keccak component circuit. +pub type CoreInputKeccak = Vec>; + +#[derive(Clone, Debug, Default, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub struct OutputKeccakShard { + /// The (assumed to be deduplicated) list of requests, in the form of variable + /// length byte arrays to be hashed. Optionally include the calculated hash. + pub responses: Vec<(Bytes, Option)>, + /// To prevent inconsistencies, also specify the capacity of the keccak circuit + pub capacity: usize, +} + +impl OutputKeccakShard { + /// Createa a dummy OutputKeccakShard with the given capacity. + pub fn create_dummy(capacity: usize) -> Self { + Self { responses: vec![], capacity } + } + pub fn into_logical_results(self) -> Vec>> { + let mut total_capacity = 0; + let mut promise_results = self + .responses + .into_iter() + .map(|(input, output)| { + let input = KeccakLogicalInput::new(input.to_vec()); + total_capacity += get_num_keccak_f(input.bytes.len()); + let v_output = + if let Some(hash) = output { hash.into() } else { input.compute_output::() }; + LogicalResult::>::new(input, v_output) + }) + .collect_vec(); + assert!(total_capacity <= self.capacity); + if total_capacity < self.capacity { + let target_len = self.capacity - total_capacity + promise_results.len(); + let dummy = dummy_circuit_output::(); + promise_results.resize( + target_len, + LogicalResult::new( + KeccakLogicalInput::new(vec![]), + KeccakVirtualOutput:: { + hash: HiLo::from_hi_lo([dummy.hash_hi, dummy.hash_lo]), + }, + ), + ); + } + promise_results + } +} + +/// KeccakLogicalInput is the logical input of Keccak Component. +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct KeccakLogicalInput { + pub bytes: Vec, +} +impl KeccakLogicalInput { + // Create KeccakLogicalInput + pub fn new(bytes: Vec) -> Self { + Self { bytes } + } + pub fn compute_output(&self) -> KeccakVirtualOutput { + let hash = H256(keccak256(&self.bytes)); + hash.into() + } +} + +impl LogicalInputValue for KeccakLogicalInput { + fn get_capacity(&self) -> usize { + get_num_keccak_f(self.bytes.len()) + } +} + +pub(crate) const NUM_WITNESS_PER_KECCAK_F: usize = 6; +const KECCAK_VIRTUAL_INPUT_FIELD_SIZE: [usize; NUM_WITNESS_PER_KECCAK_F + 1] = [ + 192, 192, 192, 192, 192, 192, // packed_input + 1, // is_final +]; +const KECCAK_VIRTUAL_OUTPUT_FIELD_SIZE: [usize; 2] = [128, 128]; + +/// Virtual input of Keccak Component. +/// TODO: this cannot work if F::capacity < 192. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct KeccakVirtualInput { + // 1 length + 17 64-byte words, every 3 are compressed into 1 witness. + // spec: https://github.com/axiom-crypto/halo2-lib/blob/9e6c9a16196e7e2ce58ccb6ffc31984fc0ba69d9/hashes/zkevm/src/keccak/component/encode.rs#L25 + pub packed_input: [T; NUM_WITNESS_PER_KECCAK_F], + // Whether this is the last chunk of the input. + // TODO: this is hacky because it can be derived from packed_input but it's not really committed. + pub is_final: T, +} + +impl KeccakVirtualInput { + pub fn new(packed_input: [T; NUM_WITNESS_PER_KECCAK_F], is_final: T) -> Self { + Self { packed_input, is_final } + } +} + +impl TryFrom> for KeccakVirtualInput { + type Error = anyhow::Error; + + fn try_from(value: Flatten) -> std::result::Result { + if value.field_size != KECCAK_VIRTUAL_INPUT_FIELD_SIZE { + return Err(anyhow::anyhow!("invalid field size")); + } + if value.field_size.len() != value.fields.len() { + return Err(anyhow::anyhow!("field length doesn't match")); + } + + Ok(Self { + packed_input: value.fields[0..NUM_WITNESS_PER_KECCAK_F] + .try_into() + .map_err(|_| anyhow!("failed to convert flatten to KeccakVirtualInput"))?, + is_final: value.fields[NUM_WITNESS_PER_KECCAK_F], + }) + } +} +impl From> for Flatten { + fn from(val: KeccakVirtualInput) -> Self { + Self { + fields: [val.packed_input.as_slice(), [val.is_final].as_slice()].concat(), + field_size: &KECCAK_VIRTUAL_INPUT_FIELD_SIZE, + } + } +} +impl FixLenLogical for KeccakVirtualInput { + fn get_field_size() -> &'static [usize] { + &KECCAK_VIRTUAL_INPUT_FIELD_SIZE + } +} + +impl From>> + for PoseidonCompactChunkInput +{ + fn from(val: KeccakVirtualInput>) -> Self { + let KeccakVirtualInput::> { packed_input, is_final } = val; + assert!(packed_input.len() % POSEIDON_RATE == 0); + let inputs: Vec<[AssignedValue; POSEIDON_RATE]> = packed_input + .into_iter() + .chunks(POSEIDON_RATE) + .into_iter() + .map(|c| c.collect_vec().try_into().unwrap()) + .collect_vec(); + let is_final = SafeTypeChip::unsafe_to_bool(is_final); + Self::new(inputs, is_final) + } +} + +/// Virtual input of Keccak Component. +#[derive(Default, Debug, Clone, Hash, PartialEq, Eq)] +pub struct KeccakVirtualOutput { + /// Keccak hash result + pub hash: HiLo, +} + +impl KeccakVirtualOutput { + pub fn new(hash: HiLo) -> Self { + Self { hash } + } +} + +impl TryFrom> for KeccakVirtualOutput { + type Error = anyhow::Error; + + fn try_from(value: Flatten) -> std::result::Result { + if value.field_size != KECCAK_VIRTUAL_OUTPUT_FIELD_SIZE { + return Err(anyhow::anyhow!("invalid field size")); + } + if value.field_size.len() != value.fields.len() { + return Err(anyhow::anyhow!("field length doesn't match")); + } + + Ok(Self { + hash: HiLo::from_hi_lo( + value + .fields + .try_into() + .map_err(|_| anyhow!("failed to convert flatten to KeccakVirtualOutput"))?, + ), + }) + } +} +impl From> for Flatten { + fn from(val: KeccakVirtualOutput) -> Self { + Self { fields: val.hash.hi_lo().to_vec(), field_size: &KECCAK_VIRTUAL_OUTPUT_FIELD_SIZE } + } +} +impl FixLenLogical for KeccakVirtualOutput { + fn get_field_size() -> &'static [usize] { + &KECCAK_VIRTUAL_OUTPUT_FIELD_SIZE + } +} +impl From for KeccakVirtualOutput { + fn from(hash: H256) -> Self { + let hash_hi = u128::from_be_bytes(hash[..16].try_into().unwrap()); + let hash_lo = u128::from_be_bytes(hash[16..].try_into().unwrap()); + Self { hash: HiLo::from_hi_lo([F::from_u128(hash_hi), F::from_u128(hash_lo)]) } + } +} + +#[derive(Debug, Clone)] +pub struct ComponentTypeKeccak(PhantomData); + +impl ComponentType for ComponentTypeKeccak { + type InputValue = KeccakVirtualInput; + type InputWitness = KeccakVirtualInput>; + type OutputValue = KeccakVirtualOutput; + type OutputWitness = KeccakVirtualOutput>; + type LogicalInput = KeccakLogicalInput; + type Commiter = KeccakComponentCommiter; + + fn get_type_id() -> ComponentTypeId { + "axiom-eth:ComponentTypeKeccak".to_string() + } + + fn logical_result_to_virtual_rows_impl( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + let virtual_inputs = Self::logical_input_to_virtual_rows_impl(&ins.input); + let len = virtual_inputs.len(); + let mut virtual_outputs = Vec::with_capacity(len); + let dummy = dummy_circuit_output(); + virtual_outputs.resize( + len - 1, + Self::OutputValue { hash: HiLo::from_hi_lo([dummy.hash_hi, dummy.hash_lo]) }, + ); + virtual_outputs.push(ins.output.clone()); + virtual_inputs.into_iter().zip_eq(virtual_outputs).collect_vec() + } + fn logical_input_to_virtual_rows_impl(li: &Self::LogicalInput) -> Vec { + let mut packed_inputs = pack_native_input::(&li.bytes); + let len = packed_inputs.len(); + for (i, packed_input) in packed_inputs.iter_mut().enumerate() { + let is_final = if i + 1 == len { F::ONE } else { F::ZERO }; + packed_input.push(is_final); + } + packed_inputs + .into_iter() + .map(|p| KeccakVirtualInput::try_from_raw(p).unwrap()) + .collect_vec() + } + fn rlc_virtual_rows( + (gate_ctx, rlc_ctx): (&mut Context, &mut Context), + range_chip: &RangeChip, + rlc_chip: &RlcChip, + virtual_rows: &[(Self::InputWitness, Self::OutputWitness)], + ) -> Vec> { + let gate = &range_chip.gate; + let one = gate_ctx.load_constant(F::ONE); + let zero = gate_ctx.load_zero(); + let empty_input_rlc = rlc_chip.rlc_pow_fixed(gate_ctx, gate, NUM_WITNESS_PER_KECCAK_F - 1); + // = rlc_chip.compute_rlc_fixed_len(rlc_ctx, [one, zero, zero, zero, zero, zero]).rlc_val; + // empty_input_rlc[0] = empty_input_len + 1 = 1. empty_input corresponds to input = [] + + let chunk_multiplier = + rlc_chip.rlc_pow_fixed(gate_ctx, &range_chip.gate, NUM_WITNESS_PER_KECCAK_F); + let output_multiplier = rlc_chip.rlc_pow_fixed( + gate_ctx, + &range_chip.gate, + Self::OutputWitness::get_num_fields(), + ); + + // If last chunk is a final chunk. + let mut last_is_final = one; + // RLC of the current logical input. + let mut curr_rlc = zero; + let mut virtual_row_rlcs = Vec::with_capacity(virtual_rows.len()); + for (input, output) in virtual_rows { + let mut input_to_rlc = input.packed_input; + // +1 to length when calculating RLC in order to make sure 0 is not a valid RLC for any input. Therefore the lookup + // table column doesn't need a selector. + input_to_rlc[0] = range_chip.gate.add(gate_ctx, input_to_rlc[0], last_is_final); + + let chunk_rlc = rlc_chip.compute_rlc_fixed_len(rlc_ctx, input_to_rlc).rlc_val; + curr_rlc = range_chip.gate.mul_add(gate_ctx, curr_rlc, chunk_multiplier, chunk_rlc); + + let input_rlc = + range_chip.gate.select(gate_ctx, curr_rlc, empty_input_rlc, input.is_final); + let output_rlc = rlc_chip.compute_rlc_fixed_len(rlc_ctx, output.hash.hi_lo()).rlc_val; + let virtual_row_rlc = + range_chip.gate.mul_add(gate_ctx, input_rlc, output_multiplier, output_rlc); + virtual_row_rlcs.push(virtual_row_rlc); + + curr_rlc = range_chip.gate.select(gate_ctx, zero, curr_rlc, input.is_final); + + last_is_final = input.is_final; + } + virtual_row_rlcs + } +} + +lazy_static! { + /// We cache the dummy circuit output to avoid re-computing it. + /// The recomputation involves creating an optimized Poseidon spec, which is + /// time intensive. + static ref CACHED_DUMMY_CIRCUIT_OUTPUT: RwLock = RwLock::new(TypeMap::new()); +} + +/// The default dummy_circuit_output needs to do Poseidon. Poseidon generic over F +/// requires re-computing the optimized Poseidon spec, which is computationally +/// intensive. Since we call dummy_circuit_output very often, we cache the result +/// as a performance optimization. +fn dummy_circuit_output() -> KeccakCircuitOutput { + use zkevm_hashes::keccak::component::output::dummy_circuit_output; + + let cached_output = + CACHED_DUMMY_CIRCUIT_OUTPUT.read().unwrap().get::>().cloned(); + if let Some(cached_output) = cached_output { + return cached_output; + } + let output = dummy_circuit_output::(); + CACHED_DUMMY_CIRCUIT_OUTPUT.write().unwrap().insert(output); + output +} diff --git a/axiom-eth/src/lib.rs b/axiom-eth/src/lib.rs new file mode 100644 index 00000000..2f8a71bd --- /dev/null +++ b/axiom-eth/src/lib.rs @@ -0,0 +1,30 @@ +#![feature(trait_alias)] +#![feature(associated_type_defaults)] +#![feature(associated_type_bounds)] +#![warn(clippy::useless_conversion)] + +use serde::{de::DeserializeOwned, Serialize}; + +pub use halo2_base; +pub use halo2_base::halo2_proofs; +pub use halo2_base::halo2_proofs::halo2curves; +pub use snark_verifier; +pub use snark_verifier_sdk; +pub use zkevm_hashes; + +pub mod block_header; +pub mod keccak; +pub mod mpt; +pub mod receipt; +pub mod rlc; +pub mod rlp; +pub mod solidity; +pub mod storage; +pub mod transaction; +pub mod utils; + +#[cfg(feature = "providers")] +pub mod providers; + +pub trait RawField = zkevm_hashes::util::eth_types::Field; +pub trait Field = RawField + Serialize + DeserializeOwned; diff --git a/axiom-eth/src/mpt/mod.rs b/axiom-eth/src/mpt/mod.rs new file mode 100644 index 00000000..91e9fb36 --- /dev/null +++ b/axiom-eth/src/mpt/mod.rs @@ -0,0 +1,1015 @@ +//! Merkle Patricia Trie (MPT) inclusion & exclusion proofs in ZK. +//! +//! See https://hackmd.io/@axiom/ry35GZ4l3 for a technical walkthrough of circuit structure and logic +//! +//! # Assumptions +//! - We have tuned our circuit constants (see [`MAX_BRANCH_ITEM_LENS`]) for the case where the MPT value never ends as the 17th item in a branch. +//! - This only happens if a key in the trie is the prefix of another key in the trie. +//! - This never happens for Ethereum tries: +//! - Either the trie has fixed key length (state, storage) +//! - The key is of the form `rlp(idx)` and bytes are converted to even number of hexes (transaction, receipt). +//! If two `i, j` had `rlp(i)` prefix of `rlp(j)`, that means there would have been no way to RLP decode `rlp(j)` (since you would decode it at first as if it were `rlp(i)`). +//! - If one needed to handle this case, one can add some additional handling using a different `MAX_BRANCH_ITEM_LENS` when parsing the terminal node. +use crate::Field; +use crate::{ + keccak::{types::KeccakVarLenQuery, KeccakChip}, + rlc::{ + chip::{rlc_is_equal, rlc_select, rlc_select_by_indicator, rlc_select_from_idx, RlcChip}, + circuit::builder::RlcContextPair, + types::{RlcFixedTrace, RlcTrace, RlcVar}, + }, + rlp::{max_rlp_encoding_len, types::RlpFieldTrace, RlpChip}, +}; +use ethers_core::{types::H256, utils::hex::FromHex}; +use halo2_base::{ + gates::{GateChip, GateInstructions, RangeChip, RangeInstructions}, + utils::{bit_length, log2_ceil, ScalarField}, + AssignedValue, Context, + QuantumCell::{Constant, Existing, Witness}, +}; +use itertools::Itertools; +use lazy_static::lazy_static; +use rlp::Rlp; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::max, + iter::{self}, +}; + +#[cfg(test)] +mod tests; +mod types; + +pub use types::*; + +pub const BRANCH_NUM_ITEMS: usize = 17; +pub const MAX_BRANCH_ITEM_LENS: [usize; BRANCH_NUM_ITEMS] = max_branch_lens(1).0; // max_vt_bytes = 0 is likely also ok; for our use cases, the value in a branch is always empty +pub const MAX_BRANCH_ENCODING_BYTES: usize = max_branch_lens(1).1; + +lazy_static! { + static ref DUMMY_BRANCH: Vec = Vec::from_hex("d18080808080808080808080808080808080").unwrap(); + static ref DUMMY_EXT: Vec = Vec::from_hex( + "e21ba00000000000000000000000000000000000000000000000000000000000000000").unwrap(); + /// rlp(["", 0x0]) + static ref NULL_LEAF: Vec = Vec::from_hex( + "c3818000").unwrap(); + /// keccak(rlp("")) = keccak(0x80) + pub static ref KECCAK_RLP_EMPTY_STRING: Vec = Vec::from_hex( + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").unwrap(); + static ref RLP_EMPTY_STRING: Vec = Vec::from_hex( + "80").unwrap(); +} + +pub const fn max_leaf_lens(max_key_bytes: usize, max_value_bytes: usize) -> ([usize; 2], usize) { + let max_encoded_path_bytes = max_key_bytes + 1; + let max_encoded_path_rlp_bytes = max_rlp_encoding_len(max_encoded_path_bytes); + let max_value_rlp_bytes = max_rlp_encoding_len(max_value_bytes); + let max_field_bytes = [max_encoded_path_rlp_bytes, max_value_rlp_bytes]; + let max_leaf_bytes = max_rlp_encoding_len(max_encoded_path_rlp_bytes + max_value_rlp_bytes); + (max_field_bytes, max_leaf_bytes) +} + +pub const fn max_ext_lens(max_key_bytes: usize) -> ([usize; 2], usize) { + let max_node_ref_bytes = 32; + let max_encoded_path_bytes = max_key_bytes + 1; + let max_encoded_path_rlp_bytes = max_rlp_encoding_len(max_encoded_path_bytes); + let max_node_ref_rlp_bytes = max_rlp_encoding_len(max_node_ref_bytes); + let max_field_bytes = [max_encoded_path_rlp_bytes, max_node_ref_rlp_bytes]; + let max_ext_bytes = max_rlp_encoding_len(max_encoded_path_rlp_bytes + max_node_ref_rlp_bytes); + (max_field_bytes, max_ext_bytes) +} + +pub const fn max_branch_lens(max_vt_bytes: usize) -> ([usize; BRANCH_NUM_ITEMS], usize) { + let max_node_ref_bytes = 32; + let max_node_ref_rlp_bytes = max_rlp_encoding_len(max_node_ref_bytes); + let mut max_field_bytes = [max_node_ref_rlp_bytes; BRANCH_NUM_ITEMS]; + max_field_bytes[BRANCH_NUM_ITEMS - 1] = max_rlp_encoding_len(max_vt_bytes); + let max_field_bytes_sum = 16 * max_node_ref_rlp_bytes + max_field_bytes[BRANCH_NUM_ITEMS - 1]; + let max_branch_bytes = max_rlp_encoding_len(max_field_bytes_sum); + (max_field_bytes, max_branch_bytes) +} + +/// Thread-safe chip for performing Merkle Patricia Trie (MPT) inclusion proofs. +#[derive(Clone, Debug)] +pub struct MPTChip<'r, F: Field> { + pub rlp: RlpChip<'r, F>, + pub keccak: &'r KeccakChip, +} + +impl<'r, F: Field> MPTChip<'r, F> { + pub fn new(rlp: RlpChip<'r, F>, keccak: &'r KeccakChip) -> Self { + Self { rlp, keccak } + } + + pub fn gate(&self) -> &GateChip { + self.rlp.gate() + } + + pub fn range(&self) -> &RangeChip { + self.rlp.range + } + + pub fn rlc(&self) -> &RlcChip { + self.rlp.rlc() + } + + pub fn rlp(&self) -> RlpChip { + self.rlp + } + + pub fn keccak(&self) -> &'r KeccakChip { + self.keccak + } + + /// When one node is referenced inside another node, what is included is H(rlp.encode(x)), where H(x) = keccak256(x) if len(x) >= 32 else x and rlp.encode is the RLP encoding function. + /// + /// Assumes that `bytes` is non-empty. + pub fn mpt_hash_phase0( + &self, + ctx: &mut Context, // ctx_gate in FirstPhase + bytes: AssignedBytes, + len: AssignedValue, + ) -> MPTHashWitness { + assert!(!bytes.is_empty()); + self.keccak.keccak_var_len(ctx, bytes, len, 0usize) + } + + /// When one node is referenced inside another node, what is included is H(rlp.encode(x)), where H(x) = keccak256(x) if len(x) >= 32 else x and rlp.encode is the RLP encoding function. + /// We only return the RLC value of the MPT hash + pub fn mpt_hash_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, // ctxs in SecondPhase + keccak_query: &KeccakVarLenQuery, + ) -> MPTHashTrace { + // RLC of keccak(rlp.encode(x)) + let hash_bytes = keccak_query.output_bytes.as_ref().iter().copied(); + let hash_rlc = self.rlc().compute_rlc_fixed_len(ctx_rlc, hash_bytes); + let len = keccak_query.length; + let max_len = std::cmp::max(keccak_query.input_assigned.len(), 32); + let thirty_two = F::from(32); + let is_short = self.range().is_less_than( + ctx_gate, + len, + Constant(thirty_two), + bit_length(max_len as u64), + ); + let mpt_hash_len = self.gate().select(ctx_gate, len, Constant(thirty_two), is_short); + // input_assigned = rlp.encode(x), we then truncate to at most 32 bytes + let mut input_trunc = keccak_query.input_assigned.clone(); + input_trunc.truncate(32); + // Compute RLC(input_trunc) = RLC( (rlp.encode(x))[0..min(input_assigned.len, 32] ) + // We will only use this value if is_short = 1 + let input_rlc = + self.rlc().compute_rlc((ctx_gate, ctx_rlc), self.gate(), input_trunc, mpt_hash_len); + let mpt_hash_rlc = + self.gate().select(ctx_gate, input_rlc.rlc_val, hash_rlc.rlc_val, is_short); + MPTHashTrace { hash: hash_rlc, mpt_hash: RlcTrace::new(mpt_hash_rlc, mpt_hash_len, 32) } + } + + /// Parse the RLP encoding of an assumed leaf node. + /// Computes the keccak hash (or literal, see [`mpt_hash_phase0`]) of the node's RLP encoding. + /// + /// This is the same as the first part of [`parse_mpt_inclusion_phase0`] + /// except that the assumed maximum length of a leaf node + /// may be different from that of an extension node. + pub fn parse_terminal_node_phase0( + &self, + ctx: &mut Context, + leaf_bytes: MPTNode, + max_key_bytes: usize, + max_value_bytes: usize, + ) -> TerminalWitness { + let (_, max_leaf_bytes) = max_leaf_lens(max_key_bytes, max_value_bytes); + let (_, max_ext_bytes) = max_ext_lens(max_key_bytes); + let max_ext_bytes = max(max_ext_bytes, MAX_BRANCH_ENCODING_BYTES); + let max_leaf_bytes = max(max_ext_bytes, max_leaf_bytes); + assert_eq!(leaf_bytes.rlp_bytes.len(), max_leaf_bytes); + + let [dummy_branch, dummy_ext] = + [DUMMY_BRANCH.clone(), DUMMY_EXT.clone()].map(|mut dummy| { + dummy.resize(max_leaf_bytes, 0u8); + dummy.into_iter().map(|b| Constant(F::from(b as u64))).collect_vec() + }); + + let (ext_in, branch_in): (AssignedBytes, AssignedBytes) = leaf_bytes + .rlp_bytes + .into_iter() + .zip(dummy_ext) + .zip(dummy_branch) + .map(|((node_byte, dummy_ext_byte), dummy_branch_byte)| { + ( + self.gate().select(ctx, node_byte, dummy_ext_byte, leaf_bytes.node_type), + self.gate().select(ctx, dummy_branch_byte, node_byte, leaf_bytes.node_type), + ) + }) + .unzip(); + + let ext_parsed = self.parse_leaf_phase0(ctx, ext_in, max_key_bytes, max_value_bytes); + let branch_parsed = { + assert_eq!(branch_in.len(), max_leaf_bytes); + + let rlp = + self.rlp.decompose_rlp_array_phase0(ctx, branch_in, &MAX_BRANCH_ITEM_LENS, false); + let hash_query = self.mpt_hash_phase0(ctx, rlp.rlp_array.clone(), rlp.rlp_len); + BranchWitness { rlp, hash_query } + }; + TerminalWitness { node_type: leaf_bytes.node_type, ext: ext_parsed, branch: branch_parsed } + } + + pub fn parse_terminal_node_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + witness: TerminalWitness, + ) -> TerminalTrace { + let ext = self.parse_leaf_phase1((ctx_gate, ctx_rlc), witness.ext); + // phase 1 parsing is the same for terminal or non-terminal branches, since the max length is already determined + let branch = self.parse_nonterminal_branch_phase1((ctx_gate, ctx_rlc), witness.branch); + TerminalTrace { node_type: witness.node_type, ext, branch } + } + + pub fn parse_leaf_phase0( + &self, + ctx: &mut Context, + leaf_bytes: AssignedBytes, + max_key_bytes: usize, + max_value_bytes: usize, + ) -> LeafWitness { + let (max_field_bytes, max_leaf_bytes) = max_leaf_lens(max_key_bytes, max_value_bytes); + // for small values, max_ext_bytes may be larger than max_leaf_bytes + let (max_ext_field_bytes, max_ext_bytes) = max_ext_lens(max_key_bytes); + let max_ext_bytes = max(max_ext_bytes, MAX_BRANCH_ENCODING_BYTES); + let max_leaf_bytes = max(max_ext_bytes, max_leaf_bytes); + let max_field_bytes = [max_field_bytes[0], max(max_field_bytes[1], max_ext_field_bytes[1])]; + assert_eq!(leaf_bytes.len(), max_leaf_bytes); + let rlp_witness = + self.rlp.decompose_rlp_array_phase0(ctx, leaf_bytes, &max_field_bytes, false); + // TODO: remove unnecessary clones somehow? + let hash_query = + self.mpt_hash_phase0(ctx, rlp_witness.rlp_array.clone(), rlp_witness.rlp_len); + LeafWitness { rlp: rlp_witness, hash_query } + } + + /// Parse the RLP encoding of an assumed leaf node. + /// Computes the keccak hash (or literal, see [`mpt_hash_phase1`]) of the node's RLP encoding. + pub fn parse_leaf_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + witness: LeafWitness, + ) -> LeafTrace { + let rlp_trace = + self.rlp.decompose_rlp_array_phase1((ctx_gate, ctx_rlc), witness.rlp, false); + let [key_path, value]: [RlpFieldTrace; 2] = rlp_trace.field_trace.try_into().unwrap(); + let rlcs = self.mpt_hash_phase1((ctx_gate, ctx_rlc), &witness.hash_query); + LeafTrace { key_path, value, rlcs } + } + + /// Parse the RLP encoding of an assumed extension node. + /// Computes the keccak hash (or literal, see [`mpt_hash_phase0`]) of the node's RLP encoding. + pub fn parse_ext_phase0( + &self, + ctx: &mut Context, + ext_bytes: AssignedBytes, + max_key_bytes: usize, + ) -> ExtensionWitness { + let (max_field_bytes, max_ext_bytes) = max_ext_lens(max_key_bytes); + let max_ext_bytes = max(max_ext_bytes, MAX_BRANCH_ENCODING_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); + let hash_query = + self.mpt_hash_phase0(ctx, rlp_witness.rlp_array.clone(), rlp_witness.rlp_len); + ExtensionWitness { rlp: rlp_witness, hash_query } + } + + /// Parse the RLP encoding of an assumed extension node. + /// Computes the keccak hash (or literal, see [`mpt_hash_phase1`]) of the node's RLP encoding. + pub fn parse_ext_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + witness: ExtensionWitness, + ) -> ExtensionTrace { + let rlp_trace = + self.rlp.decompose_rlp_array_phase1((ctx_gate, ctx_rlc), witness.rlp, false); + let [key_path, node_ref]: [RlpFieldTrace; 2] = rlp_trace.field_trace.try_into().unwrap(); + let rlcs = self.mpt_hash_phase1((ctx_gate, ctx_rlc), &witness.hash_query); + ExtensionTrace { key_path, node_ref, rlcs } + } + + /// Parse the RLP encoding of an assumed branch node. + /// Computes the keccak hash (or literal, see [`mpt_hash_phase0`]) of the node's RLP encoding. + pub fn parse_nonterminal_branch_phase0( + &self, + ctx: &mut Context, + branch_bytes: AssignedBytes, + max_key_bytes: usize, + ) -> BranchWitness { + let (_, max_ext_bytes) = max_ext_lens(max_key_bytes); + let max_branch_bytes = max(max_ext_bytes, MAX_BRANCH_ENCODING_BYTES); + assert_eq!(branch_bytes.len(), max_branch_bytes); + + let rlp_witness = + self.rlp.decompose_rlp_array_phase0(ctx, branch_bytes, &MAX_BRANCH_ITEM_LENS, false); + let hash_query = + self.mpt_hash_phase0(ctx, rlp_witness.rlp_array.clone(), rlp_witness.rlp_len); + BranchWitness { rlp: rlp_witness, hash_query } + } + + pub fn parse_nonterminal_branch_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + witness: BranchWitness, + ) -> BranchTrace { + let rlp_trace = + self.rlp.decompose_rlp_array_phase1((ctx_gate, ctx_rlc), witness.rlp, false); + let node_refs: [RlpFieldTrace; 17] = rlp_trace.field_trace.try_into().unwrap(); + let rlcs = self.mpt_hash_phase1((ctx_gate, ctx_rlc), &witness.hash_query); + BranchTrace { node_refs, rlcs } + } + + pub fn compute_rlc_trace( + &self, + ctx: RlcContextPair, + inputs: Vec>, + len: AssignedValue, + ) -> RlcTrace { + self.rlc().compute_rlc(ctx, self.gate(), inputs, len) + } + + /* Inputs must follow the following unchecked constraints: + * keys must have positive length + * values must have length at least 32 + * trie has to be nonempty (root is not KECCAK_RLP_EMPTY_STRING) + */ + /// Loads input witnesses into the circuit and parses the RLP encoding of nodes and leaves + pub fn parse_mpt_inclusion_phase0( + &self, + ctx: &mut Context, + proof: MPTProof, + ) -> MPTProofWitness { + let max_key_byte_len = proof.max_key_byte_len; + let value_max_byte_len = proof.value_bytes.len(); + let max_depth = proof.max_depth; + assert_eq!(proof.nodes.len(), max_depth - 1); + assert_eq!(proof.root_hash_bytes.len(), 32); + assert_eq!(proof.key_bytes.len(), max_key_byte_len); + let (_, ext_max_byte_len) = max_ext_lens(max_key_byte_len); + let node_max_byte_len = max(ext_max_byte_len, MAX_BRANCH_ENCODING_BYTES); + + let [dummy_branch, dummy_ext] = + [DUMMY_BRANCH.clone(), DUMMY_EXT.clone()].map(|mut dummy| { + dummy.resize(node_max_byte_len, 0u8); + dummy.into_iter().map(|b| Constant(F::from(b as u64))).collect_vec() + }); + + /* Validate inputs, check that: + * all inputs are bytes + * node_types[idx] in {0, 1} + * key_frag_is_odd[idx] in {0, 1} + * slot_is_empty in {0, 1} + * key_frag_hexes are hexs + * 0 <= depth <= max_depth + * 0 <= value_byte_len <= value_max_byte_len + * 0 <= key_frag_byte_len[idx] <= key_byte_len + 1 + */ + for byte in iter::empty() + .chain(proof.key_bytes.iter()) + .chain(proof.value_bytes.iter()) + .chain(proof.root_hash_bytes.iter()) + .chain(proof.leaf.rlp_bytes.iter()) + .chain(proof.nodes.iter().flat_map(|node| node.rlp_bytes.iter())) + { + self.range().range_check(ctx, *byte, 8); + } + for bit in iter::once(&proof.slot_is_empty) + .chain(iter::once(&proof.leaf.node_type)) + .chain(proof.nodes.iter().map(|node| &node.node_type)) + .chain(proof.key_frag.iter().map(|frag| &frag.is_odd)) + { + self.gate().assert_bit(ctx, *bit); + } + for nibble in proof.key_frag.iter().flat_map(|frag| frag.nibbles.iter()) { + self.range().range_check(ctx, *nibble, 4); + } + self.range().check_less_than_safe(ctx, proof.depth, max_depth as u64 + 1); + self.range().check_less_than_safe(ctx, proof.value_byte_len, value_max_byte_len as u64 + 1); + if let Some(key_byte_len) = proof.key_byte_len { + self.range().check_less_than_safe(ctx, key_byte_len, max_key_byte_len as u64 + 1); + let two = ctx.load_constant(F::from(2u64)); + let frag_ub = self.gate().add(ctx, two, key_byte_len); + for frag_len in proof.key_frag.iter().map(|frag| frag.byte_len) { + self.range().check_less_than( + ctx, + frag_len, + frag_ub, + log2_ceil(max_key_byte_len as u64) + 2, + ); + } + } else { + for frag_len in proof.key_frag.iter().map(|frag| frag.byte_len) { + self.range().check_less_than_safe(ctx, frag_len, max_key_byte_len as u64 + 2); + } + } + /* Parse RLP + * RLP Terminal for leaf + * RLP Extension for select(dummy_extension[idx], nodes[idx], node_types[idx]) + * RLP Branch for select(nodes[idx], dummy_branch[idx], node_types[idx]) + */ + let terminal_node = + self.parse_terminal_node_phase0(ctx, proof.leaf, max_key_byte_len, value_max_byte_len); + let nodes: Vec<_> = proof + .nodes + .into_iter() + .map(|node| { + assert_eq!(node.rlp_bytes.len(), node_max_byte_len); + let (ext_in, branch_in): (AssignedBytes, AssignedBytes) = node + .rlp_bytes + .iter() + .zip(dummy_ext.iter()) + .zip(dummy_branch.iter()) + .map(|((&node_byte, &dummy_ext_byte), &dummy_branch_byte)| { + ( + self.gate().select(ctx, node_byte, dummy_ext_byte, node.node_type), + self.gate().select(ctx, dummy_branch_byte, node_byte, node.node_type), + ) + }) + .unzip(); + + let ext_parsed = self.parse_ext_phase0(ctx, ext_in, max_key_byte_len); + let branch_parsed = + self.parse_nonterminal_branch_phase0(ctx, branch_in, max_key_byte_len); + MPTNodeWitness { node_type: node.node_type, ext: ext_parsed, branch: branch_parsed } + }) + .collect(); + // Check key fragment and prefix consistency + let mut key_frag_ext_bytes = Vec::with_capacity(max_depth - 1); + let mut key_frag_leaf_bytes = Vec::with_capacity(max_depth); + let mut frag_lens = Vec::with_capacity(max_depth); + // assert to avoid capacity checks? + assert_eq!(proof.key_frag.len(), max_depth); + + for (idx, key_frag) in proof.key_frag.iter().enumerate() { + assert_eq!(key_frag.nibbles.len(), 2 * max_key_byte_len); + let leaf_path_bytes = hex_prefix_encode( + ctx, + self.gate(), + &key_frag.nibbles, + key_frag.is_odd, + max_key_byte_len, + false, + ); + if idx < max_depth - 1 { + // all except first byte are same as `leaf_path_bytes` + let ext_path_byte_first = hex_prefix_encode_first( + ctx, + self.gate(), + key_frag.nibbles[0], + key_frag.is_odd, + true, + ); + let ext_path_bytes = [&[ext_path_byte_first], &leaf_path_bytes[1..]].concat(); + key_frag_ext_bytes.push(ext_path_bytes); + } + key_frag_leaf_bytes.push(leaf_path_bytes); + + let frag_len = hex_prefix_len(ctx, self.gate(), key_frag.byte_len, key_frag.is_odd); + frag_lens.push(frag_len); + } + + let mut key_hexs = Vec::with_capacity(2 * max_key_byte_len); + for byte in proof.key_bytes.into_iter() { + let bits = self.gate().num_to_bits(ctx, byte, 8); + let hexs = [4, 0].map(|i| { + self.gate().inner_product( + ctx, + bits[i..i + 4].iter().copied(), + (0..4).map(|x| Constant(self.gate().pow_of_two()[x])), + ) + }); + key_hexs.extend(hexs); + } + MPTProofWitness { + value_bytes: proof.value_bytes, + value_byte_len: proof.value_byte_len, + root_hash_bytes: proof.root_hash_bytes, + key_byte_len: proof.key_byte_len, + depth: proof.depth, + nodes, + terminal_node, + slot_is_empty: proof.slot_is_empty, + max_key_byte_len, + max_depth, + key_frag: proof.key_frag, + key_frag_ext_bytes, + key_frag_leaf_bytes, + frag_lens, + key_hexs, + } + } + + /// Checks constraints after the proof is parsed in phase 0 + pub fn parse_mpt_inclusion_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + mut witness: MPTProofWitness, + ) { + let gate = self.gate(); + let max_depth = witness.max_depth; + let terminal_node = + self.parse_terminal_node_phase1((ctx_gate, ctx_rlc), witness.terminal_node.clone()); + let nodes: Vec> = witness + .nodes + .into_iter() + .map(|node| { + let ext_parsed = self.parse_ext_phase1((ctx_gate, ctx_rlc), node.ext); + let branch_parsed = + self.parse_nonterminal_branch_phase1((ctx_gate, ctx_rlc), node.branch); + MPTNodeTrace { node_type: node.node_type, ext: ext_parsed, branch: branch_parsed } + }) + .collect(); + let key_frag_ext_rlcs: Vec<_> = witness + .key_frag_ext_bytes + .into_iter() + .zip(witness.key_frag.iter()) + .map(|(bytes, frag)| self.compute_rlc_trace((ctx_gate, ctx_rlc), bytes, frag.byte_len)) + .collect(); + let key_frag_leaf_rlcs: Vec<_> = witness + .key_frag_leaf_bytes + .into_iter() + .zip(witness.key_frag.iter()) + .map(|(bytes, frag)| self.compute_rlc_trace((ctx_gate, ctx_rlc), bytes, frag.byte_len)) + .collect(); + let key_hexs = witness.key_hexs; + let slot_is_empty = witness.slot_is_empty; + let slot_is_occupied = self.gate().not(ctx_gate, slot_is_empty); + let mut proof_is_empty = self.gate().is_zero(ctx_gate, witness.depth); + proof_is_empty = self.gate().and(ctx_gate, proof_is_empty, slot_is_empty); + // set `depth = 1` if proof is empty + let pseudo_depth = self.gate().add(ctx_gate, witness.depth, proof_is_empty); + let pseudo_depth_minus_one = self.gate().sub(ctx_gate, pseudo_depth, Constant(F::ONE)); + // pseudo_depth_minus_one_indicator[idx] = (idx == pseudo_depth - 1); this is used many times below + let pseudo_depth_minus_one_indicator = + self.gate().idx_to_indicator(ctx_gate, pseudo_depth_minus_one, max_depth); + + // Match fragments to node key + for (((key_frag_ext_rlc, node), is_last), frag_len) in key_frag_ext_rlcs + .into_iter() + .zip(nodes.iter()) + .zip(pseudo_depth_minus_one_indicator.iter()) + .zip(witness.frag_lens.iter_mut()) + { + // When node is extension, check node key RLC equals key frag RLC + let node_key_path_rlc = rlc_select( + ctx_gate, + gate, + terminal_node.ext.key_path.field_trace, + node.ext.key_path.field_trace, + *is_last, + ); + let node_type = + self.gate().select(ctx_gate, terminal_node.node_type, node.node_type, *is_last); + let mut node_key_is_equal = + rlc_is_equal(ctx_gate, self.gate(), node_key_path_rlc, key_frag_ext_rlc); + // The key fragments must be equal unless either: + // * node is not extension + // * slot_is_empty and this is the last node + // If slot_is_empty && this is the last node && node is extension, then node key fragment must NOT equal key fragment (which is the last key fragment) + // Reminder: node_type = 1 if extension, 0 if branch + let is_ext = node_type; + let is_branch = self.gate().not(ctx_gate, node_type); + // is_ext ? node_key_is_equal : 1 + node_key_is_equal = self.gate().mul_add(ctx_gate, node_key_is_equal, is_ext, is_branch); + // !is_last || !is_ext = !(is_last && is_ext) = 1 - is_last * is_ext + let mut expected = self.gate().sub_mul(ctx_gate, Constant(F::ONE), *is_last, is_ext); + // (slot_is_empty ? !(is_last && is_ext) : 1), we cache slot_is_occupied as an optimization + let is_not_last = self.gate().not(ctx_gate, *is_last); + let slot_is_occupied_expected = + self.gate().and(ctx_gate, slot_is_occupied, is_not_last); + expected = + self.gate().mul_add(ctx_gate, expected, slot_is_empty, slot_is_occupied_expected); + // assuming node type is not extension if idx > pf.len() [we don't care what happens for these idx] + ctx_gate.constrain_equal(&node_key_is_equal, &expected); + + // We enforce that the frag_len is 1 if the node is a branch, unless it is the last node (idx = depth - 1) + // This check is only necessary if slot_is_empty; otherwise, the key length and overall concatenation check will enforce this + let is_branch_and_not_last = self.gate().mul_not(ctx_gate, *is_last, is_branch); + *frag_len = + self.gate().select(ctx_gate, Constant(F::ONE), *frag_len, is_branch_and_not_last); + } + // match hex-prefix encoding of leaf path (gotten from witness.key_frag) to the parsed leaf encoded path + // ignore leaf if slot_is_empty + { + let leaf_encoded_path_rlc = rlc_select_by_indicator( + ctx_gate, + self.gate(), + key_frag_leaf_rlcs, + pseudo_depth_minus_one_indicator.clone(), + ); + let mut check = rlc_is_equal( + ctx_gate, + self.gate(), + leaf_encoded_path_rlc, + terminal_node.ext.key_path.field_trace, + ); + check = self.gate().or(ctx_gate, check, slot_is_empty); + self.gate().assert_is_const(ctx_gate, &check, &F::ONE); + } + + // Check key fragments concatenate to key using hex RLC + // We supply witness key fragments so this check passes even if the slot is empty + let fragment_first_nibbles = { + let key_hex_rlc = if let Some(key_byte_len) = witness.key_byte_len { + // key_hex_len = 2 * key_byte_len + let key_hex_len = self.gate().add(ctx_gate, key_byte_len, key_byte_len); + self.rlc().compute_rlc((ctx_gate, ctx_rlc), self.gate(), key_hexs, key_hex_len) + } else { + let RlcFixedTrace { rlc_val, len: max_len } = + self.rlc().compute_rlc_fixed_len(ctx_rlc, key_hexs); + let len = ctx_gate.load_constant(F::from(max_len as u64)); + RlcTrace { rlc_val, len, max_len } + }; + let (fragment_rlcs, fragment_first_nibbles): (Vec<_>, Vec<_>) = witness + .key_frag + .into_iter() + .zip(witness.frag_lens) + .map(|(key_frag, frag_len)| { + let first_nibble = key_frag.nibbles[0]; + ( + self.rlc().compute_rlc( + (ctx_gate, ctx_rlc), + self.gate(), + key_frag.nibbles, + frag_len, + ), + first_nibble, + ) + }) + .unzip(); + self.rlc().load_rlc_cache( + (ctx_gate, ctx_rlc), + self.gate(), + bit_length(2 * witness.max_key_byte_len as u64), + ); + self.rlc().constrain_rlc_concat( + ctx_gate, + self.gate(), + fragment_rlcs, + &key_hex_rlc, + Some(pseudo_depth), + ); + fragment_first_nibbles + }; + /* Check value matches. Currently value_bytes is RLC encoded + * and value_byte_len is the RLC encoding's length + */ + { + let value_rlc_trace = self.rlp.rlc().compute_rlc( + (ctx_gate, ctx_rlc), + self.gate(), + witness.value_bytes.clone(), + witness.value_byte_len, + ); + // value doesn't matter if slot is empty; by default we will make leaf.value = 0 in that case + let branch_value_trace = terminal_node.branch.node_refs[16].field_trace; + let value_trace = rlc_select( + ctx_gate, + self.gate(), + terminal_node.ext.value.field_trace, + branch_value_trace, + terminal_node.node_type, + ); + let value_equals_leaf = + rlc_is_equal(ctx_gate, self.gate(), value_rlc_trace, value_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); + } + /* + Check hash chains: + Recall that nodes.len() = slot_is_empty ? depth : depth - 1 + + * hash(nodes[0]) == root_hash IF !proof_is_empty + * hash(nodes[idx + 1]) is in nodes[idx] for idx in 0..depth - 2 + * hash(slot_is_empty ? nodes[depth - 1] : leaf_bytes) is in nodes[depth - 2] + + if slot_is_empty: + we assume that depth < max_depth: if depth == max_depth, then we set hash(nodes[depth - 1]) := 0. The circuit will try to prove 0 in nodes[max_depth - 2], which will either fail or (succeed => still shows MPT does not include `key`) + if proof_is_empty: + then root_hash = keccak(rlp("")) = keccak(0x80) = 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 + */ + // if proof_is_empty, then root_hash = keccak(rlp("")) = keccak(0x80) = 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 + let root_hash_rlc = self.rlc().compute_rlc_fixed_len(ctx_rlc, witness.root_hash_bytes); + + let null_root_rlc = { + let keccak_rlp_null = KECCAK_RLP_EMPTY_STRING + .iter() + .map(|b| ctx_gate.load_constant(F::from(*b as u64))) + .collect::>(); + self.rlc().compute_rlc_fixed_len(ctx_rlc, keccak_rlp_null) + }; + let root_is_null = + self.gate().is_equal(ctx_gate, root_hash_rlc.rlc_val, null_root_rlc.rlc_val); + + let mut matches = Vec::with_capacity(max_depth); + let mut branch_refs_are_null = Vec::with_capacity(max_depth); + // assert so later array indexing doesn't do bound check + assert_eq!(nodes.len(), max_depth - 1); + + // makes match_sums[idx] = idx later on + matches.push(ctx_gate.load_constant(F::ZERO)); + + let pseudo_depth_indicator = + self.gate().idx_to_indicator(ctx_gate, pseudo_depth, max_depth); + + let leaf_hash_rlc = terminal_node.mpt_hash_rlc(ctx_gate, self.gate()); + // TODO: maybe use rust iterators instead here, would make it harder to read though + + for idx in 0..max_depth { + if idx == 0 { + // if !proof_is_empty: + // check hash(nodes[0]) == root_hash + // else: + // check root_hash == keccak(rlp("")) + + // The root_hash is always the keccak hash of the root node. + // However `node_hash_rlc` above is the `mpt_hash` of the node, which could be + // just the RLP of the node itself is its length is less than 32 bytes. + // Therefore we have to specially extract the actual hash (denoted _hash32) in this case below + let leaf_hash32_rlc = terminal_node.keccak_rlc(ctx_gate, self.gate()); + let node_hash32_rlc = if idx < max_depth - 1 { + let node_hash32_rlc = nodes[idx].keccak_rlc(ctx_gate, self.gate()); + // is_last = (idx == pseudo_depth - 1) + let is_last = pseudo_depth_minus_one_indicator[idx]; + //self.gate().mul_not(ctx_gate, slot_is_empty, is_last); + self.gate().select(ctx_gate, leaf_hash32_rlc, node_hash32_rlc, is_last) + } else { + leaf_hash32_rlc + }; + let mut root_check = + self.gate().is_equal(ctx_gate, node_hash32_rlc, root_hash_rlc.rlc_val); + root_check = self.gate().select(ctx_gate, root_is_null, root_check, proof_is_empty); + self.gate().assert_is_const(ctx_gate, &root_check, &F::ONE); + } else { + // we check that the mpt hash of a node matches its reference in a previous node + // since `terminal_node` is stored separately, we always need extra handling for it, with a select + let mut node_hash_rlc = leaf_hash_rlc; + if idx < nodes.len() { + node_hash_rlc = nodes[idx].mpt_hash_rlc(ctx_gate, self.gate()); + // is_last = (idx == pseudo_depth - 1) + let is_last = pseudo_depth_minus_one_indicator[idx]; + node_hash_rlc = + rlc_select(ctx_gate, self.gate(), leaf_hash_rlc, node_hash_rlc, is_last); + } + let prev_is_last = pseudo_depth_indicator[idx]; + // Get the previous node. there are three types to consider: + // - extension + // - branch + // - terminal branch + // - terminal branch should be used instead of branch if `prev_is_last == true` + // In each case, if the current node is extension/leaf AND the value of the current node is small enough, + // then the previous node contains the RLP of the current node as a 2-item list, and not as a hash (byte string). + // We therefore need to parse the node reference in two ways to account for this. + // This condition is equivalent to when `node_hash_rlc` is actually not the keccak of the node, but the RLP of the node itself. + let ext_ref_rlc = nodes[idx - 1].ext.node_ref.field_trace; + let ext_ref_rlp_rlc = nodes[idx - 1].ext.node_ref.rlp_trace; + let nibble = fragment_first_nibbles[idx - 1]; + let [(branch_ref_rlc, branch_ref_rlp_rlc), (terminal_branch_ref_rlc, terminal_branch_ref_rlp_rlc)] = + [&nodes[idx - 1].branch.node_refs, &terminal_node.branch.node_refs].map( + |node_refs| { + // the RLC of the decoded node reference, assuming it's a byte string + let ref_rlc = rlc_select_from_idx( + ctx_gate, + self.gate(), + node_refs.iter().map(|node| node.field_trace), + nibble, + ); + // the RLC of the RLP encoding of the node reference + let ref_rlp_rlc = rlc_select_from_idx( + ctx_gate, + self.gate(), + node_refs.iter().map(|node| node.rlp_trace), + nibble, + ); + (ref_rlc, ref_rlp_rlc) + }, + ); + let mut get_maybe_short_ref_rlc = + |ref_rlc: RlcVar, ref_rlp_rlc: RlcVar| -> RlcVar { + let is_short = self.range().is_less_than_safe(ctx_gate, ref_rlc.len, 32); + let is_null = self.range().is_less_than_safe(ctx_gate, ref_rlc.len, 2); + let is_not_hash = self.gate().mul_not(ctx_gate, is_null, is_short); + rlc_select(ctx_gate, self.gate(), ref_rlp_rlc, ref_rlc, is_not_hash) + }; + let mut branch_ref_rlc = + get_maybe_short_ref_rlc(branch_ref_rlc, branch_ref_rlp_rlc); + let ext_ref_rlc = + get_maybe_short_ref_rlc(ext_ref_rlc.into(), ext_ref_rlp_rlc.into()); + let terminal_branch_ref_rlc = + get_maybe_short_ref_rlc(terminal_branch_ref_rlc, terminal_branch_ref_rlp_rlc); + branch_ref_rlc = rlc_select( + ctx_gate, + self.gate(), + terminal_branch_ref_rlc, + branch_ref_rlc, + prev_is_last, + ); + let branch_ref_rlp_rlc = rlc_select_from_idx( + ctx_gate, + self.gate(), + nodes[idx - 1].branch.node_refs.iter().map(|node| node.rlp_trace), + fragment_first_nibbles[idx - 1], + ); + let mut terminal_branch_ref_rlc = rlc_select_from_idx( + ctx_gate, + self.gate(), + terminal_node.branch.node_refs.iter().map(|node| node.field_trace), + fragment_first_nibbles[idx - 1], + ); + let terminal_branch_ref_rlp_rlc = rlc_select_from_idx( + ctx_gate, + self.gate(), + terminal_node.branch.node_refs.iter().map(|node| node.rlp_trace), + fragment_first_nibbles[idx - 1], + ); + let is_short = self.range().is_less_than_safe(ctx_gate, branch_ref_rlc.len, 32); + let is_null = self.range().is_less_than_safe(ctx_gate, branch_ref_rlc.len, 2); + let is_not_null = self.gate().not(ctx_gate, is_null); + let is_not_hash = self.gate().and(ctx_gate, is_short, is_not_null); + branch_ref_rlc = rlc_select( + ctx_gate, + self.gate(), + branch_ref_rlp_rlc, + branch_ref_rlc, + is_not_hash, + ); + let is_short = self.range().is_less_than_safe(ctx_gate, ext_ref_rlc.len, 32); + let is_null = self.range().is_less_than_safe(ctx_gate, ext_ref_rlc.len, 2); + let is_not_null = self.gate().not(ctx_gate, is_null); + let is_not_hash = self.gate().and(ctx_gate, is_short, is_not_null); + let ext_ref_rlc = + rlc_select(ctx_gate, self.gate(), ext_ref_rlp_rlc, ext_ref_rlc, is_not_hash); + let is_short = + self.range().is_less_than_safe(ctx_gate, terminal_branch_ref_rlc.len, 32); + let is_null = + self.range().is_less_than_safe(ctx_gate, terminal_branch_ref_rlc.len, 2); + let is_not_null = self.gate().not(ctx_gate, is_null); + let is_not_hash = self.gate().and(ctx_gate, is_short, is_not_null); + terminal_branch_ref_rlc = rlc_select( + ctx_gate, + self.gate(), + terminal_branch_ref_rlp_rlc, + terminal_branch_ref_rlc, + is_not_hash, + ); + branch_ref_rlc = rlc_select( + ctx_gate, + self.gate(), + terminal_branch_ref_rlc, + branch_ref_rlc, + prev_is_last, + ); + // branch_ref_rlc should equal NULL = "" (empty string) if slot_is_empty and idx == depth and nodes[idx - 1] is a branch node; we save these checks for all idx and `select` for `depth` later + let branch_ref_is_null = self.gate().is_zero(ctx_gate, branch_ref_rlc.len); + branch_refs_are_null.push(branch_ref_is_null); + + // the node that nodes[idx - 1] actually points to + let match_hash_rlc = rlc_select( + ctx_gate, + self.gate(), + ext_ref_rlc, + branch_ref_rlc, + nodes[idx - 1].node_type, // does not need terminal_node.node_type because match_cnt ignores idx >= depth + ); + if idx == max_depth - 1 { + // if slot_is_empty: we set hash(nodes[max_depth - 1]) := 0 to rule out the case depth == max_depth + node_hash_rlc.rlc_val = + self.gate().mul_not(ctx_gate, slot_is_empty, node_hash_rlc.rlc_val); + } + let is_match = rlc_is_equal(ctx_gate, self.gate(), match_hash_rlc, node_hash_rlc); + matches.push(is_match); + } + } + // padding by 0 to avoid empty vector + branch_refs_are_null.push(ctx_gate.load_constant(F::from(0))); + // constrain hash chain + { + let match_sums = + self.gate().partial_sums(ctx_gate, matches.iter().copied()).collect_vec(); + let match_cnt = self.gate().select_by_indicator( + ctx_gate, + match_sums.into_iter().map(Existing), + pseudo_depth_minus_one_indicator.clone(), + ); + ctx_gate.constrain_equal(&match_cnt, &pseudo_depth_minus_one); + } + // if slot_is_empty: check that nodes[depth - 1] points to null if it is branch node + { + let mut branch_ref_check = self.gate().select_by_indicator( + ctx_gate, + branch_refs_are_null.into_iter().map(Existing), + pseudo_depth_minus_one_indicator, + ); + branch_ref_check = self.gate().or(ctx_gate, branch_ref_check, terminal_node.node_type); + branch_ref_check = + self.gate().select(ctx_gate, branch_ref_check, Constant(F::ONE), slot_is_empty); + // nothing to check if proof is empty + branch_ref_check = self.gate().or(ctx_gate, branch_ref_check, proof_is_empty); + self.gate().assert_is_const(ctx_gate, &branch_ref_check, &F::ONE); + } + } +} + +/// # Assumptions +/// * `is_odd` is either 0 or 1 +pub fn hex_prefix_encode_first( + ctx: &mut Context, + gate: &impl GateInstructions, + first_nibble: AssignedValue, + is_odd: AssignedValue, + is_ext: bool, +) -> AssignedValue { + let sixteen = F::from(16); + let thirty_two = F::from(32); + if is_ext { + gate.inner_product( + ctx, + [Existing(is_odd), Existing(is_odd)], + [Constant(sixteen), Existing(first_nibble)], + ) + } else { + // (1 - is_odd) * 32 + is_odd * (48 + x_0) + // | 32 | 16 | is_odd | 32 + 16 * is_odd | is_odd | x_0 | out | + let tmp = gate.mul_add(ctx, Constant(sixteen), is_odd, Constant(thirty_two)); + gate.mul_add(ctx, is_odd, first_nibble, tmp) + } +} + +/// # Assumptions +/// * `is_odd` is either 0 or 1 +pub fn hex_prefix_encode( + ctx: &mut Context, + gate: &impl GateInstructions, + key_frag_hexs: &[AssignedValue], + is_odd: AssignedValue, + key_byte_len: usize, + is_ext: bool, +) -> AssignedBytes { + let mut path_bytes = Vec::with_capacity(key_byte_len); + let sixteen = F::from(16); + for byte_idx in 0..=key_byte_len { + if byte_idx == 0 { + let byte = hex_prefix_encode_first(ctx, gate, key_frag_hexs[0], is_odd, is_ext); + path_bytes.push(byte); + } else { + let [odd_byte, even_byte] = [0, 1].map(|is_even| { + gate.mul_add( + ctx, + Existing(key_frag_hexs[2 * byte_idx - 1 - is_even]), + Constant(sixteen), + if is_even == 0 && byte_idx >= key_byte_len { + Constant(F::ZERO) + } else { + Existing(key_frag_hexs[2 * byte_idx - is_even]) + }, + ) + }); + let byte = gate.select(ctx, odd_byte, even_byte, is_odd); + path_bytes.push(byte); + } + } + path_bytes +} + +pub fn hex_prefix_len( + ctx: &mut Context, + gate: &impl GateInstructions, + key_frag_byte_len: AssignedValue, + is_odd: AssignedValue, +) -> AssignedValue { + let two = F::from(2); + let pre_val = two * key_frag_byte_len.value() + is_odd.value(); + // 2 * key_frag_byte_len + is_odd - 2 + let val = pre_val - two; + let hex_len = ctx.assign_region_last( + [ + Existing(is_odd), + Constant(two), + Existing(key_frag_byte_len), + Witness(pre_val), + Constant(-two), + Constant(F::ONE), + Witness(val), + ], + [0, 3], + ); + let byte_len_is_zero = gate.is_zero(ctx, key_frag_byte_len); + // TODO: should we constrain is_odd to be 0 when is_zero = 1? + gate.select(ctx, Constant(F::ZERO), hex_len, byte_len_is_zero) +} + +#[test] +fn test_dummy_branch() { + assert_eq!( + ::rlp::encode_list::, Vec>(&vec![vec![]; 17]).as_ref(), + &DUMMY_BRANCH[..] + ); +} diff --git a/axiom-eth/src/mpt/tests/README.md b/axiom-eth/src/mpt/tests/README.md new file mode 100644 index 00000000..4b55f649 --- /dev/null +++ b/axiom-eth/src/mpt/tests/README.md @@ -0,0 +1,7 @@ +Here is some useful information regarding the mpt_tests + +With 500 keys, it is sufficient to use the parameters max_depth = 6 and max_key_byte_len = 3. + +loose - in these tests, we use max_depth = 6 and max_key_byte_len = 32, key_byte_len = Some(key.len()) +tight - in these tests, we use max_depth = proof.len() + slot_is_empty, max_key_byte_len = key.len(), key_byte_len = Some(key.len()) +fixed - in these tests, we use max_depth = 6, max_key_byte_len = key.len(), key_byte_len = None \ No newline at end of file diff --git a/axiom-eth/src/mpt/tests/mod.rs b/axiom-eth/src/mpt/tests/mod.rs new file mode 100644 index 00000000..9839a277 --- /dev/null +++ b/axiom-eth/src/mpt/tests/mod.rs @@ -0,0 +1,231 @@ +/// Tests using cita-trie to generate random tx tries +mod tx; + +use super::*; +use crate::{ + rlc::circuit::{builder::RlcCircuitBuilder, RlcCircuitParams}, + utils::eth_circuit::{ + create_circuit, EthCircuitImpl, EthCircuitInstructions, EthCircuitParams, + }, +}; +use ark_std::{end_timer, start_timer}; +use ethers_core::utils::keccak256; +use halo2_base::{ + gates::circuit::{BaseCircuitParams, CircuitBuilderStage}, + halo2_proofs::{ + dev::MockProver, + halo2curves::bn256::Fr, + plonk::{keygen_pk, keygen_vk}, + }, + utils::{ + fs::gen_srs, + testing::{check_proof_with_instances, gen_proof_with_instances}, + }, +}; +use hex::FromHex; +use std::{fs::File, io::Write, marker::PhantomData, path::Path}; +use test_case::test_case; +use test_log::test; + +const TEST_K: u32 = 15; + +#[derive(Clone)] +struct MptTest(MPTInput, PhantomData); + +impl EthCircuitInstructions for MptTest { + type FirstPhasePayload = MPTProofWitness; + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + ) -> Self::FirstPhasePayload { + let ctx = builder.base.main(0); + let mpt_proof = self.0.clone().assign(ctx); + mpt.parse_mpt_inclusion_phase0(ctx, mpt_proof) + } + + fn virtual_assign_phase1( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + mpt_witness: Self::FirstPhasePayload, + ) { + let (ctx_gate, ctx_rlc) = builder.rlc_ctx_pair(); + mpt.parse_mpt_inclusion_phase1((ctx_gate, ctx_rlc), mpt_witness); + } +} + +fn test_mpt_circuit( + stage: CircuitBuilderStage, + params: RlcCircuitParams, + inputs: MPTInput, +) -> EthCircuitImpl> { + let test = MptTest(inputs, PhantomData); + let mut circuit = create_circuit(stage, params, test); + circuit.mock_fulfill_keccak_promises(None); + if !stage.witness_gen_only() { + circuit.calculate_params(); + } + circuit +} + +/// Assumes string does **not** start with `0x` +fn from_hex(s: &str) -> Vec { + let s = if s.len() % 2 == 1 { format!("0{s}") } else { s.to_string() }; + Vec::from_hex(s).unwrap() +} + +// The input file is generated by running `query_test.sh` in the `scripts/input_gen` directory of this repo +fn mpt_input_storage( + path: impl AsRef, + slot_is_empty: bool, + max_depth: usize, + max_key_byte_len: usize, + key_byte_len: Option, +) -> MPTInput { + /*let block: serde_json::Value = + serde_json::from_reader(File::open("scripts/input_gen/block.json").unwrap()).unwrap();*/ + + let pf_str = std::fs::read_to_string(path).unwrap(); + let pf: serde_json::Value = serde_json::from_str(pf_str.as_str()).unwrap(); + // let acct_pf = pf["accountProof"].clone(); + let storage_pf = pf["storageProof"][0].clone(); + // println!("acct_pf {:?}", acct_pf); + // println!("storage_root {:?}", pf["storageHash"]); + // println!("storage_pf {:?}", storage_pf); + + let key_bytes_str: String = serde_json::from_value(storage_pf["key"].clone()).unwrap(); + let path = keccak256(from_hex(&key_bytes_str)).to_vec().into(); + // let path = keccak256(from_hex(&key_bytes_str)); + let value_bytes_str: String = serde_json::from_value(storage_pf["value"].clone()).unwrap(); + let value_bytes_str = if value_bytes_str.len() % 2 == 1 { + format!("0{}", &value_bytes_str[2..]) + } else { + value_bytes_str[2..].to_string() + }; + let value = ::rlp::encode(&from_hex(&value_bytes_str)).to_vec(); + let root_hash_str: String = serde_json::from_value(pf["storageHash"].clone()).unwrap(); + let pf_strs: Vec = serde_json::from_value(storage_pf["proof"].clone()).unwrap(); + + let value_max_byte_len = 33; + let proof = pf_strs.into_iter().map(|pf| from_hex(&pf[2..])).collect(); + + MPTInput { + path, + value, + root_hash: H256::from_slice(&from_hex(&root_hash_str[2..])), + proof, + slot_is_empty, + value_max_byte_len, + max_depth, + max_key_byte_len, + key_byte_len, + } +} + +fn default_input() -> MPTInput { + mpt_input_storage("scripts/input_gen/default_storage_pf.json", false, 8, 32, Some(32)) +} + +fn default_params() -> RlcCircuitParams { + let mut params = RlcCircuitParams::default(); + params.base.num_instance_columns = 1; + params.base.lookup_bits = Some(8); + params.base.k = TEST_K as usize; + params +} + +#[test_case("scripts/input_gen/default_storage_pf.json", false, 5, 32, None; "default storage inclusion fixed")] +#[test_case("scripts/input_gen/default_storage_pf.json", false, 5, 32, Some(32); "default storage inclusion var")] +#[test_case("scripts/input_gen/noninclusion_branch_pf.json", true, 5, 32, None; "noninclusion branch fixed")] +#[test_case("scripts/input_gen/noninclusion_branch_pf.json", true, 5, 32, Some(32); "noninclusion branch var")] +#[test_case("scripts/input_gen/noninclusion_extension_pf.json", true, 6, 32, None; "noninclusion extension fixed")] +#[test_case("scripts/input_gen/noninclusion_extension_pf.json", true, 6, 32, Some(32); "noninclusion extension var")] +#[test_case("scripts/input_gen/noninclusion_extension_pf2.json", true, 6, 32, None; "noninclusion branch then extension fixed")] +#[test_case("scripts/input_gen/noninclusion_extension_pf2.json", true, 6, 32, Some(32); "noninclusion branch then extension var")] +#[test_case("scripts/input_gen/empty_storage_pf.json", true, 5, 32, None; "empty root fixed")] +#[test_case("scripts/input_gen/empty_storage_pf.json", true, 5, 32, Some(32); "empty root var")] +pub fn test_mock_mpt( + path: &str, + slot_is_empty: bool, + max_depth: usize, + max_key_byte_len: usize, + key_byte_len: Option, +) { + let _ = env_logger::builder().is_test(true).try_init(); + let input = mpt_input_storage(path, slot_is_empty, max_depth, max_key_byte_len, key_byte_len); + let circuit = test_mpt_circuit::(CircuitBuilderStage::Mock, default_params(), input); + let instances = circuit.instances(); + assert_eq!(instances.len(), 1); + MockProver::run(TEST_K, &circuit, instances).unwrap().assert_satisfied(); +} + +#[test] +#[ignore = "bench"] +fn bench_mpt_inclusion_fixed() -> Result<(), Box> { + let bench_params_file = File::create("configs/bench/mpt.json").unwrap(); + std::fs::create_dir_all("data/bench")?; + let mut fs_results = File::create("data/bench/mpt.csv").unwrap(); + writeln!(fs_results, "degree,total_advice,num_rlc_columns,num_advice,num_lookup,num_fixed,proof_time,verify_time")?; + + let bench_k = 15..18; + let mut all_bench_params = vec![]; + // let bench_params: Vec = serde_json::from_reader(bench_params_file).unwrap(); + for k in bench_k { + println!("---------------------- degree = {k} ------------------------------",); + let params = gen_srs(k); + let mut bench_params = EthCircuitParams::default().rlc; + bench_params.base.k = k as usize; + + let mut circuit = + test_mpt_circuit(CircuitBuilderStage::Keygen, bench_params, default_input()); + let bench_params = circuit.calculate_params().rlc; + all_bench_params.push(bench_params.clone()); + let vk = keygen_vk(¶ms, &circuit)?; + let pk = keygen_pk(¶ms, vk, &circuit)?; + let break_points = circuit.break_points(); + + // create a proof + let proof_time = start_timer!(|| "Create proof SHPLONK"); + let circuit = + test_mpt_circuit(CircuitBuilderStage::Prover, bench_params.clone(), default_input()) + .use_break_points(break_points); + let instances = circuit.instances(); + assert_eq!(instances.len(), 1); + let proof = gen_proof_with_instances(¶ms, &pk, circuit, &[&instances[0]]); + end_timer!(proof_time); + + let verify_time = start_timer!(|| "Verify time"); + check_proof_with_instances(¶ms, pk.get_vk(), &proof, &[&instances[0]], true); + end_timer!(verify_time); + + let RlcCircuitParams { + base: + BaseCircuitParams { + k, + num_advice_per_phase, + num_fixed, + num_lookup_advice_per_phase, + .. + }, + num_rlc_columns, + } = bench_params; + writeln!( + fs_results, + "{},{},{},{:?},{:?},{},{:.2}s,{:?}", + k, + num_rlc_columns + + num_advice_per_phase.iter().sum::() + + num_lookup_advice_per_phase.iter().sum::(), + num_rlc_columns, + num_advice_per_phase, + num_lookup_advice_per_phase, + num_fixed, + proof_time.time.elapsed().as_secs_f64(), + verify_time.time.elapsed() + ) + .unwrap(); + } + serde_json::to_writer_pretty(bench_params_file, &all_bench_params).unwrap(); + Ok(()) +} diff --git a/axiom-eth/src/mpt/tests/tx.rs b/axiom-eth/src/mpt/tests/tx.rs new file mode 100644 index 00000000..2b2c04c5 --- /dev/null +++ b/axiom-eth/src/mpt/tests/tx.rs @@ -0,0 +1,445 @@ +#![allow(clippy::too_many_arguments)] +use super::*; +use cita_trie::{self, MemoryDB, PatriciaTrie, Trie}; +use ethers_core::utils::keccak256; +use hasher::HasherKeccak; +use std::sync::Arc; +use test_case::test_case; + +// Max Key Length: 3 +// Max Proof Length for noninclusion: 3 +// Max Proof Length for inclusion: 6 + +fn mpt_direct_input( + value: Vec, + proof: Vec>, + hash: Vec, + key: Vec, + slot_is_empty: bool, + max_depth: usize, + max_key_byte_len: usize, + key_byte_len: Option, +) -> MPTInput { + let value_max_byte_len = 48; + + MPTInput { + path: key.into(), + value, + root_hash: H256::from_slice(hash.as_slice()), + proof, + slot_is_empty, + value_max_byte_len, + max_depth, + max_key_byte_len, + key_byte_len, + } +} + +fn verify_key_val( + trie: &PatriciaTrie, + key: Vec, + val: Vec, + root: Vec, + slot_is_empty: bool, + bad_proof: bool, + distort_idx: Option>, + case_type: usize, +) -> bool { + if case_type == 0 { + verify_key_val_loose(trie, key, val, root, slot_is_empty, bad_proof, distort_idx) + } else if case_type == 1 { + verify_key_val_tight(trie, key, val, root, slot_is_empty, bad_proof, distort_idx) + } else if case_type == 2 { + verify_key_val_fixed(trie, key, val, root, slot_is_empty, bad_proof, distort_idx) + } else { + false + } +} + +fn verify_key_val_loose( + trie: &PatriciaTrie, + key: Vec, + val: Vec, + root: Vec, + slot_is_empty: bool, + bad_proof: bool, + distort_idx: Option>, +) -> bool { + let params = default_params(); + let key_byte_len = key.len(); + let mut proof = trie.get_proof(&key).unwrap(); + if bad_proof { + let idx = match distort_idx { + Some(_idx) => _idx, + None => { + // assert!(false); + vec![0x00] + } + }; + proof = distort_proof(proof, idx); + } + let input = + mpt_direct_input(val.to_vec(), proof, root, key, slot_is_empty, 6, 32, Some(key_byte_len)); // depth = max_depth + let k = params.base.k as u32; + let circuit = test_mpt_circuit::(CircuitBuilderStage::Mock, params, input); + let instances = circuit.instances(); + let prover = MockProver::run(k, &circuit, instances).unwrap(); + prover.verify().is_ok() +} + +fn verify_key_val_tight( + trie: &PatriciaTrie, + key: Vec, + val: Vec, + root: Vec, + slot_is_empty: bool, + bad_proof: bool, + distort_idx: Option>, +) -> bool { + let params = default_params(); + let key_byte_len = key.len(); + let mut proof = trie.get_proof(&key).unwrap(); + if bad_proof { + let idx = match distort_idx { + Some(_idx) => _idx, + None => { + // assert!(false); + vec![0x00] + } + }; + proof = distort_proof(proof, idx); + } + let proof_len = proof.len() + slot_is_empty as usize; + let input = mpt_direct_input( + val.to_vec(), + proof, + root, + key, + slot_is_empty, + proof_len, + key_byte_len, + Some(key_byte_len), + ); // depth = max_depth + let k = params.base.k as u32; + let circuit = test_mpt_circuit::(CircuitBuilderStage::Mock, params, input); + let instances = circuit.instances(); + let prover = MockProver::run(k, &circuit, instances).unwrap(); + prover.verify().is_ok() +} + +fn verify_key_val_fixed( + trie: &PatriciaTrie, + key: Vec, + val: Vec, + root: Vec, + slot_is_empty: bool, + bad_proof: bool, + distort_idx: Option>, +) -> bool { + let params = default_params(); + let key_byte_len = key.len(); + let mut proof = trie.get_proof(&key).unwrap(); + if bad_proof { + let idx = match distort_idx { + Some(_idx) => _idx, + None => { + // assert!(false); + vec![0x00] + } + }; + proof = distort_proof(proof, idx); + } + let input = + mpt_direct_input(val.to_vec(), proof, root, key, slot_is_empty, 6, key_byte_len, None); // depth = max_depth + let k = params.base.k as u32; + let circuit = test_mpt_circuit::(CircuitBuilderStage::Mock, params, input); + let instances = circuit.instances(); + let prover = MockProver::run(k, &circuit, instances).unwrap(); + prover.verify().is_ok() +} + +fn gen_tx_tree( + num_keys: usize, + rand_vals: bool, + val_max_bytes: usize, +) -> (PatriciaTrie, Vec>) { + let memdb = Arc::new(MemoryDB::new(true)); + let hasher = Arc::new(HasherKeccak::new()); + let mut trie = PatriciaTrie::new(Arc::clone(&memdb), Arc::clone(&hasher)); + let mut vals: Vec> = Vec::new(); + let mut val = [ + 0x43, 0x52, 0x59, 0x50, 0x54, 0x4f, 0x50, 0x55, 0x4e, 0x4b, 0x53, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x16, + ] + .to_vec(); + for idx in 0..num_keys { + let key = rlp::encode(&from_hex(&format!("{idx:x}").to_string())).to_vec(); + if rand_vals { + let val_len = if val_max_bytes == 32 { 32 } else { 32 + idx % (val_max_bytes - 32) }; + val = keccak256(val).to_vec(); + val.resize(val_len, 0x00); + } + let val2 = val.clone(); + let val3 = val.clone(); + vals.push(val3); + trie.insert(key, val2).unwrap(); + } + let root = trie.root().unwrap(); + trie = PatriciaTrie::from(Arc::clone(&memdb), Arc::clone(&hasher), &root).unwrap(); + (trie, vals) +} + +fn distort_proof(proof: Vec>, idx: Vec) -> Vec> { + let mut proof2 = proof.clone(); + for id in idx { + let realid = if id >= 0 { id as usize } else { proof.len() - id.unsigned_abs() as usize }; + assert!(realid < proof.len()); + proof2[realid] = distort(proof2[realid].clone()); + } + proof2 +} + +fn distort(val: Vec) -> Vec { + let mut val2 = val.clone(); + for i in 0..val.len() { + val2[i] = 255 - val[i]; + } + val2 +} + +#[test_case(1, false; "1 leaf, nonrand vals")] +#[test_case(2, false; "2 keys, nonrand vals")] +#[test_case(20, false; "20 keys, nonrand vals")] +#[test_case(200, false; "200 keys, nonrand vals")] +#[test_case(1, true; "1 leaf, rand vals")] +#[test_case(2, true; "2 keys, rand vals")] +#[test_case(20, true; "20 keys, rand vals")] +#[test_case(200, true; "200 keys, rand vals")] +fn pos_full_tree_test_inclusion(num_keys: usize, randvals: bool) { + let (mut trie, vals) = gen_tx_tree(num_keys, randvals, 48); + for idx in 0..(num_keys + 19) / 20 { + let key = rlp::encode(&from_hex(&format!("{:x}", (20 * idx)).to_string())).to_vec(); + let val = vals[idx * 20].clone(); + let root = trie.root().unwrap(); + assert!(verify_key_val(&trie, key, val, root, false, false, None, 0)); + } +} + +#[test_case(18, 1; "trie has 0x01, 0x10, 0x11, 2 branches")] +fn pos_test_inclusion(num_keys: usize, idx: usize) { + let (mut trie, vals) = gen_tx_tree(num_keys, true, 48); + + let key = rlp::encode(&from_hex(&format!("{idx:x}"))).to_vec(); + let val = vals[idx].clone(); + let root = trie.root().unwrap(); + assert!(verify_key_val(&trie, key, val, root, false, false, None, 0)); +} + +#[test_case(1, false, 0; "1 leaf, nonrand vals, loose")] +#[test_case(200, false, 0; "200 keys, nonrand vals, loose")] +#[test_case(1, true, 0; "1 leaf, rand vals, loose")] +#[test_case(200, true, 0; "200 keys, rand vals, loose")] +#[test_case(1, true, 1; "1 leaf, rand vals, tight")] +#[test_case(200, true, 1; "200 keys, rand vals, tight")] +#[test_case(1, true, 2; "1 leaf, rand vals, fixed")] +#[test_case(200, true, 2; "200 keys, rand vals, fixed")] +fn neg_full_tree_test_inclusion_badval(num_keys: usize, randvals: bool, case_type: usize) { + let (mut trie, _) = gen_tx_tree(num_keys, randvals, 48); + for idx in 0..(num_keys + 19) / 20 { + let key = rlp::encode(&from_hex(&format!("{:x}", (20 * idx)).to_string())).to_vec(); + let val = [0x00; 32]; + let root = trie.root().unwrap(); + assert!(!verify_key_val(&trie, key, val.to_vec(), root, false, false, None, case_type)); + } +} + +#[test_case(1, 0; "1 leaf, loose")] +#[test_case(200, 0; "200 keys, loose")] +#[test_case(1, 1; "1 leaf, tight")] +#[test_case(200, 1; "200 keys, tight")] +#[test_case(1, 2; "1 leaf, fixed")] +#[test_case(200, 2; "200 keys, fixed")] +fn pos_test_noninclusion(num_keys: usize, case_type: usize) { + let _ = env_logger::builder().is_test(true).try_init(); + let (mut trie, _) = gen_tx_tree(num_keys, true, 48); + for i in 0..10 { + let idx = num_keys + 20 * i; + let key = rlp::encode(&from_hex(&format!("{idx:x}").to_string())).to_vec(); + let val = [ + 0x43, 0x52, 0x59, 0x50, 0x54, 0x4f, 0x50, 0x55, 0x4e, 0x4b, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x16, + ]; + let root = trie.root().unwrap(); + assert!(verify_key_val(&trie, key, val.to_vec(), root, true, false, None, case_type),); + } +} + +#[test_case(140, false, 1; "undercut by 1 inclusion")] +#[test_case(8, true, 1; "1 depth inclusion")] +#[test_case(300, false, 1; "undercut by 1 noninclusion")] +#[test_case(350, true, 1; "1 depth noninclusion")] +#[test_case(350, false, 0; "undercut by 0 noninclusion")] +fn neg_invalid_max_depth(idx: usize, zero_start: bool, offset: usize) { + let (mut trie, _) = gen_tx_tree(200, true, 48); + let key = rlp::encode(&from_hex(&format!("{idx:x}"))).to_vec(); + let val = [ + 0x43, 0x52, 0x59, 0x50, 0x54, 0x4f, 0x50, 0x55, 0x4e, 0x4b, 0x53, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x16, + ]; + let root = trie.root().unwrap(); + let key_byte_len = key.len(); + let proof = trie.get_proof(&key).unwrap(); + let proof_len = proof.len(); + let depth = if zero_start { offset } else { proof_len - offset }; + assert!(depth <= proof_len); + let input = + mpt_direct_input(val.to_vec(), proof, root, key, idx > 199, depth, 32, Some(key_byte_len)); // depth = max_depth + let params = default_params(); + let k = params.base.k as u32; + let circuit = test_mpt_circuit::(CircuitBuilderStage::Mock, params, input); + let instances = circuit.instances(); + let prover = MockProver::run(k, &circuit, instances).unwrap(); + assert!(prover.verify().is_err(), "Should not verify") + //Every outcome is valid except "Should not verify", in particular those that fail assertions elsewhere in the code +} + +#[test_case(140, false, 1; "undercut by 1 inclusion")] +#[test_case(200, true, 1; "1 len inclusion")] +#[test_case(300, false, 1; "undercut by 1 noninclusion")] +#[test_case(350, true, 1; "1 len noninclusion")] +fn neg_invalid_max_key_byte_len(idx: usize, zero_start: bool, offset: usize) { + let (mut trie, _) = gen_tx_tree(200, true, 48); + let key = rlp::encode(&from_hex(&format!("{idx:x}"))).to_vec(); + let val = [ + 0x43, 0x52, 0x59, 0x50, 0x54, 0x4f, 0x50, 0x55, 0x4e, 0x4b, 0x53, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x16, + ]; + let root = trie.root().unwrap(); + let key_byte_len = key.len(); + let proof = trie.get_proof(&key).unwrap(); + let len = if zero_start { offset } else { key_byte_len - offset }; + assert!(len < key_byte_len); + let input = + mpt_direct_input(val.to_vec(), proof, root, key, idx > 199, 6, len, Some(key_byte_len)); // depth = max_depth + let params = default_params(); + let k = params.base.k as u32; + let circuit = test_mpt_circuit::(CircuitBuilderStage::Mock, params, input); + let instance = circuit.instances(); + let prover = MockProver::run(k, &circuit, instance).unwrap(); + assert!(prover.verify().is_err(), "Should not verify"); +} + +#[test_case(140, false, 1; "undercut by 1 inclusion")] +#[test_case(120, true , 1; "over by 1 inclusion")] +#[test_case(340, false, 1; "undercut by 1 noninclusion")] +#[test_case(320, true , 1; "over by 1 noninclusion")] +fn neg_invalid_key_byte_len(idx: usize, pos: bool, offset: usize) { + let (mut trie, _) = gen_tx_tree(200, true, 48); + let key = rlp::encode(&from_hex(&format!("{idx:x}"))).to_vec(); + let val = [ + 0x43, 0x52, 0x59, 0x50, 0x54, 0x4f, 0x50, 0x55, 0x4e, 0x4b, 0x53, 0x00, 0x00, 0x00, 0x00, + 0x00, + ]; + let root = trie.root().unwrap(); + let key_byte_len = key.len(); + let proof = trie.get_proof(&key).unwrap(); + let len = if pos { key_byte_len + offset } else { key_byte_len - offset }; + let input = mpt_direct_input(val.to_vec(), proof, root, key, idx > 199, 6, 32, Some(len)); // depth = max_depth + let params = default_params(); + let k = params.base.k as u32; + let circuit = test_mpt_circuit::(CircuitBuilderStage::Mock, params, input); + let instance = circuit.instances(); + let prover = MockProver::run(k, &circuit, instance).unwrap(); + assert!(prover.verify().is_err(), "should not verify"); +} + +#[test_case(140, false, 1; "undercut by 1 inclusion")] +#[test_case(120, true , 1; "over by 1 inclusion")] +#[test_case(340, false, 1; "undercut by 1 noninclusion")] +#[test_case(320, true , 1; "over by 1 noninclusion")] +fn neg_invalid_max_key_byte_len_fixed(idx: usize, pos: bool, offset: usize) { + let (mut trie, _) = gen_tx_tree(200, true, 48); + let key = rlp::encode(&from_hex(&format!("{idx:x}"))).to_vec(); + let val = [ + 0x43, 0x52, 0x59, 0x50, 0x54, 0x4f, 0x50, 0x55, 0x4e, 0x4b, 0x53, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x16, + ]; + let root = trie.root().unwrap(); + let key_byte_len = key.len(); + let proof = trie.get_proof(&key).unwrap(); + let len = if pos { key_byte_len + offset } else { key_byte_len - offset }; + let input = mpt_direct_input(val.to_vec(), proof, root, key, idx > 199, 6, len, None); + let params = default_params(); + let k = params.base.k as u32; + let circuit = test_mpt_circuit::(CircuitBuilderStage::Mock, params, input); + let instance = circuit.instances(); + let prover = MockProver::run(k, &circuit, instance).unwrap(); + assert!(prover.verify().is_err(), "should not verify"); +} + +#[test_case(0; "empty tree, loose")] +#[test_case(1; "empty tree, tight")] +#[test_case(2; "empty tree, fixed")] +fn pos_empty_tree_noninclusion(case_type: usize) { + let (mut trie, _) = gen_tx_tree(0, true, 48); + let root = trie.root().unwrap(); + let key = [0; 32].to_vec(); + let val = [0x0].to_vec(); + println!("{root:02x?}"); + assert!(verify_key_val(&trie, key, val, root, true, false, None, case_type)); +} + +#[test_case(200, 0, 300; "empty proof, loose noninclusion")] +#[test_case(200, 1, 300; "empty proof, tight noninclusion")] +#[test_case(200, 2, 300; "empty proof, fixed noninclusion")] +fn neg_nonempty_tree_empty_proof(num_keys: usize, case_type: usize, idx: usize) { + let (mut trie, vals) = gen_tx_tree(num_keys, true, 48); + let root = trie.root().unwrap(); + let key = rlp::encode(&from_hex(&format!("{idx:x}"))).to_vec(); + let inpval = if idx > 199 { [0x0].to_vec() } else { vals[idx].clone() }; + let input = if case_type == 0 { + mpt_direct_input( + inpval, + [].to_vec(), + root, + key.clone(), + idx >= num_keys, + 6, + 32, + Some(key.len()), + ) + } else if case_type == 1 { + mpt_direct_input( + inpval, + [].to_vec(), + root, + key.clone(), + idx >= num_keys, + 1, + key.len(), + Some(key.len()), + ) + } else { + mpt_direct_input( + inpval, + [].to_vec(), + root, + key.clone(), + idx >= num_keys, + 6, + key.len(), + None, + ) + }; + let params = default_params(); + let k = params.base.k as u32; + let circuit = test_mpt_circuit::(CircuitBuilderStage::Mock, params, input); + let instance = circuit.instances(); + let prover = MockProver::run(k, &circuit, instance).unwrap(); + assert!(prover.verify().is_err(), "should not verify"); +} diff --git a/axiom-eth/src/mpt/types/input.rs b/axiom-eth/src/mpt/types/input.rs new file mode 100644 index 00000000..9d9ea4e7 --- /dev/null +++ b/axiom-eth/src/mpt/types/input.rs @@ -0,0 +1,224 @@ +//! Module that handles the logic of formatting an MPT proof into witnesses +//! used as inputs in the MPT chip. This mostly involves +//! - resizing vectors to fixed length by right padding with 0s, +//! - modifying last node in proof for exclusion proofs +//! - extracting the terminal node from the proof +use super::*; + +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] +/// The pre-assigned inputs for the MPT proof +pub struct MPTInput { + // claim specification: (path, value) + /// A Merkle-Patricia Trie is a mapping `path => value` + /// + /// As an example, the MPT state trie of Ethereum has + /// `path = keccak256(address) => value = rlp(account)` + pub path: PathBytes, + pub value: Vec, + pub root_hash: H256, + + /// Inclusion proofs will always end in a terminal node: we extract this terminal node in cases where it was originally embedded inside the last branch node. + pub proof: Vec>, + + pub slot_is_empty: bool, + + pub value_max_byte_len: usize, + pub max_depth: usize, + pub max_key_byte_len: usize, + pub key_byte_len: Option, +} + +/// The assigned input for an MPT proof. +/// The `AssignedBytes` here have **not** been range checked. +/// The range checks are performed in the `parse_mpt_inclusion_phase0` function. +#[derive(Clone, Debug)] +pub struct MPTProof { + // claim specification: (key, value) + /// The key bytes, fixed length + pub key_bytes: AssignedBytes, + /// The RLP encoded `value` as bytes, variable length, resized to `value_max_byte_len` + pub value_bytes: AssignedBytes, + pub value_byte_len: AssignedValue, + pub root_hash_bytes: AssignedBytes, + + // proof specification + /// The variable length of the key + pub key_byte_len: Option>, + /// The variable length of the proof, including the leaf node if !slot_is_empty. + /// We always have the terminal node as a separate node, even if the original proof may embed it into the last branch node. + pub depth: AssignedValue, + /// RLP encoding of the final leaf node + pub leaf: MPTNode, + /// The non-leaf nodes of the mpt proof, resized to `max_depth - 1` with dummy **branch** nodes. + /// The actual variable length is `depth - 1` if `slot_is_empty == true` (excludes leaf node), otherwise `depth`. + pub nodes: Vec>, + /// The key fragments of the mpt proof, variable length, resized to `max_depth` with dummy fragments. + /// Each fragment (nibbles aka hexes) is variable length, resized to `2 * key_byte_len` with 0s + pub key_frag: Vec>, + /// Boolean indicating whether the MPT contains a value at `key` + pub slot_is_empty: AssignedValue, + + /// The maximum byte length of the key + pub max_key_byte_len: usize, + /// `max_depth` should be `>=1` + pub max_depth: usize, +} + +impl MPTInput { + /// Does **not** perform any range checks on witnesses to check if they are actually bytes. + /// This should be done in the `parse_mpt_inclusion_phase0` function + pub fn assign(self, ctx: &mut Context) -> MPTProof { + let Self { + path, + mut value, + root_hash, + mut proof, + value_max_byte_len, + max_depth, + slot_is_empty, + max_key_byte_len, + key_byte_len, + } = self; + let depth = proof.len(); + // if empty, we have a dummy node stored as a terminal node so that the circuit still works + // we ignore any results from the node, however. + if proof.is_empty() { + proof.push(NULL_LEAF.clone()); + } + //assert!(depth <= max_depth - usize::from(slot_is_empty)); + assert!(max_depth > 0); + assert!(max_key_byte_len > 0); + + let bytes_to_nibbles = |bytes: &[u8]| { + let mut nibbles = Vec::with_capacity(bytes.len() * 2); + for byte in bytes { + nibbles.push(byte >> 4); + nibbles.push(byte & 0xf); + } + nibbles + }; + let hex_len = |byte_len: usize, is_odd: bool| 2 * byte_len + usize::from(is_odd) - 2; + let path_nibbles = bytes_to_nibbles(path.as_ref()); + let mut path_idx = 0; + + // below "key" and "path" are used interchangeably, sorry for confusion + // if slot_is_empty, leaf is dummy, but with value 0x0 to make constraints pass (assuming claimed value is also 0x0) + let mut leaf = proof.pop().unwrap(); + let (_, max_leaf_bytes) = max_leaf_lens(max_key_byte_len, value_max_byte_len); + + let (_, max_ext_bytes) = max_ext_lens(max_key_byte_len); + let max_node_bytes = max(max_ext_bytes, MAX_BRANCH_ENCODING_BYTES); + + let mut key_frag = Vec::with_capacity(max_depth); + let mut nodes = Vec::with_capacity(max_depth - 1); + let mut process_node = |node: &[u8]| { + let decode = Rlp::new(node); + let node_type = decode.item_count().unwrap() == 2; + if node_type { + let encoded_path = decode.at(0).unwrap().data().unwrap(); + let byte_len = encoded_path.len(); + let encoded_nibbles = bytes_to_nibbles(encoded_path); + let is_odd = encoded_nibbles[0] == 1u8 || encoded_nibbles[0] == 3u8; + let mut frag = encoded_nibbles[2 - usize::from(is_odd)..].to_vec(); + path_idx += frag.len(); + frag.resize(2 * max_key_byte_len, 0); + key_frag.push((frag, byte_len, is_odd)); + } else { + let mut frag = vec![0u8; 2 * max_key_byte_len]; + frag[0] = path_nibbles[path_idx]; + key_frag.push((frag, 1, true)); + path_idx += 1; + } + node_type + }; + for mut node in proof { + let node_type = process_node(&node); + node.resize(max_node_bytes, 0); + nodes.push((node, node_type)); + } + let mut dummy_branch = DUMMY_BRANCH.clone(); + dummy_branch.resize(max_node_bytes, 0); + nodes.resize(max_depth - 1, (dummy_branch, false)); + + let leaf_type = process_node(&leaf); + let leaf_type = ctx.load_witness(F::from(leaf_type)); + let max_leaf_bytes = max(max_node_bytes, max_leaf_bytes); + leaf.resize(max_leaf_bytes, 0); + let mut path_bytes = path.0; + + let key_byte_len = key_byte_len.map(|key_byte_len| { + #[cfg(not(test))] + assert_eq!(key_byte_len, path_bytes.len()); + ctx.load_witness(F::from(key_byte_len as u64)) + }); + // 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(); + if key_frag.is_empty() { + // that means proof was empty + let mut nibbles = path_nibbles; + nibbles.resize(2 * max_key_byte_len, 0); + key_frag = vec![(nibbles, path_bytes.len() + 1, false)]; + } else { + // the last frag in non-inclusion doesn't match path + key_frag.pop().unwrap(); + let hex_len = key_frag + .iter() + .map(|(_, byte_len, is_odd)| hex_len(*byte_len, *is_odd)) + .sum::(); + let mut remaining = path_nibbles[hex_len..].to_vec(); + let is_odd = remaining.len() % 2 == 1; + let byte_len = (remaining.len() + 2 - usize::from(is_odd)) / 2; + remaining.resize(2 * max_key_byte_len, 0); + key_frag.push((remaining, byte_len, is_odd)); + } + } + key_frag.resize(max_depth, (vec![0u8; 2 * max_key_byte_len], 0, false)); + + // assign all values + let value_byte_len = ctx.load_witness(F::from(value.len() as u64)); + let depth = ctx.load_witness(F::from(depth as u64)); + let load_bytes = |bytes: Vec, ctx: &mut Context| { + ctx.assign_witnesses(bytes.iter().map(|x| F::from(*x as u64))) + }; + path_bytes.resize(max_key_byte_len, 0); + let key_bytes = load_bytes(path_bytes, ctx); + value.resize(value_max_byte_len, 0); + let value_bytes = load_bytes(value.to_vec(), ctx); + let root_hash_bytes = load_bytes(root_hash.as_bytes().to_vec(), ctx); + let leaf_bytes = load_bytes(leaf.to_vec(), ctx); + let nodes = nodes + .into_iter() + .map(|(node_bytes, node_type)| { + let rlp_bytes = ctx.assign_witnesses(node_bytes.iter().map(|x| F::from(*x as u64))); + let node_type = ctx.load_witness(F::from(node_type)); + MPTNode { rlp_bytes, node_type } + }) + .collect_vec(); + let key_frag = key_frag + .into_iter() + .map(|(nibbles, byte_len, is_odd)| { + let nibbles = ctx.assign_witnesses(nibbles.iter().map(|x| F::from(*x as u64))); + let byte_len = ctx.load_witness(F::from(byte_len as u64)); + let is_odd = ctx.load_witness(F::from(is_odd)); + MPTFragment { nibbles, is_odd, byte_len } + }) + .collect_vec(); + let slot_is_empty = ctx.load_witness(F::from(slot_is_empty)); + MPTProof { + key_bytes, + value_bytes, + value_byte_len, + root_hash_bytes, + key_byte_len, + depth, + leaf: MPTNode { rlp_bytes: leaf_bytes, node_type: leaf_type }, + nodes, + key_frag, + slot_is_empty, + max_key_byte_len, + max_depth, + } + } +} diff --git a/axiom-eth/src/mpt/types/mod.rs b/axiom-eth/src/mpt/types/mod.rs new file mode 100644 index 00000000..7e92db31 --- /dev/null +++ b/axiom-eth/src/mpt/types/mod.rs @@ -0,0 +1,249 @@ +use crate::{ + rlc::types::{RlcFixedTrace, RlcTrace}, + rlp::types::{RlpArrayWitness, RlpFieldTrace}, +}; + +use super::*; + +mod input; +pub use input::*; + +/// Witness for the terminal node of an MPT proof. +/// This can be either a leaf (`ext`) or extracted from a branch (`branch`). +/// The type is determined by `node_type`. +#[derive(Clone, Debug)] +pub struct TerminalWitness { + pub node_type: AssignedValue, + pub ext: LeafWitness, + pub branch: BranchWitness, + // pub max_leaf_bytes: usize, +} + +// TODO: there is no difference structurally between `TerminalTrace` and `MPTNodeTrace` right now. Should combine somehow while still keeping the distinction between the two. +/// The RLC traces corresponding to [`TerminalWitness`] +pub struct TerminalTrace { + pub node_type: AssignedValue, + pub ext: LeafTrace, + pub branch: BranchTrace, + // pub max_leaf_bytes: usize, +} + +impl TerminalTrace { + /// Returns the RLC of the MPT hash of the node by correcting selecting based on node type + pub fn mpt_hash_rlc( + &self, + ctx_gate: &mut Context, + gate: &impl GateInstructions, + ) -> RlcVar { + rlc_select( + ctx_gate, + gate, + self.ext.rlcs.mpt_hash, + self.branch.rlcs.mpt_hash, + self.node_type, + ) + } + + /// Returns the RLC of the keccak of the node by correcting selecting based on node type + pub fn keccak_rlc( + &self, + ctx_gate: &mut Context, + gate: &impl GateInstructions, + ) -> AssignedValue { + assert_eq!(self.ext.rlcs.hash.len, self.branch.rlcs.hash.len); + gate.select( + ctx_gate, + self.ext.rlcs.hash.rlc_val, + self.branch.rlcs.hash.rlc_val, + self.node_type, + ) + } +} + +#[derive(Clone, Debug)] +pub struct LeafTrace { + pub key_path: RlpFieldTrace, + pub value: RlpFieldTrace, + pub rlcs: MPTHashTrace, +} + +#[derive(Clone, Debug)] +pub struct LeafWitness { + pub rlp: RlpArrayWitness, + pub hash_query: MPTHashWitness, +} + +#[derive(Clone, Debug)] +pub struct ExtensionTrace { + pub key_path: RlpFieldTrace, + pub node_ref: RlpFieldTrace, + pub rlcs: MPTHashTrace, +} + +#[derive(Clone, Debug)] +pub struct ExtensionWitness { + pub rlp: RlpArrayWitness, + pub hash_query: MPTHashWitness, +} + +#[derive(Clone, Debug)] +pub struct BranchTrace { + // rlc without rlp prefix + pub node_refs: [RlpFieldTrace; BRANCH_NUM_ITEMS], + pub rlcs: MPTHashTrace, +} + +#[derive(Clone, Debug)] +pub struct BranchWitness { + pub rlp: RlpArrayWitness, + pub hash_query: MPTHashWitness, +} + +// helper types for readability +pub type AssignedBytes = Vec>; // TODO: use SafeByte +pub type AssignedNibbles = Vec>; + +#[derive(Clone, Debug)] +pub struct MPTNode { + pub rlp_bytes: AssignedBytes, + /// 0 = branch, 1 = extension + pub node_type: AssignedValue, +} + +#[derive(Clone, Debug)] +/// The `node_type` flag selects whether the node is parsed as a branch or extension node. +pub struct MPTNodeWitness { + /// 0 = branch, 1 = extension + pub node_type: AssignedValue, + /// The node parsed as an extension node, or dummy extension node otherwise + pub ext: ExtensionWitness, + /// The node parsed as a branch node, or dummy branch node otherwise + pub branch: BranchWitness, +} + +#[derive(Clone, Debug)] +/// The `node_type` flag selects whether the node is parsed as a branch or extension node. +pub struct MPTNodeTrace { + /// 0 = branch, 1 = extension + pub node_type: AssignedValue, + /// The node parsed as an extension node, or dummy extension node otherwise + pub ext: ExtensionTrace, + /// The node parsed as a branch node, or dummy branch node otherwise + pub branch: BranchTrace, +} + +impl MPTNodeTrace { + /// Returns the RLC of the MPT hash of the node by correcting selecting based on node type + pub fn mpt_hash_rlc( + &self, + ctx_gate: &mut Context, + gate: &impl GateInstructions, + ) -> RlcVar { + rlc_select( + ctx_gate, + gate, + self.ext.rlcs.mpt_hash, + self.branch.rlcs.mpt_hash, + self.node_type, + ) + } + + /// Returns the RLC of the keccak of the node by correcting selecting based on node type + pub fn keccak_rlc( + &self, + ctx_gate: &mut Context, + gate: &impl GateInstructions, + ) -> AssignedValue { + assert_eq!(self.ext.rlcs.hash.len, self.branch.rlcs.hash.len); + gate.select( + ctx_gate, + self.ext.rlcs.hash.rlc_val, + self.branch.rlcs.hash.rlc_val, + self.node_type, + ) + } +} + +#[derive(Clone, Debug)] +/// A fragment of the key (bytes), stored as nibbles before hex-prefix encoding +pub struct MPTFragment { + /// A variable length string of hex-numbers, resized to a fixed max length with 0s + pub nibbles: AssignedNibbles, + pub is_odd: AssignedValue, + // hex_len = 2 * byte_len + is_odd - 2 + // if nibble for branch: byte_len = is_odd = 1 + /// The byte length of the hex-prefix encoded fragment + pub byte_len: AssignedValue, +} + +#[derive(Clone, Debug)] +pub struct MPTProofWitness { + // we keep only the parts of the proof necessary: + pub value_bytes: AssignedBytes, + pub value_byte_len: AssignedValue, + pub root_hash_bytes: AssignedBytes, + /// The variable length of the key + pub key_byte_len: Option>, + /// The variable length of the proof. This includes the leaf node in the case of an inclusion proof. There is no leaf node in the case of a non-inclusion proof. + pub depth: AssignedValue, + /// The non-leaf nodes of the mpt proof, resized to `max_depth - 1`. Each node has been parsed with both a hypothetical branch and extension node. The actual type is determined by the `node_type` flag. + /// + /// The actual variable length of `nodes` is `depth - 1` if `slot_is_empty == true` (excludes leaf node), otherwise `depth`. + pub nodes: Vec>, + /// The last node parsed + pub terminal_node: TerminalWitness, + + /// Boolean indicating whether the MPT contains a value at `key` + pub slot_is_empty: AssignedValue, + + pub max_key_byte_len: usize, + pub max_depth: usize, + + /// The key fragments (nibbles), without encoding, provided as private inputs + pub key_frag: Vec>, + /// The hex-prefix encoded path for (potential) extension nodes (hex-prefix encoding has leaf vs. extension distinction). + /// These are derived from the nodes themselves. + pub key_frag_ext_bytes: Vec>, + /// The hex-prefix encoded path for (potential) leaf nodes (hex-prefix encoding has leaf vs. extension distinction). + /// These are derived from the nodes themselves. + pub key_frag_leaf_bytes: Vec>, + pub frag_lens: Vec>, + pub key_hexs: AssignedNibbles, +} + +pub type MPTHashWitness = KeccakVarLenQuery; + +#[derive(Clone, Copy, Debug)] +pub struct MPTHashTrace { + /// 32-byte keccak hash RLC + pub hash: RlcFixedTrace, + /// input if len < 32, hash otherwise. + pub mpt_hash: RlcTrace, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] +pub struct PathBytes(pub Vec); + +impl> From<&T> for PathBytes { + fn from(value: &T) -> Self { + Self(value.as_ref().to_vec()) + } +} + +impl From> for PathBytes { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl From for PathBytes { + fn from(value: H256) -> Self { + Self(value.0.to_vec()) + } +} + +impl AsRef<[u8]> for PathBytes { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} diff --git a/axiom-eth/src/providers/account.rs b/axiom-eth/src/providers/account.rs new file mode 100644 index 00000000..250aec20 --- /dev/null +++ b/axiom-eth/src/providers/account.rs @@ -0,0 +1,55 @@ +use ethers_core::types::{Address, EIP1186ProofResponse}; +use ethers_providers::{JsonRpcClient, Middleware, Provider}; +use futures::future::join_all; +use rlp::RlpStream; +use tokio::runtime::Runtime; + +use crate::storage::{circuit::EthStorageInput, ACCOUNT_STATE_FIELDS_MAX_BYTES}; + +use super::storage::json_to_mpt_input; + +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 mut input = json_to_mpt_input(pf, acct_pf_max_depth, 0); + input.acct_pf.root_hash = block.state_root; + input +} + +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) + }))) +} + +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] +} diff --git a/axiom-eth/src/providers/block.rs b/axiom-eth/src/providers/block.rs new file mode 100644 index 00000000..5f861097 --- /dev/null +++ b/axiom-eth/src/providers/block.rs @@ -0,0 +1,136 @@ +use ethers_core::{ + types::{Block, H256}, + utils::keccak256, +}; +use ethers_providers::{JsonRpcClient, Middleware, Provider, ProviderError}; +use futures::future::join_all; +use rlp::RlpStream; +use tokio::runtime::Runtime; + +/// 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() +} + +pub fn get_block_rlp_from_num( + provider: &Provider

, + block_number: u32, +) -> Vec { + let rt = Runtime::new().unwrap(); + let block2 = rt + .block_on(provider.get_block(block_number as u64)) + .unwrap() + .unwrap_or_else(|| panic!("Block {block_number} not found")); + get_block_rlp(&block2) +} + +pub fn get_block_rlp(block: &Block) -> Vec { + let base_fee = block.base_fee_per_gas; + let withdrawals_root: Option = block.withdrawals_root; + // EIP-4844: + let blob_gas_used = block.blob_gas_used; + let excess_blob_gas = block.excess_blob_gas; + // EIP-4788: + let parent_beacon_block_root: Option = block.parent_beacon_block_root; + + let mut rlp_len = 15; + for opt in [ + base_fee.is_some(), + withdrawals_root.is_some(), + blob_gas_used.is_some(), + excess_blob_gas.is_some(), + parent_beacon_block_root.is_some(), + ] { + rlp_len += opt as usize; + } + 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)); + blob_gas_used.map(|blob_gas_used| rlp.append(&blob_gas_used)); + excess_blob_gas.map(|excess_blob_gas| rlp.append(&excess_blob_gas)); + parent_beacon_block_root.map(|parent_beacon_block_root| rlp.append(&parent_beacon_block_root)); + let encoding: Vec = rlp.out().into(); + assert_eq!(keccak256(&encoding), block.hash.unwrap().0); + encoding +} + +/// returns vector of RLP bytes of each block in [start_block_number, start_block_number + num_blocks) +pub fn get_blocks_input( + provider: &Provider

, + start_block_number: u32, + num_blocks: u32, +) -> Vec> { + 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}")); + blocks + .into_iter() + .map(|block| { + let block = block.expect("block not found"); + get_block_rlp(&block) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use ethers_core::types::Chain; + use ethers_providers::Middleware; + + use crate::providers::setup_provider; + + use super::*; + + #[tokio::test] + async fn test_retry_provider() { + let provider = setup_provider(Chain::Mainnet); + let latest = provider.get_block_number().await.unwrap().as_u64(); + for block_num in [5_000_050, 5_000_051, 17_034_973, 19_426_587, 19_426_589, latest] { + let block = provider.get_block(block_num).await.unwrap().unwrap(); + get_block_rlp(&block); + } + } + + #[tokio::test] + async fn test_retry_provider_base() { + let provider = setup_provider(Chain::Base); + let latest = provider.get_block_number().await.unwrap().as_u64(); + for block_num in [0, 100, 100_000, 5_000_050, 7_000_000, 8_000_000, 11_864_572, latest] { + let block = provider.get_block(block_num).await.unwrap().unwrap(); + get_block_rlp(&block); + } + } + + #[tokio::test] + async fn test_retry_provider_sepolia() { + let provider = setup_provider(Chain::Sepolia); + let latest = provider.get_block_number().await.unwrap().as_u64(); + for block_num in [0, 5000050, 5187023, 5187810, 5187814, latest] { + let block = provider.get_block(block_num).await.unwrap().unwrap(); + get_block_rlp(&block); + } + } +} diff --git a/axiom-eth/src/providers/mod.rs b/axiom-eth/src/providers/mod.rs new file mode 100644 index 00000000..5c842a73 --- /dev/null +++ b/axiom-eth/src/providers/mod.rs @@ -0,0 +1,38 @@ +#![allow(clippy::too_many_arguments)] +use ethers_core::{types::Chain, utils::hex::FromHex}; +use ethers_providers::{Http, Provider, RetryClient}; + +use std::env::var; + +pub mod account; +pub mod block; +pub mod receipt; +pub mod storage; +pub mod transaction; + +pub fn get_provider_uri(chain: Chain) -> String { + let key = var("ALCHEMY_KEY").expect("ALCHEMY_KEY environmental variable not set"); + match chain { + Chain::Base | Chain::Optimism | Chain::Arbitrum => { + format!("https://{chain}-mainnet.g.alchemy.com/v2/{key}") + } + Chain::Mainnet | Chain::Sepolia => { + format!("https://eth-{chain}.g.alchemy.com/v2/{key}") + } + _ => { + // Catch-all, may not always work + format!("https://{chain}.g.alchemy.com/v2/{key}") + } + } +} + +pub fn setup_provider(chain: Chain) -> Provider> { + let provider_uri = get_provider_uri(chain); + Provider::new_client(&provider_uri, 10, 500).expect("could not instantiate HTTP Provider") +} + +/// Version of `Vec::from_hex` that works with odd-length strings. Assumes string does not start with `0x` and only has hex characters. +pub fn from_hex(s: &str) -> Vec { + let s = if s.len() % 2 == 1 { format!("0{s}") } else { s.to_string() }; + Vec::from_hex(s).unwrap() +} diff --git a/axiom-eth/src/providers/receipt.rs b/axiom-eth/src/providers/receipt.rs new file mode 100644 index 00000000..a2a689c4 --- /dev/null +++ b/axiom-eth/src/providers/receipt.rs @@ -0,0 +1,471 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use anyhow::anyhow; +use cita_trie::{MemoryDB, PatriciaTrie, Trie}; +use ethers_core::types::{Address, Bloom, Bytes, Log, OtherFields, H256, U128, U256, U64}; +use ethers_providers::{JsonRpcClient, Middleware, Provider}; +use futures::future::join_all; +use hasher::HasherKeccak; +use rlp::RlpStream; +use serde::{Deserialize, Serialize}; +#[cfg(test)] +use tokio::runtime::Runtime; + +#[cfg(test)] +use crate::{ + mpt::MPTInput, + providers::block::{get_block_rlp, get_blocks}, + providers::from_hex, + receipt::EthReceiptInput, + receipt::{calc_max_val_len as rc_calc_max_val_len, EthBlockReceiptInput}, +}; + +use super::transaction::get_tx_key_from_index; + +// Issue: https://github.com/gakonst/ethers-rs/issues/2768 +// Copying from: https://github.com/alloy-rs/alloy/blob/410850b305a28297483d819b669b04ba31796359/crates/rpc-types/src/eth/transaction/receipt.rs#L8 +/// "Receipt" of an executed transaction: details of its execution. +/// Transaction receipt +#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionReceipt { + /// Transaction Hash. + pub transaction_hash: Option, + /// Index within the block. + pub transaction_index: U64, + /// Hash of the block this transaction was included within. + pub block_hash: Option, + /// Number of the block this transaction was included within. + pub block_number: Option, + /// Address of the sender + pub from: Address, + /// Address of the receiver. None when its a contract creation transaction. + pub to: Option

, + /// Cumulative gas used within the block after this was executed. + pub cumulative_gas_used: U256, + /// Gas used by this transaction alone. + /// + /// Gas used is `None` if the the client is running in light client mode. + pub gas_used: Option, + /// Contract address created, or None if not a deployment. + pub contract_address: Option
, + /// Logs emitted by this transaction. + pub logs: Vec, + /// Status: either 1 (success) or 0 (failure). Only present after activation of EIP-658 + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + /// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub root: Option, + /// Logs bloom + pub logs_bloom: Bloom, + /// EIP-2718 Transaction type, Some(1) for AccessList transaction, None for Legacy + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] + pub transaction_type: Option, + /// The price paid post-execution by the transaction (i.e. base fee + priority fee). Both + /// fields in 1559-style transactions are maximums (max fee + max priority fee), the amount + /// that's actually paid by users can only be determined post-execution + #[serde(rename = "effectiveGasPrice", default, skip_serializing_if = "Option::is_none")] + pub effective_gas_price: Option, + + // Note: blob_gas_used and blob_gas_price are not part of the EIP-2718 ReceiptPayload + /// Blob gas used by the eip-4844 transaction + /// + /// This is None for non eip-4844 transactions + #[serde(skip_serializing_if = "Option::is_none")] + pub blob_gas_used: Option, + /// The price paid by the eip-4844 transaction per blob gas. + #[serde(skip_serializing_if = "Option::is_none")] + pub blob_gas_price: Option, + + /// Arbitrary extra fields. Contains fields specific to, e.g., L2s. + #[serde(flatten)] + pub other: OtherFields, +} + +// Copied from https://github.com/alloy-rs/alloy/blob/410850b305a28297483d819b669b04ba31796359/crates/rpc-types/src/eth/transaction/optimism.rs#L25 +/// Additional fields for Optimism transaction receipts +#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OptimismTransactionReceiptFields { + /// Deposit nonce for deposit transactions post-regolith + #[serde(skip_serializing_if = "Option::is_none")] + pub deposit_nonce: Option, + /// Deposit receipt version for deposit transactions post-canyon + #[serde(skip_serializing_if = "Option::is_none")] + pub deposit_receipt_version: Option, + /// L1 fee for the transaction + #[serde(skip_serializing_if = "Option::is_none")] + pub l1_fee: Option, + /// L1 fee scalar for the transaction + #[serde(default, skip_serializing_if = "Option::is_none", with = "l1_fee_scalar_serde")] + pub l1_fee_scalar: Option, + /// L1 gas price for the transaction + #[serde(skip_serializing_if = "Option::is_none")] + pub l1_gas_price: Option, + /// L1 gas used for the transaction + #[serde(skip_serializing_if = "Option::is_none")] + pub l1_gas_used: Option, +} + +/// Serialize/Deserialize l1FeeScalar to/from string +mod l1_fee_scalar_serde { + use serde::{de, Deserialize}; + + pub(super) fn serialize(value: &Option, s: S) -> Result + where + S: serde::Serializer, + { + if let Some(v) = value { + return s.serialize_str(&v.to_string()); + } + s.serialize_none() + } + + pub(super) fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let s: Option = Option::deserialize(deserializer)?; + if let Some(s) = s { + return Ok(Some(s.parse::().map_err(de::Error::custom)?)); + } + + Ok(None) + } +} + +/// This is a fix to . +/// Also fixes the OP stack deposit receipt RLP encoding. +/// `reth` reference: https://github.com/paradigmxyz/reth/blob/4e1c56f8d0baf7282b8ceb5ff8c93da66961ca2a/crates/primitives/src/receipt.rs#L486 +pub fn get_receipt_rlp(receipt: &TransactionReceipt) -> anyhow::Result { + let mut s = RlpStream::new(); + s.begin_unbounded_list(); + if let Some(post_state) = receipt.root { + s.append(&post_state); + } else { + s.append(&receipt.status.ok_or(anyhow!("No post-state or status in receipt"))?); + } + s.append(&receipt.cumulative_gas_used); + s.append(&receipt.logs_bloom); + s.append_list(&receipt.logs); + + // OP stack deposit transaction + // https://specs.optimism.io/protocol/deposits.html#deposit-receipt + if receipt.transaction_type == Some(U64::from(0x7E)) { + let op_fields: OptimismTransactionReceiptFields = + serde_json::from_value(serde_json::to_value(&receipt.other)?)?; + // https://github.com/paradigmxyz/reth/blob/4e1c56f8d0baf7282b8ceb5ff8c93da66961ca2a/crates/primitives/src/receipt.rs#L40 + if let Some(deposit_receipt_version) = op_fields.deposit_receipt_version { + // RPC providers seem to provide depositNonce even before Canyon, so we use receipt version as indicator + let deposit_nonce = op_fields + .deposit_nonce + .ok_or(anyhow!("Canyon deposit receipt without depositNonce"))?; + s.append(&deposit_nonce); + // This is denoted as "depositReceiptVersion" in RPC responses, not "depositNonceVersion" like in the docs + s.append(&deposit_receipt_version); + } + } + + s.finalize_unbounded_list(); + let rlp_bytes: Bytes = s.out().freeze().into(); + let mut encoded = vec![]; + if let Some(tx_type) = receipt.transaction_type { + let tx_type = u8::try_from(tx_type.as_u64()) + .map_err(|_| anyhow!("Transaction type is not a byte"))?; + if tx_type > 0 { + encoded.extend_from_slice(&[tx_type]); + } + } + encoded.extend_from_slice(rlp_bytes.as_ref()); + Ok(encoded.into()) +} + +// ========================= Receipts Trie Construction ========================= +pub struct BlockReceiptsDb { + pub trie: PatriciaTrie, + pub root: H256, + pub rc_rlps: Vec>, +} + +impl BlockReceiptsDb { + pub fn new( + trie: PatriciaTrie, + root: H256, + rc_rlps: Vec>, + ) -> Self { + Self { trie, root, rc_rlps } + } +} + +// ===== Block with Receipts ===== +/// A block with all receipts. We require the receiptsRoot to be provided for a safety check. +/// Deserialization should still work on an object with extra fields. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct BlockWithReceipts { + /// Block number + pub number: U64, + /// Receipts root hash + pub receipts_root: H256, + /// All receipts in the block + pub receipts: Vec, +} + +pub fn construct_rc_tries_from_full_blocks( + blocks: Vec, +) -> anyhow::Result> { + let mut tries = HashMap::new(); + for block in blocks { + let mut trie = + PatriciaTrie::new(Arc::new(MemoryDB::new(true)), Arc::new(HasherKeccak::new())); + let mut rc_rlps = Vec::with_capacity(block.receipts.len()); + for (idx, rc) in block.receipts.into_iter().enumerate() { + let tx_key = get_tx_key_from_index(idx); + let rc_rlp = get_receipt_rlp(&rc)?.to_vec(); + rc_rlps.push(rc_rlp.clone()); + trie.insert(tx_key, rc_rlp)?; + } + // safety check: + let root = trie.root()?; + if root != block.receipts_root.as_bytes() { + anyhow::bail!("Transactions trie incorrectly constructed for block {}", block.number); + } + let root = block.receipts_root; + tries.insert(block.number.as_u64(), BlockReceiptsDb::new(trie, root, rc_rlps)); + } + Ok(tries) +} + +// This function only for testing use +#[cfg(test)] +async fn get_receipt_query( + receipt_pf_max_depth: usize, + receipts: Vec, // all receipts in the block + tx_idx: usize, + receipts_root: H256, + max_data_byte_len: usize, + max_log_num: usize, + topic_num_bounds: (usize, usize), +) -> EthReceiptInput { + let memdb = Arc::new(MemoryDB::new(true)); + let hasher = Arc::new(HasherKeccak::new()); + let mut trie = PatriciaTrie::new(Arc::clone(&memdb), Arc::clone(&hasher)); + let num_rcs = receipts.len(); + let mut vals_cache = Vec::new(); + for (idx, receipt) in receipts.into_iter().enumerate() { + let mut rc_key = rlp::encode(&from_hex(&format!("{idx:x}"))).to_vec(); + if idx == 0 { + rc_key = vec![0x80]; + } + let rc_rlp = get_receipt_rlp(&receipt).unwrap().to_vec(); + trie.insert(rc_key, rc_rlp.clone()).unwrap(); + vals_cache.push(rc_rlp); + } + let root = trie.root().unwrap(); + trie = PatriciaTrie::from(Arc::clone(&memdb), Arc::clone(&hasher), &root).unwrap(); + let root = trie.root().unwrap(); + assert!(root == receipts_root.as_bytes().to_vec()); + + let mut rc_key = rlp::encode(&from_hex(&format!("{tx_idx:x}"))).to_vec(); + if tx_idx == 0 { + rc_key = vec![0x80]; + } + assert!(tx_idx < num_rcs, "Invalid transaction index"); + let rc_rlp = vals_cache[tx_idx].clone(); + let proof = MPTInput { + path: (&rc_key).into(), + value: rc_rlp, + root_hash: receipts_root, + proof: trie.get_proof(&rc_key).unwrap(), + value_max_byte_len: rc_calc_max_val_len(max_data_byte_len, max_log_num, topic_num_bounds), + max_depth: receipt_pf_max_depth, + slot_is_empty: false, + max_key_byte_len: 3, + key_byte_len: Some(rc_key.len()), + }; + EthReceiptInput { idx: tx_idx, proof } +} + +/// Default is 3 retries for each receipt. +pub async fn get_block_with_receipts( + provider: &Provider

, + block_number: u64, + retries: Option, +) -> anyhow::Result { + let default_retries = 3; + let block = provider.get_block(block_number).await?.ok_or(anyhow!("Failed to get block"))?; + let receipts = join_all(block.transactions.iter().map(|&tx_hash| { + let mut retries = retries.unwrap_or(default_retries); + async move { + loop { + let receipt = provider.request("eth_getTransactionReceipt", [tx_hash]).await; + match receipt { + Ok(Some(receipt)) => return Ok(receipt), + Ok(None) => { + if retries == 0 { + return Err(anyhow!("Receipt not found after {}", retries)); + } + retries -= 1; + } + Err(e) => { + if retries == 0 { + return Err(e.into()); + } + retries -= 1; + } + } + } + } + })) + .await + .into_iter() + .collect::>>()?; + + Ok(BlockWithReceipts { + number: block_number.into(), + receipts_root: block.receipts_root, + receipts, + }) +} + +#[cfg(test)] +pub fn get_block_receipt_input( + provider: &Provider

, + tx_hash: H256, + receipt_pf_max_depth: usize, + max_data_byte_len: usize, + max_log_num: usize, + topic_num_bounds: (usize, usize), +) -> EthBlockReceiptInput { + let rt = Runtime::new().unwrap(); + let tx = rt + .block_on(provider.get_transaction(tx_hash)) + .unwrap() + .unwrap_or_else(|| panic!("Transaction {tx_hash} not found")); + let block_number = tx.block_number.unwrap().as_u64(); + let block = get_blocks(provider, [block_number]).unwrap().pop().unwrap().unwrap(); + let block_hash = block.hash.unwrap(); + let block_header = get_block_rlp(&block); + let receipts = + rt.block_on(get_block_with_receipts(provider, block_number, None)).unwrap().receipts; + // requested receipt pf + let receipt = rt.block_on(get_receipt_query( + receipt_pf_max_depth, + receipts, + tx.transaction_index.unwrap().as_usize(), + block.receipts_root, + max_data_byte_len, + max_log_num, + topic_num_bounds, + )); + EthBlockReceiptInput { block_number: block_number as u32, block_hash, block_header, receipt } +} + +#[cfg(test)] +mod tests { + use ethers_core::types::Chain; + use ethers_providers::Middleware; + + use crate::providers::{ + receipt::{ + construct_rc_tries_from_full_blocks, get_block_with_receipts, BlockWithReceipts, + }, + setup_provider, + }; + + // Tests some fixed blocks as well as the 10 latest blocks + #[tokio::test] + async fn test_reconstruct_receipt_trie_mainnet() -> anyhow::Result<()> { + let provider = setup_provider(Chain::Mainnet); + let latest_block = provider.get_block_number().await.unwrap().as_u64(); + let mut block_nums = vec![ + 50_000, 500_000, 5_000_050, 5_000_051, 17_000_000, 17_034_973, 19_426_587, 19_426_589, + ]; + for i in 0..10 { + block_nums.push(latest_block - i); + } + let mut full_blocks = Vec::new(); + for block_num in block_nums { + let block = get_block_with_receipts(&provider, block_num, None).await?; + let block: BlockWithReceipts = serde_json::from_value(serde_json::to_value(block)?)?; + full_blocks.push(block); + } + // Will panic if any tx root does not match trie root: + construct_rc_tries_from_full_blocks(full_blocks)?; + Ok(()) + } + + // Tests some fixed blocks as well as the 10 latest blocks + // Tests OP stack deposit transactions + #[tokio::test] + async fn test_reconstruct_receipt_trie_base() -> anyhow::Result<()> { + let provider = setup_provider(Chain::Base); + let latest_block = provider.get_block_number().await.unwrap().as_u64(); + let mut block_nums = vec![10, 100_000, 5_000_050, 8_000_000, 8578617, 11_864_572]; + for i in 0..10 { + block_nums.push(latest_block - i); + } + let mut full_blocks = Vec::new(); + for block_num in block_nums { + let block = get_block_with_receipts(&provider, block_num, None).await?; + let block: BlockWithReceipts = serde_json::from_value(serde_json::to_value(block)?)?; + full_blocks.push(block); + } + // Will panic if any tx root does not match trie root: + construct_rc_tries_from_full_blocks(full_blocks)?; + Ok(()) + } +} + +#[cfg(test)] +mod data_analysis { + use std::{fs::File, io::Write}; + + use ethers_core::types::Chain; + + use crate::providers::setup_provider; + + use super::*; + #[test] + #[ignore] + pub fn find_receipt_lens() -> Result<(), Box> { + let rt = Runtime::new().unwrap(); + let provider = setup_provider(Chain::Mainnet); + + let mut data_file = File::create("data.txt").expect("creation failed"); + for i in 0..100 { + let receipts = rt + .block_on(get_block_with_receipts(&provider, 17578525 - i, None)) + .unwrap() + .receipts; + for (j, receipt) in receipts.into_iter().enumerate() { + let _len = { + let mut s = RlpStream::new(); + s.append_list(&receipt.logs); + let rlp_bytes: Vec = s.out().freeze().into(); + rlp_bytes.len() + }; + //let len = transaction.input.len(); + //let len = receipts[j].logs.len(); + for i in 0..receipt.logs.len() { + let len = receipt.logs[i].data.len(); + data_file + .write_all( + (len.to_string() + + ", " + + &j.to_string() + + ", " + + &(17578525 - i).to_string() + + ", " + + "\n") + .as_bytes(), + ) + .expect("write failed"); + } + } + } + Ok(()) + } +} diff --git a/axiom-eth/src/providers/storage.rs b/axiom-eth/src/providers/storage.rs new file mode 100644 index 00000000..01973eb8 --- /dev/null +++ b/axiom-eth/src/providers/storage.rs @@ -0,0 +1,272 @@ +use ethers_core::{ + types::{Address, Bytes, EIP1186ProofResponse, H256}, + utils::keccak256, +}; +use ethers_providers::{JsonRpcClient, Middleware, Provider}; +use futures::future::join_all; +use rlp::{Encodable, Rlp, RlpStream}; +use tokio::runtime::Runtime; +use zkevm_hashes::util::eth_types::ToBigEndian; + +use crate::{ + mpt::{MPTInput, KECCAK_RLP_EMPTY_STRING}, + providers::account::get_acct_list, + storage::{ + circuit::{EthBlockStorageInput, EthStorageInput}, + ACCOUNT_PROOF_VALUE_MAX_BYTE_LEN, STORAGE_PROOF_VALUE_MAX_BYTE_LEN, + }, +}; + +use super::block::get_block_rlp; + +/// stateRoot is not provided and set to H256(0) +pub fn json_to_mpt_input( + pf: EIP1186ProofResponse, + acct_pf_max_depth: usize, + storage_pf_max_depth: usize, +) -> EthStorageInput { + let addr = pf.address; + let acct_key = H256(keccak256(addr)); + let slot_is_empty = !is_assigned_slot(&acct_key, &pf.account_proof); + log::debug!("address: {addr}, account is empty: {slot_is_empty}"); + let acct_state = get_acct_list(&pf); + // JSON-RPC Provider has been known to provide wrong codeHash vs MPT value, so we extract value from MPT proof itself + // If the proof ends with a branch node that contains the leaf node, we extract the + // leaf node and add it to the end of the proof so that our mpt implementation can + // handle it. + let get_new_proof_and_value = |key: &H256, proof: &[Bytes]| { + if proof.is_empty() { + return (vec![], vec![]); + } + let decode = Rlp::new(proof.last().unwrap()); + assert!(decode.is_list()); + let add_leaf = decode.item_count().unwrap() == 17; + let mut new_proof: Vec> = proof.iter().map(|x| x.to_vec()).collect(); + let value; + if add_leaf { + let last_nibble = last_nibble(key, proof); + assert!(last_nibble < 16); + let leaf = decode.at(last_nibble as usize).unwrap(); + if let Ok(leaf) = leaf.as_list::>() { + if leaf.is_empty() { + // this is a non-inclusion proof + value = vec![0x80]; + } else { + assert_eq!(leaf.len(), 2); + value = leaf.last().unwrap().to_vec(); + let leaf = rlp::encode_list::, Vec>(&leaf).to_vec(); + new_proof.push(leaf); + } + } else { + value = leaf.as_val().unwrap() + } + } else { + value = decode.val_at(1).unwrap() + } + (new_proof, value) + }; + let (new_acct_pf, mut acct_state_rlp) = get_new_proof_and_value(&acct_key, &pf.account_proof); + let mut storage_root = pf.storage_hash; + if slot_is_empty { + acct_state_rlp = EMPTY_ACCOUNT_RLP.clone(); + // If account is empty, then storage root should be + // - null root hash = keccak(rlp("")) = keccak(0x80) = 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 + storage_root = H256::from_slice(&KECCAK_RLP_EMPTY_STRING); + } + let acct_pf = MPTInput { + path: acct_key.into(), + value: acct_state_rlp, + root_hash: H256([0u8; 32]), // STATE ROOT IS UNKNOWN IN THIS FUNCTION AND NOT SET + proof: new_acct_pf, + value_max_byte_len: ACCOUNT_PROOF_VALUE_MAX_BYTE_LEN, + max_depth: acct_pf_max_depth, + slot_is_empty, + max_key_byte_len: 32, + key_byte_len: None, + }; + let storage_pfs = pf + .storage_proof + .into_iter() + .map(|storage_pf| { + let path = H256(keccak256(storage_pf.key.to_be_bytes())); + let (new_proof, mut value) = get_new_proof_and_value(&path, &storage_pf.proof); + let new_proof_bytes: Vec = + new_proof.clone().into_iter().map(Bytes::from_iter).collect(); + let slot_is_empty = !is_assigned_slot(&path, &new_proof_bytes); + if slot_is_empty { + value = vec![0x80]; + } + assert_eq!(&value, storage_pf.value.rlp_bytes().as_ref()); + log::info!( + "address: {addr}, slot: {}, storage slot is empty: {slot_is_empty}", + storage_pf.key + ); + ( + storage_pf.key, + storage_pf.value, + MPTInput { + path: path.into(), + value, + root_hash: storage_root, + proof: new_proof, + value_max_byte_len: STORAGE_PROOF_VALUE_MAX_BYTE_LEN, + max_depth: storage_pf_max_depth, + slot_is_empty, + max_key_byte_len: 32, + key_byte_len: None, + }, + ) + }) + .collect(); + EthStorageInput { addr, acct_state, acct_pf, storage_pfs } +} + +/// 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 { + log::debug!("block number: {block_number}"); + let pf = provider.get_proof(addr, slots, Some(block_number.into())).await.unwrap(); + json_to_mpt_input(pf, acct_pf_max_depth, storage_pf_max_depth) +} + +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 +} + +/// In the case that we have a branch node at the end and we want to +/// read the next node, this tells us which entry to look at. +pub fn last_nibble(key: &H256, proof: &[Bytes]) -> u8 { + 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; + } + } + key_nibbles[path_idx - 1] +} + +pub fn empty_account_rlp() -> Vec { + let mut rlp = RlpStream::new_list(4); + rlp.append(&0u8); + rlp.append(&0u8); + rlp.append(&H256::from_slice(&KECCAK_RLP_EMPTY_STRING)); // null storageRoot + rlp.append(&H256::zero()); + rlp.out().to_vec() +} + +lazy_static::lazy_static! { + pub static ref EMPTY_ACCOUNT_RLP: Vec = empty_account_rlp(); +} diff --git a/axiom-eth/src/providers/transaction.rs b/axiom-eth/src/providers/transaction.rs new file mode 100644 index 00000000..c915a54a --- /dev/null +++ b/axiom-eth/src/providers/transaction.rs @@ -0,0 +1,540 @@ +use std::{collections::HashMap, sync::Arc}; + +use anyhow::{anyhow, bail}; +use cita_trie::{MemoryDB, PatriciaTrie, Trie}; +use ethers_core::types::{Block, Bytes, Transaction, H256, U128, U64}; +use ethers_providers::{JsonRpcClient, Middleware, Provider}; +use hasher::HasherKeccak; +use rlp::RlpStream; +use serde::{Deserialize, Serialize}; +use tokio::runtime::Runtime; + +use crate::providers::from_hex; +#[cfg(test)] +use crate::{ + mpt::MPTInput, + transaction::{ + calc_max_val_len as tx_calc_max_val_len, EthBlockTransactionsInput, EthTransactionLenProof, + EthTransactionProof, + }, +}; + +// ========== Raw Transaction RLP computations ============= +// These are not yet implemented in alloy, and ethers_core does not support EIP-4844 transactions, so we keep a custom implementation here. + +/// The new fields in a EIP 4844 blob transaction. +/// This object is meant to be transmuted from the `other` field in [`Transaction`]. +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BlobTransactionFields { + /// Configured max fee per blob gas for eip-4844 transactions + pub max_fee_per_blob_gas: U128, + /// Contains the blob hashes for eip-4844 transactions. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub blob_versioned_hashes: Vec, +} + +/// Computes the RLP encoding, in bytes, of a raw transaction. +/// Updates `ethers_core` to support EIP-4844 transactions. +pub fn get_transaction_rlp(transaction: &Transaction) -> anyhow::Result { + match transaction.transaction_type { + // EIP-4844 blob transaction + Some(x) if x == U64::from(3) => { + let other = serde_json::to_value(&transaction.other)?; + let blob_fields: BlobTransactionFields = serde_json::from_value(other)?; + let mut rlp = RlpStream::new(); + rlp.begin_unbounded_list(); + let chain_id = transaction.chain_id.ok_or(anyhow!("Eip4844 tx missing chainId"))?; + let max_priority_fee_per_gas = transaction + .max_priority_fee_per_gas + .ok_or(anyhow!("Eip4844 tx missing maxPriorityFeePerGas"))?; + let max_fee_per_gas = + transaction.max_fee_per_gas.ok_or(anyhow!("Eip4844 tx missing maxFeePerGas"))?; + let to = transaction.to.ok_or(anyhow!("Eip4844 tx `to` MUST NOT be nil"))?; + rlp.append(&chain_id); + rlp.append(&transaction.nonce); + rlp.append(&max_priority_fee_per_gas); + rlp.append(&max_fee_per_gas); + rlp.append(&transaction.gas); + rlp.append(&to); + rlp.append(&transaction.value); + rlp.append(&transaction.input.as_ref()); + rlp_opt_list(&mut rlp, &transaction.access_list); + rlp.append(&blob_fields.max_fee_per_blob_gas); + rlp.append_list(&blob_fields.blob_versioned_hashes); + rlp.append(&normalize_v(transaction.v.as_u64(), chain_id.as_u64())); + rlp.append(&transaction.r); + rlp.append(&transaction.s); + rlp.finalize_unbounded_list(); + let rlp_bytes: Bytes = rlp.out().freeze().into(); + let mut encoded = vec![]; + encoded.extend_from_slice(&[0x3]); + encoded.extend_from_slice(rlp_bytes.as_ref()); + Ok(encoded.into()) + } + // Legacy, EIP-2718, or EIP-155 transactions are handled by ethers_core + // So are Optimism Deposited Transactions + _ => Ok(transaction.rlp()), + } +} + +// Copied from https://github.com/gakonst/ethers-rs/blob/5394d899adca736a602e316e6f0c06fdb5aa64b9/ethers-core/src/types/transaction/mod.rs#L22 +/// RLP encode a value if it exists or else encode an empty string. +pub fn rlp_opt(rlp: &mut rlp::RlpStream, opt: &Option) { + if let Some(inner) = opt { + rlp.append(inner); + } else { + rlp.append(&""); + } +} + +/// RLP encode a value if it exists or else encode an empty list. +pub fn rlp_opt_list(rlp: &mut rlp::RlpStream, opt: &Option) { + if let Some(inner) = opt { + rlp.append(inner); + } else { + // Choice of `u8` type here is arbitrary as all empty lists are encoded the same. + rlp.append_list::(&[]); + } +} + +/// normalizes the signature back to 0/1 +pub fn normalize_v(v: u64, chain_id: u64) -> u64 { + if v > 1 { + v - chain_id * 2 - 35 + } else { + v + } +} + +// ========================= Transactions Trie Construction ========================= +pub struct BlockTransactionsDb { + pub trie: PatriciaTrie, + pub root: H256, + pub tx_rlps: Vec>, +} + +impl BlockTransactionsDb { + pub fn new( + trie: PatriciaTrie, + root: H256, + tx_rlps: Vec>, + ) -> Self { + Self { trie, root, tx_rlps } + } +} + +// ===== Block with Transactions ===== +/// A block with all transactions. We require the transactionsRoot to be provided for a safety check. +/// Deserialization should still work on an object with extra fields. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct BlockWithTransactions { + /// Block number + pub number: U64, + /// Transactions root hash + pub transactions_root: H256, + /// All transactions in the block + pub transactions: Vec, +} + +impl TryFrom> for BlockWithTransactions { + type Error = &'static str; + fn try_from(block: Block) -> Result { + Ok(Self { + number: block.number.ok_or("Block not in chain")?, + transactions_root: block.transactions_root, + transactions: block.transactions, + }) + } +} + +/// For each block with all raw transactions, constructs the Merkle Patricia trie and returns a map from block number to the trie as well as flat vector of transactions. +pub fn construct_tx_tries_from_full_blocks( + blocks: Vec, +) -> anyhow::Result> { + let mut tries = HashMap::new(); + for block in blocks { + let mut trie = + PatriciaTrie::new(Arc::new(MemoryDB::new(true)), Arc::new(HasherKeccak::new())); + let mut tx_rlps = Vec::with_capacity(block.transactions.len()); + for (idx, tx) in block.transactions.into_iter().enumerate() { + let tx_key = get_tx_key_from_index(idx); + let tx_rlp = get_transaction_rlp(&tx)?.to_vec(); + tx_rlps.push(tx_rlp.clone()); + trie.insert(tx_key, tx_rlp)?; + } + // safety check: + let root = trie.root()?; + if root != block.transactions_root.as_bytes() { + bail!("Transactions trie incorrectly constructed"); + } + let root = block.transactions_root; + tries.insert(block.number.as_u64(), BlockTransactionsDb::new(trie, root, tx_rlps)); + } + Ok(tries) +} + +pub fn get_tx_key_from_index(idx: usize) -> Vec { + let mut tx_key = rlp::encode(&from_hex(&format!("{idx:x}"))).to_vec(); + if idx == 0 { + tx_key = vec![0x80]; + } + tx_key +} + +// This function only for testing use +#[cfg(test)] +/// Creates transaction proof by reconstructing the transaction MPTrie. +/// `transactions` should be all transactions in the block +async fn get_transaction_query( + transaction_pf_max_depth: usize, + transactions: Vec, + idxs: Vec, + transactions_root: H256, + max_data_byte_len: usize, + max_access_list_len: usize, + enable_types: [bool; 3], + constrain_len: bool, +) -> (Vec, Option) { + let memdb = Arc::new(MemoryDB::new(true)); + let hasher = Arc::new(HasherKeccak::new()); + let mut trie = PatriciaTrie::new(Arc::clone(&memdb), Arc::clone(&hasher)); + let num_txs = transactions.len(); + let mut pfs = Vec::new(); + let mut vals_cache = Vec::new(); + #[allow(clippy::needless_range_loop)] + for idx in 0..num_txs { + let tx = transactions[idx].clone(); + let mut tx_key = rlp::encode(&from_hex(&format!("{idx:x}").to_string())).to_vec(); + if idx == 0 { + tx_key = vec![0x80]; + } + let tx_rlp = get_transaction_rlp(&tx).unwrap().to_vec(); + trie.insert(tx_key.clone(), tx_rlp.clone()).unwrap(); + vals_cache.push(tx_rlp); + } + let root = trie.root().unwrap(); + trie = PatriciaTrie::from(Arc::clone(&memdb), Arc::clone(&hasher), &root).unwrap(); + let root = trie.root().unwrap(); + assert_eq!(&root, transactions_root.as_bytes()); + + for idx in idxs { + let mut tx_key = rlp::encode(&from_hex(&format!("{idx:x}").to_string())).to_vec(); + if idx == 0 { + tx_key = vec![0x80]; + } + let tx_rlp = if idx < num_txs { vals_cache[idx].clone() } else { vec![0xba; 32] }; + let proof = MPTInput { + path: (&tx_key).into(), + value: tx_rlp, + root_hash: transactions_root, + proof: trie.get_proof(&tx_key).unwrap(), + value_max_byte_len: tx_calc_max_val_len( + max_data_byte_len, + max_access_list_len, + enable_types, + ), + max_depth: transaction_pf_max_depth, + slot_is_empty: idx >= num_txs, + max_key_byte_len: 3, + key_byte_len: Some(tx_key.len()), + }; + pfs.push(EthTransactionProof { tx_index: idx, proof }); + } + let pf0 = if num_txs > 0 { + let mut tx_key = rlp::encode(&from_hex(&format!("{:x}", num_txs - 1))).to_vec(); + if num_txs == 1 { + tx_key = vec![0x80]; + } + let tx_rlp = vals_cache[num_txs - 1].clone(); + EthTransactionProof { + tx_index: num_txs - 1, + proof: MPTInput { + path: (&tx_key).into(), + value: tx_rlp, + root_hash: transactions_root, + proof: trie.get_proof(&tx_key).unwrap(), + value_max_byte_len: tx_calc_max_val_len( + max_data_byte_len, + max_access_list_len, + enable_types, + ), + max_depth: transaction_pf_max_depth, + slot_is_empty: false, + max_key_byte_len: 3, + key_byte_len: Some(tx_key.len()), + }, + } + } else { + let tx_key = rlp::encode(&from_hex(&format!("{:x}", num_txs + 1))).to_vec(); + let tx_rlp = [0xba; 32].to_vec(); + EthTransactionProof { + tx_index: num_txs + 1, + proof: MPTInput { + path: (&tx_key).into(), + value: tx_rlp, + root_hash: transactions_root, + proof: trie.get_proof(&tx_key).unwrap(), + value_max_byte_len: tx_calc_max_val_len( + max_data_byte_len, + max_access_list_len, + enable_types, + ), + max_depth: transaction_pf_max_depth, + slot_is_empty: true, + max_key_byte_len: 3, + key_byte_len: Some(tx_key.len()), + }, + } + }; + + let mut tx_key = rlp::encode(&from_hex(&format!("{num_txs:x}"))).to_vec(); + if num_txs == 0 { + tx_key = vec![0x80]; + } + let tx_rlp = [0xba; 2].to_vec(); + let pf1 = EthTransactionProof { + tx_index: num_txs, + proof: MPTInput { + path: (&tx_key).into(), + value: tx_rlp, + root_hash: transactions_root, + proof: trie.get_proof(&tx_key).unwrap(), + value_max_byte_len: tx_calc_max_val_len( + max_data_byte_len, + max_access_list_len, + enable_types, + ), + max_depth: transaction_pf_max_depth, + slot_is_empty: true, + max_key_byte_len: 3, + key_byte_len: Some(tx_key.len()), + }, + }; + let len_proof = if constrain_len { + Some(EthTransactionLenProof { inclusion: pf0, noninclusion: pf1 }) + } else { + None + }; + (pfs, len_proof) +} + +#[cfg(test)] +pub fn get_block_transaction_input( + provider: &Provider

, + idxs: Vec, + block_number: u32, + transaction_pf_max_depth: usize, + max_data_byte_len: usize, + max_access_list_len: usize, + enable_types: [bool; 3], + constrain_len: bool, +) -> EthBlockTransactionsInput { + let rt = Runtime::new().unwrap(); + let block = rt + .block_on(provider.get_block_with_txs(block_number as u64)) + .unwrap() + .unwrap_or_else(|| panic!("Block {block_number} not found")); + let block2 = rt + .block_on(provider.get_block(block_number as u64)) + .unwrap() + .unwrap_or_else(|| panic!("Block {block_number} not found")); + + for i in 0..block.transactions.len() { + assert_eq!(block.transactions[i].hash(), block2.transactions[i]); + } + + let block_hash = block.hash.unwrap(); + let block_header = super::block::get_block_rlp(&block); + + let (tx_proofs, len_proof) = rt.block_on(get_transaction_query( + transaction_pf_max_depth, + block.clone().transactions, + idxs, + block.transactions_root, + max_data_byte_len, + max_access_list_len, + enable_types, + constrain_len, + )); + // println!("{block_header:?}"); + // println!("{tx_input:?}"); + EthBlockTransactionsInput { + block, + block_number, + block_hash, + block_header, + tx_proofs, + len_proof, + } +} + +pub fn get_block_transaction_len( + provider: &Provider

, + block_number: u32, +) -> usize { + let rt = Runtime::new().unwrap(); + let block2 = rt + .block_on(provider.get_block(block_number as u64)) + .unwrap() + .unwrap_or_else(|| panic!("Block {block_number} not found")); + block2.transactions.len() +} + +pub fn get_block_transactions( + provider: &Provider

, + block_number: u32, +) -> Vec { + let rt = Runtime::new().unwrap(); + let block2 = rt + .block_on(provider.get_block_with_txs(block_number as u64)) + .unwrap() + .unwrap_or_else(|| panic!("Block {block_number} not found")); + block2.transactions +} + +pub fn get_block_access_list_num( + provider: &Provider

, + block_number: u32, +) -> usize { + let rt = Runtime::new().unwrap(); + let block2 = rt + .block_on(provider.get_block_with_txs(block_number as u64)) + .unwrap() + .unwrap_or_else(|| panic!("Block {block_number} not found")); + let mut cnt: usize = 0; + for tx in block2.transactions { + match tx.access_list { + None => {} + Some(al) => { + cnt += !al.0.is_empty() as usize; + } + } + } + cnt +} + +#[cfg(test)] +mod data_analysis { + use std::{fs::File, io::Write}; + + use ethers_core::types::Chain; + use ethers_providers::Middleware; + use rlp::RlpStream; + + use crate::providers::{ + setup_provider, + transaction::{construct_tx_tries_from_full_blocks, BlockWithTransactions}, + }; + + use super::{get_block_access_list_num, get_block_transaction_len, get_block_transactions}; + + // Tests some fixed blocks as well as the 10 latest blocks + #[tokio::test] + async fn test_reconstruct_tx_trie_mainnet() -> anyhow::Result<()> { + let provider = setup_provider(Chain::Mainnet); + let latest_block = provider.get_block_number().await.unwrap().as_u64(); + let mut block_nums = + vec![500_000, 5_000_050, 5_000_051, 17_034_973, 19_426_587, 19_426_589]; + for i in 0..10 { + block_nums.push(latest_block - i); + } + let mut full_blocks = Vec::new(); + for block_num in block_nums { + let block = provider.get_block_with_txs(block_num).await?.unwrap(); + let block: BlockWithTransactions = + serde_json::from_value(serde_json::to_value(block)?)?; + full_blocks.push(block); + } + // Will panic if any tx root does not match trie root: + construct_tx_tries_from_full_blocks(full_blocks)?; + Ok(()) + } + + // Tests some fixed blocks as well as the 10 latest blocks + // Tests OP stack deposit transactions + #[tokio::test] + async fn test_reconstruct_tx_trie_base() -> anyhow::Result<()> { + let provider = setup_provider(Chain::Base); + let latest_block = provider.get_block_number().await.unwrap().as_u64(); + let mut block_nums = vec![10, 100_000, 5_000_050, 8_000_000, 11_864_572]; + for i in 0..10 { + block_nums.push(latest_block - i); + } + let mut full_blocks = Vec::new(); + dbg!(&block_nums); + for block_num in block_nums { + let block = provider.get_block_with_txs(block_num).await?.unwrap(); + let block: BlockWithTransactions = + serde_json::from_value(serde_json::to_value(block)?)?; + full_blocks.push(block); + } + // Will panic if any tx root does not match trie root: + construct_tx_tries_from_full_blocks(full_blocks)?; + Ok(()) + } + + #[test] + #[ignore] + pub fn find_good_block256() { + let provider = setup_provider(Chain::Mainnet); + for block_number in 5000000..6000000 { + let num_tx = get_block_transaction_len(&provider, block_number.try_into().unwrap()); + if num_tx > 256 { + println!("Desired Block: {block_number:?}"); + } + } + } + + #[test] + #[ignore] + pub fn find_access_lists() { + let provider = setup_provider(Chain::Mainnet); + let mut trend = Vec::new(); + + let mut data_file = File::create("data.txt").expect("creation failed"); + for i in 0..100 { + let cnt = get_block_access_list_num(&provider, 17578525 - i); + trend.push((17578525 - i, cnt)); + data_file.write_all((cnt.to_string() + "\n").as_bytes()).expect("write failed"); + } + } + + #[test] + #[ignore] + pub fn find_transaction_lens() { + let provider = setup_provider(Chain::Mainnet); + let mut trend = Vec::new(); + + let mut data_file = File::create("data.txt").expect("creation failed"); + for i in 0..100 { + let transactions = get_block_transactions(&provider, 17578525 - i); + for (j, transaction) in transactions.into_iter().enumerate() { + trend.push((17578525 - i, transaction.input.len())); + let _len = match transaction.access_list { + Some(a_list) => { + let mut s = RlpStream::new(); + s.append(&a_list); + let rlp_bytes: Vec = s.out().freeze().into(); + rlp_bytes.len() + } + None => 0, + }; + let len = transaction.input.len(); + data_file + .write_all( + (len.to_string() + + ", " + + &j.to_string() + + ", " + + &(17578525 - i).to_string() + + ", " + + "\n") + .as_bytes(), + ) + .expect("write failed"); + } + } + } +} diff --git a/axiom-eth/src/receipt/mod.rs b/axiom-eth/src/receipt/mod.rs new file mode 100644 index 00000000..6be1ec72 --- /dev/null +++ b/axiom-eth/src/receipt/mod.rs @@ -0,0 +1,283 @@ +//! See https://hackmd.io/@axiom/H1TYkiBt2 for receipt data format + +use crate::{ + block_header::EthBlockHeaderChip, + keccak::KeccakChip, + mpt::MPTChip, + rlc::{ + chip::RlcChip, + circuit::builder::{RlcCircuitBuilder, RlcContextPair}, + }, + rlp::{evaluate_byte_array, max_rlp_len_len, RlpChip}, + transaction::TRANSACTION_IDX_MAX_LEN, + utils::circuit_utils::constrain_no_leading_zeros, +}; + +use ethers_core::types::Chain; +use halo2_base::{ + gates::{ + flex_gate::threads::parallelize_core, GateChip, GateInstructions, RangeChip, + RangeInstructions, + }, + AssignedValue, Context, + QuantumCell::{Constant, Existing}, +}; +use itertools::Itertools; + +//pub mod task; +#[cfg(all(test, feature = "providers"))] +pub mod tests; +mod types; +use crate::Field; +use serde::{Deserialize, Serialize}; +pub use types::*; + +pub const RECEIPT_NUM_FIELDS: usize = 4; +pub const RECEIPT_FIELDS_LOG_INDEX: usize = 3; + +pub fn calc_max_val_len( + max_data_byte_len: usize, + max_log_num: usize, + (_min_topic_num, max_topic_num): (usize, usize), +) -> usize { + let max_log_len = calc_max_log_len(max_data_byte_len, max_topic_num); + 4 + 33 + 33 + 259 + 4 + max_log_num * max_log_len +} + +fn calc_max_log_len(max_data_byte_len: usize, max_topic_num: usize) -> usize { + 3 + 21 + 3 + 33 * max_topic_num + 3 + max_data_byte_len + 1 +} + +/// Configuration parameters for [EthReceiptChip] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Hash, Default)] +pub struct EthReceiptChipParams { + pub max_data_byte_len: usize, + pub max_log_num: usize, + pub topic_num_bounds: (usize, usize), // min, max + /// Must be provided if using functions involving block header + pub network: Option, +} + +#[derive(Clone, Debug)] +pub struct EthReceiptChip<'chip, F: Field> { + pub mpt: &'chip MPTChip<'chip, F>, + pub params: EthReceiptChipParams, +} + +impl<'chip, F: Field> EthReceiptChip<'chip, F> { + pub fn new(mpt: &'chip MPTChip<'chip, F>, params: EthReceiptChipParams) -> Self { + Self { mpt, params } + } + pub fn gate(&self) -> &GateChip { + self.mpt.gate() + } + + pub fn range(&self) -> &RangeChip { + self.mpt.range() + } + + pub fn rlc(&self) -> &RlcChip { + self.mpt.rlc() + } + + pub fn rlp(&self) -> RlpChip { + self.mpt.rlp() + } + + pub fn keccak(&self) -> &KeccakChip { + self.mpt.keccak() + } + + pub fn mpt(&self) -> &'chip MPTChip<'chip, F> { + self.mpt + } + + pub fn network(&self) -> Option { + self.params.network + } + + pub fn block_header_chip(&self) -> EthBlockHeaderChip { + EthBlockHeaderChip::new_from_network( + self.rlp(), + self.network().expect("Must provide network to access block header chip"), + ) + } + + /// FirstPhase of proving the inclusion **or** exclusion of a transaction index within a receipt root. + /// In the case of + /// - inclusion: then parses the receipt + /// - exclusion: `input.proof.slot_is_empty` is true, and we return `tx_type = -1, value = rlp(0x00)`. + pub fn parse_receipt_proof_phase0( + &self, + ctx: &mut Context, + input: EthReceiptInputAssigned, + ) -> EthReceiptWitness { + let EthReceiptChipParams { max_data_byte_len, max_log_num, topic_num_bounds, .. } = + self.params; + let EthReceiptInputAssigned { tx_idx: transaction_index, proof } = input; + // Load value early to avoid borrow errors + let slot_is_empty = proof.slot_is_empty; + // check key is rlp(idx) + let idx_witness = self.rlp().decompose_rlp_field_phase0( + ctx, + proof.key_bytes.clone(), + TRANSACTION_IDX_MAX_LEN, + ); + let tx_idx = + evaluate_byte_array(ctx, self.gate(), &idx_witness.field_cells, idx_witness.field_len); + ctx.constrain_equal(&tx_idx, &transaction_index); + constrain_no_leading_zeros( + ctx, + self.gate(), + &idx_witness.field_cells, + idx_witness.field_len, + ); + + // check MPT inclusion + let mpt_witness = self.mpt.parse_mpt_inclusion_phase0(ctx, proof); + + // parse receipt + // when we disable all types, we use that as a flag to parse dummy values which are two bytes long + let type_is_not_zero = + self.range().is_less_than(ctx, mpt_witness.value_bytes[0], Constant(F::from(128)), 8); + // if the first byte is greater than 0xf7, the type is zero. Otherwise, the type is the first byte. + let mut tx_type = self.gate().mul(ctx, type_is_not_zero, mpt_witness.value_bytes[0]); + let max_val_len = calc_max_val_len(max_data_byte_len, max_log_num, topic_num_bounds); + let mut new_value_witness = Vec::with_capacity(max_val_len); + let slot_is_full = self.gate().not(ctx, slot_is_empty); + tx_type = self.gate().mul(ctx, tx_type, slot_is_full); + // tx_type = -1 if and only if the slot is empty, serves as a flag + tx_type = self.gate().sub(ctx, tx_type, slot_is_empty); + // parse the zeroes string if the slot is empty so that we don't run into errors + for i in 0..max_val_len { + let mut val_byte = self.gate().select( + ctx, + mpt_witness + .value_bytes + .get(i + 1) + .map(|a| Existing(*a)) + .unwrap_or(Constant(F::ZERO)), + mpt_witness.value_bytes[i], + type_is_not_zero, + ); + val_byte = if i == 0 { + // 0xc100 = rlp(0x00) + self.gate().select(ctx, val_byte, Constant(F::from(0xc1)), slot_is_full) + } else { + self.gate().mul(ctx, val_byte, slot_is_full) + }; + new_value_witness.push(val_byte); + } + // max byte length of each log + let max_log_len = calc_max_log_len(max_data_byte_len, topic_num_bounds.1); + // max byte length of rlp encoding of all logs + let max_logs_rlp_len = + 1 + max_rlp_len_len(max_log_len * max_log_num) + max_log_len * max_log_num; + let max_field_lens = [33, 33, 259, max_logs_rlp_len]; + let value = + self.rlp().decompose_rlp_array_phase0(ctx, new_value_witness, &max_field_lens, true); + let max_log_lens = vec![max_log_len; max_log_num]; + let logs = self.rlp().decompose_rlp_array_phase0( + ctx, + value.field_witness[RECEIPT_FIELDS_LOG_INDEX].encoded_item.clone(), + &max_log_lens, + true, + ); + + EthReceiptWitness { receipt_type: tx_type, tx_idx, idx_witness, value, logs, mpt_witness } + } + + /// SecondPhase of proving inclusion **or** exclusion of a transaction index in receipt root, and then parses + /// the receipt. See [`parse_receipt_proof_phase0`] for more details. + pub fn parse_receipt_proof_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + witness: EthReceiptWitness, + ) -> EthReceiptTrace { + // Comments below just to log what load_rlc_cache calls are done in the internal functions: + self.rlp().decompose_rlp_field_phase1((ctx_gate, ctx_rlc), witness.idx_witness); + // load_rlc_cache bit_length(2*mpt_witness.key_byte_len) + self.mpt.parse_mpt_inclusion_phase1((ctx_gate, ctx_rlc), witness.mpt_witness); + // load rlc_cache bit_length(value_witness.rlp_field.len()) + let trace = self.rlp().decompose_rlp_array_phase1((ctx_gate, ctx_rlc), witness.value, true); + let value_trace = trace.field_trace; + self.rlp().decompose_rlp_array_phase1((ctx_gate, ctx_rlc), witness.logs, true); + EthReceiptTrace { receipt_type: witness.receipt_type, value_trace } + } + + /// Parallelizes `parse_receipt_proof_phase0`. + pub fn parse_receipt_proofs_phase0( + &self, + builder: &mut RlcCircuitBuilder, + input: Vec>, + ) -> Vec> { + parallelize_core(builder.base.pool(0), input, |ctx, input| { + self.parse_receipt_proof_phase0(ctx, input) + }) + } + + /// Parallelizes `parse_receipt_proof_phase1` + pub fn parse_receipt_proofs_phase1( + &self, + builder: &mut RlcCircuitBuilder, + receipt_witness: Vec>, + ) -> Vec> { + // load rlc cache should be done globally; no longer done here + builder.parallelize_phase1(receipt_witness, |(ctx_gate, ctx_rlc), witness| { + self.parse_receipt_proof_phase1((ctx_gate, ctx_rlc), witness) + }) + } + + /// Extracts the field at `field_idx` from the given rlp decomposition of a receipt. + pub fn extract_receipt_field( + &self, + ctx: &mut Context, + witness: &EthReceiptWitness, + field_idx: AssignedValue, + ) -> EthReceiptFieldWitness { + let zero = ctx.load_zero(); + let field_witness = &witness.value.field_witness; + assert_eq!(field_witness.len(), RECEIPT_NUM_FIELDS); + let ans_len = field_witness.iter().map(|w| w.field_cells.len()).max().unwrap(); + let mut value_bytes = Vec::with_capacity(ans_len); + let indicator = self.gate().idx_to_indicator(ctx, field_idx, RECEIPT_NUM_FIELDS); + for i in 0..ans_len { + let entries = + field_witness.iter().map(|w| *w.field_cells.get(i).unwrap_or(&zero)).collect_vec(); + let byte = self.gate().select_by_indicator(ctx, entries, indicator.clone()); + value_bytes.push(byte); + } + let lens = field_witness.iter().map(|w| w.field_len).collect_vec(); + let value_len = self.gate().select_by_indicator(ctx, lens, indicator); + EthReceiptFieldWitness { field_idx, value_bytes, value_len } + } + + /// Extracts the log at `log_idx` from the given rlp decomposition of a receipt. + pub fn extract_receipt_log( + &self, + ctx: &mut Context, + witness: &EthReceiptWitness, + log_idx: AssignedValue, + ) -> EthReceiptLogWitness { + let zero = ctx.load_zero(); + let max_log_num = self.params.max_log_num; + // select log by log_idx + let log_ind = self.gate().idx_to_indicator(ctx, log_idx, max_log_num); + let logs = &witness.logs.field_witness; + assert_eq!(witness.logs.field_witness.len(), max_log_num); + let max_log_len = logs.iter().map(|w| w.max_field_len).max().unwrap(); + let mut log_bytes = Vec::with_capacity(max_log_len); + for i in 0..max_log_len { + let byte = self.gate().select_by_indicator( + ctx, + logs.iter().map(|w| *w.encoded_item.get(i).unwrap_or(&zero)), + log_ind.clone(), + ); + log_bytes.push(byte); + } + let log_len = + self.gate().select_by_indicator(ctx, logs.iter().map(|w| w.encoded_item_len), log_ind); + + EthReceiptLogWitness { log_idx, log_len, log_bytes } + } +} diff --git a/axiom-eth/src/receipt/tests/data/field/single_rc_pos_test_legacy.json b/axiom-eth/src/receipt/tests/data/field/single_rc_pos_test_legacy.json new file mode 100644 index 00000000..cb1d4084 --- /dev/null +++ b/axiom-eth/src/receipt/tests/data/field/single_rc_pos_test_legacy.json @@ -0,0 +1,5 @@ +{ + "tx_hash": "4174b713caa15ee44fbbb73e2990c5ab794fe79f448a711430f34acf41d4b17f", + "field_idx": 1, + "log_idx": 1 +} \ No newline at end of file diff --git a/axiom-eth/src/receipt/tests/data/field/single_rc_pos_test_new.json b/axiom-eth/src/receipt/tests/data/field/single_rc_pos_test_new.json new file mode 100644 index 00000000..088772d1 --- /dev/null +++ b/axiom-eth/src/receipt/tests/data/field/single_rc_pos_test_new.json @@ -0,0 +1,5 @@ +{ + "tx_hash": "da6310baf32ffeb16afb7c2f064a7b64d79beccb585082753c083da15b8c38f2", + "field_idx": 1, + "log_idx": 1 +} \ No newline at end of file diff --git a/axiom-eth/src/receipt/tests/data/multi_tx_pos_test_legacy.json b/axiom-eth/src/receipt/tests/data/multi_tx_pos_test_legacy.json new file mode 100644 index 00000000..a3cd191a --- /dev/null +++ b/axiom-eth/src/receipt/tests/data/multi_tx_pos_test_legacy.json @@ -0,0 +1,5 @@ +{ + "idxs": [0, 1, 130, 257], + "block_number": 5000050 +} + \ No newline at end of file diff --git a/axiom-eth/src/receipt/tests/data/multi_tx_pos_test_new.json b/axiom-eth/src/receipt/tests/data/multi_tx_pos_test_new.json new file mode 100644 index 00000000..be83a028 --- /dev/null +++ b/axiom-eth/src/receipt/tests/data/multi_tx_pos_test_new.json @@ -0,0 +1,5 @@ +{ + "idxs": [0, 1], + "block_number": 17578525 +} + \ No newline at end of file diff --git a/axiom-eth/src/receipt/tests/data/single_rc_pos_test_legacy.json b/axiom-eth/src/receipt/tests/data/single_rc_pos_test_legacy.json new file mode 100644 index 00000000..349cde13 --- /dev/null +++ b/axiom-eth/src/receipt/tests/data/single_rc_pos_test_legacy.json @@ -0,0 +1,3 @@ +{ + "tx_hash": "4174b713caa15ee44fbbb73e2990c5ab794fe79f448a711430f34acf41d4b17f" +} \ No newline at end of file diff --git a/axiom-eth/src/receipt/tests/data/single_rc_pos_test_new.json b/axiom-eth/src/receipt/tests/data/single_rc_pos_test_new.json new file mode 100644 index 00000000..174b3e2d --- /dev/null +++ b/axiom-eth/src/receipt/tests/data/single_rc_pos_test_new.json @@ -0,0 +1,3 @@ +{ + "tx_hash": "da6310baf32ffeb16afb7c2f064a7b64d79beccb585082753c083da15b8c38f2" +} \ No newline at end of file diff --git a/axiom-eth/src/receipt/tests/data/stress_test.json b/axiom-eth/src/receipt/tests/data/stress_test.json new file mode 100644 index 00000000..9a037142 --- /dev/null +++ b/axiom-eth/src/receipt/tests/data/stress_test.json @@ -0,0 +1 @@ +10 \ No newline at end of file diff --git a/axiom-eth/src/receipt/tests/data/zero_tx_pos_test_legacy.json b/axiom-eth/src/receipt/tests/data/zero_tx_pos_test_legacy.json new file mode 100644 index 00000000..11c50660 --- /dev/null +++ b/axiom-eth/src/receipt/tests/data/zero_tx_pos_test_legacy.json @@ -0,0 +1,5 @@ +{ + "idxs": [], + "block_number": 1000 +} + \ No newline at end of file diff --git a/axiom-eth/src/receipt/tests/data/zero_tx_pos_test_new.json b/axiom-eth/src/receipt/tests/data/zero_tx_pos_test_new.json new file mode 100644 index 00000000..11c50660 --- /dev/null +++ b/axiom-eth/src/receipt/tests/data/zero_tx_pos_test_new.json @@ -0,0 +1,5 @@ +{ + "idxs": [], + "block_number": 1000 +} + \ No newline at end of file diff --git a/axiom-eth/src/receipt/tests/field.rs b/axiom-eth/src/receipt/tests/field.rs new file mode 100644 index 00000000..1e11994f --- /dev/null +++ b/axiom-eth/src/receipt/tests/field.rs @@ -0,0 +1,215 @@ +#![cfg(feature = "providers")] +use super::*; +use crate::block_header::get_block_header_rlp_max_lens; +use crate::providers::setup_provider; +use ethers_core::types::H256; + +use halo2_base::halo2_proofs::dev::MockProver; + +use ethers_providers::{JsonRpcClient, Provider}; +use halo2_base::halo2_proofs::halo2curves::bn256::Fr; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use test_log::test; + +//pub mod field; + +#[derive(Clone, Debug)] +pub struct EthBlockReceiptFieldCircuit { + pub inputs: EthBlockReceiptInput, // public and private inputs + pub field_idx: usize, + pub log_idx: usize, + pub params: EthReceiptChipParams, + _marker: PhantomData, +} + +impl EthBlockReceiptFieldCircuit { + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "providers")] + pub fn from_provider( + provider: &Provider

, + tx_hash: H256, + field_idx: usize, + log_idx: usize, + receipt_pf_max_depth: usize, + network: Chain, + max_data_byte_len: usize, + max_log_num: usize, + topic_num_bounds: (usize, usize), + ) -> Self { + use crate::providers::receipt::get_block_receipt_input; + + let inputs = get_block_receipt_input( + provider, + tx_hash, + receipt_pf_max_depth, + max_data_byte_len, + max_log_num, + topic_num_bounds, + ); + let params = EthReceiptChipParams { + max_data_byte_len, + max_log_num, + topic_num_bounds, + network: Some(network), + }; + Self { inputs, field_idx, log_idx, params, _marker: PhantomData } + } +} + +impl EthCircuitInstructions for EthBlockReceiptFieldCircuit { + type FirstPhasePayload = (EthBlockHeaderWitness, EthReceiptWitness, EthReceiptChipParams); + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + ) -> Self::FirstPhasePayload { + let ctx = builder.base.main(FIRST_PHASE); + let chip = EthReceiptChip::new(mpt, self.params); + let max_header_len = get_block_header_rlp_max_lens(chip.network().unwrap()).0; + let field_idx = ctx.load_witness(F::from(self.field_idx as u64)); + let log_idx = ctx.load_witness(F::from(self.log_idx as u64)); + let block_header = assign_vec(ctx, self.inputs.block_header.clone(), max_header_len); + let block_witness = chip.block_header_chip().decompose_block_header_phase0( + ctx, + mpt.keccak(), + &block_header, + ); + let receipts_root = &block_witness.get_receipts_root().field_cells; + let tx_idx = ctx.load_witness(F::from(self.inputs.receipt.idx as u64)); + let proof = self.inputs.receipt.proof.clone().assign(ctx); + let rc_input = EthReceiptInputAssigned { tx_idx, proof }; + // check MPT root of transaction_witness is block_witness.transaction_root + let receipt_witness = { + let witness = chip.parse_receipt_proof_phase0(ctx, rc_input); + // check MPT root is transactions_root + for (pf_byte, byte) in + witness.mpt_witness.root_hash_bytes.iter().zip_eq(receipts_root.iter()) + { + ctx.constrain_equal(pf_byte, byte); + } + witness + }; + + let field_witness = chip.extract_receipt_field(ctx, &receipt_witness, field_idx); + let log_witness = chip.extract_receipt_log(ctx, &receipt_witness, log_idx); + println!("FIELD_LEN: {:?}", field_witness.value_len); + println!("FIELD_BYTES: {:?}", &field_witness.value_bytes[0..16]); + println!("LOG_LEN: {:?}", log_witness.log_len); + println!("LOG_BYTES: {:?}", &log_witness.log_bytes[0..16]); + (block_witness, receipt_witness, self.params) + } + + fn virtual_assign_phase1( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + (block_witness, receipt_witness, chip_params): Self::FirstPhasePayload, + ) { + let chip = EthReceiptChip::new(mpt, chip_params); + let (ctx_gate, ctx_rlc) = builder.rlc_ctx_pair(); + let _receipt_trace = chip.parse_receipt_proof_phase1((ctx_gate, ctx_rlc), receipt_witness); + let _block_trace = chip + .block_header_chip() + .decompose_block_header_phase1((ctx_gate, ctx_rlc), block_witness); + } +} + +#[derive(Default, Clone, Debug, Serialize, Deserialize)] +pub struct ReceiptFieldProviderInput { + pub tx_hash: H256, + pub field_idx: usize, + pub log_idx: usize, +} + +fn get_test_circuit( + network: Chain, + tx_hash: H256, + field_idx: usize, + log_idx: usize, + max_data_byte_len: usize, + max_log_num: usize, + topic_num_bounds: (usize, usize), +) -> EthBlockReceiptFieldCircuit { + let provider = setup_provider(network); + + EthBlockReceiptFieldCircuit::from_provider( + &provider, + tx_hash, + field_idx, + log_idx, + 6, + network, + max_data_byte_len, + max_log_num, + topic_num_bounds, + ) +} + +pub fn test_valid_input_json( + path: String, + max_data_byte_len: usize, + max_log_num: usize, + topic_num_bounds: (usize, usize), +) { + let file_inputs: ReceiptFieldProviderInput = + serde_json::from_reader(File::open(path).expect("path does not exist")).unwrap(); + let tx_hash = file_inputs.tx_hash; + let field_idx = file_inputs.field_idx; + let log_idx = file_inputs.log_idx; + test_valid_input_direct( + tx_hash, + field_idx, + log_idx, + max_data_byte_len, + max_log_num, + topic_num_bounds, + ); +} + +pub fn test_valid_input_direct( + tx_hash: H256, + field_idx: usize, + log_idx: usize, + max_data_byte_len: usize, + max_log_num: usize, + topic_num_bounds: (usize, usize), +) { + let params = get_rlc_params("configs/tests/transaction.json"); + let k = params.base.k as u32; + + let input = get_test_circuit( + Chain::Mainnet, + tx_hash, + field_idx, + log_idx, + max_data_byte_len, + max_log_num, + topic_num_bounds, + ); + let mut circuit = create_circuit(CircuitBuilderStage::Mock, params, input); + circuit.mock_fulfill_keccak_promises(None); + circuit.calculate_params(); + let instances = circuit.instances(); + MockProver::run(k, &circuit, instances).unwrap().assert_satisfied(); +} + +#[test] +pub fn test_mock_single_rc_field_legacy() { + test_valid_input_json( + "src/receipt/tests/data/field/single_rc_pos_test_legacy.json".to_string(), + 256, + 8, + (0, 4), + ); +} + +#[test] +pub fn test_mock_single_rc_field_new() { + test_valid_input_json( + "src/receipt/tests/data/field/single_rc_pos_test_new.json".to_string(), + 256, + 8, + (0, 4), + ); +} diff --git a/axiom-eth/src/receipt/tests/mod.rs b/axiom-eth/src/receipt/tests/mod.rs new file mode 100644 index 00000000..ae92e19d --- /dev/null +++ b/axiom-eth/src/receipt/tests/mod.rs @@ -0,0 +1,185 @@ +#![cfg(feature = "providers")] +use super::*; +use crate::block_header::{get_block_header_rlp_max_lens, EthBlockHeaderWitness}; +use crate::providers::setup_provider; +use crate::rlc::tests::get_rlc_params; +use crate::rlc::FIRST_PHASE; +use crate::utils::assign_vec; +use crate::utils::eth_circuit::{create_circuit, EthCircuitInstructions}; +use ethers_core::types::H256; + +use halo2_base::gates::circuit::CircuitBuilderStage; +use halo2_base::halo2_proofs::dev::MockProver; + +use ethers_providers::{JsonRpcClient, Provider}; +use halo2_base::halo2_proofs::halo2curves::bn256::Fr; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::marker::PhantomData; +use test_log::test; + +pub mod field; + +#[derive(Clone, Debug)] +pub struct EthBlockReceiptCircuit { + pub inputs: EthBlockReceiptInput, // public and private inputs + pub params: EthReceiptChipParams, + _marker: PhantomData, +} + +impl EthBlockReceiptCircuit { + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "providers")] + pub fn from_provider( + provider: &Provider

, + tx_hash: H256, + receipt_pf_max_depth: usize, + network: Chain, + max_data_byte_len: usize, + max_log_num: usize, + topic_num_bounds: (usize, usize), + ) -> Self { + use crate::providers::receipt::get_block_receipt_input; + + let inputs = get_block_receipt_input( + provider, + tx_hash, + receipt_pf_max_depth, + max_data_byte_len, + max_log_num, + topic_num_bounds, + ); + let params = EthReceiptChipParams { + max_data_byte_len, + max_log_num, + topic_num_bounds, + network: Some(network), + }; + Self { inputs, params, _marker: PhantomData } + } +} + +impl EthCircuitInstructions for EthBlockReceiptCircuit { + type FirstPhasePayload = (EthBlockHeaderWitness, EthReceiptWitness, EthReceiptChipParams); + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + ) -> Self::FirstPhasePayload { + let ctx = builder.base.main(FIRST_PHASE); + let chip = EthReceiptChip::new(mpt, self.params); + let max_header_len = get_block_header_rlp_max_lens(chip.network().unwrap()).0; + let block_header = assign_vec(ctx, self.inputs.block_header.clone(), max_header_len); + let block_witness = chip.block_header_chip().decompose_block_header_phase0( + ctx, + mpt.keccak(), + &block_header, + ); + let receipts_root = &block_witness.get_receipts_root().field_cells; + let tx_idx = ctx.load_witness(F::from(self.inputs.receipt.idx as u64)); + let proof = self.inputs.receipt.proof.clone().assign(ctx); + let rc_input = EthReceiptInputAssigned { tx_idx, proof }; + // check MPT root of transaction_witness is block_witness.transaction_root + let receipt_witness = { + let witness = chip.parse_receipt_proof_phase0(ctx, rc_input); + // check MPT root is transactions_root + for (pf_byte, byte) in + witness.mpt_witness.root_hash_bytes.iter().zip_eq(receipts_root.iter()) + { + ctx.constrain_equal(pf_byte, byte); + } + witness + }; + (block_witness, receipt_witness, self.params) + } + + fn virtual_assign_phase1( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + (block_witness, receipt_witness, chip_params): Self::FirstPhasePayload, + ) { + let chip = EthReceiptChip::new(mpt, chip_params); + let (ctx_gate, ctx_rlc) = builder.rlc_ctx_pair(); + let _receipt_trace = chip.parse_receipt_proof_phase1((ctx_gate, ctx_rlc), receipt_witness); + let _block_trace = chip + .block_header_chip() + .decompose_block_header_phase1((ctx_gate, ctx_rlc), block_witness); + } +} + +#[derive(Default, Clone, Debug, Serialize, Deserialize)] +pub struct ReceiptProviderInput { + pub tx_hash: H256, +} + +fn get_test_circuit( + network: Chain, + tx_hash: H256, + max_data_byte_len: usize, + max_log_num: usize, + topic_num_bounds: (usize, usize), +) -> EthBlockReceiptCircuit { + let provider = setup_provider(network); + + EthBlockReceiptCircuit::from_provider( + &provider, + tx_hash, + 6, + network, + max_data_byte_len, + max_log_num, + topic_num_bounds, + ) +} + +pub fn test_valid_input_json( + path: String, + max_data_byte_len: usize, + max_log_num: usize, + topic_num_bounds: (usize, usize), +) { + let file_inputs: ReceiptProviderInput = + serde_json::from_reader(File::open(path).expect("path does not exist")).unwrap(); + let tx_hash = file_inputs.tx_hash; + + test_valid_input_direct(tx_hash, max_data_byte_len, max_log_num, topic_num_bounds); +} + +pub fn test_valid_input_direct( + tx_hash: H256, + max_data_byte_len: usize, + max_log_num: usize, + topic_num_bounds: (usize, usize), +) { + let params = get_rlc_params("configs/tests/transaction.json"); + let k = params.base.k as u32; + + let input = + get_test_circuit(Chain::Mainnet, tx_hash, max_data_byte_len, max_log_num, topic_num_bounds); + let mut circuit = create_circuit(CircuitBuilderStage::Mock, params, input); + circuit.mock_fulfill_keccak_promises(None); + circuit.calculate_params(); + let instances = circuit.instances(); + MockProver::run(k, &circuit, instances).unwrap().assert_satisfied(); +} + +#[test] +pub fn test_mock_single_rc_legacy() { + test_valid_input_json( + "src/receipt/tests/data/single_rc_pos_test_legacy.json".to_string(), + 256, + 8, + (0, 4), + ); +} + +#[test] +pub fn test_mock_single_rc_new() { + test_valid_input_json( + "src/receipt/tests/data/single_rc_pos_test_new.json".to_string(), + 256, + 8, + (0, 4), + ); +} diff --git a/axiom-eth/src/receipt/types.rs b/axiom-eth/src/receipt/types.rs new file mode 100644 index 00000000..365f38cb --- /dev/null +++ b/axiom-eth/src/receipt/types.rs @@ -0,0 +1,103 @@ +use crate::Field; +use crate::{ + mpt::{MPTInput, MPTProof, MPTProofWitness}, + rlp::types::{RlpArrayWitness, RlpFieldTrace, RlpFieldWitness}, +}; +use ethers_core::types::H256; +use getset::Getters; +use halo2_base::{AssignedValue, Context}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug)] +pub struct EthReceiptInputAssigned { + pub tx_idx: AssignedValue, + pub proof: MPTProof, +} + +#[derive(Clone, Debug, Getters)] +pub struct EthReceiptWitness { + pub receipt_type: AssignedValue, + pub tx_idx: AssignedValue, + pub idx_witness: RlpFieldWitness, + #[getset(get = "pub")] + pub(crate) value: RlpArrayWitness, + pub logs: RlpArrayWitness, + #[getset(get = "pub")] + pub(crate) mpt_witness: MPTProofWitness, +} + +#[derive(Clone, Debug)] +pub struct EthReceiptTrace { + pub receipt_type: AssignedValue, + pub value_trace: Vec>, +} + +#[derive(Clone, Debug)] +pub struct EthReceiptFieldWitness { + pub field_idx: AssignedValue, + /// Value of the field, right padded to some max length + pub value_bytes: Vec>, + pub value_len: AssignedValue, +} + +#[derive(Clone, Debug)] +pub struct EthReceiptLogWitness { + pub log_idx: AssignedValue, + /// Log in bytes, right padded to some max length + pub log_bytes: Vec>, + pub log_len: AssignedValue, +} + +#[derive(Clone, Debug)] +pub struct EthReceiptLogFieldWitness { + /// Witness for parsed log list + pub log_list: RlpArrayWitness, + /// Witness for parsed topics list + pub topics_list: RlpArrayWitness, +} + +impl EthReceiptLogFieldWitness { + /// Variable number of topics. + pub fn num_topics(&self) -> AssignedValue { + self.topics_list.list_len.unwrap() + } + /// List of topics, each topic as bytes. The list is padded to fixed length. + pub fn topics_bytes(&self) -> Vec>> { + self.topics_list.field_witness.iter().map(|h| h.field_cells.clone()).collect() + } + pub fn address(&self) -> &[AssignedValue] { + &self.log_list.field_witness[0].field_cells + } + pub fn data_bytes(&self) -> &[AssignedValue] { + &self.log_list.field_witness[2].field_cells + } + pub fn data_len(&self) -> AssignedValue { + self.log_list.field_witness[2].field_len + } +} + +// rust native types + +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] +pub struct EthReceiptInput { + pub idx: usize, + pub proof: MPTInput, +} + +#[derive(Clone, Debug, Hash, Deserialize, Serialize)] +pub struct EthBlockReceiptInput { + pub block_number: u32, + pub block_hash: H256, // provided for convenience, actual block_hash is computed from block_header + pub block_header: Vec, + pub receipt: EthReceiptInput, +} + +impl EthReceiptInput { + pub fn assign(self, ctx: &mut Context) -> EthReceiptInputAssigned { + // let block_hash = encode_h256_to_field(&self.block_hash); + // let block_hash = block_hash.map(|block_hash| ctx.load_witness(block_hash)); + let tx_idx = ctx.load_witness(F::from(self.idx as u64)); + let proof = self.proof.assign(ctx); + EthReceiptInputAssigned { tx_idx, proof } + } +} diff --git a/axiom-eth/src/rlc/chip.rs b/axiom-eth/src/rlc/chip.rs new file mode 100644 index 00000000..6478cac9 --- /dev/null +++ b/axiom-eth/src/rlc/chip.rs @@ -0,0 +1,387 @@ +use halo2_base::{ + gates::GateInstructions, + utils::{bit_length, ScalarField}, + AssignedValue, Context, + QuantumCell::{Constant, Existing, Witness}, +}; +use itertools::Itertools; +use std::{ + iter, + sync::{RwLock, RwLockReadGuard}, +}; + +use super::{ + circuit::builder::RlcContextPair, + types::{RlcFixedTrace, RlcTrace, RlcVar, RlcVarPtr}, +}; + +/// This chip provides functions related to computing Random Linear Combinations (RLCs) using powers of a random +/// challenge value `gamma`. Throughout we assume that `gamma` is supplied through the Halo2 Challenge API. +/// The chip can be borrowed so that the cache can be updated to higher powers. +#[derive(Debug)] +pub struct RlcChip { + /// `gamma_pow_cached[i] = gamma^{2^i}` + gamma_pow_cached: RwLock>>, // Use RwLock so we can read from multiple threads if necessary + gamma: F, +} + +impl RlcChip { + pub fn new(gamma: F) -> Self { + Self { gamma_pow_cached: RwLock::new(vec![]), gamma } + } + + pub fn gamma(&self) -> &F { + &self.gamma + } + + /// `gamma_pow_cached[i] = gamma^{2^i}` + pub fn gamma_pow_cached(&self) -> RwLockReadGuard>> { + self.gamma_pow_cached.read().unwrap() + } + + /// Computes the RLC of `inputs` where the given `inputs` is assumed to be padded to a fixed length `max_len`, + /// but the RLC is computed for a variable length `len`. If `a := inputs, l := len, r := gamma` then + /// ``` + /// RLC(a, l) = \sum_{i = 0}^{l - 1} a_i r^{l - 1 - i} + /// ``` + /// We assume all cells of `inputs` are in a previous phase, and `ctx_gate` and `ctx_rlc` are both + /// [`Context`]s in a later phase. Here `ctx_gate` is used for [halo2_base] gate assignments, while `ctx_rlc` + /// is used for assignments in special RLC gate assignments. + /// + /// Assumes `0 <= len <= max_len`. + pub fn compute_rlc( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + gate: &impl GateInstructions, + inputs: impl IntoIterator>, + len: AssignedValue, + ) -> RlcTrace { + self.compute_rlc_with_min_len((ctx_gate, ctx_rlc), gate, inputs, len, 0) + } + + /// Same as `compute_rlc` but assumes `min_len <= len <= max_len` as an optimization. + /// The case `len = 0` is handled with some special treatment + pub fn compute_rlc_with_min_len( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + gate: &impl GateInstructions, + inputs: impl IntoIterator>, + len: AssignedValue, + min_len: usize, + ) -> RlcTrace { + let mut inputs = inputs.into_iter(); + let is_zero = gate.is_zero(ctx_gate, len); + let shift_amt = if min_len != 0 { min_len } else { 1 }; + let shifted_len = gate.sub(ctx_gate, len, Constant(F::from(shift_amt as u64))); + let idx = gate.select(ctx_gate, Constant(F::ZERO), shifted_len, is_zero); + + let mut max_len: usize = 0; + let row_offset = ctx_rlc.advice.len() as isize; + if let Some(first) = inputs.next() { + max_len = 1; + let mut running_rlc = *first.value(); + let rlc_vals = iter::once(Existing(first)).chain(inputs.flat_map(|input| { + max_len += 1; + running_rlc = running_rlc * self.gamma() + input.value(); + [Existing(input), Witness(running_rlc)] + })); + if ctx_rlc.witness_gen_only() { + ctx_rlc.assign_region(rlc_vals, []); + } else { + let rlc_vals = rlc_vals.collect_vec(); + ctx_rlc.assign_region(rlc_vals, (0..2 * max_len as isize - 2).step_by(2)); + } + } + // ctx_rlc.advice looks like | rlc0=val0 | val1 | rlc1 | val2 | rlc2 | ... | rlc_{max_len - 1} | + // therefore we want to index into `2 * (len - 1)` unless len is 0, in which case we just return 0 + assert!(min_len <= max_len); + let rlc_val = if max_len == 0 { + ctx_gate.load_zero() + } else if shift_amt == max_len { + // same as ctx_rlc.get(-1): + ctx_rlc.get(row_offset + 2 * (max_len - 1) as isize) + } else { + gate.select_from_idx( + ctx_gate, + (shift_amt - 1..max_len).map(|i| ctx_rlc.get(row_offset + 2 * i as isize)), + idx, + ) + }; + // rlc_val = rlc_val * (1 - is_zero) + let rlc_val = gate.mul_not(ctx_gate, is_zero, rlc_val); + + RlcTrace { rlc_val, len, max_len } + } + + /// Same as [`compute_rlc`] but now the input is of known fixed length. + pub fn compute_rlc_fixed_len( + &self, + ctx_rlc: &mut Context, + inputs: impl IntoIterator>, + ) -> RlcFixedTrace { + let mut inputs = inputs.into_iter(); + if let Some(first) = inputs.next() { + let mut running_rlc = *first.value(); + let mut len: usize = 1; + let rlc_vals = iter::once(Existing(first)).chain(inputs.flat_map(|input| { + len += 1; + running_rlc = running_rlc * self.gamma() + input.value(); + [Existing(input), Witness(running_rlc)] + })); + let rlc_val = if ctx_rlc.witness_gen_only() { + ctx_rlc.assign_region_last(rlc_vals, []) + } else { + let rlc_vals = rlc_vals.collect_vec(); + ctx_rlc.assign_region_last(rlc_vals, (0..2 * (len as isize) - 2).step_by(2)) + }; + RlcFixedTrace { rlc_val, len } + } else { + RlcFixedTrace { rlc_val: ctx_rlc.load_zero(), len: 0 } + } + } + + /// Define the dynamic RLC: RLC(a, l) = \sum_{i = 0}^{l - 1} a_i r^{l - 1 - i} + /// * We have that: + /// RLC(a || b, l_a + l_b) = RLC(a, l_a) * r^{l_a} + RLC(b, l_b). + /// * Prop: For sequences b^0, \ldots, b^{k-1} with l(b^i) = l_i and + /// RLC(a, l) = RLC(b^0, l_0) * r^{l_1 + ... + l_{k - 1}} + /// + RLC(b^1, l_1) * r^{l_2 + ... + l_{k - 1}} + /// ... + RLC(b^{k - 1}, l_{k - 1}), and + /// l = l_0 + ... + l_{k - 1}, + /// then a = b^0 || ... || b^{k - 1}. + /// * Pf: View both sides as polynomials in r. + /// + /// If `var_num_frags` is Some, then it concatenates the first `var_num_frags` inputs into the RLC. Otherwise it concatenates all inputs. + /// + /// # Assumptions + /// * each tuple of the input is (RLC(a, l), l) for some sequence a_i of length l + /// * all rlc_len values have been range checked + /// * `ctx_gate` should be in later phase than `inputs` + /// * `0 < var_num_frags <= inputs.len()` + /// + // if num_frags.value() = 0, then (rlc = 0, len = 0) because of how `select_from_idx` works (`num_frags_minus_1` will be very large) + pub fn rlc_concat( + &self, + ctx_gate: &mut Context, + gate: &impl GateInstructions, + inputs: impl IntoIterator>, + var_num_frags: Option>, + ) -> RlcTrace { + let mut inputs = inputs.into_iter(); + let (size, hi) = inputs.size_hint(); + // size only used for capacity estimation + debug_assert_eq!(Some(size), hi); + + let mut partial_rlc = Vec::with_capacity(size); + let mut partial_len = Vec::with_capacity(size); + + let initial = inputs.next().unwrap(); + let mut running_rlc = initial.rlc_val; + let mut running_len = initial.len; + let mut running_max_len = initial.max_len; + partial_rlc.push(running_rlc); + partial_len.push(running_len); + for input in inputs { + let RlcTrace { rlc_val, len, max_len } = input; + running_len = gate.add(ctx_gate, running_len, len); + let gamma_pow = self.rlc_pow(ctx_gate, gate, len, bit_length(max_len as u64)); + running_rlc = gate.mul_add(ctx_gate, running_rlc, gamma_pow, rlc_val); + partial_len.push(running_len); + partial_rlc.push(running_rlc); + running_max_len += max_len; + } + if let Some(num_frags) = var_num_frags { + let num_frags_minus_1 = gate.sub(ctx_gate, num_frags, Constant(F::ONE)); + let indicator = gate.idx_to_indicator(ctx_gate, num_frags_minus_1, partial_len.len()); + let total_len = gate.select_by_indicator(ctx_gate, partial_len, indicator.clone()); + let rlc_select = gate.select_by_indicator(ctx_gate, partial_rlc, indicator); + RlcTrace { rlc_val: rlc_select, len: total_len, max_len: running_max_len } + } else { + RlcTrace { + rlc_val: partial_rlc.pop().unwrap(), + len: partial_len.pop().unwrap(), + max_len: running_max_len, + } + } + } + + /// We compute `rlc_concat` of `inputs` (the first `var_num_frags` if Some), and then constrain the result equals `concatenation`. + /// + /// `ctx_gate` and `ctx_rlc` should be in later phase than `inputs` + pub fn constrain_rlc_concat<'a>( + &self, + ctx_gate: &mut Context, + gate: &impl GateInstructions, + inputs: impl IntoIterator>, + concatenation: impl Into>, + var_num_frags: Option>, + ) { + let claimed_concat = self.rlc_concat(ctx_gate, gate, inputs, var_num_frags); + rlc_constrain_equal(ctx_gate, &claimed_concat, concatenation.into()); + } + + fn load_gamma(&self, ctx_rlc: &mut Context, gamma: F) -> AssignedValue { + ctx_rlc.assign_region_last([Constant(F::ONE), Constant(F::ZERO), Witness(gamma)], [0]) + } + + /// Updates `gamma_pow_cached` to contain assigned values for `gamma^{2^i}` for `i = 0,...,cache_bits - 1` where `gamma` is the challenge value + /// + /// WARNING: this must be called in a deterministic way. It is NOT thread-safe, even though the compiler thinks it is. + pub fn load_rlc_cache( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + gate: &impl GateInstructions, + cache_bits: usize, + ) { + if cache_bits <= self.gamma_pow_cached().len() { + return; + } + log::debug!( + "Loading RLC cache ({} bits) with existing {} bits", + cache_bits, + self.gamma_pow_cached().len() + ); + let mut gamma_pow_cached = self.gamma_pow_cached.write().unwrap(); + if gamma_pow_cached.is_empty() { + let gamma_assigned = self.load_gamma(ctx_rlc, *self.gamma()); + gamma_pow_cached.push(gamma_assigned); + }; + + for _ in gamma_pow_cached.len()..cache_bits { + let last = *gamma_pow_cached.last().unwrap(); + let sq = gate.mul(ctx_gate, last, last); + gamma_pow_cached.push(sq); + } + } + + /// Computes `gamma^pow` where `gamma` is the challenge value. + pub fn rlc_pow( + &self, + ctx_gate: &mut Context, // ctx_gate in SecondPhase + gate: &impl GateInstructions, + pow: AssignedValue, + mut pow_bits: usize, + ) -> AssignedValue { + if pow_bits == 0 { + pow_bits = 1; + } + assert!(pow_bits <= self.gamma_pow_cached().len()); + + let bits = gate.num_to_bits(ctx_gate, pow, pow_bits); + let mut out = None; + + for (bit, &gamma_pow) in bits.into_iter().zip(self.gamma_pow_cached().iter()) { + let multiplier = gate.select(ctx_gate, gamma_pow, Constant(F::ONE), bit); + out = Some(if let Some(prev) = out { + gate.mul(ctx_gate, multiplier, prev) + } else { + multiplier + }); + } + out.unwrap() + } + + /// Computes `gamma^pow` where `gamma` is the challenge value. + pub fn rlc_pow_fixed( + &self, + ctx_gate: &mut Context, // ctx_gate in SecondPhase + gate: &impl GateInstructions, + pow: usize, + ) -> AssignedValue { + if pow == 0 { + return ctx_gate.load_constant(F::ONE); + } + let gamma_pow2 = self.gamma_pow_cached(); + let bits = bit_length(pow as u64); + assert!(bits <= gamma_pow2.len()); + let mut out = None; + for i in 0..bits { + if pow >> i & 1 == 1 { + let multiplier = gamma_pow2[i]; + out = + Some(out.map(|prev| gate.mul(ctx_gate, multiplier, prev)).unwrap_or(multiplier)) + } + } + out.unwrap() + } +} + +/// Define the dynamic RLC: `RLC(a, l) = \sum_{i = 0}^{l - 1} a_i r^{l - 1 - i}` +/// where `a` is a variable length vector of length `l`. +/// +/// We have `a == b` iff `RLC(a, l_a) == RLC(b, l_b)` AND `l_a == l_b`. +/// The length equality constraint is necessary because `a` and `b` can have leading zeros. +pub fn rlc_is_equal( + ctx_gate: &mut Context, + gate: &impl GateInstructions, + a: impl Into>, + b: impl Into>, +) -> AssignedValue { + let a = a.into(); + let b = b.into(); + let len_is_equal = gate.is_equal(ctx_gate, a.len, b.len); + let rlc_is_equal = gate.is_equal(ctx_gate, a.rlc_val, b.rlc_val); + gate.and(ctx_gate, len_is_equal, rlc_is_equal) +} + +pub fn rlc_constrain_equal<'a, F: ScalarField>( + ctx: &mut Context, + a: impl Into>, + b: impl Into>, +) { + let a = a.into(); + let b = b.into(); + ctx.constrain_equal(a.len, b.len); + ctx.constrain_equal(a.rlc_val, b.rlc_val); +} + +pub fn rlc_select( + ctx_gate: &mut Context, + gate: &impl GateInstructions, + a: impl Into>, + b: impl Into>, + condition: AssignedValue, +) -> RlcVar { + let a = a.into(); + let b = b.into(); + let len = gate.select(ctx_gate, a.len, b.len, condition); + let rlc_val = gate.select(ctx_gate, a.rlc_val, b.rlc_val, condition); + RlcVar { rlc_val, len } +} + +pub fn rlc_select_from_idx( + ctx_gate: &mut Context, + gate: &impl GateInstructions, + a: impl IntoIterator, + idx: AssignedValue, +) -> RlcVar +where + R: Into>, +{ + let a = a.into_iter(); + let (len, hi) = a.size_hint(); + assert_eq!(Some(len), hi); + let indicator = gate.idx_to_indicator(ctx_gate, idx, len); + rlc_select_by_indicator(ctx_gate, gate, a, indicator) +} + +pub fn rlc_select_by_indicator( + ctx_gate: &mut Context, + gate: &impl GateInstructions, + a: impl IntoIterator, + indicator: Vec>, +) -> RlcVar +where + R: Into>, +{ + let (a_len, a_rlc): (Vec<_>, Vec<_>) = a + .into_iter() + .map(|a| { + let a = a.into(); + (a.len, a.rlc_val) + }) + .unzip(); + let len = gate.select_by_indicator(ctx_gate, a_len, indicator.clone()); + let rlc_val = gate.select_by_indicator(ctx_gate, a_rlc, indicator); + RlcVar { rlc_val, len } +} diff --git a/axiom-eth/src/rlc/circuit/builder.rs b/axiom-eth/src/rlc/circuit/builder.rs new file mode 100644 index 00000000..885ee344 --- /dev/null +++ b/axiom-eth/src/rlc/circuit/builder.rs @@ -0,0 +1,278 @@ +use halo2_base::{ + gates::{ + circuit::{ + builder::{BaseCircuitBuilder, RangeStatistics}, + CircuitBuilderStage, + }, + GateInstructions, RangeChip, + }, + halo2_proofs::plonk::Circuit, + utils::ScalarField, + virtual_region::copy_constraints::SharedCopyConstraintManager, + AssignedValue, Context, +}; +use itertools::Itertools; +use rayon::prelude::*; + +use crate::rlc::{ + chip::RlcChip, + virtual_region::{manager::RlcManager, RlcThreadBreakPoints}, + RLC_PHASE, +}; + +use super::RlcCircuitParams; + +/// Circuit builder that extends [BaseCircuitBuilder] with support for RLC gate +#[derive(Clone, Debug, Default)] +pub struct RlcCircuitBuilder { + pub base: BaseCircuitBuilder, + pub rlc_manager: RlcManager, + /// Number of advice columns for RLC gate + pub num_rlc_columns: usize, + /// The challenge value, will be set after FirstPhase + pub gamma: Option, + /// To avoid concurrency issues with `RlcChip::load_rlc_cache`, we will call it once + /// when `RlcChip` is first created. This means we compute `gamma^{2^i}` for `i=0..max_cache_bits`. + /// Concretely this means this circuit builder will only support concatenating strings where + /// the max length of a fragment is < 2^max_cache_bits. + max_cache_bits: usize, +} + +impl RlcCircuitBuilder { + pub fn new(witness_gen_only: bool, max_cache_bits: usize) -> Self { + let base = BaseCircuitBuilder::new(witness_gen_only); + let rlc_manager = RlcManager::new(witness_gen_only, base.core().copy_manager.clone()); + Self { base, rlc_manager, max_cache_bits, ..Default::default() } + } + + pub fn unknown(mut self, use_unknown: bool) -> Self { + self.base = self.base.unknown(use_unknown); + self.rlc_manager = self.rlc_manager.unknown(use_unknown); + self + } + + pub fn from_stage(stage: CircuitBuilderStage, max_cache_bits: usize) -> Self { + Self::new(stage.witness_gen_only(), max_cache_bits) + .unknown(stage == CircuitBuilderStage::Keygen) + } + + pub fn prover( + config_params: RlcCircuitParams, + break_points: RlcThreadBreakPoints, + max_cache_bits: usize, + ) -> Self { + Self::new(true, max_cache_bits).use_params(config_params).use_break_points(break_points) + } + + /// Returns global shared copy manager + pub fn copy_manager(&self) -> &SharedCopyConstraintManager { + &self.base.core().copy_manager + } + + /// Sets the copy manager to the given one in all shared references. + pub fn set_copy_manager(&mut self, copy_manager: SharedCopyConstraintManager) { + self.base.set_copy_manager(copy_manager.clone()); + self.rlc_manager.set_copy_manager(copy_manager); + } + + /// Returns `self` with a given copy manager + pub fn use_copy_manager(mut self, copy_manager: SharedCopyConstraintManager) -> Self { + self.set_copy_manager(copy_manager); + self + } + + pub fn set_max_cache_bits(&mut self, max_cache_bits: usize) { + self.max_cache_bits = max_cache_bits; + } + + pub fn use_max_cache_bits(mut self, max_cache_bits: usize) -> Self { + self.set_max_cache_bits(max_cache_bits); + self + } + + /// Deep clone of `self`, where the underlying object of shared references in [SharedCopyConstraintManager] and [LookupAnyManager] are cloned. + pub fn deep_clone(&self) -> Self { + let base = self.base.deep_clone(); + let rlc_manager = + self.rlc_manager.clone().use_copy_manager(base.core().copy_manager.clone()); + Self { + base, + rlc_manager, + num_rlc_columns: self.num_rlc_columns, + gamma: self.gamma, + max_cache_bits: self.max_cache_bits, + } + } + + pub fn clear(&mut self) { + self.base.clear(); + self.rlc_manager.clear(); + } + + /// Returns whether or not the circuit is only used for witness generation. + pub fn witness_gen_only(&self) -> bool { + assert_eq!(self.base.witness_gen_only(), self.rlc_manager.witness_gen_only()); + self.base.witness_gen_only() + } + + /// Circuit configuration parameters + pub fn params(&self) -> RlcCircuitParams { + RlcCircuitParams { base: self.base.params(), num_rlc_columns: self.num_rlc_columns } + } + + /// Set config params + pub fn set_params(&mut self, params: RlcCircuitParams) { + self.base.set_params(params.base); + self.num_rlc_columns = params.num_rlc_columns; + } + + /// Returns new with config params + pub fn use_params(mut self, params: RlcCircuitParams) -> Self { + self.set_params(params); + self + } + + /// The break points of the circuit. + pub fn break_points(&self) -> RlcThreadBreakPoints { + let base = self.base.break_points(); + let rlc = + self.rlc_manager.break_points.borrow().as_ref().expect("break points not set").clone(); + RlcThreadBreakPoints { base, rlc } + } + + /// Sets the break points of the circuit. + pub fn set_break_points(&mut self, break_points: RlcThreadBreakPoints) { + self.base.set_break_points(break_points.base); + *self.rlc_manager.break_points.borrow_mut() = Some(break_points.rlc); + } + + /// Returns new with break points + pub fn use_break_points(mut self, break_points: RlcThreadBreakPoints) -> Self { + self.set_break_points(break_points); + self + } + + /// Set lookup bits + pub fn set_lookup_bits(&mut self, lookup_bits: usize) { + self.base.config_params.lookup_bits = Some(lookup_bits); + } + + /// Returns new with lookup bits + pub fn use_lookup_bits(mut self, lookup_bits: usize) -> Self { + self.set_lookup_bits(lookup_bits); + self + } + + /// Set `k` = log2 of domain + pub fn set_k(&mut self, k: usize) { + self.base.config_params.k = k; + } + + /// Returns new with `k` set + pub fn use_k(mut self, k: usize) -> Self { + self.set_k(k); + self + } + + pub fn rlc_ctx_pair(&mut self) -> RlcContextPair { + (self.base.main(RLC_PHASE), self.rlc_manager.main()) + } + + /// Returns some statistics about the virtual region. + pub fn statistics(&self) -> RlcStatistics { + let base = self.base.statistics(); + let total_rlc_advice = self.rlc_manager.total_advice(); + RlcStatistics { base, total_rlc_advice } + } + + /// Virtual cells that will be copied to public instance columns + pub fn public_instances(&mut self) -> &mut [Vec>] { + &mut self.base.assigned_instances + } + + /// Auto-calculates configuration parameters for the circuit and sets them. + /// + /// * `minimum_rows`: The minimum number of rows in the circuit that cannot be used for witness assignments and contain random `blinding factors` to ensure zk property, defaults to 0. + pub fn calculate_params(&mut self, minimum_rows: Option) -> RlcCircuitParams { + let base = self.base.calculate_params(minimum_rows); + let total_rlc_advice = self.rlc_manager.total_advice(); + let max_rows = (1 << base.k) - minimum_rows.unwrap_or(0); + let num_rlc_columns = (total_rlc_advice + max_rows - 1) / max_rows; + self.num_rlc_columns = num_rlc_columns; + + let params = RlcCircuitParams { base, num_rlc_columns }; + #[cfg(feature = "display")] + { + println!("Total RLC advice cells: {total_rlc_advice}"); + log::info!("Auto-calculated config params:\n {params:#?}"); + } + params + } + + /// Creates a new [RangeChip] sharing the same [LookupAnyManager]s as `self`. + pub fn range_chip(&self) -> RangeChip { + self.base.range_chip() + } + + /// Returns [RlcChip] if challenge value `gamma` is available. Panics otherwise. + /// This should only be called in SecondPhase. + pub fn rlc_chip(&mut self, gate: &impl GateInstructions) -> RlcChip { + #[cfg(feature = "halo2-axiom")] + { + // safety check + assert!( + !self.witness_gen_only() || self.gamma.is_some(), + "Challenge value not available before SecondPhase" + ); + } + let gamma = self.gamma.unwrap_or(F::ZERO); + let rlc_chip = RlcChip::new(gamma); + // Precompute gamma^{2^i} to avoid concurrency issues + let cache_bits = self.max_cache_bits; + let (ctx_gate, ctx_rlc) = self.rlc_ctx_pair(); + rlc_chip.load_rlc_cache((ctx_gate, ctx_rlc), gate, cache_bits); + rlc_chip + } + + /// 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(&mut self, input: Vec, f: FR) -> Vec + where + F: ScalarField, + T: Send, + R: Send, + FR: Fn(RlcContextPair, T) -> R + Send + Sync, + { + // to prevent concurrency issues with context id, we generate all the ids first + let core_thread_count = self.base.pool(RLC_PHASE).thread_count(); + let rlc_thread_count = self.rlc_manager.thread_count(); + let mut ctxs_gate = (0..input.len()) + .map(|i| self.base.pool(RLC_PHASE).new_context(core_thread_count + i)) + .collect_vec(); + let mut ctxs_rlc = (0..input.len()) + .map(|i| self.rlc_manager.new_context(rlc_thread_count + i)) + .collect_vec(); + let outputs: Vec<_> = input + .into_par_iter() + .zip((ctxs_gate.par_iter_mut()).zip(ctxs_rlc.par_iter_mut())) + .map(|(input, (ctx_gate, ctx_rlc))| f((ctx_gate, ctx_rlc), input)) + .collect(); + // we collect the new threads to ensure they are a FIXED order, otherwise the circuit will not be deterministic + self.base.pool(RLC_PHASE).threads.append(&mut ctxs_gate); + self.rlc_manager.threads.append(&mut ctxs_rlc); + outputs + } +} + +/// Wrapper so we don't need to pass around two contexts separately. The pair consists of `(ctx_gate, ctx_rlc)` where +/// * `ctx_gate` should be an `RLC_PHASE` context for use with `GateChip`. +/// * `ctx_rlc` should be a context for use with `RlcChip`. +pub type RlcContextPair<'a, F> = (&'a mut Context, &'a mut Context); + +pub struct RlcStatistics { + pub base: RangeStatistics, + pub total_rlc_advice: usize, +} diff --git a/axiom-eth/src/rlc/circuit/instructions.rs b/axiom-eth/src/rlc/circuit/instructions.rs new file mode 100644 index 00000000..e1d98ec3 --- /dev/null +++ b/axiom-eth/src/rlc/circuit/instructions.rs @@ -0,0 +1,103 @@ +use halo2_base::{ + gates::{circuit::MaybeRangeConfig, RangeChip}, + halo2_proofs::circuit::Layouter, + utils::ScalarField, + virtual_region::manager::VirtualRegionManager, +}; + +use crate::rlc::{ + chip::RlcChip, + circuit::{builder::RlcCircuitBuilder, RlcConfig}, +}; + +impl RlcCircuitBuilder { + /// Assigns the raw Halo2 cells for [RlcConfig] in FirstPhase. This should be the only time + /// FirstPhase cells are assigned to [RlcConfig]. + /// + /// This also imposes the equality constraints on + /// public instance columns (requires the copy manager to be assigned). + /// + /// (We are only imposing the copy constraints on the instance column, not assigning values to the instance column -- + /// The instance values are provided in `create_proof` as an argument, and that is what is used for Fiat-Shamir. + /// Therefore the equality constraints on instance columsn can also be done in SecondPhase instead. + /// We keep it in FirstPhase for logical clarity.) + pub fn raw_synthesize_phase0(&self, config: &RlcConfig, mut layouter: impl Layouter) { + let usable_rows = config.rlc.usable_rows; + layouter + .assign_region( + || "base phase 0", + |mut region| { + self.base.core().phase_manager[0] + .assign_raw(&(config.basic_gates(0), usable_rows), &mut region); + if let MaybeRangeConfig::WithRange(config) = &config.base.base { + self.base.assign_lookups_in_phase(config, &mut region, 0); + } + Ok(()) + }, + ) + .unwrap(); + self.base.assign_instances(&config.base.instance, layouter.namespace(|| "expose public")); + } + + /// Loads challenge value `gamma`, if after FirstPhase + pub fn load_challenge(&mut self, config: &RlcConfig, layouter: impl Layouter) { + let gamma = layouter.get_challenge(config.rlc.gamma); + gamma.map(|g| self.gamma = Some(g)); + log::info!("Challenge value: {gamma:?}"); + } + + /// Assigns the raw Halo2 cells for [RlcConfig] (which is [BaseConfig] and [PureRlcConfig]) + /// in SecondPhase. This should be the only time SecondPhase cells are assigned to [RlcConfig], + /// i.e., there is not shared ownership of some columns. + /// + /// If there is nothing after this that uses [CopyConstraintManager], then `enforce_copy_constraints` should + /// be set to true (this is usually the default). + pub fn raw_synthesize_phase1( + &self, + config: &RlcConfig, + mut layouter: impl Layouter, + enforce_copy_constraints: bool, + ) { + let usable_rows = config.rlc.usable_rows; + layouter + .assign_region( + || "base+rlc phase 1", + |mut region| { + let core = self.base.core(); + core.phase_manager[1] + .assign_raw(&(config.basic_gates(1), usable_rows), &mut region); + if let MaybeRangeConfig::WithRange(config) = &config.base.base { + self.base.assign_lookups_in_phase(config, &mut region, 1); + } + self.rlc_manager.assign_raw(&config.rlc, &mut region); + // Impose equality constraints + if enforce_copy_constraints && !core.witness_gen_only() { + core.copy_manager.assign_raw(config.base.constants(), &mut region); + } + Ok(()) + }, + ) + .unwrap(); + } +} + +/// Simple trait describing the FirstPhase and SecondPhase witness generation of a circuit +/// that only uses [RlcConfig]. This is mostly provided for convenience to use with +/// [RlcExecutor]; for more customization +/// you will have to implement [TwoPhaseInstructions] directly. +pub trait RlcCircuitInstructions { + type FirstPhasePayload; + + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + ) -> Self::FirstPhasePayload; + + fn virtual_assign_phase1( + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + rlc: &RlcChip, + payload: Self::FirstPhasePayload, + ); +} diff --git a/axiom-eth/src/rlc/circuit/mod.rs b/axiom-eth/src/rlc/circuit/mod.rs new file mode 100644 index 00000000..ef2a6423 --- /dev/null +++ b/axiom-eth/src/rlc/circuit/mod.rs @@ -0,0 +1,105 @@ +use std::marker::PhantomData; + +use halo2_base::{ + gates::circuit::BaseCircuitParams, + gates::{circuit::BaseConfig, flex_gate::BasicGateConfig}, + halo2_proofs::{ + plonk::{Challenge, ConstraintSystem, FirstPhase, SecondPhase}, + poly::Rotation, + }, + utils::ScalarField, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +/// The circuit builder that coordinates all virtual region managers to support [BaseConfig] and [RlcConfig] +pub mod builder; +/// Module to help auto-implement [TwoPhaseCircuitInstructions] using [RlcCircuitBuilder] +pub mod instructions; + +/// This config consists of a variable number of advice columns, all in [SecondPhase]. +/// Each advice column has a selector column that enables a custom gate to aid RLC computation. +/// +/// The intention is that this chip is only used for the actual RLC computation. All other operations should use `GateInstructions` by advancing the phase to [SecondPhase]. +/// +/// Note: this uses a similar vertical gate structure as [FlexGateConfig] **however** the RLC gate uses only 3 contiguous rows instead of 4. +/// +/// We re-use the [BasicGateConfig] struct for the RLC gate, but do not call `BasicGateConfig::configure` because the custom gate we use here is different. +#[derive(Clone, Debug)] +pub struct PureRlcConfig { + pub basic_gates: Vec>, + pub gamma: Challenge, + /// Total number of usable (non-poisoned) rows in the circuit. + pub usable_rows: usize, + _marker: PhantomData, +} + +impl PureRlcConfig { + pub fn configure(meta: &mut ConstraintSystem, k: usize, num_advice_col: usize) -> Self { + let basic_gates = (0..num_advice_col) + .map(|_| { + let a = meta.advice_column_in(SecondPhase); + meta.enable_equality(a); + let q = meta.selector(); + BasicGateConfig::new(q, a) + }) + .collect_vec(); + + let gamma = meta.challenge_usable_after(FirstPhase); + + for gate in &basic_gates { + meta.create_gate("RLC computation", |meta| { + let q = meta.query_selector(gate.q_enable); + let rlc_prev = meta.query_advice(gate.value, Rotation::cur()); + let val = meta.query_advice(gate.value, Rotation::next()); + let rlc_curr = meta.query_advice(gate.value, Rotation(2)); + // TODO: see if reducing number of distinct rotation sets speeds up SHPLONK: + // Phantom query so rotation set is also size 4 to match `FlexGateConfig` + // meta.query_advice(rlc, Rotation(3)); + + let gamma = meta.query_challenge(gamma); + + vec![q * (rlc_prev * gamma + val - rlc_curr)] + }); + } + log::info!("Poisoned rows after RlcConfig::configure {}", meta.minimum_rows()); + // Warning: this needs to be updated if you create more advice columns after this `RlcConfig` is created + let usable_rows = (1usize << k) - meta.minimum_rows(); + + Self { basic_gates, gamma, usable_rows, _marker: PhantomData } + } +} + +/// Configuration parameters for [RlcConfig] +#[derive(Clone, Default, Hash, Debug, Serialize, Deserialize)] +pub struct RlcCircuitParams { + pub base: BaseCircuitParams, + pub num_rlc_columns: usize, +} + +/// Combination of [BaseConfig] and [PureRlcConfig]. +// We name this `RlcConfig` because we almost never use `PureRlcConfig` by itself. +#[derive(Clone, Debug)] +pub struct RlcConfig { + pub base: BaseConfig, + pub rlc: PureRlcConfig, +} + +impl RlcConfig { + pub fn configure(meta: &mut ConstraintSystem, params: RlcCircuitParams) -> Self { + let k = params.base.k; + let mut base = BaseConfig::configure(meta, params.base); + let rlc = PureRlcConfig::configure(meta, k, params.num_rlc_columns); + base.set_usable_rows(rlc.usable_rows); + RlcConfig { base, rlc } + } + + pub fn basic_gates(&self, phase: usize) -> Vec> { + self.base.gate().basic_gates[phase].clone() + } + + pub fn set_usable_rows(&mut self, usable_rows: usize) { + self.base.set_usable_rows(usable_rows); + self.rlc.usable_rows = usable_rows; + } +} diff --git a/axiom-eth/src/rlc/concat_array.rs b/axiom-eth/src/rlc/concat_array.rs new file mode 100644 index 00000000..71702d8e --- /dev/null +++ b/axiom-eth/src/rlc/concat_array.rs @@ -0,0 +1,61 @@ +use std::iter; + +use halo2_base::{ + gates::GateInstructions, utils::ScalarField, AssignedValue, Context, QuantumCell::Constant, +}; + +use super::{ + chip::RlcChip, + circuit::builder::RlcContextPair, + types::{AssignedVarLenVec, ConcatVarFixedArrayTrace, ConcatVarFixedArrayWitness}, +}; + +/// Both `prefix` and `suffix` are fixed length arrays, with length known at compile time. +/// However `prefix` is used to represent a variable length array, with variable length given +/// by `prefix_len`. +/// +/// This is the FirstPhase of computing `[&prefix[..prefix_len], &suffix[..]].concat()`. +/// This function **does not constrain anything**. It only computes the witness. +/// You **must** call [concat_var_fixed_array_phase1] to use RLC to constrain the concatenation. +pub fn concat_var_fixed_array_phase0( + ctx: &mut Context, + gate: &impl GateInstructions, + prefix: AssignedVarLenVec, + suffix: Vec>, +) -> ConcatVarFixedArrayWitness { + let concat_len = gate.add(ctx, prefix.len, Constant(F::from(suffix.len() as u64))); + let max_concat_len = prefix.max_len() + suffix.len(); + let prefix_len = prefix.len.value().get_lower_64() as usize; + assert!(prefix_len <= prefix.max_len()); + // Unsafe: unconstrained; to be constrained in phase1 + let concat_padded = ctx.assign_witnesses( + prefix.values[..prefix_len] + .iter() + .chain(suffix.iter()) + .map(|a| *a.value()) + .chain(iter::repeat(F::ZERO)) + .take(max_concat_len), + ); + let concat = AssignedVarLenVec { values: concat_padded, len: concat_len }; + + ConcatVarFixedArrayWitness { prefix, suffix, concat } +} + +pub fn concat_var_fixed_array_phase1( + (ctx_gate, ctx_rlc): RlcContextPair, + gate: &impl GateInstructions, + rlc: &RlcChip, + concat_witness: ConcatVarFixedArrayWitness, +) -> ConcatVarFixedArrayTrace { + let ConcatVarFixedArrayWitness { prefix, suffix, concat } = concat_witness; + let multiplier = rlc.rlc_pow_fixed(ctx_gate, gate, suffix.len()); + + let prefix_rlc = rlc.compute_rlc((ctx_gate, ctx_rlc), gate, prefix.values, prefix.len); + let suffix_rlc = rlc.compute_rlc_fixed_len(ctx_rlc, suffix); + let concat_rlc = rlc.compute_rlc((ctx_gate, ctx_rlc), gate, concat.values, concat.len); + + let claimed_concat = gate.mul_add(ctx_gate, prefix_rlc.rlc_val, multiplier, suffix_rlc.rlc_val); + ctx_gate.constrain_equal(&claimed_concat, &concat_rlc.rlc_val); + + ConcatVarFixedArrayTrace { prefix_rlc, suffix_rlc, concat_rlc } +} diff --git a/axiom-eth/src/rlc/mod.rs b/axiom-eth/src/rlc/mod.rs new file mode 100644 index 00000000..1cfd3951 --- /dev/null +++ b/axiom-eth/src/rlc/mod.rs @@ -0,0 +1,19 @@ +//! Custom gate, chip, and circuit builder for use with RLC computations + +/// Chip with functions using RLC +pub mod chip; +/// Circuit builder for RLC +pub mod circuit; +/// Utility functions for concatenating variable length arrays +pub mod concat_array; +#[cfg(test)] +pub mod tests; +/// Types +pub mod types; +/// Module for managing the virtual region corresponding to RLC columns +pub mod virtual_region; + +/// FirstPhase of challenge API +pub const FIRST_PHASE: usize = 0; +/// RLC is hard-coded to take place in SecondPhase +pub const RLC_PHASE: usize = 1; diff --git a/axiom-eth/src/rlc/tests/mod.rs b/axiom-eth/src/rlc/tests/mod.rs new file mode 100644 index 00000000..0739f7f4 --- /dev/null +++ b/axiom-eth/src/rlc/tests/mod.rs @@ -0,0 +1,160 @@ +use std::fs::File; + +use super::{ + chip::RlcChip, + circuit::{builder::RlcCircuitBuilder, instructions::RlcCircuitInstructions, RlcCircuitParams}, +}; +use ethers_core::k256::elliptic_curve::Field; +use halo2_base::{ + gates::{ + circuit::{BaseCircuitParams, CircuitBuilderStage}, + RangeChip, RangeInstructions, + }, + halo2_proofs::{ + dev::MockProver, + halo2curves::bn256::{Bn256, Fr}, + plonk::{keygen_pk, keygen_vk, Error}, + poly::kzg::commitment::ParamsKZG, + }, + utils::{ + testing::{check_proof, gen_proof}, + ScalarField, + }, + AssignedValue, +}; +use itertools::Itertools; +use rand::{rngs::StdRng, SeedableRng}; +use test_log::test; + +pub mod utils; +use utils::executor::{RlcCircuit, RlcExecutor}; + +const K: usize = 16; +fn test_params() -> RlcCircuitParams { + RlcCircuitParams { + base: BaseCircuitParams { + k: K, + num_advice_per_phase: vec![1, 1], + num_fixed: 1, + num_lookup_advice_per_phase: vec![], + lookup_bits: None, + num_instance_columns: 0, + }, + num_rlc_columns: 1, + } +} + +pub fn get_rlc_params(path: &str) -> RlcCircuitParams { + serde_json::from_reader(File::open(path).unwrap()).unwrap() +} + +struct Test { + padded_input: Vec, + len: usize, +} + +struct TestPayload { + true_input: Vec, + inputs: Vec>, + len: AssignedValue, +} + +impl RlcCircuitInstructions for Test { + type FirstPhasePayload = TestPayload; + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + _: &RangeChip, + ) -> Self::FirstPhasePayload { + let ctx = builder.base.main(0); + let true_input = self.padded_input[..self.len].to_vec(); + let inputs = ctx.assign_witnesses(self.padded_input.clone()); + let len = ctx.load_witness(F::from(self.len as u64)); + TestPayload { true_input, inputs, len } + } + + fn virtual_assign_phase1( + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + rlc: &RlcChip, + payload: Self::FirstPhasePayload, + ) { + let TestPayload { true_input, inputs, len } = payload; + let (ctx_gate, ctx_rlc) = builder.rlc_ctx_pair(); + let gate = range.gate(); + let rlc_trace = rlc.compute_rlc((ctx_gate, ctx_rlc), gate, inputs, len); + let rlc_val = *rlc_trace.rlc_val.value(); + let real_rlc = compute_rlc_acc(&true_input, *rlc.gamma()); + assert_eq!(real_rlc, rlc_val); + } +} + +fn compute_rlc_acc(msg: &[F], r: F) -> F { + let mut rlc = msg[0]; + for val in msg.iter().skip(1) { + rlc = rlc * r + val; + } + rlc +} + +fn rlc_test_circuit( + stage: CircuitBuilderStage, + inputs: Vec, + len: usize, +) -> RlcCircuit> { + let params = test_params(); + let mut builder = RlcCircuitBuilder::from_stage(stage, 0).use_params(params); + builder.base.set_lookup_bits(8); // not used, just to create range chip + RlcExecutor::new(builder, Test { padded_input: inputs, len }) +} + +#[test] +pub fn test_mock_rlc() { + let input_bytes = vec![ + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, + 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, + ] + .into_iter() + .map(|x| Fr::from(x as u64)) + .collect_vec(); + let len = 32; + + let circuit = rlc_test_circuit(CircuitBuilderStage::Mock, input_bytes, len); + MockProver::run(K as u32, &circuit, vec![]).unwrap().assert_satisfied(); +} + +#[test] +pub fn test_rlc() -> Result<(), Error> { + let input_bytes = vec![ + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, + 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, + ] + .into_iter() + .map(|x| Fr::from(x as u64)) + .collect_vec(); + let len = 32; + + let mut rng = StdRng::from_seed([0u8; 32]); + let k = K as u32; + let params = ParamsKZG::::setup(k, &mut rng); + let circuit = + rlc_test_circuit(CircuitBuilderStage::Keygen, vec![Fr::ZERO; input_bytes.len()], 1); + + println!("vk gen started"); + let vk = keygen_vk(¶ms, &circuit)?; + println!("vk gen done"); + let pk = keygen_pk(¶ms, vk, &circuit)?; + println!("pk gen done"); + let break_points = circuit.0.builder.borrow().break_points(); + drop(circuit); + println!(); + println!("==============STARTING PROOF GEN==================="); + + let circuit = rlc_test_circuit(CircuitBuilderStage::Prover, input_bytes, len); + circuit.0.builder.borrow_mut().set_break_points(break_points); + let proof = gen_proof(¶ms, &pk, circuit); + println!("proof gen done"); + check_proof(¶ms, pk.get_vk(), &proof, true); + println!("verify done"); + Ok(()) +} diff --git a/axiom-eth/src/rlc/tests/utils/executor.rs b/axiom-eth/src/rlc/tests/utils/executor.rs new file mode 100644 index 00000000..6b976b39 --- /dev/null +++ b/axiom-eth/src/rlc/tests/utils/executor.rs @@ -0,0 +1,119 @@ +use std::cell::{OnceCell, RefCell}; + +use halo2_base::{ + gates::{RangeChip, RangeInstructions}, + halo2_proofs::{circuit::Layouter, plonk::ConstraintSystem}, + utils::ScalarField, +}; + +use crate::rlc::circuit::{ + builder::RlcCircuitBuilder, instructions::RlcCircuitInstructions, RlcCircuitParams, RlcConfig, +}; + +use super::two_phase::{TwoPhaseCircuit, TwoPhaseCircuitInstructions}; + +/// This struct provides a quick way to create a circuit that only uses [RlcConfig] that +/// is less verbose than implementing [TwoPhaseCircuitInstructions] directly. +/// +/// If additional in-circuit logic is required or columns not managed by [RlcConfig] are necessary, +/// then one needs to create a new struct (often a circuit builder) that implements +/// [TwoPhaseCircuitInstructions] directly. +pub struct RlcExecutor> { + pub logic_inputs: I, + pub range_chip: RangeChip, + pub builder: RefCell>, + /// The FirstPhasePayload is set after FirstPhase witness generation. + /// This is used both to pass payload between phases and also to detect if `generate_witnesses_phase0` + /// was already run outside of `synthesize` (e.g., to determine public instances) + pub payload: RefCell>, +} + +pub type RlcCircuit = TwoPhaseCircuit>; + +impl RlcExecutor +where + I: RlcCircuitInstructions, +{ + pub fn new(builder: RlcCircuitBuilder, logic_inputs: I) -> RlcCircuit { + let range_chip = builder.base.range_chip(); + let ex = Self { + logic_inputs, + range_chip, + builder: RefCell::new(builder), + payload: RefCell::new(OnceCell::new()), + }; + TwoPhaseCircuit::new(ex) + } + + pub fn calculate_params(&self, minimum_rows: Option) -> RlcCircuitParams { + let mut builder = self.builder.borrow().deep_clone(); + let range_chip = builder.base.range_chip(); + let payload = I::virtual_assign_phase0(&self.logic_inputs, &mut builder, &range_chip); + // as long as not in Prover stage, this will just set challenge = 0 + let rlc_chip = builder.rlc_chip(range_chip.gate()); + I::virtual_assign_phase1(&mut builder, &range_chip, &rlc_chip, payload); + let params = builder.calculate_params(minimum_rows); + builder.clear(); // clear so dropping copy manager doesn't complain + self.builder.borrow_mut().set_params(params.clone()); + params + } +} + +impl TwoPhaseCircuitInstructions for RlcExecutor +where + F: ScalarField, + I: RlcCircuitInstructions, +{ + type Config = RlcConfig; + type Params = RlcCircuitParams; + + fn params(&self) -> Self::Params { + self.builder.borrow().params() + } + + fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config { + RlcConfig::configure(meta, params) + } + + /// We never clear if running a real proof for "halo2-axiom". + fn clear(&self) { + if self.builder.borrow().witness_gen_only() { + return; + } + self.builder.borrow_mut().clear(); + self.payload.borrow_mut().take(); + } + + fn initialize(&self, config: &Self::Config, mut layouter: impl Layouter) { + config.base.initialize(&mut layouter); + } + + fn virtual_assign_phase0(&self) { + if self.payload.borrow().get().is_some() { + return; + } + let mut builder = self.builder.borrow_mut(); + let payload = I::virtual_assign_phase0(&self.logic_inputs, &mut builder, &self.range_chip); + let _ = self.payload.borrow().set(payload); + } + + fn raw_synthesize_phase0(&self, config: &Self::Config, layouter: impl Layouter) { + self.builder.borrow().raw_synthesize_phase0(config, layouter); + } + + fn load_challenges(&self, config: &Self::Config, layouter: impl Layouter) { + self.builder.borrow_mut().load_challenge(config, layouter); + } + + fn virtual_assign_phase1(&self) { + let payload = + self.payload.borrow_mut().take().expect("FirstPhase witness generation was not run"); + let mut builder = self.builder.borrow_mut(); + let rlc_chip = builder.rlc_chip(self.range_chip.gate()); + I::virtual_assign_phase1(&mut builder, &self.range_chip, &rlc_chip, payload) + } + + fn raw_synthesize_phase1(&self, config: &Self::Config, layouter: impl Layouter) { + self.builder.borrow().raw_synthesize_phase1(config, layouter, true); + } +} diff --git a/axiom-eth/src/rlc/tests/utils/mod.rs b/axiom-eth/src/rlc/tests/utils/mod.rs new file mode 100644 index 00000000..712ef75d --- /dev/null +++ b/axiom-eth/src/rlc/tests/utils/mod.rs @@ -0,0 +1,3 @@ +/// Helper struct to automatically implement the `TwoPhaseCircuitInstructions` trait for fast testing. +pub mod executor; +pub mod two_phase; diff --git a/axiom-eth/src/rlc/tests/utils/two_phase.rs b/axiom-eth/src/rlc/tests/utils/two_phase.rs new file mode 100644 index 00000000..9c68bf29 --- /dev/null +++ b/axiom-eth/src/rlc/tests/utils/two_phase.rs @@ -0,0 +1,89 @@ +//! Template for functions a circuit should implement to work with two challenge phases, +//! with particular attention to + +use std::marker::PhantomData; + +use halo2_base::{ + halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Circuit, ConstraintSystem, Error}, + }, + utils::ScalarField, +}; + +/// Interface for what functions need to be supplied to write a circuit that +/// uses two challenge phases. +pub trait TwoPhaseCircuitInstructions { + type Config: Clone; + type Params: Clone + Default; + + fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config; + fn params(&self) -> Self::Params; + /// The multi-phase challenge API requires `Circuit::synthesize` to be called multiple times unless in `create_proof` mode and using `halo2-axiom`. To prevent issues with + /// the multiple calls, we require a function to clear the state of any circuit builders. + fn clear(&self); + // Instructions are listed in order they will be run + fn initialize(&self, _config: &Self::Config, _layouter: impl Layouter) {} + /// Phase0 assign to virtual regions. Any data passing from phase0 to phase1 will be done internally within the circuit and stored in `OnceCell` or `RefCell`. + fn virtual_assign_phase0(&self); + fn raw_synthesize_phase0(&self, config: &Self::Config, layouter: impl Layouter); + fn load_challenges(&self, config: &Self::Config, layouter: impl Layouter); + fn virtual_assign_phase1(&self); + fn raw_synthesize_phase1(&self, config: &Self::Config, layouter: impl Layouter); +} + +// Rust does not like blanket implementations of `Circuit` for multiple other traits. +// To get around this, we will wrap `TwoPhaseCircuitInstructions` +#[derive(Clone, Debug)] +pub struct TwoPhaseCircuit>( + pub CI, + PhantomData, +); + +impl> TwoPhaseCircuit { + pub fn new(instructions: CI) -> Self { + Self(instructions, PhantomData) + } +} + +impl> Circuit for TwoPhaseCircuit { + type Config = CI::Config; + type FloorPlanner = SimpleFloorPlanner; + type Params = CI::Params; + + fn params(&self) -> Self::Params { + self.0.params() + } + + fn without_witnesses(&self) -> Self { + unimplemented!() + } + + fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config { + CI::configure_with_params(meta, params) + } + + fn configure(_: &mut ConstraintSystem) -> Self::Config { + unreachable!() + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // clear in case synthesize is called multiple times + self.0.clear(); + self.0.initialize(&config, layouter.namespace(|| "initialize")); + self.0.virtual_assign_phase0(); + self.0.raw_synthesize_phase0(&config, layouter.namespace(|| "raw synthesize phase0")); + #[cfg(feature = "halo2-axiom")] + { + layouter.next_phase(); + } + self.0.load_challenges(&config, layouter.namespace(|| "load challenges")); + self.0.virtual_assign_phase1(); + self.0.raw_synthesize_phase1(&config, layouter.namespace(|| "raw synthesize phase1")); + Ok(()) + } +} diff --git a/axiom-eth/src/rlc/types.rs b/axiom-eth/src/rlc/types.rs new file mode 100644 index 00000000..462b0898 --- /dev/null +++ b/axiom-eth/src/rlc/types.rs @@ -0,0 +1,102 @@ +use halo2_base::{safe_types::VarLenBytesVec, utils::ScalarField, AssignedValue, Context}; + +#[derive(Clone, Copy, Debug)] +/// RLC of a vector of `F` values of variable length but known maximum length +pub struct RlcTrace { + pub rlc_val: AssignedValue, // in SecondPhase + pub len: AssignedValue, // in FirstPhase + pub max_len: usize, + // We no longer store the input values as they should be exposed elsewhere + // pub values: Vec>, +} + +impl RlcTrace { + pub fn new(rlc_val: AssignedValue, len: AssignedValue, max_len: usize) -> Self { + Self { rlc_val, len, max_len } + } + + pub fn from_fixed(ctx: &mut Context, trace: RlcFixedTrace) -> Self { + let len = ctx.load_constant(F::from(trace.len as u64)); + Self { rlc_val: trace.rlc_val, len, max_len: trace.len } + } +} + +#[derive(Clone, Copy, Debug)] +/// RLC of a trace of known fixed length +pub struct RlcFixedTrace { + pub rlc_val: AssignedValue, // SecondPhase + // pub values: Vec>, // FirstPhase + pub len: usize, +} + +// to deal with selecting / comparing RLC of variable length strings + +#[derive(Clone, Copy, Debug)] +pub struct RlcVar { + pub rlc_val: AssignedValue, + pub len: AssignedValue, +} + +impl From> for RlcVar { + fn from(trace: RlcTrace) -> Self { + RlcVar { rlc_val: trace.rlc_val, len: trace.len } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct RlcVarPtr<'a, F: ScalarField> { + pub rlc_val: &'a AssignedValue, + pub len: &'a AssignedValue, +} + +impl<'a, F: ScalarField> From<&'a RlcTrace> for RlcVarPtr<'a, F> { + fn from(trace: &'a RlcTrace) -> Self { + RlcVarPtr { rlc_val: &trace.rlc_val, len: &trace.len } + } +} + +impl<'a, F: ScalarField> From<&'a RlcVar> for RlcVarPtr<'a, F> { + fn from(trace: &'a RlcVar) -> RlcVarPtr<'a, F> { + RlcVarPtr { rlc_val: &trace.rlc_val, len: &trace.len } + } +} + +/// Length of `values` known at compile time. +/// Represents a variable length array with length given by `len`. +/// +/// Construction of this struct assumes you have checked `len < values.len()`. +#[derive(Clone, Debug)] +pub struct AssignedVarLenVec { + pub values: Vec>, + pub len: AssignedValue, +} + +impl AssignedVarLenVec { + pub fn max_len(&self) -> usize { + self.values.len() + } +} + +impl From> for AssignedVarLenVec { + fn from(array: VarLenBytesVec) -> Self { + let values = array.bytes().iter().map(|b| *b.as_ref()).collect(); + let len = *array.len(); + Self { values, len } + } +} + +#[derive(Clone, Debug)] +pub struct ConcatVarFixedArrayWitness { + pub prefix: AssignedVarLenVec, + pub suffix: Vec>, + pub concat: AssignedVarLenVec, +} + +/// Rlc traces of the rlc concatenation used to constrain the concatenation +/// of variable length `prefix` and fixed length `suffix` arrays. +#[derive(Debug, Clone)] +pub struct ConcatVarFixedArrayTrace { + pub prefix_rlc: RlcTrace, + pub suffix_rlc: RlcFixedTrace, + pub concat_rlc: RlcTrace, +} diff --git a/axiom-eth/src/rlc/virtual_region/manager.rs b/axiom-eth/src/rlc/virtual_region/manager.rs new file mode 100644 index 00000000..ef1b033c --- /dev/null +++ b/axiom-eth/src/rlc/virtual_region/manager.rs @@ -0,0 +1,165 @@ +use std::cell::RefCell; + +use crate::rlc::{circuit::PureRlcConfig, RLC_PHASE}; +use getset::CopyGetters; +use halo2_base::{ + gates::{ + circuit::CircuitBuilderStage, + flex_gate::{ + threads::single_phase::{assign_with_constraints, assign_witnesses}, + ThreadBreakPoints, + }, + }, + halo2_proofs::circuit::Region, + utils::ScalarField, + virtual_region::{ + copy_constraints::SharedCopyConstraintManager, manager::VirtualRegionManager, + }, + Context, +}; + +/// Virtual region manager for managing virtual columns ([Context]s) in [RLC_PHASE] corresponding to [RlcConfig]. +/// +/// Note: this uses a similar vertical gate structure as [FlexGateConfig] **however** the RLC gate uses only 3 contiguous rows instead of 4. +/// +/// The implementation and functionality of this manager is very similar to `SinglePhaseCoreManager` for [FlexGateConfig] except the aforementioned 3 rows vs 4 rows gate. +#[derive(Clone, Debug, Default, CopyGetters)] +pub struct RlcManager { + /// Virtual columns. These cannot be shared across CPU threads while keeping the circuit deterministic. + pub threads: Vec>, + /// Global shared copy manager + pub copy_manager: SharedCopyConstraintManager, + /// Flag for witness generation. If true, the gate thread builder is used for witness generation only. + #[getset(get_copy = "pub")] + witness_gen_only: bool, + /// The `unknown` flag is used during key generation. If true, during key generation witness [Value]s are replaced with Value::unknown() for safety. + #[getset(get_copy = "pub")] + pub(crate) use_unknown: bool, + /// A very simple computation graph for the basic vertical RLC gate. Must be provided as a "pinning" + /// when running the production prover. + pub break_points: RefCell>, +} + +// Copied impl from `SinglePhaseCoreManager`, modified so TypeId is of RlcManager, and phase is always RLC_PHASE = 1 (SecondPhase) +impl RlcManager { + /// Creates a new [RlcManager] and spawns a main thread. + /// * `witness_gen_only`: If true, the [RlcManager] is used for witness generation only and does not impose any constraints. + pub fn new(witness_gen_only: bool, copy_manager: SharedCopyConstraintManager) -> Self { + Self { + threads: vec![], + witness_gen_only, + use_unknown: false, + copy_manager, + ..Default::default() + } + } + + /// The phase of this manager is always [RLC_PHASE] + pub fn phase(&self) -> usize { + RLC_PHASE + } + + /// Creates a new [GateThreadBuilder] depending on the stage of circuit building. If the stage is [CircuitBuilderStage::Prover], the [GateThreadBuilder] is used for witness generation only. + pub fn from_stage( + stage: CircuitBuilderStage, + copy_manager: SharedCopyConstraintManager, + ) -> Self { + Self::new(stage.witness_gen_only(), copy_manager) + .unknown(stage == CircuitBuilderStage::Keygen) + } + + /// Creates a new [RlcManager] with `use_unknown` flag set. + /// * `use_unknown`: If true, during key generation witness [Value]s are replaced with Value::unknown() for safety. + pub fn unknown(self, use_unknown: bool) -> Self { + Self { use_unknown, ..self } + } + + /// Sets the copy manager to the given one in all shared references. + pub fn set_copy_manager(&mut self, copy_manager: SharedCopyConstraintManager) { + for ctx in &mut self.threads { + ctx.copy_manager = copy_manager.clone(); + } + self.copy_manager = copy_manager; + } + + /// Returns `self` with a given copy manager + pub fn use_copy_manager(mut self, copy_manager: SharedCopyConstraintManager) -> Self { + self.set_copy_manager(copy_manager); + self + } + + pub fn clear(&mut self) { + self.threads.clear(); + self.copy_manager.lock().unwrap().clear(); + } + + /// Returns a mutable reference to the [Context] of a gate thread. Spawns a new thread for the given phase, if none exists. + pub fn main(&mut self) -> &mut Context { + if self.threads.is_empty() { + self.new_thread() + } else { + self.threads.last_mut().unwrap() + } + } + + /// Returns the number of threads + pub fn thread_count(&self) -> usize { + self.threads.len() + } + + /// Creates new context but does not append to `self.threads` + pub(crate) fn new_context(&self, context_id: usize) -> Context { + Context::new( + self.witness_gen_only, + RLC_PHASE, + "axiom-eth:RlcManager:SecondPhase", + context_id, + self.copy_manager.clone(), + ) + } + + /// Spawns a new thread for a new given `phase`. Returns a mutable reference to the [Context] of the new thread. + /// * `phase`: The phase (index) of the gate thread. + pub fn new_thread(&mut self) -> &mut Context { + let context_id = self.thread_count(); + self.threads.push(self.new_context(context_id)); + self.threads.last_mut().unwrap() + } + + /// Returns total advice cells + pub fn total_advice(&self) -> usize { + self.threads.iter().map(|ctx| ctx.advice.len()).sum::() + } +} + +impl VirtualRegionManager for RlcManager { + type Config = PureRlcConfig; + + fn assign_raw(&self, rlc_config: &Self::Config, region: &mut Region) { + if self.witness_gen_only { + let binding = self.break_points.borrow(); + let break_points = binding.as_ref().expect("break points not set"); + assign_witnesses(&self.threads, &rlc_config.basic_gates, region, break_points); + } else { + let mut copy_manager = self.copy_manager.lock().unwrap(); + assert!( + self.threads.iter().all(|ctx| ctx.phase() == RLC_PHASE), + "all threads must be in RLC_PHASE" + ); + let break_points = assign_with_constraints::( + &self.threads, + &rlc_config.basic_gates, + region, + &mut copy_manager, + rlc_config.usable_rows, + self.use_unknown, + ); + let mut bp = self.break_points.borrow_mut(); + if let Some(bp) = bp.as_ref() { + assert_eq!(bp, &break_points, "break points don't match"); + } else { + *bp = Some(break_points); + } + } + } +} diff --git a/axiom-eth/src/rlc/virtual_region/mod.rs b/axiom-eth/src/rlc/virtual_region/mod.rs new file mode 100644 index 00000000..f5ef9a3c --- /dev/null +++ b/axiom-eth/src/rlc/virtual_region/mod.rs @@ -0,0 +1,10 @@ +use halo2_base::gates::flex_gate::{MultiPhaseThreadBreakPoints, ThreadBreakPoints}; +use serde::{Deserialize, Serialize}; + +pub mod manager; + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct RlcThreadBreakPoints { + pub base: MultiPhaseThreadBreakPoints, + pub rlc: ThreadBreakPoints, +} diff --git a/axiom-eth/src/rlp/mod.rs b/axiom-eth/src/rlp/mod.rs new file mode 100644 index 00000000..cea07c11 --- /dev/null +++ b/axiom-eth/src/rlp/mod.rs @@ -0,0 +1,604 @@ +use std::iter; + +use crate::{ + rlc::{chip::RlcChip, circuit::builder::RlcContextPair, types::RlcTrace}, + utils::circuit_utils::constrain_no_leading_zeros, +}; + +use halo2_base::{ + gates::{GateChip, GateInstructions, RangeChip, RangeInstructions}, + utils::{bit_length, ScalarField}, + AssignedValue, Context, + QuantumCell::{Constant, Existing}, +}; + +use self::types::{ + RlpArrayPrefixParsed, RlpArrayTrace, RlpArrayWitness, RlpFieldPrefixParsed, RlpFieldTrace, + RlpFieldWitness, RlpPrefixParsed, +}; + +#[cfg(test)] +mod tests; +pub mod types; + +pub const fn max_rlp_len_len(max_len: usize) -> usize { + if max_len > 55 { + (bit_length(max_len as u64) + 7) / 8 + } else { + 0 + } +} + +pub const fn max_rlp_encoding_len(payload_len: usize) -> usize { + 1 + max_rlp_len_len(payload_len) + payload_len +} + +/// Returns array whose first `sub_len` cells are +/// `array[start_idx..start_idx + sub_len]` +/// and whose last cells are `0`. +/// +/// These cells are witnessed but _NOT_ constrained +pub fn witness_subarray( + ctx: &mut Context, + array: &[AssignedValue], + start_id: &F, + sub_len: &F, + max_len: usize, +) -> Vec> { + // `u32` should be enough for array indices + let [start_id, sub_len] = [start_id, sub_len].map(|fe| fe.get_lower_64() as usize); + debug_assert!(sub_len <= max_len, "{sub_len} > {max_len}"); + ctx.assign_witnesses( + array[start_id..start_id + sub_len] + .iter() + .map(|a| *a.value()) + .chain(iter::repeat(F::ZERO)) + .take(max_len), + ) +} + +/// 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 { + if !array.is_empty() { + let incremental_evals = gate.accumulated_product( + ctx, + iter::repeat(Constant(F::from(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() + } +} + +/// Chip for proving RLP decoding. This only contains references to other chips, so it can be freely copied. +/// This will only contain [RlcChip] after [SecondPhase]. +#[derive(Clone, Copy, Debug)] +pub struct RlpChip<'range, F: ScalarField> { + pub rlc: Option<&'range RlcChip>, // We use this chip in FirstPhase when there is no RlcChip + pub range: &'range RangeChip, +} + +impl<'range, F: ScalarField> RlpChip<'range, F> { + pub fn new(range: &'range RangeChip, rlc: Option<&'range RlcChip>) -> Self { + Self { rlc, range } + } + + pub fn gate(&self) -> &GateChip { + self.range.gate() + } + + pub fn range(&self) -> &RangeChip { + self.range + } + + pub fn rlc(&self) -> &RlcChip { + self.rlc.as_ref().expect("RlcChip should be constructed and used only in SecondPhase") + } + + /// Parse a byte by interpreting it as the first byte in the RLP encoding of a byte string. + /// Constrains that the byte is valid for RLP encoding of byte strings (not a list). + /// + /// `ctx` can be in any phase but this is usually called in phase 0. + /// + /// https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ + /// * For a single byte whose value is in the [0x00, 0x7f] (decimal [0, 127]) range, that byte is its own RLP encoding. + /// * Otherwise, if a string is 0-55 bytes long, the RLP encoding consists of a single byte with value 0x80 (dec. 128) plus the length of the string followed by the string. The range of the first byte is thus [0x80, 0xb7] (dec. [128, 183]). + /// * If a string is more than 55 bytes long, the RLP encoding consists of a single byte with value 0xb7 (dec. 183) plus the length in bytes of the length of the string in binary form, followed by the length of the string, followed by the string. + /// + /// ## Warning + /// This function does not constrain that if `is_big == true` then the string length must be greater than 55 bytes. This is done separately in `parse_rlp_len`. + fn parse_rlp_field_prefix( + &self, + ctx: &mut Context, + prefix: AssignedValue, + ) -> RlpFieldPrefixParsed { + let is_not_literal = self.range.is_less_than(ctx, Constant(F::from(127)), prefix, 8); + let is_len_or_literal = self.range.is_less_than(ctx, prefix, Constant(F::from(184)), 8); + // is valid + self.range.check_less_than(ctx, prefix, Constant(F::from(192)), 8); + + let field_len = self.gate().sub(ctx, prefix, Constant(F::from(128))); + let len_len = self.gate().sub(ctx, prefix, Constant(F::from(183))); + + let is_big = self.gate().not(ctx, is_len_or_literal); + + // length of the next RLP field + let next_len = self.gate().select(ctx, len_len, field_len, is_big); + let next_len = self.gate().select(ctx, next_len, Constant(F::ONE), is_not_literal); + let len_len = self.gate().mul(ctx, len_len, is_big); + let len_len = self.gate().mul(ctx, is_not_literal, len_len); + RlpFieldPrefixParsed { is_not_literal, is_big, next_len, len_len } + } + + /// Parse a byte by interpreting it as the first byte in the RLP encoding of a list. + /// Constrains that the byte is valid for RLP encoding of lists (and is not a byte string). + /// + /// `ctx` can be in any phase but this is usually called in phase 0. + /// + /// https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ + /// * If the total payload of a list (i.e. the combined length of all its items being RLP encoded) is 0-55 bytes long, the RLP encoding consists of a single byte with value 0xc0 plus the length of the list followed by the concatenation of the RLP encodings of the items. The range of the first byte is thus [0xc0, 0xf7] (dec. [192, 247]). + /// * If the total payload of a list is more than 55 bytes long, the RLP encoding consists of a single byte with value 0xf7 plus the length in bytes of the length of the payload in binary form, followed by the length of the payload, followed by the concatenation of the RLP encodings of the items. The range of the first byte is thus [0xf8, 0xff] (dec. [248, 255]). + /// + /// ## Warning + /// This function does not constrain that if `is_big == true` then the total payload length of the list must be greater than 55 bytes. This is done separately in `parse_rlp_len`. + fn parse_rlp_array_prefix( + &self, + ctx: &mut Context, + prefix: AssignedValue, + ) -> RlpArrayPrefixParsed { + // is valid + self.range.check_less_than(ctx, Constant(F::from(191)), prefix, 8); + + let is_big = self.range.is_less_than(ctx, Constant(F::from(247)), prefix, 8); + + let array_len = self.gate().sub(ctx, prefix, Constant(F::from(192))); + let len_len = self.gate().sub(ctx, prefix, Constant(F::from(247))); + let next_len = self.gate().select(ctx, len_len, array_len, is_big); + let len_len = self.gate().mul(ctx, len_len, is_big); + + RlpArrayPrefixParsed { is_big, next_len, len_len } + } + + /// Parse a byte by interpreting it as the first byte in the RLP encoding (either a byte string or a list). + /// Output should be identical to `parse_rlp_field_prefix` when `prefix` is a byte string. + /// Output should be identical to `parse_rlp_array_prefix` when `prefix` is a list. + /// + /// Assumes that `prefix` has already been range checked to be a byte + /// + /// ## Warning + /// This function does not constrain that if `is_big == true` then the total payload length must be greater than 55 bytes. This is done separately in `parse_rlp_len`. + fn parse_rlp_prefix( + &self, + ctx: &mut Context, + prefix: AssignedValue, + ) -> RlpPrefixParsed { + let is_not_literal = self.range.is_less_than(ctx, Constant(F::from(127)), prefix, 8); + let is_field = self.range.is_less_than(ctx, prefix, Constant(F::from(192)), 8); + + let is_big_if_field = self.range.is_less_than(ctx, Constant(F::from(183)), prefix, 8); + let is_big_if_array = self.range.is_less_than(ctx, Constant(F::from(247)), prefix, 8); + + let is_big = self.gate().select(ctx, is_big_if_field, is_big_if_array, is_field); + + let field_len = self.gate().sub(ctx, prefix, Constant(F::from(128))); + let field_len_len = self.gate().sub(ctx, prefix, Constant(F::from(183))); + let next_field_len = self.gate().select(ctx, field_len_len, field_len, is_big_if_field); + let next_field_len = + self.gate().select(ctx, next_field_len, Constant(F::ONE), is_not_literal); + + let array_len = self.gate().sub(ctx, prefix, Constant(F::from(192))); + let array_len_len = self.gate().sub(ctx, prefix, Constant(F::from(247))); + let next_array_len = self.gate().select(ctx, array_len_len, array_len, is_big_if_array); + + let next_len = self.gate().select(ctx, next_field_len, next_array_len, is_field); + let len_len = self.gate().select(ctx, field_len_len, array_len_len, is_field); + let len_len = self.gate().mul(ctx, len_len, is_big); + + RlpPrefixParsed { is_not_literal, is_big, next_len, len_len } + } + + /// Given a full RLP encoding `rlp_cells` string, and the length in bytes of the length of the payload, `len_len`, parse the length of the payload. + /// + /// Assumes that it is known that `len_len <= max_len_len`. + /// + /// Returns the *witness* for the length of the payload in bytes, together with the BigInt value of this length, which is constrained assuming that the witness is valid. (The witness for the length as byte string is checked later in an RLC concatenation.) + /// + /// The BigInt value of the length is returned as `len_val`. + /// We constrain that `len_val > 55` if and only if `is_big == true`. + /// + /// ## Assumptions + /// * `rlp_cells` have already been constrained to be bytes. + fn parse_rlp_len( + &self, + ctx: &mut Context, + rlp_cells: &[AssignedValue], + len_len: AssignedValue, + max_len_len: usize, + is_big: AssignedValue, + ) -> (Vec>, AssignedValue) { + let len_cells = witness_subarray( + ctx, + rlp_cells, + &F::ONE, // the 0th index is the prefix byte, and is skipped + len_len.value(), + max_len_len, + ); + // The conversion from length as BigInt to bytes must have no leading zeros + constrain_no_leading_zeros(ctx, self.gate(), &len_cells, len_len); + let len_val = evaluate_byte_array(ctx, self.gate(), &len_cells, len_len); + // Constrain that `len_val > 55` if and only if `is_big == true`. + // `len_val` has at most `max_len_len * 8` bits, and `55` is 6 bits. + let len_is_big = self.range.is_less_than( + ctx, + Constant(F::from(55)), + len_val, + std::cmp::max(8 * max_len_len, 6), + ); + ctx.constrain_equal(&len_is_big, &is_big); + (len_cells, len_val) + } + + /// Given a byte string `rlp_field`, this function together with [`Self::decompose_rlp_field_phase1`] + /// constrains that the byte string is the RLP encoding of a byte string (and not a list). + /// + /// In the present function, the witnesses for the RLP decoding are computed and assigned. This decomposition is NOT yet constrained. + /// + /// Witnesses MUST be generated in `FirstPhase` to be able to compute RLC of them in `SecondPhase` + /// + /// # Assumptions + /// - `rlp_field` elements are already range checked to be bytes + // TODO: use SafeByte + pub fn decompose_rlp_field_phase0( + &self, + ctx: &mut Context, // context for GateChip + rlp_field: Vec>, + max_field_len: usize, + ) -> RlpFieldWitness { + let max_len_len = max_rlp_len_len(max_field_len); + debug_assert_eq!(rlp_field.len(), 1 + max_len_len + max_field_len); + + // Witness consists of + // * prefix_parsed + // * len_rlc + // * field_rlc + // * rlp_field_rlc + // + // check that: + // * len_rlc.rlc_len in [0, max_len_len] + // * field_rlc.rlc_len in [0, max_field_len] + // * rlp_field_rlc.rlc_len in [0, max_rlp_field_len] + // + // * 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 = (1 - prefix_parsed.is_big) * prefix_parsed.next_len + // + prefix_parsed.is_big * byte_value(len) + // + // * rlp_field_rlc = accumulate( + // [(prefix, 1), + // (len_rlc.rlc_val, len_rlc.rlc_len), + // (field_rlc.rlc_val, field_rlc.rlc_len)]) + + let prefix_parsed = self.parse_rlp_field_prefix(ctx, rlp_field[0]); + let prefix = self.gate().mul(ctx, rlp_field[0], prefix_parsed.is_not_literal); + + let len_len = prefix_parsed.len_len; + self.range.check_less_than_safe(ctx, len_len, (max_len_len + 1) as u64); + let (len_cells, len_byte_val) = + self.parse_rlp_len(ctx, &rlp_field, len_len, max_len_len, prefix_parsed.is_big); + + let field_len = + self.gate().select(ctx, len_byte_val, prefix_parsed.next_len, prefix_parsed.is_big); + self.range.check_less_than_safe(ctx, field_len, (max_field_len + 1) as u64); + + let field_cells = witness_subarray( + ctx, + &rlp_field, + &(*prefix_parsed.is_not_literal.value() + len_len.value()), + field_len.value(), + max_field_len, + ); + + let rlp_len = self.gate().sum(ctx, [prefix_parsed.is_not_literal, len_len, field_len]); + + RlpFieldWitness { + prefix, + prefix_len: prefix_parsed.is_not_literal, + len_len, + len_cells, + field_len, + field_cells, + max_field_len, + encoded_item: rlp_field, + encoded_item_len: rlp_len, + } + } + + /// Use RLC to constrain the parsed RLP field witness. This MUST be done in `SecondPhase`. + pub fn decompose_rlp_field_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + witness: RlpFieldWitness, + ) -> RlpFieldTrace { + let RlpFieldWitness { + prefix, + prefix_len, + len_len, + len_cells, + field_len, + field_cells, + max_field_len, + encoded_item: rlp_field, + encoded_item_len: rlp_field_len, + } = witness; + assert_eq!(max_rlp_len_len(max_field_len), len_cells.len()); + assert_eq!(max_field_len, field_cells.len()); + + let rlc = self.rlc(); + // Disabling as it should be called globally to avoid concurrency issues: + // rlc.load_rlc_cache((ctx_gate, ctx_rlc), self.gate(), bit_length(rlp_field.len() as u64)); + + let len_rlc = rlc.compute_rlc((ctx_gate, ctx_rlc), self.gate(), len_cells, len_len); + let field_rlc = rlc.compute_rlc((ctx_gate, ctx_rlc), self.gate(), field_cells, field_len); + let rlp_field_rlc = + rlc.compute_rlc((ctx_gate, ctx_rlc), self.gate(), rlp_field, rlp_field_len); + + rlc.constrain_rlc_concat( + ctx_gate, + self.gate(), + [RlcTrace::new(prefix, prefix_len, 1), len_rlc, field_rlc], + &rlp_field_rlc, + None, + ); + + RlpFieldTrace { + prefix, + prefix_len, + len_trace: len_rlc, + field_trace: field_rlc, + rlp_trace: rlp_field_rlc, + } + } + + /// Compute and assign witnesses for deserializing an RLP list of byte strings. Does not support nested lists. + /// + /// In the present function, the witnesses for the RLP decoding are computed and assigned. This decomposition is NOT yet constrained. + /// Witnesses MUST be generated in `FirstPhase` to be able to compute RLC of them in `SecondPhase` + /// + /// * If `is_variable_len = false`, then the circuit will constrain that the list has number of items exactly equal to `max_field_lens.len()`. + /// * Otherwise if `is_variable_len = true`, then `max_field_lens.len()` is assumed to be the maximum number of items of any list represented by this RLP encoding. + /// * `max_field_lens` is the maximum length of each field in the list. + /// + /// # Assumptions + /// * In order for the circuit to pass, the excess witness values in `rlp_array` beyond the actual RLP sequence should all be `0`s. + /// * `rlp_array` should be an array of `AssignedValue`s that are range checked to be bytes + /// + /// For each item in the array, we must decompose its prefix and length to determine how long the item is. + /// + pub fn decompose_rlp_array_phase0( + &self, + ctx: &mut Context, // context for GateChip + rlp_array: Vec>, + max_field_lens: &[usize], + is_variable_len: bool, + ) -> RlpArrayWitness { + let max_rlp_array_len = rlp_array.len(); + let max_len_len = max_rlp_len_len(max_rlp_array_len); + + // Witness consists of + // * prefix_parsed + // * len_rlc + // * field_rlcs: Vec + // * rlp_array_rlc + // + // check that: + // * len_rlc.len in [0, max_len_len] + // * field_rlcs[idx].len in [0, max_field_len[idx]] + // * rlp_field_rlc.len in [0, max_rlp_field_len] + // + // * rlp_field_rlc.len = 1 + len_rlc.rlc_len + field_rlc.rlc_len + // * len_rlc.len = prefix_parsed.is_big * prefix_parsed.next_len + // * field_rlc.len = prefix_parsed.is_big * prefix_parsed.next_len + // + (1 - prefix_parsed.is_big) * byte_value(len) + // + // * rlp_field_rlc = accumulate( + // [(prefix, 1), + // (len_rlc.rlc_val, len_rlc.rlc_len), + // (field_rlc.rlc_val, field_rlc.rlc_len)]) + + let prefix = rlp_array[0]; + + let prefix_parsed = self.parse_rlp_array_prefix(ctx, prefix); + let len_len = prefix_parsed.len_len; + let next_len = prefix_parsed.next_len; + let is_big = prefix_parsed.is_big; + + self.range.check_less_than_safe(ctx, len_len, (max_len_len + 1) as u64); + + let (len_cells, len_byte_val) = + self.parse_rlp_len(ctx, &rlp_array, len_len, max_len_len, is_big); + + let list_payload_len = self.gate().select(ctx, len_byte_val, next_len, is_big); + self.range.check_less_than_safe( + ctx, + list_payload_len, + (max_rlp_array_len - max_len_len) as u64, + ); + + // this is automatically <= max_rlp_array_len + let rlp_len = + self.gate().sum(ctx, [Constant(F::ONE), Existing(len_len), Existing(list_payload_len)]); + + let mut field_witness = Vec::with_capacity(max_field_lens.len()); + + let mut prefix_idx = self.gate().add(ctx, Constant(F::ONE), len_len); + let mut running_max_len = max_len_len + 1; + + let mut list_len = ctx.load_zero(); + + for &max_field_len in max_field_lens { + let mut prefix = self.gate().select_from_idx( + ctx, + // selecting from the whole array is wasteful: we only select from the max range currently possible + rlp_array.iter().copied().take(running_max_len + 1), + prefix_idx, + ); + + let prefix_parsed = self.parse_rlp_prefix(ctx, prefix); + let mut len_len = prefix_parsed.len_len; + + let max_field_len_len = max_rlp_len_len(max_field_len); + self.range.check_less_than_safe( + ctx, + prefix_parsed.len_len, + (max_field_len_len + 1) as u64, + ); + + let len_start_id = *prefix_parsed.is_not_literal.value() + prefix_idx.value(); + let len_cells = witness_subarray( + ctx, + &rlp_array, + &len_start_id, + prefix_parsed.len_len.value(), + max_field_len_len, + ); + + let field_len_byte_val = evaluate_byte_array(ctx, self.gate(), &len_cells, len_len); + let mut field_len = self.gate().select( + ctx, + field_len_byte_val, + prefix_parsed.next_len, + prefix_parsed.is_big, + ); + + running_max_len += 1 + max_field_len_len + max_field_len; + + // prefix_len is either 0 or 1 + let mut prefix_len = prefix_parsed.is_not_literal; + if is_variable_len { + // If `prefix_idx >= rlp_len`, that means we are done + let field_in_list = self.range.is_less_than( + ctx, + prefix_idx, + rlp_len, + bit_length(max_rlp_array_len as u64), + ); + list_len = self.gate().add(ctx, list_len, field_in_list); + // In cases where the RLP sequence is a list of unknown variable length, we keep track + // of whether the corresponding index actually is a list item by constraining that + // all of `prefix_len, len_len, field_len` are 0 when the current field should be treated + // as a dummy and not actually in the list + prefix_len = self.gate().mul(ctx, prefix_len, field_in_list); + len_len = self.gate().mul(ctx, prefix_parsed.len_len, field_in_list); + field_len = self.gate().mul(ctx, field_len, field_in_list); + } + + self.range.check_less_than_safe(ctx, field_len, (max_field_len + 1) as u64); + + let field_cells = witness_subarray( + ctx, + &rlp_array, + &(len_start_id + len_len.value()), + field_len.value(), + max_field_len, + ); + + let encoded_item_len = + self.gate().sum(ctx, [prefix_parsed.is_not_literal, len_len, field_len]); + let encoded_item = witness_subarray( + ctx, + &rlp_array, + prefix_idx.value(), + encoded_item_len.value(), + max_rlp_encoding_len(max_field_len), + ); // *** unconstrained + + prefix = self.gate().mul(ctx, prefix, prefix_len); + prefix_idx = + self.gate().sum(ctx, [prefix_idx, prefix_len, prefix_parsed.len_len, field_len]); + + let witness = RlpFieldWitness { + prefix, // 0 if phantom or literal, 1st byte otherwise + prefix_len, // 0 if phantom or literal, 1 otherwise + len_len, + len_cells, // have not constrained this subarray + field_len, // have not constrained the copy used to make this + field_cells, // have not constrained this subarray + max_field_len, + encoded_item, // have not constrained this subarray + encoded_item_len, + }; + field_witness.push(witness); + } + let list_len = is_variable_len.then_some(list_len); + RlpArrayWitness { field_witness, len_len, len_cells, rlp_len, rlp_array, list_len } + } + + /// Use RLC to constrain the parsed RLP array witness. This MUST be done in `SecondPhase`. + /// + /// We do not make any guarantees on the values in the original RLP sequence beyond the parsed length for the total payload + pub fn decompose_rlp_array_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + rlp_array_witness: RlpArrayWitness, + _is_variable_len: bool, + ) -> RlpArrayTrace { + let RlpArrayWitness { field_witness, len_len, len_cells, rlp_len, rlp_array, .. } = + rlp_array_witness; + let rlc = self.rlc(); + // we only need rlc_pow up to the maximum length in a fragment of `constrain_rlc_concat` + // let max_item_rlp_len = + // field_witness.iter().map(|w| max_rlp_encoding_len(w.max_field_len)).max().unwrap(); + // Disabling this as it should be called once globally: + // rlc.load_rlc_cache((ctx_gate, ctx_rlc), self.gate(), bit_length(max_item_rlp_len as u64)); + + let len_trace = rlc.compute_rlc((ctx_gate, ctx_rlc), self.gate(), len_cells, len_len); + + let mut field_trace = Vec::with_capacity(field_witness.len()); + for w in field_witness { + let len_rlc = rlc.compute_rlc((ctx_gate, ctx_rlc), self.gate(), w.len_cells, w.len_len); + let field_rlc = + rlc.compute_rlc((ctx_gate, ctx_rlc), self.gate(), w.field_cells, w.field_len); + // RLC of the entire RLP encoded item + let rlp_trace = rlc.compute_rlc( + (ctx_gate, ctx_rlc), + self.gate(), + w.encoded_item, + w.encoded_item_len, + ); + // We need to constrain that the `encoded_item` is the concatenation of the prefix, the length, and the payload + rlc.constrain_rlc_concat( + ctx_gate, + self.gate(), + [RlcTrace::new(w.prefix, w.prefix_len, 1), len_rlc, field_rlc], + &rlp_trace, + None, + ); + field_trace.push(RlpFieldTrace { + prefix: w.prefix, + prefix_len: w.prefix_len, + len_trace: len_rlc, + field_trace: field_rlc, + rlp_trace, + }); + } + + let prefix = rlp_array[0]; + let one = ctx_gate.load_constant(F::ONE); + let rlp_rlc = rlc.compute_rlc((ctx_gate, ctx_rlc), self.gate(), rlp_array, rlp_len); + let inputs = iter::empty() + .chain([RlcTrace::new(prefix, one, 1), len_trace]) + .chain(field_trace.iter().map(|trace| trace.rlp_trace)); + + rlc.constrain_rlc_concat(ctx_gate, self.gate(), inputs, &rlp_rlc, None); + + RlpArrayTrace { len_trace, field_trace } + } +} diff --git a/axiom-eth/src/rlp/test_data/array_of_literals_big.json b/axiom-eth/src/rlp/test_data/array_of_literals_big.json new file mode 100644 index 00000000..89bfdade --- /dev/null +++ b/axiom-eth/src/rlp/test_data/array_of_literals_big.json @@ -0,0 +1,18 @@ +{ + "input": "f8408d123000000000000000000000028824232222222222238b32222222222222222412528a04233333333333332322912323333333333333333333333333333333", + "max_field_lens": [ + 20, + 20, + 20, + 20, + 20 + ], + "is_var_len": true, + "parsed": [ + "8d12300000000000000000000002", + "882423222222222223", + "8b3222222222222222241252", + "8a04233333333333332322", + "912323333333333333333333333333333333" + ] +} \ No newline at end of file diff --git a/axiom-eth/src/rlp/test_data/arrays_and_fields.json b/axiom-eth/src/rlp/test_data/arrays_and_fields.json new file mode 100644 index 00000000..6da44ded --- /dev/null +++ b/axiom-eth/src/rlp/test_data/arrays_and_fields.json @@ -0,0 +1,42 @@ +{ + "input": "d5808080c2340280c236038080808080808080808001", + "max_field_lens": [ + 2, + 2, + 2, + 2, + 2, + 8, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "is_var_len": true, + "parsed": [ + "80", + "80", + "80", + "c23402", + "80", + "c23603", + "80", + "80", + "80", + "80", + "80", + "80", + "80", + "80", + "80", + "80", + "01" + ] +} \ No newline at end of file diff --git a/axiom-eth/src/rlp/test_data/list_of_strings.json b/axiom-eth/src/rlp/test_data/list_of_strings.json new file mode 100644 index 00000000..4fafd4af --- /dev/null +++ b/axiom-eth/src/rlp/test_data/list_of_strings.json @@ -0,0 +1,12 @@ +{ + "input": "c88363617483646f67", + "max_field_lens": [ + 4, + 4 + ], + "is_var_len": true, + "parsed": [ + "83636174", + "83646f67" + ] +} \ No newline at end of file diff --git a/axiom-eth/src/rlp/test_data/nested_arrays.json b/axiom-eth/src/rlp/test_data/nested_arrays.json new file mode 100644 index 00000000..be7b3478 --- /dev/null +++ b/axiom-eth/src/rlp/test_data/nested_arrays.json @@ -0,0 +1,12 @@ +{ + "input": "d9820012d5808080c2340280c236038080808080808080808001", + "max_field_lens": [ + 5, + 25 + ], + "is_var_len": true, + "parsed": [ + "820012", + "d5808080c2340280c236038080808080808080808001" + ] +} \ No newline at end of file diff --git a/axiom-eth/src/rlp/test_data/nested_arrays_big.json b/axiom-eth/src/rlp/test_data/nested_arrays_big.json new file mode 100644 index 00000000..5954b5a4 --- /dev/null +++ b/axiom-eth/src/rlp/test_data/nested_arrays_big.json @@ -0,0 +1,16 @@ +{ + "input": "f872820012d5808080c2340280c236038080808080808080808001d5808080c2340280c236038080808080808080808001f841808080c2340280c236038080808080808080808001d5808080c2340280c236038080808080808080808001d5808080c2340280c236038080808080808080808001", + "max_field_lens": [ + 100, + 100, + 100, + 100 + ], + "is_var_len": true, + "parsed": [ + "820012", + "d5808080c2340280c236038080808080808080808001", + "d5808080c2340280c236038080808080808080808001", + "f841808080c2340280c236038080808080808080808001d5808080c2340280c236038080808080808080808001d5808080c2340280c236038080808080808080808001" + ] +} \ No newline at end of file diff --git a/axiom-eth/src/rlp/tests/combo.rs b/axiom-eth/src/rlp/tests/combo.rs new file mode 100644 index 00000000..666653b6 --- /dev/null +++ b/axiom-eth/src/rlp/tests/combo.rs @@ -0,0 +1,74 @@ +use std::fs::File; + +use super::*; + +use serde::{Deserialize, Serialize}; +use test_case::test_case; + +#[derive(Default, Clone, Debug, Serialize, Deserialize)] +struct RlpComboTestInput { + pub input: String, + pub max_field_lens: Vec, + pub is_var_len: bool, + pub parsed: Vec, +} + +impl RlcCircuitInstructions for RlpComboTestInput { + type FirstPhasePayload = (RlpArrayWitness, bool); + + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + ) -> Self::FirstPhasePayload { + let ctx = builder.base.main(0); + let mut input_bytes: Vec = Vec::from_hex(&self.input).unwrap(); + let input_size = self.max_field_lens.iter().sum::() * 2; + input_bytes.resize(input_size, 0u8); + let inputs = ctx.assign_witnesses(input_bytes.iter().map(|x| Fr::from(*x as u64))); + let chip = RlpChip::new(range, None); + let witness = + chip.decompose_rlp_array_phase0(ctx, inputs, &self.max_field_lens, self.is_var_len); + assert_eq!(witness.field_witness.len(), self.parsed.len()); + for (item_witness, parsed) in witness.field_witness.iter().zip(self.parsed.iter()) { + let parsed_bytes = Vec::from_hex(parsed).unwrap(); + + let field = &item_witness.encoded_item; + let parsed_bytes = parsed_bytes.iter().map(|x| Fr::from(*x as u64)); + for (a, b) in field.iter().zip(parsed_bytes) { + assert_eq!(a.value(), &b); + } + } + (witness, self.is_var_len) + } + + fn virtual_assign_phase1( + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + rlc: &RlcChip, + (witness, is_var_len): Self::FirstPhasePayload, + ) { + let chip = RlpChip::new(range, Some(rlc)); + chip.decompose_rlp_array_phase1(builder.rlc_ctx_pair(), witness, is_var_len); + } +} + +#[test_case("src/rlp/test_data/list_of_strings.json" ; "fields")] +#[test_case("src/rlp/test_data/arrays_and_fields.json" ; "arrays and fields")] +#[test_case("src/rlp/test_data/nested_arrays.json" ; "nested arrays")] +#[test_case("src/rlp/test_data/array_of_literals_big.json" ; "big array of literals")] +#[test_case("src/rlp/test_data/nested_arrays_big.json" ; "big nested arrays")] + +pub fn test_mock_rlp_combo(path: &str) { + let k = DEGREE; + let input: RlpComboTestInput = + serde_json::from_reader(File::open(path).expect("path does not exist")).unwrap(); + + let builder = RlcCircuitBuilder::from_stage(CircuitBuilderStage::Mock, 7) + .use_k(DEGREE as usize) + .use_lookup_bits(8); + let circuit = RlcExecutor::new(builder, input); + circuit.0.calculate_params(Some(20)); + + MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied(); +} diff --git a/axiom-eth/src/rlp/tests/list.rs b/axiom-eth/src/rlp/tests/list.rs new file mode 100644 index 00000000..5a63fcf1 --- /dev/null +++ b/axiom-eth/src/rlp/tests/list.rs @@ -0,0 +1,177 @@ +use super::*; +use test_log::test; + +// Both positive and negative tests for RLP decoding a byte string +struct RlpListTest { + encoded: Vec, + max_field_lens: Vec, + is_var_len: bool, + prank_idx: Option, + prank_component: Option, + _marker: PhantomData, +} + +impl RlcCircuitInstructions for RlpListTest { + type FirstPhasePayload = (RlpArrayWitness, bool); + + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + ) -> Self::FirstPhasePayload { + let ctx = builder.base.main(0); + let inputs = ctx.assign_witnesses(self.encoded.iter().map(|x| F::from(*x as u64))); + let chip = RlpChip::new(range, None); + let mut witness = + chip.decompose_rlp_array_phase0(ctx, inputs, &self.max_field_lens, self.is_var_len); + if let Some(prank_component) = self.prank_component { + let (prank_len, prank_comp) = match prank_component { + 0 => (witness.rlp_len, &mut witness.rlp_array), + 1 => (witness.len_len, &mut witness.len_cells), + 2 => ( + witness.field_witness[0].encoded_item_len, + &mut witness.field_witness[0].encoded_item, + ), + _ => ( + witness.field_witness[0].encoded_item_len, + &mut witness.field_witness[0].encoded_item, + ), + }; + if let Some(prank_idx) = self.prank_idx { + if prank_component < 3 && prank_idx < prank_comp.len() { + let prankval = range.gate().add(ctx, prank_comp[prank_idx], Constant(F::ONE)); + prank_comp[prank_idx].debug_prank(ctx, *prankval.value()); + } + let bad_idx = range.is_less_than_safe(ctx, prank_len, prank_idx as u64 + 1); + let zero = ctx.load_zero(); + ctx.constrain_equal(&bad_idx, &zero); + } + } + (witness, self.is_var_len) + } + + fn virtual_assign_phase1( + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + rlc: &RlcChip, + (witness, is_var_len): (RlpArrayWitness, bool), + ) { + let chip = RlpChip::new(range, Some(rlc)); + let (ctx_gate, ctx_rlc) = builder.rlc_ctx_pair(); + chip.decompose_rlp_array_phase1((ctx_gate, ctx_rlc), witness, is_var_len); + } +} + +fn rlp_list_circuit( + stage: CircuitBuilderStage, + encoded: Vec, + max_field_lens: &[usize], + is_var_len: bool, + prank_idx: Option, + prank_component: Option, +) -> RlcCircuit> { + let input = RlpListTest { + encoded, + max_field_lens: max_field_lens.to_vec(), + is_var_len, + prank_idx, + prank_component, + _marker: PhantomData, + }; + let mut builder = RlcCircuitBuilder::from_stage(stage, 10).use_k(DEGREE as usize); + builder.base.set_lookup_bits(8); + let circuit = RlcExecutor::new(builder, input); + if !stage.witness_gen_only() { + circuit.0.calculate_params(Some(9)); + } + circuit +} + +#[test] +pub fn test_mock_rlp_array() { + let k = DEGREE; + // the list [ "cat", "dog" ] = [ 0xc8, 0x83, 'c', 'a', 't', 0x83, 'd', 'o', 'g' ] + let cat_dog: Vec = vec![0xc8, 0x83, b'c', b'a', b't', 0x83, b'd', b'o', b'g']; + // the empty list = [ 0xc0 ] + let empty_list: Vec = vec![0xc0]; + let input_bytes: Vec = Vec::from_hex("f8408d123000000000000000000000028824232222222222238b32222222222222222412528a04233333333333332322912323333333333333333333333333333333000000").unwrap(); + + for mut test_input in [cat_dog, empty_list, input_bytes] { + test_input.resize(69, 0); + let circuit = rlp_list_circuit::( + CircuitBuilderStage::Mock, + test_input, + &[15, 9, 11, 10, 17], + true, + None, + None, + ); + MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied(); + } +} + +#[test] +pub fn prank_test_mock_rlp_array() { + let k = DEGREE; + // the list [ "cat", "dog" ] = [ 0xc8, 0x83, 'c', 'a', 't', 0x83, 'd', 'o', 'g' ] + let cat_dog: Vec = vec![0xc8, 0x83, b'c', b'a', b't', 0x83, b'd', b'o', b'g']; + // the empty list = [ 0xc0 ] + let empty_list: Vec = vec![0xc0]; + let input_bytes: Vec = Vec::from_hex("f8408d123000000000000000000000028824232222222222238b32222222222222222412528a04233333333333332322912323333333333333333333333333333333000000").unwrap(); + + for mut test_input in [cat_dog, empty_list, input_bytes] { + let prank_lens = [test_input.len(), 3, test_input.len()]; + for (j, prank_len) in prank_lens.into_iter().enumerate() { + test_input.resize(69, 0); + for i in 0..prank_len { + let circuit = rlp_list_circuit::( + CircuitBuilderStage::Mock, + test_input.clone(), + &[15, 9, 11, 10, 17], + true, + Some(i), + Some(j), + ); + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert!(prover.verify().is_err(), "Unconstrained at {i}. Should not have verified"); + } + } + } +} + +#[test] +fn test_list_len_not_big_fail() { + let k = DEGREE; + // the list [ "cat", "dog" ] = [ 0xc8, 0x83, 'c', 'a', 't', 0x83, 'd', 'o', 'g' ] + // try to do RLP encoding where length is_big = true (even though it's not) + // 0x08 is the length of the payload + let mut attack: Vec = vec![0xf8, 0x08, 0x83, b'c', b'a', b't', 0x83, b'd', b'o', b'g']; + attack.resize(69, 0); + let circuit = rlp_list_circuit::( + CircuitBuilderStage::Mock, + attack, + &[15, 9, 11, 10, 17], + true, + None, + None, + ); + assert!(MockProver::run(k, &circuit, vec![]).unwrap().verify().is_err()); +} + +#[test] +fn test_list_len_leading_zeros_fail() { + let k = DEGREE; + // original: + // let input_bytes: Vec = Vec::from_hex("f8408d123000000000000000000000028824232222222222238b32222222222222222412528a04233333333333332322912323333333333333333333333333333333000000").unwrap(); + let mut attack: Vec = Vec::from_hex("f900408d123000000000000000000000028824232222222222238b32222222222222222412528a04233333333333332322912323333333333333333333333333333333000000").unwrap(); + attack.resize(310, 0); + let circuit = rlp_list_circuit::( + CircuitBuilderStage::Mock, + attack, + &[256, 9, 11, 10, 17], // make max payload length > 256 so we allow 2 bytes for len_len + true, + None, + None, + ); + assert!(MockProver::run(k, &circuit, vec![]).unwrap().verify().is_err()); +} diff --git a/axiom-eth/src/rlp/tests/mod.rs b/axiom-eth/src/rlp/tests/mod.rs new file mode 100644 index 00000000..43d1cb1e --- /dev/null +++ b/axiom-eth/src/rlp/tests/mod.rs @@ -0,0 +1,24 @@ +use std::marker::PhantomData; + +use crate::rlc::{ + chip::RlcChip, + circuit::{builder::RlcCircuitBuilder, instructions::RlcCircuitInstructions}, + tests::utils::executor::{RlcCircuit, RlcExecutor}, +}; + +use halo2_base::{ + gates::{circuit::CircuitBuilderStage, GateInstructions, RangeChip, RangeInstructions}, + utils::ScalarField, + QuantumCell::Constant, +}; + +use halo2_base::halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; +use hex::FromHex; + +use super::{max_rlp_encoding_len, types::RlpArrayWitness, RlpChip}; + +mod combo; +mod list; +mod string; + +const DEGREE: u32 = 10; diff --git a/axiom-eth/src/rlp/tests/string.rs b/axiom-eth/src/rlp/tests/string.rs new file mode 100644 index 00000000..07b87825 --- /dev/null +++ b/axiom-eth/src/rlp/tests/string.rs @@ -0,0 +1,122 @@ +use crate::rlp::types::RlpFieldWitness; + +use super::*; +use test_case::test_case; + +// Both positive and negative tests for RLP decoding a byte string +struct RlpStringTest { + encoded: Vec, + max_len: usize, + prank_idx: Option, + prank_component: Option, + _marker: PhantomData, +} + +impl RlcCircuitInstructions for RlpStringTest { + type FirstPhasePayload = RlpFieldWitness; + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + ) -> Self::FirstPhasePayload { + let ctx = builder.base.main(0); + let inputs = ctx.assign_witnesses(self.encoded.iter().map(|x| F::from(*x as u64))); + let chip = RlpChip::new(range, None); + let mut witness = chip.decompose_rlp_field_phase0(ctx, inputs, self.max_len); + // pranking for negative tests + if let Some(prank_component) = self.prank_component { + let (prank_comp, mut prank_len) = match prank_component { + 0 => (&mut witness.encoded_item, &witness.encoded_item_len), + 1 => (&mut witness.len_cells, &witness.len_len), + 2 => (&mut witness.field_cells, &witness.field_len), + _ => (&mut witness.field_cells, &witness.field_len), + }; + if let Some(prank_idx) = self.prank_idx { + if prank_component < 3 { + if prank_idx < prank_comp.len() { + let prankval = *prank_comp[prank_idx].value() + F::ONE; + prank_comp[prank_idx].debug_prank(ctx, prankval); + } + } else { + prank_len = &witness.prefix_len; + if prank_idx == 0 { + let prankval = *prank_comp[prank_idx].value() + F::ONE; + witness.prefix.debug_prank(ctx, prankval); + } + } + let bad_idx = range.is_less_than_safe(ctx, *prank_len, prank_idx as u64 + 1); + println!("{:?}", *prank_len); + println!("{:?}", prank_idx); + let zero = ctx.load_zero(); + ctx.constrain_equal(&bad_idx, &zero); + } + } + witness + } + fn virtual_assign_phase1( + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + rlc: &RlcChip, + witness: Self::FirstPhasePayload, + ) { + let chip = RlpChip::new(range, Some(rlc)); + let (ctx_gate, ctx_rlc) = builder.rlc_ctx_pair(); + chip.decompose_rlp_field_phase1((ctx_gate, ctx_rlc), witness); + } +} + +fn rlp_string_circuit( + stage: CircuitBuilderStage, + encoded: Vec, + max_len: usize, + prank_idx: Option, + prank_component: Option, +) -> RlcCircuit> { + let input = + RlpStringTest { encoded, max_len, prank_idx, prank_component, _marker: PhantomData }; + let mut builder = RlcCircuitBuilder::from_stage(stage, 6).use_k(DEGREE as usize); + builder.base.set_lookup_bits(8); + let circuit = RlcExecutor::new(builder, input); + // auto-configure circuit if not in prover mode for convenience + if !stage.witness_gen_only() { + circuit.0.calculate_params(Some(9)); + } + circuit +} + +#[test_case(Vec::from_hex("a012341234123412341234123412341234123412341234123412341234123412340000").unwrap(), 34; "default")] +#[test_case(vec![127], 34; "short")] +#[test_case(vec![0], 32; "literal")] +#[test_case(Vec::from_hex("a09bdb004d9b1e7f3e5f86fbdc9856f21f9dcb07a44c42f5de8eec178514d279df0000000000000000000000000000000000000000000000000000000000").unwrap(), 60; "long")] +#[test_case(Vec::from_hex("b83adb004d9b1e7f3e5f86fbdc9856f21f9dcb07a44c42f5de8eec178514d279df0000000000000000000000000000000000000000000000000000000000").unwrap(), 60; "long long")] +pub fn test_mock_rlp_string(mut input_bytes: Vec, max_len: usize) { + let k = DEGREE; + input_bytes.resize(max_rlp_encoding_len(max_len), 0u8); + let circuit = + rlp_string_circuit::(CircuitBuilderStage::Mock, input_bytes, max_len, None, None); + MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied(); +} + +#[test_case(Vec::from_hex("a012341234123412341234123412341234123412341234123412341234123412340000").unwrap(), 34; "default")] +#[test_case(vec![127], 34; "short")] +#[test_case(vec![0], 32; "literal")] +#[test_case(Vec::from_hex("a09bdb004d9b1e7f3e5f86fbdc9856f21f9dcb07a44c42f5de8eec178514d279df0000000000000000000000000000000000000000000000000000000000").unwrap(), 60; "long")] +#[test_case(Vec::from_hex("b83adb004d9b1e7f3e5f86fbdc9856f21f9dcb07a44c42f5de8eec178514d279df0000000000000000000000000000000000000000000000000000000000").unwrap(), 60; "long long")] +pub fn prank_test_mock_rlp_string(mut input_bytes: Vec, max_len: usize) { + let k = DEGREE; + input_bytes.resize(max_rlp_encoding_len(max_len), 0u8); + let prank_lens = [input_bytes.len(), 3, input_bytes.len(), 1]; + for (j, prank_len) in prank_lens.into_iter().enumerate() { + for i in 0..prank_len { + let circuit = rlp_string_circuit::( + CircuitBuilderStage::Mock, + input_bytes.clone(), + max_len, + Some(i), + Some(j), + ); + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert!(prover.verify().is_err(), "Unconstrained at {i}, should not have verified",); + } + } +} diff --git a/axiom-eth/src/rlp/types.rs b/axiom-eth/src/rlp/types.rs new file mode 100644 index 00000000..de2756fc --- /dev/null +++ b/axiom-eth/src/rlp/types.rs @@ -0,0 +1,90 @@ +use halo2_base::{utils::ScalarField, AssignedValue}; + +use crate::rlc::types::RlcTrace; + +#[derive(Clone, Debug)] +pub struct RlpFieldPrefixParsed { + pub is_not_literal: AssignedValue, + pub is_big: AssignedValue, + + pub next_len: AssignedValue, + pub len_len: AssignedValue, +} + +#[derive(Clone, Debug)] +pub struct RlpArrayPrefixParsed { + // is_empty: AssignedValue, + pub is_big: AssignedValue, + + pub next_len: AssignedValue, + pub len_len: AssignedValue, +} + +#[derive(Clone, Debug)] +pub struct RlpPrefixParsed { + pub is_not_literal: AssignedValue, + pub is_big: AssignedValue, + + pub next_len: AssignedValue, + pub len_len: AssignedValue, +} + +/// All witnesses involved in the RLP decoding of (prefix + length + payload) +/// where payload can be either a byte string or a list. +#[derive(Clone, Debug)] +pub struct RlpFieldWitness { + // The RLP encoding is decomposed into: prefix, length, payload + pub prefix: AssignedValue, // value of the prefix + pub prefix_len: AssignedValue, + pub len_len: AssignedValue, + pub len_cells: Vec>, + + /// The byte length of the payload bytes (decoded byte string) + pub field_len: AssignedValue, + /// The payload (decoded byte string) + pub field_cells: Vec>, + pub max_field_len: usize, + + /// This is the original raw RLP encoded byte string, padded with zeros to a known fixed maximum length + pub encoded_item: Vec>, + /// This is variable length of `encoded_item` in bytes + pub encoded_item_len: AssignedValue, +} + +#[derive(Clone, Debug)] +/// The outputed values after RLP decoding of (prefix + length + payload) in SecondPhase. +/// This contains RLC of substrings from the decomposition. Payload can be either a byte string or a list. +pub struct RlpFieldTrace { + pub prefix: AssignedValue, // value of the prefix + pub prefix_len: AssignedValue, + pub len_trace: RlcTrace, + pub field_trace: RlcTrace, + // to save memory maybe we don't need this + /// This is the rlc of the full rlp (prefix + len + payload) + pub rlp_trace: RlcTrace, +} + +#[derive(Clone, Debug)] +pub struct RlpArrayWitness { + pub field_witness: Vec>, + + pub len_len: AssignedValue, + pub len_cells: Vec>, + + /// Length of the full RLP encoding (bytes) of the array + pub rlp_len: AssignedValue, + /// The original raw RLP encoded array, padded with zeros to a known fixed maximum length + pub rlp_array: Vec>, + + /// The length of the array/list this is an encoding of. Only stored if + /// this is the encoding of a variable length array. + pub list_len: Option>, +} + +#[derive(Clone, Debug)] +pub struct RlpArrayTrace { + pub len_trace: RlcTrace, + pub field_trace: Vec>, + // to save memory we don't need this + // pub array_trace: RlcTrace, +} diff --git a/axiom-eth/src/sha256/mod.rs b/axiom-eth/src/sha256/mod.rs new file mode 100644 index 00000000..718f8c40 --- /dev/null +++ b/axiom-eth/src/sha256/mod.rs @@ -0,0 +1,226 @@ +//! PLACEHOLDER SHA-256 CHIP +use crate::Field; +use core::iter::once; +use halo2_base::{ + gates::{GateInstructions, RangeChip, RangeInstructions}, + utils::bit_length, + AssignedValue, Context, + QuantumCell::Constant, +}; +use itertools::Itertools; +use sha2::{Digest, Sha256}; + +use crate::keccak::get_bytes; + +pub(crate) const NUM_BYTES_TO_SQUEEZE: usize = 32; + +#[derive(Clone, Debug)] +pub struct Sha256FixedLenQuery { + pub input_bytes: Vec, + pub input_assigned: Vec>, + + pub output_bytes: [u8; NUM_BYTES_TO_SQUEEZE], + pub output_assigned: Vec>, +} + +#[derive(Clone, Debug)] +pub struct Sha256VarLenQuery { + pub min_bytes: usize, + pub max_bytes: usize, + pub num_bytes: usize, + // if `length` is `None`, then this is a fixed length sha256 query + // and it is assumed `min_bytes = max_bytes` + pub length: AssignedValue, + pub input_bytes: Vec, + pub input_assigned: Vec>, + + pub output_bytes: [u8; NUM_BYTES_TO_SQUEEZE], + pub output_assigned: Vec>, +} + +// TODO: this should be analogous to KeccakChip, but we should probably make a more general trait to reduce dup code +#[derive(Clone, Debug)] +pub struct Sha256Chip<'r, F: Field> { + pub range: &'r RangeChip, +} + +pub fn sha256>(bytes: T) -> [u8; NUM_BYTES_TO_SQUEEZE] { + let mut hasher = Sha256::new(); + hasher.update(bytes.as_ref()); + let output = hasher.finalize(); + output.into() +} + +impl<'r, F: Field> Sha256Chip<'r, F> { + pub fn new(range: &'r RangeChip) -> Self { + Self { range } + } + + /// Takes a byte vector of known fixed length and computes the sha256 digest of `bytes`. + /// - Returns `(output_assigned, output_bytes)`, where `output_bytes` is provided just for convenience. + /// - This function only computes witnesses for output bytes. + /// The guarantee is that in `SecondPhase`, `input_assigned` and `output_assigned` + /// will have their RLCs computed and these RLCs will be constrained to equal the + /// correct ones in the sha256 table. + /// + /// Assumes that `input_bytes` coincides with the values of `bytes_assigned` as bytes, + /// if provided (`bytes` is used for faster witness generation). + /// + /// Returns the index in `self.fixed_len_queries` of the query. + pub fn sha256_fixed_len( + &self, + ctx: &mut Context, + input_assigned: Vec>, + ) -> Sha256FixedLenQuery { + let bytes = get_bytes(&input_assigned[..]); + + let output_bytes = sha256(&bytes); + let output_assigned = ctx.assign_witnesses(output_bytes.iter().map(|b| F::from(*b as u64))); + + Sha256FixedLenQuery { input_bytes: bytes, input_assigned, output_bytes, output_assigned } + } + + /// Takes a fixed length byte vector and computes the sha256 digest of `bytes[..len]`. + /// - Returns `(output_assigned, output_bytes)`, where `output_bytes` is provided just for convenience. + /// - This function only computes witnesses for output bytes. + /// The guarantee is that in `SecondPhase`, `input_assigned` and `output_assigned` + /// will have their RLCs computed and these RLCs will be constrained to equal the + /// correct ones in the sha256 table. + /// + /// Assumes that `input_bytes[..len]` coincides with the values of `input_assigned[..len]` as bytes, if provided (`bytes` is used for faster witness generation). + /// + /// Constrains `min_len <= len <= bytes.len()`. + /// + /// Returns the index in `self.var_len_queries` of the query. + pub fn sha256_var_len( + &self, + ctx: &mut Context, + input_assigned: Vec>, + len: AssignedValue, + min_len: usize, + ) -> Sha256VarLenQuery { + let bytes = get_bytes(&input_assigned[..]); + let max_len = input_assigned.len(); + + let range = &self.range; + range.check_less_than_safe(ctx, len, (max_len + 1) as u64); + if min_len != 0 { + range.check_less_than( + ctx, + Constant(F::from((min_len - 1) as u64)), + len, + bit_length((max_len + 1) as u64), + ); + } + let num_bytes = len.value().get_lower_64() as usize; + debug_assert!(bytes.len() >= num_bytes); + let output_bytes = sha256(&bytes[..num_bytes]); + let output_assigned = ctx.assign_witnesses(output_bytes.iter().map(|b| F::from(*b as u64))); + + Sha256VarLenQuery { + min_bytes: min_len, + max_bytes: max_len, + num_bytes, + length: len, + input_bytes: bytes, + input_assigned, + output_bytes, + output_assigned, + } + } + + /// Computes the sha256 merkle root of a tree with leaves `leaves`. + /// + /// 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 `sha256_fixed_len` to perform the hashes. + /// + /// # 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, + 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].as_ref().to_vec(); + } + + // bottom layer hashes + let mut hashes = leaves + .chunks(2) + .map(|pair| { + let leaves_concat = [pair[0].as_ref(), pair[1].as_ref()].concat(); + self.sha256_fixed_len(ctx, leaves_concat) + }) + .collect_vec(); + debug_assert_eq!(hashes.len(), 1 << (depth - 1)); + for d in (0..depth - 1).rev() { + for i in 0..(1 << d) { + let leaves_concat = + [2 * i, 2 * i + 1].map(|idx| &hashes[idx].output_assigned[..]).concat(); + hashes[i] = self.sha256_fixed_len(ctx, leaves_concat); + } + } + hashes[0].output_assigned.clone() + } + + /// Computes a sha256 merkle mountain range of a tree with leaves `leaves`. + /// + /// Assumptions: + /// - Each element of `leaves` is a slice of assigned byte values of fixed length `NUM_BYTES_TO_SQUEEZE = 32`. + /// - `num_leaves_bits` is the little endian bit representation of `num_leaves` + /// - `leaves.len()` is a power of two (i.e., we have a full binary tree), but `leaves[num_leaves..]` can be arbitrary dummy leaves. + /// - The byte length of each element of `leaves` is known and fixed, i.e., we use `sha256_fixed_len` to perform the hashes. + /// + /// Returns the merkle mountain range associated with `leaves[..num_leaves]` + /// as a length `log_2(leaves.len()) + 1` vector of byte arrays. + /// The mountain range is ordered with the largest mountain first. For example, if `num_leaves = leaves.len()` then the first mountain is the merkle root of the full tree. + /// For `i` where `(num_leaves >> i) & 1 == 0`, the value of the corresponding peak should be considered UNDEFINED. + /// + /// The merkle root of the tree with leaves `leaves[..num_leaves]` can be recovered by successively hashing the elements in the merkle mountain range, in reverse order, corresponding to indices + /// where `num_leaves` has a 1 bit. + pub fn merkle_mountain_range( + &mut self, + ctx: &mut Context, + leaves: &[Vec>], + num_leaves_bits: &[AssignedValue], + ) -> Vec>> { + let max_depth = leaves.len().ilog2() as usize; + assert_eq!(leaves.len(), 1 << max_depth); + assert_eq!(num_leaves_bits.len(), max_depth + 1); + + // start_idx[i] = (num_leaves >> i) << i + // below we will want to select `leaves[start_idx[depth+1]..start_idx[depth+1] + 2^depth] for depth = max_depth - 1, ..., 0 + // we do this with a barrel-shifter, by shifting `leaves` left by 2^i or 0 depending on the bit in `num_leaves_bits` + // we skip the first shift by 2^max_depth because if num_leaves == 2^max_depth then all these subsequent peaks are undefined + let mut shift_leaves = leaves.to_vec(); + once(self.merkle_tree_root(ctx, leaves)) + .chain(num_leaves_bits.iter().enumerate().rev().skip(1).map(|(depth, &sel)| { + let peak = self.merkle_tree_root(ctx, &shift_leaves[..(1usize << depth)]); + // no need to shift if we're at the end + if depth != 0 { + // shift left by sel == 1 ? 2^depth : 0 + for i in 0..1 << depth { + debug_assert_eq!(shift_leaves[i].len(), NUM_BYTES_TO_SQUEEZE); + for j in 0..shift_leaves[i].len() { + shift_leaves[i][j] = self.range.gate.select( + ctx, + shift_leaves[i + (1 << depth)][j], + shift_leaves[i][j], + sel, + ); + } + } + } + + peak + })) + .collect() + } +} diff --git a/axiom-eth/src/solidity/array_types.rs b/axiom-eth/src/solidity/array_types.rs new file mode 100644 index 00000000..49d681cb --- /dev/null +++ b/axiom-eth/src/solidity/array_types.rs @@ -0,0 +1,59 @@ +use halo2_base::{safe_types::SafeBytes32, AssignedValue, Context}; + +use crate::Field; + +use super::{add_to_slot, types::SolidityStoragePosition, SolidityChip}; + +// Note: storage arrays could be larger than this +pub const MAX_ARRAY_LEN_LOG2: u32 = 64; +pub const MAX_ARRAY_LEN: u128 = 2u128.pow(MAX_ARRAY_LEN_LOG2); + +impl<'chip, F: Field> SolidityChip<'chip, F> { + /// Performs witness generation within phase0 to constrain the computation of a Solidity array beginning at evm storage slot `beginning_slot`. + /// + /// * `ctx`: Circuit [Context] to assign witnesses to. + /// * `range: [RangeChip] to constrain the division and modulo operations. + /// * `item_bit_size`: [AssignedValue] representing the size of the array items in bits. + /// * `idx`: [AssignedValue] representing the index within the array. + /// * `beginning_slot`: [SafeBytes32] representing the beginning of the evm storage slot of the array. + /// + /// Returns: + /// * [SolidityStoragePosition] that is the slot where our desired item begins and the byte offset. + pub fn slot_for_array_idx( + &self, + ctx: &mut Context, + idx: AssignedValue, + item_byte_size: AssignedValue, + beginning_slot: &SafeBytes32, + ) -> SolidityStoragePosition { + let (offset, pos) = self.slot_and_offset(ctx, idx, item_byte_size); + + let res_bytes = add_to_slot(ctx, self.range(), beginning_slot, offset); + SolidityStoragePosition { slot: res_bytes, byte_offset: pos, item_byte_len: item_byte_size } + } + + /// Performs witness generation within phase0 to constrain the computation of a Solidity dynamic array located at evm storage slot `array_slot`. + /// DOES NOT CHECK IF IDX IS OUT OF BOUNDS. + /// + /// * `ctx`: Circuit [Context] to assign witnesses to. + /// * `range: [RangeChip] to constrain the division and modulo operations. + /// * `keccak`: [KeccakChip] to constrain the computation of the Solidity dynamic array. + /// * `array_slot`: [SafeBytes32] representing the evm storage slot of the dynamic array. + /// * `item_bit_size`: [AssignedValue] representing the size of the array items in bits. + /// * `idx`: [AssignedValue] representing the index within the array. + /// + /// Returns: + /// * (Vec<[AssignedValue]>, [AssignedValue]) that is the slot where our desired item begins with the byte offset of the struct. + pub fn slot_for_dynamic_array_idx_unchecked( + &self, + ctx: &mut Context, + array_slot: &SafeBytes32, + item_byte_size: AssignedValue, + idx: AssignedValue, + ) -> SolidityStoragePosition { + // Find slot where data begins + let hash_query = self.keccak().keccak_fixed_len(ctx, array_slot.value().to_vec()); + // Find slot for the idx + self.slot_for_array_idx(ctx, idx, item_byte_size, &hash_query.output_bytes) + } +} diff --git a/axiom-eth/src/solidity/mod.rs b/axiom-eth/src/solidity/mod.rs new file mode 100644 index 00000000..571241d2 --- /dev/null +++ b/axiom-eth/src/solidity/mod.rs @@ -0,0 +1,366 @@ +use crate::utils::uint_to_bytes_be; +use crate::Field; +use crate::{ + keccak::KeccakChip, + mpt::{MPTChip, MPTProof}, + rlc::{ + chip::RlcChip, + circuit::builder::RlcContextPair, + concat_array::{concat_var_fixed_array_phase0, concat_var_fixed_array_phase1}, + types::{AssignedVarLenVec, ConcatVarFixedArrayWitness}, + }, + rlp::RlpChip, + solidity::types::VarMappingTrace, + storage::{EthStorageChip, EthStorageTrace, EthStorageWitness}, +}; +use halo2_base::{ + gates::{GateChip, GateInstructions, RangeChip, RangeInstructions}, + safe_types::{SafeBytes32, SafeTypeChip, VarLenBytesVec}, + AssignedValue, Context, + QuantumCell::Constant, +}; +use itertools::Itertools; + +use self::types::{ + MappingTrace, MappingWitness, NestedMappingWitness, SolidityType, VarMappingWitness, +}; + +pub mod array_types; +#[cfg(all(test, feature = "providers"))] +pub mod tests; +pub mod types; + +/// Trait which implements functions to prove statements about Solidity operations over Ethereum storage. +/// +/// Supports constraining: +/// * Mappings for keys of fixed length (Value) solidity types: +/// * `keccak256(left_pad_32(key) . mapping_slot)` +/// * Mappings for keys of variable length (NonValue) solidity types: +/// * `keccak256(key . mapping_slot)` +/// * Nested Mappings +/// * Spec for double nested mapping: +/// * Fixed Length: +/// * `keccak256(left_pad_32(key_2) . keccak256(left_pad_32(key_1) . mapping_slot))` +/// * Variable Length: +/// * `keccak256(key_2 . keccak256(key_1 . mapping_slot))` +/// * Proving the inclusion of a value at the storage slot of the constrained mapping or nested mapping. +pub struct SolidityChip<'chip, F: Field> { + pub mpt: &'chip MPTChip<'chip, F>, + pub max_nesting: usize, + pub max_key_byte_len: usize, // currently not used +} + +impl<'chip, F: Field> SolidityChip<'chip, F> { + pub fn new(mpt: &'chip MPTChip<'chip, F>, max_nesting: usize, max_key_byte_len: usize) -> Self { + Self { mpt, max_nesting, max_key_byte_len } + } + + pub fn gate(&self) -> &GateChip { + self.mpt.gate() + } + + pub fn range(&self) -> &RangeChip { + self.mpt.range() + } + + /// SafeTypeChip + pub fn safe(&self) -> SafeTypeChip { + SafeTypeChip::new(self.range()) + } + + pub fn rlc(&self) -> &RlcChip { + self.mpt.rlc() + } + + pub fn rlp(&self) -> RlpChip { + self.mpt.rlp() + } + + pub fn keccak(&self) -> &KeccakChip { + self.mpt.keccak() + } + + /// Computes the storage slot of a solidity mapping at a fixed length (Value type) key by: + /// ```ignore + /// keccak256(left_pad_32(key) . mapping_slot) + /// ``` + /// + /// Inputs: + /// - `mapping_slot`: [SafeBytes32] representing the slot of the Solidity mapping itself. + /// - `key`: [SafeBytes32] representing the key of the Solidity mapping. + /// + /// Returns: + /// * The storage slot of the mapping key in question + pub fn slot_for_mapping_value_key( + &self, + ctx: &mut Context, + mapping_slot: &SafeBytes32, + key: &SafeBytes32, + ) -> SafeBytes32 { + let key_slot_concat = [key.value(), mapping_slot.value()].concat(); + debug_assert_eq!(key_slot_concat.len(), 64); + + let hash_query = self.keccak().keccak_fixed_len(ctx, key_slot_concat); + hash_query.output_bytes + } + + /// Performs witness generation within phase0 to compute the storage slot of a Solidity mapping at a variable length (non-value type) key. + /// The storage slot corresponding to `key` is computed according to https://docs.soliditylang.org/en/v0.8.19/internals/layout_in_storage.html#mappings-and-dynamic-arrays + /// ```ignore + /// keccak256(key . mapping_slot) + /// ``` + /// This is a variable length concatenation, so we need to use RLC in two phases. + /// + /// Inputs: + /// - `mapping_slot`: [SafeBytes32] representing the evm storage slot of the mapping. + /// - `key`: [VarLenBytesVec] representing the key of the mapping. + /// + /// Returns: + /// - [VarMappingWitness] representing the witness of the computation of the Solidity mapping. + pub fn slot_for_mapping_nonvalue_key_phase0( + &self, + ctx: &mut Context, + mapping_slot: SafeBytes32, + key: VarLenBytesVec, + ) -> VarMappingWitness { + let key_values = key.bytes().iter().map(|b| *b.as_ref()).collect(); + let prefix = AssignedVarLenVec { values: key_values, len: *key.len() }; + let suffix = mapping_slot.value().to_vec(); + let concat_witness = concat_var_fixed_array_phase0(ctx, self.gate(), prefix, suffix); + let concat_values = concat_witness.concat.values; + let concat_len = concat_witness.concat.len; + + let hash_query = self.keccak().keccak_var_len(ctx, concat_values, concat_len, 32); + VarMappingWitness { mapping_slot, key, hash_query } + } + + /// Performs rlc concatenation within phase1 to constrain the computation of a Solidity `mapping(key => value)`. + /// + /// * `ctx`: Circuit [Context] to assign witnesses to. + /// * `witness`: [VarMappingWitness] representing the witness of the computation of the Solidity mapping. + /// + /// Returns: + /// * [VarMappingTrace] trace of the rlc computation to constrain the computation of the Solidity mapping. + pub fn slot_for_mapping_nonvalue_key_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + witness: VarMappingWitness, + ) -> VarMappingTrace { + let VarMappingWitness { mapping_slot, key, hash_query } = witness; + // constrain the concatenation + let key_values = key.bytes().iter().map(|b| *b.as_ref()).collect(); + let prefix = AssignedVarLenVec { values: key_values, len: *key.len() }; + let suffix = mapping_slot.value().to_vec(); + let concat = + AssignedVarLenVec { values: hash_query.input_assigned.clone(), len: hash_query.length }; + let witness = ConcatVarFixedArrayWitness { prefix, suffix, concat }; + + concat_var_fixed_array_phase1((ctx_gate, ctx_rlc), self.gate(), self.rlc(), witness) + } + + /// Performs witness generation within phase0 to compute the storage slot of a Solidity mapping at a key of either value or non-value type. + /// The storage slot corresponding to `key` is computed according to https://docs.soliditylang.org/en/v0.8.19/internals/layout_in_storage.html#mappings-and-dynamic-arrays + /// + /// # Assumptions + /// - The type of the key is known at compile time + pub fn slot_for_mapping_key_phase0( + &self, + ctx: &mut Context, + mapping_slot: SafeBytes32, + key: SolidityType, + ) -> MappingWitness { + match key { + SolidityType::Value(key) => { + MappingWitness::Value(self.slot_for_mapping_value_key(ctx, &mapping_slot, &key)) + } + SolidityType::NonValue(key) => MappingWitness::NonValue( + self.slot_for_mapping_nonvalue_key_phase0(ctx, mapping_slot, key), + ), + } + } + + /// Performs rlc concatenation within phase1 to constrain the computation of a Solidity `mapping(key => value)`. + /// Does nothing if mapping key was of Value type. + /// + /// * `ctx`: Circuit [Context] to assign witnesses to. + /// * `witness`: [MappingWitness] representing the witness of the computation of the Solidity mapping. + /// + /// Returns: + /// * [MappingTrace] trace of the rlc computation to constrain the computation of the Solidity mapping. + pub fn slot_for_mapping_key_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + witness: MappingWitness, + ) -> MappingTrace { + match witness { + MappingWitness::Value(_) => None, + MappingWitness::NonValue(witness) => { + Some(self.slot_for_mapping_nonvalue_key_phase1((ctx_gate, ctx_rlc), witness)) + } + } + } + + /// Performs witness generation within phase0 to constrain the computation of a nested Solidity mapping. + /// Supports variable number of nestings, up to `MAX_NESTING`. + /// + /// Inputs: + /// - `keccak`: [KeccakChip] to constrain the computation of the Solidity mapping. + /// - `mappings`: (Vec<[SolidityType]>, [SafeType]) representing the successive keys of the nested mapping in order paired with the mapping slot. + /// - `nestings`: Specifies the amount of nesting (as this function supports variable nesting). + /// + /// Returns: + /// - [NestedMappingWitness] representing the witnesses to constrain the computation of the nested mapping, the desired mapping_slot, and the number of nestings. + /// - Will return `bytes32(0)` if `nestings` is 0. + /// + /// # Assumptions + /// - The type (Value vs Non-Value) of each key is known at compile time. + /// - `keys` is padded to `MAX_NESTING` length + pub fn slot_for_nested_mapping_phase0( + &self, + ctx: &mut Context, + mapping_slot: SafeBytes32, + keys: [SolidityType; MAX_NESTING], + nestings: AssignedValue, + ) -> NestedMappingWitness { + self.range().check_less_than_safe(ctx, nestings, MAX_NESTING as u64 + 1); + let mut witness = Vec::with_capacity(MAX_NESTING); + // slots[i] is the storage slot corresponding to keys[i] + let mut slots = Vec::with_capacity(MAX_NESTING); + for key in keys { + let m_slot = slots.last().unwrap_or(&mapping_slot).clone(); + let w = self.slot_for_mapping_key_phase0(ctx, m_slot.clone(), key); + slots.push(w.slot()); + witness.push(w); + } + let slot = if MAX_NESTING == 1 { + // if only 1 nesting, we ignore `nestings` and just return the slot assuming `nestings` is 1 + slots.pop().unwrap() + } else { + let nestings_minus_one = self.gate().sub(ctx, nestings, Constant(F::ONE)); + let indicator = self.gate().idx_to_indicator(ctx, nestings_minus_one, MAX_NESTING); + let slot = self.gate().select_array_by_indicator(ctx, &slots, &indicator); + SafeTypeChip::unsafe_to_safe_type(slot) + }; + NestedMappingWitness { witness, slot, nestings } + } + + /// Performs rlc concatenation within phase1 to constrain the computation of a Solidity nested mapping. + /// + /// * `ctx`: Circuit [Context] to assign witnesses to. + /// * `witnesses`: Vec<[MappingWitness]> representing the witnesses of the computation of the Solidity mapping that will be constrained. + /// + /// Returns: + /// * `Vec<[MappingTrace]>` containing the traces of the rlc computation to constrain the computation of the Solidity mapping. + pub fn slot_for_nested_mapping_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + witness: NestedMappingWitness, + ) -> Vec> { + witness + .witness + .into_iter() + .map(|w| self.slot_for_mapping_key_phase1((ctx_gate, ctx_rlc), w)) + .collect_vec() + } + + /// Combines `EthStorageChip::parse_storage_proof_phase0` with `slot_for_nested_mapping_phase0` to compute the storage slot of a nested Solidity mapping and a storage proof of its inclusion within the storage trie. + /// + /// Inputs: + /// - `proof` should be the MPT for the storage slot of the nested mapping, which must match the output of `slot_for_nested_mapping_phase0`. + pub fn verify_mapping_storage_phase0( + &self, + ctx: &mut Context, + mapping_slot: SafeBytes32, + keys: [SolidityType; MAX_NESTING], + nestings: AssignedValue, + proof: MPTProof, + ) -> (NestedMappingWitness, EthStorageWitness) { + let mapping_witness = + self.slot_for_nested_mapping_phase0(ctx, mapping_slot, keys, nestings); + let storage_witness = { + let storage_chip = EthStorageChip::new(self.mpt, None); + storage_chip.parse_storage_proof_phase0(ctx, mapping_witness.slot.clone(), proof) + }; + (mapping_witness, storage_witness) + } + + /// Combines `EthStorageChip::parse_storage_proof_phase1` with `slot_for_nested_mapping_phase1` to compute the slot of a nested Solidity mapping and a storage proof of its inclusion within the storage trie. + pub fn verify_mapping_storage_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + mapping_witness: NestedMappingWitness, + storage_witness: EthStorageWitness, + ) -> (Vec>, EthStorageTrace) { + let mapping_trace = + self.slot_for_nested_mapping_phase1((ctx_gate, ctx_rlc), mapping_witness); + let storage_trace = { + let storage_chip = EthStorageChip::new(self.mpt, None); + storage_chip.parse_storage_proof_phase1((ctx_gate, ctx_rlc), storage_witness) + }; + (mapping_trace, storage_trace) + } + + /// Returns the (slot_offset, pos) where slot_offset is the offset to add to the slot, and then `pos` is the byte offset within the slot that the `id`th item is stored at, + /// if we are packing multiple items each of `item_byte_size` bytes contiguously. + /// - `id` is the index of the item to access. Assumed to be 128 bits or less (but usually much smaller). + /// - `item_byte_size` is the number of bytes in each item. This is used to determine packing. + /// + /// ## Assumptions + /// - `item_byte_size` is not zero. + /// - `item_byte_size` is 128 bits or less (but usually much smaller). + /// - `id` is 128 bits or less (but usually much smaller). + fn slot_and_offset( + &self, + ctx: &mut Context, + id: AssignedValue, + item_byte_size: AssignedValue, + ) -> (AssignedValue, AssignedValue) { + let thirty_two = ctx.load_constant(F::from(32)); + let is_small_item = self.range().is_less_than(ctx, item_byte_size, thirty_two, 129); + + let small_item_size = + self.gate().select(ctx, item_byte_size, Constant(F::from(32)), is_small_item); + let (items_per_slot, _) = + self.range().div_mod_var(ctx, Constant(F::from(32)), small_item_size, 6, 6); + let mut small_item_offset = self.range().div_mod_var(ctx, id, items_per_slot, 129, 129); + small_item_offset.1 = self.range().gate().mul(ctx, small_item_offset.1, small_item_size); + let (mut slots_per_item, rem) = self.range().div_mod(ctx, item_byte_size, 32_usize, 129); + let round_up = self.range().gate().is_zero(ctx, rem); + let round_up = self.range().gate().not(ctx, round_up); + slots_per_item = self.range().gate().add(ctx, slots_per_item, round_up); + let big_item_slot = self.range().gate().mul(ctx, id, slots_per_item); + let zero = ctx.load_zero(); + + let slot = self.gate().select(ctx, small_item_offset.0, big_item_slot, is_small_item); + let pos = self.gate().select(ctx, small_item_offset.1, zero, is_small_item); + (slot, pos) + } +} + +/// Find the slot that results from adding some offset to another slot. +/// - `slot` is bytes32 +/// - `add` is uint256 that doesn't overflow `F`. +fn add_to_slot( + ctx: &mut Context, + range: &RangeChip, + slot: &SafeBytes32, + add: AssignedValue, +) -> SafeBytes32 { + let add_bytes = uint_to_bytes_be(ctx, range, &add, 32); + let zero = ctx.load_zero(); + let mut carry = zero; + let mut res_bytes = Vec::with_capacity(32); + let gate = range.gate(); + // This is un-optimized byte-by-byte addition: + for i in 0..32 { + let ans = gate.add(ctx, add_bytes[31 - i], slot.as_ref()[31 - i]); + let ans = gate.add(ctx, ans, carry); + carry = range.is_less_than(ctx, ans, Constant(F::from(256)), 9); + carry = range.gate().not(ctx, carry); + let ans = gate.sub_mul(ctx, ans, carry, Constant(F::from(256))); + res_bytes.push(ans); + } + res_bytes.reverse(); + gate.assert_is_const(ctx, &carry, &F::ZERO); + SafeTypeChip::unsafe_to_safe_type(res_bytes) +} diff --git a/axiom-eth/src/solidity/tests/mapping.rs b/axiom-eth/src/solidity/tests/mapping.rs new file mode 100644 index 00000000..bec5ae85 --- /dev/null +++ b/axiom-eth/src/solidity/tests/mapping.rs @@ -0,0 +1,138 @@ +use test_case::test_case; +use test_log::test; + +use crate::{ + rlc::{circuit::RlcCircuitParams, tests::get_rlc_params}, + solidity::types::MappingWitness, +}; + +use super::*; + +//======== Mock Prover Tests ========= +#[test_case(ANVIL_BALANCE_OF_PATH; "anvil_balance_of")] +#[test_case(WETH_BALANCE_OF_ADDRESS_PATH; "weth_balance_of_address")] +#[test_case(WETH_BALANCE_OF_BYTES32_PATH; "weth_balance_of_bytes32")] +pub fn test_mock_mapping_pos_from_json(json_path: &str) { + let data = mapping_test_input(Path::new(json_path)); + let (circuit, instance) = mapping_circuit(CircuitBuilderStage::Mock, data, None, None); + let k = circuit.params().k() as u32; + MockProver::run(k, &circuit, vec![instance]).unwrap().assert_satisfied() +} + +#[test_case((20, None); "address")] +#[test_case((1, None); "uint8")] +#[test_case((2, None); "uint16")] +#[test_case((4, None); "uint32")] +#[test_case((8, None); "uint64")] +#[test_case((16, None); "uint128")] +#[test_case((32, None); "uint256")] +#[test_case((0, Some(1)); "dynamic_var_len_0")] +#[test_case((1, Some(1)); "dynamic_var_len_1")] +pub fn test_mock_mapping_pos_random(data: MappingTestData) { + let data = rand_mapping_data(data); + let (circuit, instance) = mapping_circuit(CircuitBuilderStage::Mock, data, None, None); + let k = circuit.params().k() as u32; + MockProver::run(k, &circuit, vec![instance]).unwrap().assert_satisfied() +} + +// Should panic from assert +#[test] +#[should_panic] +pub fn neg_mock_dynamic_var_len_equal_max_len() { + let data = rand_mapping_data((2, Some(1))); + let (circuit, instance) = mapping_circuit(CircuitBuilderStage::Mock, data, None, None); + let k = circuit.params().k() as u32; + assert!(MockProver::run(k, &circuit, vec![instance]).unwrap().verify().is_ok()); +} + +//======== Prover Tests ========= +impl EthCircuitInstructions for MappingTest { + type FirstPhasePayload = (MappingWitness, usize); + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + ) -> Self::FirstPhasePayload { + let max_key_byte_len = self.max_var_len.unwrap_or(32); + let chip = SolidityChip::new(mpt, MAX_NESTING, max_key_byte_len); + let safe = SafeTypeChip::new(mpt.range()); + let ctx = builder.base.main(FIRST_PHASE); + let input = self.clone().assign(ctx, &safe); + let witness = chip.slot_for_mapping_key_phase0(ctx, input.mapping_slot, input.key); + + let assigned_instances = witness.slot().as_ref().to_vec(); + builder.base.assigned_instances[0] = assigned_instances; + (witness, max_key_byte_len) + } + fn virtual_assign_phase1( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + (witness, max_key_byte_len): Self::FirstPhasePayload, + ) { + let chip = SolidityChip::new(mpt, MAX_NESTING, max_key_byte_len); + let (ctx_gate, ctx_rlc) = builder.rlc_ctx_pair(); + chip.slot_for_mapping_key_phase1((ctx_gate, ctx_rlc), witness); + } +} + +pub fn mapping_circuit( + stage: CircuitBuilderStage, + test_data: MappingTest, + params: Option, + break_points: Option, +) -> (EthCircuitImpl>, Vec) { + let instance_wo_commit = + test_data.ground_truth_slot.iter().map(|x| Fr::from(*x as u64)).collect_vec(); + let mut params = if let Some(params) = params { + params + } else { + get_rlc_params("configs/tests/storage_mapping.json") + }; + params.base.num_instance_columns = 1; + let mut circuit = create_circuit(stage, params, test_data); + circuit.mock_fulfill_keccak_promises(None); + if !stage.witness_gen_only() { + circuit.calculate_params(); + } + if let Some(bp) = break_points { + circuit.set_break_points(bp); + } + let instances = circuit.instances(); + let mut instance = instances[0].clone(); + instance.pop().unwrap(); + assert_eq!(instance_wo_commit, instance); + + (circuit, instances[0].clone()) +} + +#[test_case((1, None), (1, None), true; "pos_prover_uint8")] +#[test_case((32, None), (32, None), true; "pos_prover_bytes32")] +#[test_case((1, Some(32)), (1, Some(32)), true; "pos_prover_dynamic_keys_same_var_len_and_max_len")] +#[test_case((1, Some(32)), (3, Some(32)), true; "pos_prover_dynamic_keys_diff_var_len_same_max_len")] +#[test_case((1, None), (2, None), false; "neg_prover_uint8_uint16")] +#[test_case((1, Some(32)), (1, Some(33)), false; "neg_dynamic_keys_diff_max_len")] +pub fn mapping_prover_satisfied( + keygen_data: MappingTestData, + proof_data: MappingTestData, + expected: bool, +) { + let (circuit, _) = + mapping_circuit(CircuitBuilderStage::Keygen, rand_mapping_data(keygen_data), None, None); + let bench_params = circuit.params().rlc; + let k = bench_params.base.k as u32; + let params = gen_srs(k); + let vk = keygen_vk(¶ms, &circuit).unwrap(); + let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); + + let break_points = circuit.break_points(); + + let (circuit, instance) = mapping_circuit( + CircuitBuilderStage::Prover, + rand_mapping_data(proof_data), + Some(bench_params), + Some(break_points), + ); + let proof = gen_proof_with_instances(¶ms, &pk, circuit, &[&instance]); + check_proof_with_instances(¶ms, pk.get_vk(), &proof, &[&instance], expected); +} diff --git a/axiom-eth/src/solidity/tests/mapping_storage.rs b/axiom-eth/src/solidity/tests/mapping_storage.rs new file mode 100644 index 00000000..49632a68 --- /dev/null +++ b/axiom-eth/src/solidity/tests/mapping_storage.rs @@ -0,0 +1,129 @@ +use std::str::FromStr; + +use test_case::test_case; + +use crate::rlc::{circuit::RlcCircuitParams, tests::get_rlc_params}; + +use super::*; +const MAX_MAPPING_KEY_BYTE_LEN: usize = 32; + +//======== Sourced Data Tests ========= +#[test_case(vec![CRYPTOPUNKS_BALANCE_OF_PATH], CRYPTOPUNKS_MAINNET_ADDR; "cryptopunks_balance_of")] +#[test_case(vec![UNISOCKS_ERC20_BALANCE_OF_PATH], UNISOCKS_ERC20_MAINNET_ADDR; "unisocks_erc20_balance_of")] +#[test_case(vec![UNISOCKS_ERC721_BALANCE_OF_PATH], UNISOCKS_ERC721_MAINNET_ADDR; "unisocks_erc721_balance_of")] +#[test_case(vec![WETH_BALANCE_OF_ADDRESS_PATH], WETH_MAINNET_ADDR; "weth_balance_of")] +#[test_case(vec![WETH_ALLOWANCE_ADDR_ADDR_PATH, WETH_ALLOWANCE_ADDR_UINT_PATH], WETH_MAINNET_ADDR; "weth_allowance_double_mapping")] +#[test_case(vec![UNI_V3_ADDR_ADDR_PATH, UNI_V3_ADDR_UINT_PATH, UNI_V3_UINT_ADDR_PATH], UNI_V3_FACTORY_MAINNET_ADDR; "uni_v3_triple_mapping")] +pub fn test_mock_mapping_storage_pos(paths: Vec<&str>, addr: &str) { + let mapping_data = + paths.into_iter().map(|path| mapping_test_input(Path::new(path))).collect_vec(); + let slot = vec![H256::from_slice(&mapping_data.last().unwrap().ground_truth_slot)]; + let storage_pf = get_block_storage_input( + &setup_provider(Chain::Mainnet), + TEST_BLOCK_NUM, + H160::from_str(addr).unwrap(), + slot, + 8, + 8, + ) + .storage; + let circuit = + mapping_storage_circuit(CircuitBuilderStage::Mock, mapping_data, storage_pf, None, None); + let k = circuit.params().k() as u32; + let instances = circuit.instances(); + MockProver::run(k, &circuit, instances).unwrap().assert_satisfied(); +} + +#[test_case(vec![WETH_BALANCE_OF_ADDRESS_PATH], WETH_MAINNET_ADDR, H256::zero())] +#[test_case(vec![WETH_ALLOWANCE_ADDR_ADDR_PATH, WETH_ALLOWANCE_ADDR_UINT_PATH], WETH_MAINNET_ADDR, H256::zero())] +#[test_case(vec![UNI_V3_ADDR_ADDR_PATH, UNI_V3_ADDR_UINT_PATH, UNI_V3_UINT_ADDR_PATH], UNI_V3_FACTORY_MAINNET_ADDR, H256::zero())] +pub fn test_mock_mapping_storage_neg(paths: Vec<&str>, addr: &str, invalid_slot: H256) { + let mapping_data = + paths.into_iter().map(|path| mapping_test_input(Path::new(path))).collect_vec(); + let invalid_storage_pf = get_block_storage_input( + &setup_provider(Chain::Mainnet), + TEST_BLOCK_NUM, + H160::from_str(addr).unwrap(), + vec![invalid_slot], + 8, + 8, + ) + .storage; + let circuit = mapping_storage_circuit( + CircuitBuilderStage::Mock, + mapping_data, + invalid_storage_pf, + None, + None, + ); + let k = circuit.params().k() as u32; + let instances = circuit.instances(); + assert!(MockProver::run(k, &circuit, instances).unwrap().verify().is_err()); +} + +#[derive(Clone)] +struct MappingStorageTest { + test_data: Vec>, + proof: EthStorageInput, +} + +impl EthCircuitInstructions for MappingStorageTest { + type FirstPhasePayload = (NestedMappingWitness, EthStorageWitness); + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + ) -> Self::FirstPhasePayload { + let chip = SolidityChip::new(mpt, MAX_NESTING, MAX_MAPPING_KEY_BYTE_LEN); + let safe = SafeTypeChip::new(mpt.range()); + let ctx = builder.base.main(FIRST_PHASE); + let inputs = self.test_data.iter().map(|data| data.assign(ctx, &safe)).collect_vec(); + let mapping_slot = inputs[0].mapping_slot.clone(); + let proof = self.proof.storage_pfs[0].2.clone().assign(ctx); + let mut keys = inputs.iter().map(|input| input.key.clone()).collect_vec(); + let nestings = ctx.load_witness(F::from(inputs.len() as u64)); + keys.resize(MAX_NESTING, keys[0].clone()); + chip.verify_mapping_storage_phase0::<{ MAX_NESTING }>( + ctx, + mapping_slot, + keys.try_into().unwrap(), + nestings, + proof, + ) + } + fn virtual_assign_phase1( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + (nested_witness, storage_witness): Self::FirstPhasePayload, + ) { + let chip = SolidityChip::new(mpt, MAX_NESTING, MAX_MAPPING_KEY_BYTE_LEN); + let (ctx_gate, ctx_rlc) = builder.rlc_ctx_pair(); + chip.verify_mapping_storage_phase1((ctx_gate, ctx_rlc), nested_witness, storage_witness); + } +} + +fn mapping_storage_circuit( + stage: CircuitBuilderStage, + test_data: Vec>, + proof: EthStorageInput, + params: Option, + break_points: Option, +) -> EthCircuitImpl> { + let params = if let Some(params) = params { + params + } else { + get_rlc_params("configs/tests/storage_mapping.json") + }; + let input = MappingStorageTest { test_data, proof }; + + let mut circuit = create_circuit(stage, params, input); + circuit.mock_fulfill_keccak_promises(None); + circuit.calculate_params(); + if let Some(bp) = break_points { + circuit.set_break_points(bp); + } + circuit +} + +// Skipping prover tests because it is combination of mapping slot computation and storage proof diff --git a/axiom-eth/src/solidity/tests/mod.rs b/axiom-eth/src/solidity/tests/mod.rs new file mode 100644 index 00000000..e88b4568 --- /dev/null +++ b/axiom-eth/src/solidity/tests/mod.rs @@ -0,0 +1,36 @@ +use ethers_core::types::{H160, H256}; + +use ethers_core::types::Chain; +use halo2_base::{ + gates::circuit::CircuitBuilderStage, + halo2_proofs::dev::MockProver, + halo2_proofs::{ + halo2curves::bn256::Fr, + plonk::{keygen_pk, keygen_vk, Circuit}, + }, + safe_types::SafeTypeChip, + utils::{ + fs::gen_srs, + testing::{check_proof_with_instances, gen_proof_with_instances}, + }, +}; +use itertools::Itertools; +use std::{panic::catch_unwind, path::Path, vec}; + +use crate::{ + mpt::MPTChip, + providers::{setup_provider, storage::get_block_storage_input}, + rlc::{circuit::builder::RlcCircuitBuilder, virtual_region::RlcThreadBreakPoints, FIRST_PHASE}, + solidity::{tests::utils::*, types::NestedMappingWitness, SolidityChip}, + storage::{circuit::EthStorageInput, EthStorageWitness}, + utils::eth_circuit::{create_circuit, EthCircuitImpl, EthCircuitInstructions}, + Field, +}; + +pub mod mapping; +pub mod mapping_storage; +pub mod nested_mappings; +pub mod prop_pos; +pub mod utils; + +const MAX_NESTING: usize = 3; diff --git a/axiom-eth/src/solidity/tests/nested_mappings.rs b/axiom-eth/src/solidity/tests/nested_mappings.rs new file mode 100644 index 00000000..ab436262 --- /dev/null +++ b/axiom-eth/src/solidity/tests/nested_mappings.rs @@ -0,0 +1,172 @@ +use test_case::test_case; + +use crate::rlc::{circuit::RlcCircuitParams, tests::get_rlc_params}; + +use super::*; + +//======== Sourced Data Tests ========= +#[test] +pub fn test_mock_weth_allowance() { + let data = vec![ + mapping_test_input(Path::new(WETH_ALLOWANCE_ADDR_ADDR_PATH)), + mapping_test_input(Path::new(WETH_ALLOWANCE_ADDR_UINT_PATH)), + ]; + let (circuit, instance) = nested_mapping_circuit(CircuitBuilderStage::Mock, data, None, None); + let k = circuit.params().k() as u32; + MockProver::run(k, &circuit, vec![instance]).unwrap().assert_satisfied() +} + +#[test] +pub fn test_mock_uni_v3_factory_get_pool() { + let data = vec![ + mapping_test_input(Path::new(UNI_V3_ADDR_ADDR_PATH)), + mapping_test_input(Path::new(UNI_V3_ADDR_UINT_PATH)), + mapping_test_input(Path::new(UNI_V3_UINT_ADDR_PATH)), + ]; + let (circuit, instance) = nested_mapping_circuit(CircuitBuilderStage::Mock, data, None, None); + let k = circuit.params().k() as u32; + MockProver::run(k, &circuit, vec![instance]).unwrap().assert_satisfied() +} + +//======== Mock Prover Tests ========= +#[test_case(vec![(32, None), (20, None)]; "double_mapping_test_bytes32_addr")] +#[test_case(vec![(1, None), (20, None), (32, None)]; "triple_mapping_test_uint8_addr_bytes32")] +#[test_case(vec![(1, Some(2)), (1, None), (20, None)]; "triple_mapping_test_dynamic_uint8_addr")] +#[test_case(vec![(1, None), (1, Some(32)), (20, None)]; "triple_mapping_test_uint8_dynamic_addr")] +#[test_case(vec![(1, None), (20, None), (1, Some(32))]; "triple_mapping_test_uint8_addr_dynamic")] +pub fn test_mock_nested_mapping_pos(data: Vec) { + let data = rand_nested_mapping_data(data); + let (circuit, instance) = nested_mapping_circuit(CircuitBuilderStage::Mock, data, None, None); + let k = circuit.params().k() as u32; + MockProver::run(k, &circuit, vec![instance]).unwrap().assert_satisfied() +} + +//======== Prover Tests ========= +#[derive(Clone)] +struct NestedMappingTest(Vec>); + +impl EthCircuitInstructions for NestedMappingTest { + type FirstPhasePayload = (NestedMappingWitness, usize); + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + ) -> Self::FirstPhasePayload { + let max_key_byte_len = self.0[0].max_var_len.unwrap_or(32); + let chip = SolidityChip::new(mpt, MAX_NESTING, max_key_byte_len); + let safe = SafeTypeChip::new(mpt.range()); + let ctx = builder.base.main(FIRST_PHASE); + let inputs = self.0.iter().map(|data| data.assign(ctx, &safe)).collect_vec(); + let mapping_slot = inputs[0].mapping_slot.clone(); + let mut keys = inputs.iter().map(|input| input.key.clone()).collect_vec(); + let nestings = ctx.load_witness(F::from(keys.len() as u64)); + keys.resize(MAX_NESTING, keys[0].clone()); + let witness = chip.slot_for_nested_mapping_phase0::<{ MAX_NESTING }>( + ctx, + mapping_slot, + keys.try_into().unwrap(), + nestings, + ); + builder.base.assigned_instances[0] = witness.slot.as_ref().to_vec(); + (witness, max_key_byte_len) + } + fn virtual_assign_phase1( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + (witness, max_key_byte_len): Self::FirstPhasePayload, + ) { + let chip = SolidityChip::new(mpt, MAX_NESTING, max_key_byte_len); + chip.slot_for_nested_mapping_phase1(builder.rlc_ctx_pair(), witness); + } +} + +// re-use MappingTest struct for keys, but only use first mapping slot +fn nested_mapping_circuit( + stage: CircuitBuilderStage, + test_data: Vec>, + params: Option, + break_points: Option, +) -> (EthCircuitImpl>, Vec) { + let mut params = if let Some(params) = params { + params + } else { + get_rlc_params("configs/tests/storage_mapping.json") + }; + let instance_wo_commit = test_data + .last() + .unwrap() + .ground_truth_slot + .iter() + .map(|x| Fr::from(*x as u64)) + .collect_vec(); + params.base.num_instance_columns = 1; + let mut circuit = create_circuit(stage, params, NestedMappingTest(test_data)); + circuit.mock_fulfill_keccak_promises(None); + if !stage.witness_gen_only() { + circuit.calculate_params(); + } + if let Some(bp) = break_points { + circuit.set_break_points(bp); + } + let instances = circuit.instances(); + let mut instance = instances[0].clone(); + instance.pop().unwrap(); + assert_eq!(instance_wo_commit, instance); + + (circuit, instances[0].clone()) +} + +#[test_case(vec![(20, None), (1, None), (32, None)], vec![(20, None), (1, None), (32, None)], true; +"pos_prover_triple_mapping_addr_uint8_bytes32")] +#[test_case(vec![(1, Some(2)), (20, None), (32, None)], vec![(1, Some(2)), (20, None), (32, None)], true; +"pos_prover_triple_mapping_dynamic_addr_bytes32")] +#[test_case(vec![(20, None), (1, Some(2)), (32, None)], vec![(20, None), (1, Some(2)), (32, None)], true; +"pos_prover_triple_mapping_addr_dynamic_bytes32")] +#[test_case(vec![(20, None), (32, None), (1, Some(2))], vec![(20, None), (32, None), (1, Some(2))], true; +"pos_prover_triple_mapping_addr_bytes32_dynamic")] +#[test_case(vec![(20, None), (1, None), (32, None)], vec![(20, None), (32, None), (1, None)], false; +"neg_prover_triple_mapping_addr_uint8_bytes32_to_address_bytes32_uint8")] +// #[test_case(vec![(20, None), (1, None), (32, None)], vec![(20, Some(21)), (1, None), (32, None)], false; +// "neg_prover_triple_mapping_addr_uint8_bytes32_to_dynamic_uint8_bytes32")] // catch_unwind not catching this, should panic +// #[test_case(vec![(20, None), (1, None), (32, None)], vec![(20, None), (1, Some(2)), (32, None)], false; +// "neg_prover_triple_mapping_addr_uint8_bytes32_to_addr_dynamic_uint8")] // catch_unwind not catching, should panic +// #[test_case(vec![(20, None), (1, None), (32, None)], vec![(20, None), (1, None), (32, Some(33))], false; +// "neg_prover_triple_mapping_addr_uint8_bytes32_to_addr_uint8_dynamic")] // catch_unwind not catching this, should panic +pub fn nested_mapping_prover_satisfied( + keygen_input: Vec, + proof_input: Vec, + expected: bool, +) { + let (circuit, _) = nested_mapping_circuit( + CircuitBuilderStage::Keygen, + rand_nested_mapping_data(keygen_input), + None, + None, + ); + let bench_params = circuit.params().rlc; + let k = bench_params.base.k as u32; + + let params = gen_srs(k); + let vk = keygen_vk(¶ms, &circuit).unwrap(); + let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); + + let break_points = circuit.break_points(); + + let pf_instance = catch_unwind(|| { + let (circuit, instance) = nested_mapping_circuit( + CircuitBuilderStage::Prover, + rand_nested_mapping_data(proof_input), + Some(bench_params), + Some(break_points), + ); + let proof = gen_proof_with_instances(¶ms, &pk, circuit, &[&instance]); + (proof, instance) + }); + if let Ok((proof, instance)) = pf_instance { + check_proof_with_instances(¶ms, pk.get_vk(), &proof, &[&instance], expected); + } else { + // On some bad inputs we have assert fails during witness generation + assert!(!expected, "Runtime error in proof generation"); + } +} diff --git a/axiom-eth/src/solidity/tests/prop_pos.rs b/axiom-eth/src/solidity/tests/prop_pos.rs new file mode 100644 index 00000000..b46d1f98 --- /dev/null +++ b/axiom-eth/src/solidity/tests/prop_pos.rs @@ -0,0 +1,122 @@ +use crate::{ + solidity::tests::{mapping::*, utils::MappingTest}, + utils::eth_circuit::EthCircuitImpl, +}; +use ethers_core::{types::H256, utils::keccak256}; +use halo2_base::{ + gates::circuit::CircuitBuilderStage, + halo2_proofs::{ + dev::{MockProver, VerifyFailure}, + halo2curves::bn256::Fr, + plonk::Circuit, + }, +}; +use proptest::{collection::vec, prelude::*, sample::select}; +use std::marker::PhantomData; + +prop_compose! { + pub fn rand_hex_string(max: usize)(val in vec(any::(), max)) -> Vec { val } +} + +prop_compose! { + // We assume key is of integer type so we left pad with 0s. Note bytes20 would need to be right padded with 0s + pub fn rand_mapping_test(max: usize) + (key in rand_hex_string(max), mapping_slot in rand_hex_string(32)) -> MappingTest { + let mapping_slot = [vec![0; 32 - mapping_slot.len()], mapping_slot.clone()].concat(); + let mslot = H256::from_slice(&mapping_slot.clone()); + assert!(key.len() <= 32); + let padded_key = [vec![0; 32 - key.len()], key.clone()].concat(); + let ground_truth_concat_key = [padded_key.as_slice(), mslot.as_bytes()].concat(); + debug_assert_eq!(ground_truth_concat_key.len(), padded_key.len() + 32); + let ground_truth_slot = keccak256(ground_truth_concat_key.clone()).to_vec(); + + MappingTest { + key, + var_len: None, + max_var_len: None, + mapping_slot, + ground_truth_concat_key, + ground_truth_slot, + _marker: PhantomData + } + } +} + +prop_compose! { + pub fn rand_var_len_mapping_test(max_len: usize) + (var_len in 1..=max_len) + (mut key in rand_hex_string(var_len), mapping_slot in rand_hex_string(32), var_len in Just(var_len), max_len in Just(max_len)) -> MappingTest { + assert_eq!(key.len(), var_len); + let slot = [vec![0; 32 - mapping_slot.len()], mapping_slot.clone()].concat(); + let m_slot = H256::from_slice(&slot); + let mut ground_truth_concat_key = [key.as_slice(), m_slot.as_bytes()].as_slice().concat(); + let ground_truth_slot = keccak256(ground_truth_concat_key.clone()).to_vec(); + ground_truth_concat_key.resize(max_len + 32, 0); + key.resize(max_len, 0); + MappingTest { + key, + var_len: Some(var_len), + max_var_len: Some(max_len), + mapping_slot, + ground_truth_concat_key, + ground_truth_slot, + _marker: PhantomData + } + } +} + +prop_compose! { + pub fn rand_byte_sized_mapping_test() + (size in select(vec![1,2,4,8,16,32])) + (key in rand_hex_string(size), mapping_slot in rand_hex_string(32)) -> MappingTest { + let slot = [vec![0; 32 - mapping_slot.len()], mapping_slot.clone()].concat(); + let mslot = H256::from_slice(&slot.clone()); + let padded_key = [vec![0; 32 - key.len()], key.clone()].concat(); + let ground_truth_concat_key = [padded_key.as_slice(), mslot.as_bytes()].concat(); + debug_assert!(ground_truth_concat_key.len() == 64); + let ground_truth_slot = keccak256(ground_truth_concat_key.clone()).to_vec(); + MappingTest { + key, + var_len: None, + max_var_len: None, + mapping_slot, + ground_truth_concat_key, + ground_truth_slot, + _marker: PhantomData + } + } +} + +pub fn prop_mock_prover_satisfied( + (circuit, instance): (EthCircuitImpl>, Vec), +) -> Result<(), Vec> { + let k = circuit.params().k() as u32; + MockProver::run(k, &circuit, vec![instance]).unwrap().verify() +} + +proptest! { + + #[test] + #[ignore] + fn prop_test_pos_rand_bytes32_key(input in rand_mapping_test(32)) { + prop_mock_prover_satisfied(mapping_circuit(CircuitBuilderStage::Mock, input,None, None)).unwrap(); + } + + #[test] + #[ignore] + fn prop_test_pos_rand_address_key(input in rand_mapping_test(20)) { + prop_mock_prover_satisfied(mapping_circuit(CircuitBuilderStage::Mock, input,None, None)).unwrap(); + } + + #[test] + #[ignore] + fn prop_test_pos_rand_uint_key(input in rand_byte_sized_mapping_test()) { + prop_mock_prover_satisfied(mapping_circuit(CircuitBuilderStage::Mock, input, None, None)).unwrap(); + } + + #[test] + #[ignore] + fn prop_test_pos_rand_dynamic_array_key(input in rand_var_len_mapping_test(36)) { + prop_mock_prover_satisfied(mapping_circuit(CircuitBuilderStage::Mock, input, None, None)).unwrap(); + } +} diff --git a/axiom-eth/src/solidity/tests/utils.rs b/axiom-eth/src/solidity/tests/utils.rs new file mode 100644 index 00000000..2895680b --- /dev/null +++ b/axiom-eth/src/solidity/tests/utils.rs @@ -0,0 +1,237 @@ +use crate::Field; +use ethers_core::{types::H256, utils::keccak256}; +use halo2_base::{ + halo2_proofs::halo2curves::bn256::Fr, + safe_types::{SafeBytes32, SafeTypeChip}, + AssignedValue, Context, +}; +use hex::FromHex; +use itertools::Itertools; +use rand::RngCore; +use serde::{Deserialize, Serialize}; +use std::{iter, marker::PhantomData, path::Path}; + +use crate::{ + solidity::types::SolidityType, utils::eth_circuit::EthCircuitParams, + utils::unsafe_bytes_to_assigned, +}; + +pub const TEST_BLOCK_NUM: u32 = 17595887; + +//Mainnet Contract Address +pub const WETH_MAINNET_ADDR: &str = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"; +pub const CRYPTOPUNKS_MAINNET_ADDR: &str = "0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb"; +pub const UNI_V3_FACTORY_MAINNET_ADDR: &str = "0x1f98431c8ad98523631ae4a59f267346ea31f984"; +pub const UNISOCKS_ERC20_MAINNET_ADDR: &str = "0x23b608675a2b2fb1890d3abbd85c5775c51691d5"; +pub const UNISOCKS_ERC721_MAINNET_ADDR: &str = "0x65770b5283117639760bea3f867b69b3697a91dd"; + +//Json Paths +pub const UNI_V3_ADDR_ADDR_PATH: &str = "data/mappings/uni_v3_factory_get_pool_addr_addr.json"; +pub const UNI_V3_ADDR_UINT_PATH: &str = "data/mappings/uni_v3_factory_get_pool_addr_uint.json"; +pub const UNI_V3_UINT_ADDR_PATH: &str = "data/mappings/uni_v3_factory_get_pool_uint_addr.json"; +pub const UNI_V3_FAKE_PATH: &str = "data/mappings/uni_v3_factory_fake.json"; + +pub const UNISOCKS_ERC20_BALANCE_OF_PATH: &str = + "data/mappings/unisocks_erc20_balance_of_addr_uint.json"; +pub const UNISOCKS_ERC721_BALANCE_OF_PATH: &str = + "data/mappings/unisocks_erc721_balance_of_addr_uint.json"; +pub const CRYPTOPUNKS_BALANCE_OF_PATH: &str = "data/mappings/cryptopunks_balance_of_addr_uint.json"; + +pub const WETH_BALANCE_OF_ADDRESS_PATH: &str = "data/mappings/weth_balance_of_addr_uint.json"; +pub const WETH_BALANCE_OF_BYTES32_PATH: &str = "data/mappings/weth_balance_of_bytes32_uint.json"; + +pub const WETH_ALLOWANCE_ADDR_ADDR_PATH: &str = "data/mappings/weth_allowance_addr_addr.json"; +pub const WETH_ALLOWANCE_ADDR_UINT_PATH: &str = "data/mappings/weth_allowance_addr_uint.json"; + +pub const ANVIL_BALANCE_OF_PATH: &str = "data/mappings/anvil_dynamic_uint.json"; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct MappingTest { + pub key: Vec, + pub var_len: Option, + pub max_var_len: Option, + pub mapping_slot: Vec, + pub ground_truth_concat_key: Vec, + pub ground_truth_slot: Vec, + pub _marker: PhantomData, +} + +#[derive(Debug, Clone)] +pub struct AssignedMappingTest { + pub key: SolidityType, + pub var_len: Option, + pub max_var_len: Option, + pub mapping_slot: SafeBytes32, +} + +impl MappingTest { + pub fn assign(&self, ctx: &mut Context, safe: &SafeTypeChip) -> AssignedMappingTest { + let key_bytes = unsafe_bytes_to_assigned(ctx, &self.key); + // Determine value or nonvalue based on Option of var_len + let key = get_test_key(&self.var_len, &self.max_var_len, safe, ctx, key_bytes); + + let mapping_slot_bytes = unsafe_bytes_to_assigned(ctx, &self.mapping_slot); + let mapping_slot = safe.raw_bytes_to::<1, 256>(ctx, mapping_slot_bytes); + + AssignedMappingTest { + key, + var_len: self.var_len, + max_var_len: self.max_var_len, + mapping_slot, + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct BenchParams(pub EthCircuitParams, pub usize); // (params, num_slots) +/// (byte len, optional max byte len if non-value type) +pub type MappingTestData = (usize, Option); + +/// Inputs a mapping test from a json file +pub fn mapping_test_input(path: impl AsRef) -> MappingTest { + let mapping_test_str = std::fs::read_to_string(path).unwrap(); + let mapping_test: serde_json::Value = serde_json::from_str(mapping_test_str.as_str()).unwrap(); + + let key_bytes_str: String = serde_json::from_value(mapping_test["key"].clone()).unwrap(); + let key = Vec::from_hex(key_bytes_str).unwrap(); + + let var_len_str: String = serde_json::from_value(mapping_test["var_len"].clone()).unwrap(); + let var_len = match var_len_str.is_empty() { + true => None, + false => Some(var_len_str.parse::().unwrap()), + }; + + let max_var_len_str: String = + serde_json::from_value(mapping_test["max_var_len"].clone()).unwrap(); + let max_var_len = match max_var_len_str.is_empty() { + true => None, + false => Some(max_var_len_str.parse::().unwrap()), + }; + + let mapping_slot_str: String = + serde_json::from_value(mapping_test["mapping_slot"].clone()).unwrap(); + let mapping_slot = Vec::from_hex(mapping_slot_str).unwrap(); + + let ground_truth_concat_key_str: String = + serde_json::from_value(mapping_test["ground_truth_concat_key"].clone()).unwrap(); + let ground_truth_concat_key = Vec::from_hex(ground_truth_concat_key_str).unwrap(); + + let ground_truth_slot_str: String = + serde_json::from_value(mapping_test["ground_truth_slot"].clone()).unwrap(); + let ground_truth_slot = Vec::from_hex(ground_truth_slot_str).unwrap(); + + MappingTest { + key, + var_len, + max_var_len, + mapping_slot, + ground_truth_concat_key, + ground_truth_slot, + _marker: PhantomData, + } +} + +pub fn get_test_key( + var_len: &Option, + max_var_len: &Option, + safe: &SafeTypeChip, + ctx: &mut Context, + test_key_bytes: Vec>, +) -> SolidityType { + match var_len { + Some(var_len) => SolidityType::NonValue({ + let max_var_len = max_var_len.unwrap_or(test_key_bytes.len()); + let var_len = ctx.load_witness(F::from(*var_len as u64)); + safe.raw_to_var_len_bytes_vec(ctx, test_key_bytes, var_len, max_var_len) + }), + None => { + let test_key_bytes = iter::repeat(ctx.load_zero()) + .take(32 - test_key_bytes.len()) + .chain(test_key_bytes) + .collect_vec(); + SolidityType::Value(safe.raw_bytes_to::<1, 256>(ctx, test_key_bytes)) + } + } +} + +pub fn rand_hex_array(max: usize) -> Vec { + let mut rng = rand::thread_rng(); + let mut bytes = vec![0; max]; + rng.fill_bytes(&mut bytes); + bytes +} + +pub fn rand_mapping_data((len, max_len): (usize, Option)) -> MappingTest { + let key = rand_hex_array(len); + let mapping_slot = rand_hex_array(32); + let m_slot = H256::from_slice(&mapping_slot.clone()); + + let (key, ground_truth_slot, ground_truth_concat_key, var_len, max_var_len) = match max_len { + Some(max_len) => { + let mut ground_truth_concat_key = [key.as_slice(), m_slot.as_bytes()].concat(); + let ground_truth_slot = keccak256(&ground_truth_concat_key[..len + 32]).to_vec(); + ground_truth_concat_key.resize(max_len + 32, 0); + let key = + key.iter().chain(iter::repeat(&0)).take(max_len).cloned().collect::>(); + (key, ground_truth_slot, ground_truth_concat_key, Some(len), Some(max_len)) + } + None => { + let padded_key = [vec![0; 32 - key.len()], key.clone()].concat(); + let ground_truth_concat_key = [padded_key.as_slice(), m_slot.as_bytes()].concat(); + debug_assert!(ground_truth_concat_key.len() == 64); + let ground_truth_slot = keccak256(ground_truth_concat_key.clone()).to_vec(); + (key, ground_truth_slot, ground_truth_concat_key, None, None) + } + }; + + MappingTest { + key, + var_len, + max_var_len, + mapping_slot, + ground_truth_concat_key, + ground_truth_slot, + _marker: PhantomData, + } +} + +pub fn rand_nested_mapping_data(tests: Vec<(usize, Option)>) -> Vec> { + let mut slot = rand_hex_array(32); + + tests.into_iter().fold(Vec::new(), |mut acc, (len, max_len)| { + let key = rand_hex_array(len); + let mapping_slot = slot.to_vec(); + let m_slot = H256::from_slice(&mapping_slot.clone()); + + let (key, ground_truth_slot, ground_truth_concat_key, var_len, max_var_len) = match max_len + { + Some(max_len) => { + let ground_truth_concat_key = + [key.as_slice(), m_slot.as_bytes(), vec![0; max_len - len].as_slice()].concat(); + let key = + key.iter().chain(iter::repeat(&0)).take(max_len).cloned().collect::>(); + let ground_truth_slot = keccak256(&ground_truth_concat_key[..len + 32]).to_vec(); + (key, ground_truth_slot, ground_truth_concat_key, Some(len), Some(max_len)) + } + None => { + let padded_key = [vec![0; 32 - key.len()], key.clone()].concat(); + let ground_truth_concat_key = [padded_key.as_slice(), m_slot.as_bytes()].concat(); + debug_assert!(ground_truth_concat_key.len() == 64); + let ground_truth_slot = keccak256(ground_truth_concat_key.clone()).to_vec(); + (key, ground_truth_slot, ground_truth_concat_key, None, None) + } + }; + + acc.push(MappingTest { + key, + var_len, + max_var_len, + mapping_slot, + ground_truth_concat_key, + ground_truth_slot: ground_truth_slot.clone(), + _marker: PhantomData, + }); + slot = ground_truth_slot; + acc + }) +} diff --git a/axiom-eth/src/solidity/types.rs b/axiom-eth/src/solidity/types.rs new file mode 100644 index 00000000..d8393b1a --- /dev/null +++ b/axiom-eth/src/solidity/types.rs @@ -0,0 +1,72 @@ +//TODO: Replace arrays and gate with &impl +use crate::Field; +use crate::{keccak::types::KeccakVarLenQuery, rlc::types::ConcatVarFixedArrayTrace}; +use halo2_base::{ + safe_types::{SafeBytes32, VarLenBytesVec}, + AssignedValue, +}; + +/// Witness for the computation of a nested mapping +#[derive(Debug, Clone)] +pub struct NestedMappingWitness { + pub witness: Vec>, + pub slot: SafeBytes32, + pub nestings: AssignedValue, +} + +/// Witness for the computation of a mapping with a variable length (Non-Value) key. +#[derive(Debug, Clone)] +pub struct VarMappingWitness { + pub mapping_slot: SafeBytes32, + pub key: VarLenBytesVec, + /// The output of this hash is the storage slot for the mapping key + pub hash_query: KeccakVarLenQuery, +} + +pub type VarMappingTrace = ConcatVarFixedArrayTrace; + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Clone)] +pub enum MappingWitness { + /// The storage slot corresponding to mapping key of value type + Value(SafeBytes32), + /// Witness for mapping key of non-value type + NonValue(VarMappingWitness), +} + +impl MappingWitness { + pub fn slot(&self) -> SafeBytes32 { + match self { + MappingWitness::Value(slot) => slot.clone(), + MappingWitness::NonValue(witness) => witness.hash_query.output_bytes.clone(), + } + } +} + +/// Return after phase1 of mapping computation. None if the mapping key is of Value type. +pub type MappingTrace = Option>; + +/// Enum whose variants which represents different primitive types of Solidity types that can be represented in circuit. +/// Each variant wraps a `Vec>` representing the bytes of primitive type. +/// +/// Fixed Length Types (Value): +/// -------------------------- +/// * `UInt256`: 32 bytes +/// Fixed length primitive types are represented by a fixed length `Vec>` converted to a `SafeBytes32`. +/// SafeTypes range check that each AssignedValue of the vector is within byte range 0-255 and the vector has length 32. +/// +/// Variable Length Types (NonValue): +/// --------------------------------- +/// * `NonValue`: Variable length byte array +#[derive(Debug, Clone)] +pub enum SolidityType { + Value(SafeBytes32), + NonValue(VarLenBytesVec), +} + +#[derive(Debug, Clone)] +pub struct SolidityStoragePosition { + pub slot: SafeBytes32, + pub byte_offset: AssignedValue, + pub item_byte_len: AssignedValue, +} diff --git a/axiom-eth/src/ssz/mod.rs b/axiom-eth/src/ssz/mod.rs new file mode 100644 index 00000000..a8360e58 --- /dev/null +++ b/axiom-eth/src/ssz/mod.rs @@ -0,0 +1,361 @@ +use crate::Field; +use crate::{ + rlc::{ + chip::RlcChip, + circuit::{builder::RlcCircuitBuilder, instructions::RlcCircuitInstructions}, + }, + sha256::Sha256Chip, + utils::{assign_vec, uint_to_bytes_be}, +}; +use halo2_base::{ + gates::{GateChip, GateInstructions, RangeChip, RangeInstructions}, + utils::bit_length, + AssignedValue, Context, + QuantumCell::Constant, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; + +use self::types::{Chunk, SszBasicType, SszBasicTypeList, SszBasicTypeVector, SszStruct}; + +#[cfg(test)] +pub mod tests; +pub mod types; + +pub const NUM_BASIC_TYPES: usize = 7; +pub const BASIC_TYPE_BIT_SIZES: [usize; NUM_BASIC_TYPES] = [1, 8, 16, 32, 64, 128, 256]; + +#[derive(Clone, Debug)] +pub struct SszChip<'r, F: Field> { + pub rlc: Option<&'r RlcChip>, // We use this chip in FirstPhase when there is no RlcChip + pub range: &'r RangeChip, + pub sha256: Sha256Chip<'r, F>, +} + +pub fn next_pow2(n: usize) -> usize { + let mut pow2 = 1; + loop { + if pow2 >= n { + return pow2; + } + pow2 *= 2; + } +} + +impl<'r, F: Field> SszChip<'r, F> { + pub fn new( + rlc: Option<&'r RlcChip>, + range: &'r RangeChip, + sha256: Sha256Chip<'r, F>, + ) -> Self { + Self { rlc, range, sha256 } + } + + pub fn gate(&self) -> &GateChip { + self.range.gate() + } + + pub fn range(&self) -> &RangeChip { + self.range + } + + pub fn rlc(&self) -> &RlcChip { + self.rlc.as_ref().expect("RlcChip should be constructed and used only in SecondPhase") + } + + pub fn sha256(&self) -> &Sha256Chip { + &self.sha256 + } + + pub fn pack_basic_type(&self, ctx: &mut Context, value: &SszBasicType) -> Chunk { + let mut val = value.value().clone(); + let zero = ctx.load_zero(); + val.resize(32, zero); + val + } + + pub fn pack_basic_type_vector( + &self, + ctx: &mut Context, + value: &SszBasicTypeVector, + ) -> Vec> { + let int_bit_size = value.int_bit_size(); + let int_byte_size = (int_bit_size + 7) / 8; + let values = value.values(); + let mut total_bytes_len = next_pow2(int_byte_size * values.len()); + total_bytes_len = 32 * ((total_bytes_len + 31) / 32); + let mut packed_bytes = Vec::new(); + for i in 0..values.len() { + let int = values[i].value(); + for j in 0..int_byte_size { + packed_bytes.push(int[j]); + } + } + let zero = ctx.load_zero(); + packed_bytes.resize(total_bytes_len, zero); + let packed_bytes = packed_bytes.chunks(32); + packed_bytes.map(|v| v.to_vec()).collect_vec() + } + + pub fn pack_basic_type_list( + &self, + ctx: &mut Context, + value: &SszBasicTypeList, + ) -> Vec> { + let int_bit_size = value.int_bit_size(); + let int_byte_size = (int_bit_size + 7) / 8; + let values = value.values(); + let mut total_bytes_len = next_pow2(int_byte_size * values.len()); + total_bytes_len = 32 * ((total_bytes_len + 31) / 32); + let mut packed_bytes = Vec::new(); + for i in 0..values.len() { + let int = values[i].value(); + for j in 0..int_byte_size { + packed_bytes.push(int[j]); + } + } + let zero = ctx.load_zero(); + packed_bytes.resize(total_bytes_len, zero); + let packed_bytes = packed_bytes.chunks(32); + packed_bytes.map(|v| v.to_vec()).collect_vec() + } + + pub fn merkleize(&self, ctx: &mut Context, chunks: Vec>) -> Chunk { + assert!(chunks.len().is_power_of_two()); + let len = chunks.len(); + if len == 1 { + return chunks[0].clone(); + } + let mut new_chunks = Vec::new(); + for i in 0..(len / 2) { + let mut chunk0 = chunks[2 * i].clone(); + let mut chunk1 = chunks[2 * i + 1].clone(); + chunk0.append(&mut chunk1); + let hash_query = self.sha256.sha256_fixed_len(ctx, chunk0); + new_chunks.push(hash_query.output_assigned.clone()); + } + return self.merkleize(ctx, new_chunks); + } + + pub fn basic_type_hash_tree_root( + &self, + ctx: &mut Context, + value: &SszBasicType, + ) -> Chunk { + // let val_bytes = self.pack_basic_type(ctx, value); + // self.merkleize(ctx, sha, vec![val_bytes]) + self.pack_basic_type(ctx, value) + } + + pub fn basic_type_vector_hash_tree_root( + &self, + ctx: &mut Context, + vec: &SszBasicTypeVector, + ) -> Chunk { + let chunks = self.pack_basic_type_vector(ctx, &vec); + self.merkleize(ctx, chunks) + } + + pub fn basic_type_list_hash_tree_root( + &self, + ctx: &mut Context, + list: &SszBasicTypeList, + ) -> Chunk { + let len_bytes = uint_to_bytes_be(ctx, self.range, &list.len(), 32); + let len_bytes = len_bytes.into_iter().map(|b| b.into()).rev().collect(); + let chunks = self.pack_basic_type_list(ctx, &list); + let root = self.merkleize(ctx, chunks); + self.merkleize(ctx, vec![root, len_bytes]) + } + + pub fn verify_inclusion_proof( + &self, + ctx: &mut Context, + input: SSZInputAssigned, + ) -> SSZInclusionWitness { + let val = &input.val; + let root_bytes = &input.root_bytes; + let proof = &input.proof; + let directions = &input.directions; + let depth = input.depth; + // Check that depth is nonzero + let depth_is_zero = self.gate().is_zero(ctx, depth); + self.gate().assert_is_const(ctx, &depth_is_zero, &F::ZERO); + let max_depth = proof.len(); + self.range.check_less_than_safe(ctx, depth, (max_depth + 1) as u64); + assert!(proof.len() == directions.len()); + assert!(proof.len() == max_depth); + for i in 0..max_depth { + self.range.check_less_than_safe(ctx, directions[i], 2); + } + let zero = ctx.load_zero(); + let zero_chunk = vec![zero; 32]; + let mut roots = vec![zero_chunk]; + let depth_minus_one = self.gate().dec(ctx, depth); + let depth_minus_one_indicator = + self.gate().idx_to_indicator(ctx, depth_minus_one, max_depth); + for i in 0..max_depth { + let idx = max_depth - 1 - i; + let mut child = Vec::new(); + for j in 0..32 { + let child_byte = + self.gate().select(ctx, val[j], roots[i][j], depth_minus_one_indicator[idx]); + child.push(child_byte); + } + let other_child = proof[idx].clone(); + let left_root = self.merkleize(ctx, vec![child.clone(), other_child.clone()]); + let right_root = self.merkleize(ctx, vec![other_child, child]); + let mut root = Vec::new(); + for j in 0..32 { + let root_byte = + self.gate().select(ctx, right_root[j], left_root[j], directions[idx]); + root.push(root_byte); + } + roots.push(root); + } + for j in 0..32 { + ctx.constrain_equal(&roots[max_depth][j], &root_bytes[j]); + } + input + } + + pub fn verify_field_hash( + &self, + ctx: &mut Context, + field_num: AssignedValue, + max_fields: usize, + proof: SSZInputAssigned, + ) -> SSZInclusionWitness { + assert!(max_fields > 0); + let log_max_fields = bit_length(max_fields as u64); + self.range().check_less_than_safe(ctx, field_num, max_fields as u64); + let field_num_bits = self.gate().num_to_bits(ctx, field_num, log_max_fields); + let witness = self.verify_inclusion_proof(ctx, proof); + let bad_depth = self.range().is_less_than_safe(ctx, witness.depth, log_max_fields as u64); + self.gate().assert_is_const(ctx, &bad_depth, &F::from(0)); + for i in 1..(log_max_fields + 1) { + let index = self.gate().sub(ctx, witness.depth, Constant(F::from(i as u64))); + let dir_bit = self.gate().select_from_idx(ctx, witness.directions.clone(), index); + ctx.constrain_equal(&dir_bit, field_num_bits[log_max_fields - i].as_ref()); + } + witness + } + + pub fn verify_struct_inclusion( + &self, + ctx: &mut Context, + proof: SSZInputAssigned, + ssz_struct: &impl SszStruct, + ) -> SSZInclusionWitness { + let witness = self.verify_inclusion_proof(ctx, proof); + let struct_root = ssz_struct.hash_root(ctx, self); + let zipped_vals: Vec<(&AssignedValue, &AssignedValue)> = + witness.val.iter().zip(struct_root.iter()).collect(); + for (root_byte, hash_byte) in zipped_vals { + ctx.constrain_equal(root_byte, hash_byte); + } + witness + } + + pub fn verify_struct_field_inclusion( + &self, + ctx: &mut Context, + field_num: AssignedValue, + max_fields: usize, + proof: SSZInputAssigned, + ssz_struct: &impl SszStruct, + ) -> SSZInclusionWitness { + let witness = self.verify_field_hash(ctx, field_num, max_fields, proof); + let struct_root = ssz_struct.hash_root(ctx, self); + let zipped_vals: Vec<(&AssignedValue, &AssignedValue)> = + witness.val.iter().zip(struct_root.iter()).collect(); + for (root_byte, hash_byte) in zipped_vals { + ctx.constrain_equal(root_byte, hash_byte); + } + witness + } +} + +pub type SSZInclusionWitness = SSZInputAssigned; + +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] + +pub struct SSZInput { + pub val: Vec, + pub root_bytes: Vec, + pub proof: Vec>, + pub directions: Vec, + pub depth: usize, + pub max_depth: usize, +} + +impl SSZInput { + pub fn assign(self, ctx: &mut Context) -> SSZInputAssigned { + assert!(self.val.len() == 32); + assert!(self.root_bytes.len() == 32); + for i in 0..self.proof.len() { + assert!(self.proof[i].len() == 32); + } + assert!(self.directions.len() == self.proof.len()); + assert!(self.depth <= self.max_depth); + assert!(self.depth == self.proof.len()); + let val = assign_vec(ctx, self.val, 32); + let root_bytes = assign_vec(ctx, self.root_bytes, 32); + let zeros: Vec = [0; 32].to_vec(); + let mut padded_proof = self.proof.clone(); + padded_proof.resize(self.max_depth, zeros); + let proof = padded_proof.into_iter().map(|node| assign_vec(ctx, node, 32)).collect_vec(); + let directions = assign_vec(ctx, self.directions, self.max_depth); + let depth = ctx.load_witness(F::from(self.depth as u64)); + SSZInputAssigned { val, root_bytes, proof, directions, depth } + } +} + +#[derive(Clone, Debug)] +pub struct SSZInputAssigned { + pub val: Chunk, + pub root_bytes: Chunk, + pub proof: Vec>, + pub directions: Vec>, + pub depth: AssignedValue, +} + +#[derive(Clone, Debug)] +pub struct SSZInclusionCircuit { + pub input: SSZInput, // public and private inputs + pub max_depth: usize, + pub _marker: PhantomData, +} + +impl SSZInclusionCircuit { + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "providers")] + pub fn from_input(input: SSZInput) -> Self { + let max_depth = input.max_depth; + Self { input, max_depth, _marker: PhantomData } + } +} + +// TEMPORARY: We'll need an EthBeaconCircuitBuilder or something similar. Need to think about how to reduce code duplication. +impl RlcCircuitInstructions for SSZInclusionCircuit { + type FirstPhasePayload = (); + fn generate_witnesses_phase0( + &self, + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + ) -> Self::FirstPhasePayload { + let sha = Sha256Chip::new(range); + let ctx = builder.base.main(0); + let input = self.input.clone().assign(ctx); + let ssz = SszChip::new(None, &range, sha); + let _witness = ssz.verify_inclusion_proof(ctx, input); + } + fn generate_witnesses_phase1( + _builder: &mut RlcCircuitBuilder, + _range: &RangeChip, + _rlc: &RlcChip, + _payload: Self::FirstPhasePayload, + ) { + } +} diff --git a/axiom-eth/src/ssz/tests/basic_types/bool.json b/axiom-eth/src/ssz/tests/basic_types/bool.json new file mode 100644 index 00000000..97d3d995 --- /dev/null +++ b/axiom-eth/src/ssz/tests/basic_types/bool.json @@ -0,0 +1,5 @@ +{ + "hash_root": "0100000000000000000000000000000000000000000000000000000000000000", + "value": 1, + "bit_size": 1 +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/basic_types/byte.json b/axiom-eth/src/ssz/tests/basic_types/byte.json new file mode 100644 index 00000000..c61b914e --- /dev/null +++ b/axiom-eth/src/ssz/tests/basic_types/byte.json @@ -0,0 +1,5 @@ +{ + "hash_root": "0c00000000000000000000000000000000000000000000000000000000000000", + "value": 12, + "bit_size": 8 +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/basic_types/neg_bool.json b/axiom-eth/src/ssz/tests/basic_types/neg_bool.json new file mode 100644 index 00000000..ff48318d --- /dev/null +++ b/axiom-eth/src/ssz/tests/basic_types/neg_bool.json @@ -0,0 +1,5 @@ +{ + "hash_root": "0300000000000000000000000000000000000000000000000000000000000000", + "value": 3, + "bit_size": 1 +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/basic_types/neg_byte.json b/axiom-eth/src/ssz/tests/basic_types/neg_byte.json new file mode 100644 index 00000000..9ca96674 --- /dev/null +++ b/axiom-eth/src/ssz/tests/basic_types/neg_byte.json @@ -0,0 +1,5 @@ +{ + "hash_root": "0101000000000000000000000000000000000000000000000000000000000000", + "value": 257, + "bit_size": 8 +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/basic_types/u16.json b/axiom-eth/src/ssz/tests/basic_types/u16.json new file mode 100644 index 00000000..36bc0846 --- /dev/null +++ b/axiom-eth/src/ssz/tests/basic_types/u16.json @@ -0,0 +1,5 @@ +{ + "hash_root": "0104000000000000000000000000000000000000000000000000000000000000", + "value": 1025, + "bit_size": 16 +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/basic_types/u64.json b/axiom-eth/src/ssz/tests/basic_types/u64.json new file mode 100644 index 00000000..52ee92e0 --- /dev/null +++ b/axiom-eth/src/ssz/tests/basic_types/u64.json @@ -0,0 +1,5 @@ +{ + "hash_root": "0104000000000000000000000000000000000000000000000000000000000000", + "value": 1025, + "bit_size": 64 +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/lists/bad_len_list.json b/axiom-eth/src/ssz/tests/lists/bad_len_list.json new file mode 100644 index 00000000..cbfdf0f0 --- /dev/null +++ b/axiom-eth/src/ssz/tests/lists/bad_len_list.json @@ -0,0 +1,13 @@ +{ + "hash_root": "0ba04aed8d8ed403b721cab95237e5933ecbf0c768096265b71f1149fd446f28", + "value": [ + 3, + 3, + 3, + 3, + 3 + ], + "bit_size": 64, + "max_len": 7, + "len": 4 +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/lists/empty_list.json b/axiom-eth/src/ssz/tests/lists/empty_list.json new file mode 100644 index 00000000..a3df2f20 --- /dev/null +++ b/axiom-eth/src/ssz/tests/lists/empty_list.json @@ -0,0 +1,7 @@ +{ + "hash_root": "7a0501f5957bdf9cb3a8ff4966f02265f968658b7a9c62642cba1165e86642f5", + "value": [], + "bit_size": 64, + "max_len": 7, + "len": 0 +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/lists/unfull_list.json b/axiom-eth/src/ssz/tests/lists/unfull_list.json new file mode 100644 index 00000000..4b2a4876 --- /dev/null +++ b/axiom-eth/src/ssz/tests/lists/unfull_list.json @@ -0,0 +1,13 @@ +{ + "hash_root": "3fec59517fd04f012de8e5610d431741629391dd34dab06290ec90a41f549c62", + "value": [ + 3, + 3, + 3, + 3, + 3 + ], + "bit_size": 64, + "max_len": 7, + "len": 5 +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/merkle_proof/incorrect_proof.json b/axiom-eth/src/ssz/tests/merkle_proof/incorrect_proof.json new file mode 100644 index 00000000..9cde2347 --- /dev/null +++ b/axiom-eth/src/ssz/tests/merkle_proof/incorrect_proof.json @@ -0,0 +1,14 @@ +{ + "val": "000000000000000000000000000000000000000000000000000000000000000f", + "root_bytes": "338372d7b73937a1d42b73746748b04ba031146a4a6315356efe93b6155cab62", + "proof": [ + "123500000000000000000000000000000000000000000000000000000000000f", + "abcd00000000000000000000000000000000000000000000000000000000000f", + "000000000000000000000000000000000000000000000000000000000000000e" + ], + "directions": [ + 0, + 1, + 0 + ] +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/merkle_proof/misdirected_proof.json b/axiom-eth/src/ssz/tests/merkle_proof/misdirected_proof.json new file mode 100644 index 00000000..8190d429 --- /dev/null +++ b/axiom-eth/src/ssz/tests/merkle_proof/misdirected_proof.json @@ -0,0 +1,14 @@ +{ + "val": "000000000000000000000000000000000000000000000000000000000000000f", + "root_bytes": "338372d7b73937a1d42b73746748b04ba031146a4a6315356efe93b6155cab62", + "proof": [ + "123400000000000000000000000000000000000000000000000000000000000f", + "abcd00000000000000000000000000000000000000000000000000000000000f", + "000000000000000000000000000000000000000000000000000000000000000e" + ], + "directions": [ + 1, + 1, + 0 + ] +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/merkle_proof/proof.json b/axiom-eth/src/ssz/tests/merkle_proof/proof.json new file mode 100644 index 00000000..ed1cd2bd --- /dev/null +++ b/axiom-eth/src/ssz/tests/merkle_proof/proof.json @@ -0,0 +1,14 @@ +{ + "val": "000000000000000000000000000000000000000000000000000000000000000f", + "root_bytes": "338372d7b73937a1d42b73746748b04ba031146a4a6315356efe93b6155cab62", + "proof": [ + "123400000000000000000000000000000000000000000000000000000000000f", + "abcd00000000000000000000000000000000000000000000000000000000000f", + "000000000000000000000000000000000000000000000000000000000000000e" + ], + "directions": [ + 0, + 1, + 0 + ] +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/merkle_proof/real_beacon_proof.json b/axiom-eth/src/ssz/tests/merkle_proof/real_beacon_proof.json new file mode 100644 index 00000000..af1814bf --- /dev/null +++ b/axiom-eth/src/ssz/tests/merkle_proof/real_beacon_proof.json @@ -0,0 +1,100 @@ +{ + "directions": [ + 0, + 1, + 0, + 1, + 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, + 1 + ], + "proof": [ + "c9242002acfdc02f45fe2197f073648c73c9a33a1452f6727ff499a537541b94", + "0aecf74c9bcd7832df78a3cc117be981e7c8eed81b11148a3de1435ebbd30daa", + "e03a9fe84575c25ab0fc8981b7e6adcdd67a15e52c4b95f64b548e532eea1606", + "96d20167d9625b7724b0b1cca28bfc5fffe6f03c80cdb411ffe2078330de5b52", + "04d3030000000000000000000000000000000000000000000000000000000000", + "1fd2030000000000000000000000000000000000000000000000000000000000", + "ad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f", + "f7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76", + "55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", + "bfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", + "328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", + "2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d", + "1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5", + "c6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff", + "985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7", + "b5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "e71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "feb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "cddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "f893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "1364984a688be918803722cc46da0483f33b02105d3b19c72e06cdc5e3d1bb31", + "9a26432d1dbd10e13353c662f331ddf4fb222038fdb0d34fc0387b91ba2a0c79", + "282c52f515d8af128124878b81d7f69320ef02f90aa481dad6742bdf4c062047", + "fb40bce185e9d7bba1cde4c7f22dd7f080d179bd2057309f4f6a3bd9428732b5", + "bdc0e62b2afaf2eef84f62baacaf9c66d32e7e091c9b2c986f196bb9b5418254", + "f865e3537b0566699cd2cd8f0ccc49cad2a88780490d72d51e57664aeadd17af", + "bb60ad5abcd3373fbcdcedbc25d0da1b22ab3e13e514551a43995c915939954d", + "3c48c4224f27ce63662003a5408faf8ce1b4b98772ff3f8f1e47bf6a38399911", + "6bb474e1f0a0596da8e167b2f338327f192b173b291113dd5899909540a4ad83", + "559f5b3ce5b3476e70ed9ac21a1852a7e6530e798e864b627c3c2c2e3a04ff26", + "3cb0c5972e7b766d5eb77b9821d67d98b2a5caf3b7fe7b948db7abc53bcbaa1d", + "c9c0dda4fc2fe2e5e47521a0541aac460c098a8b21a2ebdd1324a95622340565", + "b01bf107353ebb075804d46811b763881fe9c5d37609a8f84edd57abc7b6e95e", + "c1c516abf7fe24803cc9fab9fce13f24896918a8fdd20842fced3edc40a06346", + "b62392a4ff10941b060ba484e9278421d22783ac33a07868de0bdf46c140a3ce", + "e5395b5fb9dee6a3e333f804dc3afeace4c942a63f0a71457050ea5f2f626089", + "6ed3e668abae2d769de867ceb287d16d32506254962b1d01f053e1b437f1f93f", + "962e81bfccf903e305a09e1cbb30e5ff2cf13c6e391179d14a1367ba1b0b3fd0" + ], + "root_bytes": "5b59dc93be660001b7744993091cb9017abde79adc96713fe890b659da46a6c8", + "val": "ef9c0147eb32dd5d8af46230dca90da445f0d544bc5ff3df0cf485c68c8f4ba1" +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/merkle_proof/real_proof.json b/axiom-eth/src/ssz/tests/merkle_proof/real_proof.json new file mode 100644 index 00000000..a891c292 --- /dev/null +++ b/axiom-eth/src/ssz/tests/merkle_proof/real_proof.json @@ -0,0 +1,90 @@ +{ + "directions": [ + 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, + 1 + ], + "proof": [ + "1fd2030000000000000000000000000000000000000000000000000000000000", + "ad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f", + "f7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76", + "55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", + "bfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", + "328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", + "2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d", + "1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5", + "c6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff", + "985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7", + "b5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "e71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "feb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "cddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "f893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "1364984a688be918803722cc46da0483f33b02105d3b19c72e06cdc5e3d1bb31", + "9a26432d1dbd10e13353c662f331ddf4fb222038fdb0d34fc0387b91ba2a0c79", + "282c52f515d8af128124878b81d7f69320ef02f90aa481dad6742bdf4c062047", + "fb40bce185e9d7bba1cde4c7f22dd7f080d179bd2057309f4f6a3bd9428732b5", + "bdc0e62b2afaf2eef84f62baacaf9c66d32e7e091c9b2c986f196bb9b5418254", + "f865e3537b0566699cd2cd8f0ccc49cad2a88780490d72d51e57664aeadd17af", + "bb60ad5abcd3373fbcdcedbc25d0da1b22ab3e13e514551a43995c915939954d", + "3c48c4224f27ce63662003a5408faf8ce1b4b98772ff3f8f1e47bf6a38399911", + "6bb474e1f0a0596da8e167b2f338327f192b173b291113dd5899909540a4ad83", + "559f5b3ce5b3476e70ed9ac21a1852a7e6530e798e864b627c3c2c2e3a04ff26", + "3cb0c5972e7b766d5eb77b9821d67d98b2a5caf3b7fe7b948db7abc53bcbaa1d", + "c9c0dda4fc2fe2e5e47521a0541aac460c098a8b21a2ebdd1324a95622340565", + "b01bf107353ebb075804d46811b763881fe9c5d37609a8f84edd57abc7b6e95e", + "c1c516abf7fe24803cc9fab9fce13f24896918a8fdd20842fced3edc40a06346", + "b62392a4ff10941b060ba484e9278421d22783ac33a07868de0bdf46c140a3ce", + "e5395b5fb9dee6a3e333f804dc3afeace4c942a63f0a71457050ea5f2f626089", + "6ed3e668abae2d769de867ceb287d16d32506254962b1d01f053e1b437f1f93f", + "962e81bfccf903e305a09e1cbb30e5ff2cf13c6e391179d14a1367ba1b0b3fd0" + ], + "root_bytes": "e8cd2d3c1d619a21f551a7f6ff4b6ae801aca61de539fe4abf7157512841f9b7", + "val": "ef9c0147eb32dd5d8af46230dca90da445f0d544bc5ff3df0cf485c68c8f4ba1" +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/tests/mod.rs b/axiom-eth/src/ssz/tests/mod.rs new file mode 100644 index 00000000..c442ac9c --- /dev/null +++ b/axiom-eth/src/ssz/tests/mod.rs @@ -0,0 +1,272 @@ +use crate::{providers::from_hex, rlc::circuit::executor::RlcExecutor}; + +use self::test_circuits::{ + SSZAssignedListTestCircuit, SSZBasicTypeTestCircuit, SSZListTestCircuit, SSZVectorTestCircuit, +}; + +use super::*; +use halo2_base::{ + gates::circuit::CircuitBuilderStage, + halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}, +}; +use test_log::test; + +mod test_circuits; + +fn get_test_circuit(input: SSZInput) -> SSZInclusionCircuit { + SSZInclusionCircuit::from_input(input) +} + +fn get_basic_type_test_circuit( + hash_root: &str, + int_bit_size: usize, + value: u64, +) -> SSZBasicTypeTestCircuit { + SSZBasicTypeTestCircuit::from_input(from_hex(hash_root), int_bit_size, value) +} + +fn get_vector_test_circuit( + hash_root: &str, + int_bit_size: usize, + value: Vec, +) -> SSZVectorTestCircuit { + SSZVectorTestCircuit::from_input(from_hex(hash_root), int_bit_size, value) +} + +fn get_list_test_circuit( + hash_root: &str, + int_bit_size: usize, + value: Vec, + len: usize, + max_len: usize, +) -> SSZListTestCircuit { + SSZListTestCircuit::from_input(from_hex(hash_root), int_bit_size, value, len, max_len) +} + +fn get_assigned_list_test_circuit( + hash_root: &str, + int_bit_size: usize, + value: Vec, + len: usize, + max_len: usize, +) -> SSZAssignedListTestCircuit { + SSZAssignedListTestCircuit::from_input(from_hex(hash_root), int_bit_size, value, len, max_len) +} + +//const CACHE_BITS: usize = 10; +const DEGREE: usize = 15; +pub fn test_mock_circuit(input: impl RlcCircuitInstructions) -> bool { + let mut builder = RlcCircuitBuilder::from_stage(CircuitBuilderStage::Mock, 10).use_k(DEGREE); + builder.base.set_lookup_bits(8); + let circuit = RlcExecutor::new(builder, input); + circuit.0.calculate_params(Some(9)); + MockProver::run(DEGREE as u32, &circuit, vec![]).unwrap().verify().is_ok() +} + +pub fn test_valid_input_json(path: String, max_depth: usize) -> bool { + let pf_str = std::fs::read_to_string(path).unwrap(); + let pf: serde_json::Value = serde_json::from_str(pf_str.as_str()).unwrap(); + let val: String = serde_json::from_value(pf["val"].clone()).unwrap(); + let val = from_hex(&val); + let root_bytes: String = serde_json::from_value(pf["root_bytes"].clone()).unwrap(); + let root_bytes = from_hex(&root_bytes); + let pf_strs: Vec = serde_json::from_value(pf["proof"].clone()).unwrap(); + let proof: Vec> = pf_strs.into_iter().map(|pf| from_hex(&pf)).collect(); + let directions: Vec = serde_json::from_value(pf["directions"].clone()).unwrap(); + let depth = proof.len(); + let input = SSZInput { val, root_bytes, proof, directions, depth, max_depth }; + let input = get_test_circuit(input); + test_mock_circuit(input) +} + +pub fn test_basic_type_json(path: String) -> bool { + let pf_str = std::fs::read_to_string(path).unwrap(); + let pf: serde_json::Value = serde_json::from_str(pf_str.as_str()).unwrap(); + let val: u64 = serde_json::from_value(pf["value"].clone()).unwrap(); + let hash_root: String = serde_json::from_value(pf["hash_root"].clone()).unwrap(); + let int_bit_size: usize = serde_json::from_value(pf["bit_size"].clone()).unwrap(); + let input = get_basic_type_test_circuit(&hash_root, int_bit_size, val); + test_mock_circuit(input) +} + +pub fn test_vector_json(path: String) -> bool { + let pf_str = std::fs::read_to_string(path).unwrap(); + let pf: serde_json::Value = serde_json::from_str(pf_str.as_str()).unwrap(); + let val: Vec = serde_json::from_value(pf["value"].clone()).unwrap(); + let hash_root: String = serde_json::from_value(pf["hash_root"].clone()).unwrap(); + let int_bit_size: usize = serde_json::from_value(pf["bit_size"].clone()).unwrap(); + let input = get_vector_test_circuit(&hash_root, int_bit_size, val); + test_mock_circuit(input) +} + +pub fn test_list_json(path: String) -> bool { + let pf_str = std::fs::read_to_string(path).unwrap(); + let pf: serde_json::Value = serde_json::from_str(pf_str.as_str()).unwrap(); + let val: Vec = serde_json::from_value(pf["value"].clone()).unwrap(); + let hash_root: String = serde_json::from_value(pf["hash_root"].clone()).unwrap(); + let int_bit_size: usize = serde_json::from_value(pf["bit_size"].clone()).unwrap(); + let max_len: usize = serde_json::from_value(pf["max_len"].clone()).unwrap(); + let len: usize = serde_json::from_value(pf["len"].clone()).unwrap(); + let input = get_list_test_circuit(&hash_root, int_bit_size, val, len, max_len); + test_mock_circuit(input) +} + +pub fn test_assigned_list_json(path: String) -> bool { + let pf_str = std::fs::read_to_string(path).unwrap(); + let pf: serde_json::Value = serde_json::from_str(pf_str.as_str()).unwrap(); + let val: Vec = serde_json::from_value(pf["value"].clone()).unwrap(); + let hash_root: String = serde_json::from_value(pf["hash_root"].clone()).unwrap(); + let int_bit_size: usize = serde_json::from_value(pf["bit_size"].clone()).unwrap(); + let max_len: usize = serde_json::from_value(pf["max_len"].clone()).unwrap(); + let len: usize = serde_json::from_value(pf["len"].clone()).unwrap(); + let input = get_assigned_list_test_circuit(&hash_root, int_bit_size, val, len, max_len); + test_mock_circuit(input) +} + +#[test] +pub fn test_mock_ssz_merkle_proof() -> Result<(), Box> { + match test_valid_input_json("src/ssz/tests/merkle_proof/proof.json".to_string(), 10) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_misdirected_ssz_merkle_proof() -> Result<(), Box> { + match test_valid_input_json("src/ssz/tests/merkle_proof/misdirected_proof.json".to_string(), 10) + { + true => panic!("Should not have verified"), + false => Ok(()), + } +} + +#[test] +pub fn test_incorrect_ssz_merkle_proof() -> Result<(), Box> { + match test_valid_input_json("src/ssz/tests/merkle_proof/incorrect_proof.json".to_string(), 10) { + true => panic!("Should not have verified"), + false => Ok(()), + } +} + +#[test] +pub fn test_real_ssz_merkle_proof() -> Result<(), Box> { + match test_valid_input_json("src/ssz/tests/merkle_proof/real_proof.json".to_string(), 41) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_validator_into_beacon_state() -> Result<(), Box> { + match test_valid_input_json("src/ssz/tests/merkle_proof/real_beacon_proof.json".to_string(), 50) + { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_byte_basic_type_root() -> Result<(), Box> { + match test_basic_type_json("src/ssz/tests/basic_types/byte.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_bool_basic_type_root() -> Result<(), Box> { + match test_basic_type_json("src/ssz/tests/basic_types/bool.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_u64_basic_type_root() -> Result<(), Box> { + match test_basic_type_json("src/ssz/tests/basic_types/u64.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_u16_basic_type_root() -> Result<(), Box> { + match test_basic_type_json("src/ssz/tests/basic_types/u16.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_neg_bool_root() -> Result<(), Box> { + match test_basic_type_json("src/ssz/tests/basic_types/neg_bool.json".to_string()) { + true => panic!("Should not have verified"), + false => Ok(()), + } +} + +#[test] +#[should_panic = "assertion failed"] // check this later +pub fn test_neg_byte_root() { + assert!( + !test_basic_type_json("src/ssz/tests/basic_types/neg_byte.json".to_string()), + "Should not have verified" + ); +} + +#[test] +pub fn test_six_u64_root() -> Result<(), Box> { + match test_vector_json("src/ssz/tests/vectors/six_u64.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_unfull_list_root() -> Result<(), Box> { + match test_list_json("src/ssz/tests/lists/unfull_list.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_empty_list_root() -> Result<(), Box> { + match test_list_json("src/ssz/tests/lists/empty_list.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +#[should_panic = "assertion failed"] // check this later +pub fn test_bad_len_list_root() { + assert!( + !test_list_json("src/ssz/tests/lists/bad_len_list.json".to_string()), + "Should not have verified" + ); +} + +#[test] +pub fn test_assigned_unfull_list_root() -> Result<(), Box> { + match test_assigned_list_json("src/ssz/tests/lists/unfull_list.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_assigned_empty_list_root() -> Result<(), Box> { + match test_assigned_list_json("src/ssz/tests/lists/empty_list.json".to_string()) { + true => Ok(()), + false => panic!("Should have verified"), + } +} + +#[test] +pub fn test_assigned_bad_len_list_root() -> Result<(), Box> { + match test_assigned_list_json("src/ssz/tests/lists/bad_len_list.json".to_string()) { + true => panic!("Should not have verified"), + false => Ok(()), + } +} diff --git a/axiom-eth/src/ssz/tests/test_circuits.rs b/axiom-eth/src/ssz/tests/test_circuits.rs new file mode 100644 index 00000000..44364fa4 --- /dev/null +++ b/axiom-eth/src/ssz/tests/test_circuits.rs @@ -0,0 +1,202 @@ +use super::*; + +/// Tests the hash root calculation +pub struct SSZBasicTypeTestCircuit { + pub hash_root: Vec, + pub int_bit_size: usize, + pub value: u64, + _marker: PhantomData, +} + +impl SSZBasicTypeTestCircuit { + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "providers")] + pub fn from_input(hash_root: Vec, int_bit_size: usize, value: u64) -> Self { + Self { hash_root, int_bit_size, value, _marker: PhantomData } + } +} + +impl RlcCircuitInstructions for SSZBasicTypeTestCircuit { + type FirstPhasePayload = (); + fn generate_witnesses_phase0( + &self, + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + ) -> Self::FirstPhasePayload { + let sha = Sha256Chip::new(range); + let ctx = builder.base.main(0); + let ssz = SszChip::new(None, &range, sha); + let hash_root = assign_vec(ctx, self.hash_root.clone(), 32); + let basic_type = SszBasicType::new_from_int(ctx, &range, self.value, self.int_bit_size); + let calc_root = ssz.basic_type_hash_tree_root(ctx, &basic_type); + for (hash_byte, calc_byte) in hash_root.into_iter().zip(calc_root.into_iter()).collect_vec() + { + let diff = range.gate.sub(ctx, hash_byte, calc_byte); + range.gate.assert_is_const(ctx, &diff, &F::ZERO); + } + } + fn generate_witnesses_phase1( + _builder: &mut RlcCircuitBuilder, + _range: &RangeChip, + _rlc: &RlcChip, + _payload: Self::FirstPhasePayload, + ) { + } +} + +pub struct SSZVectorTestCircuit { + pub hash_root: Vec, + pub int_bit_size: usize, + pub value: Vec, + _marker: PhantomData, +} + +impl SSZVectorTestCircuit { + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "providers")] + pub fn from_input(hash_root: Vec, int_bit_size: usize, value: Vec) -> Self { + Self { hash_root, int_bit_size, value, _marker: PhantomData } + } +} + +impl RlcCircuitInstructions for SSZVectorTestCircuit { + type FirstPhasePayload = (); + fn generate_witnesses_phase0( + &self, + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + ) -> Self::FirstPhasePayload { + let sha = Sha256Chip::new(range); + let ctx = builder.base.main(0); + let ssz = SszChip::new(None, &range, sha); + let hash_root = assign_vec(ctx, self.hash_root.clone(), 32); + let basic_type = + SszBasicTypeVector::new_from_ints(ctx, &range, self.value.clone(), self.int_bit_size); + let calc_root = ssz.basic_type_vector_hash_tree_root(ctx, &basic_type); + for (hash_byte, calc_byte) in hash_root.into_iter().zip(calc_root.into_iter()).collect_vec() + { + println!("CALC: {:?}", calc_byte); + println!("HASH: {:?}", hash_byte); + ctx.constrain_equal(&hash_byte, &calc_byte); + } + } + fn generate_witnesses_phase1( + _builder: &mut RlcCircuitBuilder, + _range: &RangeChip, + _rlc: &RlcChip, + _payload: Self::FirstPhasePayload, + ) { + } +} + +pub struct SSZListTestCircuit { + pub hash_root: Vec, + pub int_bit_size: usize, + pub value: Vec, + pub len: usize, + pub max_len: usize, + _marker: PhantomData, +} + +impl SSZListTestCircuit { + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "providers")] + pub fn from_input( + hash_root: Vec, + int_bit_size: usize, + value: Vec, + len: usize, + max_len: usize, + ) -> Self { + Self { hash_root, int_bit_size, value, len, max_len, _marker: PhantomData } + } +} + +impl RlcCircuitInstructions for SSZListTestCircuit { + type FirstPhasePayload = (); + fn generate_witnesses_phase0( + &self, + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + ) -> Self::FirstPhasePayload { + let sha = Sha256Chip::new(range); + let ctx = builder.base.main(0); + let ssz = SszChip::new(None, &range, sha); + let hash_root = assign_vec(ctx, self.hash_root.clone(), 32); + let mut value = self.value.clone(); + value.resize(self.max_len, 0); + let basic_type = + SszBasicTypeList::new_from_ints(ctx, &range, value, self.int_bit_size, self.len); + let calc_root = ssz.basic_type_list_hash_tree_root(ctx, &basic_type); + for (hash_byte, calc_byte) in hash_root.into_iter().zip(calc_root.into_iter()).collect_vec() + { + let diff = ssz.gate().sub(ctx, hash_byte, calc_byte); + ssz.gate().assert_is_const(ctx, &diff, &F::ZERO); + } + } + fn generate_witnesses_phase1( + _builder: &mut RlcCircuitBuilder, + _range: &RangeChip, + _rlc: &RlcChip, + _payload: Self::FirstPhasePayload, + ) { + } +} + +pub struct SSZAssignedListTestCircuit { + pub hash_root: Vec, + pub int_bit_size: usize, + pub value: Vec, + pub len: usize, + pub max_len: usize, + _marker: PhantomData, +} + +impl SSZAssignedListTestCircuit { + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "providers")] + pub fn from_input( + hash_root: Vec, + int_bit_size: usize, + value: Vec, + len: usize, + max_len: usize, + ) -> Self { + Self { hash_root, int_bit_size, value, len, max_len, _marker: PhantomData } + } +} + +impl RlcCircuitInstructions for SSZAssignedListTestCircuit { + type FirstPhasePayload = (); + fn generate_witnesses_phase0( + &self, + builder: &mut RlcCircuitBuilder, + range: &RangeChip, + ) -> Self::FirstPhasePayload { + let sha = Sha256Chip::new(range); + let ctx = builder.base.main(0); + let ssz = SszChip::new(None, &range, sha); + let hash_root = assign_vec(ctx, self.hash_root.clone(), 32); + let mut value = self.value.clone(); + value.resize(self.max_len, 0); + let newvalue = value + .into_iter() + .map(|v| SszBasicType::new_from_int(ctx, &range, v, self.int_bit_size)) + .collect_vec(); + let len = ctx.load_witness(F::from(self.len as u64)); + let basic_type = SszBasicTypeList::new(ctx, &range, newvalue, self.int_bit_size, len); + let calc_root = ssz.basic_type_list_hash_tree_root(ctx, &basic_type); + for (hash_byte, calc_byte) in hash_root.into_iter().zip(calc_root.into_iter()).collect_vec() + { + let diff = ssz.gate().sub(ctx, hash_byte, calc_byte); + ssz.gate().assert_is_const(ctx, &diff, &F::ZERO); + } + } + fn generate_witnesses_phase1( + _builder: &mut RlcCircuitBuilder, + _range: &RangeChip, + _rlc: &RlcChip, + _payload: Self::FirstPhasePayload, + ) { + } +} diff --git a/axiom-eth/src/ssz/tests/vectors/six_u64.json b/axiom-eth/src/ssz/tests/vectors/six_u64.json new file mode 100644 index 00000000..eb00db4d --- /dev/null +++ b/axiom-eth/src/ssz/tests/vectors/six_u64.json @@ -0,0 +1,12 @@ +{ + "hash_root": "4a2913845605cc5c9d68113cb25596cc1899c5a6b106648b395739ca0e4e04c1", + "value": [ + 3, + 3, + 3, + 3, + 3, + 4 + ], + "bit_size": 64 +} \ No newline at end of file diff --git a/axiom-eth/src/ssz/types.rs b/axiom-eth/src/ssz/types.rs new file mode 100644 index 00000000..38a32701 --- /dev/null +++ b/axiom-eth/src/ssz/types.rs @@ -0,0 +1,384 @@ +use crate::Field; +use crate::{ + mpt::AssignedBytes, + ssz::{BASIC_TYPE_BIT_SIZES, NUM_BASIC_TYPES}, + utils::assign_vec, +}; +use halo2_base::{ + gates::{GateInstructions, RangeChip, RangeInstructions}, + utils::ScalarField, + AssignedValue, Context, +}; +use itertools::Itertools; + +use super::SszChip; + +pub type Chunk = AssignedBytes; + +// pub trait SszContainer: SszStruct { +// /// Verifies inclusion of another SszStruct within a SszContainer. +// /// Include own root so that computation isn't wasted on recomputing the root +// fn verify_inclusion( +// &self, +// ctx: &mut Context, +// ssz: &SszChip, +// sha: &mut Sha256Chip, +// root: Chunk, +// proof: SSZInputAssigned, +// field_num: AssignedValue, +// max_fields: usize, +// comp: &dyn SszStruct, +// ) -> SSZInputAssigned { +// let inclusion_val = comp.hash_root(ctx, ssz, sha); +// for i in 0..32 { +// ctx.constrain_equal(&inclusion_val[i], &proof.val[i]); +// } +// for i in 0..32 { +// ctx.constrain_equal(&root[i], &proof.root_bytes[i]); +// } +// ssz.verify_field_hash(ctx, sha, field_num, max_fields, proof) +// } +// } + +pub trait SszStruct { + fn hash_root(&self, ctx: &mut Context, ssz: &SszChip) -> Chunk; +} + +impl SszStruct for SszBasicType { + fn hash_root(&self, ctx: &mut Context, ssz: &SszChip) -> Chunk { + ssz.basic_type_hash_tree_root(ctx, self) + } +} + +impl SszStruct for SszBasicTypeVector { + fn hash_root(&self, ctx: &mut Context, ssz: &SszChip) -> Chunk { + ssz.basic_type_vector_hash_tree_root(ctx, self) + } +} + +impl SszStruct for SszBasicTypeList { + fn hash_root(&self, ctx: &mut Context, ssz: &SszChip) -> Chunk { + ssz.basic_type_list_hash_tree_root(ctx, self) + } +} + +impl SszStruct for Chunk { + fn hash_root(&self, _ctx: &mut Context, _ssz: &SszChip) -> Chunk { + self.to_vec() + } +} + +pub fn num_to_bytes_le(n: u64, max_len: usize) -> Vec { + let mut temp = n; + let mut ans = Vec::new(); + for _ in 0..max_len { + ans.push((temp % 256) as u8); + temp /= 256; + } + assert_eq!(temp, 0); + ans +} + +#[derive(Clone, Debug)] +pub struct SszBasicType { + /// Constrained to be length 32 + value: AssignedBytes, + int_bit_size: usize, +} + +impl SszBasicType { + pub fn new( + ctx: &mut Context, + range: &RangeChip, + value: AssignedBytes, + int_bit_size: usize, + ) -> Self { + let mut is_valid_int_bit_size = false; + for i in 0..NUM_BASIC_TYPES { + if int_bit_size == BASIC_TYPE_BIT_SIZES[i] { + is_valid_int_bit_size = true; + } + } + assert!(is_valid_int_bit_size); + assert!(value.len() == ((int_bit_size + 7) / 8)); + if int_bit_size == 1 { + range.check_less_than_safe(ctx, value[0], 2); + } else { + for i in 0..value.len() { + range.check_less_than_safe(ctx, value[i], 256); + } + } + Self { value, int_bit_size } + } + + pub fn new_from_unassigned_vec( + ctx: &mut Context, + range: &RangeChip, + value: Vec, + int_bit_size: usize, + ) -> Self { + let len = value.len(); + let assigned_bytes = assign_vec(ctx, value, len); + Self::new(ctx, range, assigned_bytes, int_bit_size) + } + + pub fn new_from_int( + ctx: &mut Context, + range: &RangeChip, + value: u64, + int_bit_size: usize, + ) -> Self { + let byte_size = (int_bit_size + 7) / 8; + let value_vec = num_to_bytes_le(value, byte_size); + Self::new_from_unassigned_vec(ctx, range, value_vec, int_bit_size) + } + + pub fn value(&self) -> &AssignedBytes { + &self.value + } + + pub fn int_bit_size(&self) -> usize { + self.int_bit_size + } +} + +#[derive(Clone, Debug)] +pub struct SszBasicTypeVector { + /// Constrained to be length 32 + values: Vec>, + int_bit_size: usize, +} + +impl SszBasicTypeVector { + pub fn new(values: Vec>, int_bit_size: usize) -> Self { + for i in 0..values.len() { + assert!(int_bit_size == values[i].int_bit_size); + } + Self { values, int_bit_size } + } + + pub fn new_from_bytes( + ctx: &mut Context, + range: &RangeChip, + vals: Vec>, + int_bit_size: usize, + ) -> Self { + let mut values = Vec::new(); + for value in vals { + values.push(SszBasicType::new(ctx, range, value, int_bit_size)); + } + Self { values, int_bit_size } + } + + pub fn new_from_unassigned_vecs( + ctx: &mut Context, + range: &RangeChip, + vals: Vec>, + int_bit_size: usize, + ) -> Self { + let mut values = Vec::new(); + for value in vals { + values.push(SszBasicType::new_from_unassigned_vec(ctx, range, value, int_bit_size)); + } + Self { values, int_bit_size } + } + + pub fn new_from_ints( + ctx: &mut Context, + range: &RangeChip, + vals: Vec, + int_bit_size: usize, + ) -> Self { + let mut values = Vec::new(); + for value in vals { + values.push(SszBasicType::new_from_int(ctx, range, value, int_bit_size)); + } + Self { values, int_bit_size } + } + + pub fn values(&self) -> &Vec> { + &self.values + } + + pub fn int_bit_size(&self) -> usize { + self.int_bit_size + } + + pub fn len(&self) -> usize { + self.values.len() + } +} + +#[derive(Clone, Debug)] +pub struct SszBasicTypeList { + /// Constrained to be length 32 + values: Vec>, + int_bit_size: usize, + len: AssignedValue, +} + +impl SszBasicTypeList { + pub fn new( + ctx: &mut Context, + range: &RangeChip, + values: Vec>, + int_bit_size: usize, + len: AssignedValue, + ) -> Self { + range.check_less_than_safe(ctx, len, (values.len() + 1) as u64); + for i in 0..values.len() { + assert!(int_bit_size == values[i].int_bit_size); + } + // safety constraints? + let len_minus_one = range.gate.dec(ctx, len); + let len_minus_one_indicator = range.gate.idx_to_indicator(ctx, len_minus_one, values.len()); + let zero = ctx.load_zero(); + let one = ctx.load_constant(F::from(1)); + let mut pre_len = vec![zero; values.len()]; + pre_len[values.len() - 1] = len_minus_one_indicator[values.len() - 1]; + // creates an indicator for all slots less than len + for i in 1..values.len() { + pre_len[values.len() - 1 - i] = range.gate.add( + ctx, + pre_len[values.len() - i], + len_minus_one_indicator[values.len() - 1 - i], + ); + } + let int_byte_size = (int_bit_size + 7) / 8; + for j in 0..values.len() { + for i in 0..int_byte_size { + let is_zero = range.gate().is_zero(ctx, values[j].value()[i]); + let is_valid = range.gate().or(ctx, is_zero, pre_len[j]); + ctx.constrain_equal(&is_valid, &one); + } + } + Self { values, int_bit_size, len } + } + + pub fn new_mask( + ctx: &mut Context, + range: &RangeChip, + values: Vec>, + int_bit_size: usize, + len: AssignedValue, + ) -> Self { + range.check_less_than_safe(ctx, len, (values.len() + 1) as u64); + for i in 0..values.len() { + assert!(int_bit_size == values[i].int_bit_size); + } + // safety constraints? + let len_minus_one = range.gate.dec(ctx, len); + let len_minus_one_indicator = range.gate.idx_to_indicator(ctx, len_minus_one, values.len()); + let zero = ctx.load_zero(); + let mut pre_len = vec![zero; values.len()]; + pre_len[values.len() - 1] = len_minus_one_indicator[values.len() - 1]; + // creates an indicator for all slots less than len + for i in 1..values.len() { + pre_len[values.len() - 1 - i] = range.gate.add( + ctx, + pre_len[values.len() - i], + len_minus_one_indicator[values.len() - 1 - i], + ); + } + let int_byte_size = (int_bit_size + 7) / 8; + let mut new_list = Vec::new(); + for j in 0..values.len() { + let mut new_bytes = Vec::new(); + for i in 0..int_byte_size { + let val = range.gate().mul(ctx, values[j].value()[i], pre_len[j]); + new_bytes.push(val); + } + let new_basic = SszBasicType::new(ctx, range, new_bytes, int_bit_size); + new_list.push(new_basic); + } + Self { values: new_list, int_bit_size, len } + } + + pub fn new_from_unassigned_vecs( + ctx: &mut Context, + range: &RangeChip, + vals: Vec>, + int_bit_size: usize, + len: usize, + ) -> Self { + assert!(len <= vals.len()); + for i in len..vals.len() { + for j in 0..32 { + assert!(vals[i][j] == 0); + } + } + let mut values = Vec::new(); + for value in vals { + values.push(SszBasicType::new_from_unassigned_vec(ctx, range, value, int_bit_size)); + } + let len = ctx.load_witness(F::from(len as u64)); + Self { values, int_bit_size, len } + } + + pub fn new_from_ints( + ctx: &mut Context, + range: &RangeChip, + vals: Vec, + int_bit_size: usize, + len: usize, + ) -> Self { + assert!(len <= vals.len()); + for i in len..vals.len() { + assert!(vals[i] == 0); + } + let mut values = Vec::new(); + for value in vals { + values.push(SszBasicType::new_from_int(ctx, range, value, int_bit_size)); + } + let len = ctx.load_witness(F::from(len as u64)); + Self { values, int_bit_size, len } + } + + pub fn values(&self) -> &Vec> { + &self.values + } + + pub fn int_bit_size(&self) -> usize { + self.int_bit_size + } + + pub fn len(&self) -> AssignedValue { + self.len + } + + pub fn max_len(&self) -> usize { + self.values.len() + } +} + +#[derive(Debug, Clone)] +pub struct SszVector { + pub values: Vec, +} + +/// Put dummy SszStruct to fill to full capacity +#[derive(Debug, Clone)] +pub struct SszList { + pub values: Vec, + pub len: AssignedValue, +} + +impl> SszStruct for SszVector { + fn hash_root(&self, ctx: &mut Context, ssz: &SszChip) -> Chunk { + let roots = self.values.iter().map(|val| val.hash_root(ctx, ssz)).collect(); + let roots = SszBasicTypeVector::new_from_bytes(ctx, ssz.range, roots, 256); + roots.hash_root(ctx, ssz) + } +} + +impl> SszStruct for SszList { + fn hash_root(&self, ctx: &mut Context, ssz: &SszChip) -> Chunk { + let roots = self + .values + .iter() + .map(|val| SszBasicType { value: val.hash_root(ctx, ssz), int_bit_size: 256 }) + .collect_vec(); + let roots = SszBasicTypeList::new_mask(ctx, ssz.range, roots, 256, self.len); + roots.hash_root(ctx, ssz) + } +} diff --git a/axiom-eth/src/storage/circuit.rs b/axiom-eth/src/storage/circuit.rs new file mode 100644 index 00000000..36b617eb --- /dev/null +++ b/axiom-eth/src/storage/circuit.rs @@ -0,0 +1,161 @@ +use ethers_core::types::{Address, Block, Chain, H256, U256}; +#[cfg(feature = "providers")] +use ethers_providers::{JsonRpcClient, Provider}; +use halo2_base::{gates::GateInstructions, Context}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; +use zkevm_hashes::util::eth_types::ToBigEndian; + +use crate::{ + block_header::get_block_header_rlp_max_lens, + mpt::{MPTChip, MPTInput}, + rlc::{circuit::builder::RlcCircuitBuilder, FIRST_PHASE}, + utils::{ + assign_vec, encode_addr_to_field, encode_h256_to_hilo, eth_circuit::EthCircuitInstructions, + }, + Field, +}; + +use super::{ + EIP1186ResponseDigest, EthBlockAccountStorageWitness, EthBlockStorageInputAssigned, + EthStorageChip, EthStorageInputAssigned, +}; + +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] +pub struct EthStorageInput { + pub addr: Address, + pub acct_pf: MPTInput, + pub acct_state: Vec>, + /// A vector of (slot, value, proof) tuples + pub storage_pfs: Vec<(U256, U256, MPTInput)>, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct EthBlockStorageInput { + pub block: Block, + pub block_number: u32, + pub block_hash: H256, // provided for convenience, actual block_hash is computed from block_header + pub block_header: Vec, + pub storage: EthStorageInput, +} + +impl EthStorageInput { + pub fn assign(self, ctx: &mut Context) -> EthStorageInputAssigned { + let address = encode_addr_to_field(&self.addr); + let address = ctx.load_witness(address); + let acct_pf = self.acct_pf.assign(ctx); + let storage_pfs = self + .storage_pfs + .into_iter() + .map(|(slot, _, pf)| { + let slot = encode_h256_to_hilo(&H256(slot.to_be_bytes())).hi_lo(); + let slot = slot.map(|slot| ctx.load_witness(slot)); + let pf = pf.assign(ctx); + (slot, pf) + }) + .collect(); + EthStorageInputAssigned { address, acct_pf, storage_pfs } + } +} + +impl EthBlockStorageInput { + pub fn assign( + self, + ctx: &mut Context, + network: Chain, + ) -> EthBlockStorageInputAssigned { + // let block_hash = encode_h256_to_field(&self.block_hash); + // let block_hash = block_hash.map(|block_hash| ctx.load_witness(block_hash)); + let storage = self.storage.assign(ctx); + let max_len = get_block_header_rlp_max_lens(network).0; + let block_header = assign_vec(ctx, self.block_header, max_len); + EthBlockStorageInputAssigned { block_header, storage } + } +} + +#[derive(Clone, Debug)] +pub struct EthBlockStorageCircuit { + pub inputs: EthBlockStorageInput, // public and private inputs + pub network: Chain, + _marker: PhantomData, +} + +impl EthBlockStorageCircuit { + pub fn new(inputs: EthBlockStorageInput, network: Chain) -> Self { + Self { inputs, network, _marker: PhantomData } + } + + #[cfg(feature = "providers")] + pub fn from_provider( + provider: &Provider

, + block_number: u32, + address: Address, + slots: Vec, + acct_pf_max_depth: usize, + storage_pf_max_depth: usize, + network: Chain, + ) -> Self { + use crate::providers::storage::get_block_storage_input; + + let inputs = get_block_storage_input( + provider, + block_number, + address, + slots, + acct_pf_max_depth, + storage_pf_max_depth, + ); + Self::new(inputs, network) + } +} + +impl EthCircuitInstructions for EthBlockStorageCircuit { + type FirstPhasePayload = (EthBlockAccountStorageWitness, Chain); + + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + ) -> Self::FirstPhasePayload { + let chip = EthStorageChip::new(mpt, Some(self.network)); + // ================= FIRST PHASE ================ + let ctx = builder.base.main(FIRST_PHASE); + let input = self.inputs.clone().assign(ctx, self.network); + let (witness, digest) = chip.parse_eip1186_proofs_from_block_phase0(builder, input); + let EIP1186ResponseDigest { + block_hash, + block_number, + address, + slots_values, + address_is_empty, + slot_is_empty, + } = digest; + let assigned_instances = block_hash + .into_iter() + .chain([block_number, address]) + .chain(slots_values.into_iter().flat_map(|(slot, value)| slot.into_iter().chain(value))) + .collect_vec(); + assert_eq!(builder.base.assigned_instances.len(), 1); + builder.base.assigned_instances[0] = assigned_instances; + // 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.base.main(FIRST_PHASE); + mpt.gate().assert_is_const(ctx, &address_is_empty, &F::ZERO); + for slot_is_empty in slot_is_empty { + mpt.gate().assert_is_const(ctx, &slot_is_empty, &F::ZERO); + } + } + (witness, self.network) + } + + fn virtual_assign_phase1( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + (witness, network): Self::FirstPhasePayload, + ) { + let chip = EthStorageChip::new(mpt, Some(network)); + let _trace = chip.parse_eip1186_proofs_from_block_phase1(builder, witness); + } +} diff --git a/axiom-eth/src/storage/mod.rs b/axiom-eth/src/storage/mod.rs new file mode 100644 index 00000000..60649783 --- /dev/null +++ b/axiom-eth/src/storage/mod.rs @@ -0,0 +1,470 @@ +use ethers_core::types::Chain; +use getset::Getters; +use halo2_base::{ + gates::{flex_gate::threads::parallelize_core, GateChip, RangeChip}, + safe_types::{SafeAddress, SafeBytes32, SafeTypeChip}, + AssignedValue, Context, +}; +use itertools::Itertools; + +use crate::{ + block_header::{EthBlockHeaderChip, EthBlockHeaderTrace, EthBlockHeaderWitness}, + keccak::KeccakChip, + mpt::{MPTChip, MPTProof, MPTProofWitness}, + rlc::{ + chip::RlcChip, + circuit::builder::{RlcCircuitBuilder, RlcContextPair}, + types::RlcTrace, + FIRST_PHASE, + }, + rlp::{ + types::{RlpArrayWitness, RlpFieldWitness}, + RlpChip, + }, + utils::{bytes_be_to_u128, uint_to_bytes_be, AssignedH256}, + Field, +}; + +pub mod circuit; +#[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 const NUM_ACCOUNT_STATE_FIELDS: usize = 4; +pub const ACCOUNT_STATE_FIELDS_MAX_BYTES: [usize; NUM_ACCOUNT_STATE_FIELDS] = [8, 12, 32, 32]; +#[allow(dead_code)] +pub 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; +#[allow(dead_code)] +pub(crate) const STORAGE_PROOF_KEY_MAX_BYTE_LEN: usize = 32; + +/// Stores Account rlcs to be used in later functions. Is returned by `parse_account_proof_phase1`. +#[derive(Clone, Debug)] +pub struct EthAccountTrace { + pub nonce_trace: RlcTrace, + pub balance_trace: RlcTrace, + pub storage_root_trace: RlcTrace, + pub code_hash_trace: RlcTrace, +} + +/// Stores Account information to be used in later functions. Is returned by `parse_account_proof_phase0`. +#[derive(Clone, Debug, Getters)] +pub struct EthAccountWitness { + pub address: SafeAddress, + #[getset(get = "pub")] + pub(crate) array_witness: RlpArrayWitness, + #[getset(get = "pub")] + pub(crate) mpt_witness: MPTProofWitness, +} + +impl EthAccountWitness { + 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] + } +} + +/// Stores the rlc of a value in a storage slot. Is returned by `parse_storage_proof_phase1`. +#[derive(Clone, Debug)] +pub struct EthStorageTrace { + pub value_trace: RlcTrace, +} + +/// Stores storage slot information as well as a proof of inclusion to be verified in parse_storage_phase1. Is returned +/// by `parse_storage_phase0`. +#[derive(Clone, Debug, Getters)] +pub struct EthStorageWitness { + pub slot: SafeBytes32, + #[getset(get = "pub")] + pub(crate) value_witness: RlpFieldWitness, + #[getset(get = "pub")] + pub(crate) mpt_witness: MPTProofWitness, +} + +/// Stores the rlcs for an account in a block, and the rlcs of slots in the account. Is returned by `parse_eip1186_proofs_from_block_phase1`. +#[derive(Clone, Debug)] +pub struct EthBlockAccountStorageTrace { + pub block_trace: EthBlockHeaderTrace, + pub acct_trace: EthAccountTrace, + pub storage_trace: Vec>, +} + +/// Stores a block, an account, and multiple storage witnesses. Is returned by `parse_eip1186_proofs_from_block_phase0`. +#[derive(Clone, Debug)] +pub struct EthBlockAccountStorageWitness { + pub block_witness: EthBlockHeaderWitness, + pub acct_witness: EthAccountWitness, + pub storage_witness: Vec>, +} + +/// Returns public instances from `parse_eip1186_proofs_from_block_phase0`. +#[derive(Clone, Debug)] +pub struct EIP1186ResponseDigest { + pub block_hash: AssignedH256, + pub block_number: AssignedValue, + pub address: AssignedValue, + // the value U256 is interpreted as H256 (padded with 0s on left) + /// Pairs of (slot, value) where value is a left padded with 0s to fixed width 32 bytes + pub slots_values: Vec<(AssignedH256, AssignedH256)>, + pub address_is_empty: AssignedValue, + pub slot_is_empty: Vec>, +} + +/// Chip to prove correctness of account and storage proofs +pub struct EthStorageChip<'chip, F: Field> { + pub mpt: &'chip MPTChip<'chip, F>, + /// The network to use for block header decoding. Must be provided if using functions that prove into block header (as opposed to state / storage root) + pub network: Option, +} + +impl<'chip, F: Field> EthStorageChip<'chip, F> { + pub fn new(mpt: &'chip MPTChip<'chip, F>, network: Option) -> Self { + Self { mpt, network } + } + + pub fn gate(&self) -> &GateChip { + self.mpt.gate() + } + + pub fn range(&self) -> &RangeChip { + self.mpt.range() + } + + pub fn rlc(&self) -> &RlcChip { + self.mpt.rlc() + } + + pub fn rlp(&self) -> RlpChip { + self.mpt.rlp() + } + + pub fn keccak(&self) -> &KeccakChip { + self.mpt.keccak() + } + /// Does inclusion/exclusion proof of `key = keccak(addr)` into an alleged MPT state trie. Alleged means the proof is with respect to an alleged stateRoot. + /// 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]. + /// + /// Does input validation of `proof` (e.g., checking witnesses are bytes). + pub fn parse_account_proof_phase0( + &self, + ctx: &mut Context, + address: SafeAddress, + proof: MPTProof, + ) -> EthAccountWitness { + assert_eq!(32, proof.key_bytes.len()); + + // check key is keccak(addr) + let hash_query = self.keccak().keccak_fixed_len(ctx, address.as_ref().to_vec()); + let hash_addr = hash_query.output_bytes.as_ref(); + + 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(), + &ACCOUNT_STATE_FIELDS_MAX_BYTES, + false, + ); + // Check MPT inclusion for: + // keccak(addr) => RLP([nonce, balance, storage_root, code_hash]) + let mpt_witness = self.mpt.parse_mpt_inclusion_phase0(ctx, proof); + + EthAccountWitness { address, array_witness, mpt_witness } + } + + /// SecondPhase of account proof parsing. + pub fn parse_account_proof_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + witness: EthAccountWitness, + ) -> 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.mpt.parse_mpt_inclusion_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) + .field_trace + .try_into() + .unwrap(); + let [nonce_trace, balance_trace, storage_root_trace, code_hash_trace] = + array_trace.map(|trace| trace.field_trace); + EthAccountTrace { nonce_trace, balance_trace, storage_root_trace, code_hash_trace } + } + + /// Does multiple calls to [`parse_account_proof_phase0`] in parallel. + pub fn parse_account_proofs_phase0( + &self, + builder: &mut RlcCircuitBuilder, + addr_proofs: Vec<(SafeAddress, MPTProof)>, + ) -> Vec> { + parallelize_core(builder.base.pool(0), addr_proofs, |ctx, (addr, proof)| { + self.parse_account_proof_phase0(ctx, addr, proof) + }) + } + + /// SecondPhase of account proofs parsing. + pub fn parse_account_proofs_phase1( + &self, + builder: &mut RlcCircuitBuilder, + acct_witness: Vec>, + ) -> Vec> { + // rlc cache is loaded globally when `builder` was constructed; no longer done here to avoid concurrency issues + builder.parallelize_phase1(acct_witness, |(ctx_gate, ctx_rlc), witness| { + self.parse_account_proof_phase1((ctx_gate, ctx_rlc), witness) + }) + } + + /// Does inclusion/exclusion proof of `keccak(slot)` into an alleged MPT storage trie. Alleged means the proof is with respect to an alleged storageRoot. + /// + /// 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. + /// + /// Will do input validation on `proof` (e.g., checking witnesses are bytes). + pub fn parse_storage_proof_phase0( + &self, + ctx: &mut Context, + slot: SafeBytes32, + proof: MPTProof, + ) -> EthStorageWitness { + assert_eq!(32, proof.key_bytes.len()); + + // check key is keccak(slot) + let hash_query = self.keccak().keccak_fixed_len(ctx, slot.as_ref().to_vec()); + let hash_bytes = hash_query.output_bytes.as_ref(); + + for (hash, key) in hash_bytes.iter().zip_eq(proof.key_bytes.iter()) { + ctx.constrain_equal(hash, key); + } + + // 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.mpt.parse_mpt_inclusion_phase0(ctx, proof); + EthStorageWitness { slot, value_witness, mpt_witness } + } + + /// SecondPhase of storage proof parsing. + pub fn parse_storage_proof_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + witness: EthStorageWitness, + ) -> EthStorageTrace { + // 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.mpt.parse_mpt_inclusion_phase1((ctx_gate, ctx_rlc), witness.mpt_witness); + // load rlc_cache bit_length(value_witness.rlp_field.len()) + let value_trace = + self.rlp().decompose_rlp_field_phase1((ctx_gate, ctx_rlc), witness.value_witness); + let value_trace = value_trace.field_trace; + debug_assert_eq!(value_trace.max_len, 32); + EthStorageTrace { value_trace } + } + + /// Does multiple calls to [`parse_storage_proof_phase0`] in parallel. + pub fn parse_storage_proofs_phase0( + &self, + builder: &mut RlcCircuitBuilder, + slot_proofs: Vec<(SafeBytes32, MPTProof)>, + ) -> Vec> { + parallelize_core(builder.base.pool(0), slot_proofs, |ctx, (slot, proof)| { + self.parse_storage_proof_phase0(ctx, slot, proof) + }) + } + + /// SecondPhase of account proofs parsing. + pub fn parse_storage_proofs_phase1( + &self, + builder: &mut RlcCircuitBuilder, + storage_witness: Vec>, + ) -> Vec> { + // rlc cache is loaded globally when `builder` was constructed; no longer done here to avoid concurrency issues + builder.parallelize_phase1(storage_witness, |(ctx_gate, ctx_rlc), witness| { + self.parse_storage_proof_phase1((ctx_gate, ctx_rlc), witness) + }) + } + + /// Does inclusion/exclusion proof of `key = keccak(addr)` into an alleged MPT state trie. Alleged means the proof is with respect to an alleged stateRoot. + /// + /// 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. + pub fn parse_eip1186_proofs_phase0( + &self, + builder: &mut RlcCircuitBuilder, + addr: SafeAddress, + acct_pf: MPTProof, + storage_pfs: Vec<(SafeBytes32, MPTProof)>, // (slot_bytes, storage_proof) + ) -> (EthAccountWitness, Vec>) { + // TODO: spawn separate thread for account proof; just need to get storage_root first somehow + let ctx = builder.base.main(FIRST_PHASE); + let acct_trace = self.parse_account_proof_phase0(ctx, addr, acct_pf); + // ctx dropped + let storage_root = &acct_trace.get_storage_root().field_cells; + let storage_trace = + parallelize_core(builder.base.pool(0), storage_pfs, |ctx, (slot, storage_pf)| { + let witness = self.parse_storage_proof_phase0(ctx, 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) + } + + /// SecondPhase of `parse_eip1186_proofs_phase0` + pub fn parse_eip1186_proofs_phase1( + &self, + builder: &mut RlcCircuitBuilder, + (acct_witness, storage_witness): (EthAccountWitness, Vec>), + ) -> (EthAccountTrace, Vec>) { + let (ctx_gate, ctx_rlc) = builder.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(builder, storage_witness); + + (acct_trace, storage_trace) + } + + /// Proves (multiple) storage proofs into storageRoot, prove account proof of storageRoot into stateRoot, and proves stateRoot is an RLP decoded block header. + /// Computes the block hash by hashing the RLP encoded block header. + /// In other words, proves block, account, storage with respect to an alleged block hash. + // inputs have H256 represented in (hi,lo) format as two u128s + // block number and slot values can be derived from the final trace output + pub fn parse_eip1186_proofs_from_block_phase0( + &self, + builder: &mut RlcCircuitBuilder, + input: EthBlockStorageInputAssigned, + ) -> (EthBlockAccountStorageWitness, EIP1186ResponseDigest) { + let ctx = builder.base.main(FIRST_PHASE); + let address = input.storage.address; + let block_header = input.block_header; + let block_witness = { + let block_header_chip = + EthBlockHeaderChip::new_from_network(self.rlp(), self.network.unwrap()); + block_header_chip.decompose_block_header_phase0(ctx, self.keccak(), &block_header) + }; + + let state_root = &block_witness.get_state_root().field_cells; + let block_hash_hi_lo = block_witness.get_block_hash_hi_lo(); + + // compute block number from big-endian bytes + let block_number = block_witness.get_number_value(ctx, self.gate()); + + // verify account + storage proof + let addr_bytes = uint_to_bytes_be(ctx, self.range(), &address, 20); + let (slots, storage_pfs): (Vec<_>, Vec<_>) = input + .storage + .storage_pfs + .into_iter() + .map(|(slot, storage_pf)| { + let slot_bytes = + slot.iter().map(|u128| uint_to_bytes_be(ctx, self.range(), u128, 16)).concat(); + (slot, (slot_bytes.try_into().unwrap(), storage_pf)) + }) + .unzip(); + // drop ctx + let (acct_witness, storage_witness) = self.parse_eip1186_proofs_phase0( + builder, + addr_bytes.try_into().unwrap(), + input.storage.acct_pf, + storage_pfs, + ); + + let ctx = builder.base.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.field_cells.clone(); + let value_bytes_len = witness.value_witness.field_len; + let var_bytes = + SafeTypeChip::unsafe_to_var_len_bytes_vec(value_bytes, value_bytes_len, 32); + let value_bytes = var_bytes.left_pad_to_fixed(ctx, self.gate()); + let value: [_; 2] = + bytes_be_to_u128(ctx, self.gate(), value_bytes.bytes()).try_into().unwrap(); + (slot, value) + }) + .collect_vec(); + let digest = EIP1186ResponseDigest { + block_hash: block_hash_hi_lo, + block_number, + address, + slots_values, + address_is_empty: acct_witness.mpt_witness.slot_is_empty, + slot_is_empty: storage_witness + .iter() + .map(|witness| witness.mpt_witness.slot_is_empty) + .collect_vec(), + }; + (EthBlockAccountStorageWitness { block_witness, acct_witness, storage_witness }, digest) + } + + pub fn parse_eip1186_proofs_from_block_phase1( + &self, + builder: &mut RlcCircuitBuilder, + witness: EthBlockAccountStorageWitness, + ) -> EthBlockAccountStorageTrace { + let block_trace = { + let block_header_chip = + EthBlockHeaderChip::new_from_network(self.rlp(), self.network.unwrap()); + block_header_chip + .decompose_block_header_phase1(builder.rlc_ctx_pair(), witness.block_witness) + }; + let (acct_trace, storage_trace) = self + .parse_eip1186_proofs_phase1(builder, (witness.acct_witness, witness.storage_witness)); + EthBlockAccountStorageTrace { block_trace, acct_trace, storage_trace } + } +} + +/// Account and storage proof inputs in compressed form +#[derive(Clone, Debug)] +pub struct EthStorageInputAssigned { + pub address: AssignedValue, // U160 + pub acct_pf: MPTProof, + pub storage_pfs: Vec<(AssignedH256, MPTProof)>, // (slot, proof) where slot is H256 as (u128, u128) +} + +#[derive(Clone, Debug)] +pub struct EthBlockStorageInputAssigned { + // block_hash: AssignedH256, // H256 as (u128, u128) + /// The RLP encoded block header for the block that alleged contains the stateRoot from `storage`. + pub block_header: Vec>, + /// Account proof and (multiple) storage proofs, with respect to an alleged stateRoot. + pub storage: EthStorageInputAssigned, +} diff --git a/axiom-eth/src/storage/tests.rs b/axiom-eth/src/storage/tests.rs new file mode 100644 index 00000000..bb856179 --- /dev/null +++ b/axiom-eth/src/storage/tests.rs @@ -0,0 +1,190 @@ +use super::{circuit::EthBlockStorageCircuit, *}; +use crate::{ + providers::setup_provider, + rlc::{circuit::RlcCircuitParams, tests::get_rlc_params}, + utils::eth_circuit::{create_circuit, EthCircuitParams}, +}; +use ark_std::{end_timer, start_timer}; +use ethers_core::{ + types::{Address, H256}, + utils::keccak256, +}; +use halo2_base::{ + gates::circuit::BaseCircuitParams, + utils::{ + fs::gen_srs, + testing::{check_proof_with_instances, gen_proof_with_instances}, + }, +}; +use halo2_base::{ + gates::circuit::CircuitBuilderStage, + halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr, plonk::*}, +}; +use serde::{Deserialize, Serialize}; +use std::{fs::File, io::Write}; +use test_log::test; + +fn get_test_circuit(network: Chain, num_slots: usize) -> EthBlockStorageCircuit { + let provider = setup_provider(network); + let addr; + let block_number; + match network { + Chain::Mainnet => { + // cryptopunks + addr = "0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB".parse::

().unwrap(); + block_number = 16356350; + //block_number = 0xf929e6; + } + Chain::Goerli => { + addr = "0xf2d1f94310823fe26cfa9c9b6fd152834b8e7849".parse::
().unwrap(); + block_number = 0x713d54; + } + _ => { + todo!() + } + } + // For only occupied slots: + let slot_nums = [0u64, 1u64, 2u64, 3u64, 6u64, 8u64]; + let mut slots = vec![]; + slots.extend(slot_nums.iter().map(|x| H256::from_low_u64_be(*x))); + slots.extend((0..num_slots.saturating_sub(slot_nums.len())).map(|x| { + let mut bytes = [0u8; 64]; + bytes[31] = x as u8; + bytes[63] = 10; + H256::from_slice(&keccak256(bytes)) + })); + slots.truncate(num_slots); + EthBlockStorageCircuit::from_provider(&provider, block_number, addr, slots, 13, 13, network) +} + +#[test] +pub fn test_mock_single_eip1186() { + let params = get_rlc_params("configs/tests/storage.json"); + let k = params.base.k as u32; + + let input = get_test_circuit(Chain::Mainnet, 1); + let mut circuit = create_circuit(CircuitBuilderStage::Mock, params, input); + circuit.mock_fulfill_keccak_promises(None); + circuit.calculate_params(); + let instances = circuit.instances(); + MockProver::run(k, &circuit, instances).unwrap().assert_satisfied(); +} + +fn get_test_circuit_detailed_tether( + network: Chain, + slot_nums: Vec, +) -> EthBlockStorageCircuit { + assert!(slot_nums.len() <= 10); + let provider = setup_provider(network); + let addr; + let block_number; + match network { + Chain::Mainnet => { + addr = "0xdAC17F958D2ee523a2206206994597C13D831ec7".parse::
().unwrap(); + block_number = 16799999; + } + Chain::Goerli => { + addr = "0xdAC17F958D2ee523a2206206994597C13D831ec7".parse::
().unwrap(); + block_number = 16799999; + } + _ => todo!(), + } + // For only occupied slots: + let slots = slot_nums; + EthBlockStorageCircuit::from_provider(&provider, block_number, addr, slots, 8, 9, network) +} + +#[test] +pub fn test_mock_small_val() { + let params = get_rlc_params("configs/tests/storage.json"); + let k = params.base.k as u32; + let lower: u128 = 0xdfad11d8b97bedfbd5b2574864aec982; + let upper: u128 = 0x015130eac76c1a0c44f4cd1dcd859cd8; + let mut bytes: [u8; 32] = [0; 32]; + bytes[..16].copy_from_slice(&upper.to_be_bytes()); + bytes[16..].copy_from_slice(&lower.to_be_bytes()); + let slot = H256(bytes); + let input = get_test_circuit_detailed_tether(Chain::Mainnet, vec![slot]); + let mut circuit = create_circuit(CircuitBuilderStage::Mock, params, input); + circuit.mock_fulfill_keccak_promises(None); + circuit.calculate_params(); + let instances = circuit.instances(); + MockProver::run(k, &circuit, instances).unwrap().assert_satisfied(); +} + +#[derive(Serialize, Deserialize)] +struct BenchParams(RlcCircuitParams, usize); // (params, num_slots) + +#[test] +#[ignore = "bench"] +pub fn bench_eip1186() -> Result<(), Box> { + let bench_params_file = File::create("configs/bench/storage.json").unwrap(); + std::fs::create_dir_all("data/bench")?; + let mut fs_results = File::create("data/bench/storage.csv").unwrap(); + writeln!(fs_results, "degree,num_slots,total_advice,num_rlc_columns,num_advice,num_lookup,num_fixed,proof_time,verify_time")?; + + let mut all_bench_params = vec![]; + let bench_k_num = vec![(15, 1), (18, 10), (20, 32)]; + for (k, num_slots) in bench_k_num { + println!("---------------------- degree = {k} ------------------------------",); + let input = get_test_circuit(Chain::Mainnet, num_slots); + let mut dummy_params = EthCircuitParams::default().rlc; + dummy_params.base.k = k; + let mut circuit = create_circuit(CircuitBuilderStage::Keygen, dummy_params, input.clone()); + circuit.mock_fulfill_keccak_promises(None); + circuit.calculate_params(); + + let params = gen_srs(k as u32); + let vk = keygen_vk(¶ms, &circuit)?; + let pk = keygen_pk(¶ms, vk, &circuit)?; + let bench_params = circuit.params().rlc; + let break_points = circuit.break_points(); + + // create a proof + let proof_time = start_timer!(|| "create proof SHPLONK"); + let phase0_time = start_timer!(|| "phase 0 synthesize"); + let circuit = create_circuit(CircuitBuilderStage::Prover, bench_params.clone(), input) + .use_break_points(break_points); + circuit.mock_fulfill_keccak_promises(None); + let instances = circuit.instances(); + let instances = instances.iter().map(|x| &x[..]).collect_vec(); + end_timer!(phase0_time); + let proof = gen_proof_with_instances(¶ms, &pk, circuit, &instances); + end_timer!(proof_time); + + let verify_time = start_timer!(|| "Verify time"); + check_proof_with_instances(¶ms, pk.get_vk(), &proof, &instances, true); + end_timer!(verify_time); + + let RlcCircuitParams { + base: + BaseCircuitParams { + k, + num_advice_per_phase, + num_fixed, + num_lookup_advice_per_phase, + .. + }, + num_rlc_columns, + } = bench_params.clone(); + writeln!( + fs_results, + "{},{},{},{},{:?},{:?},{},{:.2}s,{:?}", + k, + num_slots, + num_rlc_columns + + num_advice_per_phase.iter().sum::() + + num_lookup_advice_per_phase.iter().sum::(), + num_rlc_columns, + num_advice_per_phase, + num_lookup_advice_per_phase, + num_fixed, + proof_time.time.elapsed().as_secs_f64(), + verify_time.time.elapsed() + ) + .unwrap(); + all_bench_params.push(BenchParams(bench_params, num_slots)); + } + serde_json::to_writer_pretty(bench_params_file, &all_bench_params).unwrap(); + Ok(()) +} diff --git a/axiom-eth/src/transaction/mod.rs b/axiom-eth/src/transaction/mod.rs new file mode 100644 index 00000000..b36703ac --- /dev/null +++ b/axiom-eth/src/transaction/mod.rs @@ -0,0 +1,519 @@ +use std::cmp::max; + +use ethers_core::{ + types::{Block, Chain, Transaction, H256}, + utils::hex::FromHex, +}; +use halo2_base::{ + gates::{ + flex_gate::threads::parallelize_core, GateChip, GateInstructions, RangeChip, + RangeInstructions, + }, + AssignedValue, Context, + QuantumCell::{Constant, Existing}, +}; +use itertools::Itertools; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; + +use crate::{ + block_header::{ + get_block_header_rlp_max_lens, EthBlockHeaderChip, EthBlockHeaderTrace, + EthBlockHeaderWitness, + }, + keccak::KeccakChip, + mpt::{MPTChip, MPTInput, MPTProof, MPTProofWitness}, + rlc::{ + chip::RlcChip, + circuit::builder::{RlcCircuitBuilder, RlcContextPair}, + FIRST_PHASE, + }, + rlp::{evaluate_byte_array, RlpChip}, + utils::circuit_utils::constrain_no_leading_zeros, + Field, +}; + +// pub mod helpers; +#[cfg(all(test, feature = "providers"))] +mod tests; +mod types; + +pub use types::*; + +lazy_static! { + static ref KECCAK_RLP_EMPTY_STRING: Vec = + Vec::from_hex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").unwrap(); +} + +// type 0 tx has 9 fields +// type 1 tx has 11 fields +// type 2 tx has 12 fields +pub const TRANSACTION_MAX_FIELDS: usize = 12; +pub(crate) const TRANSACTION_TYPE_0_FIELDS_MAX_BYTES: [usize; TRANSACTION_MAX_FIELDS] = + [32, 32, 32, 20, 32, 0, 8, 32, 32, 1, 1, 1]; +pub(crate) const TRANSACTION_TYPE_1_FIELDS_MAX_BYTES: [usize; TRANSACTION_MAX_FIELDS] = + [8, 32, 32, 32, 20, 32, 0, 0, 1, 32, 32, 1]; +pub(crate) const TRANSACTION_TYPE_2_FIELDS_MAX_BYTES: [usize; TRANSACTION_MAX_FIELDS] = + [8, 8, 8, 8, 32, 20, 32, 0, 0, 1, 32, 32]; +pub(crate) const TRANSACTION_IDX_MAX_LEN: usize = 2; +pub const TX_IDX_MAX_BYTES: usize = TRANSACTION_IDX_MAX_LEN; + +/// Calculate max rlp length of a transaction given some parameters +pub fn calc_max_val_len( + max_data_byte_len: usize, + max_access_list_len: usize, + enable_types: [bool; 3], +) -> usize { + let mut t0_len = 2; + // TODO: do not hardcode + let prefix_tot_max = 1 + 4 + 10 + 4 * 2; + let mut field_len_sum = max_data_byte_len; + for field_len in TRANSACTION_TYPE_0_FIELDS_MAX_BYTES { + field_len_sum += field_len; + } + if enable_types[0] { + t0_len = max(t0_len, prefix_tot_max + field_len_sum); + } + field_len_sum = max_data_byte_len + max_access_list_len; + for field_len in TRANSACTION_TYPE_1_FIELDS_MAX_BYTES { + field_len_sum += field_len; + } + if enable_types[1] { + t0_len = max(t0_len, prefix_tot_max + field_len_sum); + } + field_len_sum = max_data_byte_len + max_access_list_len; + for field_len in TRANSACTION_TYPE_2_FIELDS_MAX_BYTES { + field_len_sum += field_len; + } + if enable_types[2] { + t0_len = max(t0_len, prefix_tot_max + field_len_sum); + } + // Transaction and variable len fields have a prefix at most 3, others have prefix at most 1. + // Add 1 for the transaction type byte + t0_len +} + +/// Calculate the max capacity needed to contain fields at all positions across all transaction types. +fn calc_max_field_len( + max_data_byte_len: usize, + max_access_list_len: usize, + enable_types: [bool; 3], +) -> Vec { + let mut base = vec![0; TRANSACTION_MAX_FIELDS]; + base[0] = 1; + for i in 0..TRANSACTION_MAX_FIELDS { + if enable_types[0] { + if i == 5 { + base[i] = max(base[i], max_data_byte_len); + } else { + base[i] = max(base[i], TRANSACTION_TYPE_0_FIELDS_MAX_BYTES[i]); + } + } + if enable_types[1] { + if i == 6 { + base[i] = max(base[i], max_data_byte_len); + } else if i == 7 { + base[i] = max(base[i], max_access_list_len); + } else { + base[i] = max(base[i], TRANSACTION_TYPE_1_FIELDS_MAX_BYTES[i]); + } + } + if enable_types[2] { + if i == 7 { + base[i] = max(base[i], max_data_byte_len); + } else if i == 8 { + base[i] = max(base[i], max_access_list_len); + } else { + base[i] = max(base[i], TRANSACTION_TYPE_2_FIELDS_MAX_BYTES[i]); + } + } + } + base +} + +/// Configuration parameters to construct [EthTransactionChip] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Hash, Default)] +pub struct EthTransactionChipParams { + /// Sets the `max_field_length` for possible positions for the `data` field. + pub max_data_byte_len: usize, + /// Sets the `max_field_length` for possible positions for the `accessList` field. + pub max_access_list_len: usize, + /// Specifies which transaction types [0x0, 0x1, 0x2] this chip supports + pub enable_types: [bool; 3], + /// Must provide network to use functions involving block header + pub network: Option, +} + +/// Chip that supports functions that prove transactions and transaction fields +#[derive(Clone, Debug)] +pub struct EthTransactionChip<'chip, F: Field> { + pub mpt: &'chip MPTChip<'chip, F>, + pub params: EthTransactionChipParams, +} + +impl<'chip, F: Field> EthTransactionChip<'chip, F> { + pub fn new(mpt: &'chip MPTChip<'chip, F>, params: EthTransactionChipParams) -> Self { + Self { mpt, params } + } + + pub fn gate(&self) -> &GateChip { + self.mpt.gate() + } + + pub fn range(&self) -> &RangeChip { + self.mpt.range() + } + + pub fn rlc(&self) -> &RlcChip { + self.mpt.rlc() + } + + pub fn rlp(&self) -> RlpChip { + self.mpt.rlp() + } + + pub fn keccak(&self) -> &KeccakChip { + self.mpt.keccak() + } + + pub fn mpt(&self) -> &'chip MPTChip<'chip, F> { + self.mpt + } + + pub fn network(&self) -> Option { + self.params.network + } + + pub fn block_header_chip(&self) -> EthBlockHeaderChip { + EthBlockHeaderChip::new_from_network( + self.rlp(), + self.network().expect("Must provide network to access block header chip"), + ) + } + + /// FirstPhase of proving the inclusion **or** exclusion of a transaction index within a transaction root. + /// In the case of + /// - inclusion: then parses the transaction + /// - exclusion: `input.proof.slot_is_empty` is true, and we return `tx_type = -1, value = rlp(0x00)`. + pub fn parse_transaction_proof_phase0( + &self, + ctx: &mut Context, + input: EthTransactionInputAssigned, + ) -> EthTransactionWitness { + let EthTransactionChipParams { + max_data_byte_len, max_access_list_len, enable_types, .. + } = self.params; + let EthTransactionInputAssigned { transaction_index, proof } = input; + // Load value early to avoid borrow errors + let slot_is_empty = proof.slot_is_empty; + + let all_disabled = !(enable_types[0] || enable_types[1] || enable_types[2]); + // check key is rlp(idx): + // given rlp(idx), parse idx as var len bytes + let idx_witness = self.rlp().decompose_rlp_field_phase0( + ctx, + proof.key_bytes.clone(), + TRANSACTION_IDX_MAX_LEN, + ); + // evaluate idx to number + let tx_idx = + evaluate_byte_array(ctx, self.gate(), &idx_witness.field_cells, idx_witness.field_len); + // check idx equals provided transaction_index from input + ctx.constrain_equal(&tx_idx, &transaction_index); + constrain_no_leading_zeros( + ctx, + self.gate(), + &idx_witness.field_cells, + idx_witness.field_len, + ); + + // check MPT inclusion + let mpt_witness = self.mpt.parse_mpt_inclusion_phase0(ctx, proof); + // parse transaction + // when we disable all types, we use that as a flag to parse dummy values which are two bytes long + let max_field_lens = + calc_max_field_len(max_data_byte_len, max_access_list_len, enable_types); + if all_disabled { + let one = ctx.load_constant(F::ONE); + ctx.constrain_equal(&slot_is_empty, &one); + } + // type > 0 are stored as {0x01, 0x02} . encode(tx) + let type_is_not_zero = + self.range().is_less_than(ctx, mpt_witness.value_bytes[0], Constant(F::from(128)), 8); + // if the first byte is greater than 0xf7, the type is zero. Otherwise, the type is the first byte. + let mut tx_type = self.gate().mul(ctx, type_is_not_zero, mpt_witness.value_bytes[0]); + let max_val_len = if all_disabled { + 2_usize + } else { + calc_max_val_len(max_data_byte_len, max_access_list_len, enable_types) + }; + debug_assert!(max_val_len > 1); + let mut new_value_witness = Vec::with_capacity(max_val_len); + let slot_is_full = self.gate().not(ctx, slot_is_empty); + tx_type = self.gate().mul(ctx, tx_type, slot_is_full); + // tx_type = -1 if and only if the slot is empty, serves as a flag + tx_type = self.gate().sub(ctx, tx_type, slot_is_empty); + // parse the zeroes string if the slot is empty so that we don't run into errors + for i in 0..max_val_len { + let mut val_byte = self.gate().select( + ctx, + mpt_witness + .value_bytes + .get(i + 1) + .map(|a| Existing(*a)) + .unwrap_or(Constant(F::ZERO)), + mpt_witness.value_bytes[i], + type_is_not_zero, + ); + val_byte = if i == 0 { + // 0xc100 = rlp(0x00) + self.gate().select(ctx, val_byte, Constant(F::from(0xc1)), slot_is_full) + } else { + self.gate().mul(ctx, val_byte, slot_is_full) + }; + new_value_witness.push(val_byte); + } + let value_witness = + self.rlp().decompose_rlp_array_phase0(ctx, new_value_witness, &max_field_lens, true); + EthTransactionWitness { + transaction_type: tx_type, + idx: tx_idx, + idx_witness, + value_witness, + mpt_witness, + } + } + + /// SecondPhase of proving inclusion **or** exclusion of a transaction index in transaction root, and then parses + /// the transaction. See [`parse_transaction_proof_phase0`] for more details. + pub fn parse_transaction_proof_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + witness: EthTransactionWitness, + ) -> EthTransactionTrace { + // Comments below just to log what load_rlc_cache calls are done in the internal functions: + self.rlp().decompose_rlp_field_phase1((ctx_gate, ctx_rlc), witness.idx_witness); + // load_rlc_cache bit_length(2*mpt_witness.key_byte_len) + self.mpt.parse_mpt_inclusion_phase1((ctx_gate, ctx_rlc), witness.mpt_witness); + // load rlc_cache bit_length(value_witness.rlp_field.len()) + let value_trace = + self.rlp().decompose_rlp_array_phase1((ctx_gate, ctx_rlc), witness.value_witness, true); + let value_trace = value_trace.field_trace; + EthTransactionTrace { transaction_type: witness.transaction_type, value_trace } + } + + /// Parallelizes `parse_transaction_proof_phase0`. + pub fn parse_transaction_proofs_phase0( + &self, + builder: &mut RlcCircuitBuilder, + input: Vec>, + ) -> Vec> { + parallelize_core(builder.base.pool(0), input, |ctx, input| { + self.parse_transaction_proof_phase0(ctx, input) + }) + } + + /// Parallelizes `parse_transaction_proof_phase1` + pub fn parse_transaction_proofs_phase1( + &self, + builder: &mut RlcCircuitBuilder, + transaction_witness: Vec>, + ) -> Vec> { + // rlc cache should be loaded globally, no longer done here + builder.parallelize_phase1(transaction_witness, |(ctx_gate, ctx_rlc), witness| { + self.parse_transaction_proof_phase1((ctx_gate, ctx_rlc), witness) + }) + } + + /// FirstPhase of proving the inclusion **or** exclusion of transactions into the block header of a given block. + /// Also parses the transaction into fields. + /// + /// If `input.len_proof` is Some, then we prove the total number of transactions in this block. + /// + /// This performs [`EthBlockHeaderChip::decompose_block_header_phase0`] (single-threaded) and then multi-threaded `parse_transaction_proof_phase0`. + pub fn parse_transaction_proofs_from_block_phase0( + &self, + builder: &mut RlcCircuitBuilder, + input: EthBlockTransactionsInputAssigned, + ) -> EthBlockTransactionsWitness { + let block_witness = { + let ctx = builder.base.main(FIRST_PHASE); + let block_header = input.block_header; + self.block_header_chip().decompose_block_header_phase0( + ctx, + self.keccak(), + &block_header, + ) + }; + let transactions_root = &block_witness.get_transactions_root().field_cells; + + // verify transaction proofs + let transaction_witness = { + parallelize_core(builder.base.pool(FIRST_PHASE), input.tx_inputs, |ctx, input| { + let witness = self.parse_transaction_proof_phase0(ctx, input); + // check MPT root is transactions_root + for (pf_byte, byte) in + witness.mpt_witness.root_hash_bytes.iter().zip_eq(transactions_root.iter()) + { + ctx.constrain_equal(pf_byte, byte); + } + witness + }) + }; + // ctx dropped + let (len, len_witness) = if let Some(len_proof) = input.len_proof { + // we calculate and prove the total number of transactions in this block + let ctx = builder.base.main(FIRST_PHASE); + let one = ctx.load_constant(F::ONE); + let is_empty = { + let mut is_empty = one; + let mut empty_hash = Vec::with_capacity(32); + for i in 0..32 { + empty_hash.push(ctx.load_constant(F::from(KECCAK_RLP_EMPTY_STRING[i] as u64))); + } + for (pf_byte, byte) in empty_hash.iter().zip(transactions_root.iter()) { + let byte_match = self.gate().is_equal(ctx, *pf_byte, *byte); + is_empty = self.gate().and(ctx, is_empty, byte_match); + } + is_empty + }; + let inclusion_idx = len_proof[0].transaction_index; + let noninclusion_idx = len_proof[1].transaction_index; + let diff = self.gate().sub(ctx, noninclusion_idx, inclusion_idx); + // If non_empty, the difference should be equal to 1 + let correct_diff = self.gate().is_equal(ctx, diff, one); + // If empty, the second index should be 0 + let correct_empty = self.gate().is_zero(ctx, noninclusion_idx); + let correct = self.gate().or(ctx, correct_diff, correct_empty); + ctx.constrain_equal(&correct, &one); + // Constrains that the first is an inclusion proof and that the latter is a noninclusion proof + // If empty, the first can be a noninclusion proof + let slot_is_full = self.gate().not(ctx, len_proof[0].proof.slot_is_empty); + // If transaction trie is empty, then `noninclusion_idx` must equal `0`. + // Otherwise the proof for `inclusion_idx` must be an inclusion proof. + let inclusion_constraint = + self.gate().select(ctx, correct_empty, slot_is_full, is_empty); + ctx.constrain_equal(&inclusion_constraint, &one); + ctx.constrain_equal(&len_proof[1].proof.slot_is_empty, &one); + // Checks that the proofs are correct + ( + Some(noninclusion_idx), + Some(len_proof.map(|tx_input| { + let witness = self.parse_transaction_proof_phase0(ctx, tx_input); + // check MPT root is transactions_root + for (pf_byte, byte) in + witness.mpt_witness.root_hash_bytes.iter().zip(transactions_root.iter()) + { + ctx.constrain_equal(pf_byte, byte); + } + witness + })), + ) + } else { + (None, None) + }; + + EthBlockTransactionsWitness { block_witness, transaction_witness, len, len_witness } + } + + /// SecondPhase of proving the inclusion of transactions into the transaction root of a given block. + /// Also parses the transaction into fields. + pub fn parse_transaction_proofs_from_block_phase1( + &self, + builder: &mut RlcCircuitBuilder, + witness: EthBlockTransactionsWitness, + ) -> EthBlockTransactionsTrace { + let block_trace = self + .block_header_chip() + .decompose_block_header_phase1(builder.rlc_ctx_pair(), witness.block_witness); + let transaction_trace = + self.parse_transaction_proofs_phase1(builder, witness.transaction_witness); + let len_trace = witness + .len_witness + .map(|len_witness| self.parse_transaction_proofs_phase1(builder, len_witness.to_vec())); + EthBlockTransactionsTrace { block_trace, transaction_trace, len: witness.len, len_trace } + } + + /// Combination of `decompose_block_header_phase0` and `parse_transaction_proof_phase0`. + /// Constrains that the `transaction_root` is contained in the `block_header`. + /// + /// The difference between this function and `parse_transaction_proofs_from_block_phase0` is that this function + /// is entirely single-threaded, so you can then further parallelize the entire function. + /// + /// This _will_ range check `block_header` to be bytes. + pub fn parse_transaction_proof_from_block_phase0( + &self, + ctx: &mut Context, + block_header: &[AssignedValue], + tx_input: EthTransactionInputAssigned, + ) -> (EthBlockHeaderWitness, EthTransactionWitness) { + let block_witness = self.block_header_chip().decompose_block_header_phase0( + ctx, + self.keccak(), + block_header, + ); + let transactions_root = &block_witness.get_transactions_root().field_cells; + // check MPT root of transaction_witness is block_witness.transaction_root + let transaction_witness = { + let witness = self.parse_transaction_proof_phase0(ctx, tx_input); + // check MPT root is transactions_root + for (pf_byte, byte) in + witness.mpt_witness.root_hash_bytes.iter().zip_eq(transactions_root.iter()) + { + ctx.constrain_equal(pf_byte, byte); + } + witness + }; + (block_witness, transaction_witness) + } + + /// Combination of `decompose_block_header_phase1` and `parse_transaction_proof_phase1`. Single-threaded. + pub fn parse_transaction_proof_from_block_phase1( + &self, + (ctx_gate, ctx_rlc): RlcContextPair, + block_witness: EthBlockHeaderWitness, + tx_witness: EthTransactionWitness, + ) -> (EthBlockHeaderTrace, EthTransactionTrace) { + let block_trace = self + .block_header_chip() + .decompose_block_header_phase1((ctx_gate, ctx_rlc), block_witness); + let transaction_trace = + self.parse_transaction_proof_phase1((ctx_gate, ctx_rlc), tx_witness); + (block_trace, transaction_trace) + } + + /// Extracts the field at `field_idx` from the given rlp decomposition of a transaction. + /// + /// Constrains that `witness` must be an inclusion proof of the transaction into transaction root + /// (a priori it could be exclusion proof). + pub fn extract_field( + &self, + ctx: &mut Context, + witness: EthTransactionWitness, + field_idx: AssignedValue, + ) -> EthTransactionFieldWitness { + let field_witness = &witness.value_witness.field_witness; + let slot_is_empty = witness.mpt_witness.slot_is_empty; + let ans_len = field_witness.iter().map(|w| w.field_cells.len()).max().unwrap(); + let indicator = self.gate().idx_to_indicator(ctx, field_idx, TRANSACTION_MAX_FIELDS); + assert_eq!(field_witness.len(), TRANSACTION_MAX_FIELDS); + let zero = ctx.load_zero(); + ctx.constrain_equal(&slot_is_empty, &zero); + let mut field_bytes = Vec::with_capacity(ans_len); + for i in 0..ans_len { + let entries = field_witness.iter().map(|w| *w.field_cells.get(i).unwrap_or(&zero)); + let field_byte = self.gate().select_by_indicator(ctx, entries, indicator.clone()); + field_bytes.push(field_byte); + } + let lens = field_witness.iter().map(|w| w.field_len); + let len = self.gate().select_by_indicator(ctx, lens, indicator); + EthTransactionFieldWitness { + transaction_type: witness.transaction_type, + transaction_witness: witness, + field_idx, + field_bytes, + len, + max_len: ans_len, + } + } +} diff --git a/axiom-eth/src/transaction/tests/data/field/single_tx_pos_test_legacy.json b/axiom-eth/src/transaction/tests/data/field/single_tx_pos_test_legacy.json new file mode 100644 index 00000000..c03a1dac --- /dev/null +++ b/axiom-eth/src/transaction/tests/data/field/single_tx_pos_test_legacy.json @@ -0,0 +1,9 @@ +{ + "idxs": [ + [ + 257, + 4 + ] + ], + "block_number": 5000008 +} \ No newline at end of file diff --git a/axiom-eth/src/transaction/tests/data/field/single_tx_pos_test_new.json b/axiom-eth/src/transaction/tests/data/field/single_tx_pos_test_new.json new file mode 100644 index 00000000..998198e8 --- /dev/null +++ b/axiom-eth/src/transaction/tests/data/field/single_tx_pos_test_new.json @@ -0,0 +1,9 @@ +{ + "idxs": [ + [ + 208, + 11 + ] + ], + "block_number": 17578525 +} \ No newline at end of file diff --git a/axiom-eth/src/transaction/tests/data/multi_tx_pos_test_legacy.json b/axiom-eth/src/transaction/tests/data/multi_tx_pos_test_legacy.json new file mode 100644 index 00000000..a3cd191a --- /dev/null +++ b/axiom-eth/src/transaction/tests/data/multi_tx_pos_test_legacy.json @@ -0,0 +1,5 @@ +{ + "idxs": [0, 1, 130, 257], + "block_number": 5000050 +} + \ No newline at end of file diff --git a/axiom-eth/src/transaction/tests/data/multi_tx_pos_test_new.json b/axiom-eth/src/transaction/tests/data/multi_tx_pos_test_new.json new file mode 100644 index 00000000..be83a028 --- /dev/null +++ b/axiom-eth/src/transaction/tests/data/multi_tx_pos_test_new.json @@ -0,0 +1,5 @@ +{ + "idxs": [0, 1], + "block_number": 17578525 +} + \ No newline at end of file diff --git a/axiom-eth/src/transaction/tests/data/single_tx_pos_test_legacy.json b/axiom-eth/src/transaction/tests/data/single_tx_pos_test_legacy.json new file mode 100644 index 00000000..134d6092 --- /dev/null +++ b/axiom-eth/src/transaction/tests/data/single_tx_pos_test_legacy.json @@ -0,0 +1,5 @@ +{ + "idxs": [257], + "block_number": 5000008 +} + \ No newline at end of file diff --git a/axiom-eth/src/transaction/tests/data/single_tx_pos_test_new.json b/axiom-eth/src/transaction/tests/data/single_tx_pos_test_new.json new file mode 100644 index 00000000..aecc9df0 --- /dev/null +++ b/axiom-eth/src/transaction/tests/data/single_tx_pos_test_new.json @@ -0,0 +1,5 @@ +{ + "idxs": [2], + "block_number": 17578525 +} + \ No newline at end of file diff --git a/axiom-eth/src/transaction/tests/data/stress_test.json b/axiom-eth/src/transaction/tests/data/stress_test.json new file mode 100644 index 00000000..9a037142 --- /dev/null +++ b/axiom-eth/src/transaction/tests/data/stress_test.json @@ -0,0 +1 @@ +10 \ No newline at end of file diff --git a/axiom-eth/src/transaction/tests/data/zero_tx_pos_test_legacy.json b/axiom-eth/src/transaction/tests/data/zero_tx_pos_test_legacy.json new file mode 100644 index 00000000..11c50660 --- /dev/null +++ b/axiom-eth/src/transaction/tests/data/zero_tx_pos_test_legacy.json @@ -0,0 +1,5 @@ +{ + "idxs": [], + "block_number": 1000 +} + \ No newline at end of file diff --git a/axiom-eth/src/transaction/tests/data/zero_tx_pos_test_new.json b/axiom-eth/src/transaction/tests/data/zero_tx_pos_test_new.json new file mode 100644 index 00000000..11c50660 --- /dev/null +++ b/axiom-eth/src/transaction/tests/data/zero_tx_pos_test_new.json @@ -0,0 +1,5 @@ +{ + "idxs": [], + "block_number": 1000 +} + \ No newline at end of file diff --git a/axiom-eth/src/transaction/tests/field.rs b/axiom-eth/src/transaction/tests/field.rs new file mode 100644 index 00000000..49b10b63 --- /dev/null +++ b/axiom-eth/src/transaction/tests/field.rs @@ -0,0 +1,260 @@ +#![cfg(feature = "providers")] +use crate::utils::assign_vec; + +use super::*; +use ethers_core::types::Chain; +use halo2_base::halo2_proofs::dev::MockProver; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use test_log::test; + +#[derive(Clone, Debug, Hash, Deserialize, Serialize)] +/// Contains block information, a single `EthTransactionInput` for a transaction we want to prove in the block, +/// and a `constrain_len` flag that decides whether the number of transactions in the block should be proved as well. +/// In most cases, constrain_len will be set to `false` and len_proof to `None` when this is used. +pub struct EthBlockTransactionFieldInput { + pub block_number: u32, + pub block_hash: H256, // provided for convenience, actual block_hash is computed from block_header + pub block_header: Vec, + + pub tx_input: EthTransactionFieldInput, + pub constrain_len: bool, + // Inclusion and noninclusion proof pair of neighboring indices + pub len_proof: Option, +} + +#[derive(Clone, Debug)] +/// Assigned version of `EthTransactionFieldInput` +pub struct EthTransactionFieldInputAssigned { + pub transaction: EthTransactionInputAssigned, + pub field_idx: AssignedValue, +} + +#[derive(Clone, Debug)] +/// Assigned version of `EthBlockTransactionFieldInput` +pub struct EthBlockTransactionFieldInputAssigned { + // block_hash: AssignedH256, // H256 as (u128, u128) + pub block_header: Vec>, + pub single_field: EthTransactionFieldInputAssigned, +} + +impl EthTransactionFieldInput { + pub fn assign(self, ctx: &mut Context) -> EthTransactionFieldInputAssigned { + let transaction = EthTransactionInputAssigned { + transaction_index: ctx.load_witness(F::from(self.transaction_index as u64)), + proof: self.proof.assign(ctx), + }; + let field_idx = ctx.load_witness(F::from(self.field_idx as u64)); + EthTransactionFieldInputAssigned { transaction, field_idx } + } +} + +impl EthBlockTransactionFieldInput { + pub fn assign( + self, + ctx: &mut Context, + network: Chain, + ) -> EthBlockTransactionFieldInputAssigned { + let max_len = get_block_header_rlp_max_lens(network).0; + let block_header = assign_vec(ctx, self.block_header, max_len); + EthBlockTransactionFieldInputAssigned { + block_header, + single_field: self.tx_input.assign(ctx), + } + } +} + +#[derive(Clone)] +pub struct EthBlockTransactionFieldCircuit { + pub inputs: EthBlockTransactionFieldInput, // public and private inputs + pub params: EthTransactionChipParams, + _marker: PhantomData, +} + +impl EthBlockTransactionFieldCircuit { + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "providers")] + pub fn from_provider( + provider: &Provider

, + idxs: Vec<(usize, usize)>, // (tx_idx, field_idx) + block_number: u32, + transaction_pf_max_depth: usize, + network: Chain, + max_data_byte_len: usize, + max_access_list_len: usize, + enable_types: [bool; 3], + constrain_len: bool, + ) -> Self { + use crate::providers::transaction::get_block_transaction_input; + + let tx_idx = idxs.clone().into_iter().map(|p| p.0).collect_vec(); + let inputs = get_block_transaction_input( + provider, + tx_idx, + block_number, + transaction_pf_max_depth, + max_data_byte_len, + max_access_list_len, + enable_types, + constrain_len, + ); + let tx_inputs = inputs + .tx_proofs + .into_iter() + .zip(idxs) + .map(|(tx_proof, (_, field_idx))| EthTransactionFieldInput { + transaction_index: tx_proof.tx_index, + proof: tx_proof.proof, + field_idx, + }) + .collect_vec(); + let inputs = EthBlockTransactionFieldInput { + block_number: inputs.block_number, + block_hash: inputs.block_hash, + block_header: inputs.block_header, + tx_input: tx_inputs[0].clone(), + constrain_len: false, + len_proof: None, + }; + let params = EthTransactionChipParams { + max_data_byte_len, + max_access_list_len, + enable_types, + network: Some(network), + }; + Self { inputs, params, _marker: PhantomData } + } +} + +impl EthCircuitInstructions for EthBlockTransactionFieldCircuit { + type FirstPhasePayload = + (EthBlockHeaderWitness, EthTransactionWitness, EthTransactionChipParams); + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + ) -> Self::FirstPhasePayload { + let chip = EthTransactionChip::new(mpt, self.params); + let ctx = builder.base.main(FIRST_PHASE); + let input = self.inputs.clone().assign(ctx, chip.network().unwrap()); + let (block_witness, tx_witness) = chip.parse_transaction_proof_from_block_phase0( + ctx, + &input.block_header, + input.single_field.transaction, + ); + let _ = chip.extract_field(ctx, tx_witness.clone(), input.single_field.field_idx); + (block_witness, tx_witness, self.params) + } + fn virtual_assign_phase1( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + (block_witness, tx_witness, chip_params): Self::FirstPhasePayload, + ) { + let chip = EthTransactionChip::new(mpt, chip_params); + chip.parse_transaction_proof_from_block_phase1( + builder.rlc_ctx_pair(), + block_witness, + tx_witness, + ); + } +} + +#[derive(Default, Clone, Debug, Serialize, Deserialize)] +pub struct TxFieldProviderInput { + pub idxs: Vec<(usize, usize)>, + pub block_number: usize, +} + +fn get_test_field_circuit( + network: Chain, + idxs: Vec<(usize, usize)>, + block_number: usize, + max_data_byte_len: usize, + max_access_list_len: usize, + enable_types: [bool; 3], +) -> EthBlockTransactionFieldCircuit { + assert!(idxs.len() == 1); + let provider = setup_provider(network); + + EthBlockTransactionFieldCircuit::from_provider( + &provider, + idxs, + block_number.try_into().unwrap(), + 6, + network, + max_data_byte_len, + max_access_list_len, + enable_types, + false, + ) +} + +pub fn test_field_valid_input_json( + path: String, + max_data_byte_len: usize, + max_access_list_len: usize, + enable_types: [bool; 3], +) { + let file_inputs: TxFieldProviderInput = + serde_json::from_reader(File::open(path).expect("path does not exist")).unwrap(); + let idxs = file_inputs.idxs; + let block_number = file_inputs.block_number; + test_field_valid_input_direct( + idxs, + block_number, + max_data_byte_len, + max_access_list_len, + enable_types, + ); +} + +pub fn test_field_valid_input_direct( + idxs: Vec<(usize, usize)>, + block_number: usize, + max_data_byte_len: usize, + max_access_list_len: usize, + enable_types: [bool; 3], +) { + let params = get_rlc_params("configs/tests/transaction.json"); + let k = params.base.k as u32; + + let input = get_test_field_circuit( + Chain::Mainnet, + idxs, + block_number, + max_data_byte_len, + max_access_list_len, + enable_types, + ); + let mut circuit = create_circuit(CircuitBuilderStage::Mock, params, input); + circuit.mock_fulfill_keccak_promises(None); + circuit.calculate_params(); + let instances = circuit.instances(); + MockProver::run(k, &circuit, instances).unwrap().assert_satisfied(); +} + +#[test] +pub fn test_mock_single_field_legacy() { + test_field_valid_input_direct(vec![(257, 0 /*nonce*/)], 5000008, 256, 0, [true, false, false]); +} + +#[test] +pub fn test_mock_single_field_legacy_json() { + test_field_valid_input_json( + "src/transaction/tests/data/field/single_tx_pos_test_legacy.json".to_string(), + 256, + 0, + [true, false, false], + ); +} + +#[test] +pub fn test_mock_single_field_new_json() { + test_field_valid_input_json( + "src/transaction/tests/data/field/single_tx_pos_test_new.json".to_string(), + 256, + 512, + [true, false, true], + ); +} diff --git a/axiom-eth/src/transaction/tests/mod.rs b/axiom-eth/src/transaction/tests/mod.rs new file mode 100644 index 00000000..20c41395 --- /dev/null +++ b/axiom-eth/src/transaction/tests/mod.rs @@ -0,0 +1,458 @@ +#![cfg(feature = "providers")] +use crate::providers::block::get_block_rlp_from_num; +use crate::providers::setup_provider; +use crate::rlc::circuit::RlcCircuitParams; +use crate::rlc::tests::get_rlc_params; +use crate::utils::eth_circuit::{create_circuit, EthCircuitInstructions, EthCircuitParams}; + +use super::*; +use ark_std::{end_timer, start_timer}; +use halo2_base::gates::circuit::{BaseCircuitParams, CircuitBuilderStage}; +use halo2_base::halo2_proofs::dev::MockProver; + +use ethers_providers::{JsonRpcClient, Provider}; +use halo2_base::halo2_proofs::halo2curves::bn256::Fr; +use halo2_base::halo2_proofs::plonk::{keygen_pk, keygen_vk, Circuit}; +use halo2_base::utils::fs::gen_srs; +use halo2_base::utils::testing::{check_proof_with_instances, gen_proof_with_instances}; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; +use std::{fs::File, io::Write}; +use test_log::test; + +pub mod field; + +#[derive(Clone, Debug)] +pub struct EthBlockTransactionCircuit { + pub inputs: EthBlockTransactionsInput, // public and private inputs + pub params: EthTransactionChipParams, + _marker: PhantomData, +} + +impl EthBlockTransactionCircuit { + #[allow(clippy::too_many_arguments)] + #[cfg(feature = "providers")] + pub fn from_provider( + provider: &Provider

, + idxs: Vec, + block_number: u32, + transaction_pf_max_depth: usize, + network: Chain, + max_data_byte_len: usize, + max_access_list_len: usize, + enable_types: [bool; 3], + constrain_len: bool, + ) -> Self { + use crate::providers::transaction::get_block_transaction_input; + + let inputs = get_block_transaction_input( + provider, + idxs, + block_number, + transaction_pf_max_depth, + max_data_byte_len, + max_access_list_len, + enable_types, + constrain_len, + ); + let params = EthTransactionChipParams { + max_data_byte_len, + max_access_list_len, + enable_types, + network: Some(network), + }; + Self { inputs, params, _marker: PhantomData } + } +} + +impl EthCircuitInstructions for EthBlockTransactionCircuit { + type FirstPhasePayload = (EthBlockTransactionsWitness, EthTransactionChipParams); + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + ) -> Self::FirstPhasePayload { + let ctx = builder.base.main(FIRST_PHASE); + let chip = EthTransactionChip::new(mpt, self.params); + let input = self.inputs.clone().assign(ctx, chip.network().unwrap()); + let witness = chip.parse_transaction_proofs_from_block_phase0(builder, input); + (witness, self.params) + } + fn virtual_assign_phase1( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + (witness, chip_params): Self::FirstPhasePayload, + ) { + let chip = EthTransactionChip::new(mpt, chip_params); + chip.parse_transaction_proofs_from_block_phase1(builder, witness); + } +} + +#[derive(Default, Clone, Debug, Serialize, Deserialize)] +pub struct TxProviderInput { + pub idxs: Vec, + pub block_number: usize, +} + +fn get_test_circuit( + network: Chain, + idxs: Vec, + block_number: usize, + max_data_byte_len: usize, + max_access_list_len: usize, + enable_types: [bool; 3], + constrain_len: bool, +) -> EthBlockTransactionCircuit { + let provider = setup_provider(network); + + EthBlockTransactionCircuit::from_provider( + &provider, + idxs, + block_number.try_into().unwrap(), + 6, + network, + max_data_byte_len, + max_access_list_len, + enable_types, + constrain_len, + ) +} + +pub fn test_valid_input_json( + path: String, + max_data_byte_len: usize, + max_access_list_len: usize, + enable_types: [bool; 3], + constrain_len: bool, +) { + let file_inputs: TxProviderInput = + serde_json::from_reader(File::open(path).expect("path does not exist")).unwrap(); + let idxs = file_inputs.idxs; + let block_number = file_inputs.block_number; + test_valid_input_direct( + idxs, + block_number, + max_data_byte_len, + max_access_list_len, + enable_types, + constrain_len, + ); +} + +pub fn test_valid_input_direct( + idxs: Vec, + block_number: usize, + max_data_byte_len: usize, + max_access_list_len: usize, + enable_types: [bool; 3], + constrain_len: bool, +) { + let params = get_rlc_params("configs/tests/transaction.json"); + let k = params.base.k as u32; + + let input = get_test_circuit( + Chain::Mainnet, + idxs, + block_number, + max_data_byte_len, + max_access_list_len, + enable_types, + constrain_len, + ); + let mut circuit = create_circuit(CircuitBuilderStage::Mock, params, input); + circuit.mock_fulfill_keccak_promises(None); + circuit.calculate_params(); + let instances = circuit.instances(); + MockProver::run(k, &circuit, instances).unwrap().assert_satisfied(); +} + +#[test] +pub fn test_mock_single_tx_legacy() { + test_valid_input_json( + "src/transaction/tests/data/single_tx_pos_test_legacy.json".to_string(), + 256, + 0, + [true, false, false], + false, + ); +} + +#[test] +pub fn test_mock_multi_tx_legacy() { + test_valid_input_json( + "src/transaction/tests/data/multi_tx_pos_test_legacy.json".to_string(), + 256, + 0, + [true, false, false], + false, + ); +} + +#[test] +pub fn test_mock_zero_tx_legacy() { + test_valid_input_json( + "src/transaction/tests/data/zero_tx_pos_test_legacy.json".to_string(), + 256, + 0, + [true, false, false], + false, + ); +} + +#[test] +pub fn test_mock_single_tx_new() { + test_valid_input_json( + "src/transaction/tests/data/single_tx_pos_test_new.json".to_string(), + 256, + 512, + [true, true, true], + false, + ); +} + +#[test] +pub fn test_mock_multi_tx_new() { + test_valid_input_json( + "src/transaction/tests/data/multi_tx_pos_test_new.json".to_string(), + 256, + 512, + [true, false, true], + false, + ); +} + +#[test] +pub fn stress_test() { + let tx_num: usize = serde_json::from_reader( + File::open("src/transaction/tests/data/stress_test.json").expect("path does not exist"), + ) + .unwrap(); + let mut idxs = Vec::new(); + for i in 0..tx_num { + idxs.push(i); + } + return test_valid_input_direct(idxs, 5000008, 256, 0, [true, false, false], false); +} + +#[test] +pub fn test_invalid_block_header() { + let params = get_rlc_params("configs/tests/transaction.json"); + let file_inputs: TxProviderInput = serde_json::from_reader( + File::open("src/transaction/tests/data/multi_tx_pos_test_legacy.json") + .expect("path does not exist"), + ) + .unwrap(); + let idxs = file_inputs.idxs; + let block_number = file_inputs.block_number; + let k = params.base.k as u32; + + let mut input = + get_test_circuit(Chain::Mainnet, idxs, block_number, 256, 0, [true, false, false], false); + let provider = setup_provider(Chain::Mainnet); + let new_block_header = get_block_rlp_from_num(&provider, 1000000); + input.inputs.block_header = new_block_header; + let mut circuit = create_circuit(CircuitBuilderStage::Mock, params, input); + circuit.mock_fulfill_keccak_promises(None); + circuit.calculate_params(); + let instances = circuit.instances(); + let prover = MockProver::run(k, &circuit, instances).unwrap(); + assert!(prover.verify().is_err()); +} + +/* // ignore for now because an assert fails +#[test] +pub fn test_valid_root_wrong_block_header() -> Result<(), Box> { + let params = EthConfigParams::from_path("configs/tests/transaction.json"); + let file_inputs: TxProviderInput = serde_json::from_reader( + File::open("src/transaction/tests/data/multi_tx_pos_test_legacy.json") + .expect("path does not exist"), + ) + .unwrap(); + let idxs = file_inputs.idxs; + let block_number = file_inputs.block_number; + let k = params.degree; + + let mut input = + get_test_circuit(Chain::Mainnet, idxs, block_number, 256, 0, [true, false, false], false); + let provider = setup_provider(Chain::Mainnet); + let blocks = get_blocks(&provider, vec![block_number as u64 + 1]).unwrap(); + let block = blocks[0].clone(); + match block { + None => Ok(()), + Some(mut _block) => { + _block.transactions_root = input.inputs.tx_proofs[0].proof.root_hash; + let new_block_header = get_block_rlp(&_block); // panics because block hash fails + input.inputs.block_header = new_block_header; + let circuit = input.create_circuit(RlcThreadBuilder::mock(), params, None); + MockProver::run(k, &circuit, vec![circuit.instance()]).unwrap().assert_satisfied(); + Ok(()) + } + } +} +*/ + +// Tests if the key = rlp(idx) is correctly constrained +#[test] +pub fn test_invalid_key() { + let params = get_rlc_params("configs/tests/transaction.json"); + let confused_pairs = [(1, 256), (256, 1), (1, 0), (0, 1), (0, 256), (256, 0)]; + let block_number = 5000050; + let k = params.base.k as u32; + for (idx, tx_index) in confused_pairs { + let idxs = vec![idx]; + let mut input = get_test_circuit( + Chain::Mainnet, + idxs, + block_number, + 256, + 0, + [true, false, false], + false, + ); + input.inputs.tx_proofs[0].tx_index = tx_index; + let mut circuit = create_circuit(CircuitBuilderStage::Mock, params.clone(), input); + circuit.mock_fulfill_keccak_promises(None); + circuit.calculate_params(); + let instances = circuit.instances(); + let prover = MockProver::run(k, &circuit, instances).unwrap(); + assert!(prover.verify().is_err(), "Should not have verified"); + } +} + +#[test] +pub fn test_mock_single_tx_len_legacy() { + test_valid_input_json( + "src/transaction/tests/data/single_tx_pos_test_legacy.json".to_string(), + 256, + 0, + [true, false, false], + true, + ); +} + +#[test] +pub fn test_mock_multi_tx_len_legacy() { + test_valid_input_json( + "src/transaction/tests/data/multi_tx_pos_test_legacy.json".to_string(), + 256, + 0, + [true, false, false], + true, + ); +} + +#[test] +pub fn test_mock_zero_len_new() { + test_valid_input_direct([].into(), 1000, 256, 512, [true, false, true], true); +} + +#[test] +pub fn test_mock_zero_len_legacy() { + test_valid_input_direct([].into(), 1000, 256, 512, [true, false, false], true); +} + +#[test] +pub fn test_mock_one_len_new() { + test_valid_input_direct([].into(), 3482144, 256, 512, [true, false, true], true); +} + +#[test] +pub fn test_mock_one_len_legacy() { + test_valid_input_direct([0].into(), 3482144, 256, 512, [true, false, false], true); +} + +#[test] +pub fn test_mock_nonzero_len_new() { + test_valid_input_direct([].into(), 5000008, 256, 512, [true, false, true], true); +} + +#[test] +pub fn test_mock_nonzero_len_legacy() { + test_valid_input_direct([].into(), 5000008, 256, 512, [true, false, false], true); +} + +#[derive(Serialize, Deserialize)] +struct BenchParams(RlcCircuitParams, usize); // (params, num_slots) + +#[test] +#[ignore = "bench"] +pub fn bench_tx() -> Result<(), Box> { + let bench_params_file = File::create("configs/bench/transaction.json").unwrap(); + std::fs::create_dir_all("data/transaction")?; + let mut fs_results = File::create("data/bench/transaction.csv").unwrap(); + writeln!(fs_results, "degree,num_slots,total_advice,num_rlc_columns,num_advice,num_lookup,num_fixed,proof_time,verify_time")?; + + let mut all_bench_params = vec![]; + let bench_k_num = vec![(15, 1), (17, 10), (18, 32)]; + for (k, num_slots) in bench_k_num { + println!("---------------------- degree = {k} ------------------------------",); + let input = get_test_circuit( + Chain::Mainnet, + (0..num_slots).collect(), + 5000008, + 256, + 0, + [true, false, true], + false, + ); + let mut dummy_params = EthCircuitParams::default().rlc; + dummy_params.base.k = k; + let mut circuit = create_circuit(CircuitBuilderStage::Keygen, dummy_params, input.clone()); + circuit.mock_fulfill_keccak_promises(None); + circuit.calculate_params(); + + let params = gen_srs(k as u32); + let vk = keygen_vk(¶ms, &circuit)?; + let pk = keygen_pk(¶ms, vk, &circuit)?; + let bench_params = circuit.params().rlc; + let break_points = circuit.break_points(); + + // create a proof + let proof_time = start_timer!(|| "create proof SHPLONK"); + let phase0_time = start_timer!(|| "phase 0 synthesize"); + let circuit = create_circuit(CircuitBuilderStage::Prover, bench_params.clone(), input) + .use_break_points(break_points); + circuit.mock_fulfill_keccak_promises(None); + let instances = circuit.instances(); + end_timer!(phase0_time); + assert_eq!(instances.len(), 1); + let proof = gen_proof_with_instances(¶ms, &pk, circuit, &[&instances[0]]); + end_timer!(proof_time); + + let verify_time = start_timer!(|| "Verify time"); + check_proof_with_instances(¶ms, pk.get_vk(), &proof, &[&instances[0]], true); + end_timer!(verify_time); + + let RlcCircuitParams { + base: + BaseCircuitParams { + k, + num_advice_per_phase, + num_fixed, + num_lookup_advice_per_phase, + .. + }, + num_rlc_columns, + } = bench_params.clone(); + writeln!( + fs_results, + "{},{},{},{},{:?},{:?},{},{:.2}s,{:?}", + k, + num_slots, + num_rlc_columns + + num_advice_per_phase.iter().sum::() + + num_lookup_advice_per_phase.iter().sum::(), + num_rlc_columns, + num_advice_per_phase, + num_lookup_advice_per_phase, + num_fixed, + proof_time.time.elapsed().as_secs_f64(), + verify_time.time.elapsed() + ) + .unwrap(); + all_bench_params.push(BenchParams(bench_params, num_slots)); + } + serde_json::to_writer_pretty(bench_params_file, &all_bench_params).unwrap(); + Ok(()) +} diff --git a/axiom-eth/src/transaction/types.rs b/axiom-eth/src/transaction/types.rs new file mode 100644 index 00000000..11f3a92c --- /dev/null +++ b/axiom-eth/src/transaction/types.rs @@ -0,0 +1,133 @@ +use getset::Getters; + +use crate::{ + rlp::types::{RlpArrayWitness, RlpFieldTrace, RlpFieldWitness}, + utils::assign_vec, +}; + +use super::*; + +/// Assigned version of [`EthTransactionInput`] +#[derive(Clone, Debug)] +pub struct EthTransactionInputAssigned { + /// idx is the transaction index, varying from 0 to around 500 + pub transaction_index: AssignedValue, + pub proof: MPTProof, +} + +#[derive(Clone, Debug, Getters)] +pub struct EthTransactionWitness { + pub transaction_type: AssignedValue, + pub idx: AssignedValue, + pub(crate) idx_witness: RlpFieldWitness, + #[getset(get = "pub")] + pub(crate) value_witness: RlpArrayWitness, + #[getset(get = "pub")] + pub(crate) mpt_witness: MPTProofWitness, +} + +#[derive(Clone, Debug)] +pub struct EthTransactionTrace { + pub transaction_type: AssignedValue, + pub value_trace: Vec>, +} + +/// Container for extracting a specific field from a single transaction +#[derive(Clone, Debug)] +pub struct EthTransactionFieldWitness { + pub transaction_witness: EthTransactionWitness, + pub transaction_type: AssignedValue, + pub field_idx: AssignedValue, + pub field_bytes: Vec>, + pub len: AssignedValue, + pub max_len: usize, +} + +/// Assigned version of [`EthBlockTransactionsInput`] +#[derive(Clone, Debug)] +pub struct EthBlockTransactionsInputAssigned { + // block_hash: AssignedH256, // H256 as (u128, u128) + pub block_header: Vec>, + pub tx_inputs: Vec>, + // Inclusion and noninclusion proof pair of neighboring indices + pub len_proof: Option<[EthTransactionInputAssigned; 2]>, +} + +#[derive(Clone, Debug)] +pub struct EthBlockTransactionsWitness { + pub block_witness: EthBlockHeaderWitness, + pub transaction_witness: Vec>, + pub len: Option>, + pub len_witness: Option<[EthTransactionWitness; 2]>, +} + +#[derive(Clone, Debug)] +pub struct EthBlockTransactionsTrace { + pub block_trace: EthBlockHeaderTrace, + pub transaction_trace: Vec>, + pub len: Option>, + pub len_trace: Option>>, +} + +// rust native types + +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] +pub struct EthTransactionProof { + pub tx_index: usize, + pub proof: MPTInput, +} + +/// Used to prove total number of transactions in a block +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] +pub struct EthTransactionLenProof { + pub inclusion: EthTransactionProof, + pub noninclusion: EthTransactionProof, +} + +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] +/// Contains an index, a proof, the desired field_idx to be queried. +pub struct EthTransactionFieldInput { + pub transaction_index: usize, + pub proof: MPTInput, + pub field_idx: usize, +} + +/// Contains block information, multiple `EthTransactionInput` for transactions we want to prove in the block, +/// and a `constrain_len` flag that decides whether the number of transactions in the block should be proved as well. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct EthBlockTransactionsInput { + pub block: Block, + pub block_number: u32, + pub block_hash: H256, // provided for convenience, actual block_hash is computed from block_header + pub block_header: Vec, + pub tx_proofs: Vec, + /// Inclusion and noninclusion proof pair of neighboring indices + pub len_proof: Option, +} + +impl EthTransactionProof { + pub fn assign(self, ctx: &mut Context) -> EthTransactionInputAssigned { + // let block_hash = encode_h256_to_field(&self.block_hash); + // let block_hash = block_hash.map(|block_hash| ctx.load_witness(block_hash)); + let transaction_index = ctx.load_witness(F::from(self.tx_index as u64)); + let proof = self.proof.assign(ctx); + EthTransactionInputAssigned { transaction_index, proof } + } +} + +impl EthBlockTransactionsInput { + pub fn assign( + self, + ctx: &mut Context, + network: Chain, + ) -> EthBlockTransactionsInputAssigned { + // let block_hash = encode_h256_to_field(&self.block_hash); + // let block_hash = block_hash.map(|block_hash| ctx.load_witness(block_hash)); + let tx_inputs = self.tx_proofs.into_iter().map(|pf| pf.assign(ctx)).collect(); + let len_proof = + self.len_proof.map(|pf| [pf.inclusion.assign(ctx), pf.noninclusion.assign(ctx)]); + let max_len = get_block_header_rlp_max_lens(network).0; + let block_header = assign_vec(ctx, self.block_header, max_len); + EthBlockTransactionsInputAssigned { block_header, tx_inputs, len_proof } + } +} diff --git a/axiom-eth/src/utils/README.md b/axiom-eth/src/utils/README.md new file mode 100644 index 00000000..d8492d62 --- /dev/null +++ b/axiom-eth/src/utils/README.md @@ -0,0 +1,71 @@ +# Component Framework + +We introduce a new circuit design concept of **Component Circuits** in this crate. + +## Definition of Component Circuit + +A component circuit is a circuit that is designed so that it can: + +- Output a _virtual table_ of `(key, value)` pairs that can then be used by an external circuit + - The output table is virtual because the circuit only outputs a commitment (some kind of hash) of the virtual table, and not the table itself +- Load the virtual output table from another component circuit and be able to look up the value for any given key in the table. + - The loaded virtual table depends on the verification of the other component circuit that generated the table, so we call this loading and lookup process a **promise call**. + - The circuit making the promise call is provided the virtual table as unconstrained private witnesses. It then commits to the table in exactly the same way as the circuit that generated the table, and exposes the commitment as a public instance. The aggregation circuit that verifies both the caller circuit and callee circuit must check that the **promise commitment** and **output commitment** are equal. + - To actually use the loaded table in-circuit, the caller circuit must use a dynamic lookup table: the promise table is loaded into advice columns, and whenever one wants to use a `(key, value)` pair elsewhere in the circuit, a dynamic lookup must be done of `(key, value)` into the assigned advice table. + +Concretely, any component circuit will have a corresponding struct `ComponentType{Name}` that implements the [`ComponentType`](./component/mod.rs) trait. + +### Shards + +The `ComponentType` only specifies the format of the virtual table output by the component circuit - it does not specify how the output commit of the virtual table should be computed. This is because we allow multiple concrete circuit implementations associated to a single component type. + +Concretely, we implement a component circuit as either a **shard** circuit or an **aggregation** circuit. The shard circuit is what actually constrains the correctness of the virtual table. It then commits to the virtual table by performing a flat Poseidon hash of the entire concatenated table. Multiple shard circuits can be aggregated in an aggregation circuit, which will compute the merkle root of the output commitments of the shard circuits. We provide a generic aggregation circuit implementation that does this in [`merkle_aggregation`](./merkle_aggregation.rs). + +In summary, in all of our concrete implementations of component circuits, the output commitment to the virtual table is always a Merkle root of flat hashes of subsections of the table. + +### `component` module + +The [component](./component/) module is designed to automate the above process as much as possible. + +Above, we have not specified the types of `(key, value)`. We provide traits so that `key` can be a variable length array of field elements, and `value` is a fixed length array of field elements (fixed length known at compile time). If one were to directly assign such a virtual table of `(key, value)`s, one would need multiple columns for the length of each `key, value`. While the Halo2 backend will RLC these columns together in the prover backend before performing dynamic lookups, this is only worth it if your table size, or number of lookups performed, is comparable to the number of total rows in your circuit. For our use cases we have found that to rarely be the case, so instead we do not directly assign the virtual table to raw advice columns. We first RLC the `key, value` into a single value (using `RlcCircuitBuilder`) and then assign the RLCed value to a single raw advice column. Then when you need to look up a `(key, value)` pair, you must perform the same RLC on this pair and then dynamically look up the RLCed value into the previously assigned "table". + +One creates a concrete Component Circuit implementation by creating a `ComponentCircuitImpl, P: PromiseBuilder>` struct. One can think of this struct as a combination of three circuit builders (in the sense of the previous section): + +1. `RlcCircuitBuilder`: we have enshrined this circuit builder in this crate as it is integral and used everywhere. +2. `CoreBuilder`: trait +3. `PromiseBuilder`: trait + +### Promise Builder + +The `PromiseBuilder` trait is an interface for any concrete implementation of the circuit builder that controls and automates the promise call process described above: the `PromiseBuilder` controls the process of actually loading the virtual table into this circuit, performing RLCs, and also adding dynamic lookups. It owns new circuit columns corresponding to both the lookup table and columns for values-to-lookup. Because `PromiseBuilder` needs to perform RLCs, the `virtual_assign_*` functions have access to `RlcCircuitBuilder`. + +We have four concrete implementations of `PromiseBuilder` in this crate: + +- `EmptyPromiseLoader`: does nothing, just so you have something to stick into `ComponentCircuitImpl`. This is for component circuits that do not need to make promise calls -- the component circuit only outputs a commitment. +- `PromiseLoader`: the most commonly used. Does exactly what is described above for the virtual promise table output by a _single_ component circuit. +- `MultiPromiseLoader`: if you load _multiple_ virtual tables from separate component circuits, with possibly different `(key, value)` types, but want to concatenate and assign all these tables into the same raw table (with some tag for the component type). +- `PromiseBuilderCombo`: boilerplate for combining two `PromiseBuilder`s and auto-implement `PromiseBuilder` again. + +### Core Builder + +The `CoreBuilder` is where you specify the main business logic of what your circuit should do. +In the main logic, you are also allowed to make promise calls to other component circuits: these requests are relayed to the `PromiseBuilder` via [`PromiseCaller`](./component/promise_collector.rs). We emphasize that `PromiseCaller` is **not** a circuit concept. The `PromiseCaller` is really just a struct that collects promise calls and relays them to the `PromiseBuilder` to actually adds the dynamic lookups. (This is necessary for example to _collect_ the requests that actually need to be sent as inputs to the called component circuit.) +The `PromiseCaller` is a shared thread-safe `PromiseCollector`. +The `PromiseCollector` implements another trait `PromiseCallsGetter`: this trait is exposed to `PromiseBuilder` as the way to get the promise calls to be looked up. + +The `CoreBuilder::virtual_assign_*` gets access to `RlcCircuitBuilder` and `PromiseCaller`. +The `CoreBuilder` can own its own columns/gates, and it does have the ability to raw assign +to these columns, but a common use of `CoreBuilder` is to only specify the `virtual_assign_*` +logic using `RlcCircuitBuilder` and do no additional raw assignments of its own. + +### Component Circuit Implementation + +The synthesis of the above pieces is done in the actual [implementation](./component/circuit/mod.rs) +of the Halo2 `Circuit` trait for `ComponentCircuitImpl`. This will call `virtual_assign_*` for both +`CoreBuilder` and `PromiseBuilder`, and then it will call `raw_synthesize_*` for `CoreBuilder`, `PromiseBuilder`, **and** `RlcCircuitBuilder`. + +In addition, `ComponentCircuitImpl` controls the assignment of public instances, because there are special instances (2, one output commitment, one promise commitment) reserved for Component Circuit usage. The `CoreBuilder` can specify additional public instances to be exposed in the return of `CoreBuilder::virtual_assign_phase0`. + +## Notes + +Many parts of the implementation of the Component Circuit framework (and circuit builders in general) could likely be streamlined with the use of runtime `dyn` and type inference. One main blocker to this is that many circuit related traits are not object-safe, so one cannot use `dyn` on them. In particular, the `Circuit` trait itself is not object safe because one needs to specify `Circuit::Config`. Moreover, there is no way to create `Circuit::Config` without access to a `ConstraintSystem` object. diff --git a/axiom-eth/src/utils/build_utils/aggregation.rs b/axiom-eth/src/utils/build_utils/aggregation.rs new file mode 100644 index 00000000..37a64988 --- /dev/null +++ b/axiom-eth/src/utils/build_utils/aggregation.rs @@ -0,0 +1,20 @@ +use snark_verifier_sdk::{halo2::aggregation::AggregationCircuit, CircuitExt}; + +use crate::utils::snark_verifier::AggregationCircuitParams; + +pub trait CircuitMetadata { + const HAS_ACCUMULATOR: bool; + + fn accumulator_indices() -> Option> { + if Self::HAS_ACCUMULATOR { + AggregationCircuit::accumulator_indices() + } else { + None + } + } + fn num_instance(&self) -> Vec; +} + +pub fn get_dummy_aggregation_params(k: usize) -> AggregationCircuitParams { + AggregationCircuitParams { degree: k as u32, lookup_bits: k - 1, ..Default::default() } +} diff --git a/axiom-eth/src/utils/build_utils/dummy.rs b/axiom-eth/src/utils/build_utils/dummy.rs new file mode 100644 index 00000000..fcafa121 --- /dev/null +++ b/axiom-eth/src/utils/build_utils/dummy.rs @@ -0,0 +1,6 @@ +/// Generates a dummy of this type from a seed. +/// This is used to generate dummy inputs for the circuit. +pub trait DummyFrom { + /// Dummy from a seed. + fn dummy_from(seed: S) -> Self; +} diff --git a/axiom-eth/src/utils/build_utils/keygen.rs b/axiom-eth/src/utils/build_utils/keygen.rs new file mode 100644 index 00000000..a74d5eb5 --- /dev/null +++ b/axiom-eth/src/utils/build_utils/keygen.rs @@ -0,0 +1,118 @@ +use std::{ + fs::File, + io::{BufReader, BufWriter}, + path::Path, +}; + +use anyhow::Context; +use halo2_base::halo2_proofs::poly::commitment::ParamsProver; +use snark_verifier::{util::arithmetic::Domain, verifier::plonk::PlonkProtocol}; +use snark_verifier_sdk::halo2::utils::{ + AggregationDependencyIntent, AggregationDependencyIntentOwned, +}; + +use crate::{ + halo2_base::gates::circuit::BaseCircuitParams, + halo2_proofs::{ + plonk::{ProvingKey, VerifyingKey}, + poly::{commitment::Params, kzg::commitment::ParamsKZG}, + SerdeFormat, + }, + halo2curves::bn256::{Bn256, Fr, G1Affine}, + rlc::circuit::RlcCircuitParams, + utils::keccak::decorator::RlcKeccakCircuitParams, +}; + +pub fn get_dummy_rlc_circuit_params(k: usize, lookup_bits: usize) -> RlcCircuitParams { + RlcCircuitParams { + base: BaseCircuitParams { + k, + lookup_bits: Some(lookup_bits), + num_instance_columns: 1, + ..Default::default() + }, + num_rlc_columns: 0, + } +} + +pub fn get_dummy_rlc_keccak_params(k: usize, lookup_bits: usize) -> RlcKeccakCircuitParams { + let rlc = get_dummy_rlc_circuit_params(k, lookup_bits); + RlcKeccakCircuitParams { rlc, keccak_rows_per_round: 20 } +} + +pub fn get_circuit_id(vk: &VerifyingKey) -> String { + let buf = vk.to_bytes(crate::halo2_proofs::SerdeFormat::RawBytes); + format!("{}", blake3::hash(&buf)) +} + +/// Write vk to separate file just for ease of inspection. +pub fn write_pk_and_pinning( + dir: &Path, + pk: &ProvingKey, + pinning: &impl serde::Serialize, +) -> anyhow::Result { + let circuit_id = get_circuit_id(pk.get_vk()); + let pk_path = dir.join(format!("{circuit_id}.pk")); + let vk_path = dir.join(format!("{circuit_id}.vk")); + let pinning_path = dir.join(format!("{circuit_id}.json")); + + serde_json::to_writer_pretty( + File::create(&pinning_path) + .with_context(|| format!("Failed to create file {}", pinning_path.display()))?, + pinning, + ) + .context("Failed to serialize pinning")?; + pk.get_vk() + .write( + &mut File::create(&vk_path) + .with_context(|| format!("Failed to create file {}", vk_path.display()))?, + SerdeFormat::RawBytes, + ) + .context("Failed to serialize vk")?; + + let mut writer = BufWriter::with_capacity( + 128 * 1024 * 1024, + File::create(&pk_path) + .with_context(|| format!("Failed to create file {}", pk_path.display()))?, + ); // 128 MB capacity + pk.write(&mut writer, SerdeFormat::RawBytes).context("Failed to serialize pk")?; + + Ok(circuit_id) +} + +pub fn read_srs_from_dir(params_dir: &Path, k: u32) -> anyhow::Result> { + let srs_path = params_dir.join(format!("kzg_bn254_{k}.srs")); + let params = ParamsKZG::::read(&mut BufReader::new( + File::open(srs_path.clone()) + .with_context(|| format!("Failed to read SRS from {}", srs_path.display()))?, + ))?; + Ok(params) +} + +/// Converts `intent` into `PlonkProtocol` for pinning. +/// If `universal_agg == true`, that means this intent is going to be a dependency in a universal aggregation circuit. +/// In that case, the domain and preprocessed commitments are loaded as witnesses, so we clear them so they are not stored in the pinning. +pub fn compile_agg_dep_to_protocol( + kzg_params: &ParamsKZG, + intent: &AggregationDependencyIntentOwned, + universal_agg: bool, +) -> PlonkProtocol { + let k = intent.vk.get_domain().k(); + // BAD UX: `compile` for `Config::kzg()` only uses `params` for `params.k()`, so we will just generate a random params with the correct `k`. + // We provide the correct deciding key just for safety + let dummy_params = kzg_params.from_parts( + k, + vec![kzg_params.get_g()[0]], + Some(vec![]), + kzg_params.g2(), + kzg_params.s_g2(), + ); + let mut protocol = AggregationDependencyIntent::from(intent).compile(&dummy_params); + if universal_agg { + protocol.domain = Domain::new(0, Fr::one()); + protocol.preprocessed.clear(); + protocol.transcript_initial_state = None; + } + protocol.domain_as_witness = None; + protocol +} diff --git a/axiom-eth/src/utils/build_utils/mod.rs b/axiom-eth/src/utils/build_utils/mod.rs new file mode 100644 index 00000000..dce8d152 --- /dev/null +++ b/axiom-eth/src/utils/build_utils/mod.rs @@ -0,0 +1,8 @@ +/// Instructions for aggregating a circuit using `snark-verifier` SDK +#[cfg(feature = "aggregation")] +pub mod aggregation; +pub mod dummy; +#[cfg(feature = "keygen")] +pub mod keygen; +/// Circut pinning instructions +pub mod pinning; diff --git a/axiom-eth/src/utils/build_utils/pinning.rs b/axiom-eth/src/utils/build_utils/pinning.rs new file mode 100644 index 00000000..d70cb91c --- /dev/null +++ b/axiom-eth/src/utils/build_utils/pinning.rs @@ -0,0 +1,254 @@ +use halo2_base::{ + gates::{ + circuit::{builder::BaseCircuitBuilder, BaseCircuitParams}, + flex_gate::MultiPhaseThreadBreakPoints, + }, + halo2_proofs::{ + halo2curves::bn256::{Bn256, Fr, G1Affine}, + plonk::{Circuit, ProvingKey}, + poly::kzg::commitment::ParamsKZG, + }, + utils::ScalarField, +}; +use serde::{Deserialize, Serialize}; +use snark_verifier_sdk::gen_pk; +use std::{fs::File, io, path::Path}; + +use crate::{ + rlc::{circuit::RlcCircuitParams, virtual_region::RlcThreadBreakPoints}, + utils::eth_circuit::ETH_LOOKUP_BITS, +}; + +/// Perhaps `CircuitPinning` and `CircuitParams` are the same thing. +/// For now the distinction is that `CircuitParams` is what is needed +/// in `configure` to build the `Circuit::Config` and the pinning can be +/// derived from that by running `synthesize` during keygen. +/// The difference +pub trait Halo2CircuitPinning: Serialize + Sized + for<'de> Deserialize<'de> { + type CircuitParams; + type BreakPoints; + /// Constructor + fn new(params: Self::CircuitParams, break_points: Self::BreakPoints) -> Self; + /// Returns the configuration parameters + fn params(&self) -> Self::CircuitParams; + /// Returns break points + fn break_points(&self) -> Self::BreakPoints; + /// Degree of the circuit, log_2(number of rows) + fn k(&self) -> usize; + /// Loads from a file + fn from_path>(path: P) -> anyhow::Result { + let p = serde_json::from_reader(File::open(&path)?)?; + Ok(p) + } + /// Writes to file + fn write>(&self, path: P) -> anyhow::Result<()> { + serde_json::to_writer_pretty(File::create(path)?, self)?; + Ok(()) + } +} + +pub trait CircuitPinningInstructions { + type Pinning: Halo2CircuitPinning; + + fn pinning(&self) -> Self::Pinning; +} + +pub trait PinnableCircuit: CircuitPinningInstructions + Sized + Circuit { + /// Reads the proving key for the pre-circuit. + fn read_pk( + path: impl AsRef, + circuit_params: >::Params, + ) -> io::Result> { + snark_verifier_sdk::read_pk::(path.as_ref(), circuit_params) + } + + /// 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, + pk_path: impl AsRef, + pinning_path: impl AsRef, + ) -> anyhow::Result<(ProvingKey, Self::Pinning)> { + let circuit_params = self.params(); + if let Ok(pk) = Self::read_pk(pk_path.as_ref(), circuit_params) { + let pinning = Self::Pinning::from_path(pinning_path.as_ref())?; + Ok((pk, pinning)) + } else { + let pk = gen_pk(params, self, Some(pk_path.as_ref())); + // should only write pinning data if we created a new pkey + let pinning = self.pinning(); + pinning.write(pinning_path)?; + Ok((pk, pinning)) + } + } +} + +impl PinnableCircuit for C where C: CircuitPinningInstructions + Sized + Circuit {} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct RlcCircuitPinning { + pub params: RlcCircuitParams, + pub break_points: RlcThreadBreakPoints, +} + +impl Halo2CircuitPinning for RlcCircuitPinning { + type CircuitParams = RlcCircuitParams; + type BreakPoints = RlcThreadBreakPoints; + + fn new(params: Self::CircuitParams, break_points: Self::BreakPoints) -> Self { + Self { params, break_points } + } + + fn from_path>(path: P) -> anyhow::Result { + let mut pinning: Self = serde_json::from_reader(File::open(&path)?)?; + if pinning.params.base.lookup_bits.is_none() { + pinning.params.base.lookup_bits = Some(ETH_LOOKUP_BITS); + } + Ok(pinning) + } + + fn params(&self) -> Self::CircuitParams { + self.params.clone() + } + + fn break_points(&self) -> RlcThreadBreakPoints { + self.break_points.clone() + } + + fn k(&self) -> usize { + self.params.base.k + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Default, Hash)] +pub struct BaseCircuitPinning { + pub params: BaseCircuitParams, + pub break_points: MultiPhaseThreadBreakPoints, +} + +impl Halo2CircuitPinning for BaseCircuitPinning { + type CircuitParams = BaseCircuitParams; + type BreakPoints = MultiPhaseThreadBreakPoints; + + fn new(params: Self::CircuitParams, break_points: Self::BreakPoints) -> Self { + Self { params, break_points } + } + + fn params(&self) -> Self::CircuitParams { + self.params.clone() + } + + fn break_points(&self) -> MultiPhaseThreadBreakPoints { + self.break_points.clone() + } + + fn k(&self) -> usize { + self.params.k + } +} + +impl CircuitPinningInstructions for BaseCircuitBuilder { + type Pinning = BaseCircuitPinning; + fn pinning(&self) -> Self::Pinning { + let break_points = self.break_points(); + let params = self.params(); + Self::Pinning::new(params, break_points) + } +} + +#[cfg(feature = "aggregation")] +pub mod aggregation { + use halo2_base::{ + gates::flex_gate::MultiPhaseThreadBreakPoints, + halo2_proofs::{ + halo2curves::bn256::{Bn256, G1Affine}, + plonk::Circuit, + }, + }; + use serde::{Deserialize, Serialize}; + use serde_with::serde_as; + use snark_verifier::{pcs::kzg::KzgDecidingKey, verifier::plonk::PlonkProtocol}; + use snark_verifier_sdk::halo2::aggregation::{AggregationCircuit, AggregationConfigParams}; + + use super::{CircuitPinningInstructions, Halo2CircuitPinning}; + + #[derive(Clone, Debug, Serialize, Deserialize, Default)] + pub struct AggregationCircuitPinning { + pub params: AggregationConfigParams, + pub break_points: MultiPhaseThreadBreakPoints, + } + + impl Halo2CircuitPinning for AggregationCircuitPinning { + type CircuitParams = AggregationConfigParams; + type BreakPoints = MultiPhaseThreadBreakPoints; + + fn new(params: Self::CircuitParams, break_points: Self::BreakPoints) -> Self { + Self { params, break_points } + } + + fn params(&self) -> Self::CircuitParams { + self.params + } + + fn break_points(&self) -> MultiPhaseThreadBreakPoints { + self.break_points.clone() + } + + fn k(&self) -> usize { + self.params.degree as usize + } + } + + impl CircuitPinningInstructions for AggregationCircuit { + type Pinning = AggregationCircuitPinning; + fn pinning(&self) -> Self::Pinning { + let break_points = self.break_points(); + let params = self.params(); + AggregationCircuitPinning::new(params, break_points) + } + } + + /// Generic aggregation pinning + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct GenericAggPinning { + /// Aggregation configuration parameters + pub params: AggParams, + /// Number of instances in each instance column + pub num_instance: Vec, + /// The indices of the KZG accumulator in the public instance column(s) + pub accumulator_indices: Vec<(usize, usize)>, + /// Aggregate vk hash, if universal aggregation circuit. + /// * ((i, j), agg_vkey_hash), where the hash is located at (i, j) in the public instance columns + pub agg_vk_hash_data: Option<((usize, usize), String)>, + /// g1 generator, g2 generator, s_g2 (s is generator of trusted setup). + /// Together with domain size `2^k`, this commits to the trusted setup used. + /// This is all that's needed to verify the final ecpairing check on the KZG proof. + pub dk: KzgDecidingKey, + /// Break points. Should only have phase0, so MultiPhase is extraneous. + pub break_points: MultiPhaseThreadBreakPoints, + } + + /// Generic aggregation circuit configuration parameters + #[serde_as] + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct GenericAggParams { + /// The compiled verification key of each dependency circuit to be aggregated. + #[serde_as(as = "Vec")] + pub to_agg: Vec>, + pub agg_params: AggregationConfigParams, + } + + /// The circuit IDs of the aggregation tree + #[derive(Clone, Debug, Default, Serialize, Deserialize)] + pub struct AggTreeId { + /// Root circuit ID + pub circuit_id: String, + /// Children aggregation tree IDs + pub children: Vec, + /// If the root circuit is a universal aggregation circuit, this is the aggregate vkey hash. + /// None otherwise. + #[serde(skip_serializing_if = "Option::is_none")] + pub aggregate_vk_hash: Option, + } +} diff --git a/axiom-eth/src/utils/circuit_utils/bytes.rs b/axiom-eth/src/utils/circuit_utils/bytes.rs new file mode 100644 index 00000000..d248d9e9 --- /dev/null +++ b/axiom-eth/src/utils/circuit_utils/bytes.rs @@ -0,0 +1,126 @@ +use crate::Field; +use crate::{ + mpt::MPTProof, + utils::{bytes_be_to_u128, bytes_be_to_uint}, +}; +use halo2_base::{ + gates::GateInstructions, + safe_types::{SafeBool, SafeByte, SafeBytes32, SafeTypeChip}, + utils::ScalarField, + AssignedValue, Context, +}; + +use crate::utils::hilo::HiLo; + +/// Takes `bytes` as fixed length byte array, left pads with 0s, and then converts +/// to HiLo form. Optmization where if `bytes` is less than 16 bytes, it can +/// skip the Hi part. +pub fn pack_bytes_to_hilo( + ctx: &mut Context, + gate: &impl GateInstructions, + bytes: &[SafeByte], +) -> HiLo> { + let len = bytes.len(); + assert!(len <= 32); + let hi = if len > 16 { + let hi_bytes = &bytes[0..len - 16]; + bytes_be_to_uint(ctx, gate, hi_bytes, hi_bytes.len()) + } else { + ctx.load_zero() + }; + let lo = { + let lo_len = if len > 16 { 16 } else { len }; + let lo_bytes = &bytes[len - lo_len..len]; + bytes_be_to_uint(ctx, gate, lo_bytes, lo_len) + }; + HiLo::from_hi_lo([hi, lo]) +} + +pub fn select_hi_lo( + ctx: &mut Context, + gate: &impl GateInstructions, + if_true: &HiLo>, + if_false: &HiLo>, + condition: SafeBool, +) -> HiLo> { + let condition = *condition.as_ref(); + let hi = gate.select(ctx, if_true.hi(), if_false.hi(), condition); + let lo = gate.select(ctx, if_true.lo(), if_false.lo(), condition); + HiLo::from_hi_lo([hi, lo]) +} + +pub fn select_hi_lo_by_indicator( + ctx: &mut Context, + gate: &impl GateInstructions, + values: &[HiLo>], + indicator: Vec>, +) -> HiLo> { + let his = values.iter().map(|hilo| hilo.hi()); + let los = values.iter().map(|hilo| hilo.lo()); + let hi = gate.select_by_indicator(ctx, his, indicator.clone()); + let lo = gate.select_by_indicator(ctx, los, indicator); + HiLo::from_hi_lo([hi, lo]) +} + +// Is there a more Rust way to do this? +/// Conversion from `&[SafeByte]` to `Vec>` +pub fn safe_bytes_vec_into(bytes: &[SafeByte]) -> Vec> { + bytes.iter().map(|b| *b.as_ref()).collect() +} + +/// Conversion from [SafeBytes32] to [HiLo] +pub fn safe_bytes32_to_hi_lo( + ctx: &mut Context, + gate: &impl GateInstructions, + bytes: &SafeBytes32, +) -> HiLo> { + let bytes = SafeTypeChip::unsafe_to_fix_len_bytes_vec(bytes.value().to_vec(), 32); + HiLo::from_hi_lo(bytes_be_to_u128(ctx, gate, bytes.bytes()).try_into().unwrap()) +} + +/// Conversion from the MPT root as bytes32 to [HiLo]. Unsafe because this assumes that +/// the root bytes are constrained to be bytes somewhere else. +pub fn unsafe_mpt_root_to_hi_lo( + ctx: &mut Context, + gate: &impl GateInstructions, + proof: &MPTProof, +) -> HiLo> { + let bytes = SafeTypeChip::unsafe_to_fix_len_bytes_vec(proof.root_hash_bytes.clone(), 32); + HiLo::from_hi_lo(bytes_be_to_u128(ctx, gate, bytes.bytes()).try_into().unwrap()) +} + +pub fn encode_const_u8_to_safe_bytes( + ctx: &mut Context, + constant: u8, +) -> [SafeByte; 1] { + let encoded = constant.to_be_bytes().map(|b| F::from(b as u64)); + let raw = ctx.load_constants(&encoded).try_into().unwrap(); + SafeTypeChip::unsafe_to_fix_len_bytes::<1>(raw).into_bytes() +} + +pub fn encode_const_u16_to_safe_bytes( + ctx: &mut Context, + constant: u16, +) -> [SafeByte; 2] { + let encoded = constant.to_be_bytes().map(|b| F::from(b as u64)); + let raw = ctx.load_constants(&encoded).try_into().unwrap(); + SafeTypeChip::unsafe_to_fix_len_bytes::<2>(raw).into_bytes() +} + +pub fn encode_const_u32_to_safe_bytes( + ctx: &mut Context, + constant: u32, +) -> [SafeByte; 4] { + let encoded = constant.to_be_bytes().map(|b| F::from(b as u64)); + let raw = ctx.load_constants(&encoded).try_into().unwrap(); + SafeTypeChip::unsafe_to_fix_len_bytes::<4>(raw).into_bytes() +} + +pub fn encode_const_u64_to_safe_bytes( + ctx: &mut Context, + constant: u64, +) -> [SafeByte; 8] { + let encoded = constant.to_be_bytes().map(|b| F::from(b as u64)); + let raw = ctx.load_constants(&encoded).try_into().unwrap(); + SafeTypeChip::unsafe_to_fix_len_bytes::<8>(raw).into_bytes() +} diff --git a/axiom-eth/src/utils/circuit_utils/mod.rs b/axiom-eth/src/utils/circuit_utils/mod.rs new file mode 100644 index 00000000..068a25c8 --- /dev/null +++ b/axiom-eth/src/utils/circuit_utils/mod.rs @@ -0,0 +1,217 @@ +use std::{cmp::max, ops::Range}; + +use halo2_base::{ + gates::{GateInstructions, RangeInstructions}, + safe_types::{SafeBool, SafeTypeChip}, + utils::{bit_length, ScalarField}, + AssignedValue, Context, + QuantumCell::Constant, +}; + +use itertools::Itertools; + +pub mod bytes; + +// save typing.. +/// See [GateInstructions::is_equal] +pub fn is_equal_usize( + ctx: &mut Context, + gate: &impl GateInstructions, + a: AssignedValue, + b: usize, +) -> SafeBool { + SafeTypeChip::unsafe_to_bool(gate.is_equal(ctx, a, Constant(F::from(b as u64)))) +} + +/// See [RangeInstructions::is_less_than] +pub fn is_lt_usize( + ctx: &mut Context, + range: &impl RangeInstructions, + a: AssignedValue, + b: usize, + max_bits: usize, +) -> SafeBool { + let bit = range.is_less_than(ctx, a, Constant(F::from(b as u64)), max_bits); + SafeTypeChip::unsafe_to_bool(bit) +} + +/// See [RangeInstructions::is_less_than] +/// `a >= b` iff `b - 1 < a` +pub fn is_gte_usize( + ctx: &mut Context, + range: &impl RangeInstructions, + a: AssignedValue, + b: usize, + max_bits: usize, +) -> SafeBool { + let bit = if b == 0 { + ctx.load_constant(F::ONE) + } else { + range.is_less_than(ctx, Constant(F::from(b as u64 - 1)), a, max_bits) + }; + SafeTypeChip::unsafe_to_bool(bit) +} + +/// Returns whether `a` is in the range `[range.start, range.end)`. +/// Assumes `a` and `range.end` are both less than `2^max_bits`. +pub fn is_in_range( + ctx: &mut Context, + range_chip: &impl RangeInstructions, + a: AssignedValue, + range: Range, + max_bits: usize, +) -> SafeBool { + let is_gte = is_gte_usize(ctx, range_chip, a, range.start, max_bits); + let is_lt = is_lt_usize(ctx, range_chip, a, range.end, max_bits); + let is_in_range = range_chip.gate().and(ctx, *is_gte.as_ref(), *is_lt.as_ref()); + SafeTypeChip::unsafe_to_bool(is_in_range) +} + +/// Returns `min(a, b)`. +/// Assumes `a` and `b` are both less than `2^max_bits`. +pub fn min_with_usize( + ctx: &mut Context, + range: &impl RangeInstructions, + a: AssignedValue, + b: usize, + max_bits: usize, +) -> AssignedValue { + let const_b = Constant(F::from(b as u64)); + let lt = range.is_less_than(ctx, a, const_b, max_bits); + range.gate().select(ctx, a, const_b, lt) +} + +/// Creates the length `len` array `mask` with `mask[i] = i < threshold ? 1 : 0`. +/// +/// Denoted `unsafe` because it assumes that `threshold <= len`. +pub fn unsafe_lt_mask( + ctx: &mut Context, + gate: &impl GateInstructions, + threshold: AssignedValue, + len: usize, +) -> Vec> { + let t = threshold.value().get_lower_64() as usize; + let mut last = None; + let mask = (0..len) + .map(|i| { + let mut bit = ctx.load_witness(F::from(i < t)); + gate.assert_bit(ctx, bit); + // constrain the list goes 1, ..., 1, 0, 0, ..., 0 + if let Some(last) = last { + bit = gate.and(ctx, bit, last); + } + last = Some(bit); + bit + }) + .collect_vec(); + let sum = gate.sum(ctx, mask.clone()); + ctx.constrain_equal(&sum, &threshold); + mask.into_iter().map(|x| SafeTypeChip::unsafe_to_bool(x)).collect() +} + +/// Constrains that `array[i] = 0` for `i > len`. +/// +/// ## Assumptions +/// - Marked unsafe because we assume `len <= array.len()` +pub fn unsafe_constrain_trailing_zeros( + ctx: &mut Context, + gate: &impl GateInstructions, + array: &mut [AssignedValue], + len: AssignedValue, +) { + let mask = unsafe_lt_mask(ctx, gate, len, array.len()); + for (byte, mask) in array.iter_mut().zip(mask) { + *byte = gate.mul(ctx, *byte, mask); + } +} + +pub fn log2_ceil( + ctx: &mut Context, + gate: &impl GateInstructions, + x: AssignedValue, + max_bits: usize, +) -> AssignedValue { + let mut bits = gate.num_to_bits(ctx, x, max_bits); + let total_bits = gate.sum(ctx, bits.clone()); + for i in (0..max_bits - 1).rev() { + bits[i] = gate.or(ctx, bits[i], bits[i + 1]); + } + let bit_length = gate.sum(ctx, bits); + let is_pow2 = gate.is_equal(ctx, total_bits, Constant(F::ONE)); + gate.sub(ctx, bit_length, is_pow2) +} + +/// Returns `array[chunk_size * idx.. chunk_size * (idx+1)]`. +/// +/// Assumes that `chunk_size * idx < array.len()`. Otherwise will return all zeros. +pub fn extract_array_chunk( + ctx: &mut Context, + gate: &impl GateInstructions, + array: &[AssignedValue], + idx: AssignedValue, + chunk_size: usize, +) -> Vec> { + let num_chunks = (array.len() + chunk_size - 1) / chunk_size; + let indicator = gate.idx_to_indicator(ctx, idx, num_chunks); + let const_zero = ctx.load_zero(); + (0..chunk_size) + .map(|i| { + let entries = + (0..num_chunks).map(|j| *array.get(chunk_size * j + i).unwrap_or(&const_zero)); + gate.select_by_indicator(ctx, entries, indicator.clone()) + }) + .collect() +} + +/// Returns `array[chunk_size * idx.. chunk_size * (idx+1)]` and constrains that any entries beyond `len` must be zero. +/// +/// Also returns `chunk_size * idx < len` as [SafeBool]. +/// +/// ## Assumptions +/// - `array.len()` is fixed at compile time +/// - `len <= array.len()` +/// - `idx` has been range checked to have at most `idx_max_bits` bits +pub fn extract_array_chunk_and_constrain_trailing_zeros( + ctx: &mut Context, + range: &impl RangeInstructions, + array: &[AssignedValue], + len: AssignedValue, + idx: AssignedValue, + chunk_size: usize, + idx_max_bits: usize, +) -> (Vec>, SafeBool) { + let gate = range.gate(); + let mut chunk = extract_array_chunk(ctx, gate, array, idx, chunk_size); + let chunk_size = chunk_size as u64; + let start = gate.mul(ctx, idx, Constant(F::from(chunk_size))); + let max_len_bits = bit_length(array.len() as u64); + // not worth optimizing: + let max_bits = max(max_len_bits, idx_max_bits + bit_length(chunk_size)); + let is_lt = range.is_less_than(ctx, start, len, max_bits); + // chunk_len = min(len - idx * chunk_size, chunk_size) + let mut chunk_len = gate.sub(ctx, len, start); + chunk_len = gate.mul(ctx, chunk_len, is_lt); + chunk_len = min_with_usize(ctx, range, chunk_len, chunk_size as usize, max_len_bits); + unsafe_constrain_trailing_zeros(ctx, gate, &mut chunk, chunk_len); + + (chunk, SafeTypeChip::unsafe_to_bool(is_lt)) +} + +/// Given fixed length array `array` checks that either `array[0]` is nonzero or `var_len == 0`. Does nothing if `array` is empty. +/// +/// There technically does not need to be any relation between `array` and `var_len` in this implementation. However the usual use case is where `array` is a fixed length array that is meant to represent a variable length array of length `var_len`. +pub fn constrain_no_leading_zeros( + ctx: &mut Context, + gate: &impl GateInstructions, + array: &[AssignedValue], + var_len: AssignedValue, +) { + if array.is_empty() { + return; + } + let leading_zero = gate.is_zero(ctx, array[0]); + let mut no_leading_zero = gate.not(ctx, leading_zero); + let len_is_zero = gate.is_zero(ctx, var_len); + no_leading_zero = gate.or(ctx, no_leading_zero, len_is_zero); + gate.assert_is_const(ctx, &no_leading_zero, &F::ONE); +} diff --git a/axiom-eth/src/utils/component/circuit/comp_circuit_impl.rs b/axiom-eth/src/utils/component/circuit/comp_circuit_impl.rs new file mode 100644 index 00000000..f5f487f0 --- /dev/null +++ b/axiom-eth/src/utils/component/circuit/comp_circuit_impl.rs @@ -0,0 +1,441 @@ +use std::{ + any::Any, + borrow::BorrowMut, + cell::RefCell, + ops::DerefMut, + sync::{Arc, Mutex}, +}; + +#[cfg(feature = "aggregation")] +use crate::utils::build_utils::aggregation::CircuitMetadata; +use crate::{ + rlc::circuit::{builder::RlcCircuitBuilder, RlcCircuitParams, RlcConfig}, + rlc::virtual_region::RlcThreadBreakPoints, + utils::{ + build_utils::pinning::{ + CircuitPinningInstructions, Halo2CircuitPinning, RlcCircuitPinning, + }, + component::{ + circuit::{CoreBuilder, CoreBuilderOutput, PromiseBuilder}, + promise_collector::{PromiseCaller, PromiseCollector, SharedPromiseCollector}, + promise_loader::comp_loader::ComponentCommiter, + types::ComponentPublicInstances, + utils::create_hasher, + ComponentCircuit, ComponentPromiseResultsInMerkle, ComponentType, ComponentTypeId, + GroupedPromiseCalls, GroupedPromiseResults, LogicalInputValue, PromiseShardMetadata, + }, + DEFAULT_RLC_CACHE_BITS, + }, +}; +use anyhow::anyhow; +use halo2_base::{ + gates::{circuit::CircuitBuilderStage, GateChip}, + halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Circuit, ConstraintSystem, Error}, + }, + virtual_region::manager::VirtualRegionManager, + AssignedValue, +}; +use itertools::Itertools; + +use crate::Field; +#[cfg(feature = "aggregation")] +use snark_verifier_sdk::CircuitExt; + +#[derive(Clone, Debug)] +pub struct ComponentCircuitImpl, P: PromiseBuilder> { + pub rlc_builder: RefCell>, + pub promise_collector: SharedPromiseCollector, + pub core_builder: RefCell, + pub promise_builder: RefCell

, + pub val_public_instances: RefCell>>, +} + +impl, P: PromiseBuilder> ComponentCircuitImpl { + /// Create a new component circuit. + pub fn new( + core_builder_params: C::Params, + promise_builder_params: P::Params, + prompt_rlc_params: RlcCircuitParams, + ) -> Self { + Self::new_impl(false, core_builder_params, promise_builder_params, prompt_rlc_params) + } + /// Create a new component circuit, with special prover-only optimizations + /// when `witness_gen_only` is true. + pub fn new_impl( + witness_gen_only: bool, + core_builder_params: C::Params, + promise_builder_params: P::Params, + prompt_rlc_params: RlcCircuitParams, + ) -> Self { + let stage = + if witness_gen_only { CircuitBuilderStage::Prover } else { CircuitBuilderStage::Mock }; + Self::new_from_stage(stage, core_builder_params, promise_builder_params, prompt_rlc_params) + } + /// Create a new component circuit, with special prover-only optimizations + /// when `witness_gen_only` is true. When `stage` is `Keygen`, `use_unknown` + /// is set to true in [RlcCircuitBuilder], and `promise_collector` does not + /// check promise results have been provided before fulfilling. + pub fn new_from_stage( + stage: CircuitBuilderStage, + core_builder_params: C::Params, + promise_builder_params: P::Params, + prompt_rlc_params: RlcCircuitParams, + ) -> Self { + let mut rlc_builder = RlcCircuitBuilder::from_stage(stage, DEFAULT_RLC_CACHE_BITS) + .use_params(prompt_rlc_params); + // Public instances are fully managed by ComponentCircuitImpl. + rlc_builder.base.set_instance_columns(1); + Self { + rlc_builder: RefCell::new(rlc_builder), + promise_collector: Arc::new(Mutex::new(PromiseCollector::new( + P::get_component_type_dependencies(), + ))), + core_builder: RefCell::new(C::new(core_builder_params)), + promise_builder: RefCell::new(P::new(promise_builder_params)), + val_public_instances: RefCell::new(None), + } + } + pub fn use_break_points(self, break_points: RlcThreadBreakPoints) -> Self { + self.rlc_builder.borrow_mut().set_break_points(break_points); + self + } + pub fn prover( + core_builder_params: C::Params, + promise_builder_params: P::Params, + prompt_rlc_pinning: RlcCircuitPinning, + ) -> Self { + Self::new_impl(true, core_builder_params, promise_builder_params, prompt_rlc_pinning.params) + .use_break_points(prompt_rlc_pinning.break_points) + } + + /// Calculate params. This should be called only after all promise results are fulfilled. + pub fn calculate_params(&mut self) -> as Circuit>::Params { + self.virtual_assign_phase0().expect("virtual assign phase0 failed"); + self.virtual_assign_phase1(); + + let result = ( + self.core_builder.borrow_mut().calculate_params(), + self.promise_builder.borrow_mut().calculate_params(), + self.rlc_builder.borrow_mut().calculate_params(Some(9)), + ); + + // clear in case synthesize is called multiple times + self.clear_witnesses(); + + self.rlc_builder.borrow_mut().set_params(result.2.clone()); + + result + } + + pub fn virtual_assign_phase0(&self) -> Result<(), Error> { + let mut borrowed_rlc_builder = self.rlc_builder.borrow_mut(); + let rlc_builder = borrowed_rlc_builder.deref_mut(); + + let mut core_builder = self.core_builder.borrow_mut(); + let mut promise_builder = self.promise_builder.borrow_mut(); + + { + let mut borrowed_promise_collector = self.promise_collector.lock().unwrap(); + let promise_collector = borrowed_promise_collector.deref_mut(); + promise_builder.virtual_assign_phase0(rlc_builder, promise_collector); + } + + let CoreBuilderOutput { public_instances: other_pis, virtual_table: vt, .. } = core_builder + .virtual_assign_phase0(rlc_builder, PromiseCaller::new(self.promise_collector.clone())); + let output_commit = + <>::CompType as ComponentType>::Commiter::compute_commitment( + &mut rlc_builder.base, + &vt, + ); + + let mut borrowed_promise_collector = self.promise_collector.lock().unwrap(); + let promise_collector = borrowed_promise_collector.deref_mut(); + let public_instances = self.generate_public_instances( + rlc_builder, + promise_collector, + &P::get_component_type_dependencies(), + output_commit, + other_pis, + )?; + + let pis = rlc_builder.public_instances(); + pis[0] = public_instances.into(); + Ok(()) + } + + fn virtual_assign_phase1(&self) { + let mut rlc_builder = self.rlc_builder.borrow_mut(); + + // Load promise results first in case the core builder depends on them. + { + let mut promise_collector = self.promise_collector.lock().unwrap(); + self.promise_builder + .borrow_mut() + .virtual_assign_phase1(&mut rlc_builder, promise_collector.deref_mut()); + } + self.core_builder.borrow_mut().virtual_assign_phase1(&mut rlc_builder); + } + + fn generate_public_instances( + &self, + rlc_builder: &mut RlcCircuitBuilder, + promise_collector: &PromiseCollector, + dependencies: &[ComponentTypeId], + output_commit: AssignedValue, + other_pis: Vec>, + ) -> Result>, Error> { + let mut promise_commits = Vec::with_capacity(dependencies.len()); + for component_type_id in dependencies { + if let Some(commit) = + promise_collector.get_commit_by_component_type_id(component_type_id) + { + promise_commits.push(commit); + } + } + let gate_chip = GateChip::new(); + + let ctx = rlc_builder.base.main(0); + let mut hasher = create_hasher::(); + hasher.initialize_consts(ctx, &gate_chip); + let promise_commit = hasher.hash_fix_len_array(ctx, &gate_chip, &promise_commits); + + let public_instances = ComponentPublicInstances::> { + output_commit, + promise_result_commit: promise_commit, + other: other_pis, + }; + if promise_collector.promise_results_ready() { + *self.val_public_instances.borrow_mut() = Some(public_instances.clone().into()); + } + Ok(public_instances) + } +} + +impl, P: PromiseBuilder> ComponentCircuit + for ComponentCircuitImpl +{ + fn clear_witnesses(&self) { + self.rlc_builder.borrow_mut().clear(); + self.promise_collector.lock().unwrap().clear_witnesses(); + self.core_builder.borrow_mut().clear_witnesses(); + self.promise_builder.borrow_mut().clear_witnesses(); + } + /// **Warning:** the returned deduped calls ordering is not deterministic. + fn compute_promise_calls(&self) -> anyhow::Result { + let mut borrowed_rlc_builder = self.rlc_builder.borrow_mut(); + let rlc_builder = borrowed_rlc_builder.deref_mut(); + let mut borrowed_core_builder = self.core_builder.borrow_mut(); + let core_builder = borrowed_core_builder.deref_mut(); + + core_builder + .virtual_assign_phase0(rlc_builder, PromiseCaller::new(self.promise_collector.clone())); + let mut borrowed_promise_collector = self.promise_collector.lock().unwrap(); + let deduped_calls = borrowed_promise_collector.get_deduped_calls(); + + // clear in case synthesize is called multiple times + core_builder.clear_witnesses(); + borrowed_promise_collector.clear_witnesses(); + rlc_builder.clear(); + + Ok(deduped_calls) + } + + /// Feed inputs into the core builder. The `input` type should be the `::CoreInput` type. + /// It is the caller's responsibility to ensure that the capacity of the input + /// is equal to the configured capacity of the component circuit. This function + /// does **not** check this. + fn feed_input(&self, input: Box) -> anyhow::Result<()> { + let typed_input = input + .as_ref() + .downcast_ref::<>::CoreInput>() + .ok_or_else(|| anyhow!("invalid input type"))? + .clone(); + self.core_builder.borrow_mut().feed_input(typed_input)?; + Ok(()) + } + + fn fulfill_promise_results( + &self, + promise_results: &GroupedPromiseResults, + ) -> anyhow::Result<()> { + let mut borrowed_promise_collector = self.promise_collector.lock().unwrap(); + let promise_collector = borrowed_promise_collector.deref_mut(); + promise_collector.fulfill(promise_results); + + self.promise_builder.borrow_mut().fulfill_promise_results(promise_collector); + Ok(()) + } + + /// When inputs and promise results are ready, we can generate outputs of this component. + /// + /// Return logical outputs and the output commit of the virtual table. The output commit is calculated using the configured capacity in the params. + /// But the returned metadata capacity is the true used capacity of the component based on the inputs, **not** the configured capacity. + fn compute_outputs(&self) -> anyhow::Result> { + self.promise_collector.lock().unwrap().set_promise_results_ready(true); + + let mut borrowed_rlc_builder = self.rlc_builder.borrow_mut(); + let rlc_builder = borrowed_rlc_builder.deref_mut(); + let mut borrowed_core_builder = self.core_builder.borrow_mut(); + let core_builder = borrowed_core_builder.deref_mut(); + + let CoreBuilderOutput { virtual_table: vt, logical_results, .. } = core_builder + .virtual_assign_phase0(rlc_builder, PromiseCaller::new(self.promise_collector.clone())); + let capacity: usize = logical_results.iter().map(|lr| lr.input.get_capacity()).sum(); + + let vt = vt.into_iter().map(|(v_i, v_o)| (v_i.into(), v_o.into())).collect_vec(); + let output_commit_val = + <>::CompType as ComponentType>::Commiter::compute_native_commitment( + &vt, + ); + // Release RefCell for clear_witnesses later. + drop(borrowed_rlc_builder); + drop(borrowed_core_builder); + self.clear_witnesses(); + + Ok(ComponentPromiseResultsInMerkle::::new( + vec![PromiseShardMetadata { commit: output_commit_val, capacity }], + vec![(0, logical_results.into_iter().map(|lr| lr.into()).collect())], + )) + } + + fn get_public_instances(&self) -> ComponentPublicInstances { + let has_pi_value = self.val_public_instances.borrow().is_some(); + if !has_pi_value { + self.promise_collector.lock().unwrap().set_promise_results_ready(true); + self.virtual_assign_phase0().unwrap(); + self.clear_witnesses(); + } + self.val_public_instances.borrow().as_ref().unwrap().clone() + } +} + +impl, P: PromiseBuilder> Circuit + for ComponentCircuitImpl +{ + type Config = (C::Config, P::Config, RlcConfig); + type Params = (C::Params, P::Params, RlcCircuitParams); + type FloorPlanner = SimpleFloorPlanner; + + fn params(&self) -> Self::Params { + ( + self.core_builder.borrow().get_params(), + self.promise_builder.borrow().get_params(), + self.rlc_builder.borrow().params(), + ) + } + + fn without_witnesses(&self) -> Self { + unimplemented!() + } + + fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config { + let k = params.2.base.k; + let core_config = C::configure_with_params(meta, params.0); + let mut rlc_config = RlcConfig::configure(meta, params.2); + // There must be some phase 0 columns before creating phase 1 columns. + let promise_config = P::configure_with_params(meta, params.1); + // This is really tricky.. + let usable_rows = (1 << k) - meta.minimum_rows(); + rlc_config.set_usable_rows(usable_rows); + (core_config, promise_config, rlc_config) + } + + fn configure(_: &mut ConstraintSystem) -> Self::Config { + unreachable!() + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // Promise results must be ready at this point, unless in Keygen mode. + if !self.rlc_builder.borrow().base.core().use_unknown() { + self.promise_collector.lock().unwrap().set_promise_results_ready(true); + } + config.2.base.initialize(&mut layouter); + self.virtual_assign_phase0()?; + { + let mut core_builder = self.core_builder.borrow_mut(); + let mut promise_builder = self.promise_builder.borrow_mut(); + let rlc_builder = self.rlc_builder.borrow_mut(); + + let mut phase0_layouter = layouter.namespace(|| "raw synthesize phase0"); + core_builder.borrow_mut().raw_synthesize_phase0(&config.0, &mut phase0_layouter); + promise_builder.raw_synthesize_phase0(&config.1, &mut phase0_layouter); + rlc_builder.raw_synthesize_phase0(&config.2, phase0_layouter); + } + #[cfg(feature = "halo2-axiom")] + { + layouter.next_phase(); + } + self.rlc_builder + .borrow_mut() + .load_challenge(&config.2, layouter.namespace(|| "load challenges")); + + self.virtual_assign_phase1(); + { + let mut core_builder = self.core_builder.borrow_mut(); + let mut promise_builder = self.promise_builder.borrow_mut(); + let rlc_builder = self.rlc_builder.borrow(); + + let mut phase1_layouter = + layouter.namespace(|| "Core + RlcCircuitBuilder raw synthesize phase1"); + core_builder.raw_synthesize_phase1(&config.0, &mut phase1_layouter); + rlc_builder.raw_synthesize_phase1(&config.2, phase1_layouter, false); + promise_builder.raw_synthesize_phase1(&config.1, &mut layouter); + } + + let rlc_builder = self.rlc_builder.borrow(); + if !rlc_builder.witness_gen_only() { + layouter.assign_region( + || "copy constraints", + |mut region| { + let constant_cols = config.2.base.constants(); + rlc_builder.copy_manager().assign_raw(constant_cols, &mut region); + Ok(()) + }, + )?; + } + drop(rlc_builder); + + // clear in case synthesize is called multiple times + self.clear_witnesses(); + + Ok(()) + } +} + +// TODO: Maybe change? +impl, P: PromiseBuilder> CircuitPinningInstructions + for ComponentCircuitImpl +{ + type Pinning = RlcCircuitPinning; + fn pinning(&self) -> Self::Pinning { + let break_points = self.rlc_builder.borrow().break_points(); + let params = self.rlc_builder.borrow().params(); + RlcCircuitPinning::new(params, break_points) + } +} + +#[cfg(feature = "aggregation")] +impl, P: PromiseBuilder> CircuitExt + for ComponentCircuitImpl +where + C: CircuitMetadata, +{ + fn accumulator_indices() -> Option> { + C::accumulator_indices() + } + + fn instances(&self) -> Vec> { + let res = vec![self.get_public_instances().into()]; + res + } + + fn num_instance(&self) -> Vec { + vec![self.instances()[0].len()] + } +} diff --git a/axiom-eth/src/utils/component/circuit/mod.rs b/axiom-eth/src/utils/component/circuit/mod.rs new file mode 100644 index 00000000..672998ff --- /dev/null +++ b/axiom-eth/src/utils/component/circuit/mod.rs @@ -0,0 +1,139 @@ +use crate::Field; +use crate::{rlc::circuit::builder::RlcCircuitBuilder, utils::build_utils::dummy::DummyFrom}; +use getset::Getters; +use halo2_base::{ + halo2_proofs::{circuit::Layouter, plonk::ConstraintSystem}, + AssignedValue, +}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +use super::{ + promise_collector::{ + PromiseCaller, PromiseCallsGetter, PromiseCommitSetter, PromiseResultsGetter, + }, + promise_loader::comp_loader::SingleComponentLoaderParams, + types::FixLenLogical, + ComponentType, ComponentTypeId, FlattenVirtualTable, LogicalResult, +}; + +mod comp_circuit_impl; +pub use comp_circuit_impl::ComponentCircuitImpl; + +pub trait ComponentBuilder { + type Config: Clone = (); + type Params: Clone + Default = (); + + /// Create Self. + fn new(params: Self::Params) -> Self; + /// Get params for this module. + fn get_params(&self) -> Self::Params; + /// Clear all stored witnesses. Data should be untouched. + fn clear_witnesses(&mut self) {} + /// Configure this module with params. + // Alas we cannot have default implementation only for Self::Config = (). + fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config; + /// Calculate params for this module. + fn calculate_params(&mut self) -> Self::Params; +} + +/// CoreBuilder must specify its output format. +pub trait CoreBuilderParams { + /// Return the output capacity. + fn get_output_params(&self) -> CoreBuilderOutputParams; +} +#[derive(Clone, Default, Getters)] +pub struct CoreBuilderOutputParams { + /// Capacities for each shard. + #[getset(get = "pub")] + cap_per_shard: Vec, +} +impl CoreBuilderOutputParams { + /// create a CoreBuilderOutputParams + pub fn new(cap_per_shard: Vec) -> Self { + assert!(cap_per_shard.is_empty() || cap_per_shard.len().is_power_of_two()); + Self { cap_per_shard } + } +} +impl CoreBuilderParams for CoreBuilderOutputParams { + fn get_output_params(&self) -> CoreBuilderOutputParams { + self.clone() + } +} +/// Input for CoreBuilder. +/// TODO: specify its output capacity. +pub trait CoreBuilderInput = Serialize + DeserializeOwned + Clone + 'static; + +/// Output for CoreBuilder which is determined at phase0. +pub struct CoreBuilderOutput> { + /// Public instances except the output commit. + pub public_instances: Vec>, + /// Flatten output virtual table. + pub virtual_table: FlattenVirtualTable>, + /// Value of logical results. + pub logical_results: Vec>, +} + +pub trait CoreBuilder: ComponentBuilder { + type CompType: ComponentType; + type PublicInstanceValue: FixLenLogical; + type PublicInstanceWitness: FixLenLogical>; + type CoreInput: CoreBuilderInput + DummyFrom; + /// Feed inputs to this module. + fn feed_input(&mut self, input: Self::CoreInput) -> anyhow::Result<()>; + /// Generate witnesses for phase0. Any data passing to other steps should be stored inside `self`. + /// Return `(, )`. + fn virtual_assign_phase0( + &mut self, + // TODO: This could be replaced with a more generic CircuitBuilder. Question: can be CircuitBuilder treated as something like PromiseCircuit? + builder: &mut RlcCircuitBuilder, + // Core circuits can make promise calls. + promise_caller: PromiseCaller, + // TODO: Output commitmment + ) -> CoreBuilderOutput; + /// Synthesize for phase0. Any data passing to other steps should be stored inside `self`. + #[allow(unused_variables)] + fn raw_synthesize_phase0(&mut self, config: &Self::Config, layouter: &mut impl Layouter) {} + /// Generate witnesses for phase1. Any data passing to other steps should be stored inside `self`. + #[allow(unused_variables)] + fn virtual_assign_phase1(&mut self, builder: &mut RlcCircuitBuilder) {} + /// Synthesize for phase1. Any data passing to other steps should be stored inside `self`. + #[allow(unused_variables)] + fn raw_synthesize_phase1(&mut self, config: &Self::Config, layouter: &mut impl Layouter) {} +} + +#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] +pub struct LoaderParamsPerComponentType { + pub component_type_id: ComponentTypeId, + pub loader_params: SingleComponentLoaderParams, +} + +/// A ComponentModule load promise results from other components and owns some columns and corresponding gates. +/// All ComponentModules in a single circuit share a RlcCircuitBuilder and they communicate with each other through PromiseCollector. +pub trait PromiseBuilder: ComponentBuilder { + /// Get component type dependencies of this ComponentBuilder in a determinstic order. + fn get_component_type_dependencies() -> Vec; + /// Extract loader params per component type from circuit params. + /// Assumption: Return value is in a deterministic order which we use to compute the promise commit. + fn extract_loader_params_per_component_type( + params: &Self::Params, + ) -> Vec; + /// Fulfill promise results. + fn fulfill_promise_results(&mut self, promise_results_getter: &impl PromiseResultsGetter); + /// Generate witnesses for phase0. Any data passing to other steps should be stored inside `self`. + /// Also need to set promise result commits. + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_commit_setter: &mut impl PromiseCommitSetter, + ); + /// Synthesize for phase0. Any data passing to other steps should be stored inside `self`. + fn raw_synthesize_phase0(&mut self, config: &Self::Config, layouter: &mut impl Layouter); + /// Generate witnesses for phase1. Any data passing to other steps should be stored inside `self`. + fn virtual_assign_phase1( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_calls_getter: &mut impl PromiseCallsGetter, + ); + /// Synthesize for phase1. Any data passing to other steps should be stored inside `self`. + fn raw_synthesize_phase1(&mut self, config: &Self::Config, layouter: &mut impl Layouter); +} diff --git a/axiom-eth/src/utils/component/mod.rs b/axiom-eth/src/utils/component/mod.rs new file mode 100644 index 00000000..d7f74e67 --- /dev/null +++ b/axiom-eth/src/utils/component/mod.rs @@ -0,0 +1,279 @@ +use std::{any::Any, collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData}; + +use crate::{Field, RawField}; +use getset::Getters; +use halo2_base::{ + gates::{circuit::builder::BaseCircuitBuilder, GateInstructions, RangeChip}, + halo2_proofs::halo2curves::bn256::Fr, + AssignedValue, Context, +}; +use itertools::Itertools; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use static_assertions::assert_impl_all; + +use crate::rlc::chip::RlcChip; + +use self::{ + promise_loader::{ + comp_loader::{BasicComponentCommiter, ComponentCommiter}, + flatten_witness_to_rlc, + }, + types::{ComponentPublicInstances, FixLenLogical, Flatten}, + utils::{into_key, try_from_key}, +}; + +pub mod circuit; +pub mod param; +pub mod promise_collector; +pub mod promise_loader; +#[cfg(test)] +mod tests; +pub mod types; +pub mod utils; + +pub type ComponentId = u64; +pub type ComponentTypeId = String; +pub const USER_COMPONENT_ID: ComponentId = 0; + +/// Unified representation of a logical input of a component type. +/// TODO: Can this be extended to variable length output? +/// In the caller end, there could be multiple formats of promise calls for a component type. e.g. +/// fix/var length array to keccak. But in the receiver end, we only need to know the logical input. +/// In the receiver end, a logical input could take 1(fix len input) or multiple virtual rows(var len). +/// The number of virtual rows a logical input take is "capacity". +pub trait LogicalInputValue: + Debug + Send + Sync + Clone + Eq + Serialize + DeserializeOwned + 'static +{ + /// Get the capacity of this logical input. + /// The default implementaion is for the fixed length case. + fn get_capacity(&self) -> usize; +} +/// A format of a promise call to component type T. +pub trait PromiseCallWitness: Debug + Send + Sync + 'static { + /// The component type this promise call is for. + fn get_component_type_id(&self) -> ComponentTypeId; + /// Get the capacity of this promise call. + fn get_capacity(&self) -> usize; + /// Encode the promise call into RLC. + /// TODO: maybe pass builder here for better flexiability? but constructing chips are slow. + fn to_rlc( + &self, + ctx_pair: (&mut Context, &mut Context), + range_chip: &RangeChip, + rlc_chip: &RlcChip, + ) -> AssignedValue; + /// Get the logical input of this promise call. + fn to_typeless_logical_input(&self) -> TypelessLogicalInput; + /// Get dummy output of this promise call. + fn get_mock_output(&self) -> Flatten; + /// Enable downcasting + fn as_any(&self) -> &dyn Any; +} + +/// The flatten version of output of a component. +pub type FlattenVirtualTable = Vec>; +/// A flatten virtual row in a virtual table. +pub type FlattenVirtualRow = (Flatten, Flatten); + +/// Logical result of a component type. +#[derive(Clone)] +pub struct LogicalResult> { + pub input: T::LogicalInput, + pub output: T::OutputValue, + pub _marker: PhantomData, +} +impl> LogicalResult { + /// Create LogicalResult + pub fn new(input: T::LogicalInput, output: T::OutputValue) -> Self { + Self { input, output, _marker: PhantomData } + } +} +impl> TryFrom> for LogicalResult { + type Error = anyhow::Error; + fn try_from(value: ComponentPromiseResult) -> Result { + let (input, output) = value; + let input = try_from_key::(&input)?; + Ok(Self::new(input, T::OutputValue::try_from_raw(output)?)) + } +} +impl> From> for ComponentPromiseResult { + fn from(value: LogicalResult) -> Self { + let LogicalResult { input, output, .. } = value; + (into_key(input), output.into_raw()) + } +} +impl> From> for Vec> { + fn from(value: LogicalResult) -> Self { + let logical_virtual_rows = T::logical_result_to_virtual_rows(&value); + logical_virtual_rows + .into_iter() + .map(|(input, output)| (input.into(), output.into())) + .collect_vec() + } +} +/// Specify the logical types of a component type. +pub trait ComponentType: 'static + Sized { + type InputValue: FixLenLogical; + type InputWitness: FixLenLogical>; + type OutputValue: FixLenLogical; + type OutputWitness: FixLenLogical>; + type LogicalInput: LogicalInputValue; + type Commiter: ComponentCommiter = BasicComponentCommiter; + + /// Get ComponentTypeId of this component type. + fn get_type_id() -> ComponentTypeId; + /// Get ComponentTypeName for logging/debugging. + fn get_type_name() -> &'static str { + std::any::type_name::() + } + + /// Wrap logical_result_to_virtual_rows_impl with sanity check. + fn logical_result_to_virtual_rows( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + let v_rows = Self::logical_result_to_virtual_rows_impl(ins); + assert_eq!(v_rows.len(), ins.input.get_capacity()); + v_rows + } + /// Convert a logical result to 1 or multiple virtual rows. + fn logical_result_to_virtual_rows_impl( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)>; + + /// Wrap logical_input_to_virtual_rows_impl with sanity check. + /// TODO: we are not using this. + fn logical_input_to_virtual_rows(li: &Self::LogicalInput) -> Vec { + let v_rows = Self::logical_input_to_virtual_rows_impl(li); + assert_eq!(v_rows.len(), li.get_capacity()); + v_rows + } + /// Real implementation to convert a logical input to virtual rows. + fn logical_input_to_virtual_rows_impl(li: &Self::LogicalInput) -> Vec; + + /// RLC virtual rows. A logical input might take multiple virtual rows. + /// The default implementation is for the fixed length case. + fn rlc_virtual_rows( + (gate_ctx, rlc_ctx): (&mut Context, &mut Context), + range_chip: &RangeChip, + rlc_chip: &RlcChip, + inputs: &[(Self::InputWitness, Self::OutputWitness)], + ) -> Vec> { + let input_multiplier = rlc_chip.rlc_pow_fixed( + gate_ctx, + &range_chip.gate, + Self::OutputWitness::get_num_fields(), + ); + + inputs + .iter() + .map(|(input, output)| { + let i_rlc = flatten_witness_to_rlc(rlc_ctx, rlc_chip, &input.clone().into()); + let o_rlc = flatten_witness_to_rlc(rlc_ctx, rlc_chip, &output.clone().into()); + range_chip.gate.mul_add(gate_ctx, i_rlc, input_multiplier, o_rlc) + }) + .collect_vec() + } +} + +// ============= Data types passed between components ============= +pub type TypelessLogicalInput = Vec; +#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct TypelessPromiseCall { + pub capacity: usize, + pub logical_input: TypelessLogicalInput, +} + +/// (Receiver ComponentType, serialized logical input) +pub type GroupedPromiseCalls = HashMap>; +/// (typeless logical input, output) +pub type ComponentPromiseResult = (TypelessLogicalInput, Vec); + +/// Metadata for a promise shard +#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)] +pub struct PromiseShardMetadata { + pub commit: F, + pub capacity: usize, +} +/// (shard index, shard data) +pub type SelectedDataShard = (usize, S); +/// (shard index, vec of ComponentPromiseResult) +pub type SelectedPromiseResultShard = SelectedDataShard>>; + +#[derive(Debug, Clone, Getters, Serialize, Deserialize, Hash, PartialEq, Eq)] +pub struct SelectedDataShardsInMerkle { + // metadata of leaves of this Merkle tree + #[getset(get = "pub")] + leaves: Vec>, + /// Selected shards. + #[getset(get = "pub")] + shards: Vec>, +} + +impl SelectedDataShardsInMerkle { + // create SelectedDataShardsInMerkle + pub fn new(leaves: Vec>, shards: Vec>) -> Self { + assert!(leaves.len().is_power_of_two()); + // TODO: check capacity of each shard. + Self { leaves, shards } + } + /// Map data into another type. + pub fn map_data(self, f: impl Fn(S) -> NS) -> SelectedDataShardsInMerkle { + SelectedDataShardsInMerkle::new( + self.leaves, + self.shards.into_iter().map(|(i, s)| (i, f(s))).collect(), + ) + } +} + +/// Each shard is a virtual table, so shards is a vector of virtual tables. +pub type ComponentPromiseResultsInMerkle = + SelectedDataShardsInMerkle>>; + +impl ComponentPromiseResultsInMerkle { + /// Helper function to create ComponentPromiseResults from a single shard. + pub fn from_single_shard>(lr: Vec>) -> Self { + let vt = lr.iter().flat_map(T::logical_result_to_virtual_rows).collect_vec(); + let mut mock_builder = BaseCircuitBuilder::::new(true).use_k(18).use_lookup_bits(8); + let ctx = mock_builder.main(0); + let witness_vt = vt + .into_iter() + .map(|(v_i, v_o)| (v_i.into().assign(ctx), v_o.into().assign(ctx))) + .collect_vec(); + let witness_commit = T::Commiter::compute_commitment(&mut mock_builder, &witness_vt); + let commit = *witness_commit.value(); + mock_builder.clear(); // prevent drop warning + Self { + leaves: vec![PromiseShardMetadata:: { commit, capacity: witness_vt.len() }], + shards: vec![(0, lr.into_iter().map(|lr| lr.into()).collect())], + } + } +} +pub type GroupedPromiseResults = HashMap>; + +assert_impl_all!(ComponentPromiseResultsInMerkle: Serialize, DeserializeOwned); + +pub const NUM_COMPONENT_OWNED_INSTANCES: usize = 2; + +pub trait ComponentCircuit { + fn clear_witnesses(&self); + /// Compute promise calls. + fn compute_promise_calls(&self) -> anyhow::Result; + /// Feed inputs into the core builder. The `input` type should be the `CoreInput` type specified by the `CoreBuilder`. + /// It is the caller's responsibility to ensure that the capacity of the input + /// is equal to the configured capacity of the component circuit. This function + /// does **not** check this. + fn feed_input(&self, input: Box) -> anyhow::Result<()>; + /// Fulfill promise results. + fn fulfill_promise_results( + &self, + promise_results: &GroupedPromiseResults, + ) -> anyhow::Result<()>; + /// When inputs and promise results are ready, we can generate outputs of this component. + /// * When you call `compute_outputs`, `feed_inputs` must have already be called. + /// * Input capacity checking should happen when calling `feed_inputs`, not in this function. This function assumes that the input capacity is equal to the configured capacity of the component circuit. + // We don't have padding in the framework level because we don't have a formal interface to get a dummy input with capacity = 1. But even if we want to pad, it should happen when `feed_input`. + /// * The only goal of `compute_outputs` is to return the virtual table and its commit. + fn compute_outputs(&self) -> anyhow::Result>; + // Get public instances of this component. + fn get_public_instances(&self) -> ComponentPublicInstances; +} diff --git a/axiom-eth/src/utils/component/param.rs b/axiom-eth/src/utils/component/param.rs new file mode 100644 index 00000000..5512cd8f --- /dev/null +++ b/axiom-eth/src/utils/component/param.rs @@ -0,0 +1,4 @@ +// re-export constants, these also match constants from snark-verifier Poseidon +pub use zkevm_hashes::keccak::component::param::{ + POSEIDON_RATE, POSEIDON_R_F, POSEIDON_R_P, POSEIDON_SECURE_MDS, POSEIDON_T, +}; diff --git a/axiom-eth/src/utils/component/promise_collector.rs b/axiom-eth/src/utils/component/promise_collector.rs new file mode 100644 index 00000000..6d64401d --- /dev/null +++ b/axiom-eth/src/utils/component/promise_collector.rs @@ -0,0 +1,229 @@ +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + sync::{Arc, Mutex}, +}; + +use crate::Field; +use anyhow::anyhow; +use getset::{CopyGetters, Setters}; +use halo2_base::{AssignedValue, Context, ContextTag}; +use itertools::Itertools; + +use super::{ + types::Flatten, ComponentPromiseResultsInMerkle, ComponentType, ComponentTypeId, + GroupedPromiseCalls, GroupedPromiseResults, PromiseCallWitness, TypelessLogicalInput, + TypelessPromiseCall, +}; + +// (flatten input value, flatten input witness, flatten output witness) +// is for dedup. +pub type PromiseResultWitness = (Box>, Flatten>); + +pub type SharedPromiseCollector = Arc>>; +/// Newtype for [SharedPromiseCollector] with promise `call` functionality. +#[derive(Clone, Debug)] +pub struct PromiseCaller(pub SharedPromiseCollector); +impl PromiseCaller { + /// Create a PromiseCallerWrap + pub fn new(shared_promise_collector: SharedPromiseCollector) -> Self { + Self(shared_promise_collector) + } + /// Make a promise call. + pub fn call, B: ComponentType>( + &self, + ctx: &mut Context, + input_witness: P, + ) -> anyhow::Result { + assert_eq!(input_witness.get_component_type_id(), B::get_type_id()); + let witness_output_flatten = + self.0.lock().unwrap().call_impl(ctx, Box::new(input_witness))?; + B::OutputWitness::try_from(witness_output_flatten) + } +} + +#[derive(CopyGetters, Setters, Debug)] +pub struct PromiseCollector { + dependencies_lookup: HashSet, + dependencies: Vec, + // TypeId -> (ContextTag -> Vec) + // ContextTag is to support multi-threaded calls while maintaining deterministic in-circuit assignment order. + witness_grouped_calls: + HashMap>>>, + // Promise results for each component type. + value_results: HashMap>, + // map version of `value_results` for quick lookup promise results. + value_results_lookup: HashMap>>, + witness_commits: HashMap>, + #[getset(get_copy = "pub", set = "pub")] + promise_results_ready: bool, +} + +/// This is to limit PromiseCollector's interfaces exposed to implementaion. +pub trait PromiseCallsGetter { + /// Get promise calls by component type id. This is used to add these promises into lookup columns. + /// TODO: This should return `Vec>` because the order is determined at that time. But it's tricky to + /// flatten the BTreeMap without cloning Box. + fn get_calls_by_component_type_id( + &self, + component_type_id: &ComponentTypeId, + ) -> Option<&BTreeMap>>>; +} + +/// This is to limit PromiseCollector's interfaces exposed to implementaion. +pub trait PromiseResultsGetter { + /// Get promise results by component type id. This is used to add these promises into lookup columns. + fn get_results_by_component_type_id( + &self, + component_type_id: &ComponentTypeId, + ) -> Option<&ComponentPromiseResultsInMerkle>; +} + +/// This is to limit PromiseCollector's interfaces exposed to implementaion. +pub trait PromiseCommitSetter { + /// Get promise results by component type id. This is used to add these promises into lookup columns. + fn set_commit_by_component_type_id( + &mut self, + component_type_id: ComponentTypeId, + commit: AssignedValue, + ); +} + +impl PromiseCollector { + pub fn new(dependencies: Vec) -> Self { + Self { + dependencies_lookup: dependencies.clone().into_iter().collect(), + dependencies, + witness_grouped_calls: Default::default(), + value_results: Default::default(), + value_results_lookup: Default::default(), + witness_commits: Default::default(), + promise_results_ready: false, + } + } + + pub fn clear_witnesses(&mut self) { + self.witness_grouped_calls.clear(); + self.witness_commits.clear(); + // self.result should not be cleared because it comes from external. + } + + /// Get promise commit by component type id. + pub fn get_commit_by_component_type_id( + &self, + component_type_id: &ComponentTypeId, + ) -> Option> { + self.witness_commits.get(component_type_id).copied() + } + + /// Return dedup promises. + /// For each component type, the calls are sorted and deduped so that the returned order is deterministic. + pub fn get_deduped_calls(&self) -> GroupedPromiseCalls { + self.witness_grouped_calls + .iter() + .map(|(type_id, calls)| { + ( + type_id.clone(), + calls + .iter() + .flat_map(|(_, calls_per_context)| { + calls_per_context.iter().map(|(p, _)| TypelessPromiseCall { + capacity: p.get_capacity(), + logical_input: p.to_typeless_logical_input(), + }) + }) + .sorted() // sorting to ensure the order is deterministic. + // Note: likely not enough promise calls to be worth using par_sort + .dedup() + .collect_vec(), + ) + }) + .collect() + } + + /// Fulfill promise calls with the coressponding results. + pub fn fulfill(&mut self, results: &GroupedPromiseResults) { + assert!(!self.promise_results_ready); + for dep in &self.dependencies { + if let Some(results_per_comp) = results.get(dep) { + let results_per_comp = results_per_comp.clone(); + // TODO: check if it has already been fulfilled. + self.value_results_lookup.insert( + dep.clone(), + results_per_comp + .shards + .clone() + .into_iter() + .flat_map(|(_, data)| data) + .collect(), + ); + self.value_results.insert(dep.clone(), results_per_comp); + } + } + } + + pub(crate) fn call_impl( + &mut self, + ctx: &mut Context, + witness_input: Box>, + ) -> anyhow::Result>> { + // Special case: virtual call + let is_virtual = witness_input.get_capacity() == 0; + + let component_type_id = witness_input.get_component_type_id(); + if !is_virtual && !self.dependencies_lookup.contains(&component_type_id) { + return Err(anyhow!("Unsupport component type id {:?}.", component_type_id)); + } + + let value_serialized_input = witness_input.to_typeless_logical_input(); + + let call_results = self.value_results_lookup.get(&component_type_id); + // Virtual component type promise calls always go to the else branch. + let witness_output = if !is_virtual && self.promise_results_ready { + // Hack: there is no direct way to get field size information. + let mut flatten_output = witness_input.get_mock_output(); + // If promise results are fullfilled, use the results directly. + // Crash if the promise result is not fullfilled. + flatten_output.fields = + call_results.unwrap().get(&value_serialized_input).unwrap().clone(); + flatten_output.assign(ctx) + } else { + witness_input.get_mock_output().assign(ctx) + }; + self.witness_grouped_calls + .entry(component_type_id) + .or_default() + .entry(ctx.tag()) + .or_default() + .push((witness_input, witness_output.clone())); + Ok(witness_output) + } +} + +impl PromiseCallsGetter for PromiseCollector { + fn get_calls_by_component_type_id( + &self, + component_type_id: &ComponentTypeId, + ) -> Option<&BTreeMap>>> { + self.witness_grouped_calls.get(component_type_id) + } +} + +impl PromiseResultsGetter for PromiseCollector { + fn get_results_by_component_type_id( + &self, + component_type_id: &ComponentTypeId, + ) -> Option<&ComponentPromiseResultsInMerkle> { + self.value_results.get(component_type_id) + } +} + +impl PromiseCommitSetter for PromiseCollector { + fn set_commit_by_component_type_id( + &mut self, + component_type_id: ComponentTypeId, + commit: AssignedValue, + ) { + log::debug!("component_type_id: {} commit: {:?}", component_type_id, commit.value()); + self.witness_commits.insert(component_type_id, commit); + } +} diff --git a/axiom-eth/src/utils/component/promise_loader/combo.rs b/axiom-eth/src/utils/component/promise_loader/combo.rs new file mode 100644 index 00000000..f5fa3b6a --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/combo.rs @@ -0,0 +1,98 @@ +use std::marker::PhantomData; + +use halo2_base::halo2_proofs::{circuit::Layouter, plonk::ConstraintSystem}; + +use crate::{ + rlc::circuit::builder::RlcCircuitBuilder, + utils::component::{ + circuit::{ComponentBuilder, LoaderParamsPerComponentType, PromiseBuilder}, + promise_collector::{PromiseCallsGetter, PromiseCommitSetter, PromiseResultsGetter}, + ComponentTypeId, + }, + Field, +}; + +pub struct PromiseBuilderCombo, SECOND: PromiseBuilder> { + pub to_combine: (FIRST, SECOND), + _phantom: PhantomData<(F, FIRST, SECOND)>, +} + +impl, SECOND: PromiseBuilder> + PromiseBuilderCombo +{ +} + +impl, SECOND: PromiseBuilder> ComponentBuilder + for PromiseBuilderCombo +{ + type Config = (FIRST::Config, SECOND::Config); + type Params = (FIRST::Params, SECOND::Params); + + fn new(params: Self::Params) -> Self { + Self { to_combine: (FIRST::new(params.0), SECOND::new(params.1)), _phantom: PhantomData } + } + fn get_params(&self) -> Self::Params { + (self.to_combine.0.get_params(), self.to_combine.1.get_params()) + } + fn clear_witnesses(&mut self) { + self.to_combine.0.clear_witnesses(); + self.to_combine.1.clear_witnesses(); + } + + fn calculate_params(&mut self) -> Self::Params { + (self.to_combine.0.calculate_params(), self.to_combine.1.calculate_params()) + } + + fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config { + ( + FIRST::configure_with_params(meta, params.0), + SECOND::configure_with_params(meta, params.1), + ) + } +} + +impl, SECOND: PromiseBuilder> PromiseBuilder + for PromiseBuilderCombo +{ + fn get_component_type_dependencies() -> Vec { + [FIRST::get_component_type_dependencies(), SECOND::get_component_type_dependencies()] + .concat() + } + fn extract_loader_params_per_component_type( + params: &Self::Params, + ) -> Vec { + FIRST::extract_loader_params_per_component_type(¶ms.0) + .into_iter() + .chain(SECOND::extract_loader_params_per_component_type(¶ms.1)) + .collect() + } + + fn fulfill_promise_results(&mut self, promise_results_getter: &impl PromiseResultsGetter) { + self.to_combine.0.fulfill_promise_results(promise_results_getter); + self.to_combine.1.fulfill_promise_results(promise_results_getter); + } + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_commit_setter: &mut impl PromiseCommitSetter, + ) { + self.to_combine.0.virtual_assign_phase0(builder, promise_commit_setter); + self.to_combine.1.virtual_assign_phase0(builder, promise_commit_setter); + } + fn raw_synthesize_phase0(&mut self, config: &Self::Config, layouter: &mut impl Layouter) { + self.to_combine.0.raw_synthesize_phase0(&config.0, layouter); + self.to_combine.1.raw_synthesize_phase0(&config.1, layouter); + } + fn virtual_assign_phase1( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_calls_getter: &mut impl PromiseCallsGetter, + ) { + self.to_combine.0.virtual_assign_phase1(builder, promise_calls_getter); + self.to_combine.1.virtual_assign_phase1(builder, promise_calls_getter); + } + fn raw_synthesize_phase1(&mut self, config: &Self::Config, layouter: &mut impl Layouter) { + self.to_combine.0.raw_synthesize_phase1(&config.0, layouter); + self.to_combine.1.raw_synthesize_phase1(&config.1, layouter); + } +} diff --git a/axiom-eth/src/utils/component/promise_loader/comp_loader.rs b/axiom-eth/src/utils/component/promise_loader/comp_loader.rs new file mode 100644 index 00000000..6d77f458 --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/comp_loader.rs @@ -0,0 +1,372 @@ +#![allow(clippy::type_complexity)] +use std::{iter, marker::PhantomData}; + +use crate::utils::component::utils::compute_poseidon; +use crate::Field; +use crate::{ + rlc::{chip::RlcChip, circuit::builder::RlcCircuitBuilder, RLC_PHASE}, + utils::component::{ + types::FixLenLogical, utils::compute_poseidon_merkle_tree, FlattenVirtualRow, + FlattenVirtualTable, LogicalResult, PromiseShardMetadata, SelectedDataShardsInMerkle, + }, +}; +use getset::{CopyGetters, Getters}; +use halo2_base::{ + gates::{circuit::builder::BaseCircuitBuilder, GateInstructions, RangeChip, RangeInstructions}, + AssignedValue, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use super::{ + super::{ + promise_collector::PromiseResultWitness, + types::Flatten, + utils::{compute_commitment_with_flatten, create_hasher}, + ComponentPromiseResultsInMerkle, ComponentType, ComponentTypeId, + }, + flatten_witness_to_rlc, +}; + +/// To seal SingleComponentLoader so no external implementation is allowed. +mod private { + pub trait Sealed {} +} + +#[derive(Clone, Debug, Hash, Getters, CopyGetters, Serialize, Deserialize, Eq, PartialEq)] +/// Specify what merkle tree of commits can be loaded. +pub struct SingleComponentLoaderParams { + /// The maximum height of the merkle tree this loader can load. + #[getset(get_copy = "pub")] + max_height: usize, + /// Specify the number of shards to be loaded and the capacity of each shard. + #[getset(get = "pub")] + shard_caps: Vec, +} + +impl SingleComponentLoaderParams { + /// Create SingleComponentLoaderParams + pub fn new(max_height: usize, shard_caps: Vec) -> Self { + // Tip: binary tree with only 1 node has height 0. + assert!(shard_caps.len() <= 1 << max_height); + Self { max_height, shard_caps } + } + /// Create SingleComponentLoaderParams for only 1 shard + pub fn new_for_one_shard(cap: usize) -> Self { + Self { max_height: 0, shard_caps: vec![cap] } + } +} + +impl Default for SingleComponentLoaderParams { + fn default() -> Self { + Self::new(0, vec![0]) + } +} + +/// Object safe trait for loading promises of a component type. +pub trait SingleComponentLoader: private::Sealed { + /// Get the component type id this loader is for. + fn get_component_type_id(&self) -> ComponentTypeId; + /// Get ComponentTypeName for logging/debugging. + fn get_component_type_name(&self) -> &'static str; + fn get_params(&self) -> &SingleComponentLoaderParams; + /// Check if promise results are ready. + fn promise_results_ready(&self) -> bool; + /// Load promise results from promise results getter. + fn load_promise_results(&mut self, promise_results: ComponentPromiseResultsInMerkle); + /// Load dummy promise results according to the loader params. + fn load_dummy_promise_results(&mut self); + /// Return (merkle_tree_root, concat_assigned_virtual_tables). Data is preloaded. + /// TODO: do we really need to return assigned virtual table? + fn assign_and_compute_commitment( + &self, + builder: &mut RlcCircuitBuilder, + ) -> (AssignedValue, FlattenVirtualTable>); + /// Return (to_lookup, lookup_table) + fn generate_lookup_rlc( + &self, + builder: &mut RlcCircuitBuilder, + promise_calls: &[&PromiseResultWitness], + promise_results: &[FlattenVirtualRow>], + ) -> (Vec>, Vec>); +} + +/// Promise results but each shard is in the virtual table format. +type PromiseVirtualTableResults = SelectedDataShardsInMerkle>; + +/// Implementation of SingleComponentLoader for a component type. +pub struct SingleComponentLoaderImpl> { + val_promise_results: Option>, + params: SingleComponentLoaderParams, + _phantom: PhantomData, +} + +impl> SingleComponentLoaderImpl { + /// Create SingleComponentLoaderImpl for T. + pub fn new(params: SingleComponentLoaderParams) -> Self { + Self { val_promise_results: None, params, _phantom: PhantomData } + } + /// Create dummy promise results based on params for CircuitBuilder params calculation. + fn create_dummy_promise_result_merkle(&self) -> PromiseVirtualTableResults { + let num_shards = self.params.shard_caps.len(); + let num_leaves = num_shards.next_power_of_two(); + let mut leaves = Vec::with_capacity(num_leaves); + for i in 0..num_leaves { + let commit = F::ZERO; + leaves.push(PromiseShardMetadata:: { + commit, + capacity: if i < num_shards { self.params.shard_caps[i] } else { 0 }, + }); + } + let shards = self + .params + .shard_caps + .iter() + .copied() + .enumerate() + .map(|(idx, shard_cap)| { + let dummy_input = Flatten:: { + fields: vec![F::ZERO; T::InputValue::get_num_fields()], + field_size: T::InputValue::get_field_size(), + }; + let dummy_output = Flatten:: { + fields: vec![F::ZERO; T::OutputValue::get_num_fields()], + field_size: T::OutputValue::get_field_size(), + }; + let shard = vec![(dummy_input, dummy_output); shard_cap]; + (idx, shard) + }) + .collect_vec(); + PromiseVirtualTableResults::::new(leaves, shards) + } +} + +impl> private::Sealed for SingleComponentLoaderImpl {} + +impl> SingleComponentLoader for SingleComponentLoaderImpl { + fn get_component_type_id(&self) -> ComponentTypeId { + T::get_type_id() + } + fn get_component_type_name(&self) -> &'static str { + T::get_type_name() + } + fn get_params(&self) -> &SingleComponentLoaderParams { + &self.params + } + fn promise_results_ready(&self) -> bool { + self.val_promise_results.is_some() + } + fn load_promise_results(&mut self, promise_results: ComponentPromiseResultsInMerkle) { + // Tip: binary tree with only 1 node has height 0. + assert!(promise_results.leaves().len() <= 1 << self.params.max_height); + assert_eq!(promise_results.shards().len(), self.params.shard_caps().len()); + + let merkle_vt = promise_results.map_data(|typeless_prs| { + typeless_prs + .into_iter() + .flat_map(|typeless_prs| { + FlattenVirtualTable::::from( + LogicalResult::::try_from(typeless_prs).unwrap(), + ) + }) + .collect_vec() + }); + + for (shard_idx, shard) in merkle_vt.shards() { + let shard_capacity = merkle_vt.leaves()[*shard_idx].capacity; + assert_eq!(shard_capacity, shard.len()); + } + self.val_promise_results = Some(merkle_vt); + } + + fn assign_and_compute_commitment( + &self, + builder: &mut RlcCircuitBuilder, + ) -> (AssignedValue, FlattenVirtualTable>) { + let val_promise_results = + if let Some(val_promise_results) = self.val_promise_results.as_ref() { + val_promise_results.clone() + } else { + self.create_dummy_promise_result_merkle() + }; + let leaves_to_load = val_promise_results.leaves(); + + let assigned_per_shard = val_promise_results + .shards() + .iter() + .map(|(_, vt)| { + let ctx = builder.base.main(0); + let witness_vt = + vt.iter().map(|(v_i, v_o)| (v_i.assign(ctx), v_o.assign(ctx))).collect_vec(); + let commit = T::Commiter::compute_commitment(&mut builder.base, &witness_vt); + (commit, witness_vt) + }) + .collect_vec(); + + let range_chip = &builder.range_chip(); + let gate_chip = &range_chip.gate; + let ctx = builder.base.main(0); + // Indexes of selected shards. The length is deterministic because we had + // checked selected_shard.len() == params.shard_caps.len() in load_promise_results. + let selected_shards = ctx.assign_witnesses( + val_promise_results.shards().iter().map(|(shard_idx, _)| F::from(*shard_idx as u64)), + ); + + // The circuit has a fixed `self.params.max_height`. This is the maximum height merkle tree supported. + // However the private inputs of the circuit will dictate the actual heigh of the merkle tree of shards that this circuit is using. + // Example: + // We have max height is 3. + // However, this circuit will only get `leaves_to_load` for 4 shard commitments: [a, b, c, d] + // It will compute the merkle root of [a, b, c, d] where `4` is a private witness. + // Then it may have `selected_shards = [0, 2]`, meaning it only de-commits the shards for a, c. It does this by using `select_from_idx` on `[a, b, c, d]` to get `a, c`. + // Because we always decommit the leaves, meaning we dictate that the leaves much be flat hashes of virtual tables of fixed size (given by `shard_caps`), the + // private witness for the true height (in this example `4`), is commited to by the merkle root we generate. + // In other words, our definition of shard commitment provides domain separation for the merkle leaves. + + // The loader's behavior should not depend on inputs. So the loader always computes a merkle tree with a pre-defined height. + // Then we put the merkle tree to load in the left-bottom of the pre-defined merkle tree. The rest of the leaves are filled with zeros. + // The root of the merkle tree to load will be on the leftmost path of the pre-defined merkle tree. So we can select the root by + // the height of the merkle tree to load. + + let num_leaves = 1 << self.params.max_height; + let leaves_commits = ctx.assign_witnesses( + leaves_to_load.iter().map(|l| l.commit).chain(iter::repeat(F::ZERO)).take(num_leaves), + ); + let mut assigned_vts = Vec::with_capacity(assigned_per_shard.len()); + for (selected_shard, (shard_commit, assigned_vt)) in + selected_shards.into_iter().zip_eq(assigned_per_shard) + { + range_chip.check_less_than_safe(ctx, selected_shard, num_leaves as u64); + let leaf_commit = + gate_chip.select_from_idx(ctx, leaves_commits.clone(), selected_shard); + ctx.constrain_equal(&leaf_commit, &shard_commit); + + assigned_vts.push(assigned_vt); + } + let flatten_assigned_vts = assigned_vts.into_iter().flatten().collect_vec(); + + // Optimization: if there is only one shard, we don't need to compute the merkle tree so no need to create hasher. + if leaves_commits.len() == 1 { + return (leaves_commits[0], flatten_assigned_vts); + }; + + let mut hasher = create_hasher::(); + hasher.initialize_consts(ctx, gate_chip); + let nodes = compute_poseidon_merkle_tree(ctx, gate_chip, &hasher, leaves_commits); + + // Leftmost nodes of the pre-defined merkle tree from bottom to top. + let leftmost_nodes = + (0..=self.params.max_height).rev().map(|i| nodes[(1 << i) - 1]).collect_vec(); + // The height of the merkle tree to load. + let result_height: AssignedValue = + ctx.load_witness(F::from(leaves_to_load.len().ilog2() as u64)); + range_chip.check_less_than_safe(ctx, result_height, (self.params.max_height + 1) as u64); + + let output_commit = gate_chip.select_from_idx(ctx, leftmost_nodes, result_height); + + (output_commit, flatten_assigned_vts) + } + + fn generate_lookup_rlc( + &self, + builder: &mut RlcCircuitBuilder, + promise_calls: &[&PromiseResultWitness], + promise_results: &[FlattenVirtualRow>], + ) -> (Vec>, Vec>) { + let range_chip = &builder.range_chip(); + let rlc_chip = builder.rlc_chip(&range_chip.gate); + generate_lookup_rlcs_impl::( + builder, + range_chip, + &rlc_chip, + promise_calls, + promise_results, + ) + } + + fn load_dummy_promise_results(&mut self) { + let vt = self.create_dummy_promise_result_merkle(); + self.val_promise_results = Some(vt); + } +} + +/// Returns `(to_lookup_rlc, lookup_table_rlc)` +/// where `to_lookup_rlc` corresponds to `promise_calls` and +/// `lookup_table_rlc` corresponds to `promise_results`. +/// +/// This should only be called in phase1. +pub fn generate_lookup_rlcs_impl>( + builder: &mut RlcCircuitBuilder, + range_chip: &RangeChip, + rlc_chip: &RlcChip, + promise_calls: &[&PromiseResultWitness], + promise_results: &[(Flatten>, Flatten>)], +) -> (Vec>, Vec>) { + let gate_ctx = builder.base.main(RLC_PHASE); + + let input_multiplier = + rlc_chip.rlc_pow_fixed(gate_ctx, range_chip.gate(), T::OutputValue::get_num_fields()); + + let to_lookup_rlc = + builder.parallelize_phase1(promise_calls.to_vec(), |(gate_ctx, rlc_ctx), (f_i, f_o)| { + let i_rlc = f_i.to_rlc((gate_ctx, rlc_ctx), range_chip, rlc_chip); + let o_rlc = flatten_witness_to_rlc(rlc_ctx, rlc_chip, f_o); + range_chip.gate.mul_add(gate_ctx, i_rlc, input_multiplier, o_rlc) + }); + + let (gate_ctx, rlc_ctx) = builder.rlc_ctx_pair(); + + let lookup_table_rlc = T::rlc_virtual_rows( + (gate_ctx, rlc_ctx), + range_chip, + rlc_chip, + &promise_results + .iter() + .map(|(f_i, f_o)| { + ( + T::InputWitness::try_from(f_i.clone()).unwrap(), + T::OutputWitness::try_from(f_o.clone()).unwrap(), + ) + }) + .collect_vec(), + ); + (to_lookup_rlc, lookup_table_rlc) +} + +/// Trait for computing commit of ONE virtual table. +pub trait ComponentCommiter { + /// Compute the commitment of a virtual table. + fn compute_commitment( + builder: &mut BaseCircuitBuilder, + witness_promise_results: &[(Flatten>, Flatten>)], + ) -> AssignedValue; + /// The implementor **must** enforce that the output of this function + /// is the same as the output value of `compute_commitment`. + /// We allow a separate implemenation purely for performance, as the native commitmnt + /// computation is much faster than doing it in the circuit. + fn compute_native_commitment(witness_promise_results: &[(Flatten, Flatten)]) -> F; +} + +/// BasicComponentCommiter simply compute poseidon of all virtual rows. +pub struct BasicComponentCommiter(PhantomData); + +impl ComponentCommiter for BasicComponentCommiter { + fn compute_commitment( + builder: &mut BaseCircuitBuilder, + witness_promise_results: &[(Flatten>, Flatten>)], + ) -> AssignedValue { + let range_chip = &builder.range_chip(); + let ctx = builder.main(0); + + let mut hasher = create_hasher::(); + hasher.initialize_consts(ctx, &range_chip.gate); + compute_commitment_with_flatten(ctx, &range_chip.gate, &hasher, witness_promise_results) + } + fn compute_native_commitment(witness_promise_results: &[(Flatten, Flatten)]) -> F { + let to_commit = witness_promise_results + .iter() + .flat_map(|(i, o)| i.fields.iter().chain(o.fields.iter()).copied()) + .collect_vec(); + compute_poseidon(&to_commit) + } +} diff --git a/axiom-eth/src/utils/component/promise_loader/empty.rs b/axiom-eth/src/utils/component/promise_loader/empty.rs new file mode 100644 index 00000000..8d0e8741 --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/empty.rs @@ -0,0 +1,71 @@ +use std::marker::PhantomData; + +use crate::rlc::circuit::builder::RlcCircuitBuilder; +use crate::utils::component::circuit::LoaderParamsPerComponentType; +use halo2_base::halo2_proofs::{circuit::Layouter, plonk::ConstraintSystem}; + +use crate::Field; + +use crate::utils::component::{ + circuit::{ComponentBuilder, PromiseBuilder}, + promise_collector::{PromiseCallsGetter, PromiseCommitSetter, PromiseResultsGetter}, + ComponentTypeId, +}; + +#[derive(Default)] +/// Empty promise loader. +pub struct EmptyPromiseLoader(PhantomData); + +impl ComponentBuilder for EmptyPromiseLoader { + type Config = (); + type Params = (); + + fn new(_params: Self::Params) -> Self { + Self(PhantomData) + } + + fn get_params(&self) -> Self::Params {} + + fn clear_witnesses(&mut self) {} + + fn configure_with_params( + _meta: &mut ConstraintSystem, + _params: Self::Params, + ) -> Self::Config { + } + fn calculate_params(&mut self) -> Self::Params {} +} + +impl PromiseBuilder for EmptyPromiseLoader { + fn get_component_type_dependencies() -> Vec { + vec![] + } + fn extract_loader_params_per_component_type( + _params: &Self::Params, + ) -> Vec { + vec![] + } + fn fulfill_promise_results(&mut self, _promise_results_getter: &impl PromiseResultsGetter) { + // Do nothing. + } + fn virtual_assign_phase0( + &mut self, + _builder: &mut RlcCircuitBuilder, + _promise_commit_setter: &mut impl PromiseCommitSetter, + ) { + // Do nothing. + } + fn raw_synthesize_phase0(&mut self, _config: &Self::Config, _layouter: &mut impl Layouter) { + // Do nothing. + } + fn virtual_assign_phase1( + &mut self, + _builder: &mut RlcCircuitBuilder, + _promise_calls_getter: &mut impl PromiseCallsGetter, + ) { + // Do nothing. + } + fn raw_synthesize_phase1(&mut self, _config: &Self::Config, _layouter: &mut impl Layouter) { + // Do nothing. + } +} diff --git a/axiom-eth/src/utils/component/promise_loader/mod.rs b/axiom-eth/src/utils/component/promise_loader/mod.rs new file mode 100644 index 00000000..9f59dc2a --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/mod.rs @@ -0,0 +1,23 @@ +use crate::{rlc::chip::RlcChip, Field}; +use halo2_base::{AssignedValue, Context}; + +use super::types::Flatten; + +pub mod combo; +pub mod comp_loader; +pub mod empty; +pub mod multi; +pub mod single; +#[cfg(test)] +pub mod tests; +/// Utilities to help with creating dummy circuits for proving and verifying key generation. +pub mod utils; + +/// A helper function to compute RLC of (flatten input, flattne output). +pub fn flatten_witness_to_rlc( + rlc_ctx: &mut Context, + rlc_chip: &RlcChip, + f: &Flatten>, +) -> AssignedValue { + rlc_chip.compute_rlc_fixed_len(rlc_ctx, f.fields.clone()).rlc_val +} diff --git a/axiom-eth/src/utils/component/promise_loader/multi.rs b/axiom-eth/src/utils/component/promise_loader/multi.rs new file mode 100644 index 00000000..b97c5003 --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/multi.rs @@ -0,0 +1,337 @@ +#![allow(clippy::type_complexity)] +use std::{collections::HashMap, marker::PhantomData}; + +use crate::{ + rlc::{ + chip::RlcChip, + circuit::builder::{RlcCircuitBuilder, RlcContextPair}, + }, + utils::component::{ + circuit::LoaderParamsPerComponentType, + promise_loader::comp_loader::SingleComponentLoaderImpl, + }, + Field, +}; +use getset::{CopyGetters, Setters}; +use halo2_base::{ + gates::GateInstructions, + halo2_proofs::{ + circuit::Layouter, + plonk::{ConstraintSystem, SecondPhase}, + }, + virtual_region::{ + copy_constraints::SharedCopyConstraintManager, lookups::basic::BasicDynLookupConfig, + }, + AssignedValue, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::utils::component::{ + circuit::{ComponentBuilder, PromiseBuilder}, + promise_collector::{PromiseCallsGetter, PromiseCommitSetter, PromiseResultsGetter}, + promise_loader::flatten_witness_to_rlc, + types::{FixLenLogical, Flatten, LogicalEmpty}, + ComponentType, ComponentTypeId, +}; + +use super::comp_loader::{SingleComponentLoader, SingleComponentLoaderParams}; + +pub trait ComponentTypeList { + fn get_component_type_ids() -> Vec; + fn build_component_loaders( + params_per_component: &HashMap, + ) -> Vec>>; +} +pub struct ComponentTypeListEnd { + _phantom: PhantomData, +} +impl ComponentTypeList for ComponentTypeListEnd { + fn get_component_type_ids() -> Vec { + vec![] + } + fn build_component_loaders( + _params_per_component: &HashMap, + ) -> Vec>> { + vec![] + } +} +pub struct ComponentTypeListImpl, LATER: ComponentTypeList> { + _phantom: PhantomData<(F, HEAD, LATER)>, +} +impl, LATER: ComponentTypeList> ComponentTypeList + for ComponentTypeListImpl +{ + fn get_component_type_ids() -> Vec { + let mut ret = vec![HEAD::get_type_id()]; + ret.extend(LATER::get_component_type_ids()); + ret + } + fn build_component_loaders( + params_per_component: &HashMap, + ) -> Vec>> { + type Loader = SingleComponentLoaderImpl; + let mut ret = Vec::new(); + if let Some(params) = params_per_component.get(&HEAD::get_type_id()) { + let comp_loader: Box> = + Box::new(Loader::::new(params.clone())); + ret.push(comp_loader); + } + ret.extend(LATER::build_component_loaders(params_per_component)); + ret + } +} +#[macro_export] +macro_rules! component_type_list { + ($field:ty, $comp_type:ty) => { + $crate::utils::component::promise_loader::multi::ComponentTypeListImpl<$field, $comp_type, $crate::utils::component::promise_loader::multi::ComponentTypeListEnd<$field>> + }; + ($field:ty, $comp_type:ty, $($comp_types:ty),+) => { + $crate::utils::component::promise_loader::multi::ComponentTypeListImpl<$field, $comp_type, $crate::component_type_list!($field, $($comp_types),+)> + } +} + +#[derive(Clone)] +pub struct MultiPromiseLoaderConfig { + pub dyn_lookup_config: BasicDynLookupConfig<1>, +} + +// TODO: this is useless now because comp_loaders already have the information. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct MultiPromiseLoaderParams { + pub params_per_component: HashMap, +} + +/// Load promises of multiple component types which share the same lookup table. +/// The size of promise result it receives MUST match its capacity. +/// VT is a virtual component type which is used to generate lookup table. Its promise +/// results should not be fulfilled by external. +/// TODO: Currently we don't support promise calls for virtual component types so we enforce output to be empty. +/// TODO: remove virtual component type. +#[derive(CopyGetters, Setters)] +pub struct MultiPromiseLoader< + F: Field, + VT: ComponentType, OutputWitness = LogicalEmpty>>, + CLIST: ComponentTypeList, + A: RlcAdapter, +> { + params: MultiPromiseLoaderParams, + // ComponentTypeId -> (input, output) + witness_promise_results: Option< + HashMap>, Flatten>)>>, + >, + // (to lookup, lookup table) + witness_rlc_lookup: Option<(Vec>, Vec>)>, + // A bit hacky.. + witness_gen_only: bool, + copy_manager: Option>, + pub(super) comp_loaders: Vec>>, + _phantom: PhantomData<(VT, CLIST, A)>, +} + +pub trait RlcAdapter { + fn to_rlc( + ctx_pair: RlcContextPair, + gate: &impl GateInstructions, + rlc: &RlcChip, + type_id: &ComponentTypeId, + io_pairs: &[(Flatten>, Flatten>)], + ) -> Vec>; +} + +impl< + F: Field, + VT: ComponentType< + F, + OutputValue = LogicalEmpty, + OutputWitness = LogicalEmpty>, + >, + CLIST: ComponentTypeList, + A: RlcAdapter, + > ComponentBuilder for MultiPromiseLoader +{ + type Config = MultiPromiseLoaderConfig; + type Params = MultiPromiseLoaderParams; + + /// Create MultiPromiseLoader + fn new(params: MultiPromiseLoaderParams) -> Self { + let comp_loaders = CLIST::build_component_loaders(¶ms.params_per_component); + Self { + params, + witness_promise_results: None, + witness_rlc_lookup: None, + witness_gen_only: false, + copy_manager: None, + comp_loaders, + _phantom: PhantomData, + } + } + fn get_params(&self) -> Self::Params { + self.params.clone() + } + + fn clear_witnesses(&mut self) { + self.witness_promise_results = None; + self.witness_rlc_lookup = None; + self.copy_manager = None; + } + + fn configure_with_params( + meta: &mut ConstraintSystem, + _params: Self::Params, + ) -> Self::Config { + // TODO: adjust num of columns based on params. + let dyn_lookup_config = BasicDynLookupConfig::new(meta, || SecondPhase, 1); + Self::Config { dyn_lookup_config } + } + fn calculate_params(&mut self) -> Self::Params { + self.params.clone() + } +} + +impl< + F: Field, + VT: ComponentType< + F, + OutputValue = LogicalEmpty, + OutputWitness = LogicalEmpty>, + >, + CLIST: ComponentTypeList, + A: RlcAdapter, + > PromiseBuilder for MultiPromiseLoader +{ + // NOTE: the actual dependencies are based on the params. + fn get_component_type_dependencies() -> Vec { + CLIST::get_component_type_ids() + } + fn extract_loader_params_per_component_type( + params: &Self::Params, + ) -> Vec { + let mut ret = Vec::new(); + for type_id in Self::get_component_type_dependencies() { + if let Some(loader_params) = params.params_per_component.get(&type_id) { + ret.push(LoaderParamsPerComponentType { + component_type_id: type_id, + loader_params: loader_params.clone(), + }) + } + } + ret + } + fn fulfill_promise_results(&mut self, promise_results_getter: &impl PromiseResultsGetter) { + assert!( + promise_results_getter.get_results_by_component_type_id(&VT::get_type_id()).is_none(), + "promise results of the virtual component type should not be fulfilled" + ); + for comp_loader in &mut self.comp_loaders { + let component_type_id = comp_loader.get_component_type_id(); + let promise_results = promise_results_getter + .get_results_by_component_type_id(&component_type_id) + .unwrap_or_else(|| { + panic!("missing promise results for component type id {:?}", component_type_id) + }); + + comp_loader.load_promise_results(promise_results.clone()); + } + } + + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_commit_setter: &mut impl PromiseCommitSetter, + ) { + assert!(self.witness_promise_results.is_none()); + self.witness_gen_only = builder.witness_gen_only(); + + let mut witness_promise_results = HashMap::new(); + + for comp_loader in &self.comp_loaders { + // TODO: Multi-thread here? + let (commit, witness_promise_results_per_type) = + comp_loader.assign_and_compute_commitment(builder); + let component_type_id = comp_loader.get_component_type_id(); + promise_commit_setter + .set_commit_by_component_type_id(component_type_id.clone(), commit); + witness_promise_results.insert(component_type_id, witness_promise_results_per_type); + } + + self.witness_promise_results = Some(witness_promise_results); + } + + fn raw_synthesize_phase0(&mut self, _config: &Self::Config, _layouter: &mut impl Layouter) { + // Do nothing. + } + + fn virtual_assign_phase1( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_calls_getter: &mut impl PromiseCallsGetter, + ) { + assert!(self.witness_promise_results.is_some()); + let range_chip = &builder.range_chip(); + let rlc_chip = builder.rlc_chip(&range_chip.gate); + let (gate_ctx, rlc_ctx) = builder.rlc_ctx_pair(); + + let input_multiplier = + rlc_chip.rlc_pow_fixed(gate_ctx, &range_chip.gate, VT::OutputValue::get_num_fields()); + + let component_type_id = VT::get_type_id(); + + let calls_per_context = + promise_calls_getter.get_calls_by_component_type_id(&component_type_id).unwrap(); + + let to_lookup_rlc = calls_per_context + .values() + .flatten() + .map(|(f_i, f_o)| { + let i_rlc = f_i.to_rlc((gate_ctx, rlc_ctx), range_chip, &rlc_chip); + let o_rlc = flatten_witness_to_rlc(rlc_ctx, &rlc_chip, f_o); + range_chip.gate.mul_add(gate_ctx, i_rlc, input_multiplier, o_rlc) + }) + .collect_vec(); + + let num_dependencies = self.comp_loaders.len(); + let mut lookup_table_rlc = Vec::with_capacity(num_dependencies); + + // **Order must be deterministic.** + for comp_loader in &self.comp_loaders { + let component_type_id = comp_loader.get_component_type_id(); + let ctx_pair = (&mut *gate_ctx, &mut *rlc_ctx); + let lookup_table_rlc_per_type = A::to_rlc( + ctx_pair, + &range_chip.gate, + &rlc_chip, + &component_type_id, + &self.witness_promise_results.as_ref().unwrap()[&component_type_id], + ); + lookup_table_rlc.push(lookup_table_rlc_per_type); + } + let lookup_table_rlc = lookup_table_rlc.concat(); + + self.witness_rlc_lookup = Some((to_lookup_rlc, lookup_table_rlc)); + self.copy_manager = Some(builder.copy_manager().clone()); + } + + fn raw_synthesize_phase1(&mut self, config: &Self::Config, layouter: &mut impl Layouter) { + assert!(self.witness_rlc_lookup.is_some()); + + let (to_lookup, lookup_table) = self.witness_rlc_lookup.as_ref().unwrap(); + let dyn_lookup_config = &config.dyn_lookup_config; + + let copy_manager = (!self.witness_gen_only).then(|| self.copy_manager.as_ref().unwrap()); + dyn_lookup_config.assign_virtual_table_to_raw( + layouter.namespace(|| { + format!("promise loader adds advice to lookup for {}", VT::get_type_name()) + }), + lookup_table.iter().map(|a| [*a; 1]), + copy_manager, + ); + + dyn_lookup_config.assign_virtual_to_lookup_to_raw( + layouter + .namespace(|| format!("promise loader loads lookup table {}", VT::get_type_name())), + to_lookup.iter().map(|a| [*a; 1]), + copy_manager, + ); + } +} diff --git a/axiom-eth/src/utils/component/promise_loader/single.rs b/axiom-eth/src/utils/component/promise_loader/single.rs new file mode 100644 index 00000000..ed6d47ae --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/single.rs @@ -0,0 +1,205 @@ +#![allow(clippy::type_complexity)] +use std::marker::PhantomData; + +use getset::{CopyGetters, Setters}; +use halo2_base::{ + halo2_proofs::{ + circuit::Layouter, + plonk::{ConstraintSystem, SecondPhase}, + }, + virtual_region::{ + copy_constraints::SharedCopyConstraintManager, lookups::basic::BasicDynLookupConfig, + }, + AssignedValue, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::{ + rlc::circuit::builder::RlcCircuitBuilder, + utils::component::{ + circuit::{ComponentBuilder, LoaderParamsPerComponentType, PromiseBuilder}, + promise_collector::{PromiseCallsGetter, PromiseCommitSetter, PromiseResultsGetter}, + types::Flatten, + ComponentType, ComponentTypeId, + }, + Field, +}; + +use super::comp_loader::{ + SingleComponentLoader, SingleComponentLoaderImpl, SingleComponentLoaderParams, +}; + +#[derive(Clone)] +pub struct PromiseLoaderConfig { + pub dyn_lookup_config: BasicDynLookupConfig<1>, +} + +#[derive(Default, Clone, Debug, Hash, Serialize, Deserialize)] +pub struct PromiseLoaderParams { + pub comp_loader_params: SingleComponentLoaderParams, +} + +impl PromiseLoaderParams { + pub fn new(comp_loader_params: SingleComponentLoaderParams) -> Self { + Self { comp_loader_params } + } + pub fn new_for_one_shard(capacity: usize) -> Self { + Self { comp_loader_params: SingleComponentLoaderParams::new(0, vec![capacity]) } + } +} + +/// PromiseLoader loads promises of a component type. It owns a lookup table dedicated for the component type. +/// The size of promise result it receives MUST match its capacity. +#[derive(Setters, CopyGetters)] +pub struct PromiseLoader> { + params: PromiseLoaderParams, + witness_promise_results: Option>, Flatten>)>>, + // (to lookup, lookup table) + witness_rlc_lookup: Option<(Vec>, Vec>)>, + // A bit hacky.. + witness_gen_only: bool, + copy_manager: Option>, + pub(super) comp_loader: Box>, + _phantom: PhantomData, +} + +impl> ComponentBuilder for PromiseLoader { + type Config = PromiseLoaderConfig; + type Params = PromiseLoaderParams; + + // Create PromiseLoader + fn new(params: PromiseLoaderParams) -> Self { + Self { + params: params.clone(), + witness_promise_results: None, + witness_rlc_lookup: None, + witness_gen_only: false, + copy_manager: None, + comp_loader: Box::new(SingleComponentLoaderImpl::::new( + params.comp_loader_params, + )), + _phantom: PhantomData, + } + } + + fn get_params(&self) -> Self::Params { + self.params.clone() + } + + fn clear_witnesses(&mut self) { + self.witness_promise_results = None; + self.witness_rlc_lookup = None; + self.copy_manager = None; + } + + fn configure_with_params( + meta: &mut ConstraintSystem, + _params: Self::Params, + ) -> Self::Config { + // TODO: adjust num of columns based on params. + let dyn_lookup_config = BasicDynLookupConfig::new(meta, || SecondPhase, 1); + Self::Config { dyn_lookup_config } + } + fn calculate_params(&mut self) -> Self::Params { + self.params.clone() + } +} + +impl> PromiseBuilder for PromiseLoader { + fn get_component_type_dependencies() -> Vec { + vec![T::get_type_id()] + } + fn extract_loader_params_per_component_type( + params: &Self::Params, + ) -> Vec { + vec![LoaderParamsPerComponentType { + component_type_id: T::get_type_id(), + loader_params: params.comp_loader_params.clone(), + }] + } + fn fulfill_promise_results(&mut self, promise_results_getter: &impl PromiseResultsGetter) { + let component_type_id = self.comp_loader.get_component_type_id(); + let promise_results = promise_results_getter + .get_results_by_component_type_id(&component_type_id) + .unwrap_or_else(|| { + panic!("missing promise results for component type id {:?}", component_type_id) + }); + self.comp_loader.load_promise_results(promise_results.clone()); + // TODO: shard size check + } + + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_commit_setter: &mut impl PromiseCommitSetter, + ) { + assert!(self.witness_promise_results.is_none()); + self.witness_gen_only = builder.witness_gen_only(); + + let (commit, witness_promise_results) = + self.comp_loader.assign_and_compute_commitment(builder); + + let component_type_id = self.comp_loader.get_component_type_id(); + promise_commit_setter.set_commit_by_component_type_id(component_type_id, commit); + + self.witness_promise_results = Some(witness_promise_results); + } + + fn raw_synthesize_phase0(&mut self, _config: &Self::Config, _layouter: &mut impl Layouter) { + // Do nothing. + } + + fn virtual_assign_phase1( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_calls_getter: &mut impl PromiseCallsGetter, + ) { + assert!(self.witness_promise_results.is_some()); + let calls = promise_calls_getter + .get_calls_by_component_type_id(&self.comp_loader.get_component_type_id()) + .unwrap() + .values() + .flatten() + .collect_vec(); + let (to_lookup_rlc, lookup_table_rlc) = self.comp_loader.generate_lookup_rlc( + builder, + &calls, + self.witness_promise_results.as_ref().unwrap(), + ); + + self.witness_rlc_lookup = Some((to_lookup_rlc, lookup_table_rlc)); + + self.copy_manager = Some(builder.copy_manager().clone()); + } + + fn raw_synthesize_phase1(&mut self, config: &Self::Config, layouter: &mut impl Layouter) { + assert!(self.witness_rlc_lookup.is_some()); + + let (to_lookup, lookup_table) = self.witness_rlc_lookup.as_ref().unwrap(); + let dyn_lookup_config = &config.dyn_lookup_config; + + let copy_manager = (!self.witness_gen_only).then(|| self.copy_manager.as_ref().unwrap()); + dyn_lookup_config.assign_virtual_table_to_raw( + layouter.namespace(|| { + format!( + "promise loader adds advice to lookup for {}", + self.comp_loader.get_component_type_name() + ) + }), + lookup_table.iter().map(|a| [*a; 1]), + copy_manager, + ); + + dyn_lookup_config.assign_virtual_to_lookup_to_raw( + layouter.namespace(|| { + format!( + "promise loader loads lookup table {}", + self.comp_loader.get_component_type_name() + ) + }), + to_lookup.iter().map(|a| [*a; 1]), + copy_manager, + ); + } +} diff --git a/axiom-eth/src/utils/component/promise_loader/tests/combo.rs b/axiom-eth/src/utils/component/promise_loader/tests/combo.rs new file mode 100644 index 00000000..7df48b5c --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/tests/combo.rs @@ -0,0 +1,43 @@ +use crate::{ + halo2curves::bn256::Fr, + keccak::types::ComponentTypeKeccak, + utils::component::{ + circuit::{LoaderParamsPerComponentType, PromiseBuilder}, + promise_loader::{ + combo::PromiseBuilderCombo, + comp_loader::SingleComponentLoaderParams, + single::{PromiseLoader, PromiseLoaderParams}, + }, + tests::dummy_comp::ComponentTypeAdd, + ComponentType, + }, +}; + +#[test] +fn test_extract_loader_params_per_component_type() { + type Loader = PromiseBuilderCombo< + Fr, + PromiseLoader>, + PromiseLoader>, + >; + let comp_loader_params_1 = SingleComponentLoaderParams::new(3, vec![200]); + let comp_loader_params_2 = SingleComponentLoaderParams::new(2, vec![20]); + let params = ( + PromiseLoaderParams { comp_loader_params: comp_loader_params_1.clone() }, + PromiseLoaderParams { comp_loader_params: comp_loader_params_2.clone() }, + ); + let result = Loader::extract_loader_params_per_component_type(¶ms); + assert_eq!( + result, + vec![ + LoaderParamsPerComponentType { + component_type_id: ComponentTypeKeccak::::get_type_id(), + loader_params: comp_loader_params_1 + }, + LoaderParamsPerComponentType { + component_type_id: ComponentTypeAdd::::get_type_id(), + loader_params: comp_loader_params_2 + } + ] + ); +} diff --git a/axiom-eth/src/utils/component/promise_loader/tests/comp_loader.rs b/axiom-eth/src/utils/component/promise_loader/tests/comp_loader.rs new file mode 100644 index 00000000..14badced --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/tests/comp_loader.rs @@ -0,0 +1,242 @@ +use super::super::comp_loader::{ + BasicComponentCommiter, ComponentCommiter, SingleComponentLoader, SingleComponentLoaderImpl, + SingleComponentLoaderParams, +}; +use crate::Field; +use crate::{ + halo2curves::bn256::Fr, + rlc::circuit::builder::RlcCircuitBuilder, + utils::component::{ + tests::{ + dummy_comp::{ComponentTypeAdd, LogicalInputAdd, LogicalOutputAdd}, + sum_comp::{ComponentTypeSum, SumLogicalInput}, + }, + utils::compute_poseidon, + ComponentPromiseResult, ComponentPromiseResultsInMerkle, ComponentType, FlattenVirtualRow, + FlattenVirtualTable, LogicalResult, PromiseShardMetadata, + }, +}; +use halo2_base::{gates::circuit::builder::BaseCircuitBuilder, AssignedValue}; +use itertools::Itertools; +use lazy_static::lazy_static; + +type AddLogicalResult = LogicalResult>; +lazy_static! { + static ref MOCK_GAMMA: Fr = Fr::from(100u64); + static ref ADD_LOGICAL_RESUTLS_1: Vec = vec![ + AddLogicalResult::new( + LogicalInputAdd { a: Fr::from(1u64), b: Fr::from(2u64) }, + LogicalOutputAdd { c: Fr::from(3u64) }, + ), + AddLogicalResult::new( + LogicalInputAdd { a: Fr::from(4u64), b: Fr::from(5u64) }, + LogicalOutputAdd { c: Fr::from(9u64) }, + ), + ]; + static ref ADD_RESULTS_1_SQUEZZED_VT: Vec = + vec![1u64, 2, 3, 4, 5, 9].into_iter().map(Fr::from).collect_vec(); + static ref ADD_RESULTS_1_COMMIT: Fr = compute_poseidon(&ADD_RESULTS_1_SQUEZZED_VT); + static ref ADD_RESULTS_1_RLC: Vec = vec![Fr::from(10203), Fr::from(40509)]; + static ref ADD_LOGICAL_RESUTLS_2: Vec = vec![AddLogicalResult::new( + LogicalInputAdd { a: Fr::from(8u64), b: Fr::from(9u64) }, + LogicalOutputAdd { c: Fr::from(17u64) }, + ),]; + static ref ADD_RESULTS_2_SQUEZZED_VT: Vec = + vec![8u64, 9, 17].into_iter().map(Fr::from).collect_vec(); + static ref ADD_RESULTS_2_COMMIT: Fr = compute_poseidon(&ADD_RESULTS_2_SQUEZZED_VT); + static ref ADD_RESULTS_2_RLC: Vec = vec![Fr::from(80917)]; + static ref ADD_RESULTS_LEAVES: Vec> = vec![ + PromiseShardMetadata:: { + commit: *ADD_RESULTS_1_COMMIT, + capacity: ADD_LOGICAL_RESUTLS_1.len() + }, + PromiseShardMetadata:: { + commit: *ADD_RESULTS_2_COMMIT, + capacity: ADD_LOGICAL_RESUTLS_2.len() + } + ]; + static ref ADD_RESULTS_ROOT: Fr = + compute_poseidon(&[*ADD_RESULTS_1_COMMIT, *ADD_RESULTS_2_COMMIT]); +} + +fn squeeze_vritual_table(vt: FlattenVirtualTable) -> Vec { + vt.into_iter().flat_map(|(f_in, f_out)| [f_in.fields, f_out.fields]).flatten().collect_vec() +} + +fn assigned_flatten_vt_to_value( + vt: FlattenVirtualTable>, +) -> FlattenVirtualTable { + vt.clone().into_iter().map(|(f_in, f_out)| (f_in.into(), f_out.into())).collect_vec() +} + +fn logical_results_to_component_results>( + lrs: Vec>, +) -> Vec> { + lrs.into_iter().map(|lr| lr.into()).collect_vec() +} + +fn verify_component_loader>( + promise_results: ComponentPromiseResultsInMerkle, + comp_loader_params: SingleComponentLoaderParams, + mock_gamma: F, + expected_squeezed_vt: Vec, + expected_commit: F, + expected_promise_rlcs: Vec, +) { + let mut comp_loader = SingleComponentLoaderImpl::::new(comp_loader_params); + comp_loader.load_promise_results(promise_results); + let mut mock_builder = RlcCircuitBuilder::::new(false, 32).use_k(18).use_lookup_bits(8); + let (commit, flatten_vt) = comp_loader.assign_and_compute_commitment(&mut mock_builder); + let flatten_vt_val = assigned_flatten_vt_to_value(flatten_vt.clone()); + let squeezed_vt = squeeze_vritual_table(flatten_vt_val); + assert_eq!(squeezed_vt, expected_squeezed_vt); + assert_eq!(*commit.value(), expected_commit); + // Mock gamma to test RLC + mock_builder.gamma = Some(mock_gamma); + let (_, vt_rlcs) = comp_loader.generate_lookup_rlc(&mut mock_builder, &[], &flatten_vt); + assert_eq!(vt_rlcs.into_iter().map(|rlc| *rlc.value()).collect_vec(), expected_promise_rlcs); + // Clear to avoid warning outputs + mock_builder.clear(); +} + +#[test] +fn test_component_loader_1_shard() { + let promise_results = + ComponentPromiseResultsInMerkle::from_single_shard(ADD_LOGICAL_RESUTLS_1.clone()); + + let comp_loader_params = SingleComponentLoaderParams::new(2, vec![2]); + verify_component_loader::>( + promise_results, + comp_loader_params, + *MOCK_GAMMA, + ADD_RESULTS_1_SQUEZZED_VT.clone(), + *ADD_RESULTS_1_COMMIT, + ADD_RESULTS_1_RLC.clone(), + ); +} + +#[test] +fn test_component_loader_1_shard_3_times() { + let promise_results = ComponentPromiseResultsInMerkle::new( + ADD_RESULTS_LEAVES.clone(), + // Read shard 0 three times. + vec![ + (0, logical_results_to_component_results(ADD_LOGICAL_RESUTLS_1.clone())), + (0, logical_results_to_component_results(ADD_LOGICAL_RESUTLS_1.clone())), + (0, logical_results_to_component_results(ADD_LOGICAL_RESUTLS_1.clone())), + ], + ); + + // Read 3 shards with capacity = 2. + let comp_loader_params = SingleComponentLoaderParams::new(2, vec![2, 2, 2]); + + verify_component_loader::>( + promise_results, + comp_loader_params, + *MOCK_GAMMA, + [ + ADD_RESULTS_1_SQUEZZED_VT.clone(), + ADD_RESULTS_1_SQUEZZED_VT.clone(), + ADD_RESULTS_1_SQUEZZED_VT.clone(), + ] + .concat(), + *ADD_RESULTS_ROOT, + [ADD_RESULTS_1_RLC.clone(), ADD_RESULTS_1_RLC.clone(), ADD_RESULTS_1_RLC.clone()].concat(), + ); +} + +#[test] +fn test_component_loader_2_shard() { + let promise_results = ComponentPromiseResultsInMerkle::new( + ADD_RESULTS_LEAVES.clone(), + // Read shard 0 twice times and shard 1 once. + vec![ + (0, logical_results_to_component_results(ADD_LOGICAL_RESUTLS_1.clone())), + (1, logical_results_to_component_results(ADD_LOGICAL_RESUTLS_2.clone())), + (0, logical_results_to_component_results(ADD_LOGICAL_RESUTLS_1.clone())), + ], + ); + + // Read 3 shards with capacity = [2,1,2]. + let comp_loader_params = SingleComponentLoaderParams::new(2, vec![2, 1, 2]); + + verify_component_loader::>( + promise_results, + comp_loader_params, + *MOCK_GAMMA, + [ + ADD_RESULTS_1_SQUEZZED_VT.clone(), + ADD_RESULTS_2_SQUEZZED_VT.clone(), + ADD_RESULTS_1_SQUEZZED_VT.clone(), + ] + .concat(), + *ADD_RESULTS_ROOT, + [ADD_RESULTS_1_RLC.clone(), ADD_RESULTS_2_RLC.clone(), ADD_RESULTS_1_RLC.clone()].concat(), + ); +} + +#[test] +fn test_basic_commiter() { + let flatten_add_lrs: Vec> = ADD_LOGICAL_RESUTLS_1 + .clone() + .into_iter() + .flat_map(Vec::>::from) + .collect_vec(); + let mut mock_builder = BaseCircuitBuilder::::new(false).use_k(18).use_lookup_bits(8); + let ctx = mock_builder.main(0); + let assigned_flatten_add_lrs = flatten_add_lrs + .into_iter() + .map(|(f_lr_in, f_lr_out)| (f_lr_in.assign(ctx), f_lr_out.assign(ctx))) + .collect_vec(); + let commit = BasicComponentCommiter::::compute_commitment( + &mut mock_builder, + &assigned_flatten_add_lrs, + ); + assert_eq!(*commit.value(), ADD_RESULTS_1_COMMIT.clone()); +} + +#[test] +fn test_basic_native_commiter() { + let flatten_add_lrs: Vec> = ADD_LOGICAL_RESUTLS_1 + .clone() + .into_iter() + .flat_map(Vec::>::from) + .collect_vec(); + let commit = BasicComponentCommiter::::compute_native_commitment(&flatten_add_lrs); + assert_eq!(commit, ADD_RESULTS_1_COMMIT.clone()); +} + +type SumLogicalResult = LogicalResult>; +lazy_static! { + static ref SUM_LOGICAL_RESUTLS_1: Vec = vec![ + SumLogicalResult::new( + SumLogicalInput { to_sum: vec![3u64, 4u64, 5u64] }, + LogicalOutputAdd { c: Fr::from(12u64) }, + ), + SumLogicalResult::new( + SumLogicalInput { to_sum: vec![] }, + LogicalOutputAdd { c: Fr::from(0) }, + ), + ]; + static ref SUM_RESULTS_1_SQUEZZED_VT: Vec = + vec![0u64, 3, 3, 0, 4, 7, 1, 5, 12, 1, 0, 0].into_iter().map(Fr::from).collect_vec(); + static ref SUM_RESULTS_1_COMMIT: Fr = compute_poseidon(&SUM_RESULTS_1_SQUEZZED_VT); + static ref SUM_RESULTS_1_RLC: Vec = + vec![Fr::from(0), Fr::from(0), Fr::from(3040512), Fr::from(0)]; +} + +#[test] +fn test_component_loader_var_len_1_shard() { + let promise_results = + ComponentPromiseResultsInMerkle::from_single_shard(SUM_LOGICAL_RESUTLS_1.clone()); + + let comp_loader_params = SingleComponentLoaderParams::new(2, vec![4]); + verify_component_loader::>( + promise_results, + comp_loader_params, + *MOCK_GAMMA, + SUM_RESULTS_1_SQUEZZED_VT.clone(), + *SUM_RESULTS_1_COMMIT, + SUM_RESULTS_1_RLC.clone(), + ); +} diff --git a/axiom-eth/src/utils/component/promise_loader/tests/empty.rs b/axiom-eth/src/utils/component/promise_loader/tests/empty.rs new file mode 100644 index 00000000..2dcf4a7c --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/tests/empty.rs @@ -0,0 +1,10 @@ +use crate::{ + halo2curves::bn256::Fr, + utils::component::{circuit::PromiseBuilder, promise_loader::empty::EmptyPromiseLoader}, +}; + +#[test] +fn test_extract_loader_params_per_component_type() { + let result = EmptyPromiseLoader::::extract_loader_params_per_component_type(&()); + assert_eq!(result, vec![]); +} diff --git a/axiom-eth/src/utils/component/promise_loader/tests/mod.rs b/axiom-eth/src/utils/component/promise_loader/tests/mod.rs new file mode 100644 index 00000000..0b0b2403 --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/tests/mod.rs @@ -0,0 +1,5 @@ +pub mod combo; +pub mod comp_loader; +pub mod empty; +pub mod multi; +pub mod single; diff --git a/axiom-eth/src/utils/component/promise_loader/tests/multi.rs b/axiom-eth/src/utils/component/promise_loader/tests/multi.rs new file mode 100644 index 00000000..f059329e --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/tests/multi.rs @@ -0,0 +1,74 @@ +use halo2_base::{gates::GateInstructions, AssignedValue}; + +use crate::{ + component_type_list, + halo2curves::bn256::Fr, + keccak::types::ComponentTypeKeccak, + rlc::{chip::RlcChip, circuit::builder::RlcContextPair}, + utils::component::{ + circuit::{LoaderParamsPerComponentType, PromiseBuilder}, + promise_loader::{ + comp_loader::SingleComponentLoaderParams, + multi::{MultiPromiseLoader, MultiPromiseLoaderParams, RlcAdapter}, + }, + tests::dummy_comp::{ComponentTypeAdd, ComponentTypeAddMul}, + types::{EmptyComponentType, Flatten}, + ComponentType, ComponentTypeId, + }, +}; + +// Just for teseting purpose +struct DummyRlcAdapter {} +impl RlcAdapter for DummyRlcAdapter { + fn to_rlc( + _ctx_pair: RlcContextPair, + _gate: &impl GateInstructions, + _rlc: &RlcChip, + _type_id: &ComponentTypeId, + _io_pairs: &[(Flatten>, Flatten>)], + ) -> Vec> { + vec![] + } +} + +#[test] +fn test_extract_loader_params_per_component_type() { + type Dependencies = component_type_list!( + Fr, + ComponentTypeAdd, + ComponentTypeAddMul, + ComponentTypeKeccak + ); + type Loader = MultiPromiseLoader, Dependencies, DummyRlcAdapter>; + + let comp_loader_params_1 = SingleComponentLoaderParams::new(3, vec![200]); + let comp_loader_params_2 = SingleComponentLoaderParams::new(2, vec![500]); + let comp_loader_params_3 = SingleComponentLoaderParams::new(5, vec![20]); + + let expected_results = vec![ + LoaderParamsPerComponentType { + component_type_id: ComponentTypeAdd::::get_type_id(), + loader_params: comp_loader_params_1.clone(), + }, + LoaderParamsPerComponentType { + component_type_id: ComponentTypeAddMul::::get_type_id(), + loader_params: comp_loader_params_2.clone(), + }, + LoaderParamsPerComponentType { + component_type_id: ComponentTypeKeccak::::get_type_id(), + loader_params: comp_loader_params_3.clone(), + }, + ]; + + let params = MultiPromiseLoaderParams { + params_per_component: [ + (ComponentTypeKeccak::::get_type_id(), comp_loader_params_3), + (ComponentTypeAddMul::::get_type_id(), comp_loader_params_2), + (ComponentTypeAdd::::get_type_id(), comp_loader_params_1), + ] + .into_iter() + .collect(), + }; + let result = Loader::extract_loader_params_per_component_type(¶ms); + assert_eq!(result, expected_results); +} diff --git a/axiom-eth/src/utils/component/promise_loader/tests/single.rs b/axiom-eth/src/utils/component/promise_loader/tests/single.rs new file mode 100644 index 00000000..3e3f9dfc --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/tests/single.rs @@ -0,0 +1,29 @@ +use crate::{ + halo2curves::bn256::Fr, + keccak::types::ComponentTypeKeccak, + utils::component::{ + circuit::{LoaderParamsPerComponentType, PromiseBuilder}, + promise_loader::{ + comp_loader::SingleComponentLoaderParams, + single::{PromiseLoader, PromiseLoaderParams}, + }, + ComponentType, + }, +}; + +#[test] +fn test_extract_loader_params_per_component_type() { + let comp_loader_params = SingleComponentLoaderParams::new(3, vec![200]); + let params = PromiseLoaderParams { comp_loader_params: comp_loader_params.clone() }; + let result = + PromiseLoader::>::extract_loader_params_per_component_type( + ¶ms, + ); + assert_eq!( + result, + vec![LoaderParamsPerComponentType { + component_type_id: ComponentTypeKeccak::::get_type_id(), + loader_params: comp_loader_params + }] + ); +} diff --git a/axiom-eth/src/utils/component/promise_loader/utils.rs b/axiom-eth/src/utils/component/promise_loader/utils.rs new file mode 100644 index 00000000..83cef5ce --- /dev/null +++ b/axiom-eth/src/utils/component/promise_loader/utils.rs @@ -0,0 +1,60 @@ +use halo2_base::AssignedValue; + +use crate::{ + utils::component::{circuit::PromiseBuilder, types::LogicalEmpty, ComponentType}, + Field, +}; + +use super::{ + combo::PromiseBuilderCombo, + empty::EmptyPromiseLoader, + multi::{ComponentTypeList, MultiPromiseLoader, RlcAdapter}, + single, +}; + +/// Sub-trait for dummy fulfillment. Only used to create dummy circuits for +/// the purpose of proving/verifying key generation. +pub trait DummyPromiseBuilder: PromiseBuilder { + /// This should be the same behavior as `fulfill_promise_results` but with + /// dummy results. The exact configuration of the results is determined by + /// the `loader_params` of the promise builder. + fn fulfill_dummy_promise_results(&mut self); +} + +impl DummyPromiseBuilder for EmptyPromiseLoader { + fn fulfill_dummy_promise_results(&mut self) {} +} + +impl> DummyPromiseBuilder for single::PromiseLoader { + fn fulfill_dummy_promise_results(&mut self) { + self.comp_loader.load_dummy_promise_results(); + } +} + +impl, SECOND: DummyPromiseBuilder> DummyPromiseBuilder + for PromiseBuilderCombo +{ + fn fulfill_dummy_promise_results(&mut self) { + let (first, second) = &mut self.to_combine; + first.fulfill_dummy_promise_results(); + second.fulfill_dummy_promise_results(); + } +} + +impl< + F: Field, + VT: ComponentType< + F, + OutputValue = LogicalEmpty, + OutputWitness = LogicalEmpty>, + >, + CLIST: ComponentTypeList, + A: RlcAdapter, + > DummyPromiseBuilder for MultiPromiseLoader +{ + fn fulfill_dummy_promise_results(&mut self) { + for comp_loader in &mut self.comp_loaders { + comp_loader.load_dummy_promise_results(); + } + } +} diff --git a/axiom-eth/src/utils/component/tests/collector.rs b/axiom-eth/src/utils/component/tests/collector.rs new file mode 100644 index 00000000..198b75f3 --- /dev/null +++ b/axiom-eth/src/utils/component/tests/collector.rs @@ -0,0 +1,114 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +use halo2_base::AssignedValue; +use lazy_static::lazy_static; + +use crate::{ + halo2_proofs::halo2curves::bn256::Fr, + rlc::circuit::builder::RlcCircuitBuilder, + utils::component::{ + promise_collector::{PromiseCaller, PromiseCollector}, + tests::dummy_comp::LogicalOutputAdd, + ComponentPromiseResultsInMerkle, ComponentType, LogicalResult, + }, +}; + +use super::dummy_comp::{ComponentTypeAdd, LogicalInputAdd}; + +type AddLogicalResult = LogicalResult>; +lazy_static! { + static ref ADD_LOGICAL_RESUTLS_1: Vec = vec![ + AddLogicalResult::new( + LogicalInputAdd { a: Fr::from(1u64), b: Fr::from(2u64) }, + // 1+2=3 but we give 4 here to check if the result is not computed in circuit. + LogicalOutputAdd { c: Fr::from(4u64) }, + ), + AddLogicalResult::new( + LogicalInputAdd { a: Fr::from(4u64), b: Fr::from(5u64) }, + LogicalOutputAdd { c: Fr::from(9u64) }, + ), + ]; + static ref ADD_LOGICAL_RESUTLS_2: Vec = vec![AddLogicalResult::new( + LogicalInputAdd { a: Fr::from(8u64), b: Fr::from(9u64) }, + LogicalOutputAdd { c: Fr::from(17u64) }, + ),]; +} + +#[test] +fn test_promise_call_happy_path() { + let pc = PromiseCollector::::new(vec![ComponentTypeAdd::::get_type_id()]); + let shared_pc = Arc::new(Mutex::new(pc)); + + let promise_results = + ComponentPromiseResultsInMerkle::from_single_shard(ADD_LOGICAL_RESUTLS_1.clone()); + let mut results = HashMap::new(); + results.insert(ComponentTypeAdd::::get_type_id(), promise_results); + shared_pc.lock().unwrap().fulfill(&results); + + shared_pc.lock().unwrap().set_promise_results_ready(true); + let mut mock_builder = RlcCircuitBuilder::::new(false, 32).use_k(18).use_lookup_bits(8); + let ctx = mock_builder.base.main(0); + let a = ctx.load_constant(Fr::from(1u64)); + let b = ctx.load_constant(Fr::from(2u64)); + + let caller = PromiseCaller::::new(shared_pc.clone()); + let call_result = caller + .call::>, ComponentTypeAdd>( + ctx, + LogicalInputAdd { a, b }, + ) + .unwrap(); + // 1+2=3 but we give 4 here to check if the result is not computed in circuit. + assert_eq!(*call_result.c.value(), Fr::from(4)); + // To avoid warning outputs. + mock_builder.clear(); +} + +#[test] +#[should_panic] +fn test_promise_call_not_fulfilled() { + let pc = PromiseCollector::::new(vec![ComponentTypeAdd::::get_type_id()]); + let shared_pc = Arc::new(Mutex::new(pc)); + let caller = PromiseCaller::::new(shared_pc.clone()); + shared_pc.lock().unwrap().set_promise_results_ready(true); + let mut mock_builder = RlcCircuitBuilder::::new(false, 32).use_k(18).use_lookup_bits(8); + let ctx = mock_builder.base.main(0); + let a = ctx.load_constant(Fr::from(1u64)); + let b = ctx.load_constant(Fr::from(2u64)); + caller + .call::>, ComponentTypeAdd>( + ctx, + LogicalInputAdd { a, b }, + ) + .unwrap(); +} + +#[test] +#[should_panic] +fn test_promise_call_missing_result() { + let pc = PromiseCollector::::new(vec![ComponentTypeAdd::::get_type_id()]); + let shared_pc = Arc::new(Mutex::new(pc)); + + let promise_results = + ComponentPromiseResultsInMerkle::from_single_shard(ADD_LOGICAL_RESUTLS_2.clone()); + let mut results = HashMap::new(); + results.insert(ComponentTypeAdd::::get_type_id(), promise_results); + shared_pc.lock().unwrap().fulfill(&results); + + shared_pc.lock().unwrap().set_promise_results_ready(true); + let mut mock_builder = RlcCircuitBuilder::::new(false, 32).use_k(18).use_lookup_bits(8); + let ctx = mock_builder.base.main(0); + let a = ctx.load_constant(Fr::from(1u64)); + let b = ctx.load_constant(Fr::from(2u64)); + + let caller = PromiseCaller::::new(shared_pc.clone()); + caller + .call::>, ComponentTypeAdd>( + ctx, + LogicalInputAdd { a, b }, + ) + .unwrap(); +} diff --git a/axiom-eth/src/utils/component/tests/dummy_comp.rs b/axiom-eth/src/utils/component/tests/dummy_comp.rs new file mode 100644 index 00000000..67fbc075 --- /dev/null +++ b/axiom-eth/src/utils/component/tests/dummy_comp.rs @@ -0,0 +1,300 @@ +use crate::{ + impl_flatten_conversion, impl_logical_input, + rlc::circuit::builder::RlcCircuitBuilder, + utils::{ + build_utils::dummy::DummyFrom, + component::{ + circuit::{CoreBuilderInput, CoreBuilderOutput, CoreBuilderOutputParams}, + utils::{get_logical_value, into_key}, + }, + }, +}; +use halo2_base::{ + gates::GateInstructions, + halo2_proofs::{circuit::Layouter, plonk::ConstraintSystem}, + AssignedValue, +}; + +use super::{ + circuit::{ComponentBuilder, CoreBuilder}, + promise_collector::PromiseCaller, + promise_loader::flatten_witness_to_rlc, + types::{FixLenLogical, LogicalEmpty}, + utils::load_logical_value, + *, +}; + +// =============== Add Component =============== + +const ADD_INPUT_FIELD_SIZE: [usize; 2] = [64, 64]; +const ADD_OUTPUT_FIELD_SIZE: [usize; 1] = [128]; + +/// a + b +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct LogicalInputAdd { + pub a: T, + pub b: T, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)] +pub struct LogicalOutputAdd { + pub c: T, +} + +impl TryFrom> for LogicalInputAdd { + type Error = anyhow::Error; + fn try_from(mut value: Vec) -> Result { + if value.len() != 2 { + return Err(anyhow::anyhow!("invalid length")); + } + let b = value.pop().unwrap(); + let a = value.pop().unwrap(); + + Ok(LogicalInputAdd:: { a, b }) + } +} + +impl LogicalInputAdd { + pub fn flatten(self) -> Vec { + vec![self.a, self.b] + } +} + +impl_flatten_conversion!(LogicalInputAdd, ADD_INPUT_FIELD_SIZE); +impl_logical_input!(LogicalInputAdd, 1); + +impl PromiseCallWitness for LogicalInputAdd> { + fn get_component_type_id(&self) -> ComponentTypeId { + ComponentTypeAdd::::get_type_id() + } + fn get_capacity(&self) -> usize { + 1 + } + fn to_rlc( + &self, + (_, rlc_ctx): (&mut Context, &mut Context), + _range_chip: &RangeChip, + rlc_chip: &RlcChip, + ) -> AssignedValue { + flatten_witness_to_rlc(rlc_ctx, rlc_chip, &self.clone().into()) + } + fn to_typeless_logical_input(&self) -> TypelessLogicalInput { + let f_a: Flatten> = self.clone().into(); + let f_v: Flatten = f_a.into(); + let l_v: LogicalInputAdd = f_v.try_into().unwrap(); + into_key(l_v) + } + fn get_mock_output(&self) -> Flatten { + let output_val: as ComponentType>::OutputValue = Default::default(); + output_val.into() + } + fn as_any(&self) -> &dyn Any { + self + } +} + +// This should be done by marco. +impl TryFrom> for LogicalOutputAdd { + type Error = anyhow::Error; + + fn try_from(value: Flatten) -> std::result::Result { + if value.field_size != ADD_OUTPUT_FIELD_SIZE { + return Err(anyhow::anyhow!("invalid field size for add output")); + } + Ok(LogicalOutputAdd:: { c: value.fields[0] }) + } +} +// This should be done by marco. +impl From> for Flatten { + fn from(val: LogicalOutputAdd) -> Self { + Flatten:: { fields: vec![val.c], field_size: &ADD_OUTPUT_FIELD_SIZE } + } +} +// This should be done by marco. +impl FixLenLogical for LogicalOutputAdd { + fn get_field_size() -> &'static [usize] { + &ADD_OUTPUT_FIELD_SIZE + } +} + +#[derive(Debug, Clone)] +pub struct ComponentTypeAdd { + _phantom: std::marker::PhantomData, +} + +impl ComponentType for ComponentTypeAdd { + type InputValue = LogicalInputAdd; + type InputWitness = LogicalInputAdd>; + type OutputValue = LogicalOutputAdd; + type OutputWitness = LogicalOutputAdd>; + type LogicalInput = LogicalInputAdd; + + fn get_type_id() -> ComponentTypeId { + "ComponentTypeAdd".to_string() + } + + fn logical_result_to_virtual_rows_impl( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + vec![(ins.input.clone(), ins.output.clone())] + } + fn logical_input_to_virtual_rows_impl(li: &Self::LogicalInput) -> Vec { + vec![li.clone()] + } +} + +const ADD_MUL_INPUT_FIELD_SIZE: [usize; 3] = [64, 64, 64]; + +/// a * b + c +#[derive(Debug, Clone, Hash, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct LogicalInputAddMul { + pub a: T, + pub b: T, + pub c: T, +} + +pub type LogicalOutputAddMul = LogicalOutputAdd; + +impl TryFrom> for LogicalInputAddMul { + type Error = anyhow::Error; + fn try_from(mut value: Vec) -> Result { + if value.len() != 3 { + return Err(anyhow::anyhow!("invalid length")); + } + let c = value.pop().unwrap(); + let b = value.pop().unwrap(); + let a = value.pop().unwrap(); + + Ok(LogicalInputAddMul:: { a, b, c }) + } +} + +impl LogicalInputAddMul { + pub fn flatten(self) -> Vec { + vec![self.a, self.b, self.c] + } +} + +impl_flatten_conversion!(LogicalInputAddMul, ADD_MUL_INPUT_FIELD_SIZE); +impl_logical_input!(LogicalInputAddMul, 1); + +// =============== AddMul Component =============== +#[derive(Debug, Clone)] +pub struct ComponentTypeAddMul { + _phantom: std::marker::PhantomData, +} + +impl ComponentType for ComponentTypeAddMul { + type InputValue = LogicalInputAddMul; + type InputWitness = LogicalInputAddMul>; + type OutputValue = LogicalOutputAddMul; + type OutputWitness = LogicalOutputAddMul>; + type LogicalInput = LogicalInputAddMul; + + fn get_type_id() -> ComponentTypeId { + "axiom-eth:ComponentTypeAddMul".to_string() + } + + fn logical_result_to_virtual_rows_impl( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + vec![(ins.input.clone(), ins.output.clone())] + } + fn logical_input_to_virtual_rows_impl(li: &Self::LogicalInput) -> Vec { + vec![li.clone()] + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct CoreInputAddMul { + pub inputs: Vec>, +} + +impl DummyFrom for CoreInputAddMul { + fn dummy_from(params: CoreBuilderOutputParams) -> Self { + Self { + inputs: params + .cap_per_shard() + .iter() + .flat_map(|c| { + vec![LogicalInputAddMul:: { a: F::ZERO, b: F::ZERO, c: F::ZERO }; *c] + }) + .collect(), + } + } +} + +pub struct BuilderAddMul { + input: Option>, + params: CoreBuilderOutputParams, +} + +impl ComponentBuilder for BuilderAddMul { + type Config = (); + type Params = CoreBuilderOutputParams; + + fn new(params: CoreBuilderOutputParams) -> Self { + Self { input: None, params } + } + fn get_params(&self) -> Self::Params { + self.params.clone() + } + fn clear_witnesses(&mut self) {} + fn configure_with_params( + _meta: &mut ConstraintSystem, + _params: Self::Params, + ) -> Self::Config { + } + fn calculate_params(&mut self) -> Self::Params { + self.params.clone() + } +} + +impl CoreBuilder for BuilderAddMul { + type CompType = ComponentTypeAddMul; + type PublicInstanceValue = LogicalEmpty; + type PublicInstanceWitness = LogicalEmpty>; + type CoreInput = CoreInputAddMul; + fn feed_input(&mut self, input: Self::CoreInput) -> anyhow::Result<()> { + self.input = Some(input); + Ok(()) + } + + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_caller: PromiseCaller, + ) -> CoreBuilderOutput { + let range_chip = builder.range_chip(); + let ctx = builder.base.main(0); + let inputs: &Vec> = &self.input.as_ref().unwrap().inputs; + let (vt, lr): (FlattenVirtualTable>, Vec>) = inputs + .iter() + .map(|input| { + let witness_input = load_logical_value::< + F, + LogicalInputAddMul, + LogicalInputAddMul>, + >(ctx, input); + let witness_mul = range_chip.gate.mul(ctx, witness_input.a, witness_input.b); + let to_add = LogicalInputAdd { a: witness_mul, b: witness_input.c }; + let add_result = promise_caller + .call::>, ComponentTypeAdd>(ctx, to_add) + .unwrap(); + let add_result_val = get_logical_value(&add_result); + ( + (witness_input.into(), add_result.into()), + LogicalResult::::new(input.clone(), add_result_val), + ) + }) + .unzip(); + CoreBuilderOutput:: { + public_instances: vec![], + virtual_table: vt, + logical_results: lr, + } + } + fn raw_synthesize_phase0(&mut self, _config: &Self::Config, _layouter: &mut impl Layouter) {} + fn virtual_assign_phase1(&mut self, _builder: &mut RlcCircuitBuilder) {} + fn raw_synthesize_phase1(&mut self, _config: &Self::Config, _layouter: &mut impl Layouter) {} +} diff --git a/axiom-eth/src/utils/component/tests/mod.rs b/axiom-eth/src/utils/component/tests/mod.rs new file mode 100644 index 00000000..57641383 --- /dev/null +++ b/axiom-eth/src/utils/component/tests/mod.rs @@ -0,0 +1,276 @@ +use anyhow::Ok; +use ark_std::{end_timer, start_timer}; + +use crate::{ + rlc::circuit::RlcCircuitParams, + utils::{ + build_utils::pinning::CircuitPinningInstructions, + component::{ + circuit::CoreBuilderOutputParams, + promise_loader::comp_loader::SingleComponentLoaderParams, + }, + }, +}; +use halo2_base::{ + gates::circuit::BaseCircuitParams, + halo2_proofs::{ + halo2curves::bn256::{Bn256, Fr}, + plonk::{keygen_pk, keygen_vk}, + poly::kzg::commitment::ParamsKZG, + }, + utils::testing::{check_proof_with_instances, gen_proof_with_instances}, +}; +use lazy_static::lazy_static; +use rand_core::OsRng; + +use super::{ + circuit::ComponentCircuitImpl, + promise_loader::single::{PromiseLoader, PromiseLoaderParams}, + *, +}; + +pub mod collector; +/// Dummy components for testing. +pub mod dummy_comp; +pub mod sum_comp; +use dummy_comp::*; + +type CompCircuit = + ComponentCircuitImpl, PromiseLoader>>; + +fn build_dummy_component_circuit( + k: usize, + comp_loader_params: SingleComponentLoaderParams, + add_mul_cap: usize, + input: CoreInputAddMul, + promise_results: &GroupedPromiseResults, +) -> anyhow::Result { + let prompt_rlc_params = RlcCircuitParams { + base: BaseCircuitParams { + k, + lookup_bits: Some(8), + num_instance_columns: 1, + ..Default::default() + }, + num_rlc_columns: 1, + }; + let component_circuit: CompCircuit = ComponentCircuitImpl::new( + CoreBuilderOutputParams::new(vec![add_mul_cap]), + PromiseLoaderParams { comp_loader_params: comp_loader_params.clone() }, + prompt_rlc_params, + ); + component_circuit.feed_input(Box::new(input.clone()))?; + component_circuit.fulfill_promise_results(promise_results)?; + Ok(component_circuit) +} + +fn prover_test_dummy_component( + comp_loader_params: SingleComponentLoaderParams, + add_mul_cap: usize, + input: CoreInputAddMul, + promise_results: GroupedPromiseResults, +) -> anyhow::Result<()> { + let k = 16; + let mut component_circuit = build_dummy_component_circuit( + k, + comp_loader_params.clone(), + add_mul_cap, + input.clone(), + &promise_results, + )?; + component_circuit.calculate_params(); + + let mut rng = OsRng; + let params = ParamsKZG::::setup(k as u32, &mut rng); + let vk_time = start_timer!(|| "vk gen"); + let vk = keygen_vk(¶ms, &component_circuit).unwrap(); + end_timer!(vk_time); + let pk_time = start_timer!(|| "pk gen"); + let pk = keygen_pk(¶ms, vk, &component_circuit).unwrap(); + end_timer!(pk_time); + + // Reconstruct the circuit from pinning. + let pinning = component_circuit.pinning(); + component_circuit = CompCircuit::new( + CoreBuilderOutputParams::new(vec![add_mul_cap]), + PromiseLoaderParams { comp_loader_params: comp_loader_params.clone() }, + pinning.params.clone(), + ) + .use_break_points(pinning.break_points); + component_circuit.feed_input(Box::new(input))?; + component_circuit.fulfill_promise_results(&promise_results)?; + + let pf_time = start_timer!(|| "proof gen"); + let instances: Vec = component_circuit.get_public_instances().into(); + + let proof = gen_proof_with_instances(¶ms, &pk, component_circuit, &[&instances]); + end_timer!(pf_time); + + let verify_time = start_timer!(|| "verify"); + check_proof_with_instances(¶ms, pk.get_vk(), &proof, &[&instances], true); + end_timer!(verify_time); + + Ok(()) +} + +lazy_static! { + static ref ADD_MUL_INPUT: CoreInputAddMul = CoreInputAddMul:: { + inputs: vec![ + LogicalInputAddMul:: { a: Fr::from(1u64), b: Fr::from(2u64), c: Fr::from(3u64) }, + LogicalInputAddMul:: { a: Fr::from(4u64), b: Fr::from(5u64), c: Fr::from(6u64) }, + ] + }; + static ref ADD_MUL_RESULT: Vec>> = vec![ + LogicalResult::>::new( + LogicalInputAddMul:: { a: Fr::from(1u64), b: Fr::from(2u64), c: Fr::from(3u64) }, + LogicalOutputAddMul:: { c: Fr::from(5u64) }, + ), + LogicalResult::>::new( + LogicalInputAddMul:: { a: Fr::from(4u64), b: Fr::from(5u64), c: Fr::from(6u64) }, + LogicalOutputAddMul:: { c: Fr::from(26u64) }, + ) + ]; + static ref ADD_RESULT_SHARD1: Vec>> = + vec![LogicalResult::>::new( + LogicalInputAdd { a: Fr::from(7u64), b: Fr::from(8u64) }, + LogicalOutputAdd:: { c: Fr::from(15u64) }, + )]; + static ref ADD_RESULT_SHARD2: Vec>> = vec![ + LogicalResult::>::new( + LogicalInputAdd { a: Fr::from(2u64), b: Fr::from(3u64) }, + LogicalOutputAdd:: { c: Fr::from(5u64) }, + ), + LogicalResult::>::new( + LogicalInputAdd { a: Fr::from(20u64), b: Fr::from(6u64) }, + LogicalOutputAdd:: { c: Fr::from(26u64) }, + ), + ]; +} + +/// Helper function to create ComponentPromiseResults from multiple shards. +pub fn from_multi_shards>( + lrs: Vec>>, + selected_shards: Vec, +) -> ComponentPromiseResultsInMerkle { + let result_per_shard = + lrs.into_iter().map(ComponentPromiseResultsInMerkle::::from_single_shard).collect_vec(); + let leaves = result_per_shard.iter().map(|r| r.leaves[0].clone()).collect_vec(); + ComponentPromiseResultsInMerkle::::new( + leaves, + selected_shards + .into_iter() + .map(|idx| (idx, result_per_shard[idx].shards()[0].1.clone())) + .collect_vec(), + ) +} + +#[test] +fn test_input_height2_read1() -> anyhow::Result<()> { + // Read 1 shard from a merkle tree with height <= 2. + let comp_loader_params = SingleComponentLoaderParams::new(2, vec![2]); + let input = ADD_MUL_INPUT.clone(); + // mock add component. + let add_results_shard1 = ADD_RESULT_SHARD1.clone(); + let add_results_shard2 = ADD_RESULT_SHARD2.clone(); + let mut promise_results = HashMap::new(); + promise_results.insert( + ComponentTypeAdd::::get_type_id(), + from_multi_shards(vec![add_results_shard1, add_results_shard2], vec![1]), + ); + prover_test_dummy_component(comp_loader_params, input.inputs.len(), input, promise_results) +} + +#[test] +fn test_input_height2_read2() -> anyhow::Result<()> { + // Read 2 shard from a merkle tree with height <= 2. + let comp_loader_params = SingleComponentLoaderParams::new(2, vec![1, 2]); + let input = ADD_MUL_INPUT.clone(); + // mock add component. + let add_results_shard1 = ADD_RESULT_SHARD1.clone(); + let add_results_shard2 = ADD_RESULT_SHARD2.clone(); + let mut promise_results = HashMap::new(); + promise_results.insert( + ComponentTypeAdd::::get_type_id(), + from_multi_shards(vec![add_results_shard1, add_results_shard2], vec![0, 1]), + ); + prover_test_dummy_component(comp_loader_params, input.inputs.len(), input, promise_results) +} + +#[test] +fn test_input_height2_read_1shard_twice() -> anyhow::Result<()> { + // Read 2 shards from a merkle tree with height <= 2. + let comp_loader_params = SingleComponentLoaderParams::new(2, vec![2, 2]); + let input = ADD_MUL_INPUT.clone(); + // mock add component. + let add_results_shard1 = ADD_RESULT_SHARD1.clone(); + let add_results_shard2 = ADD_RESULT_SHARD2.clone(); + let mut promise_results = HashMap::new(); + promise_results.insert( + ComponentTypeAdd::::get_type_id(), + // Read shard 0 twice. + from_multi_shards(vec![add_results_shard1, add_results_shard2], vec![1, 1]), + ); + prover_test_dummy_component(comp_loader_params, input.inputs.len(), input, promise_results) +} + +#[test] +fn test_input_height0_read1() -> anyhow::Result<()> { + // Read 1 shard from a merkle tree with height = 0. + let comp_loader_params = SingleComponentLoaderParams::new(0, vec![2]); + let input = ADD_MUL_INPUT.clone(); + // mock add component. + let add_results_shard2 = ADD_RESULT_SHARD2.clone(); + let mut promise_results = HashMap::new(); + promise_results.insert( + ComponentTypeAdd::::get_type_id(), + from_multi_shards(vec![add_results_shard2], vec![0]), + ); + prover_test_dummy_component(comp_loader_params, input.inputs.len(), input, promise_results) +} + +#[test] +#[should_panic] +fn test_input_height2_missing_result() { + // Read 1 shard with cap=1 from a merkle tree with height <= 2. + let comp_loader_params = SingleComponentLoaderParams::new(2, vec![1]); + let input = ADD_MUL_INPUT.clone(); + // mock add component. + let add_results_shard1 = ADD_RESULT_SHARD1.clone(); + let add_results_shard2 = ADD_RESULT_SHARD2.clone(); + let mut promise_results = HashMap::new(); + promise_results.insert( + ComponentTypeAdd::::get_type_id(), + from_multi_shards( + vec![add_results_shard1, add_results_shard2], + // Shard 0 doesn't have all the promise results, so it should panic. + vec![0], + ), + ); + prover_test_dummy_component(comp_loader_params, input.inputs.len(), input, promise_results) + .unwrap(); +} + +#[test] +fn test_compute_outputs() -> anyhow::Result<()> { + // Read 1 shard from a merkle tree with height <= 2. + let comp_loader_params = SingleComponentLoaderParams::new(2, vec![2]); + let input = ADD_MUL_INPUT.clone(); + // mock add component. + let add_results_shard1 = ADD_RESULT_SHARD1.clone(); + let add_results_shard2 = ADD_RESULT_SHARD2.clone(); + let mut promise_results = HashMap::new(); + promise_results.insert( + ComponentTypeAdd::::get_type_id(), + from_multi_shards(vec![add_results_shard1, add_results_shard2], vec![1]), + ); + let circuit = build_dummy_component_circuit( + 16, + comp_loader_params, + input.inputs.len(), + input, + &promise_results, + )?; + let output = circuit.compute_outputs()?; + assert_eq!(output, SelectedDataShardsInMerkle::from_single_shard(ADD_MUL_RESULT.clone())); + Ok(()) +} diff --git a/axiom-eth/src/utils/component/tests/sum_comp.rs b/axiom-eth/src/utils/component/tests/sum_comp.rs new file mode 100644 index 00000000..44ffdb67 --- /dev/null +++ b/axiom-eth/src/utils/component/tests/sum_comp.rs @@ -0,0 +1,135 @@ +use crate::impl_flatten_conversion; +use halo2_base::{gates::GateInstructions, AssignedValue}; +use itertools::Itertools; + +use super::*; + +// An example of variable length component. +// =============== Sum Component =============== + +/// SumLogicalInput is the logical input of SumLogicalInput Component. +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct SumLogicalInput { + pub to_sum: Vec, +} + +impl LogicalInputValue for SumLogicalInput { + fn get_capacity(&self) -> usize { + if self.to_sum.is_empty() { + 1 + } else { + self.to_sum.len() + } + } +} + +const SUM_INPUT_FIELD_SIZE: [usize; 2] = [1, 64]; + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct FixLenLogicalInputSum { + pub is_final: T, + pub to_add: T, +} +impl TryFrom> for FixLenLogicalInputSum { + type Error = anyhow::Error; + fn try_from(mut value: Vec) -> Result { + if value.len() != 2 { + return Err(anyhow::anyhow!("invalid length")); + } + let to_add = value.pop().unwrap(); + let is_final = value.pop().unwrap(); + + Ok(FixLenLogicalInputSum:: { is_final, to_add }) + } +} + +impl FixLenLogicalInputSum { + pub fn flatten(self) -> Vec { + vec![self.is_final, self.to_add] + } +} + +impl_flatten_conversion!(FixLenLogicalInputSum, SUM_INPUT_FIELD_SIZE); + +pub type FixLenLogicalOutputSum = LogicalOutputAdd; + +#[derive(Debug, Clone)] +pub struct ComponentTypeSum { + _phantom: std::marker::PhantomData, +} + +impl ComponentType for ComponentTypeSum { + type InputValue = FixLenLogicalInputSum; + type InputWitness = FixLenLogicalInputSum>; + type OutputValue = FixLenLogicalOutputSum; + type OutputWitness = FixLenLogicalOutputSum>; + type LogicalInput = SumLogicalInput; + + fn get_type_id() -> ComponentTypeId { + "axiom-eth:ComponentTypeSum".to_string() + } + + fn logical_result_to_virtual_rows_impl( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + let input = Self::logical_input_to_virtual_rows_impl(&ins.input); + let mut to_sum = ins.input.to_sum.clone(); + if to_sum.is_empty() { + to_sum.push(0); + } + let mut prefix_sum = Vec::with_capacity(to_sum.len()); + let mut curr = 0u128; + for x in to_sum { + curr += x as u128; + prefix_sum.push(curr); + } + let output = prefix_sum + .into_iter() + .map(|ps| Self::OutputValue { c: F::from_u128(ps) }) + .collect_vec(); + input.into_iter().zip_eq(output).collect_vec() + } + fn logical_input_to_virtual_rows_impl(li: &Self::LogicalInput) -> Vec { + let len = li.to_sum.len(); + if len == 0 { + vec![FixLenLogicalInputSum { is_final: F::ONE, to_add: F::ZERO }] + } else { + li.to_sum + .iter() + .enumerate() + .map(|(idx, x)| FixLenLogicalInputSum { + is_final: if idx + 1 == len { F::ONE } else { F::ZERO }, + to_add: F::from(*x), + }) + .collect_vec() + } + } + + fn rlc_virtual_rows( + (gate_ctx, _rlc_ctx): (&mut Context, &mut Context), + range_chip: &RangeChip, + rlc_chip: &RlcChip, + inputs: &[(Self::InputWitness, Self::OutputWitness)], + ) -> Vec> { + // Overview: + // 1. if is_final == 0, rlc = 0. + // 2. if is_final == 1, rlc = rlc([input,result]) + // we don't need length information in RLC because zeros in the end will not affect the result. + let gate = &range_chip.gate; + + let gamma = rlc_chip.rlc_pow_fixed(gate_ctx, gate, 1); + let zero = gate_ctx.load_zero(); + + let mut ret = Vec::with_capacity(inputs.len()); + let mut curr_rlc = zero; + for (input, output) in inputs { + curr_rlc = gate.mul_add(gate_ctx, curr_rlc, gamma, input.to_add); + // rlc if is_final == 1 + let row_rlc = gate.mul_add(gate_ctx, curr_rlc, gamma, output.c); + let to_push = gate.select(gate_ctx, row_rlc, zero, input.is_final); + ret.push(to_push); + curr_rlc = gate.select(gate_ctx, zero, curr_rlc, input.is_final); + } + ret + } +} diff --git a/axiom-eth/src/utils/component/types.rs b/axiom-eth/src/utils/component/types.rs new file mode 100644 index 00000000..f5ad8ec0 --- /dev/null +++ b/axiom-eth/src/utils/component/types.rs @@ -0,0 +1,266 @@ +use std::{hash::Hash, marker::PhantomData}; + +use crate::Field; +use halo2_base::{AssignedValue, Context}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use super::param::{POSEIDON_RATE, POSEIDON_T}; +use super::{ComponentType, ComponentTypeId, LogicalInputValue, LogicalResult}; + +pub type PoseidonHasher = + halo2_base::poseidon::hasher::PoseidonHasher; + +/// Flatten represents a flatten fixed-len logical input/output/public instances. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Flatten { + pub fields: Vec, + pub field_size: &'static [usize], +} + +impl From>> for Flatten { + fn from(f: Flatten>) -> Self { + Flatten:: { + fields: f.fields.into_iter().map(|v| *v.value()).collect_vec(), + field_size: f.field_size, + } + } +} + +impl Flatten { + /// Assign Flatten. + pub fn assign(&self, ctx: &mut Context) -> Flatten> { + Flatten::> { + fields: ctx.assign_witnesses(self.fields.clone()), + field_size: self.field_size, + } + } +} + +impl From> for Vec { + fn from(val: Flatten) -> Self { + val.fields + } +} + +/// A logical input/output should be able to convert to a flatten logical input/ouptut. +pub trait FixLenLogical: + TryFrom, Error = anyhow::Error> + Into> + Clone +{ + /// Get field size of this logical. + fn get_field_size() -> &'static [usize]; + /// Get number of fields of this logical. + fn get_num_fields() -> usize { + Self::get_field_size().len() + } + /// From raw vec to logical. + fn try_from_raw(fields: Vec) -> anyhow::Result { + // TODO: we should auto generate this as Into> + let flatten = Flatten:: { fields, field_size: Self::get_field_size() }; + Self::try_from(flatten) + } + /// Into raw vec. + fn into_raw(self) -> Vec { + // TODO: we should auto generate this as Into> + self.into().fields + } +} + +#[derive(Clone, Debug)] +pub struct ComponentPublicInstances { + pub output_commit: T, + pub promise_result_commit: T, + pub other: Vec, +} + +type V = Vec; +impl From> for V { + fn from(val: ComponentPublicInstances) -> Self { + [vec![val.output_commit, val.promise_result_commit], val.other].concat() + } +} + +impl TryFrom> for ComponentPublicInstances { + type Error = anyhow::Error; + fn try_from(val: V) -> anyhow::Result { + if val.len() < 2 { + return Err(anyhow::anyhow!("invalid length")); + } + Ok(Self { output_commit: val[0], promise_result_commit: val[1], other: val[2..].to_vec() }) + } +} + +impl From>> for ComponentPublicInstances { + fn from(f: ComponentPublicInstances>) -> Self { + Self { + output_commit: *f.output_commit.value(), + promise_result_commit: *f.promise_result_commit.value(), + other: f.other.into_iter().map(|v| *v.value()).collect_vec(), + } + } +} + +const FIELD_SIZE_EMPTY: [usize; 0] = []; +/// Type for empty public instance +#[derive(Default, Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)] +pub struct LogicalEmpty(PhantomData); + +impl TryFrom> for LogicalEmpty { + type Error = anyhow::Error; + + fn try_from(value: Flatten) -> std::result::Result { + if value.field_size != FIELD_SIZE_EMPTY { + return Err(anyhow::anyhow!("invalid field size")); + } + if value.field_size.len() != value.fields.len() { + return Err(anyhow::anyhow!("field length doesn't match")); + } + Ok(LogicalEmpty::(PhantomData::)) + } +} +// This should be done by marco. +impl From> for Flatten { + fn from(_val: LogicalEmpty) -> Self { + Flatten:: { fields: vec![], field_size: &FIELD_SIZE_EMPTY } + } +} +// This should be done by marco. +impl FixLenLogical for LogicalEmpty { + fn get_field_size() -> &'static [usize] { + &FIELD_SIZE_EMPTY + } +} + +impl From> for Vec> { + fn from(val: LogicalEmpty) -> Self { + vec![val] + } +} + +impl LogicalInputValue for LogicalEmpty { + fn get_capacity(&self) -> usize { + 1 + } +} + +/// Empty component type. +#[derive(Debug, Clone)] +pub struct EmptyComponentType(PhantomData); +impl ComponentType for EmptyComponentType { + type InputValue = LogicalEmpty; + type InputWitness = LogicalEmpty>; + type OutputValue = LogicalEmpty; + type OutputWitness = LogicalEmpty>; + type LogicalInput = LogicalEmpty; + + fn get_type_id() -> ComponentTypeId { + "axiom-eth:EmptyComponentType".to_string() + } + + fn logical_result_to_virtual_rows_impl( + _ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + unreachable!() + } + + fn logical_input_to_virtual_rows_impl(_li: &Self::LogicalInput) -> Vec { + unreachable!() + } +} + +// ================== Macro for conversion to/from Flatten ================== +#[macro_export] +macro_rules! impl_flatten_conversion { + ($struct_name:ident, $bits_per_fe:ident) => { + impl TryFrom<$crate::utils::component::types::Flatten> for $struct_name { + type Error = anyhow::Error; + + fn try_from( + value: $crate::utils::component::types::Flatten, + ) -> anyhow::Result { + if &value.field_size != &$bits_per_fe { + anyhow::bail!("invalid field size"); + } + if value.field_size.len() != value.fields.len() { + anyhow::bail!("field length doesn't match"); + } + let res = value.fields.try_into()?; + Ok(res) + } + } + + impl From<$struct_name> for $crate::utils::component::types::Flatten { + fn from(value: $struct_name) -> Self { + $crate::utils::component::types::Flatten:: { + fields: value.flatten().to_vec(), + field_size: &$bits_per_fe, + } + } + } + + impl $crate::utils::component::types::FixLenLogical for $struct_name { + fn get_field_size() -> &'static [usize] { + &$bits_per_fe + } + } + }; +} + +#[macro_export] +macro_rules! impl_logical_input { + ($struct_name:ident, $capacity:expr) => { + impl> $crate::utils::component::LogicalInputValue + for $struct_name + { + fn get_capacity(&self) -> usize { + $capacity + } + } + }; +} + +#[macro_export] +macro_rules! impl_fix_len_call_witness { + ($call_name:ident, $fix_len_logical_name:ident, $component_type_name:ident) => { + #[derive(Clone, Debug)] + pub struct $call_name(pub $fix_len_logical_name>); + impl $crate::utils::component::PromiseCallWitness for $call_name { + fn get_component_type_id(&self) -> $crate::utils::component::ComponentTypeId { + $component_type_name::::get_type_id() + } + fn get_capacity(&self) -> usize { + 1 + } + fn to_rlc( + &self, + (_, rlc_ctx): (&mut $crate::halo2_base::Context, &mut $crate::halo2_base::Context), + _range_chip: &$crate::halo2_base::gates::RangeChip, + rlc_chip: &$crate::rlc::chip::RlcChip, + ) -> AssignedValue { + $crate::utils::component::promise_loader::flatten_witness_to_rlc( + rlc_ctx, + &rlc_chip, + &self.0.clone().into(), + ) + } + fn to_typeless_logical_input( + &self, + ) -> $crate::utils::component::TypelessLogicalInput { + let f_a: $crate::utils::component::types::Flatten> = + self.0.clone().into(); + let f_v: $crate::utils::component::types::Flatten = f_a.into(); + let l_v: <$component_type_name as ComponentType>::LogicalInput = + f_v.try_into().unwrap(); + $crate::utils::component::utils::into_key(l_v) + } + fn get_mock_output(&self) -> $crate::utils::component::types::Flatten { + let output_val: <$component_type_name as $crate::utils::component::ComponentType>::OutputValue = + Default::default(); + output_val.into() + } + fn as_any(&self) -> &dyn std::any::Any { + self + } + } + }; +} diff --git a/axiom-eth/src/utils/component/utils.rs b/axiom-eth/src/utils/component/utils.rs new file mode 100644 index 00000000..69187a47 --- /dev/null +++ b/axiom-eth/src/utils/component/utils.rs @@ -0,0 +1,168 @@ +use super::{param::*, types::*}; +use super::{ComponentType, FlattenVirtualRow}; +use crate::Field; +use halo2_base::gates::GateInstructions; +use halo2_base::gates::RangeChip; +use halo2_base::poseidon::hasher::spec::OptimizedPoseidonSpec; +use halo2_base::AssignedValue; +use halo2_base::Context; +use itertools::Itertools; +use serde::de::DeserializeOwned; +use serde::Serialize; +use snark_verifier::{loader::native::NativeLoader, util::hash::Poseidon}; + +/// Do not recreate this unless you need to: it recomputes the OptimizedPoseidonSpec each time. +/// +/// Unfortunately we can't use lazy_static due to the generic type `F`. +pub fn native_poseidon_hasher() -> Poseidon { + Poseidon::::new::< + POSEIDON_R_F, + POSEIDON_R_P, + POSEIDON_SECURE_MDS, + >(&NativeLoader) +} + +/// Do not recreate this unless you need to: it is computationally expensive. +/// +/// Unfortunately we can't use lazy_static due to the generic type `F`. +pub fn optimized_poseidon_spec() -> OptimizedPoseidonSpec { + OptimizedPoseidonSpec::::new::< + POSEIDON_R_F, + POSEIDON_R_P, + POSEIDON_SECURE_MDS, + >() +} + +pub fn compute_poseidon(payload: &[F]) -> F { + let mut native_poseidon_sponge = native_poseidon_hasher(); + native_poseidon_sponge.update(payload); + native_poseidon_sponge.squeeze() +} + +/// Return values of merkle tree nodes. Top to bottom, left to right. +pub fn compute_poseidon_merkle_tree( + ctx: &mut Context, + gate: &impl GateInstructions, + initialized_hasher: &PoseidonHasher, + leaves: Vec>, +) -> Vec> { + let len = leaves.len(); + // Also implict len > 0 + assert!(len.is_power_of_two()); + if len == 1 { + return leaves; + } + let next_level = + leaves.chunks(2).map(|c| initialized_hasher.hash_fix_len_array(ctx, gate, c)).collect_vec(); + let mut ret: Vec> = + compute_poseidon_merkle_tree(ctx, gate, initialized_hasher, next_level); + ret.extend(leaves); + ret +} + +pub fn compress_flatten_pair( + ctx: &mut Context, + range_chip: &RangeChip, + input: &Flatten>, + output: &Flatten>, +) -> Vec> { + let mut result = vec![]; + let mut used_bits = 0; + let const_zero = ctx.load_zero(); + let mut witness_current = const_zero; + for (a, bits) in input + .fields + .iter() + .chain(output.fields.iter()) + .zip_eq(input.field_size.iter().chain(output.field_size)) + { + let bits = *bits; + // If bits > capacity, this is a hacky way to speicify this field taking a whole witness. + if used_bits + bits <= (F::CAPACITY as usize) { + let const_mul = ctx.load_constant(range_chip.gate.pow_of_two[used_bits]); + witness_current = range_chip.gate.mul_add(ctx, const_mul, *a, witness_current); + if used_bits + bits == (F::CAPACITY as usize) { + result.push(witness_current); + used_bits = 0; + witness_current = const_zero; + } else { + used_bits += bits; + } + } else { + // TODO: maybe decompose a here to fully utilize capacity. + result.push(witness_current); + used_bits = bits; + witness_current = *a; + } + } + if used_bits > 0 { + result.push(witness_current); + } + result +} + +/// Load logical value as witness using Flatten as intermediate. V and W should come from +/// the same struct. +pub fn load_logical_value, W: FixLenLogical>>( + ctx: &mut Context, + v: &V, +) -> W { + let flatten_value: Flatten = v.clone().into(); + let flatten_witness = flatten_value.assign(ctx); + W::try_from(flatten_witness).unwrap() +} + +/// Get logical value from witness using Flatten as intermediate. V and W should come from +/// the same struct. +pub fn get_logical_value>, V: FixLenLogical>( + w: &W, +) -> V { + let flatten_witness: Flatten> = w.clone().into(); + let flatten_value: Flatten = flatten_witness.into(); + V::try_from(flatten_value).unwrap() +} + +pub fn create_hasher() -> PoseidonHasher { + // Construct in-circuit Poseidon hasher. + let spec = OptimizedPoseidonSpec::new::(); + PoseidonHasher::new(spec) +} + +pub fn compute_commitment>( + ctx: &mut Context, + gate: &impl GateInstructions, + initialized_hasher: &PoseidonHasher, + io_pairs: Vec<(T::InputWitness, T::OutputWitness)>, +) -> AssignedValue { + let flatten_io_pairs = io_pairs.into_iter().map(|(i, o)| (i.into(), o.into())).collect_vec(); + let commit = compute_commitment_with_flatten(ctx, gate, initialized_hasher, &flatten_io_pairs); + log::debug!("component_type_id: {} commit: {:?}", T::get_type_id(), commit.value()); + commit +} + +#[allow(clippy::type_complexity)] +pub fn compute_commitment_with_flatten( + ctx: &mut Context, + gate: &impl GateInstructions, + initialized_hasher: &PoseidonHasher, + io_pairs: &[FlattenVirtualRow>], +) -> AssignedValue { + if io_pairs.is_empty() { + return ctx.load_zero(); + } + let to_commit: Vec> = io_pairs + .iter() + .flat_map(|(i, o)| [i.fields.clone(), o.fields.clone()].concat()) + .collect_vec(); + initialized_hasher.hash_fix_len_array(ctx, gate, &to_commit) +} + +/// Convert LogicalInputValue into key which can be used to look up promise results. +pub fn into_key(key: impl Serialize) -> Vec { + bincode::serialize(&key).unwrap() +} + +/// Convert key back into LogicalInputValue. +pub fn try_from_key(key: &[u8]) -> anyhow::Result { + bincode::deserialize(key).map_err(anyhow::Error::from) +} diff --git a/axiom-eth/src/utils/eth_circuit.rs b/axiom-eth/src/utils/eth_circuit.rs new file mode 100644 index 00000000..5ce1c66a --- /dev/null +++ b/axiom-eth/src/utils/eth_circuit.rs @@ -0,0 +1,471 @@ +use std::{ + cell::RefCell, + collections::HashMap, + fs::File, + ops::DerefMut, + path::Path, + sync::{Arc, Mutex}, +}; + +use crate::Field; +use halo2_base::{ + gates::circuit::CircuitBuilderStage, + halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{self, Circuit, ConstraintSystem, SecondPhase}, + }, + virtual_region::{lookups::basic::BasicDynLookupConfig, manager::VirtualRegionManager}, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::{ + keccak::{ + types::{ComponentTypeKeccak, KeccakLogicalInput, OutputKeccakShard}, + KeccakChip, + }, + mpt::MPTChip, + rlc::{ + circuit::{builder::RlcCircuitBuilder, RlcCircuitParams, RlcConfig}, + virtual_region::RlcThreadBreakPoints, + }, + rlp::RlpChip, + utils::{ + build_utils::pinning::{CircuitPinningInstructions, RlcCircuitPinning}, + component::{ + circuit::{ComponentBuilder, PromiseBuilder}, + promise_collector::{PromiseCaller, PromiseCollector, SharedPromiseCollector}, + promise_loader::single::{PromiseLoader, PromiseLoaderConfig, PromiseLoaderParams}, + }, + DEFAULT_RLC_CACHE_BITS, + }, +}; + +use super::{ + build_utils::pinning::Halo2CircuitPinning, + component::{ + utils::try_from_key, ComponentPromiseResultsInMerkle, ComponentType, LogicalInputValue, + }, +}; + +/// Default number of lookup bits for range check is set to 8 for range checking bytes. +pub(crate) const ETH_LOOKUP_BITS: usize = 8; + +/// Configuration parameters for [EthConfig] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EthCircuitParams { + pub rlc: RlcCircuitParams, + /// Keccak promise loader + pub keccak: PromiseLoaderParams, +} + +impl Default for EthCircuitParams { + fn default() -> Self { + let mut rlc = RlcCircuitParams::default(); + rlc.base.num_instance_columns = 1; + rlc.base.lookup_bits = Some(ETH_LOOKUP_BITS); + let keccak = Default::default(); + Self { rlc, keccak } + } +} + +impl EthCircuitParams { + pub fn new(rlc: RlcCircuitParams, keccak: PromiseLoaderParams) -> Self { + Self { rlc, keccak } + } + pub fn from_path>(path: P) -> Self { + serde_json::from_reader(File::open(&path).unwrap()).unwrap() + } + pub fn k(&self) -> usize { + self.rlc.base.k + } + pub fn set_k(&mut self, k: usize) { + self.rlc.base.k = k; + } +} + +/// Halo2 Config shared by all circuits that prove data about the Ethereum execution layer (EL). +/// Includes [BaseConfig] and [PureRlcConfig] inside [RlcConfig] that use Base + RLC + Keccak +#[derive(Clone)] +pub struct EthConfig { + pub rlc_config: RlcConfig, + pub keccak: PromiseLoaderConfig, +} + +impl EthConfig { + pub fn configure(meta: &mut ConstraintSystem, params: impl Into) -> Self { + let params: EthCircuitParams = params.into(); + let k = params.k(); + let mut rlc_config = RlcConfig::configure(meta, params.rlc); + // TODO: allow 0 columns here for more flexility + let keccak = PromiseLoaderConfig { + dyn_lookup_config: BasicDynLookupConfig::new(meta, || SecondPhase, 1), + }; + log::info!("Poisoned rows after EthConfig::configure {}", meta.minimum_rows()); + // Warning: this needs to be updated if you create more advice columns after this `EthConfig` is created + let usable_rows = (1usize << k) - meta.minimum_rows(); + rlc_config.set_usable_rows(usable_rows); + Self { rlc_config, keccak } + } +} + +/// Simple trait describing the FirstPhase and SecondPhase witness generation of a circuit +/// that only uses [EthConfig]. +/// +/// * In FirstPhase, [MPTChip] is provided with `None` for RlcChip. +/// * In SecondPhase, [MPTChip] is provided with RlcChip that has challenge value loaded. +pub trait EthCircuitInstructions: Clone { + type FirstPhasePayload; + + fn virtual_assign_phase0( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + ) -> Self::FirstPhasePayload; + + /// SecondPhase is optional + #[allow(unused_variables)] + fn virtual_assign_phase1( + &self, + builder: &mut RlcCircuitBuilder, + mpt: &MPTChip, + payload: Self::FirstPhasePayload, + ) { + } +} + +/// This struct is used for the concrete implementation of [Circuit] trait from [EthCircuitInstructions]. +/// This provides a quick way to create a circuit that only uses [EthConfig]. +// This is basically a simplified version of `ComponentCircuitImpl` with `EthCircuitInstructions` + `PromiseLoader` for Keccak. +pub struct EthCircuitImpl> { + pub logic_inputs: I, + pub keccak_chip: KeccakChip, + pub rlc_builder: RefCell>, + pub promise_collector: SharedPromiseCollector, + pub promise_builder: RefCell>>, + /// The FirstPhasePayload is set after FirstPhase witness generation. + /// This is used both to pass payload between phases and also to detect if `virtual_assign_phase0` + /// was already run outside of `synthesize` (e.g., to determine public instances) + payload: RefCell>, +} + +impl EthCircuitImpl +where + F: Field, + I: EthCircuitInstructions, +{ + pub fn new( + logic_inputs: I, + prompt_rlc_params: RlcCircuitParams, + promise_params: PromiseLoaderParams, + ) -> Self { + // Mock is general, can be used for anything + Self::new_impl(CircuitBuilderStage::Mock, logic_inputs, prompt_rlc_params, promise_params) + } + pub fn new_impl( + stage: CircuitBuilderStage, + logic_inputs: I, + prompt_rlc_params: RlcCircuitParams, + promise_params: PromiseLoaderParams, + ) -> Self { + let rlc_builder = RlcCircuitBuilder::from_stage(stage, DEFAULT_RLC_CACHE_BITS) + .use_params(prompt_rlc_params); + let promise_loader = PromiseLoader::>::new(promise_params); + let promise_collector = Arc::new(Mutex::new(PromiseCollector::new(vec![ + ComponentTypeKeccak::::get_type_id(), + ]))); + let range = rlc_builder.range_chip(); + let keccak = KeccakChip::new_with_promise_collector( + range, + PromiseCaller::new(promise_collector.clone()), + ); + Self { + logic_inputs, + keccak_chip: keccak, + rlc_builder: RefCell::new(rlc_builder), + promise_collector, + promise_builder: RefCell::new(promise_loader), + payload: RefCell::new(None), + } + } + pub fn use_break_points(self, break_points: RlcThreadBreakPoints) -> Self { + self.rlc_builder.borrow_mut().set_break_points(break_points); + self + } + pub fn prover( + logic_inputs: I, + prompt_rlc_pinning: RlcCircuitPinning, + promise_params: PromiseLoaderParams, + ) -> Self { + Self::new_impl( + CircuitBuilderStage::Prover, + logic_inputs, + prompt_rlc_pinning.params, + promise_params, + ) + .use_break_points(prompt_rlc_pinning.break_points) + } + pub fn clear_witnesses(&self) { + self.rlc_builder.borrow_mut().clear(); + self.promise_collector.lock().unwrap().clear_witnesses(); + self.payload.borrow_mut().take(); + self.promise_builder.borrow_mut().clear_witnesses(); + } + + /// FirstPhase witness generation with error handling. + pub fn virtual_assign_phase0(&self) -> Result<(), plonk::Error> { + if self.payload.borrow().is_some() { + return Ok(()); + } + let mut borrowed_rlc_builder = self.rlc_builder.borrow_mut(); + let rlc_builder = borrowed_rlc_builder.deref_mut(); + let mut promise_builder = self.promise_builder.borrow_mut(); + + log::info!("EthCircuit: FirstPhase witness generation start"); + { + let mut borrowed_promise_collector = self.promise_collector.lock().unwrap(); + let promise_collector = borrowed_promise_collector.deref_mut(); + promise_builder.virtual_assign_phase0(rlc_builder, promise_collector); + } + + let rlp = RlpChip::new(self.keccak_chip.range(), None); + let mpt = MPTChip::new(rlp, &self.keccak_chip); + let payload = I::virtual_assign_phase0(&self.logic_inputs, rlc_builder, &mpt); + self.payload.borrow_mut().replace(payload); + // Add keccak promise as the last public instance in column 0: + let promise_commit = self + .promise_collector + .lock() + .unwrap() + .get_commit_by_component_type_id(&ComponentTypeKeccak::::get_type_id()) + .ok_or(plonk::Error::InvalidInstances)?; + if rlc_builder.base.assigned_instances.is_empty() { + return Err(plonk::Error::InvalidInstances); + } + rlc_builder.base.assigned_instances[0].push(promise_commit); + log::info!("EthCircuit: FirstPhase witness generation complete"); + Ok(()) + } + + pub fn virtual_assign_phase1(&self) { + let payload = + self.payload.borrow_mut().take().expect("FirstPhase witness generation was not run"); + log::info!("EthCircuit: SecondPhase witness generation start"); + let mut rlc_builder = self.rlc_builder.borrow_mut(); + let range_chip = self.keccak_chip.range(); + let rlc_chip = rlc_builder.rlc_chip(&range_chip.gate); + let rlp = RlpChip::new(range_chip, Some(&rlc_chip)); + let mpt = MPTChip::new(rlp, &self.keccak_chip); + { + let mut promise_collector = self.promise_collector.lock().unwrap(); + self.promise_builder + .borrow_mut() + .virtual_assign_phase1(&mut rlc_builder, promise_collector.deref_mut()); + } + I::virtual_assign_phase1(&self.logic_inputs, &mut rlc_builder, &mpt, payload); + log::info!("EthCircuit: SecondPhase witness generation complete"); + } + + pub fn fulfill_keccak_promise_results( + &self, + keccak_promise_results: ComponentPromiseResultsInMerkle, + ) -> Result<(), anyhow::Error> { + let mut borrowed_promise_collector = self.promise_collector.lock().unwrap(); + let promise_collector = borrowed_promise_collector.deref_mut(); + promise_collector.fulfill(&HashMap::from_iter([( + ComponentTypeKeccak::::get_type_id(), + keccak_promise_results, + )])); + self.promise_builder.borrow_mut().fulfill_promise_results(promise_collector); + Ok(()) + } + + /// Calculate params. This should be called only after all promise results are fulfilled. + pub fn calculate_params(&mut self) -> EthCircuitParams { + self.virtual_assign_phase0().expect("virtual assign phase0 failed"); + self.virtual_assign_phase1(); + + let rlc_params = self.rlc_builder.borrow_mut().calculate_params(Some(20)); + let promise_params = self.promise_builder.borrow_mut().calculate_params(); + + self.clear_witnesses(); + + EthCircuitParams { rlc: rlc_params, keccak: promise_params } + } + + pub fn break_points(&self) -> RlcThreadBreakPoints { + self.rlc_builder.borrow().break_points() + } + pub fn set_break_points(&self, break_points: RlcThreadBreakPoints) { + self.rlc_builder.borrow_mut().set_break_points(break_points); + } + + /// For testing only. A helper function to fulfill keccak promises for this circuit. + pub fn mock_fulfill_keccak_promises(&self, capacity: Option) { + let rlp = RlpChip::new(self.keccak_chip.range(), None); + let mpt = MPTChip::new(rlp, &self.keccak_chip); + I::virtual_assign_phase0(&self.logic_inputs, &mut self.rlc_builder.borrow_mut(), &mpt); + let calls = self.promise_collector.lock().unwrap().get_deduped_calls(); + let keccak_calls = &calls[&ComponentTypeKeccak::::get_type_id()]; + let mut used_capacity = 0; + let responses = keccak_calls + .iter() + .map(|call| { + let li = try_from_key::(&call.logical_input).unwrap(); + used_capacity += >::get_capacity(&li); + (li.bytes.clone().into(), None) + }) + .collect_vec(); + + let capacity = if let Some(capacity) = capacity { capacity } else { used_capacity }; + let output_shard = OutputKeccakShard { responses, capacity }; + self.fulfill_keccak_promise_results(ComponentPromiseResultsInMerkle::from_single_shard( + output_shard.into_logical_results(), + )) + .unwrap(); + self.clear_witnesses(); + } + + pub fn instances(&self) -> Vec> { + self.virtual_assign_phase0().unwrap(); + let builder = self.rlc_builder.borrow(); + builder + .base + .assigned_instances + .iter() + .map(|instance| instance.iter().map(|x| *x.value()).collect()) + .collect() + } +} + +impl Circuit for EthCircuitImpl +where + F: Field, + I: EthCircuitInstructions, +{ + type FloorPlanner = SimpleFloorPlanner; + type Config = EthConfig; + type Params = EthCircuitParams; + + fn params(&self) -> Self::Params { + let rlc = self.rlc_builder.borrow().params(); + let keccak = self.promise_builder.borrow().get_params(); + EthCircuitParams { rlc, keccak } + } + + fn without_witnesses(&self) -> Self { + unimplemented!() + } + fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config { + EthConfig::configure(meta, params) + } + fn configure(_: &mut ConstraintSystem) -> Self::Config { + unreachable!() + } + + // Mostly copied from ComponentCircuitImpl + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), plonk::Error> { + self.promise_collector.lock().unwrap().set_promise_results_ready(true); + config.rlc_config.base.initialize(&mut layouter); + self.virtual_assign_phase0()?; + { + let mut promise_builder = self.promise_builder.borrow_mut(); + let rlc_builder = self.rlc_builder.borrow(); + + let mut phase0_layouter = layouter.namespace(|| "raw synthesize phase0"); + promise_builder.raw_synthesize_phase0(&config.keccak, &mut phase0_layouter); + rlc_builder.raw_synthesize_phase0(&config.rlc_config, phase0_layouter); + } + #[cfg(feature = "halo2-axiom")] + { + layouter.next_phase(); + } + self.rlc_builder + .borrow_mut() + .load_challenge(&config.rlc_config, layouter.namespace(|| "load challenges")); + + self.virtual_assign_phase1(); + + { + let rlc_builder = self.rlc_builder.borrow(); + let phase1_layouter = layouter.namespace(|| "RlcCircuitBuilder raw synthesize phase1"); + rlc_builder.raw_synthesize_phase1(&config.rlc_config, phase1_layouter, false); + + let mut promise_builder = self.promise_builder.borrow_mut(); + promise_builder.raw_synthesize_phase1(&config.keccak, &mut layouter); + } + + let rlc_builder = self.rlc_builder.borrow(); + if !rlc_builder.witness_gen_only() { + layouter.assign_region( + || "copy constraints", + |mut region| { + let constant_cols = config.rlc_config.base.constants(); + rlc_builder.copy_manager().assign_raw(constant_cols, &mut region); + Ok(()) + }, + )?; + } + drop(rlc_builder); + + // clear in case synthesize is called multiple times + self.clear_witnesses(); + + Ok(()) + } +} + +impl CircuitPinningInstructions for EthCircuitImpl +where + F: Field, + I: EthCircuitInstructions, +{ + type Pinning = RlcCircuitPinning; + fn pinning(&self) -> Self::Pinning { + let break_points = self.break_points(); + let params = self.rlc_builder.borrow().params(); + RlcCircuitPinning::new(params, break_points) + } +} + +#[cfg(feature = "aggregation")] +mod aggregation { + use crate::Field; + use snark_verifier_sdk::CircuitExt; + + use crate::utils::build_utils::aggregation::CircuitMetadata; + + use super::{EthCircuitImpl, EthCircuitInstructions}; + + impl CircuitExt for EthCircuitImpl + where + F: Field, + I: EthCircuitInstructions + CircuitMetadata, + { + fn accumulator_indices() -> Option> { + I::accumulator_indices() + } + + fn instances(&self) -> Vec> { + self.instances() + } + + fn num_instance(&self) -> Vec { + self.logic_inputs.num_instance() + } + } +} + +// ==== convenience functions for testing & benchmarking ==== + +pub fn create_circuit>( + stage: CircuitBuilderStage, + circuit_params: RlcCircuitParams, + logic_inputs: I, +) -> EthCircuitImpl { + EthCircuitImpl::new_impl(stage, logic_inputs, circuit_params, Default::default()) +} diff --git a/axiom-eth/src/utils/hilo.rs b/axiom-eth/src/utils/hilo.rs new file mode 100644 index 00000000..ec5b74ff --- /dev/null +++ b/axiom-eth/src/utils/hilo.rs @@ -0,0 +1,94 @@ +use std::io::{Error, ErrorKind, Result}; + +use ethers_core::types::H256; +use halo2_base::{AssignedValue, Context}; +use serde::{Deserialize, Serialize}; +use zkevm_hashes::util::{eth_types::Field, word::Word}; + +use crate::impl_flatten_conversion; + +use super::encode_h256_to_hilo; + +/// Stored as [lo, hi], just like Word2 +#[derive(Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] +pub struct HiLo([T; 2]); + +impl HiLo { + /// Create a new [HiLo] from a `[lo, hi]` array. + pub fn from_lo_hi([lo, hi]: [T; 2]) -> Self { + Self([lo, hi]) + } + /// Create a new [HiLo] from a `[hi, lo]` array. + pub fn from_hi_lo([hi, lo]: [T; 2]) -> Self { + Self([lo, hi]) + } + pub fn hi(&self) -> T + where + T: Copy, + { + self.0[1] + } + pub fn lo(&self) -> T + where + T: Copy, + { + self.0[0] + } + pub fn hi_lo(&self) -> [T; 2] + where + T: Copy, + { + [self.hi(), self.lo()] + } + pub fn flatten(&self) -> [T; 2] + where + T: Copy, + { + self.hi_lo() + } +} + +impl HiLo { + pub fn assign(&self, ctx: &mut Context) -> HiLo> { + HiLo(self.0.map(|x| ctx.load_witness(x))) + } +} + +impl From> for HiLo { + fn from(word: Word) -> Self { + Self::from_hi_lo([word.hi(), word.lo()]) + } +} + +impl From> for Word { + fn from(lohi: HiLo) -> Self { + Word::new(lohi.0) + } +} + +impl From for HiLo { + fn from(value: H256) -> Self { + encode_h256_to_hilo(&value) + } +} + +impl TryFrom> for HiLo { + type Error = Error; + + fn try_from(value: Vec) -> Result { + let [hi, lo] = value + .try_into() + .map_err(|_| Error::new(ErrorKind::InvalidInput, "invalid array length"))?; + Ok(Self::from_hi_lo([hi, lo])) + } +} + +/// We will only flatten HiLo to uint128 words. +pub const BITS_PER_FE_HILO: [usize; 2] = [128, 128]; +impl_flatten_conversion!(HiLo, BITS_PER_FE_HILO); + +impl std::fmt::Debug for HiLo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("HiLo").field(&self.0[1]).field(&self.0[0]).finish() + } +} diff --git a/axiom-eth/src/utils/keccak/decorator.rs b/axiom-eth/src/utils/keccak/decorator.rs new file mode 100644 index 00000000..7831c16b --- /dev/null +++ b/axiom-eth/src/utils/keccak/decorator.rs @@ -0,0 +1,598 @@ +//! A `Circuit` implementation for `EthCircuitInstructions` that consists of a circuit with both `RlcCircuitBuilder` and `KeccakComponentShardCircuit` as sub-circuits. +//! This is a complete circuit that can be used when keccak computations are necessary. +//! This circuit is **not** part of the Component Framework, so it does not have any additional dependencies or verification assumptions. + +// @dev We still use the `ComponentType` and `ComponentLoader` trait to share as much code as possible with the Keccak promise loader implementation. + +use std::{ + cell::RefCell, + iter::{self, zip}, + mem, + ops::DerefMut, + sync::{Arc, Mutex}, +}; + +use ethers_core::{types::H256, utils::keccak256}; +use halo2_base::{ + gates::{ + circuit::CircuitBuilderStage, + flex_gate::threads::{parallelize_core, SinglePhaseCoreManager}, + GateInstructions, RangeChip, + }, + halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{self, Circuit, ConstraintSystem}, + }, + safe_types::SafeTypeChip, + AssignedValue, + QuantumCell::Constant, +}; +use itertools::{zip_eq, Itertools}; +use serde::{Deserialize, Serialize}; +use zkevm_hashes::keccak::{ + component::{ + circuit::shard::{ + pack_inputs_from_keccak_fs, transmute_keccak_assigned_to_virtual, LoadedKeccakF, + }, + encode::format_input, + }, + vanilla::{ + keccak_packed_multi::get_num_keccak_f, witness::multi_keccak, KeccakAssignedRow, + KeccakCircuitConfig, KeccakConfigParams, + }, +}; + +use crate::{ + keccak::{ + promise::{KeccakFixLenCall, KeccakVarLenCall}, + types::{ComponentTypeKeccak, KeccakVirtualInput, KeccakVirtualOutput}, + KeccakChip, + }, + mpt::MPTChip, + rlc::{ + circuit::{builder::RlcCircuitBuilder, RlcCircuitParams, RlcConfig}, + virtual_region::RlcThreadBreakPoints, + }, + rlp::RlpChip, + utils::{ + component::{ + promise_collector::{PromiseCaller, PromiseCallsGetter, PromiseCollector}, + ComponentType, + }, + constrain_vec_equal, encode_h256_to_hilo, enforce_conditional_equality, + eth_circuit::EthCircuitInstructions, + hilo::HiLo, + keccak::get_keccak_unusable_rows_from_capacity, + DEFAULT_RLC_CACHE_BITS, + }, + Field, +}; + +/// Configuration parameters for [RlcKeccakConfig] +#[derive(Clone, Default, Debug, Serialize, Deserialize)] +pub struct RlcKeccakCircuitParams { + /// RLC circuit parameters + pub rlc: RlcCircuitParams, + /// The number of rows per round of keccak_f in the keccak circuit. + /// No `capacity` is needed because this circuit will do exactly the number of keccaks + /// needed by the instructions. The `keccak_rows_per_round` must be small enough so that the + /// circuit can performs all necessary keccaks, otherwise you will get a `NotEnoughRows` error. + pub keccak_rows_per_round: usize, +} + +impl RlcKeccakCircuitParams { + pub fn new(rlc: RlcCircuitParams, keccak_rows_per_round: usize) -> Self { + Self { rlc, keccak_rows_per_round } + } + pub fn k(&self) -> usize { + self.rlc.base.k + } + pub fn set_k(&mut self, k: usize) { + self.rlc.base.k = k; + } + pub fn use_k(mut self, k: usize) -> Self { + self.rlc.base.k = k; + self + } +} + +/// Halo2 Config that is a combination of [RlcConfig] and vanilla [KeccakCircuitConfig] +#[derive(Clone, Debug)] +pub struct RlcKeccakConfig { + pub rlc: RlcConfig, + pub keccak: KeccakCircuitConfig, +} + +impl RlcKeccakConfig { + pub fn configure(meta: &mut ConstraintSystem, params: RlcKeccakCircuitParams) -> Self { + let k = params.k(); + let mut rlc_config = RlcConfig::configure(meta, params.rlc); + let keccak_config = KeccakCircuitConfig::new( + meta, + KeccakConfigParams { k: k as u32, rows_per_round: params.keccak_rows_per_round }, + ); + log::info!("Poisoned rows after RlcKeccakConfig::configure {}", meta.minimum_rows()); + // Warning: minimum_rows may have changed between RlcConfig::configure and now: + let usable_rows = (1usize << k) - meta.minimum_rows(); + rlc_config.set_usable_rows(usable_rows); + Self { rlc: rlc_config, keccak: keccak_config } + } +} + +/// This struct is used for the concrete implementation of [Circuit] trait from [EthCircuitInstructions] *which has a vanilla zkEVM keccak sub-circuit embedded* in the full circuit. +/// The difference between this and [EthCircuitImpl] is that this circuit is self-contained: it does not rely on any promise calls. All keccak computations will be fully proved when +/// this circuit is verified. +pub struct RlcKeccakCircuitImpl> { + pub logic_inputs: I, + /// RLC Circuit Builder + pub rlc_builder: RefCell>, + /// This is a passthrough to `SharedPromiseCollector` that contains the + /// keccak functions. It collects the keccaks that needs to be done and handles + /// some formatting. The actual keccaks will be passed on to the vanilla zkevm keccak + /// sub-circuit to be proved. + pub keccak_chip: KeccakChip, + keccak_rows_per_round: usize, + /// The FirstPhasePayload is set after FirstPhase witness generation. + /// This is used both to pass payload between phases and also to detect if `virtual_assign_phase0_start` + /// was already run outside of `synthesize` (e.g., to determine public instances) + payload: RefCell>, + + // we keep these as RefCell to pass between phases just so the user doesn't need to keep track of them: + call_collector: RefCell>, +} + +impl RlcKeccakCircuitImpl +where + F: Field, + I: EthCircuitInstructions, +{ + pub fn new(logic_inputs: I, circuit_params: RlcKeccakCircuitParams) -> Self { + // Mock is general, can be used for anything + Self::new_impl( + CircuitBuilderStage::Mock, + logic_inputs, + circuit_params, + DEFAULT_RLC_CACHE_BITS, + ) + } + /// When `RlcChip` is automatically constructed in `virtual_assign_phase1`, it will + /// compute `gamma^{2^i}` for `i = 0..max_rlc_cache_bits`. This cache is used by + /// RlpChip. Usually one should just set this to [DEFAULT_RLC_CACHE_BITS] to cover + /// all possible use cases, since the cache computation cost is low. + /// + /// However we provide the option to set this to `0` because the Keccak decorator + /// does not actually need RlcChip. Therefore if you only use `BaseCircuitBuilder` and + /// KeccakChip and never use RlcChip, then setting this to `0` will mean that you do not + /// create any unnecessary RLC advice columns. + pub fn new_impl( + stage: CircuitBuilderStage, + logic_inputs: I, + circuit_params: RlcKeccakCircuitParams, + max_rlc_cache_bits: usize, + ) -> Self { + let rlc_builder = + RlcCircuitBuilder::from_stage(stage, max_rlc_cache_bits).use_params(circuit_params.rlc); + // We re-use this to save code and also because `KeccakChip` needs it in its constructor + let promise_collector = Arc::new(Mutex::new(PromiseCollector::new(vec![ + ComponentTypeKeccak::::get_type_id(), + ]))); + let range = rlc_builder.range_chip(); + let keccak = + KeccakChip::new_with_promise_collector(range, PromiseCaller::new(promise_collector)); + Self { + logic_inputs, + keccak_chip: keccak, + keccak_rows_per_round: circuit_params.keccak_rows_per_round, + rlc_builder: RefCell::new(rlc_builder), + payload: RefCell::new(None), + call_collector: Default::default(), + } + } + pub fn use_break_points(self, break_points: RlcThreadBreakPoints) -> Self { + self.rlc_builder.borrow_mut().set_break_points(break_points); + self + } + /// Resets entire circuit state. Does not change original inputs. + pub fn clear(&self) { + self.rlc_builder.borrow_mut().clear(); + self.keccak_chip.promise_caller().0.lock().unwrap().clear_witnesses(); + self.payload.borrow_mut().take(); + self.call_collector.borrow_mut().clear(); + } + + /// FirstPhase witness generation with error handling. + pub fn virtual_assign_phase0_start(&self) { + if self.payload.borrow().is_some() { + // We've already done phase0, perhaps outside of synthesize + return; + } + #[cfg(feature = "display")] + let start = std::time::Instant::now(); + let mut rlc_builder_ref = self.rlc_builder.borrow_mut(); + let rlc_builder = &mut rlc_builder_ref; + + let rlp = RlpChip::new(self.keccak_chip.range(), None); + let mpt = MPTChip::new(rlp, &self.keccak_chip); + // main instructions phase0 + let payload = I::virtual_assign_phase0(&self.logic_inputs, rlc_builder, &mpt); + self.payload.borrow_mut().replace(payload); + + // now the KeccakChip promise_collector has all of the keccak (input, outputs) that need to be proven & constrained. + let collector_guard = self.keccak_chip.promise_caller().0.lock().unwrap(); + let mut calls = self.call_collector.borrow_mut(); + assert!(calls.fix_len_calls.is_empty()); + assert!(calls.var_len_calls.is_empty()); + // these are the keccak inputs, outputs as virtual assigned cells + // need to do some rust downcasting from PromiseCallWitness to either KeccakFixLenCall or KeccakVarLenCall + for (input, output) in collector_guard + .get_calls_by_component_type_id(&ComponentTypeKeccak::::get_type_id()) + .unwrap() + .values() + .flatten() + { + if let Some(fix_len_call) = input.as_any().downcast_ref::>() { + calls + .fix_len_calls + .push((fix_len_call.clone(), output.clone().try_into().unwrap())); + } else if let Some(var_len_call) = input.as_any().downcast_ref::>() + { + calls + .var_len_calls + .push((var_len_call.clone(), output.clone().try_into().unwrap())); + } else { + unreachable!("KeccakChip should only use KeccakFixLenCall or KeccakVarLenCall"); + } + } + #[cfg(feature = "display")] + log::info!("RlcKeccackCircuit virtual_assign_phase0_start time: {:?}", start.elapsed()); + } + + pub fn virtual_assign_phase1(&self) { + let payload = + self.payload.borrow_mut().take().expect("FirstPhase witness generation was not run"); + let mut rlc_builder = self.rlc_builder.borrow_mut(); + let range_chip = self.keccak_chip.range(); + // Note: this uses rlc columns to load RLC cache. Set `max_rlc_cache_bits = 0` in the `new_impl` constructor to disable this. + let rlc_chip = rlc_builder.rlc_chip(&range_chip.gate); + let rlp = RlpChip::new(range_chip, Some(&rlc_chip)); + let mpt = MPTChip::new(rlp, &self.keccak_chip); + I::virtual_assign_phase1(&self.logic_inputs, &mut rlc_builder, &mpt, payload); + } + + /// Calculate params. This should be called only after all promise results are fulfilled. + pub fn calculate_params(&mut self) { + self.virtual_assign_phase0_start(); + let mut capacity = 0; + for (call, _) in self.call_collector.borrow().fix_len_calls.iter() { + capacity += get_num_keccak_f(call.bytes().len()); + } + for (call, _) in self.call_collector.borrow().var_len_calls.iter() { + capacity += get_num_keccak_f(call.bytes().max_len()); + } + // make mock loaded_keccak_fs just to simulate + let copy_manager_ref = self.rlc_builder.borrow().copy_manager().clone(); + let mut copy_manager = copy_manager_ref.lock().unwrap(); + let virtual_keccak_fs = (0..capacity) + .map(|_| { + LoadedKeccakF::new( + copy_manager.mock_external_assigned(F::ZERO), + core::array::from_fn(|_| copy_manager.mock_external_assigned(F::ZERO)), + SafeTypeChip::unsafe_to_bool(copy_manager.mock_external_assigned(F::ZERO)), + copy_manager.mock_external_assigned(F::ZERO), + copy_manager.mock_external_assigned(F::ZERO), + ) + }) + .collect_vec(); + drop(copy_manager); + let keccak_calls = mem::take(self.call_collector.borrow_mut().deref_mut()); + keccak_calls.pack_and_constrain( + virtual_keccak_fs, + self.rlc_builder.borrow_mut().base.pool(0), + self.keccak_chip.range(), + ); + self.virtual_assign_phase1(); + + let k = self.params().k(); + let (unusable_rows, rows_per_round) = get_keccak_unusable_rows_from_capacity(k, capacity); + self.rlc_builder.borrow_mut().calculate_params(Some(unusable_rows)); + log::debug!("RlcKeccakCircuit used capacity: {capacity}"); + log::debug!("RlcKeccakCircuit optimal rows_per_round : {rows_per_round}"); + self.keccak_rows_per_round = rows_per_round; + + self.clear(); + } + + pub fn break_points(&self) -> RlcThreadBreakPoints { + self.rlc_builder.borrow().break_points() + } + pub fn set_break_points(&self, break_points: RlcThreadBreakPoints) { + self.rlc_builder.borrow_mut().set_break_points(break_points); + } + + pub fn instances(&self) -> Vec> { + self.virtual_assign_phase0_start(); + let builder = self.rlc_builder.borrow(); + builder + .base + .assigned_instances + .iter() + .map(|instance| instance.iter().map(|x| *x.value()).collect()) + .collect() + } +} + +impl Circuit for RlcKeccakCircuitImpl +where + F: Field, + I: EthCircuitInstructions, +{ + type FloorPlanner = SimpleFloorPlanner; + type Config = RlcKeccakConfig; + type Params = RlcKeccakCircuitParams; + + fn params(&self) -> Self::Params { + let rlc = self.rlc_builder.borrow().params(); + let keccak_rows_per_round = self.keccak_rows_per_round; + RlcKeccakCircuitParams { rlc, keccak_rows_per_round } + } + fn without_witnesses(&self) -> Self { + unimplemented!() + } + fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config { + RlcKeccakConfig::configure(meta, params) + } + fn configure(_: &mut ConstraintSystem) -> Self::Config { + unreachable!() + } + + /// This is organized to match `EthCircuitImpl::synthesize` as closely as possible, except that PromiseLoader is replaced with an actual vanilla keccak sub-circuit. + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), plonk::Error> { + let RlcKeccakCircuitParams { rlc: rlc_circuit_params, keccak_rows_per_round } = + self.params(); + let k = rlc_circuit_params.base.k; + let keccak_circuit_params = + KeccakConfigParams { k: k as u32, rows_per_round: keccak_rows_per_round }; + + #[cfg(feature = "display")] + let start = std::time::Instant::now(); + log::info!("RlcKeccakCircuit: phase0 start"); + config.rlc.base.initialize(&mut layouter); + config.keccak.load_aux_tables(&mut layouter, k as u32)?; + + self.virtual_assign_phase0_start(); + let keccak_calls = mem::take(self.call_collector.borrow_mut().deref_mut()); + keccak_calls.assign_raw_and_constrain( + keccak_circuit_params, + &config.keccak, + &mut layouter.namespace(|| "keccak sub-circuit"), + self.rlc_builder.borrow_mut().base.pool(0), + self.keccak_chip.range(), + )?; + + // raw assign everything in RlcCircuitBuilder phase0, *including* the keccak virtual table (this is important because we will RLC the table in phase1). + self.rlc_builder.borrow().raw_synthesize_phase0( + &config.rlc, + layouter.namespace(|| "RlcCircuitBuilder raw synthesize phase0"), + ); + log::info!("RlcKeccakCircuit: phase0 end"); + #[cfg(feature = "display")] + log::info!("RlcKeccakCircuit phase0 synthesize time: {:?}", start.elapsed()); + + #[cfg(feature = "halo2-axiom")] + layouter.next_phase(); + + self.rlc_builder + .borrow_mut() + .load_challenge(&config.rlc, layouter.namespace(|| "load challenges")); + + self.virtual_assign_phase1(); + + self.rlc_builder.borrow_mut().raw_synthesize_phase1( + &config.rlc, + layouter.namespace(|| "RlcCircuitBuilder raw synthesize phase1 + copy constraints"), + true, + ); + + // clear in case synthesize is called multiple times + self.clear(); + + Ok(()) + } +} + +#[derive(Clone, Default, Debug)] +pub struct KeccakCallCollector { + pub fix_len_calls: Vec<(KeccakFixLenCall, HiLo>)>, + pub var_len_calls: Vec<(KeccakVarLenCall, HiLo>)>, +} + +impl KeccakCallCollector { + pub fn new( + fix_len_calls: Vec<(KeccakFixLenCall, HiLo>)>, + var_len_calls: Vec<(KeccakVarLenCall, HiLo>)>, + ) -> Self { + Self { fix_len_calls, var_len_calls } + } + + pub fn clear(&mut self) { + self.fix_len_calls.clear(); + self.var_len_calls.clear(); + } + + /// Format the KeccakFixLenCall's and KeccakVarLenCall's into + /// variable length bytes as inputs to zkEVM keccak so that the + /// keccak table from the keccak sub-circuit will exactly match + /// the ordering and packing of the calls. + pub fn format_keccak_inputs(&self) -> Vec> { + let fix_len_calls = &self.fix_len_calls; + let fix_len_calls = fix_len_calls.iter().map(|(call, _)| call.to_logical_input().bytes); + let var_len_calls = &self.var_len_calls; + let var_len_calls = var_len_calls.iter().flat_map(|(call, _)| { + let capacity = get_num_keccak_f(call.bytes().max_len()); + let bytes = call.to_logical_input().bytes; + // we need to pad with empty [] inputs so that we use exactly the same number of keccak_f as if we were computing keccak on a max_len input + let used_capacity = get_num_keccak_f(bytes.len()); + iter::once(bytes).chain(iter::repeat(vec![]).take(capacity - used_capacity)) + }); + iter::empty().chain(fix_len_calls).chain(var_len_calls).collect() + } + + /// Packs the loaded keccak_f rows and constrains that they exactly + /// match the packing from the KeccakFixLenCall's and KeccakVarLenCall's. + /// Needs to be done after raw synthesize of keccak sub-circuit. + /// + /// ## Assumptions + /// - The `virtual_keccak_fs` should exactly correspond to the inputs generated by `format_keccak_inputs`. + /// - `range_chip` should share same reference to `copy_manager` as `pool`. + pub fn pack_and_constrain( + self, + virtual_keccak_fs: Vec>, + pool: &mut SinglePhaseCoreManager, + range_chip: &RangeChip, + ) { + // We now pack the virtual cells into a virtual table using fewer field elements + let gate = &range_chip.gate; + let packed_inputs = pack_inputs_from_keccak_fs(pool.main(), gate, &virtual_keccak_fs); + // Return the virtual table of inputs and outputs: + let virtual_table = zip_eq(packed_inputs, virtual_keccak_fs).map(|(chunk, keccak_f)| { + let v_i = KeccakVirtualInput::new( + chunk.inputs().concat().try_into().unwrap(), + chunk.is_final().into(), + ); + let hash = HiLo::from_hi_lo([keccak_f.hash_hi(), keccak_f.hash_lo()]); + let v_o = KeccakVirtualOutput::new(hash); + (v_i, v_o) + }); + + // We also pack the `calls` in exactly the same way: + let Self { fix_len_calls, var_len_calls } = self; + // flat map fix_len_calls into virtual table, for each call since it's fixed len, we set is_final to true only on the last chunk + let fix_len_vt = parallelize_core(pool, fix_len_calls, |ctx, (call, hash)| { + let len = ctx.load_constant(F::from(call.bytes().len() as u64)); + let packed_input = format_input(ctx, gate, call.bytes().bytes(), len); + let capacity = packed_input.len(); + packed_input + .into_iter() + .enumerate() + .map(|(i, chunk)| { + let is_final = ctx.load_constant(F::from(i == capacity - 1)); + let v_i = KeccakVirtualInput::new(chunk.concat().try_into().unwrap(), is_final); + // we mask this with is_final later + let v_o = KeccakVirtualOutput::new(hash); + (v_i, v_o) + }) + .collect_vec() + }) + .into_iter() + .flatten(); + let empty_hash = encode_h256_to_hilo::(&H256(keccak256([]))); + // flat map fix_len_calls into virtual table + let var_len_vt = parallelize_core(pool, var_len_calls, |ctx, (call, hash)| { + let num_keccak_f_m1 = call.num_keccak_f_m1(ctx, range_chip); + // `ensure_0_padding` so that `packed_input` after variable length `bytes.len()` corresponds to format_input of [] empty bytes + let bytes = call.bytes().ensure_0_padding(ctx, gate); + let packed_input = format_input(ctx, gate, bytes.bytes(), *bytes.len()); + // since a call is variable_length, we need to set is_final to be 0, 0, ..., 0, 1, 1, ... where the first 1 is at `num_keccak_f - 1` + let capacity = get_num_keccak_f(call.bytes().max_len()); + assert_eq!(capacity, packed_input.len()); + // we have constrained that `num_keccak_f - 1 < capacity` + let indicator = gate.idx_to_indicator(ctx, num_keccak_f_m1, capacity); + let is_finals = gate.partial_sums(ctx, indicator.clone()).collect_vec(); + zip(packed_input, zip_eq(is_finals, indicator)) + .map(|(chunk, (is_final, is_out))| { + // We pad with empty hashes keccak([]) between the true capacity and the max capacity for each var len call + let v_i = KeccakVirtualInput::new(chunk.concat().try_into().unwrap(), is_final); + // If we beyond the true capacity, then we need the hash output to be empty hash + // We will later mask hash with is_final as well + let hash_hi = gate.select(ctx, hash.hi(), Constant(empty_hash.hi()), is_out); + let hash_lo = gate.select(ctx, hash.lo(), Constant(empty_hash.lo()), is_out); + let v_o = KeccakVirtualOutput::new(HiLo::from_hi_lo([hash_hi, hash_lo])); + (v_i, v_o) + }) + .collect_vec() + }) + .into_iter() + .flatten(); + + // now we compare the virtual table from the vanilla keccak circuit with the virtual table constructed from the calls. we enforce the inputs are exactly equal. we enforce the outputs are exactly equal when `is_final = true`. + let ctx = pool.main(); + for ((table_i, table_o), (call_i, call_o)) in + virtual_table.zip_eq(fix_len_vt.chain(var_len_vt)) + { + constrain_vec_equal(ctx, &table_i.packed_input, &call_i.packed_input); + ctx.constrain_equal(&table_i.is_final, &call_i.is_final); + let is_final = SafeTypeChip::unsafe_to_bool(table_i.is_final); + enforce_conditional_equality(ctx, gate, table_o.hash.hi(), call_o.hash.hi(), is_final); + enforce_conditional_equality(ctx, gate, table_o.hash.lo(), call_o.hash.lo(), is_final); + } + } + + /// Consumes all collected calls and raw assigns them to the keccak sub-circuit specified by `keccak_config`. Then constrains that the [`LoadedKeccakF`]s must equal the virtually assigned calls. + /// + /// This is the only function you need to call to process all calls. + /// + /// ## Assumptions + /// - This should be the **only** time `layouter` is allowed to assign to `keccak_config`. + /// - `range_chip` should share same reference to `copy_manager` as `pool`. + pub fn assign_raw_and_constrain( + self, + keccak_circuit_params: KeccakConfigParams, + keccak_config: &KeccakCircuitConfig, + layouter: &mut impl Layouter, + pool: &mut SinglePhaseCoreManager, + range_chip: &RangeChip, + ) -> Result<(), plonk::Error> { + // We constrain the collected keccak calls using the vanilla zkEVM keccak circuit: + // convert `calls` to actual keccak inputs as bytes (Vec) + let keccak_inputs = self.format_keccak_inputs(); + // raw synthesize of vanilla keccak circuit: + let keccak_assigned_rows: Vec> = layouter.assign_region( + || "vanilla keccak circuit", + |mut region| { + let (keccak_rows, _) = + multi_keccak::(&keccak_inputs, None, keccak_circuit_params); + Ok(keccak_config.assign(&mut region, &keccak_rows)) + }, + )?; + // Convert raw assigned cells into virtual cells + let virtual_keccak_fs = transmute_keccak_assigned_to_virtual( + &pool.copy_manager, + keccak_assigned_rows, + keccak_circuit_params.rows_per_round, + ); + self.pack_and_constrain(virtual_keccak_fs, pool, range_chip); + Ok(()) + } +} + +#[cfg(feature = "aggregation")] +mod aggregation { + use crate::Field; + use snark_verifier_sdk::CircuitExt; + + use crate::utils::build_utils::aggregation::CircuitMetadata; + + use super::{EthCircuitInstructions, RlcKeccakCircuitImpl}; + + impl CircuitExt for RlcKeccakCircuitImpl + where + F: Field, + I: EthCircuitInstructions + CircuitMetadata, + { + fn accumulator_indices() -> Option> { + I::accumulator_indices() + } + + fn instances(&self) -> Vec> { + self.instances() + } + + fn num_instance(&self) -> Vec { + self.logic_inputs.num_instance() + } + } +} diff --git a/axiom-eth/src/utils/keccak/mod.rs b/axiom-eth/src/utils/keccak/mod.rs new file mode 100644 index 00000000..4ea4269e --- /dev/null +++ b/axiom-eth/src/utils/keccak/mod.rs @@ -0,0 +1,57 @@ +use zkevm_hashes::keccak::vanilla::keccak_packed_multi::get_keccak_capacity; + +pub mod decorator; + +#[cfg(test)] +pub mod tests; + +// num_unusable_rows: this is not used in `configure_with_params`, only for auto-circuit tuning +// numbers from empirical tests and also from Scroll: https://github.com/scroll-tech/zkevm-circuits/blob/7d9bc181953cfc6e7baf82ff0ce651281fd70a8a/zkevm-circuits/src/keccak_circuit/keccak_packed_multi.rs#L59C55-L62C7 +const UNUSABLE_ROWS_BY_ROWS_PER_ROUND: [usize; 25] = [ + 23, 59, 59, 59, 59, 59, 59, 59, 55, 69, 65, 61, 47, 81, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, + 59, +]; + +/// Returns unusable rows for keccak circuit configured with `rows_per_round` rows per round. +/// This is only used for circuit tuning. +pub fn get_keccak_unusable_rows(rows_per_round: usize) -> usize { + *UNUSABLE_ROWS_BY_ROWS_PER_ROUND.get(rows_per_round - 1).unwrap_or(&109) +} + +/// Returns (unusable_rows, rows_per_round) that is optimal for the given `k` and `capacity`. +pub fn get_keccak_unusable_rows_from_capacity(k: usize, capacity: usize) -> (usize, usize) { + let mut rows_per_round = 50; + // find the largest rows per round that works, capping at 50 + let mut unusable = 109; + while rows_per_round > 0 { + unusable = get_keccak_unusable_rows(rows_per_round); + let num_rows = (1 << k) - unusable; + let avail_capacity = get_keccak_capacity(num_rows, rows_per_round); + if avail_capacity >= capacity { + // this rows_per_round is enough to meet our needs + break; + } + rows_per_round -= 1; + } + assert_ne!( + rows_per_round, 0, + "k={} is insufficient for requested keccak capacity={}", + k, capacity + ); + (unusable, rows_per_round) +} + +#[cfg(test)] +#[test] +fn test_get_keccak_unusable() { + assert_eq!(get_keccak_unusable_rows_from_capacity(20, 500), (109, 50)); + assert_eq!(get_keccak_unusable_rows_from_capacity(18, 500), (69, 20)); + assert_eq!(get_keccak_unusable_rows_from_capacity(18, 2000), (59, 5)); +} + +#[cfg(test)] +#[test] +#[should_panic] +fn test_get_keccak_insufficient_capacity() { + get_keccak_unusable_rows_from_capacity(18, 12_000); +} diff --git a/axiom-eth/src/utils/keccak/tests/merkle.rs b/axiom-eth/src/utils/keccak/tests/merkle.rs new file mode 100644 index 00000000..d95239af --- /dev/null +++ b/axiom-eth/src/utils/keccak/tests/merkle.rs @@ -0,0 +1,117 @@ +use std::{ + fs::{remove_file, File}, + ops::Deref, + path::Path, + sync::Arc, +}; + +use halo2_base::{ + gates::flex_gate::MultiPhaseThreadBreakPoints, + halo2_proofs::{halo2curves::bn256::Fr, plonk::keygen_vk_custom, SerdeFormat}, + utils::halo2::ProvingKeyGenerator, +}; +use itertools::Itertools; +use snark_verifier_sdk::{halo2::utils::AggregationDependencyIntentOwned, read_pk}; +use zkevm_hashes::keccak::component::circuit::shard::{ + KeccakComponentShardCircuit, KeccakComponentShardCircuitParams, +}; + +use crate::{ + halo2_base::{gates::circuit::CircuitBuilderStage, utils::fs::gen_srs}, + snark_verifier_sdk::{halo2::gen_snark_shplonk, LIMBS}, + utils::{ + build_utils::{ + aggregation::get_dummy_aggregation_params, + pinning::{aggregation::AggTreeId, PinnableCircuit}, + }, + component::utils::compute_poseidon, + merkle_aggregation::{keygen::AggIntentMerkle, InputMerkleAggregation}, + }, +}; + +use super::shard::get_test_keccak_shard_snark; + +#[test] +#[ignore = "prover"] +pub fn test_keccak_merkle_aggregation_prover() -> anyhow::Result<()> { + let dummy_snark = get_test_keccak_shard_snark(vec![])?; + let inputs = vec![ + (0u8..200).collect_vec(), + vec![], + (0u8..1).collect_vec(), + (0u8..135).collect_vec(), + (0u8..136).collect_vec(), + (0u8..200).collect_vec(), + ]; + let snark = get_test_keccak_shard_snark(inputs)?; + let commit = snark.inner.instances[0][0]; + let k = 20u32; + let params = gen_srs(k); + let [dummy_snarks, snarks] = + [dummy_snark, snark].map(|s| InputMerkleAggregation::new(vec![s; 2])); + + let circuit_params = get_dummy_aggregation_params(k as usize); + let mut keygen_circuit = + dummy_snarks.build(CircuitBuilderStage::Keygen, circuit_params, ¶ms)?; + keygen_circuit.calculate_params(Some(20)); + let pinning_path = "configs/tests/keccak_shard2_merkle.json"; + let pk_path = "data/tests/keccak_shard2_merkle.pk"; + let (pk, pinning) = keygen_circuit.create_pk(¶ms, pk_path, pinning_path)?; + + let prover_circuit = snarks.prover_circuit(pinning, ¶ms)?; + let snark = gen_snark_shplonk(¶ms, &pk, prover_circuit, None::<&str>); + let root = snark.instances[0][4 * LIMBS]; + assert_eq!(compute_poseidon(&[commit, commit]), root); + + remove_file(pk_path).ok(); + remove_file("data/test/keccak_shard.pk").ok(); + Ok(()) +} + +// CARGO_PROFILE_DEV_DEBUG_ASSERTIONS=false cargo t test_keygen_merkle_aggregation -- --nocapture +#[test] +#[ignore = "keygen; turn off debug assertions"] +fn test_keygen_merkle_aggregation() -> anyhow::Result<()> { + let dummy_snark = get_test_keccak_shard_snark(vec![])?; + let pk_path = Path::new("data/tests/keccak_shard.pk"); + let pinning_path = "configs/tests/keccak_shard.json"; + let (params, _): (KeccakComponentShardCircuitParams, MultiPhaseThreadBreakPoints) = + serde_json::from_reader(File::open(pinning_path)?)?; + let dep_pk = read_pk::>(pk_path, params).unwrap(); + let dep_vk = dep_pk.get_vk().clone(); + + let dep_intent = AggregationDependencyIntentOwned { + vk: dep_vk, + num_instance: vec![1], + accumulator_indices: None, + agg_vk_hash_data: None, + }; + let k = 20; + let kzg_params = gen_srs(k); + let kzg_params = Arc::new(kzg_params); + let child_id = AggTreeId::default(); + let intent = AggIntentMerkle { + kzg_params: kzg_params.clone(), + to_agg: vec![child_id; 2], + deps: vec![dep_intent; 2], + k, + }; + let (pk1, _) = intent.create_pk_and_pinning(&kzg_params); + + let dummy_snarks = InputMerkleAggregation::new(vec![dummy_snark; 2]); + let circuit_params = get_dummy_aggregation_params(k as usize); + let mut keygen_circuit = + dummy_snarks.build(CircuitBuilderStage::Keygen, circuit_params, &kzg_params)?; + keygen_circuit.calculate_params(Some(20)); + let vk2 = keygen_vk_custom(kzg_params.deref(), &keygen_circuit, false)?; + + let mut buf1 = Vec::new(); + pk1.get_vk().write(&mut buf1, SerdeFormat::RawBytesUnchecked)?; + let mut buf2 = Vec::new(); + vk2.write(&mut buf2, SerdeFormat::RawBytesUnchecked)?; + if buf1 != buf2 { + panic!("vkey mismatch"); + } + + Ok(()) +} diff --git a/axiom-eth/src/utils/keccak/tests/mod.rs b/axiom-eth/src/utils/keccak/tests/mod.rs new file mode 100644 index 00000000..52a320bb --- /dev/null +++ b/axiom-eth/src/utils/keccak/tests/mod.rs @@ -0,0 +1,2 @@ +pub mod merkle; +pub mod shard; diff --git a/axiom-eth/src/utils/keccak/tests/shard.rs b/axiom-eth/src/utils/keccak/tests/shard.rs new file mode 100644 index 00000000..e356decf --- /dev/null +++ b/axiom-eth/src/utils/keccak/tests/shard.rs @@ -0,0 +1,68 @@ +use std::{ + fs::{remove_file, File}, + path::Path, +}; + +use halo2_base::{gates::flex_gate::MultiPhaseThreadBreakPoints, halo2_proofs::plonk::Circuit}; +use itertools::Itertools; +use snark_verifier_sdk::{gen_pk, read_pk}; +use zkevm_hashes::keccak::component::circuit::shard::{ + KeccakComponentShardCircuit, KeccakComponentShardCircuitParams, +}; + +use crate::{ + halo2_base::utils::fs::gen_srs, + halo2curves::bn256::Fr, + snark_verifier_sdk::halo2::gen_snark_shplonk, + utils::{keccak::get_keccak_unusable_rows, snark_verifier::EnhancedSnark}, +}; + +pub fn get_test_keccak_shard_snark(mult_inputs: Vec>) -> anyhow::Result { + let k = 18u32; + let capacity = 50; + let rows_per_round = 20; + let num_unusable_rows = get_keccak_unusable_rows(rows_per_round); + let mut keccak_params = + KeccakComponentShardCircuitParams::new(k as usize, num_unusable_rows, capacity, false); + let base_params = + KeccakComponentShardCircuit::::calculate_base_circuit_params(&keccak_params); + keccak_params.base_circuit_params = base_params; + + let params = gen_srs(k); + let pk_path = Path::new("data/tests/keccak_shard.pk"); + let pinning_path = "configs/tests/keccak_shard.json"; + let (pk, pinning) = if let Ok(pk) = + read_pk::>(pk_path, keccak_params.clone()) + { + let pinning: (KeccakComponentShardCircuitParams, MultiPhaseThreadBreakPoints) = + serde_json::from_reader(File::open(pinning_path)?)?; + (pk, pinning) + } else { + let circuit = KeccakComponentShardCircuit::::new(vec![], keccak_params, false); + let pk = gen_pk(¶ms, &circuit, Some(pk_path)); + let break_points = circuit.base_circuit_break_points(); + let pinning = (circuit.params(), break_points); + serde_json::to_writer_pretty(File::create(pinning_path)?, &pinning)?; + (pk, pinning) + }; + + let prover_circuit = KeccakComponentShardCircuit::::new(mult_inputs, pinning.0, true); + prover_circuit.set_base_circuit_break_points(pinning.1); + let snark = gen_snark_shplonk(¶ms, &pk, prover_circuit, None::<&str>); + Ok(EnhancedSnark { inner: snark, agg_vk_hash_idx: None }) +} + +#[test] +#[ignore = "prover"] +pub fn test_keccak_shard_prover() { + let inputs = vec![ + (0u8..200).collect_vec(), + vec![], + (0u8..1).collect_vec(), + (0u8..135).collect_vec(), + (0u8..136).collect_vec(), + (0u8..200).collect_vec(), + ]; + get_test_keccak_shard_snark(inputs).unwrap(); + remove_file("data/test/keccak_shard.pk").ok(); +} diff --git a/axiom-eth/src/utils/merkle_aggregation.rs b/axiom-eth/src/utils/merkle_aggregation.rs new file mode 100644 index 00000000..72814ff2 --- /dev/null +++ b/axiom-eth/src/utils/merkle_aggregation.rs @@ -0,0 +1,291 @@ +use std::iter; + +use anyhow::{bail, Result}; +use itertools::Itertools; + +use crate::{ + halo2_base::{ + gates::{circuit::CircuitBuilderStage, GateChip}, + poseidon::hasher::PoseidonHasher, + }, + halo2_proofs::poly::kzg::commitment::ParamsKZG, + halo2curves::bn256::Bn256, + snark_verifier_sdk::{ + halo2::{ + aggregation::{AggregationCircuit, VerifierUniversality}, + POSEIDON_SPEC, + }, + SHPLONK, + }, + utils::{ + build_utils::pinning::aggregation::AggregationCircuitPinning, + component::utils::compute_poseidon_merkle_tree, + }, +}; + +use super::snark_verifier::{ + get_accumulator_indices, AggregationCircuitParams, EnhancedSnark, NUM_FE_ACCUMULATOR, +}; + +/// The input to the Merkle Aggregation Circuit is a collection of [EnhancedSnark]s. +/// The number of snarks does not need to be a power of two. +/// +/// The `snarks` are not allowed to be from universal aggregation circuits. +/// +/// We allow snarks to have accumulators (so they can be non-universal aggregation circuits). +/// +/// We check that: +/// - All snarks have the same number of instances _excluding accumulators_. +/// - All snarks must have at least one non-accumulator instance. +/// +/// The public instances of the Merkle Aggregation Circuit are: +/// - accumulator +/// - padded merkle root of `output`s of `snarks`, where merkle tree is padded with `0`s to next power of two. The `output` is defined as the first non-accumulator instance in each snark. +/// - `snark[i].instance[j] = snark[k].instance[j]` for all `i,j` pairs and all `j > 0`, where `snark[i].instance` are all the non-accumulator instances in `snark[i]`. +/// +/// `snarks` should be non-empty. +#[derive(Clone, Debug)] +pub struct InputMerkleAggregation { + pub snarks: Vec, +} + +impl InputMerkleAggregation { + /// See [InputMerkleAggregation] for details. + pub fn new(snarks: impl IntoIterator) -> Self { + let snarks = snarks.into_iter().collect_vec(); + assert!(!snarks.is_empty()); + assert!( + snarks.iter().all(|s| s.agg_vk_hash_idx.is_none()), + "[MerkleAggregation] snark cannot be universal aggregation circuit" + ); + Self { snarks } + } +} + +impl TryFrom> for InputMerkleAggregation { + type Error = anyhow::Error; + fn try_from(snarks: Vec) -> Result { + if snarks.is_empty() { + bail!("snarks cannot be empty"); + } + if snarks.iter().all(|s| s.agg_vk_hash_idx.is_none()) { + bail!("snark cannot be universal aggregation circuit"); + } + Ok(Self { snarks }) + } +} + +impl InputMerkleAggregation { + /// Returns [AggregationCircuit] with loaded witnesses for a **non-universal** aggregation + /// of the snarks in `self`. + /// + /// This circuit MUST implement `CircuitExt` with accumulator indices non-empty. + /// + /// We allow snarks to have accumulators (so they can be non-universal aggregation circuits). + /// + /// We will check that either: + /// - All snarks have a single instance besides any accumulators, in which case this is the `output` of that snark + /// - Or all snarks have 2 instances besides any accumulators, in which case this is the `(output, promise)` of that snark + /// + /// The public instances of the Merkle Aggregation Circuit are: + /// - accumulator + /// - padded merkle root of `output`s of `snarks`, where merkle tree is padded with `0`s to next power of two + /// - `promise` from `snark[0]` if all snarks have 2 instances. The circuit will constrain that `promise[i] == promise[j]` for all `i,j` + /// + /// `snarks` should be non-empty. + pub fn build( + self, + stage: CircuitBuilderStage, + circuit_params: AggregationCircuitParams, + kzg_params: &ParamsKZG, + ) -> anyhow::Result { + let snarks = self.snarks; + assert!(!snarks.is_empty()); + let prev_acc_indices = get_accumulator_indices(snarks.iter().map(|s| &s.inner)); + + let mut circuit = AggregationCircuit::new::( + stage, + circuit_params, + kzg_params, + snarks.into_iter().map(|s| s.inner), + VerifierUniversality::None, + ); + + // remove accumulator from previous instances + let mut prev_instances = circuit.previous_instances().clone(); + for (prev_instance, acc_indices) in prev_instances.iter_mut().zip_eq(prev_acc_indices) { + for i in acc_indices.iter().rev() { + prev_instance.remove(*i); + } + } + // number of non-accumulator instances per-snark + let num_instance = prev_instances[0].len(); + if num_instance == 0 { + bail!("snark should have at least 1 instances"); + } + if prev_instances.iter().any(|i| i.len() != num_instance) { + bail!("snarks should have same number of instances"); + } + let builder = &mut circuit.builder; + let ctx = builder.main(0); + let const_zero = ctx.load_zero(); + // Compute Merkle root of instance[0] over all snarks + let num_leaves = prev_instances.len().next_power_of_two(); + let leaves = prev_instances + .iter() + .map(|instance| instance[0]) + .chain(iter::repeat(const_zero)) + .take(num_leaves) + .collect_vec(); + + // Optimization: if there is only one snark, we don't need to compute the merkle tree so no need to create hasher. + let merkle_root = if leaves.len() == 1 { + leaves[0] + } else { + let mut hasher = PoseidonHasher::new(POSEIDON_SPEC.clone()); + let gate = GateChip::default(); + hasher.initialize_consts(ctx, &gate); + let nodes = compute_poseidon_merkle_tree(ctx, &gate, &hasher, leaves); + nodes[0] + }; + + // If instance[1] exists, constrain that instance[1] is equal for all snarks + for j in 1..num_instance { + let instance_0j = &prev_instances[0][j]; + for instance in prev_instances.iter().skip(1) { + ctx.constrain_equal(instance_0j, &instance[j]); + } + } + + if builder.assigned_instances.len() != 1 { + bail!("should only have 1 instance column"); + } + assert_eq!(builder.assigned_instances[0].len(), NUM_FE_ACCUMULATOR); + builder.assigned_instances[0].push(merkle_root); + builder.assigned_instances[0].extend_from_slice(&prev_instances[0][1..]); + + Ok(circuit) + } + + /// Circuit for witness generation only + pub fn prover_circuit( + self, + pinning: AggregationCircuitPinning, + kzg_params: &ParamsKZG, + ) -> Result { + Ok(self + .build(CircuitBuilderStage::Prover, pinning.params, kzg_params)? + .use_break_points(pinning.break_points)) + } +} + +#[cfg(feature = "keygen")] +/// Module only used for keygen helper utilities +pub mod keygen { + use std::sync::Arc; + + use halo2_base::{ + gates::circuit::CircuitBuilderStage, + halo2_proofs::{ + halo2curves::bn256::Bn256, + poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG}, + }, + utils::halo2::KeygenCircuitIntent, + }; + use snark_verifier_sdk::{ + halo2::{ + aggregation::AggregationCircuit, + utils::{ + AggregationDependencyIntent, AggregationDependencyIntentOwned, + KeygenAggregationCircuitIntent, + }, + }, + CircuitExt, Snark, + }; + + use crate::{ + halo2curves::bn256::Fr, + utils::{ + build_utils::{ + aggregation::get_dummy_aggregation_params, + keygen::compile_agg_dep_to_protocol, + pinning::{ + aggregation::{AggTreeId, GenericAggParams, GenericAggPinning}, + CircuitPinningInstructions, + }, + }, + snark_verifier::EnhancedSnark, + }, + }; + + use super::InputMerkleAggregation; + + /// Intent for Merkle Aggregation Circuit. + /// For now we do not record the generator of the trusted setup here since it + /// is assumed to be read from file. + #[derive(Clone, Debug)] + pub struct AggIntentMerkle { + /// This is from bad UX; only svk = kzg_params.get_g()[0] is used + pub kzg_params: Arc>, + /// Must be same length as `deps`. Has the corresponding circuit IDs for each dependency snark + pub to_agg: Vec, + /// Vec of `vk, num_instance, is_aggregation` for each dependency snark + pub deps: Vec, + /// The log_2 domain size of the current aggregation circuit + pub k: u32, + } + + impl KeygenAggregationCircuitIntent for AggIntentMerkle { + fn intent_of_dependencies(&self) -> Vec { + self.deps.iter().map(|d| d.into()).collect() + } + fn build_keygen_circuit_from_snarks(self, snarks: Vec) -> Self::AggregationCircuit { + let input = InputMerkleAggregation::new( + snarks.into_iter().map(|s| EnhancedSnark::new(s, None)), + ); + let agg_params = get_dummy_aggregation_params(self.k as usize); + let mut circuit = + input.build(CircuitBuilderStage::Keygen, agg_params, &self.kzg_params).unwrap(); + circuit.calculate_params(Some(20)); + circuit + } + } + + impl KeygenCircuitIntent for AggIntentMerkle { + type ConcreteCircuit = AggregationCircuit; + /// We omit here tags (e.g., hash of vkeys) of the dependencies, they should be recorded separately. + type Pinning = GenericAggPinning; + fn get_k(&self) -> u32 { + self.k + } + fn build_keygen_circuit(self) -> Self::ConcreteCircuit { + self.build_keygen_circuit_shplonk() + } + fn get_pinning_after_keygen( + self, + kzg_params: &ParamsKZG, + circuit: &Self::ConcreteCircuit, + ) -> Self::Pinning { + let svk = kzg_params.get_g()[0]; + let dk = (svk, kzg_params.g2(), kzg_params.s_g2()); + assert_eq!(self.kzg_params.get_g()[0], svk); + assert_eq!(self.kzg_params.g2(), dk.1); + assert_eq!(self.kzg_params.s_g2(), dk.2); + let pinning = circuit.pinning(); + let to_agg = self + .deps + .iter() + .map(|d| compile_agg_dep_to_protocol(kzg_params, d, false)) + .collect(); + let agg_params = GenericAggParams { to_agg, agg_params: pinning.params }; + GenericAggPinning { + params: agg_params, + num_instance: circuit.num_instance(), + accumulator_indices: AggregationCircuit::accumulator_indices().unwrap(), + agg_vk_hash_data: None, + dk: dk.into(), + break_points: pinning.break_points, + } + } + } +} diff --git a/axiom-eth/src/utils/mod.rs b/axiom-eth/src/utils/mod.rs new file mode 100644 index 00000000..296067c0 --- /dev/null +++ b/axiom-eth/src/utils/mod.rs @@ -0,0 +1,261 @@ +use crate::Field; +use ethers_core::{ + types::{Address, H256}, + utils::keccak256, +}; +use halo2_base::{ + gates::{GateInstructions, RangeChip}, + halo2_proofs::halo2curves::ff::PrimeField, + safe_types::{SafeBool, SafeByte, SafeBytes32, SafeTypeChip}, + utils::{decompose, BigPrimeField, ScalarField}, + AssignedValue, Context, + QuantumCell::{Constant, Witness}, +}; +use itertools::Itertools; + +use self::hilo::HiLo; + +/// Traits and templates for concrete `Circuit` implementation and compatibility with [snark_verifier_sdk] +pub mod build_utils; +/// Utilities for circuit writing using [halo2_base] +pub mod circuit_utils; +/// Component framework. +pub mod component; +/// Contains convenience `EthCircuitInstructions` to help auto-implement a Halo2 circuit that uses `RlcCircuitBuilder` and `KeccakPromiseLoader`. +pub mod eth_circuit; +/// Same as Word2 from zkevm +pub mod hilo; +/// Shim for keccak circuit from [axiom_eth::zkevm_hashes::keccak] +pub mod keccak; +/// Non-universal aggregation circuit that verifies several snarks and computes the merkle root +/// of snark inputs. See [InputMerkleAggregation] for more details. +#[cfg(feature = "aggregation")] +pub mod merkle_aggregation; +/// Snark verifier SDK helpers (eventually move to snark-verifier-sdk) +#[cfg(feature = "aggregation")] +pub mod snark_verifier; + +pub const DEFAULT_RLC_CACHE_BITS: usize = 32; + +/// H256 as hi-lo (u128, u128) +pub type AssignedH256 = [AssignedValue; 2]; + +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(h256_tree_root(&leaves[start_idx..start_idx + (1 << depth)])); + start_idx += 1 << depth; + } else { + merkle_roots.push(H256::zero()); + } + } + merkle_roots +} + +/// # 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]; + } + 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) { + leaves[i] = keccak256([&leaves[2 * i][..], &leaves[2 * i + 1][..]].concat()).to_vec(); + } + } + H256::from_slice(&leaves[0]) +} + +// Field has PrimeField +/// Takes `hash` as `bytes32` and returns `(hash[..16], hash[16..])` represented as big endian numbers in the prime field +pub fn encode_h256_to_hilo(hash: &H256) -> HiLo { + let hash_lo = u128::from_be_bytes(hash[16..].try_into().unwrap()); + let hash_hi = u128::from_be_bytes(hash[..16].try_into().unwrap()); + HiLo::from_lo_hi([hash_lo, hash_hi].map(F::from_u128)) +} + +pub fn encode_addr_to_field>(input: &Address) -> F { + let mut bytes = input.as_bytes().to_vec(); + bytes.reverse(); + let mut repr = [0u8; 32]; + repr[..20].copy_from_slice(&bytes); + F::from_bytes_le(&repr) +} + +pub fn bytes_to_fe(bytes: &[u8]) -> Vec { + bytes.iter().map(|b| F::from(*b as u64)).collect() +} + +// circuit utils: + +/// Assigns `bytes` as private witnesses **without** range checking. +pub fn unsafe_bytes_to_assigned( + ctx: &mut Context, + bytes: &[u8], +) -> Vec> { + ctx.assign_witnesses(bytes.iter().map(|b| F::from(*b as u64))) +} + +/// **Unsafe:** Resize `bytes` and assign as private witnesses **without** range checking. +pub fn assign_vec( + ctx: &mut Context, + bytes: Vec, + max_len: usize, +) -> Vec> { + let mut newbytes = bytes; + assert!(newbytes.len() <= max_len); + newbytes.resize(max_len, 0); + newbytes.into_iter().map(|byte| ctx.load_witness(F::from(byte as u64))).collect_vec() +} + +/// 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: SafeBool, +) { + let [lhs, rhs] = [lhs, rhs].map(|x| gate.mul(ctx, x, cond)); + ctx.constrain_equal(&lhs, &rhs); +} + +/// Assumes that `bytes` have witnesses that are bytes. +pub fn bytes_be_to_u128( + ctx: &mut Context, + gate: &impl GateInstructions, + bytes: &[SafeByte], +) -> Vec> { + limbs_be_to_u128(ctx, gate, bytes, 8) +} + +pub(crate) fn limbs_be_to_u128( + ctx: &mut Context, + gate: &impl GateInstructions, + limbs: &[impl AsRef>], + limb_bits: usize, +) -> Vec> { + assert!(!limbs.is_empty(), "limbs must not be empty"); + assert_eq!(128 % limb_bits, 0); + limbs + .chunks(128 / limb_bits) + .map(|chunk| { + gate.inner_product( + ctx, + chunk.iter().rev().map(|a| *a.as_ref()), + (0..chunk.len()).map(|idx| Constant(gate.pow_of_two()[limb_bits * idx])), + ) + }) + .collect_vec() +} + +/// Decomposes `uint` into `num_bytes` bytes, in big-endian, 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, + uint: &AssignedValue, + num_bytes: usize, +) -> Vec> { + let mut bytes_be = uint_to_bytes_le(ctx, range, uint, num_bytes); + bytes_be.reverse(); + bytes_be +} + +/// Decomposes `uint` into `num_bytes` bytes, in little-endian, and constrains the decomposition. +/// Here `uint` can be any uint that fits into `F`. +pub fn uint_to_bytes_le( + ctx: &mut Context, + range: &RangeChip, + uint: &AssignedValue, + num_bytes: usize, +) -> Vec> { + // Same logic as RangeChip::range_check + let pows = range.gate.pow_of_two().iter().step_by(8).take(num_bytes).map(|x| Constant(*x)); + let byte_vals = decompose(uint.value(), num_bytes, 8).into_iter().map(Witness); + let (acc, bytes_le) = range.gate.inner_product_left(ctx, byte_vals, pows); + ctx.constrain_equal(&acc, uint); + + let safe = SafeTypeChip::new(range); + safe.raw_to_fix_len_bytes_vec(ctx, bytes_le, num_bytes).into_bytes() +} + +pub fn bytes_be_to_uint( + ctx: &mut Context, + gate: &impl GateInstructions, + input: &[SafeByte], + num_bytes: usize, +) -> AssignedValue { + gate.inner_product( + ctx, + input[..num_bytes].iter().rev().map(|b| *b.as_ref()), + (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() +} + +pub fn constrain_vec_equal( + ctx: &mut Context, + a: &[impl AsRef>], + b: &[impl AsRef>], +) { + for (left, right) in a.iter().zip_eq(b.iter()) { + let left = left.as_ref(); + let right = right.as_ref(); + // debug_assert_eq!(left.value(), right.value()); + ctx.constrain_equal(left, right); + } +} + +/// Returns 1 if all entries of `input` are zero, 0 otherwise. +pub fn is_zero_vec( + ctx: &mut Context, + gate: &impl GateInstructions, + input: &[impl AsRef>], +) -> AssignedValue { + let is_zeros = input.iter().map(|x| gate.is_zero(ctx, *x.as_ref())).collect_vec(); + let sum = gate.sum(ctx, is_zeros); + let total_len = F::from(input.len() as u64); + gate.is_equal(ctx, sum, Constant(total_len)) +} + +/// Load [H256] as private witness as [SafeBytes32], where bytes have been range checked. +pub fn load_h256_to_safe_bytes32( + ctx: &mut Context, + safe: &SafeTypeChip, + hash: H256, +) -> SafeBytes32 { + load_bytes32(ctx, safe, hash.0) +} + +pub fn load_bytes32( + ctx: &mut Context, + safe: &SafeTypeChip, + bytes: [u8; 32], +) -> SafeBytes32 { + let raw = ctx.assign_witnesses(bytes.map(|b| F::from(b as u64))); + safe.raw_bytes_to(ctx, raw) +} diff --git a/axiom-eth/src/utils/snark_verifier.rs b/axiom-eth/src/utils/snark_verifier.rs new file mode 100644 index 00000000..85852c3f --- /dev/null +++ b/axiom-eth/src/utils/snark_verifier.rs @@ -0,0 +1,303 @@ +use std::hash::Hash; + +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use serde_with::{base64::Base64, serde_as, DeserializeAs, SerializeAs}; +use snark_verifier::verifier::plonk::PlonkProtocol; + +use crate::{ + halo2_base::{ + gates::{ + circuit::CircuitBuilderStage, flex_gate::threads::SinglePhaseCoreManager, GateChip, + GateInstructions, RangeChip, RangeInstructions, + }, + halo2_proofs::{ + halo2curves::bn256::{Bn256, Fr}, + poly::kzg::commitment::ParamsKZG, + }, + poseidon::hasher::PoseidonSponge, + AssignedValue, Context, + }, + halo2curves::bn256::G1Affine, + snark_verifier_sdk::{ + halo2::{ + aggregation::{ + aggregate_snarks, AggregationCircuit, AggregationConfigParams, + AssignedTranscriptObject, Halo2KzgAccumulationScheme, + PreprocessedAndDomainAsWitness, SnarkAggregationOutput, Svk, VerifierUniversality, + }, + POSEIDON_SPEC, + }, + Snark, LIMBS, + }, +}; +#[cfg(feature = "evm")] +use ethers_core::types::Bytes; +#[cfg(feature = "evm")] +use halo2_base::halo2_proofs::plonk::ProvingKey; +#[cfg(feature = "evm")] +use snark_verifier_sdk::{ + evm::{encode_calldata, gen_evm_proof_shplonk}, + CircuitExt, +}; + +pub type AggregationCircuitParams = AggregationConfigParams; + +pub const NUM_FE_ACCUMULATOR: usize = 4 * LIMBS; + +type F = Fr; + +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EnhancedSnark { + #[serde_as(as = "Base64Bytes")] + pub inner: Snark, + /// If this snark is a **universal** aggregation circuit, then it must expose an + /// `agg_vk_hash` in its public instances. In that case `agg_vk_hash_idx = Some(idx)`, + /// where `flattened_instances[idx]` is the `agg_vk_hash` and `flattened_instances` are the + /// instances **with the old accumulator removed**. + #[serde(skip_serializing_if = "Option::is_none")] + pub agg_vk_hash_idx: Option, +} + +impl EnhancedSnark { + pub fn new(snark: Snark, agg_vk_hash_idx: Option) -> Self { + Self { inner: snark, agg_vk_hash_idx } + } +} + +impl AsRef for EnhancedSnark { + fn as_ref(&self) -> &EnhancedSnark { + self + } +} +impl AsRef for EnhancedSnark { + fn as_ref(&self) -> &Snark { + &self.inner + } +} + +/// **Private** witnesses that form the output of [aggregate_enhanced_snarks]. +#[derive(Clone, Debug)] +pub struct EnhancedSnarkAggregationOutput { + /// We remove the old accumulators from the previous instances using `has_accumulator` from + /// the previous [EnhancedSnark]s + pub previous_instances: Vec>>, + pub accumulator: Vec>, + /// This is the single Poseidon hash of all previous `agg_vk_hash` in previously aggregated + /// universal aggregation snarks, together with the preprocessed digest and transcript initial state + /// (aka partial vkey) from the enhanced snarks that were aggregated. + pub agg_vk_hash: AssignedValue, + /// The proof transcript, as loaded scalars and elliptic curve points, for each SNARK that was aggregated. + pub proof_transcripts: Vec>, +} + +/// Aggregate enhanced snarks as a universal aggregation circuit, taking care to +/// compute the new `agg_vk_hash` by hashing together all previous `agg_vk_hash`s and partial vkeys. +pub fn aggregate_enhanced_snarks( + pool: &mut SinglePhaseCoreManager, + range: &RangeChip, + svk: Svk, // gotten by params.get_g()[0].into() + snarks: &[impl AsRef], +) -> EnhancedSnarkAggregationOutput +where + AS: for<'a> Halo2KzgAccumulationScheme<'a>, +{ + let SnarkAggregationOutput { previous_instances, accumulator, preprocessed, proof_transcripts } = + aggregate_snarks::( + pool, + range, + svk, + snarks.iter().map(|s| s.as_ref().inner.clone()), + VerifierUniversality::Full, + ); + let prev_acc_indices = get_accumulator_indices(snarks.iter().map(|s| &s.as_ref().inner)); + let ctx = pool.main(); + let (previous_instances, agg_vk_hash) = process_prev_instances_and_calc_agg_vk_hash( + ctx, + range.gate(), + previous_instances, + preprocessed, + &prev_acc_indices, + snarks.iter().map(|s| s.as_ref().agg_vk_hash_idx), + ); + EnhancedSnarkAggregationOutput { + previous_instances, + accumulator, + agg_vk_hash, + proof_transcripts, + } +} + +/// Returns `(circuit, prev_instances, agg_vkey_hash)` where `prev_instances` has old accumulators removed. +/// +/// ### Previous `agg_vkey_hash` indices +/// If a snark in `snarks` is a **universal** aggregation circuit, then it **must** expose an +/// `agg_vkey_hash` in its public instances. In that case `agg_vkey_hash_idx = Some(idx)`, +/// where `flattened_instances[idx]` is the `agg_vkey_hash` and `flattened_instances` are the +/// instances of the snark **with the old accumulator removed**. +pub fn create_universal_aggregation_circuit( + stage: CircuitBuilderStage, + circuit_params: AggregationCircuitParams, + kzg_params: &ParamsKZG, + snarks: Vec, + agg_vkey_hash_indices: Vec>, +) -> (AggregationCircuit, Vec>>, AssignedValue) +where + AS: for<'a> Halo2KzgAccumulationScheme<'a>, +{ + assert_eq!(snarks.len(), agg_vkey_hash_indices.len()); + let prev_acc_indices = get_accumulator_indices(&snarks); + let mut circuit = AggregationCircuit::new::( + stage, + circuit_params, + kzg_params, + snarks, + VerifierUniversality::Full, + ); + + let prev_instances = circuit.previous_instances().clone(); + let preprocessed = circuit.preprocessed().clone(); + let builder = &mut circuit.builder; + let ctx = builder.main(0); + let gate = GateChip::default(); + let (previous_instances, agg_vkey_hash) = process_prev_instances_and_calc_agg_vk_hash( + ctx, + &gate, + prev_instances, + preprocessed, + &prev_acc_indices, + agg_vkey_hash_indices, + ); + (circuit, previous_instances, agg_vkey_hash) +} + +/// Calculate agg_vk_hash and simultaneously remove old accumulators from previous_instances +/// +/// Returns `previous_instances` with old accumulators at `prev_accumulator_indices` removed, and returns the new `agg_vk_hash` +pub fn process_prev_instances_and_calc_agg_vk_hash( + ctx: &mut Context, + gate: &impl GateInstructions, + mut prev_instances: Vec>>, + preprocessed: Vec, + prev_accumulator_indices: &[Vec], + prev_agg_vk_hash_indices: impl IntoIterator>, +) -> (Vec>>, AssignedValue) { + let mut sponge = PoseidonSponge::from_spec(ctx, POSEIDON_SPEC.clone()); + for (((prev_instance, partial_vk), acc_indices), agg_vk_hash_idx) in prev_instances + .iter_mut() + .zip_eq(preprocessed) + .zip_eq(prev_accumulator_indices) + .zip_eq(prev_agg_vk_hash_indices) + { + sponge.update(&[partial_vk.k]); + sponge.update(&partial_vk.preprocessed); + for i in acc_indices.iter().sorted().rev() { + prev_instance.remove(*i); + } + if let Some(agg_vk_hash_idx) = agg_vk_hash_idx { + assert!(!acc_indices.is_empty()); + sponge.update(&[prev_instance[agg_vk_hash_idx]]); + } + } + let agg_vk_hash = sponge.squeeze(ctx, gate); + (prev_instances, agg_vk_hash) +} + +/// Returns the indices of the accumulator, if any, for each snark **in sorted (increasing) order**. +/// +/// ## Panics +/// Panics if any snark has accumulator indices not in instance column 0. +pub fn get_accumulator_indices<'a>(snarks: impl IntoIterator) -> Vec> { + snarks + .into_iter() + .map(|snark| { + let accumulator_indices = &snark.protocol.accumulator_indices; + assert!(accumulator_indices.len() <= 1, "num_proof per snark should be 1"); + if let Some(acc_indices) = accumulator_indices.last() { + acc_indices + .iter() + .map(|&(i, j)| { + assert_eq!(i, 0, "accumulator should be in instance column 0"); + j + }) + .sorted() + .dedup() + .collect() + } else { + vec![] + } + }) + .collect() +} + +/// Returns calldata as bytes to be sent to SNARK verifier smart contract. +/// Calldata is public instances (field elements) concatenated with proof (bytes). +/// Returned as [Bytes] for easy serialization to hex string. +#[cfg(feature = "evm")] +pub fn gen_evm_calldata_shplonk>( + params: &ParamsKZG, + pk: &ProvingKey, + circuit: C, +) -> Bytes { + let instances = circuit.instances(); + let proof = gen_evm_proof_shplonk(params, pk, circuit, instances.clone()); + encode_calldata(&instances, &proof).into() +} + +// Newtype wrapper around Base64 encoded/decoded bytes +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Base64Bytes(#[serde_as(as = "Base64")] Vec); + +impl SerializeAs for Base64Bytes { + fn serialize_as(snark: &Snark, serializer: S) -> Result + where + S: serde::Serializer, + { + match bincode::serialize(snark) { + Ok(bytes) => Base64Bytes(bytes).serialize(serializer), + Err(e) => Err(serde::ser::Error::custom(e)), + } + } +} + +impl<'de> DeserializeAs<'de, Snark> for Base64Bytes { + fn deserialize_as(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bytes = Base64Bytes::deserialize(deserializer)?; + bincode::deserialize(&bytes.0).map_err(serde::de::Error::custom) + } +} + +impl SerializeAs> for Base64Bytes { + fn serialize_as(protocol: &PlonkProtocol, serializer: S) -> Result + where + S: serde::Serializer, + { + match bincode::serialize(protocol) { + Ok(bytes) => Base64Bytes(bytes).serialize(serializer), + Err(e) => Err(serde::ser::Error::custom(e)), + } + } +} + +impl<'de> DeserializeAs<'de, PlonkProtocol> for Base64Bytes { + fn deserialize_as(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let bytes = Base64Bytes::deserialize(deserializer)?; + bincode::deserialize(&bytes.0).map_err(serde::de::Error::custom) + } +} + +impl Hash for EnhancedSnark { + fn hash(&self, state: &mut H) { + bincode::serialize(&self.inner).unwrap().hash(state); + self.agg_vk_hash_idx.hash(state); + } +} diff --git a/axiom-query/Cargo.toml b/axiom-query/Cargo.toml new file mode 100644 index 00000000..0910160f --- /dev/null +++ b/axiom-query/Cargo.toml @@ -0,0 +1,77 @@ +[package] +name = "axiom-query" +version = "2.1.0-alpha.1" +authors = ["Intrinsic Technologies"] +license = "MIT" +edition = "2021" +repository = "https://github.com/axiom-crypto/axiom-eth" +readme = "README.md" +description = "This contains the ZK circuits that generate proofs for the `AxiomV2Query` smart contract." +rust-version = "1.73.0" + +[[bin]] +name = "axiom-query-keygen" +path = "src/bin/keygen.rs" +required-features = ["keygen"] + +[dependencies] +itertools = "0.11" +lazy_static = "1.4.0" +# serialization +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0", default-features = false } +serde_with = { version = "3.3", features = ["base64"] } +bincode = { version = "1.3.3" } +# misc +log = "0.4" +env_logger = "0.10" +getset = "0.1.2" +ark-std = { version = "0.3.0", features = ["print-trace"], optional = true } +anyhow = "1.0" +downcast-rs = "1.2.0" +hex = "0.4.3" +byteorder = { version = "1.4.3" } +rand = "0.8" +rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } + +# halo2, features turned on by axiom-eth +axiom-eth = { version = "=0.4.2", path = "../axiom-eth", default-features = false, features = ["providers", "aggregation", "evm"] } +axiom-codec = { version = "0.3.0-alpha", path = "../axiom-codec", default-features = false } +axiom-components = { path = "../axiom-components" } + +# crypto +rlp = "0.5.2" +ethers-core = { workspace = true } +# mpt implementation +hasher = { version = "=0.1", features = ["hash-keccak"] } +cita_trie = "=5.0.0" + +# keygen +clap = { version = "=4.4.7", features = ["derive"], optional = true } +enum_dispatch = { version = "0.3.12", optional = true } +serde_yaml = { version = "0.9.16", 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" +proptest = "1.1.0" +rand_chacha = "0.3.1" +tokio = { version = "1.33", features = ["macros"] } +reqwest = { version = "0.11", features = ["json"] } +# generating circuit inputs from blockchain +ethers-providers = { workspace = true } +futures = { version = "0.3" } +blake3 = { version = "=1.5" } + +[features] +default = ["halo2-axiom", "jemallocator", "display", "keygen"] +display = ["axiom-eth/display", "dep:ark-std"] +asm = ["axiom-eth/asm"] +revm = ["axiom-eth/revm"] +halo2-pse = ["axiom-eth/halo2-pse"] +halo2-axiom = ["axiom-eth/halo2-axiom"] +jemallocator = ["axiom-eth/jemallocator"] +keygen = ["axiom-eth/keygen", "dep:enum_dispatch", "dep:clap", "dep:serde_yaml"] diff --git a/axiom-query/KEYGEN.md b/axiom-query/KEYGEN.md new file mode 100644 index 00000000..de090aff --- /dev/null +++ b/axiom-query/KEYGEN.md @@ -0,0 +1,64 @@ +# AxiomV2Query ZK Circuits + +# Proving and Verifying Key Generation + +To generate the exact proving and verifying keys we use in production on Ethereum Mainnet, you can do the following: + +1. Download the KZG trusted setup that we use with [this script](../trusted_setup_s3.sh). + +``` +bash ../trusted_setup_s3.sh +``` + +You can read more about the trusted setup we use and how it was generated [here](https://docs.axiom.xyz/docs/transparency-and-security/kzg-trusted-setup). + +The trusted setup will be downloaded to a directory called `params/` by default. You can move the directory elsewhere. We'll refer to the directory as `$SRS_DIR` below. + +2. Install `axiom-query-keygen` binary to your path via: + +```bash +cargo install --path axiom-query --force +``` + +This builds the `axiom-query-keygen` binary in release mode and installs it to your path. +Additional details about the binary can be found [here](./src/bin/README.md). + +3. Generate the proving and verifying keys for one of our production configurations. + +We have multiple aggregation configurations that we use in production. These are specified by intent YAML files in the [`configs/production`](./configs/production/) directory. The configurations, ordered by the sum of generated proving key sizes, are: + +- `all_small.yml` +- `all_32_each_default.yml` +- `all_128_each_default.yml` +- `all_large.yml` +- `all_max.yml` + +We will refer to one of these files as `$INTENT_NAME.yml` below. To generate all proving keys and verifying keys for the configuration corresponding to `$INTENT_NAME.yml`, run: + +```bash + +axiom-query-keygen --srs-dir $SRS_DIR --data-dir $CIRCUIT_DATA_DIR --intent configs/production/$INTENT_NAME.yml --tag $INTENT_NAME +``` + +where `$CIRCUIT_DATA_DIR` is the directory you want to store the output files. After the process is complete, a summary JSON containing the different circuit IDs created and the full aggregation tree of these circuit IDs will be output to `$CIRCUIT_DATA_DIR/$INTENT_NAME.tree`. At the top level of the JSON is an `"aggregate_vk_hash"` field, which commits to the aggregation configuration used in this particular `$INTENT_NAME`. + +Check that the top level `"circuit_id"` in `$INTENT_NAME.tree` equals `e94efbee3e07ae4224ed1ae0a6389f5128d210ff7a2a743e459cff501e4379ab`, _regardless of which `$INTENT_NAME` you used_. This is the circuit ID of the final Axiom Aggregation 2 circuit, which is the same for all configurations because Axiom Aggregation 2 is a universal aggregation circuit. The `aggregate_vk_hash` commits to the aggregation configuration and is used to distinguish between them. + +⚠️ **Special Note:** The `all_max.yml` configuration is very large. The largest proving key generated is 200 GB. To run `axiom-query-keygen` on `all_max.yml`, you need a machine with at least 500 GB of RAM, or enough [swap](https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-22-04) to make up the difference. + +4. Rename and forge format the Solidity SNARK verifier file for the `AxiomV2QueryVerifier` smart contract: + +Check that the top level `"circuit_id"` in `$INTENT_NAME.tree` equals `e94efbee3e07ae4224ed1ae0a6389f5128d210ff7a2a743e459cff501e4379ab`, _regardless of which `$INTENT_NAME` you used_. Then run + +```bash +bash src/bin/rename_snark_verifier.sh $CIRCUIT_DATA_DIR/e94efbee3e07ae4224ed1ae0a6389f5128d210ff7a2a743e459cff501e4379ab.sol +``` + +The final Solidity file will be output to `AxiomV2QueryVerifier.sol`. + +5. Compare the summary `*.tree` JSONs with the ones we use in production [here](./data/production/). + +6. Check the top level aggregate vkey hashes in the `*.tree` JSONs match the ones we use in production: + +- The list we use in production is provided [here](./data/production/aggregate_vk_hashes.json) +- These aggregate vkey hashes are part of the constructor arguments of our `AxiomV2Query` smart contract on Ethereum mainnet: see [logs](https://etherscan.io/tx/0xab7e570b6fbcc78841a0a5bde473e47737285aabf5fb9fb4876bd2b8043d9301#eventlog). diff --git a/axiom-query/README.md b/axiom-query/README.md new file mode 100644 index 00000000..d57eccdb --- /dev/null +++ b/axiom-query/README.md @@ -0,0 +1,284 @@ +# AxiomV2Query ZK Circuits + +# Proving and Verifying Key Generation + +For instructions on how to generate the exact proving and verifying keys we use in production on Ethereum Mainnet, see [here](./KEYGEN.md). + +# Overview + +## [Subquery Components](./src/components/subqueries/) + +These are the component circuits that prove all subqueries of a single type: + +- [header](./src/components/subqueries/block_header/) +- [account](./src/components/subqueries/account/) +- [storage](./src/components/subqueries/storage/) +- [transaction](./src/components/subqueries/transaction/) +- [receipt](./src/components/subqueries/receipt/) +- [solidity mappings](./src/components/subqueries/solidity_mappings/) + +These circuits all use the [Component Circuit Framework](../axiom-eth/src/utils/README.md). +They each consist of a `ComponentCircuitImpl` with `RlcCircuitBuilder`, a single `PromiseLoader` for keccak, and a single `PromiseLoader` for a dependent subquery type (or none in the case of header). +The `CoreBuilder` in each case specifies the business logic of the circuit in `virtual_assign_phase{0,1}` but there is no special raw assignment: the raw assignments are all done by `RlcCircuitBuilder` and the `PromiseLoader`s. The virtual table output by each circuit is the table of `(subquery, value)` pairs for that subquery type. The `subquery` type is different for each circuit, but we specify a `flatten` function for each type which uniformizes the way to compute the Poseidon-based commitment to the virtual output table. + +For each circuit, one needs to specify: + +- the types of the virtual table output +- the component type to make promise calls to: + - header: none (but it depends on an external blockhash MMR) + - account: header + - storage: account + - tx: header + - receipt: header + - solidity: storage + +What is hidden from the `CoreBuilder` implementation are the other parts of `ComponentCircuitImpl`: + +- the `PromiseLoader`s are correctly loading the promise table and adding dynamic lookups +- the promise table commitment is being computed correctly + +The unchecked assumption of each circuit are: + +- the promise table commitments for keccak and the called component circuit will be matched with the actual output commitments of those circuits + +**Disclaimer:** there is still a fair amount of copy-paste code between subquery circuit implementations. We are working to reduce this further. + +## [Results Root Component Circuit](./src/components/results/) + +This circuit also uses the [Component Circuit Framework](../axiom-eth/src/utils/README.md) but is a bit special compared to the subquery circuits. It is a component circuit with **no** component output. However it does make promise calls to every other subquery component circuit. +These promise tables are _a priori_ grouped by subquery types. Moreover they may contain intermediate subqueries that were only needed to check correctness of the user subquery (e.g., a tx subquery needs a corresponding header subquery to check the transaction root). In order to compute the `queryHash` and `resultsRoot`, we need the _ordered_ list of user subqueries and results. We do this by "joining" the promise tables of different types into one big lookup table and then doing dynamic lookups to check. + +The join is currently done in the `table` module; it will be moved to `MultiPromiseLoader` in a coming PR. + +The reason this component circuit has no component output is that the true outputs of the circuit are: `resultsRoot`, `resultsRootPoseidon`, and `subqueryHash`s. These are outward/user facing outputs, and for future compatibilty their format depends on the number of subqueries (e.g., `resultsRoot` is a padded Merkle root up to next power of 2 of `numSubqueries`). As such there is no automatic way to compute these commitments and we have custom implementations for them. + +## [Keccak Component Shard Circuit](https://github.com/axiom-crypto/halo2-lib/blob/main/hashes/zkevm/src/keccak/component/circuit/shard.rs) + +The base `KeccakComponentShardCircuit` in `halo2-lib/zkevm-hashes` is a component circuit by our [definition](../axiom-eth/src/utils/README.md#definition-of-component-circuit). +We have added adapters to [`axiom-eth`](../axiom-eth/src/keccak/README.md) so that it can be promise called as a component circuit in our framework. + +## [Merkle Aggregation Circuits](../axiom-eth/src/utils/merkle_aggregation.rs) + +Above we have described the component **shard** circuits. For any component type, multiple shard circuits will be aggregated together (exact configurations will be determined after benchmarking) using `InputMerkleAggregation`. As such we will have component aggregation circuits for each component type where the output commitment is a Merkle root of shard output commitments. +We note that this is fully supported by the Subquery Aggregation Circuit and Axiom Aggregation 1 Circuit because the public instance format of the shard and aggregation circuit of a given component type will be exactly the same, excluding accumulators. The Subquery Aggregation Circuit and Axiom Aggregation 1 Circuit know when to remove old accumulators from previous instances of aggregates snarks, so the treatment is uniform. + +## [Verify Compute Circuit](./src/verify_compute/) + +This circuit aggregates a user submitted compute snark (in the form of `AxiomV2ComputeQuery`). +It then decommits a claimed `resultsRootPoseidon` and the claimed Poseidon commitment to `subqueryHashes`. It then requires calling keccaks to combine subquery results and `computeQuery` +to compute the full `queryHash`. + +This is not a component circuit, but it is implemented using `EthCircuitImpl`, which uses `PromiseLoader` to call the keccak component. + +## [Subquery Aggregation Circuit](./src/subquery_aggregation/) + +This is a universal aggregation circuit that aggregates all subquery circuits and the results root circuit. Since the results root circuit calls each subquery circuit, this aggregation circuit will check the public instance equalities between all promise commitments and output commitments of subquery component circuits. + +It will also check that all keccak promise commitments are equal, but this promise commitment is still not checked. + +The public outputs of the subquery aggregation circuit are: + +- those of the results root circuit, +- the blockhash MMR from the header circuit +- keccak promise commitment +- accumulator and aggregate vkey hash from the universal aggregation + +After this aggregation one can forget about the subquery and results root circuits. + +## [Axiom Aggregation 1 Circuit](./src/axiom_aggregation1/) + +This circuit will aggregate: + +- Verify Compute Circuit +- Subquery Aggregation Circuit +- Keccak Component Final Aggregation Circuit + +In other words, it aggregates all remaining circuits. It will check that the `resultsRootPoseidon` and `subqueryHashes` commits in Verify Compute and Subquery Aggregation circuits match. It will also check that the keccak promise and output commitments match. + +## [Axiom Aggregation 2 Circuit](./src/axiom_aggregation2/) + +This aggregates [Axiom Aggregation 1 Circuit](#axiom-aggregation-1-circuit). It is essentially a passthrough circuit, but we also add a `payee` public instance (this is to prevent transaction frontrunning in the mempool). + +# Circuit Public IO Formats + +We start from circuits that touch the smart contract and work back towards dependencies. + +## Axiom Aggregation 2 (final, for EVM) + +This is the snark that will be verified by a fixed verifier on EVM. + +### Public IO + +The public IO is given by: + +- `accumulator` (384 bytes) +- `sourceChainId` (uint64, in F) +- `computeResultsHash` (bytes32, in hi-lo) +- `queryHash` (bytes32, in hi-lo) +- `querySchema` (bytes32, in hi-lo) +- `blockhashMMRKeccak` (bytes32, in hi-lo) +- `aggVkeyHash` (bytes32, in F) +- `payee` (address, in F) + (It doesn't save any EVM keccaks to hash these all together in-circuit, so we can keep multiple public instances.) + +This will be a fixed `AggregationCircuit` with `Universality::Full` that can verify any circuit with a fixed config. The fixed config will be that of another `AggregationCircuit` (aka single phase `BaseCircuitParams`). We call the previous circuit the final EVM verifies the `AxiomAggregation1Circuit`. + +- The `AxiomAggregation2Circuit` will just pass through public instances of the `AxiomAggregation1Circuit` it is verifying **and add a payee instance**. +- It also computes a new `aggVkeyHash` using `AxiomAggregation1Circuit.aggVkeyHash` and the `k, preprocessed_digest` of `AxiomAggregation1Circuit` itself. +- The `k` and selectors of this `AxiomAggregation2Circuit` must be fixed - our on-chain verifier does not allow universality. +- `AxiomAggregation2Circuit` will be configured to use few columns for cheapest on-chain verification. + +## Axiom Aggregation 1 Circuit + +### Public IO + +This is the same as the [Public IO of Axiom Aggregation 2](#axiom-aggregation-2-final-for-evm) except there is **no payee field**. + +- `accumulator` (384 bytes) +- `sourceChainId` (uint64, in F) +- `computeResultsHash` (bytes32, in hi-lo) +- `queryHash` (bytes32, in hi-lo) +- `querySchema` (bytes32, in hi-lo) +- `blockhashMMRKeccak` (bytes32, in hi-lo) +- `aggVkeyHash` (bytes32, in F) + +This is an `AggregationCircuit` with `Universality::Full` + +- `k` and selectors can be variable: this means we can have multiple `ControllerAggregationCircuit`s (I guess this makes this a trait) + - Any such circuit can do anything that only requires `BaseCircuitBuilder`, in particular it can verify an arbitrary fixed number of snarks + - But it cannot do dynamic lookups (requires new columns), or RLC (unless we decide to add a fixed number of RLC columns to be supported) +- Ideally proof generation takes <10s on g5.48xl + +This circuit will aggregate: + +- `VerifyComputeCircuit` +- `SubqueryAggregationCircuit` +- `KeccakFinalAggregationCircuit` + +The `AxiomAggregation2` and `AxiomAggregation1` circuits could be combined if we had a universal verifier in EVM (such as [here](https://github.com/han0110/halo2-solidity-verifier/tree/feature/solidity-generator)), but previous experience says that the `AxiomAggregation1` circuit is large enough that two layers of aggregation is faster than one big layer anyways. + +## Verify Compute Circuit + +### Public IO + +- [not used] Component managed `output_commit` (F) which should be ignored since this circuit has no virtual output +- `promiseCommitment` (F) - in this case this is `poseidon(promiseKeccakComponent)` +- `accumulator` (384 bytes) +- `sourceChainId` (uint64, in F) +- `computeResultsHash` (bytes32, in hi-lo) +- `queryHash` (bytes32, in hi-lo) +- `querySchema` (bytes32, in hi-lo) +- `resultsRootPoseidon` (F) +- `promiseSubqueryHashes` (F) + +Does the following: + +- Verify the compute snark. + - The `k` and vkey of the snark is committed to in `queryHash`. Therefore we do not have any other (Poseidon) `aggVkeyHash` since this is the only snark we're aggregating. +- Compute `dataQueryHash` from `subqueryHashes` +- Compute `queryHash` from `dataQueryHash` and `computeQuery` +- Compute `querySchema` +- Compute `computeResultsHash` + - Requires looking up compute subqueries in data results with **dynamic lookup** + +Depends on external commitment to computations done in the `ResultsRoot` circuit, which computes the actual subquery results and subquery hashes. + +We separate the calculation of query hash into this circuit and not into the circuits that are aggregated by `SubqueryAggregationCircuit` below because the final `queryHash` calculation cannot be parallelized, whereas we could in the future parallelize everything in `SubqueryAggregationCircuit` into multiple data shards. + +## Subquery Aggregation Circuit + +We can have multiple implementations of these, and each can be literally any circuit - no limitations on number of columns, gates, lookups, etc. This provides us a lot of flexibility, and allows us to add new variants later on without changing `FinalVerifier` and `ControllerAggregationCircuit`. + +### Public IO + +- `accumulator` (384 bytes) +- `promiseKeccakComponent` (F) is the re-exposed keccak component promise commit from previous aggregated component snarks +- `aggVkeyHash` (F) +- `resultsRootPoseidon` (F) +- `commitSubqueryHashes` (F) +- `blockhashMMRKeccak` (bytes32, in hi-lo) + +The `SubqueryAggregationCircuit` will: + +- Aggregate the following circuits (for each type it may either be the shard circuit or the [Merkle Aggregation Circuit](#merkle-aggregation-circuit) of circuits of that type): + - ResultsRoot + - Header + - Account + - Storage + - Tx + - Receipt + - Solidity +- Constrain equalities of data component commits between component "calls". +- Constrain the keccak component promise in each aggregated component is equal, and then re-expose this promise as a public instance + +## Results Root Component Circuit + +### Public IO + +- [not used] Component managed `output_commit`. This component has no virtual table as output, and should not be called directly. +- `promiseComponentsCommit` (F) - poseidon hash of all promises, including keccak +- `resultsRootPoseidon` (F) +- `commitSubqueryHashes` (F) + +`commitSubqueryHashes` is the Poseidon commitment to a _variable number_ `numSubqueries` of keccak subquery hashes. We choose to do a variable length Poseidon for more flexibility so the total subquery capacity in this circuit does not have to match the `userMaxSubqueries` in the `VerifyCompute` circuit. As a consequence, this `commitSubqueryHashes` also commits to `numSubqueries`. + +## Header Component Circuit + +### Public IO + +- `commitHeaderComponent` (F) +- `promiseCommitment` (F) +- `blockhashMMRKeccak` (bytes32, in hi-lo) + +## Account Component Circuit + +### Public IO + +- `commitAccountComponent` (F) +- `promiseCommitment` (F) + +## Storage Component Circuit + +### Public IO + +- `commitStorageComponent` (F) +- `promiseCommitment` (F) + +## Transaction Component Circuit + +### Public IO + +- `commitTxComponent` (F) +- `promiseCommitment` (F) + +## Receipt Component Circuit + +### Public IO + +- `commitReceiptComponent` (F) +- `promiseCommitment` (F) + +## Solidity Component Circuit + +### Public IO + +- `commitSolidityComponent` (F) +- `promiseCommitment` (F) + +## Keccak Component Shard Circuit + +### Public IO + +- `keccakComponentCommit` (F) + +## Merkle Aggregation Circuit + +Above we have specified the public IO of component **shard** circuits. We will have multiple configurations where we use the MerkleAggregationCircuit to aggregate multiple shard circuits of the same component type into a new MerkleAggregationCircuit. + +The public IO of the new MerkleAggregationCircuit will consist of the `accumulator` (384 bytes), followed by the exact same instance format as the shard circuit. The output commit is now a Merkle root of shard output commits. The promise commitments of all shards are constrained to be equal. + +# Reference Diagram + +The following diagram is for reference only. The exact configuration and number of circuits will depend on the aggregation configuration used. + +![diagram](https://lucid.app/publicSegments/view/bcfc1a84-c274-4f1d-a747-898ca1ec07f5/image.png) diff --git a/axiom-query/configs/production/all_128_each_default.yml b/axiom-query/configs/production/all_128_each_default.yml new file mode 100644 index 00000000..1539bcb7 --- /dev/null +++ b/axiom-query/configs/production/all_128_each_default.yml @@ -0,0 +1,326 @@ +AxiomAgg2: + k: 23 + force_params: + num_advice: 1 + num_lookup_advice: 1 + num_fixed: 1 + axiom_agg1_intent: + k: 22 + force_params: + num_advice: 13 + num_lookup_advice: 2 + num_fixed: 1 + intent_verify_compute: + k: 22 + core_params: + subquery_results_capacity: 128 + svk: "0100000000000000000000000000000000000000000000000000000000000000" + client_metadata: + version: 0 + numAdvicePerPhase: + - 4 + numLookupAdvicePerPhase: + - 1 + numRlcColumns: 0 + numFixed: 1 + numInstance: + - 2304 + numChallenge: + - 0 + maxOutputs: 128 + isAggregation: false + preprocessed_len: 13 + loader_params: + comp_loader_params: + max_height: 5 + shard_caps: + - 4000 + intent_keccak: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 20 + core_params: + capacity: 4000 + intent_subquery_agg: + k: 22 + deps: + - Header: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + max_extra_data_bytes: 32 + capacity: 132 + loader_params: + comp_loader_params: + note: intent_keccak + max_height: 5 + shard_caps: + - 4000 + - Account: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + capacity: 33 + max_trie_depth: 14 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 5 + shard_caps: + - 4000 + - comp_loader_params: + note: Header + max_height: 1 + shard_caps: + - 132 + - Storage: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + capacity: 33 + max_trie_depth: 13 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 5 + shard_caps: + - 4000 + - comp_loader_params: + note: Account + max_height: 2 + shard_caps: + - 33 + - Tx: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + chip_params: + max_data_byte_len: 8192 + max_access_list_len: 4096 + enable_types: + - true + - true + - true + capacity: 16 + max_trie_depth: 6 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 5 + shard_caps: + - 4000 + - comp_loader_params: + note: Header + max_height: 1 + shard_caps: + - 132 + - Receipt: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + chip_params: + max_data_byte_len: 1024 + max_log_num: 80 + topic_num_bounds: + - 0 + - 4 + capacity: 4 + max_trie_depth: 6 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 5 + shard_caps: + - 4000 + - comp_loader_params: + note: Header + max_height: 1 + shard_caps: + - 132 + - SolidityMapping: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + capacity: 32 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 5 + shard_caps: + - 4000 + - comp_loader_params: + note: Storage + max_height: 2 + shard_caps: + - 33 + - ResultsRoot: + Leaf: + k: 22 + core_params: + enabled_types: + - false + - true + - true + - true + - true + - true + - true + capacity: 128 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 5 + shard_caps: + - 4000 + - params_per_component: + axiom-query:ComponentTypeHeaderSubquery: + max_height: 1 + shard_caps: + - 132 + - 132 + axiom-query:ComponentTypeAccountSubquery: + max_height: 2 + shard_caps: + - 33 + - 33 + - 33 + - 33 + axiom-query:ComponentTypeStorageSubquery: + max_height: 2 + shard_caps: + - 33 + - 33 + - 33 + - 33 + axiom-query:ComponentTypeTxSubquery: + max_height: 3 + shard_caps: + - 16 + - 16 + - 16 + - 16 + - 16 + - 16 + - 16 + - 16 + axiom-query:ComponentTypeReceiptSubquery: + max_height: 5 + shard_caps: + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + - 4 + axiom-query:ComponentTypeSolidityNestedMappingSubquery: + max_height: 2 + shard_caps: + - 32 + - 32 + - 32 + - 32 diff --git a/axiom-query/configs/production/all_32_each_default.yml b/axiom-query/configs/production/all_32_each_default.yml new file mode 100644 index 00000000..958702e2 --- /dev/null +++ b/axiom-query/configs/production/all_32_each_default.yml @@ -0,0 +1,243 @@ +AxiomAgg2: + axiom_agg1_intent: + force_params: + num_advice: 13 + num_fixed: 1 + num_lookup_advice: 2 + intent_keccak: + Node: + child_intent: + Node: + child_intent: + Node: + child_intent: + Leaf: + core_params: + capacity: 4000 + k: 20 + k: 21 + num_children: 2 + k: 21 + num_children: 2 + k: 21 + num_children: 2 + intent_subquery_agg: + deps: + - Header: + Leaf: + core_params: + capacity: 130 + max_extra_data_bytes: 32 + k: 22 + loader_params: + comp_loader_params: + max_height: 3 + note: intent_keccak + shard_caps: + - 4000 + - Account: + Node: + child_intent: + Node: + child_intent: + Leaf: + core_params: + capacity: 24 + max_trie_depth: 14 + k: 21 + loader_params: + - comp_loader_params: + max_height: 3 + note: intent_keccak + shard_caps: + - 4000 + - comp_loader_params: + max_height: 1 + note: Header + shard_caps: + - 130 + k: 21 + num_children: 2 + k: 21 + num_children: 2 + - Storage: + Node: + child_intent: + Node: + child_intent: + Leaf: + core_params: + capacity: 16 + max_trie_depth: 13 + k: 21 + loader_params: + - comp_loader_params: + max_height: 3 + note: intent_keccak + shard_caps: + - 4000 + - comp_loader_params: + max_height: 2 + note: Account + shard_caps: + - 24 + k: 21 + num_children: 2 + k: 21 + num_children: 2 + - Tx: + Node: + child_intent: + Leaf: + core_params: + capacity: 16 + chip_params: + enable_types: + - true + - true + - true + max_access_list_len: 4096 + max_data_byte_len: 8192 + max_trie_depth: 6 + k: 21 + loader_params: + - comp_loader_params: + max_height: 3 + note: intent_keccak + shard_caps: + - 4000 + - comp_loader_params: + max_height: 1 + note: Header + shard_caps: + - 130 + k: 21 + num_children: 2 + - Receipt: + Node: + child_intent: + Leaf: + core_params: + capacity: 16 + chip_params: + max_data_byte_len: 800 + max_log_num: 20 + topic_num_bounds: + - 0 + - 4 + max_trie_depth: 6 + k: 21 + loader_params: + - comp_loader_params: + max_height: 3 + note: intent_keccak + shard_caps: + - 4000 + - comp_loader_params: + max_height: 1 + note: Header + shard_caps: + - 130 + k: 21 + num_children: 2 + - SolidityMapping: + Node: + child_intent: + Leaf: + core_params: + capacity: 16 + k: 21 + loader_params: + - comp_loader_params: + max_height: 3 + note: intent_keccak + shard_caps: + - 4000 + - comp_loader_params: + max_height: 2 + note: Storage + shard_caps: + - 16 + k: 21 + num_children: 2 + - ResultsRoot: + Leaf: + core_params: + capacity: 128 + enabled_types: + - false + - true + - true + - true + - true + - true + - true + k: 22 + loader_params: + - comp_loader_params: + max_height: 3 + note: intent_keccak + shard_caps: + - 4000 + - params_per_component: + axiom-query:ComponentTypeAccountSubquery: + max_height: 2 + shard_caps: + - 24 + - 24 + axiom-query:ComponentTypeHeaderSubquery: + max_height: 1 + shard_caps: + - 130 + axiom-query:ComponentTypeReceiptSubquery: + max_height: 1 + shard_caps: + - 16 + - 16 + axiom-query:ComponentTypeSolidityNestedMappingSubquery: + max_height: 1 + shard_caps: + - 16 + - 16 + axiom-query:ComponentTypeStorageSubquery: + max_height: 2 + shard_caps: + - 16 + - 16 + axiom-query:ComponentTypeTxSubquery: + max_height: 1 + shard_caps: + - 16 + - 16 + k: 22 + intent_verify_compute: + core_params: + client_metadata: + isAggregation: false + maxOutputs: 128 + numAdvicePerPhase: + - 4 + numChallenge: + - 0 + numFixed: 1 + numInstance: + - 2304 + numLookupAdvicePerPhase: + - 1 + numRlcColumns: 0 + version: 0 + preprocessed_len: 13 + subquery_results_capacity: 128 + svk: "0100000000000000000000000000000000000000000000000000000000000000" + k: 22 + loader_params: + comp_loader_params: + max_height: 3 + shard_caps: + - 4000 + k: 22 + force_params: + num_advice: 1 + num_fixed: 1 + num_lookup_advice: 1 + k: 23 diff --git a/axiom-query/configs/production/all_large.yml b/axiom-query/configs/production/all_large.yml new file mode 100644 index 00000000..a5122dba --- /dev/null +++ b/axiom-query/configs/production/all_large.yml @@ -0,0 +1,282 @@ +AxiomAgg2: + k: 23 + force_params: + num_advice: 1 + num_lookup_advice: 1 + num_fixed: 1 + axiom_agg1_intent: + k: 22 + force_params: + num_advice: 13 + num_lookup_advice: 2 + num_fixed: 1 + intent_verify_compute: + k: 22 + core_params: + subquery_results_capacity: 128 + svk: "0100000000000000000000000000000000000000000000000000000000000000" + client_metadata: + version: 0 + numAdvicePerPhase: + - 4 + numLookupAdvicePerPhase: + - 1 + numRlcColumns: 0 + numFixed: 1 + numInstance: + - 2304 + numChallenge: + - 0 + maxOutputs: 128 + isAggregation: false + preprocessed_len: 13 + loader_params: + comp_loader_params: + max_height: 4 + shard_caps: + - 5000 + intent_keccak: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 20 + core_params: + capacity: 5000 + intent_subquery_agg: + k: 22 + deps: + - Header: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + max_extra_data_bytes: 32 + capacity: 132 + loader_params: + comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 5000 + - Account: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + capacity: 33 + max_trie_depth: 14 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 5000 + - comp_loader_params: + note: Header + max_height: 1 + shard_caps: + - 132 + - Storage: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + capacity: 33 + max_trie_depth: 13 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 5000 + - comp_loader_params: + note: Account + max_height: 2 + shard_caps: + - 33 + - Tx: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + chip_params: + max_data_byte_len: 32768 + max_access_list_len: 16384 + enable_types: + - true + - true + - true + capacity: 4 + max_trie_depth: 6 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 5000 + - comp_loader_params: + note: Header + max_height: 1 + shard_caps: + - 132 + - Receipt: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + chip_params: + max_data_byte_len: 2048 + max_log_num: 80 + topic_num_bounds: + - 0 + - 4 + capacity: 2 + max_trie_depth: 6 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 5000 + - comp_loader_params: + note: Header + max_height: 1 + shard_caps: + - 132 + - SolidityMapping: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + capacity: 32 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 5000 + - comp_loader_params: + note: Storage + max_height: 2 + shard_caps: + - 33 + - ResultsRoot: + Leaf: + k: 22 + core_params: + enabled_types: + - false + - true + - true + - true + - true + - true + - true + capacity: 128 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 5000 + - params_per_component: + axiom-query:ComponentTypeHeaderSubquery: + max_height: 1 + shard_caps: + - 132 + - 132 + axiom-query:ComponentTypeAccountSubquery: + max_height: 2 + shard_caps: + - 33 + - 33 + - 33 + - 33 + axiom-query:ComponentTypeStorageSubquery: + max_height: 2 + shard_caps: + - 33 + - 33 + - 33 + - 33 + axiom-query:ComponentTypeTxSubquery: + max_height: 2 + shard_caps: + - 4 + - 4 + - 4 + - 4 + axiom-query:ComponentTypeReceiptSubquery: + max_height: 3 + shard_caps: + - 2 + - 2 + - 2 + - 2 + - 2 + - 2 + - 2 + - 2 + axiom-query:ComponentTypeSolidityNestedMappingSubquery: + max_height: 2 + shard_caps: + - 32 + - 32 + - 32 + - 32 diff --git a/axiom-query/configs/production/all_max.yml b/axiom-query/configs/production/all_max.yml new file mode 100644 index 00000000..8631218b --- /dev/null +++ b/axiom-query/configs/production/all_max.yml @@ -0,0 +1,271 @@ +AxiomAgg2: + k: 23 + force_params: + num_advice: 1 + num_lookup_advice: 1 + num_fixed: 1 + axiom_agg1_intent: + k: 22 + force_params: + num_advice: 13 + num_lookup_advice: 2 + num_fixed: 1 + intent_verify_compute: + k: 22 + core_params: + subquery_results_capacity: 128 + svk: "0100000000000000000000000000000000000000000000000000000000000000" + client_metadata: + version: 0 + numAdvicePerPhase: + - 4 + numLookupAdvicePerPhase: + - 1 + numRlcColumns: 0 + numFixed: 1 + numInstance: + - 2304 + numChallenge: + - 0 + maxOutputs: 128 + isAggregation: false + preprocessed_len: 13 + loader_params: + comp_loader_params: + max_height: 4 + shard_caps: + - 4000 + intent_keccak: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 20 + core_params: + capacity: 4000 + intent_subquery_agg: + k: 22 + deps: + - Header: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + max_extra_data_bytes: 32 + capacity: 132 + loader_params: + comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 4000 + - Account: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + capacity: 33 + max_trie_depth: 14 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 4000 + - comp_loader_params: + note: Header + max_height: 1 + shard_caps: + - 132 + - Storage: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + capacity: 33 + max_trie_depth: 13 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 4000 + - comp_loader_params: + note: Account + max_height: 2 + shard_caps: + - 33 + - Tx: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 22 + core_params: + chip_params: + max_data_byte_len: 330000 + max_access_list_len: 131072 + enable_types: + - true + - true + - true + capacity: 1 + max_trie_depth: 6 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 4000 + - comp_loader_params: + note: Header + max_height: 1 + shard_caps: + - 132 + - Receipt: + Node: + k: 21 + num_children: 1 + child_intent: + Node: + k: 22 + num_children: 1 + child_intent: + Leaf: + k: 22 + core_params: + chip_params: + max_data_byte_len: 1024 + max_log_num: 400 + topic_num_bounds: + - 0 + - 4 + capacity: 1 + max_trie_depth: 6 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 4000 + - comp_loader_params: + note: Header + max_height: 1 + shard_caps: + - 132 + - SolidityMapping: + Node: + k: 21 + num_children: 2 + child_intent: + Node: + k: 21 + num_children: 2 + child_intent: + Leaf: + k: 21 + core_params: + capacity: 32 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 4000 + - comp_loader_params: + note: Storage + max_height: 2 + shard_caps: + - 33 + - ResultsRoot: + Leaf: + k: 22 + core_params: + enabled_types: + - false + - true + - true + - true + - true + - true + - true + capacity: 128 + loader_params: + - comp_loader_params: + note: intent_keccak + max_height: 4 + shard_caps: + - 4000 + - params_per_component: + axiom-query:ComponentTypeHeaderSubquery: + max_height: 1 + shard_caps: + - 132 + - 132 + axiom-query:ComponentTypeAccountSubquery: + max_height: 2 + shard_caps: + - 33 + - 33 + - 33 + - 33 + axiom-query:ComponentTypeStorageSubquery: + max_height: 2 + shard_caps: + - 33 + - 33 + - 33 + - 33 + axiom-query:ComponentTypeTxSubquery: + max_height: 2 + shard_caps: + - 1 + - 1 + - 1 + - 1 + axiom-query:ComponentTypeReceiptSubquery: + max_height: 0 + shard_caps: + - 1 + axiom-query:ComponentTypeSolidityNestedMappingSubquery: + max_height: 2 + shard_caps: + - 32 + - 32 + - 32 + - 32 diff --git a/axiom-query/configs/production/all_small.yml b/axiom-query/configs/production/all_small.yml new file mode 100644 index 00000000..c36fb75d --- /dev/null +++ b/axiom-query/configs/production/all_small.yml @@ -0,0 +1,192 @@ +# The total subquery capacity is 32 +AxiomAgg2: + k: 23 + force_params: + num_advice: 1 + num_lookup_advice: 1 + num_fixed: 1 + axiom_agg1_intent: + k: 22 + force_params: + num_advice: 13 + num_lookup_advice: 2 + num_fixed: 1 + intent_verify_compute: + k: 22 + core_params: + subquery_results_capacity: 32 + svk: "0100000000000000000000000000000000000000000000000000000000000000" + client_metadata: + version: 0 + numAdvicePerPhase: [4] + numLookupAdvicePerPhase: [1] + numRlcColumns: 0 + numFixed: 1 + numInstance: [2304] + numChallenge: [0] + maxOutputs: 128 + isAggregation: false + preprocessed_len: 13 + loader_params: + comp_loader_params: # keccak + max_height: 1 + shard_caps: + - 4000 + intent_keccak: + Node: + k: 21 + num_children: 1 + child_intent: + Leaf: + k: 20 + core_params: + capacity: 4000 + intent_subquery_agg: + k: 21 + deps: + - Header: + Leaf: + k: 21 + core_params: + max_extra_data_bytes: 32 + capacity: 33 + loader_params: + comp_loader_params: # keccak + max_height: 1 + shard_caps: + - 4000 + + - Account: + Leaf: + k: 21 + core_params: + capacity: 9 + max_trie_depth: 14 + loader_params: + - comp_loader_params: # keccak + max_height: 1 + shard_caps: + - 4000 + - comp_loader_params: # header + max_height: 1 + shard_caps: + - 33 + + - Storage: + Leaf: + k: 21 + core_params: + capacity: 9 + max_trie_depth: 13 + loader_params: + - comp_loader_params: # keccak + max_height: 1 + shard_caps: + - 4000 + - comp_loader_params: # account + max_height: 1 + shard_caps: + - 9 + - Tx: + Leaf: + k: 21 + core_params: + chip_params: + max_data_byte_len: 8192 + max_access_list_len: 4096 + enable_types: + - true + - true + - true + capacity: 8 + max_trie_depth: 6 + loader_params: + - comp_loader_params: # keccak + max_height: 1 + shard_caps: + - 4000 + - comp_loader_params: # header + max_height: 1 + shard_caps: + - 33 + + - Receipt: + Leaf: + k: 21 + core_params: + chip_params: + max_data_byte_len: 800 + max_log_num: 20 + topic_num_bounds: + - 0 + - 4 + capacity: 8 + max_trie_depth: 6 + loader_params: + - comp_loader_params: # keccak + max_height: 1 + shard_caps: + - 4000 + - comp_loader_params: # header + max_height: 1 + shard_caps: + - 33 + + - SolidityMapping: + Leaf: + k: 21 + core_params: + capacity: 8 + loader_params: + - comp_loader_params: # keccak + max_height: 1 + shard_caps: + - 4000 + - comp_loader_params: # storage + max_height: 1 + shard_caps: + - 9 + + - ResultsRoot: + Leaf: + k: 21 + core_params: + enabled_types: + - false # Null + - true # Header + - true # Account + - true # Storage + - true # Transaction + - true # Receipt + - true # SolidityNestedMapping + capacity: 32 + loader_params: + - comp_loader_params: # keccak + max_height: 1 + shard_caps: + - 4000 + - params_per_component: + "axiom-query:ComponentTypeHeaderSubquery": + max_height: 1 + shard_caps: + - 33 + "axiom-query:ComponentTypeAccountSubquery": + max_height: 1 + shard_caps: + - 9 + "axiom-query:ComponentTypeStorageSubquery": + max_height: 1 + shard_caps: + - 9 + "axiom-query:ComponentTypeTxSubquery": + max_height: 1 + shard_caps: + - 8 + "axiom-query:ComponentTypeReceiptSubquery": + max_height: 1 + shard_caps: + - 8 + "axiom-query:ComponentTypeSolidityNestedMappingSubquery": + max_height: 1 + shard_caps: + - 8 diff --git a/axiom-query/configs/templates/account.leaf.yml b/axiom-query/configs/templates/account.leaf.yml new file mode 100644 index 00000000..4c0e82a6 --- /dev/null +++ b/axiom-query/configs/templates/account.leaf.yml @@ -0,0 +1,15 @@ +Subquery: + Account: + Leaf: + k: 16 + core_params: + capacity: 33 + loader_params: + - comp_loader_params: # keccak + max_height: 0 + shard_caps: + - 200 + - comp_loader_params: # header + max_height: 0 + shard_caps: + - 33 diff --git a/axiom-query/configs/templates/axiom_agg_1.yml b/axiom-query/configs/templates/axiom_agg_1.yml new file mode 100644 index 00000000..a929458d --- /dev/null +++ b/axiom-query/configs/templates/axiom_agg_1.yml @@ -0,0 +1,104 @@ +AxiomAgg1: + k: 21 + force_params: + - num_advice: 13 + - num_lookup_advice: 2 + - num_fixed: 1 + intent_verify_compute: + k: 20 + core_params: + subquery_results_capacity: 1 + svk: "0100000000000000000000000000000000000000000000000000000000000000" + client_metadata: + version: 0 + numAdvicePerPhase: [4] + numLookupAdvicePerPhase: [1] + numRlcColumns: 0 + numFixed: 1 + numInstance: [2304] + numChallenge: [0] + maxOutputs: 128 + isAggregation: false + preprocessed_len: 13 + loader_params: + comp_loader_params: # keccak + max_height: 2 + shard_caps: + - 50 + intent_keccak: + Node: + k: 20 + num_children: 2 + child_intent: + Leaf: + k: 18 + core_params: + capacity: 50 + intent_subquery_agg: + k: 21 + deps: + - Header: + Node: + k: 20 + num_children: 2 + child_intent: + Leaf: + k: 20 + core_params: + max_extra_data_bytes: 32 + capacity: 1 + loader_params: + - comp_loader_params: # keccak + max_height: 2 + shard_caps: + - 50 + + - Receipt: + Leaf: + k: 20 + core_params: + chip_params: + max_data_byte_len: 512 + max_log_num: 20 + topic_num_bounds: + - 0 + - 4 + network: null + capacity: 1 + loader_params: + - comp_loader_params: # keccak + max_height: 2 + shard_caps: + - 50 + - comp_loader_params: # header + max_height: 1 + shard_caps: + - 1 + + - ResultsRoot: + Leaf: + k: 20 + core_params: + enabled_types: + - false # Null + - true # Header + - false # Account + - false # Storage + - false # Transaction + - true # Receipt + - false # SolidityNestedMapping + capacity: 1 + loader_params: + - comp_loader_params: # keccak + max_height: 2 + shard_caps: + - 50 + - params_per_component: + "axiom-query:ComponentTypeHeaderSubquery": + max_height: 1 + shard_caps: + - 1 + "axiom-query:ComponentTypeReceiptSubquery": + max_height: 1 + shard_caps: + - 1 diff --git a/axiom-query/configs/templates/axiom_agg_2.yml b/axiom-query/configs/templates/axiom_agg_2.yml new file mode 100644 index 00000000..25ec6996 --- /dev/null +++ b/axiom-query/configs/templates/axiom_agg_2.yml @@ -0,0 +1,106 @@ +AxiomAgg2: + k: 21 + force_params: + - num_advice: 1 + - num_lookup_advice: 1 + - num_fixed: 1 + axiom_agg1_intent: + k: 21 + intent_verify_compute: + k: 20 + core_params: + subquery_results_capacity: 1 + svk: "0100000000000000000000000000000000000000000000000000000000000000" + client_metadata: + version: 0 + numAdvicePerPhase: [4] + numLookupAdvicePerPhase: [1] + numRlcColumns: 0 + numFixed: 1 + numInstance: [2304] + numChallenge: [0] + maxOutputs: 128 + isAggregation: false + preprocessed_len: 13 + loader_params: + comp_loader_params: # keccak + max_height: 2 + shard_caps: + - 50 + intent_keccak: + Node: + k: 20 + num_children: 2 + child_intent: + Leaf: + k: 18 + core_params: + capacity: 50 + intent_subquery_agg: + k: 21 + deps: + - Header: + Node: + k: 20 + num_children: 2 + child_intent: + Leaf: + k: 20 + core_params: + max_extra_data_bytes: 32 + capacity: 1 + loader_params: + - comp_loader_params: # keccak + max_height: 2 + shard_caps: + - 50 + + - Receipt: + Leaf: + k: 20 + core_params: + chip_params: + max_data_byte_len: 512 + max_log_num: 20 + topic_num_bounds: + - 0 + - 4 + network: null + capacity: 1 + loader_params: + - comp_loader_params: # keccak + max_height: 2 + shard_caps: + - 50 + - comp_loader_params: # header + max_height: 1 + shard_caps: + - 1 + + - ResultsRoot: + Leaf: + k: 20 + core_params: + enabled_types: + - false # Null + - true # Header + - false # Account + - false # Storage + - false # Transaction + - true # Receipt + - false # SolidityNestedMapping + capacity: 1 + loader_params: + - comp_loader_params: # keccak + max_height: 2 + shard_caps: + - 50 + - params_per_component: + "axiom-query:ComponentTypeHeaderSubquery": + max_height: 1 + shard_caps: + - 1 + "axiom-query:ComponentTypeReceiptSubquery": + max_height: 1 + shard_caps: + - 1 diff --git a/axiom-query/configs/templates/header.leaf.yml b/axiom-query/configs/templates/header.leaf.yml new file mode 100644 index 00000000..e9302304 --- /dev/null +++ b/axiom-query/configs/templates/header.leaf.yml @@ -0,0 +1,12 @@ +Subquery: + Header: + Leaf: + k: 16 + core_params: + max_extra_data_bytes: 32 + capacity: 33 + loader_params: + comp_loader_params: # keccak + max_height: 0 + shard_caps: + - 1000 diff --git a/axiom-query/configs/templates/header.tree.yml b/axiom-query/configs/templates/header.tree.yml new file mode 100644 index 00000000..aea16810 --- /dev/null +++ b/axiom-query/configs/templates/header.tree.yml @@ -0,0 +1,20 @@ +# tree of depth 1 with 2 leaves +Subquery: + Header: + Node: + k: 20 + num_children: 1 + child_intent: + Node: + k: 20 + num_children: 1 + child_intent: + Leaf: + k: 18 + core_params: + max_extra_data_bytes: 32 + capacity: 16 + loader_params: + comp_loader_params: # keccak + max_height: 1 + shard_caps: [100, 100] diff --git a/axiom-query/configs/templates/keccak.leaf.yml b/axiom-query/configs/templates/keccak.leaf.yml new file mode 100644 index 00000000..b449fc15 --- /dev/null +++ b/axiom-query/configs/templates/keccak.leaf.yml @@ -0,0 +1,6 @@ +Keccak: + Leaf: + k: 18 + core_params: + capacity: 50 + capacity: 50 \ No newline at end of file diff --git a/axiom-query/configs/templates/keccak.tree.yml b/axiom-query/configs/templates/keccak.tree.yml new file mode 100644 index 00000000..d72158c1 --- /dev/null +++ b/axiom-query/configs/templates/keccak.tree.yml @@ -0,0 +1,9 @@ +Keccak: + Node: + k: 20 + num_children: 2 + child_intent: + Leaf: + k: 18 + core_params: + capacity: 50 \ No newline at end of file diff --git a/axiom-query/configs/templates/receipt.leaf.yml b/axiom-query/configs/templates/receipt.leaf.yml new file mode 100644 index 00000000..be46acda --- /dev/null +++ b/axiom-query/configs/templates/receipt.leaf.yml @@ -0,0 +1,22 @@ +Subquery: + Receipt: + Leaf: + k: 16 + core_params: + chip_params: + max_data_byte_len: 512 + max_log_num: 20 + topic_num_bounds: + - 0 + - 4 + network: null + capacity: 8 + loader_params: + - comp_loader_params: # keccak + max_height: 0 + shard_caps: + - 200 + - comp_loader_params: # header + max_height: 0 + shard_caps: + - 33 diff --git a/axiom-query/configs/templates/results_root.leaf.yml b/axiom-query/configs/templates/results_root.leaf.yml new file mode 100644 index 00000000..37aa8dcc --- /dev/null +++ b/axiom-query/configs/templates/results_root.leaf.yml @@ -0,0 +1,28 @@ +Subquery: + ResultsRoot: + Leaf: + k: 20 + core_params: + enabled_types: + - false # Null + - true # Header + - false # Account + - false # Storage + - false # Transaction + - true # Receipt + - false # SolidityNestedMapping + capacity: 1 + loader_params: + - comp_loader_params: # keccak + max_height: 2 + shard_caps: + - 50 + - params_per_component: + "axiom-query:ComponentTypeHeaderSubquery": + max_height: 1 + shard_caps: + - 1 + "axiom-query:ComponentTypeReceiptSubquery": + max_height: 1 + shard_caps: + - 1 diff --git a/axiom-query/configs/templates/soliditymapping.leaf.yml b/axiom-query/configs/templates/soliditymapping.leaf.yml new file mode 100644 index 00000000..e4bbe8a4 --- /dev/null +++ b/axiom-query/configs/templates/soliditymapping.leaf.yml @@ -0,0 +1,15 @@ +Subquery: + SolidityMapping: + Leaf: + k: 16 + core_params: + capacity: 33 + loader_params: + - comp_loader_params: # keccak + max_height: 0 + shard_caps: + - 1000 + - comp_loader_params: # storage + max_height: 1 + shard_caps: + - 1 diff --git a/axiom-query/configs/templates/storage.leaf.yml b/axiom-query/configs/templates/storage.leaf.yml new file mode 100644 index 00000000..3b0b5d1f --- /dev/null +++ b/axiom-query/configs/templates/storage.leaf.yml @@ -0,0 +1,15 @@ +Subquery: + Storage: + Leaf: + k: 16 + core_params: + capacity: 33 + loader_params: + - comp_loader_params: # keccak + max_height: 0 + shard_caps: + - 1000 + - comp_loader_params: # account + max_height: 1 + shard_caps: + - 1 diff --git a/axiom-query/configs/templates/subquery_agg.yml b/axiom-query/configs/templates/subquery_agg.yml new file mode 100644 index 00000000..4711320d --- /dev/null +++ b/axiom-query/configs/templates/subquery_agg.yml @@ -0,0 +1,36 @@ +SubqueryAgg: + k: 21 + deps: + - Header: + Leaf: + k: 20 + core_params: + max_extra_data_bytes: 32 + capacity: 33 + loader_params: + - comp_loader_params: # keccak + max_height: 0 + shard_caps: + - 1000 + + - Receipt: + Leaf: + k: 20 + core_params: + chip_params: + max_data_byte_len: 512 + max_log_num: 20 + topic_num_bounds: + - 0 + - 4 + network: null + capacity: 8 + loader_params: + - comp_loader_params: # keccak + max_height: 0 + shard_caps: + - 1000 + - comp_loader_params: # header + max_height: 0 + shard_caps: + - 33 diff --git a/axiom-query/configs/templates/verifycompute.yml b/axiom-query/configs/templates/verifycompute.yml new file mode 100644 index 00000000..8de548d0 --- /dev/null +++ b/axiom-query/configs/templates/verifycompute.yml @@ -0,0 +1,21 @@ +VerifyCompute: + k: 16 + core_params: + subquery_results_capacity: 1 + svk: "0100000000000000000000000000000000000000000000000000000000000000" + client_metadata: + version: 0 + numAdvicePerPhase: [4] + numLookupAdvicePerPhase: [1] + numRlcColumns: 0 + numFixed: 1 + numInstance: [2304] + numChallenge: [0] + maxOutputs: 128 + isAggregation: false + preprocessed_len: 13 + loader_params: + comp_loader_params: # keccak + max_height: 0 + shard_caps: + - 100 diff --git a/axiom-query/configs/test/axiom_aggregation1_for_agg.json b/axiom-query/configs/test/axiom_aggregation1_for_agg.json new file mode 100644 index 00000000..f77c87e4 --- /dev/null +++ b/axiom-query/configs/test/axiom_aggregation1_for_agg.json @@ -0,0 +1,21 @@ +{ + "params": { + "degree": 22, + "num_advice": 9, + "num_lookup_advice": 1, + "num_fixed": 1, + "lookup_bits": 21 + }, + "break_points": [ + [ + 4194294, + 4194293, + 4194294, + 4194294, + 4194294, + 4194293, + 4194294, + 4194294 + ] + ] +} \ No newline at end of file diff --git a/axiom-query/configs/test/axiom_aggregation2.json b/axiom-query/configs/test/axiom_aggregation2.json new file mode 100644 index 00000000..62868f16 --- /dev/null +++ b/axiom-query/configs/test/axiom_aggregation2.json @@ -0,0 +1,12 @@ +{ + "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/axiom-query/configs/test/header_subquery.json b/axiom-query/configs/test/header_subquery.json new file mode 100644 index 00000000..ce0bfdb0 --- /dev/null +++ b/axiom-query/configs/test/header_subquery.json @@ -0,0 +1,39 @@ +{ + "params": { + "base": { + "k": 18, + "num_advice_per_phase": [ + 12, + 1 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 1 + }, + "break_points": { + "base": [ + [ + 262134, + 262134, + 262133, + 262133, + 262133, + 262134, + 262132, + 262134, + 262133, + 262134, + 262133 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-query/configs/test/header_subquery_core_params.json b/axiom-query/configs/test/header_subquery_core_params.json new file mode 100644 index 00000000..33b4acb2 --- /dev/null +++ b/axiom-query/configs/test/header_subquery_core_params.json @@ -0,0 +1,4 @@ +{ + "max_extra_data_bytes": 32, + "capacity": 3 +} \ No newline at end of file diff --git a/axiom-query/configs/test/header_subquery_for_agg.json b/axiom-query/configs/test/header_subquery_for_agg.json new file mode 100644 index 00000000..ce0bfdb0 --- /dev/null +++ b/axiom-query/configs/test/header_subquery_for_agg.json @@ -0,0 +1,39 @@ +{ + "params": { + "base": { + "k": 18, + "num_advice_per_phase": [ + 12, + 1 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 1 + }, + "break_points": { + "base": [ + [ + 262134, + 262134, + 262133, + 262133, + 262133, + 262134, + 262132, + 262134, + 262133, + 262134, + 262133 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-query/configs/test/header_subquery_loader_params.json b/axiom-query/configs/test/header_subquery_loader_params.json new file mode 100644 index 00000000..e9fff93e --- /dev/null +++ b/axiom-query/configs/test/header_subquery_loader_params.json @@ -0,0 +1,8 @@ +{ + "comp_loader_params": { + "max_height": 3, + "shard_caps": [ + 200 + ] + } +} \ No newline at end of file diff --git a/axiom-query/configs/test/keccak_for_agg.json b/axiom-query/configs/test/keccak_for_agg.json new file mode 100644 index 00000000..d379ae41 --- /dev/null +++ b/axiom-query/configs/test/keccak_for_agg.json @@ -0,0 +1,29 @@ +{ + "params": { + "degree": 20, + "num_advice": 17, + "num_lookup_advice": 2, + "num_fixed": 1, + "lookup_bits": 19 + }, + "break_points": [ + [ + 1048565, + 1048566, + 1048565, + 1048566, + 1048565, + 1048565, + 1048565, + 1048564, + 1048566, + 1048565, + 1048566, + 1048566, + 1048565, + 1048566, + 1048565, + 1048564 + ] + ] +} \ No newline at end of file diff --git a/axiom-query/configs/test/results_root.json b/axiom-query/configs/test/results_root.json new file mode 100644 index 00000000..104bc10d --- /dev/null +++ b/axiom-query/configs/test/results_root.json @@ -0,0 +1,59 @@ +{ + "params": { + "base": { + "k": 18, + "num_advice_per_phase": [ + 32, + 1 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 1 + }, + "break_points": { + "base": [ + [ + 262134, + 262134, + 262133, + 262132, + 262134, + 262134, + 262132, + 262134, + 262132, + 262134, + 262132, + 262132, + 262134, + 262132, + 262134, + 262132, + 262134, + 262132, + 262134, + 262132, + 262134, + 262134, + 262134, + 262132, + 262134, + 262133, + 262133, + 262132, + 262133, + 262133, + 262134 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-query/configs/test/results_root_for_agg.json b/axiom-query/configs/test/results_root_for_agg.json new file mode 100644 index 00000000..e1cb6209 --- /dev/null +++ b/axiom-query/configs/test/results_root_for_agg.json @@ -0,0 +1,38 @@ +{ + "params": { + "base": { + "k": 18, + "num_advice_per_phase": [ + 11, + 1 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 8, + "num_instance_columns": 1 + }, + "num_rlc_columns": 1 + }, + "break_points": { + "base": [ + [ + 262134, + 262134, + 262133, + 262133, + 262133, + 262134, + 262132, + 262134, + 262133, + 262134 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-query/configs/test/subquery_aggregation_for_agg.json b/axiom-query/configs/test/subquery_aggregation_for_agg.json new file mode 100644 index 00000000..78d0d73b --- /dev/null +++ b/axiom-query/configs/test/subquery_aggregation_for_agg.json @@ -0,0 +1,31 @@ +{ + "params": { + "degree": 20, + "num_advice": 19, + "num_lookup_advice": 3, + "num_fixed": 1, + "lookup_bits": 19 + }, + "break_points": [ + [ + 1048565, + 1048566, + 1048566, + 1048566, + 1048564, + 1048565, + 1048566, + 1048565, + 1048566, + 1048565, + 1048564, + 1048566, + 1048564, + 1048566, + 1048565, + 1048564, + 1048566, + 1048566 + ] + ] +} \ No newline at end of file diff --git a/axiom-query/configs/test/verify_compute_for_agg.json b/axiom-query/configs/test/verify_compute_for_agg.json new file mode 100644 index 00000000..14ea0c43 --- /dev/null +++ b/axiom-query/configs/test/verify_compute_for_agg.json @@ -0,0 +1,46 @@ +{ + "params": { + "base": { + "k": 19, + "num_advice_per_phase": [ + 19, + 1 + ], + "num_fixed": 1, + "num_lookup_advice_per_phase": [ + 1, + 1, + 0 + ], + "lookup_bits": 18, + "num_instance_columns": 1 + }, + "num_rlc_columns": 1 + }, + "break_points": { + "base": [ + [ + 524278, + 524278, + 524278, + 524277, + 524277, + 524278, + 524277, + 524276, + 524277, + 524278, + 524277, + 524278, + 524276, + 524278, + 524277, + 524277, + 524276, + 524277 + ], + [] + ], + "rlc": [] + } +} \ No newline at end of file diff --git a/axiom-query/data/production/aggregate_vk_hashes.json b/axiom-query/data/production/aggregate_vk_hashes.json new file mode 100644 index 00000000..bf78f382 --- /dev/null +++ b/axiom-query/data/production/aggregate_vk_hashes.json @@ -0,0 +1,7 @@ +[ + "0x0101065876114de866aa867d320a1be1bd455dc47cd86d58792a2fcd625f508a", + "0x295c9bfd347ba130992af3febdb211441fb6ea8399dd1b35bdaa7414c7b1fec8", + "0x1ccbfbc12ad4576cd2d3c29edd3fd3a4e12383c722f0eae3b8e25273045a2ec8", + "0x2f6fd2b234c2508f8572c3e85a6cf57c0b8d15f3df411ed7ac1deeada4744072", + "0x07a8cc51ad5eb676458a910857f638cda68cadaddecdc4ba5517217acd0fbbc3" +] diff --git a/axiom-query/data/production/all_128_each_default.tree b/axiom-query/data/production/all_128_each_default.tree new file mode 100644 index 00000000..71a70486 --- /dev/null +++ b/axiom-query/data/production/all_128_each_default.tree @@ -0,0 +1,765 @@ +{ + "circuit_id": "e94efbee3e07ae4224ed1ae0a6389f5128d210ff7a2a743e459cff501e4379ab", + "children": [ + { + "circuit_id": "19aa4713ce9f8636a856bcfb0b4b79ccfe922ecfed1e6a1877925fd535459ffd", + "children": [ + { + "circuit_id": "d0608445d7b3d8201db37ec00ac2fe66ee13b27bad3f2308f4e40e12d21a991f", + "children": [] + }, + { + "circuit_id": "f49ec5e1658e99d94f66bbfdb8845f15a2ffd2ac255730089855430c7a0bed92", + "children": [ + { + "circuit_id": "c791574d317d345d95496e098d7ced2939cc30497b6dfeadf1e9347a4f1e8930", + "children": [ + { + "circuit_id": "68fa1e3d173287d0e6476d10d48f1f67eaa1ad8e098a856812fc33522248e3f7", + "children": [] + }, + { + "circuit_id": "68fa1e3d173287d0e6476d10d48f1f67eaa1ad8e098a856812fc33522248e3f7", + "children": [] + } + ] + }, + { + "circuit_id": "862dbd1b215196c1e84d5df52851d7b6f9984b159dfffe22b592b18b2fbebc78", + "children": [ + { + "circuit_id": "c2977ff6a6fc44582614fbc648dda84da00363f084d60192f0dec28dbfaa5026", + "children": [ + { + "circuit_id": "4aea5c9c1c96a30baa11ac9bc5b6225ec9a3c03483c12962884130741cdb6121", + "children": [] + }, + { + "circuit_id": "4aea5c9c1c96a30baa11ac9bc5b6225ec9a3c03483c12962884130741cdb6121", + "children": [] + } + ] + }, + { + "circuit_id": "c2977ff6a6fc44582614fbc648dda84da00363f084d60192f0dec28dbfaa5026", + "children": [ + { + "circuit_id": "4aea5c9c1c96a30baa11ac9bc5b6225ec9a3c03483c12962884130741cdb6121", + "children": [] + }, + { + "circuit_id": "4aea5c9c1c96a30baa11ac9bc5b6225ec9a3c03483c12962884130741cdb6121", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "4adc13fe18564761b3f3f862417928d7a68636eab4d1f948014eec5373821be3", + "children": [ + { + "circuit_id": "f738f5dbc54c9f9f92e70515dfbad4aea8573096e9603a8dfbdbe30e44f9a52b", + "children": [ + { + "circuit_id": "bfa03665bfee45169ab1388abcebfa03c3a8de9b2346cd6f66f7aa59822813f9", + "children": [] + }, + { + "circuit_id": "bfa03665bfee45169ab1388abcebfa03c3a8de9b2346cd6f66f7aa59822813f9", + "children": [] + } + ] + }, + { + "circuit_id": "f738f5dbc54c9f9f92e70515dfbad4aea8573096e9603a8dfbdbe30e44f9a52b", + "children": [ + { + "circuit_id": "bfa03665bfee45169ab1388abcebfa03c3a8de9b2346cd6f66f7aa59822813f9", + "children": [] + }, + { + "circuit_id": "bfa03665bfee45169ab1388abcebfa03c3a8de9b2346cd6f66f7aa59822813f9", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "d1c0d3ab3a2cfd5113c93adc78e9549de6e05b2d77f7925dedbea1b897649bba", + "children": [ + { + "circuit_id": "57740dc422b1da9f01b36e389647847cdc3d58b489c81d9b815d7d1a53009a30", + "children": [ + { + "circuit_id": "21ccfabcbb637b83a5b5907e91dfb60987db9fc401ca1f6cd7f1316a556aa3de", + "children": [ + { + "circuit_id": "9588ac5eafb1427ba492747b81bb907bb9e45a7fd069ceb22478ed4feca61c7f", + "children": [] + }, + { + "circuit_id": "9588ac5eafb1427ba492747b81bb907bb9e45a7fd069ceb22478ed4feca61c7f", + "children": [] + } + ] + }, + { + "circuit_id": "21ccfabcbb637b83a5b5907e91dfb60987db9fc401ca1f6cd7f1316a556aa3de", + "children": [ + { + "circuit_id": "9588ac5eafb1427ba492747b81bb907bb9e45a7fd069ceb22478ed4feca61c7f", + "children": [] + }, + { + "circuit_id": "9588ac5eafb1427ba492747b81bb907bb9e45a7fd069ceb22478ed4feca61c7f", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "57740dc422b1da9f01b36e389647847cdc3d58b489c81d9b815d7d1a53009a30", + "children": [ + { + "circuit_id": "21ccfabcbb637b83a5b5907e91dfb60987db9fc401ca1f6cd7f1316a556aa3de", + "children": [ + { + "circuit_id": "9588ac5eafb1427ba492747b81bb907bb9e45a7fd069ceb22478ed4feca61c7f", + "children": [] + }, + { + "circuit_id": "9588ac5eafb1427ba492747b81bb907bb9e45a7fd069ceb22478ed4feca61c7f", + "children": [] + } + ] + }, + { + "circuit_id": "21ccfabcbb637b83a5b5907e91dfb60987db9fc401ca1f6cd7f1316a556aa3de", + "children": [ + { + "circuit_id": "9588ac5eafb1427ba492747b81bb907bb9e45a7fd069ceb22478ed4feca61c7f", + "children": [] + }, + { + "circuit_id": "9588ac5eafb1427ba492747b81bb907bb9e45a7fd069ceb22478ed4feca61c7f", + "children": [] + } + ] + } + ] + } + ] + }, + { + "circuit_id": "12180426c7f76ed084fa53a97d0ffaa0ea35e4efb9cb581e55ac623f0b291e9d", + "children": [ + { + "circuit_id": "8a068c013567fd23094d2e786f9a500cb19757efa9e52f4ab50d95b7b6d73745", + "children": [ + { + "circuit_id": "152d334a46fe182b5212bcff40353007db451eba29b78b57fd320f864fa324a1", + "children": [ + { + "circuit_id": "b6dd8f5190b500cc30de676a55a649b84b9d02b6f006a2605e7157aa520cd85c", + "children": [ + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + }, + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "b6dd8f5190b500cc30de676a55a649b84b9d02b6f006a2605e7157aa520cd85c", + "children": [ + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + }, + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + } + ] + } + ] + }, + { + "circuit_id": "152d334a46fe182b5212bcff40353007db451eba29b78b57fd320f864fa324a1", + "children": [ + { + "circuit_id": "b6dd8f5190b500cc30de676a55a649b84b9d02b6f006a2605e7157aa520cd85c", + "children": [ + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + }, + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "b6dd8f5190b500cc30de676a55a649b84b9d02b6f006a2605e7157aa520cd85c", + "children": [ + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + }, + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + } + ] + } + ] + } + ] + }, + { + "circuit_id": "8a068c013567fd23094d2e786f9a500cb19757efa9e52f4ab50d95b7b6d73745", + "children": [ + { + "circuit_id": "152d334a46fe182b5212bcff40353007db451eba29b78b57fd320f864fa324a1", + "children": [ + { + "circuit_id": "b6dd8f5190b500cc30de676a55a649b84b9d02b6f006a2605e7157aa520cd85c", + "children": [ + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + }, + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "b6dd8f5190b500cc30de676a55a649b84b9d02b6f006a2605e7157aa520cd85c", + "children": [ + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + }, + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + } + ] + } + ] + }, + { + "circuit_id": "152d334a46fe182b5212bcff40353007db451eba29b78b57fd320f864fa324a1", + "children": [ + { + "circuit_id": "b6dd8f5190b500cc30de676a55a649b84b9d02b6f006a2605e7157aa520cd85c", + "children": [ + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + }, + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "b6dd8f5190b500cc30de676a55a649b84b9d02b6f006a2605e7157aa520cd85c", + "children": [ + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + }, + { + "circuit_id": "b5bcb9636ce300bd161f2c033084accb9e2959d7e3cf6806bbc868c68b862f96", + "children": [ + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + }, + { + "circuit_id": "5dac74b48332273170e1246933c07e736a9cf47d207e0a90980b16f1531fb962", + "children": [] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "circuit_id": "ee9c4b345a3a577fd41be13926268d51b5fb3cdf4d88b16fc7cc32d41664fe85", + "children": [ + { + "circuit_id": "ba7b6ab7b8a24c55722de2d1c7843132f072e5501c0f348847e843ff90918265", + "children": [ + { + "circuit_id": "2b5dc6f71201581c6dccb1ca79d86802edb71c76e39ee7f172c5608b3d2337d1", + "children": [] + }, + { + "circuit_id": "2b5dc6f71201581c6dccb1ca79d86802edb71c76e39ee7f172c5608b3d2337d1", + "children": [] + } + ] + }, + { + "circuit_id": "ba7b6ab7b8a24c55722de2d1c7843132f072e5501c0f348847e843ff90918265", + "children": [ + { + "circuit_id": "2b5dc6f71201581c6dccb1ca79d86802edb71c76e39ee7f172c5608b3d2337d1", + "children": [] + }, + { + "circuit_id": "2b5dc6f71201581c6dccb1ca79d86802edb71c76e39ee7f172c5608b3d2337d1", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "f398ebe5a409cf81048de56d73ff3cf2af7eda7fd375d23d5a7029f54f234874", + "children": [] + } + ], + "aggregate_vk_hash": "0x2ab229ca21794b98f6e5150468b32982d280c37a8b2f3b34fe94c6484a96ce6d" + }, + { + "circuit_id": "9e0c538abc73dde9a432608bdac5c8814d5bc97a7d8f2aaeae7c95b31e23b5cb", + "children": [ + { + "circuit_id": "ddc653048a62ac040b26de56ce6685afa3cd95b99a6d3cba3f4d23e6d71244fe", + "children": [ + { + "circuit_id": "2b933bc26fd84fd144a5b7ac29722b9a1be10cc5bbd9462d5fa60f0663c309d2", + "children": [ + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + } + ] + }, + { + "circuit_id": "2b933bc26fd84fd144a5b7ac29722b9a1be10cc5bbd9462d5fa60f0663c309d2", + "children": [ + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + } + ] + } + ] + }, + { + "circuit_id": "ddc653048a62ac040b26de56ce6685afa3cd95b99a6d3cba3f4d23e6d71244fe", + "children": [ + { + "circuit_id": "2b933bc26fd84fd144a5b7ac29722b9a1be10cc5bbd9462d5fa60f0663c309d2", + "children": [ + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + } + ] + }, + { + "circuit_id": "2b933bc26fd84fd144a5b7ac29722b9a1be10cc5bbd9462d5fa60f0663c309d2", + "children": [ + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ], + "aggregate_vk_hash": "0x2c39f3caadda3c14bc6979082ba7e07dcc42fd3dad25b41b507e757d6831e3d7" + } + ], + "aggregate_vk_hash": "0x1ccbfbc12ad4576cd2d3c29edd3fd3a4e12383c722f0eae3b8e25273045a2ec8" +} \ No newline at end of file diff --git a/axiom-query/data/production/all_32_each_default.tree b/axiom-query/data/production/all_32_each_default.tree new file mode 100644 index 00000000..f04d7a73 --- /dev/null +++ b/axiom-query/data/production/all_32_each_default.tree @@ -0,0 +1,198 @@ +{ + "circuit_id": "e94efbee3e07ae4224ed1ae0a6389f5128d210ff7a2a743e459cff501e4379ab", + "children": [ + { + "circuit_id": "8ee10f86d0fca6d65bf8e0179aca4393f85a2719ab1adb10183a6fa1380c8069", + "children": [ + { + "circuit_id": "8cfbd5e71a936153e3e998506b7bd187a431515484ab97d261943e157c2c7440", + "children": [] + }, + { + "circuit_id": "05dbc9b7fbfdc1f14f3c56642378baa2fdd4c552823804d8c0e833376dea2ba5", + "children": [ + { + "circuit_id": "948f935ff8497cef63c9084f582518d73a34b7188bc664d4c4362c1c88b56cbf", + "children": [] + }, + { + "circuit_id": "56e520cde6857a775e3a6a1069c5c8a30f1f6730c914db766cc84deba1ccc34f", + "children": [ + { + "circuit_id": "5aa0636a2b3250c4c29edb509c81429cd918d2b9403fd851a490d75379a40c9d", + "children": [ + { + "circuit_id": "982411f26ee3383312231e114d7c5ffafda6ca3d2866024e09066125e2acec7d", + "children": [] + }, + { + "circuit_id": "982411f26ee3383312231e114d7c5ffafda6ca3d2866024e09066125e2acec7d", + "children": [] + } + ] + }, + { + "circuit_id": "5aa0636a2b3250c4c29edb509c81429cd918d2b9403fd851a490d75379a40c9d", + "children": [ + { + "circuit_id": "982411f26ee3383312231e114d7c5ffafda6ca3d2866024e09066125e2acec7d", + "children": [] + }, + { + "circuit_id": "982411f26ee3383312231e114d7c5ffafda6ca3d2866024e09066125e2acec7d", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "37c2d1ea7d1cce10cf6b67e3bc12ea67e6ae9fc0d1a5e1a4b68f9c8c28cf2ff7", + "children": [ + { + "circuit_id": "7ffb243b0bcbe56646d6731d583197cfa6a3b532fc75c822d76469db47d511a7", + "children": [ + { + "circuit_id": "46504997084fc9bde63bb5153704b9a9e3decf8695944e05d4922815eea74955", + "children": [] + }, + { + "circuit_id": "46504997084fc9bde63bb5153704b9a9e3decf8695944e05d4922815eea74955", + "children": [] + } + ] + }, + { + "circuit_id": "7ffb243b0bcbe56646d6731d583197cfa6a3b532fc75c822d76469db47d511a7", + "children": [ + { + "circuit_id": "46504997084fc9bde63bb5153704b9a9e3decf8695944e05d4922815eea74955", + "children": [] + }, + { + "circuit_id": "46504997084fc9bde63bb5153704b9a9e3decf8695944e05d4922815eea74955", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "db0bf461360ba08cf877d29fd06994eccea23ad2e2f70509729195b4188bf471", + "children": [ + { + "circuit_id": "f34f4797e1d43fe394481202eabd6ddd660e94c7ac03637b3da2eaf9fb1fde32", + "children": [] + }, + { + "circuit_id": "f34f4797e1d43fe394481202eabd6ddd660e94c7ac03637b3da2eaf9fb1fde32", + "children": [] + } + ] + }, + { + "circuit_id": "34eba6e1b9b798fc38f04f5cb242b200d78829f197fbf34a7748db71650fc98a", + "children": [ + { + "circuit_id": "330339817715254a15b716473271a05480649418cf59e76c5f3473ab31025a83", + "children": [] + }, + { + "circuit_id": "330339817715254a15b716473271a05480649418cf59e76c5f3473ab31025a83", + "children": [] + } + ] + }, + { + "circuit_id": "042719b966e23c95079a095d13bba73f61288c53f16fb9e54ff2dae216b5f0cb", + "children": [ + { + "circuit_id": "58aff8fb4228bc4276c080b6d00d44ecbdee14413b92a649d78368f22fb4db57", + "children": [] + }, + { + "circuit_id": "58aff8fb4228bc4276c080b6d00d44ecbdee14413b92a649d78368f22fb4db57", + "children": [] + } + ] + }, + { + "circuit_id": "a433b4831f43d34df6eea2ca2d6a3721ff858ef0001a8014389c85d25d8c0659", + "children": [] + } + ], + "aggregate_vk_hash": "0x0329fcf00160741630cd2808f53e5e60519024ed669880745cee7d72ef2ade6a" + }, + { + "circuit_id": "2b933bc26fd84fd144a5b7ac29722b9a1be10cc5bbd9462d5fa60f0663c309d2", + "children": [ + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + } + ] + } + ], + "aggregate_vk_hash": "0x17cfa3384ef5c68df028a8d3feb9bd93b8a8e6e5d0ee495e4c848fc7ff59d267" + } + ], + "aggregate_vk_hash": "0x295c9bfd347ba130992af3febdb211441fb6ea8399dd1b35bdaa7414c7b1fec8" +} \ No newline at end of file diff --git a/axiom-query/data/production/all_large.tree b/axiom-query/data/production/all_large.tree new file mode 100644 index 00000000..528aa786 --- /dev/null +++ b/axiom-query/data/production/all_large.tree @@ -0,0 +1,369 @@ +{ + "circuit_id": "e94efbee3e07ae4224ed1ae0a6389f5128d210ff7a2a743e459cff501e4379ab", + "children": [ + { + "circuit_id": "28b27e617502ae7f94b5f8503761241a74c67f2e10606f7e28e7135740b8b43b", + "children": [ + { + "circuit_id": "33b4bbd2a9b800f478e2ee1c6e348bd0429d8bb2e73811ccbd70e894c8111ff0", + "children": [] + }, + { + "circuit_id": "19fcdeb9b58b153bf63e9be80840a09caac8032ca90f4905b6a2caf2a360792a", + "children": [ + { + "circuit_id": "319c251cf59984bbf1f595398615982d564e68fef2ef7456fffe916798860532", + "children": [ + { + "circuit_id": "c5500d2ebd454b46638fc90047273bb6a34ea25798c798a7e6adb9e84b54bfec", + "children": [] + }, + { + "circuit_id": "c5500d2ebd454b46638fc90047273bb6a34ea25798c798a7e6adb9e84b54bfec", + "children": [] + } + ] + }, + { + "circuit_id": "7ce54113e5966313d534a3ead5753efc2c84ee836234fcc8a28a3cd7c0bfed98", + "children": [ + { + "circuit_id": "b3c78e49da525a9e60b916bcb5c02cad091fec76fa0528af4aa9af94b74b9aa0", + "children": [ + { + "circuit_id": "7ef353bc6ed0ff275abcb12510a1a5dd54d997e48ffdbf2fa6861b7b1fafaa97", + "children": [] + }, + { + "circuit_id": "7ef353bc6ed0ff275abcb12510a1a5dd54d997e48ffdbf2fa6861b7b1fafaa97", + "children": [] + } + ] + }, + { + "circuit_id": "b3c78e49da525a9e60b916bcb5c02cad091fec76fa0528af4aa9af94b74b9aa0", + "children": [ + { + "circuit_id": "7ef353bc6ed0ff275abcb12510a1a5dd54d997e48ffdbf2fa6861b7b1fafaa97", + "children": [] + }, + { + "circuit_id": "7ef353bc6ed0ff275abcb12510a1a5dd54d997e48ffdbf2fa6861b7b1fafaa97", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "faee53650374b618614509ea6da5e4f2ed033684ee01d491e44bd2257760b3ac", + "children": [ + { + "circuit_id": "1c9a10422a3193f1e4bf3bb6a3a2847b3a1021fe83650cbabc3c20bb88ce5d3b", + "children": [ + { + "circuit_id": "e3bfd00ff0bc985ff712b6156b7ba5362915a8b4dfbe2833ef34b9f52fa7b0e9", + "children": [] + }, + { + "circuit_id": "e3bfd00ff0bc985ff712b6156b7ba5362915a8b4dfbe2833ef34b9f52fa7b0e9", + "children": [] + } + ] + }, + { + "circuit_id": "1c9a10422a3193f1e4bf3bb6a3a2847b3a1021fe83650cbabc3c20bb88ce5d3b", + "children": [ + { + "circuit_id": "e3bfd00ff0bc985ff712b6156b7ba5362915a8b4dfbe2833ef34b9f52fa7b0e9", + "children": [] + }, + { + "circuit_id": "e3bfd00ff0bc985ff712b6156b7ba5362915a8b4dfbe2833ef34b9f52fa7b0e9", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "faede382b179e3377ebaeb4367e51c4fc7a2c1a2280d52ae88360e7e22dcd298", + "children": [ + { + "circuit_id": "3c767d341b5ab32ffe3f8bd6f17fc77270480fcd7a1c9f34211082b342983a3a", + "children": [ + { + "circuit_id": "2bc1ebf522c7896abba6bc281260bf43224f979081f70fb425c33f221a2141ba", + "children": [] + }, + { + "circuit_id": "2bc1ebf522c7896abba6bc281260bf43224f979081f70fb425c33f221a2141ba", + "children": [] + } + ] + }, + { + "circuit_id": "3c767d341b5ab32ffe3f8bd6f17fc77270480fcd7a1c9f34211082b342983a3a", + "children": [ + { + "circuit_id": "2bc1ebf522c7896abba6bc281260bf43224f979081f70fb425c33f221a2141ba", + "children": [] + }, + { + "circuit_id": "2bc1ebf522c7896abba6bc281260bf43224f979081f70fb425c33f221a2141ba", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "12ffccb7dce195c26f6ced679f3641939caf7a0e876ea01c3f1d36b0fbe9f0e5", + "children": [ + { + "circuit_id": "884a5e0192861ab1f01f8d32f2747cf57924ceeb40657780c35312b694128dea", + "children": [ + { + "circuit_id": "b7af53e7c4f96ff2154580077c680440e9e89bc18e3f33697fac426552930833", + "children": [ + { + "circuit_id": "0bd21424559ec952dc9de23c30318b4ed8d7b0097c17cb27db3fdf776c6e4625", + "children": [] + }, + { + "circuit_id": "0bd21424559ec952dc9de23c30318b4ed8d7b0097c17cb27db3fdf776c6e4625", + "children": [] + } + ] + }, + { + "circuit_id": "b7af53e7c4f96ff2154580077c680440e9e89bc18e3f33697fac426552930833", + "children": [ + { + "circuit_id": "0bd21424559ec952dc9de23c30318b4ed8d7b0097c17cb27db3fdf776c6e4625", + "children": [] + }, + { + "circuit_id": "0bd21424559ec952dc9de23c30318b4ed8d7b0097c17cb27db3fdf776c6e4625", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "884a5e0192861ab1f01f8d32f2747cf57924ceeb40657780c35312b694128dea", + "children": [ + { + "circuit_id": "b7af53e7c4f96ff2154580077c680440e9e89bc18e3f33697fac426552930833", + "children": [ + { + "circuit_id": "0bd21424559ec952dc9de23c30318b4ed8d7b0097c17cb27db3fdf776c6e4625", + "children": [] + }, + { + "circuit_id": "0bd21424559ec952dc9de23c30318b4ed8d7b0097c17cb27db3fdf776c6e4625", + "children": [] + } + ] + }, + { + "circuit_id": "b7af53e7c4f96ff2154580077c680440e9e89bc18e3f33697fac426552930833", + "children": [ + { + "circuit_id": "0bd21424559ec952dc9de23c30318b4ed8d7b0097c17cb27db3fdf776c6e4625", + "children": [] + }, + { + "circuit_id": "0bd21424559ec952dc9de23c30318b4ed8d7b0097c17cb27db3fdf776c6e4625", + "children": [] + } + ] + } + ] + } + ] + }, + { + "circuit_id": "b9446353a3f3a327480000688f67c9eb6321a5cfbbfb50c31bda6b1b6d7e625a", + "children": [ + { + "circuit_id": "aa78f2af4841f44ecb7a6269b459f54410cfe4adaa9dabf9202f9bce76b2d85e", + "children": [ + { + "circuit_id": "a4c0467cd16a7e2c0d570e2aee365842991267461fb12b890a166a75147864de", + "children": [] + }, + { + "circuit_id": "a4c0467cd16a7e2c0d570e2aee365842991267461fb12b890a166a75147864de", + "children": [] + } + ] + }, + { + "circuit_id": "aa78f2af4841f44ecb7a6269b459f54410cfe4adaa9dabf9202f9bce76b2d85e", + "children": [ + { + "circuit_id": "a4c0467cd16a7e2c0d570e2aee365842991267461fb12b890a166a75147864de", + "children": [] + }, + { + "circuit_id": "a4c0467cd16a7e2c0d570e2aee365842991267461fb12b890a166a75147864de", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "feea885cf03e8cf31eb4fb4274acc247f437cbf1ce7be36d809e57e75b078f55", + "children": [] + } + ], + "aggregate_vk_hash": "0x27d212195f1b2d5263160a62870a92cc5a7c4824e63e2437201fdecfdfd05b9a" + }, + { + "circuit_id": "bae4cd8e3ae65c6398caa78a0b10cf38714e9b49a686c61a7f90f793f7155d65", + "children": [ + { + "circuit_id": "e3761fa90fbb5618ad2e3100dd0100f73d2a3460584452b20a1d57de3049890f", + "children": [ + { + "circuit_id": "3f099a5ee10bf8020114a5662158786d1fa73fcc60aa7b9be8cddd156da344e3", + "children": [ + { + "circuit_id": "3bd7cbbb89d5d06348be86d324dca549b7e019dcc0b077b0fc0e2ef2530ee50a", + "children": [ + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + }, + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + } + ] + }, + { + "circuit_id": "3bd7cbbb89d5d06348be86d324dca549b7e019dcc0b077b0fc0e2ef2530ee50a", + "children": [ + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + }, + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "3f099a5ee10bf8020114a5662158786d1fa73fcc60aa7b9be8cddd156da344e3", + "children": [ + { + "circuit_id": "3bd7cbbb89d5d06348be86d324dca549b7e019dcc0b077b0fc0e2ef2530ee50a", + "children": [ + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + }, + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + } + ] + }, + { + "circuit_id": "3bd7cbbb89d5d06348be86d324dca549b7e019dcc0b077b0fc0e2ef2530ee50a", + "children": [ + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + }, + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + } + ] + } + ] + } + ] + }, + { + "circuit_id": "e3761fa90fbb5618ad2e3100dd0100f73d2a3460584452b20a1d57de3049890f", + "children": [ + { + "circuit_id": "3f099a5ee10bf8020114a5662158786d1fa73fcc60aa7b9be8cddd156da344e3", + "children": [ + { + "circuit_id": "3bd7cbbb89d5d06348be86d324dca549b7e019dcc0b077b0fc0e2ef2530ee50a", + "children": [ + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + }, + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + } + ] + }, + { + "circuit_id": "3bd7cbbb89d5d06348be86d324dca549b7e019dcc0b077b0fc0e2ef2530ee50a", + "children": [ + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + }, + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "3f099a5ee10bf8020114a5662158786d1fa73fcc60aa7b9be8cddd156da344e3", + "children": [ + { + "circuit_id": "3bd7cbbb89d5d06348be86d324dca549b7e019dcc0b077b0fc0e2ef2530ee50a", + "children": [ + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + }, + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + } + ] + }, + { + "circuit_id": "3bd7cbbb89d5d06348be86d324dca549b7e019dcc0b077b0fc0e2ef2530ee50a", + "children": [ + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + }, + { + "circuit_id": "b5b5afdd9b5e245104258f7bb3266cad77513204cf13a49de3ef55a233ad123d", + "children": [] + } + ] + } + ] + } + ] + } + ] + } + ], + "aggregate_vk_hash": "0x1aa38b1dbfd4d8c0da738c020f821fb40b08e96414928ac06896fd0c482a3c27" + } + ], + "aggregate_vk_hash": "0x2f6fd2b234c2508f8572c3e85a6cf57c0b8d15f3df411ed7ac1deeada4744072" +} \ No newline at end of file diff --git a/axiom-query/data/production/all_max.tree b/axiom-query/data/production/all_max.tree new file mode 100644 index 00000000..3a25c77a --- /dev/null +++ b/axiom-query/data/production/all_max.tree @@ -0,0 +1,316 @@ +{ + "circuit_id": "e94efbee3e07ae4224ed1ae0a6389f5128d210ff7a2a743e459cff501e4379ab", + "children": [ + { + "circuit_id": "aaf2004f06b92db06ecf815585f0998265492686c07dccfcd3f64de4a20b5d75", + "children": [ + { + "circuit_id": "068ea0469095f39946198391fbe3922bbf38af01e16e15ad9414ee94d90bcba6", + "children": [] + }, + { + "circuit_id": "82e23ed3106ce397ed5992de1bc5932377743a38f757f662208bdb9bbd12f551", + "children": [ + { + "circuit_id": "1b27b8beba29e1877515de57289d6fc09945c140a3c81c1b28a6a81b6b4ce280", + "children": [ + { + "circuit_id": "6f1f29be179a848960f2e5d711b679de1a7f61addb541ccbd364ba46707bec4c", + "children": [] + }, + { + "circuit_id": "6f1f29be179a848960f2e5d711b679de1a7f61addb541ccbd364ba46707bec4c", + "children": [] + } + ] + }, + { + "circuit_id": "4f7652293244d2ba16f213685f56b78fcf96e38ded01317209aedd2ed5e26d11", + "children": [ + { + "circuit_id": "30fa0b2e0ac80815e8f6db0bd2e46a558726e55f5898d063df2b6b62278ed3f6", + "children": [ + { + "circuit_id": "f52860b9774f65757700d4c5d1cb535af33fd0aa86c7ff4259f1e25cb6dd12fd", + "children": [] + }, + { + "circuit_id": "f52860b9774f65757700d4c5d1cb535af33fd0aa86c7ff4259f1e25cb6dd12fd", + "children": [] + } + ] + }, + { + "circuit_id": "30fa0b2e0ac80815e8f6db0bd2e46a558726e55f5898d063df2b6b62278ed3f6", + "children": [ + { + "circuit_id": "f52860b9774f65757700d4c5d1cb535af33fd0aa86c7ff4259f1e25cb6dd12fd", + "children": [] + }, + { + "circuit_id": "f52860b9774f65757700d4c5d1cb535af33fd0aa86c7ff4259f1e25cb6dd12fd", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "708d992a07008f8148b26996b8bddeed6685c765d583c2b4b09ef986c3c5dd57", + "children": [ + { + "circuit_id": "bbea6439a2a44a864a1ef1ae16ca3be391ad391a8e7f3942bf50d98de67c1297", + "children": [ + { + "circuit_id": "52e2baae9d71a05cf8568e2aa3a908a8ed802348d09f24cf17e22667d087fabc", + "children": [] + }, + { + "circuit_id": "52e2baae9d71a05cf8568e2aa3a908a8ed802348d09f24cf17e22667d087fabc", + "children": [] + } + ] + }, + { + "circuit_id": "bbea6439a2a44a864a1ef1ae16ca3be391ad391a8e7f3942bf50d98de67c1297", + "children": [ + { + "circuit_id": "52e2baae9d71a05cf8568e2aa3a908a8ed802348d09f24cf17e22667d087fabc", + "children": [] + }, + { + "circuit_id": "52e2baae9d71a05cf8568e2aa3a908a8ed802348d09f24cf17e22667d087fabc", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "57337b417ce43a651631c4ae8f5d93eacbc7c6d2b37265102a28355ef2396981", + "children": [ + { + "circuit_id": "08ef7abf64edba8ab2d87509ea55523dbc436beecb2619ddf71958ccff0a93ec", + "children": [ + { + "circuit_id": "b32418a39c363d3f66e35f66f78b9ea2c229aa0c13b91c9fce5222f5eb336a9b", + "children": [] + }, + { + "circuit_id": "b32418a39c363d3f66e35f66f78b9ea2c229aa0c13b91c9fce5222f5eb336a9b", + "children": [] + } + ] + }, + { + "circuit_id": "08ef7abf64edba8ab2d87509ea55523dbc436beecb2619ddf71958ccff0a93ec", + "children": [ + { + "circuit_id": "b32418a39c363d3f66e35f66f78b9ea2c229aa0c13b91c9fce5222f5eb336a9b", + "children": [] + }, + { + "circuit_id": "b32418a39c363d3f66e35f66f78b9ea2c229aa0c13b91c9fce5222f5eb336a9b", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "5bd5d69c3ddfd79a56fc758a1bb7ddc68e095fae2692ff59f61566fd483c187a", + "children": [ + { + "circuit_id": "881a6156828dca8d2a1cb5a76721a14e9f93d6f78dfc9f9cf80f9ab701b72fb6", + "children": [ + { + "circuit_id": "c31808a3d314c9498fc53b728d8e901bd267e6c44e763a841254660d013f44a6", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "5dddd0c2f2a3dc9fbb4da1d2e59e30977132f08ed615813587e2d1cf07036b3d", + "children": [ + { + "circuit_id": "3dfb02ee54e16070ddb4478b5e50c9cd6c88fb60a7a87ec48a235d36d8aa842c", + "children": [ + { + "circuit_id": "3a23530420ebfe4b419bd9f912f801578833bb3341074815ef0d135bbb3b1b99", + "children": [] + }, + { + "circuit_id": "3a23530420ebfe4b419bd9f912f801578833bb3341074815ef0d135bbb3b1b99", + "children": [] + } + ] + }, + { + "circuit_id": "3dfb02ee54e16070ddb4478b5e50c9cd6c88fb60a7a87ec48a235d36d8aa842c", + "children": [ + { + "circuit_id": "3a23530420ebfe4b419bd9f912f801578833bb3341074815ef0d135bbb3b1b99", + "children": [] + }, + { + "circuit_id": "3a23530420ebfe4b419bd9f912f801578833bb3341074815ef0d135bbb3b1b99", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "f38b7e8a3d8ebbc1b2abdcb65547305a5365e83401ed11ba498725ee4e460b88", + "children": [] + } + ], + "aggregate_vk_hash": "0x1e8c66f5d9a172846d2b0ac24960639dfdaaec602ecdbcf8aa79f2b692ea106e" + }, + { + "circuit_id": "ddc653048a62ac040b26de56ce6685afa3cd95b99a6d3cba3f4d23e6d71244fe", + "children": [ + { + "circuit_id": "2b933bc26fd84fd144a5b7ac29722b9a1be10cc5bbd9462d5fa60f0663c309d2", + "children": [ + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + } + ] + }, + { + "circuit_id": "2b933bc26fd84fd144a5b7ac29722b9a1be10cc5bbd9462d5fa60f0663c309d2", + "children": [ + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + }, + { + "circuit_id": "42c63049ef9d26ac8d6631dbd04dc509b89b419adae5c12f77d16d6e587d2054", + "children": [ + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + }, + { + "circuit_id": "2299cde2d28d6621e0f4221488e8006429e76ef987f1fd1ca33b8be31f95cb2a", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + }, + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ] + } + ] + } + ] + } + ], + "aggregate_vk_hash": "0x2b885c5c6aa1a0c058d4c8580917746a7dd9ccd1d57ad7fdfddb80ffaa8861ca" + } + ], + "aggregate_vk_hash": "0x07a8cc51ad5eb676458a910857f638cda68cadaddecdc4ba5517217acd0fbbc3" +} \ No newline at end of file diff --git a/axiom-query/data/production/all_small.tree b/axiom-query/data/production/all_small.tree new file mode 100644 index 00000000..4aa5730a --- /dev/null +++ b/axiom-query/data/production/all_small.tree @@ -0,0 +1,59 @@ +{ + "circuit_id": "e94efbee3e07ae4224ed1ae0a6389f5128d210ff7a2a743e459cff501e4379ab", + "children": [ + { + "circuit_id": "b4d1978a47642b8e75ef96febc75d2c185553b4d7a6205ad553cd7dab058975f", + "children": [ + { + "circuit_id": "66b0c26ba69347aaf33ccbeedddb45b2546c187f4bb9889471fed75de87f39f1", + "children": [] + }, + { + "circuit_id": "e1b72c0d68965bac19f2bcee228ce79f2706b479fd18201795ee5c3d2bb5209b", + "children": [ + { + "circuit_id": "36dce255606d6725e9f1d0b2bb4d464501d92593a64a4fe103b44bc47fcbe45a", + "children": [] + }, + { + "circuit_id": "edd2aabdb850b9b37be8d023e5188bad64f719395b67555fe400256da0585bf0", + "children": [] + }, + { + "circuit_id": "5aa31d595da2f2de6d4e427c54a2404cd4732e1b2a5e96caaa3318302ce07a67", + "children": [] + }, + { + "circuit_id": "73ea602d51433cd943c6a6c1e6c44ad7898ae7f527f46644e3dcbafe0af30bfb", + "children": [] + }, + { + "circuit_id": "66d432f4e647b078f105d6c5cce306fe956bb3dafcc4e0afa4ba86369196346e", + "children": [] + }, + { + "circuit_id": "121a04b7eb32cc4a7e41483537903c8e2a0ff0715a5520a8c575a5bbb9323d06", + "children": [] + }, + { + "circuit_id": "c933a0b1f35be833896bf7a5d4c06f5de0e784f8c3198646dfe3c655d44684d5", + "children": [] + } + ], + "aggregate_vk_hash": "0x053b0dc911e5e702a54bd208a2773540ec8adbcc15f616c100acee5d543cd909" + }, + { + "circuit_id": "14b09226156c9961a1b943400e25850ae170f52f8542519cd33b76f47b6d8131", + "children": [ + { + "circuit_id": "21745f8186a53c3e64b826dbec6b31e2d3e6cdd6a0c652570f7be4ee3105dde3", + "children": [] + } + ] + } + ], + "aggregate_vk_hash": "0x136f2c238411b5a70ba1198ab2ba488f84bcfde2b1f48cbde51f63ec9327c1e7" + } + ], + "aggregate_vk_hash": "0x0101065876114de866aa867d320a1be1bd455dc47cd86d58792a2fcd625f508a" +} \ No newline at end of file diff --git a/axiom-query/data/test/axiom_aggregation1_for_agg.snark.json b/axiom-query/data/test/axiom_aggregation1_for_agg.snark.json new file mode 100644 index 00000000..d0afd5c9 --- /dev/null +++ b/axiom-query/data/test/axiom_aggregation1_for_agg.snark.json @@ -0,0 +1 @@ +{"inner":"FgAAAAAAAAAAAEAAAAAAAEGwKWiEsB9eqk4Yqqdz0iJXfmpA1Z7P8aTU90+xTWQw3r4mgxZU1k0b6RZ/NMXO/8VHOZLXD7OhEU5R5hpfyRhkORpNIFvWfDeTdBVwvZnhIEgyW4deaLHHuE7jH1dPEwAXAAAAAAAAAD2PumVP6pqldS0PKWW8Q4IvnE/rvtm5/2fXvG196sYhB0h1WkJ/cKWfqkn59UkIeWkIHInqDJ5elCw88GiL2SQ8xgadkPBgKs9Ez6A5pLigxRoUWDKXaDgr7VskH5t1LR3bKcHa6ybj6a0ry7TdlFEF/WBqI+mu9GvSQpgOZKkFVWZyRrG9Na+r0elaHOSMk3+C4OvcTPdhYKVHgUxv0G/rr43b14EV57L+QhvTOVF/92WR305UX42vm6GjM7B3Bf0pp3KCOBUydlyDWepNtKHWF8pHAO6wMaDZxq9w7zYdYRtr9jIk6Z/ck9Fnmgi30vCcgdnLAIGbDgpE9xXhGE8rZBbm4ilIw8+nSXFBPhOH2/AakzOnAPWZWi4Qkd8IE6zi66Y/cSEiQsvATSb9YCprihQDQCI6synWa0UyV1xYIdYnzkBTgEcFXj4PbmqU/BJIQYJK4QYxe8z6gMh26EA5gGOi8pxeQRjm0LHZ9ZF7msFsISfgIepudgkVCVtvbBEgjU9mHFbeeWaZh6LNJ9HKyAwxH2FyymcQBG4CTHJmkyXc7kbUzz6QdZondkz/mxb3yuR8JPFqlyMvtCCqwSFfEhZPz59hZkwjiSO3IiNJb3RvCxhu45WGp32sgP8dIhd6ZK41KpLj3dCcbeY6HpOwLh64qq/h3wbKGFsyHD9Wtxngk4Niv6djmanaUt+0ZXg9IjMk5we7Tmb6QPpe30dj0CITj5xKMsv5H3Ie1AObXjul9EKGBp56JX+EBdofQMgyywKAzCxB0J4xkl6PZ+D1j8KU2Htcs9c7LPlPM/5c639kULTWXnlyOveMq2ch5umfMrQNPU9VCnU1taAP0FjU70x02Nyz9nkPkBt098JGiIo+VGVHeGFCesv4Ho/4GxufF4WP6aB0GXZ0s53JP9FvixmsEh6DsgKGIjfDU2tSjikxImkI9UYyV5vDtRAeCnCU75Q6g1Ip3QFsM4B4XEkBAAAAAAAAABYAAAAAAAAAAwAAAAAAAAAKAAAAAAAAAAIAAAAAAAAACAAAAAAAAAADAAAAAAAAAAEAAAAAAAAAAgAAAAAAAAABAAAAAAAAAFMAAAAAAAAAGAAAAAAAAAAAAAAAGAAAAAAAAAABAAAAGAAAAAAAAAACAAAAGAAAAAAAAAADAAAAGQAAAAAAAAAAAAAAGQAAAAAAAAABAAAAGQAAAAAAAAACAAAAGQAAAAAAAAADAAAAGgAAAAAAAAAAAAAAGgAAAAAAAAABAAAAGgAAAAAAAAACAAAAGgAAAAAAAAADAAAAGwAAAAAAAAAAAAAAGwAAAAAAAAABAAAAGwAAAAAAAAACAAAAGwAAAAAAAAADAAAAHAAAAAAAAAAAAAAAHAAAAAAAAAABAAAAHAAAAAAAAAACAAAAHAAAAAAAAAADAAAAHQAAAAAAAAAAAAAAHQAAAAAAAAABAAAAHQAAAAAAAAACAAAAHQAAAAAAAAADAAAAHgAAAAAAAAAAAAAAHgAAAAAAAAABAAAAHgAAAAAAAAACAAAAHgAAAAAAAAADAAAAHwAAAAAAAAAAAAAAHwAAAAAAAAABAAAAHwAAAAAAAAACAAAAHwAAAAAAAAADAAAAIAAAAAAAAAAAAAAAIAAAAAAAAAABAAAAIAAAAAAAAAACAAAAIAAAAAAAAAADAAAAIQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAwAAAAAAAAAAAAAABAAAAAAAAAAAAAAABQAAAAAAAAAAAAAABgAAAAAAAAAAAAAABwAAAAAAAAAAAAAACAAAAAAAAAAAAAAACQAAAAAAAAAAAAAACgAAAAAAAAAAAAAAKwAAAAAAAAAAAAAACwAAAAAAAAAAAAAADAAAAAAAAAAAAAAADQAAAAAAAAAAAAAADgAAAAAAAAAAAAAADwAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAEQAAAAAAAAAAAAAAEgAAAAAAAAAAAAAAEwAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAFQAAAAAAAAAAAAAAFgAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAJAAAAAAAAAABAAAAJAAAAAAAAAD5////JQAAAAAAAAAAAAAAJQAAAAAAAAABAAAAJQAAAAAAAAD5////JgAAAAAAAAAAAAAAJgAAAAAAAAABAAAAJgAAAAAAAAD5////JwAAAAAAAAAAAAAAJwAAAAAAAAABAAAAJwAAAAAAAAD5////KAAAAAAAAAAAAAAAKAAAAAAAAAABAAAAKAAAAAAAAAD5////KQAAAAAAAAAAAAAAKQAAAAAAAAABAAAAKgAAAAAAAAAAAAAAKgAAAAAAAAABAAAAIgAAAAAAAAAAAAAAIgAAAAAAAAD/////IwAAAAAAAAAAAAAAVAAAAAAAAAAYAAAAAAAAAAAAAAAYAAAAAAAAAAEAAAAYAAAAAAAAAAIAAAAYAAAAAAAAAAMAAAAZAAAAAAAAAAAAAAAZAAAAAAAAAAEAAAAZAAAAAAAAAAIAAAAZAAAAAAAAAAMAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAEAAAAaAAAAAAAAAAIAAAAaAAAAAAAAAAMAAAAbAAAAAAAAAAAAAAAbAAAAAAAAAAEAAAAbAAAAAAAAAAIAAAAbAAAAAAAAAAMAAAAcAAAAAAAAAAAAAAAcAAAAAAAAAAEAAAAcAAAAAAAAAAIAAAAcAAAAAAAAAAMAAAAdAAAAAAAAAAAAAAAdAAAAAAAAAAEAAAAdAAAAAAAAAAIAAAAdAAAAAAAAAAMAAAAeAAAAAAAAAAAAAAAeAAAAAAAAAAEAAAAeAAAAAAAAAAIAAAAeAAAAAAAAAAMAAAAfAAAAAAAAAAAAAAAfAAAAAAAAAAEAAAAfAAAAAAAAAAIAAAAfAAAAAAAAAAMAAAAgAAAAAAAAAAAAAAAgAAAAAAAAAAEAAAAgAAAAAAAAAAIAAAAgAAAAAAAAAAMAAAAhAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAkAAAAAAAAAAEAAAAlAAAAAAAAAAAAAAAlAAAAAAAAAAEAAAAmAAAAAAAAAAAAAAAmAAAAAAAAAAEAAAAnAAAAAAAAAAAAAAAnAAAAAAAAAAEAAAAoAAAAAAAAAAAAAAAoAAAAAAAAAAEAAAApAAAAAAAAAAAAAAApAAAAAAAAAAEAAAAoAAAAAAAAAPn///8nAAAAAAAAAPn///8mAAAAAAAAAPn///8lAAAAAAAAAPn///8kAAAAAAAAAPn///8qAAAAAAAAAAAAAAAiAAAAAAAAAAAAAAAjAAAAAAAAAAAAAAAiAAAAAAAAAP////8qAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAALAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAARAAAAAAAAAAAAAAASAAAAAAAAAAAAAAATAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAVAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAArAAAAAAAAAAAAAAABAAAAAAAAAAgAAAAbAAAAAAAAAAYAAAACAAAAAgAAAAAAAAAAAAAABQAAAAUAAAACAAAAGAAAAAAAAAAAAAAABgAAAAIAAAAYAAAAAAAAAAEAAAACAAAAGAAAAAAAAAACAAAABAAAAAIAAAAYAAAAAAAAAAMAAAAGAAAAAgAAAAMAAAAAAAAAAAAAAAUAAAAFAAAAAgAAABkAAAAAAAAAAAAAAAYAAAACAAAAGQAAAAAAAAABAAAAAgAAABkAAAAAAAAAAgAAAAQAAAACAAAAGQAAAAAAAAADAAAABgAAAAIAAAAEAAAAAAAAAAAAAAAFAAAABQAAAAIAAAAaAAAAAAAAAAAAAAAGAAAAAgAAABoAAAAAAAAAAQAAAAIAAAAaAAAAAAAAAAIAAAAEAAAAAgAAABoAAAAAAAAAAwAAAAYAAAACAAAABQAAAAAAAAAAAAAABQAAAAUAAAACAAAAGwAAAAAAAAAAAAAABgAAAAIAAAAbAAAAAAAAAAEAAAACAAAAGwAAAAAAAAACAAAABAAAAAIAAAAbAAAAAAAAAAMAAAAGAAAAAgAAAAYAAAAAAAAAAAAAAAUAAAAFAAAAAgAAABwAAAAAAAAAAAAAAAYAAAACAAAAHAAAAAAAAAABAAAAAgAAABwAAAAAAAAAAgAAAAQAAAACAAAAHAAAAAAAAAADAAAABgAAAAIAAAAHAAAAAAAAAAAAAAAFAAAABQAAAAIAAAAdAAAAAAAAAAAAAAAGAAAAAgAAAB0AAAAAAAAAAQAAAAIAAAAdAAAAAAAAAAIAAAAEAAAAAgAAAB0AAAAAAAAAAwAAAAYAAAACAAAACAAAAAAAAAAAAAAABQAAAAUAAAACAAAAHgAAAAAAAAAAAAAABgAAAAIAAAAeAAAAAAAAAAEAAAACAAAAHgAAAAAAAAACAAAABAAAAAIAAAAeAAAAAAAAAAMAAAAGAAAAAgAAAAkAAAAAAAAAAAAAAAUAAAAFAAAAAgAAAB8AAAAAAAAAAAAAAAYAAAACAAAAHwAAAAAAAAABAAAAAgAAAB8AAAAAAAAAAgAAAAQAAAACAAAAHwAAAAAAAAADAAAABgAAAAIAAAAKAAAAAAAAAAAAAAAFAAAABQAAAAIAAAAgAAAAAAAAAAAAAAAGAAAAAgAAACAAAAAAAAAAAQAAAAIAAAAgAAAAAAAAAAIAAAAEAAAAAgAAACAAAAAAAAAAAwAAAAYAAAABAAAAAQAAAAAAAAAFAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAIAAAAkAAAAAAAAAAAAAAAGAAAAAQAAAAEAAAD5////BQAAAAYAAAACAAAAKQAAAAAAAAAAAAAAAgAAACkAAAAAAAAAAAAAAAQAAAACAAAAKQAAAAAAAAAAAAAABgAAAAEAAAABAAAAAAAAAAUAAAACAAAAJQAAAAAAAAAAAAAABAAAAAIAAAAkAAAAAAAAAPn///8GAAAAAQAAAAEAAAAAAAAABQAAAAIAAAAmAAAAAAAAAAAAAAAEAAAAAgAAACUAAAAAAAAA+f///wYAAAABAAAAAQAAAAAAAAAFAAAAAgAAACcAAAAAAAAAAAAAAAQAAAACAAAAJgAAAAAAAAD5////BgAAAAEAAAABAAAAAAAAAAUAAAACAAAAKAAAAAAAAAAAAAAABAAAAAIAAAAnAAAAAAAAAPn///8GAAAAAQAAAAEAAAAAAAAABQAAAAIAAAApAAAAAAAAAAAAAAAEAAAAAgAAACgAAAAAAAAA+f///wYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAAAkAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAAQAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAALAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAAYAAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAAAwAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAACQAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAABAAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAGAAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAoukz5btWDoclP5ZejolfW3FuyNSqJuxkyvDGIm5rIgkBAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAAAlAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAGQAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAANAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAAaAAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAAA4AAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAACUAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAAZAAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAArNxDfun0nPHO9gaKHe0IRwpiPA4Fg0f4V6S/o1GCzEwEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAGgAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAUj4xq5sTidFOhU28jtFb1tSSpHbgwT9nApO96SPfrxgBAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAAAmAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAGwAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAAPAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAAcAAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAABAAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAACYAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAAbAAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAABC+4rasD/Dz8HxuI/kVUHnKOJwsy+4tz7Ipq8TujahAAEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAHAAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAnWkIkL756kQ9WGR4B9nzHyWhAdKNBZLUF19Uzg11uS4BAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAAAnAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAHQAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAARAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAAeAAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAABIAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAACcAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAAdAAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAAbQnFsr2M4U3LluNu4dr5N1VF8eCK3ev/6jElvkw5wDAEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAHgAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAS0hg2BrMj6FjByaMt1naWeNn9soexvLrRi7mtwaVgiABAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAAAoAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAHwAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAATAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAAgAAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAABQAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAACgAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAAfAAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAAn5TBIsH5NStIzBSS+EDLLioLaBlnTMOOp6oSDQSByEgEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAIAAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAFOAujNwn9ZgMFqDjXi6h/seM1vkURQrMkV7D9vgJRS4BAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAAApAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAIQAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAAVAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAAXAAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAABYAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAACkAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAAhAAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAAHZhgcDfiby3j6OKvK320SbAqjNEgcjfGiQBBxOFIIIwEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAFwAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAA4MAjESpKAW+jY3rCE49V6RziSZIa0RzJe5UqUyRkyREBAAAAAAAAAAMAAAACAAAAAAAAAAYAAAABAAAAAQAAAAAAAAAFAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAIAAAAqAAAAAAAAAAAAAAAGAAAAAQAAAAEAAAD5////BQAAAAYAAAACAAAAKgAAAAAAAAAAAAAAAgAAACoAAAAAAAAAAAAAAAQAAAACAAAAKgAAAAAAAAAAAAAABgAAAAUAAAAFAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAEAAAABAAAA+f///wQAAAAFAAAABQAAAAUAAAAFAAAABQAAAAEAAAABAAAA+v///wEAAAABAAAA+////wEAAAABAAAA/P///wEAAAABAAAA/f///wEAAAABAAAA/v///wEAAAABAAAA/////wUAAAAGAAAABgAAAAIAAAAqAAAAAAAAAAEAAAAFAAAAAgAAACIAAAAAAAAAAAAAAAMAAAABAAAAAAAAAAUAAAACAAAAIwAAAAAAAAAAAAAAAwAAAAIAAAAAAAAABAAAAAYAAAAGAAAAAgAAACoAAAAAAAAAAAAAAAUAAAAIAAAAAQAAAAAAAAACAAAAIQAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAwAAAAEAAAAAAAAABQAAAAgAAAABAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAGAAAAAQAAAAEAAAAAAAAABQAAAAIAAAAiAAAAAAAAAAAAAAAEAAAAAgAAACMAAAAAAAAAAAAAAAYAAAAGAAAABQAAAAUAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAQAAAAEAAAD5////BAAAAAUAAAAFAAAABQAAAAUAAAAFAAAAAQAAAAEAAAD6////AQAAAAEAAAD7////AQAAAAEAAAD8////AQAAAAEAAAD9////AQAAAAEAAAD+////AQAAAAEAAAD/////BQAAAAIAAAAiAAAAAAAAAAAAAAAEAAAAAgAAACMAAAAAAAAAAAAAAAUAAAACAAAAIgAAAAAAAAAAAAAABAAAAAIAAAAiAAAAAAAAAP////8DAAAAAwAAAAAAAAABP3v50FmOsef2XsidQhNZ7iTM4wiEFkwyck0RXaZ7MSgAAAEAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAACwAAAAAAAAABAAAAAAAAABYAAAAAAAAAWCXHDGzEOL5rWJEAAAAAAAAAAAAAAAAAAAAAAAAAAAC6Xif+4+q3JUvyyAAAAAAAAAAAAAAAAAAAAAAAAAAAAOblNHIw3ID0sykAAAAAAAAAAAAAAAAAAAAAAAAAAAAA29ufJGljxEZSgl4AAAAAAAAAAAAAAAAAAAAAAAAAAADxTgpFFtL/Pm1lgwAAAAAAAAAAAAAAAAAAAAAAAAAAAOu0pzhyiI4CgSQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmU8xCoDcHdRQ+n0AAAAAAAAAAAAAAAAAAAAAAAAAAAANIqpgRDzQ26+Z2wAAAAAAAAAAAAAAAAAAAAAAAAAAACOEM+0OVxvDoQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAduJ52IOKL05Wc5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAF0gEoofwJeQli4gAAAAAAAAAAAAAAAAAAAAAAAAAAAG08jGHpqusCwBMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmwQcs/eysPnDCxaZpefRjAAAAAAAAAAAAAAAAAAAAABIo34zgNedbINrC7t8QLEAAAAAAAAAAAAAAAAAAAAAAD0Q2BWn6hxdw34nOvkGjswAAAAAAAAAAAAAAAAAAAAAkWO+s2HBNzISCbQXteWQyAAAAAAAAAAAAAAAAAAAAAAlfyxQG2OUovVnsgh88Vi0AAAAAAAAAAAAAAAAAAAAAOUKUIeiV/ReeAKsJMa2CUQAAAAAAAAAAAAAAAAAAAACsYWP9ABWbsxZgwJImHBu5AAAAAAAAAAAAAAAAAAAAAPHDKvwe6JUUpuMI3HjwTc4AAAAAAAAAAAAAAAAAAAAAdghdtWkHYT1DuqLEGFXqZPJVy2htttlx2BzpQ1uFGCeADQAAAAAAAJSbmaIE+vL89Oegq602L3rlUy4n1xEjDHV73pq9I9oZbTJwEQkaTSsTnIcwSGroDTw9xoMO25vtJUUmVh0JPHDWDKhBDHFDnz5Z0l0zxQpcFDvCtHifc12auwreOZ+IWfl057BuiNbbNPkeEiSonE5NUMQMz8UoXlqkHw+AWNIVBbxdmMhNELyo02gKNrkw/HXG5XN9SdhDhhusmqRN/w3YQwEWkLBhIBWY67Hu7RBZyYR5ZiJuCEDIWQKDe5QvSS5ZmLXcoIZZrVFcy7ObOtsT2aygKD+IZPok//vm4Iko0YeZsmZYMo9zEQvv3cmMTBdPHIM1MCKkFsxzMRI+2AZTEan5UtAb7POsU6AV66NyWrVSdKeQYG+PSgf4sg7tGqhuo+90YoCda/q4qJW8Xc400T03V985oPUdlUVRRuEQ0e+m0PgnM0YKe6TfBZ9hzw/1ddz0uXHTW5lPYSmr4mXrAw6+5JAvtCoZnNrF0BosBsxtRpEfinpIsVi0VXfqLLFlRz/Sqzxvp2bpNoUertIM3yNQm1evjzzBvhUO13pHTe6d25fW0AE1uvWsGt1KQbo2GCWufGtj75HoHX6oYAW+kNHbhGhJMm672SW7d8QqcWhjdHcXiCzdvle2p+GBKg3qx5P/9L9dTXkRLFTTvjucUe2qFt+TBX8QdCZz8wRnN58g6Kr2ASxsUmNvI0AefmwH006PZi8wvCZyplePGgNYkydiyIssLnZP/BOwuZTlsNcHJqB4w5XiiNsMVws9BE41UjfCnH1X5YLbKNAW0olQ7HWvahgpwC2UCf0FNyRgKUTWK2O91SgoOStgl3hDRifSf80BebYYx2GBFRCtWEQ1cg0zWJxkVcSUObQU7MiZ5QgauxOGFAMYwUuoRyXOLzCZ2GGVLkig26HeUuB++0CukOzUBqCikd1YU9zUhqpeViLoEBwFYvkUzScvl4AfUUyWIDOApz1hsrsEzDpsmgncoyoWr/ddjFQhdN/bjgWWziZ4sSOk4LXDfxW0NKE9CCWUXuKcwPGeuW/y/VVPoDQ2uY3W4hKwVjiwXRwx50QuLjhkY0/XORCC2Ldi8ygDuoBbWLL27VtHxFYchTYoZB6++pzzhgNirtRcMbz2u7rX2YPi9+2mrgK8gGFUCsJZAVOMd5UBMAUt5nbM2qETFY7nLoEpyAH2ffwp/Nooey0SIySXojfslam9kNGdcYKARVa4Qz4J6xcHDBnSjZ6tfCNXTfOsd+JMuAH6uo1a4QyUoULYaiOcLFZzVZFeH8zUC9wh9aMzsouebBXV15YSfQmysRSkXoC4LTMHwRKsMLEY3fr5OcdEAnGwepD/i/id8Ww9UjZwDXEiNJaAPTB7FRzJkitMShsF4c+1UEEToleFiPGQTZiXY9inyagESI3FJj8+Xxl/yQb022RjW7GJdoshdc5BQSb7fbkQnaml73seZCuaIKmG3rjWMaR7bPzK/9LZkJNqkD7v1c/mGYkqqASsUX4O8mPayC3vppsVmIDoBfscWYzihQqnjdAlmOEwGqcXlIGrHOsBSfE8aA5IzBI2Gful4DuyUPgYqw1zWgIsYR5djXsI1dy9FMN87awdz7lsbrq/P36x951/ueDvJiwwP1BhWpkA8PUe8pOVyzwivs/WRE74Np+EHaOwEuf7KD9kGWV4FWkspFpK6kxYHEYizxfplEfhA3dJY3smj84ZZ/pcls9TIA5ZfZEpSieoUuWUtnC8g3ozjoEAFOTeyRCxi0kGU5O3cGaBgdA0rZUBmuZxBgmrmylDUbxJAfDQIqsXY9RGfIJh07It/bwmFgCogb4h0zClr/SPNFDNROIJKg92UsTY4+nYaMGM0EWp6sp989YhnpOKcjm62zGs5Ctxo69LD01n8wkH+ZZp995btbv1ym8C8/Hpxi6FkC+eLJBGPfmkyWk1cT3s3XGa7IjvVolXUFsEDxvJffhwqKYseyd6Vawewg5BHklmETWwXnXF/lRMQlKk1tpY4FXTDCFV0CENWd3gTFalyBCBdsWMPUS6puiLSXJyPrrT5FeSLnXzPSwXc8U0TGQCJDcyaIQ0gSzRa6t3qU9oZ9PgukwqwMpF1E2UL2wIST4k7pyPMbvlNHnUxz81pRLhbpAepyB4pS+O6sHi+DPEQV+j3kAYW+kxy2/11oqpqWXZaU+qEGgaRRNpoAefq7L48gajwW6EToiD4p1v+1Ll1yDXTQcLoP4jtBnDuMPZf3DR3Km4OmrAZjeq7NWTtXigvyeBMBVOVsuGwcSokg8OnyqjgwOHUKpZExIsn/6Gxbw1SjdMGkcVrAtiQvz5YRrBADg/Ngvmxyqa8f1IIVfNzdrJ0wQNNA4N9FuXfl2uPB3d++U8z6+SVdlKfCNR1Vk4oO+M2inuhtzEt/g4VNU0Zp+yCKnh+fXQHv5aC3Mjex2SgKULKUChstZz/Gg46Iv/5dgNqA0rfZgpUDe5RMLNLe2gPN4Zo5lSL35jG/C3kauzoAmhWQYRF0KRfaevrwRIyP5v0AEP1uZH6AX3o2lOGQ57NS3HIEPPLNgCa0sZWPDufaXKBoTi0sBar0eTDYD4Qvsp+r5HtG8XQANIz7Tdrxo/EUsbHHzNzzsr0nTfDt6qhcXr0/06l2DcMeFMP2Q6WlEFyhQ98lhKQrrrjr1+shIBKLO4wyPgSZfu+mOVayR/xO8bCaXrh5Poh+jEYICSFXr81S8gwdqJTNGBvei3EO5fQQQOTUu3bAv1sYWRxze3HEKfKKNhLfmrnJQvRGZDn9KsAiKYocR0/KEPFiwhdxdCDTa2uVgPjx9Z9IeYKsi44pxyFxcgz1/q7qXL4S/QEwfbAg2XkcjU9eL9oqeCO93wq1kdIw2ZmJe5xZ+y/s6FY+yuGKnN4hCtdj8/ufg5UBy2mBNFudWR7DyPvCFDt3YvT0Zrb4M7wJgClbe3U/H/7JCvEcUO9zUMKw546GJ+Zcg79uC5tWdBJCKnR61bIn0j+G8iewly3wEoq6qWS+J8YPqhkAefAXMn1iEeU9bdWa0QTRc5NrYfu/ZfXZFiqUi3P92eQZkLmrYy+2iVesuOEmCoGmZNLHg7czkQ1Pd/ppufWDZsakVjt0PK6haTpwwmjK0e0JCOEIGndOUGVE3fUhiSpqpr926SlbN9dsA+EpxbbxKv/oP4gLGpcoUQOqdavwzUmaHkCT8Hwfge1MsE2EdnK8NOk2cvQT48MD/t0U7OE70ewsk/lpHeSf98Sa8LO/cITQpmBiWVMxWQO5L9d0d++a/0siva8rj5xW3ga2A0WB6BzJy7k7reF8q7Q1vC/AsKiVJehqsMBmJjqoSJZUy+AZIRF/WJ1FBQWvS+X/fyYtVMvU/Ueo+mleQKm3X5XqgD+kq2Jm6ZrAm4zD7l3+CNtKv3Nh80yNc6dV68nom0whCVvBd79g8PjyTlP2HbMCUHqA8O32fUeI+j7yC0wDcmDklJcO59Y+9PmGVmBwaBThxi1RrlwM77jdmuy93Wo34WaV8/dMpySLYmvdrvew2p58z3Oww6+5PLoaMz6owQPxcZurgPnTHBKCKJqDhpul4r5OXhx7Xr8HvQb2mEDmYMAbtvLcmCp8PLowGo3opuRLWhQgv4lL5AN7M1LVtnmTgN18giQedrL5bqPwxQVOS9QpWlLSxIIvOba2whYTZ4KAiodMFBD/1QsrLtCdnA3u5snUcwsNJ/IZdRFsEHrqgyJmh91RVs7pYazDxcXTOrtUjOtoqQssucjdnMMNA8RKInCwi21FwH0lGZGWjSTjOIEoqLoHWodc+UsgsnbB/+cQ7WYWySuaaGG6Ll8fgruesEB3XP3uDanCXiBJT5GvvOBC/Au8e1VM6CoYDB5kWT3Hv80YaohBaFTQMTZxx0Cb8TC9RHqYtgIiTRmMEO7noQL6tZr8MqcSThTMzbzjkGtgbagPVKTgjTjWtkw8iPNh/Dmmx0pNAeBXUu3Hu2chOBD2L5Ter7Neq5lRypT8s8OQIOgjWDbKdwlb6hFkXI5VwccFKDhmZzsD/PEhXpyJR/O06e45DzLyxvUsrMWbYpIyFkGn+KpwtHGwAqPAiXqrSFF7gF5KfQ7nvMt4TGl7BaLFRQgemp9qO9VvB8QGSan9oX1WeolICqkDXvBxg/kYQnZhSV0os23htHD2JSuMcYK55ItNvpcRuOli85u4U69hwGd+C3Q+t8ywhhv5yveXJslcwRkBokGil8ivz380/vLM1+VxAF47L38Drp3SdfGyS057+oM5+0YrSmDih96Ukab8Qjix2HgR8tKkVcpEEU3XjWo3xIVhejrzSXmq2qeBTri3Adj01LbkoSbImfd6BJ2mKtGFOIsZLi49szgUyLJln1UjdSExdDLUuXmaNY+Ky9VPLSU2SgTzEzP5jwOXAfUdEWsXxrh0Aa3nM7KvihNEI0lqImT5oHxVhetCKgNxrM82vszzG236mOxSsUugFgCH8w9A2CfMFdF9GTLI1pJqDlI1lYyz4ly/IdCYQBiap4BwetmfL0gnLz8YoMTAkrQi9wezDiiomuWeLx/8A86b/7zmlJTyJZydOEcog/8gMNxNIu2qbErDKopeFv88EidHLaaewn9UFPWxmXlwIkBA==","aggVkHashIdx":9} \ No newline at end of file diff --git a/axiom-query/data/test/input_header_for_agg.json b/axiom-query/data/test/input_header_for_agg.json new file mode 100644 index 00000000..65886b56 --- /dev/null +++ b/axiom-query/data/test/input_header_for_agg.json @@ -0,0 +1,2157 @@ +{ + "mmr": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x348bab8f4d2acdd4022f4b7becb8ba36a260bd9f699aa4df0a0660dc772ddcfc", + "0xfff5804d8cd309c79ff26f19ecc6a9cef13556c9d5ddb18afae277d71ad7d411", + "0x4f1a2ab29b70f47ff5c88dc7048bb23fcc4a59fbcc3b9f0e9529297afb28cb8a", + "0x995310788441a017c79a1fc0edab3074e6d5495712b35e120c446301b8b60d8c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xc4933b4a3d6a41bf6c8009e24436a19e3e91a6f4198a0f498ade1179d2978500", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xdff0f5a47ed23e4dd682fa5b20c861c8d616f0124bf3366def9f620e1569ade6", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x4755084f1c80a73395852c7ede586dddb3b8db7828e4ad2cf3259f693ac4f875", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "requests": [ + { + "header_rlp": [ + 249, + 2, + 24, + 160, + 216, + 95, + 50, + 4, + 112, + 10, + 121, + 104, + 27, + 83, + 253, + 131, + 4, + 164, + 65, + 65, + 189, + 42, + 242, + 254, + 68, + 178, + 38, + 90, + 237, + 99, + 108, + 22, + 237, + 111, + 226, + 35, + 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, + 36, + 155, + 219, + 68, + 153, + 189, + 124, + 104, + 54, + 100, + 193, + 73, + 39, + 108, + 29, + 134, + 16, + 142, + 33, + 55, + 160, + 156, + 228, + 86, + 29, + 250, + 219, + 79, + 176, + 34, + 222, + 188, + 124, + 1, + 49, + 65, + 163, + 106, + 35, + 212, + 103, + 244, + 38, + 140, + 14, + 98, + 10, + 123, + 186, + 5, + 177, + 116, + 248, + 160, + 236, + 76, + 142, + 192, + 34, + 129, + 193, + 150, + 227, + 248, + 130, + 180, + 6, + 28, + 5, + 244, + 240, + 132, + 58, + 14, + 175, + 114, + 163, + 238, + 71, + 21, + 7, + 114, + 32, + 147, + 75, + 188, + 160, + 13, + 254, + 202, + 181, + 217, + 122, + 77, + 163, + 97, + 185, + 44, + 41, + 21, + 124, + 214, + 165, + 8, + 155, + 85, + 250, + 206, + 224, + 7, + 238, + 161, + 96, + 5, + 191, + 168, + 216, + 38, + 148, + 185, + 1, + 0, + 25, + 130, + 124, + 128, + 145, + 88, + 164, + 154, + 65, + 140, + 34, + 195, + 42, + 197, + 16, + 176, + 97, + 232, + 128, + 112, + 116, + 65, + 32, + 20, + 161, + 10, + 0, + 0, + 38, + 12, + 36, + 231, + 42, + 74, + 3, + 69, + 176, + 142, + 12, + 80, + 128, + 81, + 88, + 3, + 2, + 19, + 3, + 142, + 146, + 5, + 36, + 76, + 81, + 0, + 183, + 34, + 48, + 104, + 1, + 142, + 241, + 100, + 128, + 9, + 54, + 116, + 0, + 100, + 214, + 66, + 130, + 35, + 212, + 21, + 16, + 78, + 188, + 193, + 161, + 101, + 102, + 32, + 100, + 36, + 167, + 7, + 208, + 81, + 68, + 129, + 166, + 0, + 128, + 9, + 10, + 52, + 94, + 0, + 7, + 36, + 70, + 208, + 0, + 33, + 1, + 41, + 197, + 140, + 19, + 138, + 88, + 34, + 172, + 104, + 98, + 6, + 24, + 231, + 172, + 170, + 205, + 52, + 175, + 22, + 85, + 52, + 58, + 202, + 51, + 64, + 17, + 208, + 64, + 99, + 162, + 146, + 3, + 165, + 162, + 34, + 156, + 68, + 64, + 122, + 150, + 129, + 237, + 58, + 96, + 150, + 3, + 48, + 112, + 98, + 1, + 118, + 74, + 56, + 42, + 23, + 14, + 140, + 32, + 129, + 0, + 5, + 236, + 0, + 0, + 120, + 193, + 149, + 137, + 207, + 208, + 128, + 51, + 80, + 128, + 21, + 225, + 74, + 5, + 48, + 34, + 137, + 11, + 78, + 96, + 178, + 10, + 81, + 52, + 144, + 36, + 134, + 107, + 12, + 0, + 167, + 0, + 91, + 49, + 65, + 6, + 115, + 198, + 197, + 20, + 72, + 114, + 242, + 132, + 185, + 230, + 18, + 188, + 24, + 0, + 24, + 209, + 82, + 241, + 66, + 128, + 27, + 165, + 39, + 128, + 163, + 210, + 8, + 13, + 144, + 236, + 150, + 5, + 198, + 251, + 207, + 73, + 52, + 1, + 145, + 146, + 68, + 48, + 20, + 68, + 8, + 150, + 182, + 79, + 80, + 77, + 248, + 135, + 7, + 242, + 109, + 127, + 128, + 201, + 162, + 131, + 145, + 101, + 237, + 131, + 152, + 36, + 48, + 131, + 152, + 17, + 146, + 132, + 94, + 80, + 64, + 41, + 151, + 67, + 114, + 117, + 120, + 112, + 111, + 111, + 108, + 32, + 80, + 80, + 83, + 47, + 110, + 105, + 99, + 101, + 104, + 97, + 115, + 104, + 45, + 49, + 160, + 23, + 76, + 110, + 69, + 220, + 100, + 249, + 186, + 143, + 248, + 63, + 99, + 234, + 201, + 165, + 31, + 214, + 2, + 218, + 81, + 24, + 0, + 54, + 137, + 174, + 148, + 73, + 134, + 163, + 9, + 214, + 130, + 136, + 142, + 224, + 18, + 50, + 29, + 215, + 108, + 211, + 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 + ], + "mmr_proof": [ + "0xd85f3204700a79681b53fd8304a44141bd2af2fe44b2265aed636c16ed6fe223", + "0x250018a435d17b5473e05f884ea1af54be447730a201fd72a707da51a820345c", + "0x19e99f81e6a4933a1dd41191733bd7b11ee9dbdf74f3a508e04514eb5053a57e", + "0x5a58cfc592d45892857d908e3c8c2da8c40b1cab2d4c941546ed9232114d1b37", + "0x752448ae955fe7ab9d28a97fb104fccca709d7ef6c73fd814ae3fefb5d211d7b", + "0xbb33fff29f3094bd9de4d3668fd20f2a45f8028c0c532f4e10b2368ab5c783aa", + "0xf99a2e4fdc253c869f26e9e13e8794c41a3bebb7498fe90513ff1996378db3d2", + "0x1eeaaf21efadf32e39e4db63eedb3b791e5586e373bcdc783c42ea6a8028b489", + "0x82dd59d4c6cb74831ca6eebc0b9beb938a7c0e33e000c160264520780daecd87", + "0x791c7933517944067f8dcf8cdbb4bf9c634d07c0f1f91ad3648920bc29a6884d", + "0xf3d9b0595399d791bb7b2e91630175518483c8c517d1097e4b67b0ae78c1bb21", + "0x82c76ea3624a6f4e2d62c5375b3dc0a86bf69810715bc7fddebc92937cd4b0e8", + "0x1b7b0f134de7422015a5421b9d4c0fbd370493f06144b53b5ca3ebf419756f00", + "0xa3e48aa8f6637e4a196bf2e5c51d63b766cee11645a1e2cb20e38fd6c2f67616", + "0xcea8ad8ff6608360c6c43b229e482664a5ce0f23090fab77acd12803c6094c03", + "0x223b4e206927956a87b2bd3be0b39f6176172c13d773b99e11b4d0ac8c1307d4", + "0x5079605c269d43ce5a544d19e7832911a862f74b0df14d7b604232869b39ed78", + "0xfb07e7779638fe0e542e6a55e3e4068d1a0a71e178637553e2abe5177c3a3d7d", + "0x93071e157ae565f7dbfea93c3da2e03caf192a8d97e1e4f752cad4b03dad40a8", + "0xb2838c6d8d70917ab69e952493e18b67e0e1f22745f3b30528bcb4346e47455b", + "0x68d509b78ca15e200209038efa53ab5fc30eef61b87bd1c3417fd4eab256c255", + "0xc3c2b03b5831d3cf7bfc8c96e38b1f5e1c3a19745b220cdc5f9eec15f6a1f9c1", + "0x16fed94ec0cf0aa4b075e19cb2b6ff780a6c11e9b1c60195b036c1b8622c0147", + "0xf47e6584af17667881494720b50dd063d0900b288438df7a37e2e91440aedd23", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "field_idx": 1 + }, + { + "header_rlp": [ + 249, + 2, + 24, + 160, + 216, + 95, + 50, + 4, + 112, + 10, + 121, + 104, + 27, + 83, + 253, + 131, + 4, + 164, + 65, + 65, + 189, + 42, + 242, + 254, + 68, + 178, + 38, + 90, + 237, + 99, + 108, + 22, + 237, + 111, + 226, + 35, + 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, + 36, + 155, + 219, + 68, + 153, + 189, + 124, + 104, + 54, + 100, + 193, + 73, + 39, + 108, + 29, + 134, + 16, + 142, + 33, + 55, + 160, + 156, + 228, + 86, + 29, + 250, + 219, + 79, + 176, + 34, + 222, + 188, + 124, + 1, + 49, + 65, + 163, + 106, + 35, + 212, + 103, + 244, + 38, + 140, + 14, + 98, + 10, + 123, + 186, + 5, + 177, + 116, + 248, + 160, + 236, + 76, + 142, + 192, + 34, + 129, + 193, + 150, + 227, + 248, + 130, + 180, + 6, + 28, + 5, + 244, + 240, + 132, + 58, + 14, + 175, + 114, + 163, + 238, + 71, + 21, + 7, + 114, + 32, + 147, + 75, + 188, + 160, + 13, + 254, + 202, + 181, + 217, + 122, + 77, + 163, + 97, + 185, + 44, + 41, + 21, + 124, + 214, + 165, + 8, + 155, + 85, + 250, + 206, + 224, + 7, + 238, + 161, + 96, + 5, + 191, + 168, + 216, + 38, + 148, + 185, + 1, + 0, + 25, + 130, + 124, + 128, + 145, + 88, + 164, + 154, + 65, + 140, + 34, + 195, + 42, + 197, + 16, + 176, + 97, + 232, + 128, + 112, + 116, + 65, + 32, + 20, + 161, + 10, + 0, + 0, + 38, + 12, + 36, + 231, + 42, + 74, + 3, + 69, + 176, + 142, + 12, + 80, + 128, + 81, + 88, + 3, + 2, + 19, + 3, + 142, + 146, + 5, + 36, + 76, + 81, + 0, + 183, + 34, + 48, + 104, + 1, + 142, + 241, + 100, + 128, + 9, + 54, + 116, + 0, + 100, + 214, + 66, + 130, + 35, + 212, + 21, + 16, + 78, + 188, + 193, + 161, + 101, + 102, + 32, + 100, + 36, + 167, + 7, + 208, + 81, + 68, + 129, + 166, + 0, + 128, + 9, + 10, + 52, + 94, + 0, + 7, + 36, + 70, + 208, + 0, + 33, + 1, + 41, + 197, + 140, + 19, + 138, + 88, + 34, + 172, + 104, + 98, + 6, + 24, + 231, + 172, + 170, + 205, + 52, + 175, + 22, + 85, + 52, + 58, + 202, + 51, + 64, + 17, + 208, + 64, + 99, + 162, + 146, + 3, + 165, + 162, + 34, + 156, + 68, + 64, + 122, + 150, + 129, + 237, + 58, + 96, + 150, + 3, + 48, + 112, + 98, + 1, + 118, + 74, + 56, + 42, + 23, + 14, + 140, + 32, + 129, + 0, + 5, + 236, + 0, + 0, + 120, + 193, + 149, + 137, + 207, + 208, + 128, + 51, + 80, + 128, + 21, + 225, + 74, + 5, + 48, + 34, + 137, + 11, + 78, + 96, + 178, + 10, + 81, + 52, + 144, + 36, + 134, + 107, + 12, + 0, + 167, + 0, + 91, + 49, + 65, + 6, + 115, + 198, + 197, + 20, + 72, + 114, + 242, + 132, + 185, + 230, + 18, + 188, + 24, + 0, + 24, + 209, + 82, + 241, + 66, + 128, + 27, + 165, + 39, + 128, + 163, + 210, + 8, + 13, + 144, + 236, + 150, + 5, + 198, + 251, + 207, + 73, + 52, + 1, + 145, + 146, + 68, + 48, + 20, + 68, + 8, + 150, + 182, + 79, + 80, + 77, + 248, + 135, + 7, + 242, + 109, + 127, + 128, + 201, + 162, + 131, + 145, + 101, + 237, + 131, + 152, + 36, + 48, + 131, + 152, + 17, + 146, + 132, + 94, + 80, + 64, + 41, + 151, + 67, + 114, + 117, + 120, + 112, + 111, + 111, + 108, + 32, + 80, + 80, + 83, + 47, + 110, + 105, + 99, + 101, + 104, + 97, + 115, + 104, + 45, + 49, + 160, + 23, + 76, + 110, + 69, + 220, + 100, + 249, + 186, + 143, + 248, + 63, + 99, + 234, + 201, + 165, + 31, + 214, + 2, + 218, + 81, + 24, + 0, + 54, + 137, + 174, + 148, + 73, + 134, + 163, + 9, + 214, + 130, + 136, + 142, + 224, + 18, + 50, + 29, + 215, + 108, + 211, + 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 + ], + "mmr_proof": [ + "0xd85f3204700a79681b53fd8304a44141bd2af2fe44b2265aed636c16ed6fe223", + "0x250018a435d17b5473e05f884ea1af54be447730a201fd72a707da51a820345c", + "0x19e99f81e6a4933a1dd41191733bd7b11ee9dbdf74f3a508e04514eb5053a57e", + "0x5a58cfc592d45892857d908e3c8c2da8c40b1cab2d4c941546ed9232114d1b37", + "0x752448ae955fe7ab9d28a97fb104fccca709d7ef6c73fd814ae3fefb5d211d7b", + "0xbb33fff29f3094bd9de4d3668fd20f2a45f8028c0c532f4e10b2368ab5c783aa", + "0xf99a2e4fdc253c869f26e9e13e8794c41a3bebb7498fe90513ff1996378db3d2", + "0x1eeaaf21efadf32e39e4db63eedb3b791e5586e373bcdc783c42ea6a8028b489", + "0x82dd59d4c6cb74831ca6eebc0b9beb938a7c0e33e000c160264520780daecd87", + "0x791c7933517944067f8dcf8cdbb4bf9c634d07c0f1f91ad3648920bc29a6884d", + "0xf3d9b0595399d791bb7b2e91630175518483c8c517d1097e4b67b0ae78c1bb21", + "0x82c76ea3624a6f4e2d62c5375b3dc0a86bf69810715bc7fddebc92937cd4b0e8", + "0x1b7b0f134de7422015a5421b9d4c0fbd370493f06144b53b5ca3ebf419756f00", + "0xa3e48aa8f6637e4a196bf2e5c51d63b766cee11645a1e2cb20e38fd6c2f67616", + "0xcea8ad8ff6608360c6c43b229e482664a5ce0f23090fab77acd12803c6094c03", + "0x223b4e206927956a87b2bd3be0b39f6176172c13d773b99e11b4d0ac8c1307d4", + "0x5079605c269d43ce5a544d19e7832911a862f74b0df14d7b604232869b39ed78", + "0xfb07e7779638fe0e542e6a55e3e4068d1a0a71e178637553e2abe5177c3a3d7d", + "0x93071e157ae565f7dbfea93c3da2e03caf192a8d97e1e4f752cad4b03dad40a8", + "0xb2838c6d8d70917ab69e952493e18b67e0e1f22745f3b30528bcb4346e47455b", + "0x68d509b78ca15e200209038efa53ab5fc30eef61b87bd1c3417fd4eab256c255", + "0xc3c2b03b5831d3cf7bfc8c96e38b1f5e1c3a19745b220cdc5f9eec15f6a1f9c1", + "0x16fed94ec0cf0aa4b075e19cb2b6ff780a6c11e9b1c60195b036c1b8622c0147", + "0xf47e6584af17667881494720b50dd063d0900b288438df7a37e2e91440aedd23", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "field_idx": 71 + }, + { + "header_rlp": [ + 249, + 2, + 24, + 160, + 216, + 95, + 50, + 4, + 112, + 10, + 121, + 104, + 27, + 83, + 253, + 131, + 4, + 164, + 65, + 65, + 189, + 42, + 242, + 254, + 68, + 178, + 38, + 90, + 237, + 99, + 108, + 22, + 237, + 111, + 226, + 35, + 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, + 36, + 155, + 219, + 68, + 153, + 189, + 124, + 104, + 54, + 100, + 193, + 73, + 39, + 108, + 29, + 134, + 16, + 142, + 33, + 55, + 160, + 156, + 228, + 86, + 29, + 250, + 219, + 79, + 176, + 34, + 222, + 188, + 124, + 1, + 49, + 65, + 163, + 106, + 35, + 212, + 103, + 244, + 38, + 140, + 14, + 98, + 10, + 123, + 186, + 5, + 177, + 116, + 248, + 160, + 236, + 76, + 142, + 192, + 34, + 129, + 193, + 150, + 227, + 248, + 130, + 180, + 6, + 28, + 5, + 244, + 240, + 132, + 58, + 14, + 175, + 114, + 163, + 238, + 71, + 21, + 7, + 114, + 32, + 147, + 75, + 188, + 160, + 13, + 254, + 202, + 181, + 217, + 122, + 77, + 163, + 97, + 185, + 44, + 41, + 21, + 124, + 214, + 165, + 8, + 155, + 85, + 250, + 206, + 224, + 7, + 238, + 161, + 96, + 5, + 191, + 168, + 216, + 38, + 148, + 185, + 1, + 0, + 25, + 130, + 124, + 128, + 145, + 88, + 164, + 154, + 65, + 140, + 34, + 195, + 42, + 197, + 16, + 176, + 97, + 232, + 128, + 112, + 116, + 65, + 32, + 20, + 161, + 10, + 0, + 0, + 38, + 12, + 36, + 231, + 42, + 74, + 3, + 69, + 176, + 142, + 12, + 80, + 128, + 81, + 88, + 3, + 2, + 19, + 3, + 142, + 146, + 5, + 36, + 76, + 81, + 0, + 183, + 34, + 48, + 104, + 1, + 142, + 241, + 100, + 128, + 9, + 54, + 116, + 0, + 100, + 214, + 66, + 130, + 35, + 212, + 21, + 16, + 78, + 188, + 193, + 161, + 101, + 102, + 32, + 100, + 36, + 167, + 7, + 208, + 81, + 68, + 129, + 166, + 0, + 128, + 9, + 10, + 52, + 94, + 0, + 7, + 36, + 70, + 208, + 0, + 33, + 1, + 41, + 197, + 140, + 19, + 138, + 88, + 34, + 172, + 104, + 98, + 6, + 24, + 231, + 172, + 170, + 205, + 52, + 175, + 22, + 85, + 52, + 58, + 202, + 51, + 64, + 17, + 208, + 64, + 99, + 162, + 146, + 3, + 165, + 162, + 34, + 156, + 68, + 64, + 122, + 150, + 129, + 237, + 58, + 96, + 150, + 3, + 48, + 112, + 98, + 1, + 118, + 74, + 56, + 42, + 23, + 14, + 140, + 32, + 129, + 0, + 5, + 236, + 0, + 0, + 120, + 193, + 149, + 137, + 207, + 208, + 128, + 51, + 80, + 128, + 21, + 225, + 74, + 5, + 48, + 34, + 137, + 11, + 78, + 96, + 178, + 10, + 81, + 52, + 144, + 36, + 134, + 107, + 12, + 0, + 167, + 0, + 91, + 49, + 65, + 6, + 115, + 198, + 197, + 20, + 72, + 114, + 242, + 132, + 185, + 230, + 18, + 188, + 24, + 0, + 24, + 209, + 82, + 241, + 66, + 128, + 27, + 165, + 39, + 128, + 163, + 210, + 8, + 13, + 144, + 236, + 150, + 5, + 198, + 251, + 207, + 73, + 52, + 1, + 145, + 146, + 68, + 48, + 20, + 68, + 8, + 150, + 182, + 79, + 80, + 77, + 248, + 135, + 7, + 242, + 109, + 127, + 128, + 201, + 162, + 131, + 145, + 101, + 237, + 131, + 152, + 36, + 48, + 131, + 152, + 17, + 146, + 132, + 94, + 80, + 64, + 41, + 151, + 67, + 114, + 117, + 120, + 112, + 111, + 111, + 108, + 32, + 80, + 80, + 83, + 47, + 110, + 105, + 99, + 101, + 104, + 97, + 115, + 104, + 45, + 49, + 160, + 23, + 76, + 110, + 69, + 220, + 100, + 249, + 186, + 143, + 248, + 63, + 99, + 234, + 201, + 165, + 31, + 214, + 2, + 218, + 81, + 24, + 0, + 54, + 137, + 174, + 148, + 73, + 134, + 163, + 9, + 214, + 130, + 136, + 142, + 224, + 18, + 50, + 29, + 215, + 108, + 211, + 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 + ], + "mmr_proof": [ + "0xd85f3204700a79681b53fd8304a44141bd2af2fe44b2265aed636c16ed6fe223", + "0x250018a435d17b5473e05f884ea1af54be447730a201fd72a707da51a820345c", + "0x19e99f81e6a4933a1dd41191733bd7b11ee9dbdf74f3a508e04514eb5053a57e", + "0x5a58cfc592d45892857d908e3c8c2da8c40b1cab2d4c941546ed9232114d1b37", + "0x752448ae955fe7ab9d28a97fb104fccca709d7ef6c73fd814ae3fefb5d211d7b", + "0xbb33fff29f3094bd9de4d3668fd20f2a45f8028c0c532f4e10b2368ab5c783aa", + "0xf99a2e4fdc253c869f26e9e13e8794c41a3bebb7498fe90513ff1996378db3d2", + "0x1eeaaf21efadf32e39e4db63eedb3b791e5586e373bcdc783c42ea6a8028b489", + "0x82dd59d4c6cb74831ca6eebc0b9beb938a7c0e33e000c160264520780daecd87", + "0x791c7933517944067f8dcf8cdbb4bf9c634d07c0f1f91ad3648920bc29a6884d", + "0xf3d9b0595399d791bb7b2e91630175518483c8c517d1097e4b67b0ae78c1bb21", + "0x82c76ea3624a6f4e2d62c5375b3dc0a86bf69810715bc7fddebc92937cd4b0e8", + "0x1b7b0f134de7422015a5421b9d4c0fbd370493f06144b53b5ca3ebf419756f00", + "0xa3e48aa8f6637e4a196bf2e5c51d63b766cee11645a1e2cb20e38fd6c2f67616", + "0xcea8ad8ff6608360c6c43b229e482664a5ce0f23090fab77acd12803c6094c03", + "0x223b4e206927956a87b2bd3be0b39f6176172c13d773b99e11b4d0ac8c1307d4", + "0x5079605c269d43ce5a544d19e7832911a862f74b0df14d7b604232869b39ed78", + "0xfb07e7779638fe0e542e6a55e3e4068d1a0a71e178637553e2abe5177c3a3d7d", + "0x93071e157ae565f7dbfea93c3da2e03caf192a8d97e1e4f752cad4b03dad40a8", + "0xb2838c6d8d70917ab69e952493e18b67e0e1f22745f3b30528bcb4346e47455b", + "0x68d509b78ca15e200209038efa53ab5fc30eef61b87bd1c3417fd4eab256c255", + "0xc3c2b03b5831d3cf7bfc8c96e38b1f5e1c3a19745b220cdc5f9eec15f6a1f9c1", + "0x16fed94ec0cf0aa4b075e19cb2b6ff780a6c11e9b1c60195b036c1b8622c0147", + "0xf47e6584af17667881494720b50dd063d0900b288438df7a37e2e91440aedd23", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "field_idx": 12 + } + ], + "_phantom": null +} \ No newline at end of file diff --git a/axiom-query/data/test/input_keccak_shard_for_header.json b/axiom-query/data/test/input_keccak_shard_for_header.json new file mode 100644 index 00000000..b0f2eb39 --- /dev/null +++ b/axiom-query/data/test/input_keccak_shard_for_header.json @@ -0,0 +1,137 @@ +{ + "capacity": 41, + "responses": [ + [ + "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000348bab8f4d2acdd4022f4b7becb8ba36a260bd9f699aa4df0a0660dc772ddcfcfff5804d8cd309c79ff26f19ecc6a9cef13556c9d5ddb18afae277d71ad7d4114f1a2ab29b70f47ff5c88dc7048bb23fcc4a59fbcc3b9f0e9529297afb28cb8a995310788441a017c79a1fc0edab3074e6d5495712b35e120c446301b8b60d8c0000000000000000000000000000000000000000000000000000000000000000c4933b4a3d6a41bf6c8009e24436a19e3e91a6f4198a0f498ade1179d29785000000000000000000000000000000000000000000000000000000000000000000dff0f5a47ed23e4dd682fa5b20c861c8d616f0124bf3366def9f620e1569ade60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004755084f1c80a73395852c7ede586dddb3b8db7828e4ad2cf3259f693ac4f875", + null + ], + [ + "0x0e12a985789e947d5c94b2f0dfee712cdff776434e0a8c13379a81ec9cabbd90fb07e7779638fe0e542e6a55e3e4068d1a0a71e178637553e2abe5177c3a3d7d", + null + ], + [ + "0x19e99f81e6a4933a1dd41191733bd7b11ee9dbdf74f3a508e04514eb5053a57ea1aba88a1e5835420920b9de5a5c9489f57d8bee868c142f47f3716b9e1352c6", + null + ], + [ + "0x1eeaaf21efadf32e39e4db63eedb3b791e5586e373bcdc783c42ea6a8028b48907449018b55a87abbc033d85102853995a41aeaa8544452dc2240aebda5e9cb3", + null + ], + [ + "0x3020fe1044a376596e399087fc0e5cb8783a853e2c85c7206514dc2cf71e42f31b7b0f134de7422015a5421b9d4c0fbd370493f06144b53b5ca3ebf419756f00", + null + ], + [ + "0x4755084f1c80a73395852c7ede586dddb3b8db7828e4ad2cf3259f693ac4f8750000000000000000000000000000000000000000000000000000000000000000", + null + ], + [ + "0x477c055e69de14e3bbfe2af0389e6c3ac28ffb5b0cc8fa26b543ac47857fd64616fed94ec0cf0aa4b075e19cb2b6ff780a6c11e9b1c60195b036c1b8622c0147", + null + ], + [ + "0x5079605c269d43ce5a544d19e7832911a862f74b0df14d7b604232869b39ed78a0d8feace83653ed55151dbf0959b0524c9e3187f928e41aab067daed472d269", + null + ], + [ + "0x5459bcc28c212f4f7ba9b7075fad7c3f2292e83997007998dd27d5369d469616752448ae955fe7ab9d28a97fb104fccca709d7ef6c73fd814ae3fefb5d211d7b", + null + ], + [ + "0x594f03e3c07dfc61489a41b9645c2715fa0cbeebd2c13d25b30add4bbfd315950000000000000000000000000000000000000000000000000000000000000000", + null + ], + [ + "0x5a58cfc592d45892857d908e3c8c2da8c40b1cab2d4c941546ed9232114d1b373409945b63c2e1f8a04cc3236cf0d4ac7a340695c8f5641a526dc6c783ca91e0", + null + ], + [ + "0x615ab700eca77f00034ff0b208a7c9e876cde7cd947aaebb2cb004ce461a1e43b2838c6d8d70917ab69e952493e18b67e0e1f22745f3b30528bcb4346e47455b", + null + ], + [ + "0x655090a6192edeb76e76b01476071607739746e8197158c58ed6d3a001021e43c3c2b03b5831d3cf7bfc8c96e38b1f5e1c3a19745b220cdc5f9eec15f6a1f9c1", + null + ], + [ + "0x68d509b78ca15e200209038efa53ab5fc30eef61b87bd1c3417fd4eab256c255c317e0c0c4ac8c49716549e55a7ab0252ba4116ea7f7a518fb1441d02ef1e248", + null + ], + [ + "0x6d495f5672d5d7482e1063eb1e1381edb72cabfdfa2c218cc38461136cc513970000000000000000000000000000000000000000000000000000000000000000", + null + ], + [ + "0x79a9b620dd7c6e8a40e973ae2193a378de5c45d9ff7917e3f31d01b019ac0527791c7933517944067f8dcf8cdbb4bf9c634d07c0f1f91ad3648920bc29a6884d", + null + ], + [ + "0x79e3279ab366ab5dc5c1f8ad4aad3ce7689086e4f8e25572c835f43d4a826432250018a435d17b5473e05f884ea1af54be447730a201fd72a707da51a820345c", + null + ], + [ + "0x82dd59d4c6cb74831ca6eebc0b9beb938a7c0e33e000c160264520780daecd8737d210b023995e28e43315bb1a98eeddde85a35b7321d7162f51c9425fd5f905", + null + ], + [ + "0x833b246c8bdd751a108869f958b69cfaee4a03c036905b90c39bfc986bda80ee0000000000000000000000000000000000000000000000000000000000000000", + null + ], + [ + "0x9071e895206e9ac84c8ae51e7180e9479827ca3288175f4615fdc724e28008d793071e157ae565f7dbfea93c3da2e03caf192a8d97e1e4f752cad4b03dad40a8", + null + ], + [ + "0xa3e48aa8f6637e4a196bf2e5c51d63b766cee11645a1e2cb20e38fd6c2f676163eb43d4625448a26adb10215f7f092a6fc8622a21b35048c2b2ef1d991162d84", + null + ], + [ + "0xa60d89ad021ef63b49250141377cc3e835945e5d4136ffc1909047c4ecca3af70000000000000000000000000000000000000000000000000000000000000000", + null + ], + [ + "0xbb33fff29f3094bd9de4d3668fd20f2a45f8028c0c532f4e10b2368ab5c783aa22b2e40d32a936ed08307b41db986d35160fe5917c8d2609d27e7bd02f4b767e", + null + ], + [ + "0xbe8c5ff2ada208b4577bd49d7660fdaf3e17983530f5d1d574126c5ee743de900000000000000000000000000000000000000000000000000000000000000000", + null + ], + [ + "0xc364b3480c792d14112d17f167581d8fcf74b6d7805bc1ef1526f5fa7415be400000000000000000000000000000000000000000000000000000000000000000", + null + ], + [ + "0xcea8ad8ff6608360c6c43b229e482664a5ce0f23090fab77acd12803c6094c03f7533e213fc68258934880ec7189125fc6071e01f20aa465e56f7c7ecfccb800", + null + ], + [ + "0xd85f3204700a79681b53fd8304a44141bd2af2fe44b2265aed636c16ed6fe223c68b30cb54482f31743afe91abb6841704d275a6a378f7faa389d387c384d568", + null + ], + [ + "0xdad303cf18a459358a346f84e5e3a3065256a3584769c3cfae5b1729e168ed52223b4e206927956a87b2bd3be0b39f6176172c13d773b99e11b4d0ac8c1307d4", + null + ], + [ + "0xe2327533e28e723bb66ff3d0bfcb32bad0c070e8935d13c0a36a3126c8f3b7d082c76ea3624a6f4e2d62c5375b3dc0a86bf69810715bc7fddebc92937cd4b0e8", + null + ], + [ + "0xf3d9b0595399d791bb7b2e91630175518483c8c517d1097e4b67b0ae78c1bb21eab78f0a8534330a079c8179e08f594fe8ed3483c2df1c46e7177e4839ab2114", + null + ], + [ + "0xf47e6584af17667881494720b50dd063d0900b288438df7a37e2e91440aedd2388e177538a14ffca910f9c6b56f6bed99d1f748fa103093f372ecbe9db819e38", + null + ], + [ + "0xf90218a0d85f3204700a79681b53fd8304a44141bd2af2fe44b2265aed636c16ed6fe223a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794249bdb4499bd7c683664c149276c1d86108e2137a09ce4561dfadb4fb022debc7c013141a36a23d467f4268c0e620a7bba05b174f8a0ec4c8ec02281c196e3f882b4061c05f4f0843a0eaf72a3ee4715077220934bbca00dfecab5d97a4da361b92c29157cd6a5089b55facee007eea16005bfa8d82694b9010019827c809158a49a418c22c32ac510b061e8807074412014a10a0000260c24e72a4a0345b08e0c50805158030213038e9205244c5100b7223068018ef164800936740064d6428223d415104ebcc1a16566206424a707d0514481a60080090a345e00072446d000210129c58c138a5822ac68620618e7acaacd34af1655343aca334011d04063a29203a5a2229c44407a9681ed3a60960330706201764a382a170e8c20810005ec000078c19589cfd08033508015e14a053022890b4e60b20a51349024866b0c00a7005b31410673c6c5144872f284b9e612bc180018d152f142801ba52780a3d2080d90ec9605c6fbcf4934019192443014440896b64f504df88707f26d7f80c9a2839165ed8398243083981192845e5040299743727578706f6f6c205050532f6e696365686173682d31a0174c6e45dc64f9ba8ff83f63eac9a51fd602da5118003689ae944986a309d682888ee012321dd76cd3", + null + ], + [ + "0xf99a2e4fdc253c869f26e9e13e8794c41a3bebb7498fe90513ff1996378db3d2a7d918baae6f3c07d7d0cbfac129925e9080f61cd113b60dc3970edff82f7338", + null + ] + ] +} \ No newline at end of file diff --git a/axiom-query/data/test/input_mmr_proof_for_header.json b/axiom-query/data/test/input_mmr_proof_for_header.json new file mode 100644 index 00000000..f6222e57 --- /dev/null +++ b/axiom-query/data/test/input_mmr_proof_for_header.json @@ -0,0 +1,47 @@ +{ + "historicalMmr": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x348bab8f4d2acdd4022f4b7becb8ba36a260bd9f699aa4df0a0660dc772ddcfc", + "0xfff5804d8cd309c79ff26f19ecc6a9cef13556c9d5ddb18afae277d71ad7d411", + "0x4f1a2ab29b70f47ff5c88dc7048bb23fcc4a59fbcc3b9f0e9529297afb28cb8a", + "0x995310788441a017c79a1fc0edab3074e6d5495712b35e120c446301b8b60d8c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xc4933b4a3d6a41bf6c8009e24436a19e3e91a6f4198a0f498ade1179d2978500", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xdff0f5a47ed23e4dd682fa5b20c861c8d616f0124bf3366def9f620e1569ade6", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x4755084f1c80a73395852c7ede586dddb3b8db7828e4ad2cf3259f693ac4f875" + ], + "blockHash": "0xc68b30cb54482f31743afe91abb6841704d275a6a378f7faa389d387c384d568", + "logsBloom": "0x19827c809158a49a418c22c32ac510b061e8807074412014a10a0000260c24e72a4a0345b08e0c50805158030213038e9205244c5100b7223068018ef164800936740064d6428223d415104ebcc1a16566206424a707d0514481a60080090a345e00072446d000210129c58c138a5822ac68620618e7acaacd34af1655343aca334011d04063a29203a5a2229c44407a9681ed3a60960330706201764a382a170e8c20810005ec000078c19589cfd08033508015e14a053022890b4e60b20a51349024866b0c00a7005b31410673c6c5144872f284b9e612bc180018d152f142801ba52780a3d2080d90ec9605c6fbcf4934019192443014440896b64f504df8", + "mmrProof": [ + "0xd85f3204700a79681b53fd8304a44141bd2af2fe44b2265aed636c16ed6fe223", + "0x250018a435d17b5473e05f884ea1af54be447730a201fd72a707da51a820345c", + "0x19e99f81e6a4933a1dd41191733bd7b11ee9dbdf74f3a508e04514eb5053a57e", + "0x5a58cfc592d45892857d908e3c8c2da8c40b1cab2d4c941546ed9232114d1b37", + "0x752448ae955fe7ab9d28a97fb104fccca709d7ef6c73fd814ae3fefb5d211d7b", + "0xbb33fff29f3094bd9de4d3668fd20f2a45f8028c0c532f4e10b2368ab5c783aa", + "0xf99a2e4fdc253c869f26e9e13e8794c41a3bebb7498fe90513ff1996378db3d2", + "0x1eeaaf21efadf32e39e4db63eedb3b791e5586e373bcdc783c42ea6a8028b489", + "0x82dd59d4c6cb74831ca6eebc0b9beb938a7c0e33e000c160264520780daecd87", + "0x791c7933517944067f8dcf8cdbb4bf9c634d07c0f1f91ad3648920bc29a6884d", + "0xf3d9b0595399d791bb7b2e91630175518483c8c517d1097e4b67b0ae78c1bb21", + "0x82c76ea3624a6f4e2d62c5375b3dc0a86bf69810715bc7fddebc92937cd4b0e8", + "0x1b7b0f134de7422015a5421b9d4c0fbd370493f06144b53b5ca3ebf419756f00", + "0xa3e48aa8f6637e4a196bf2e5c51d63b766cee11645a1e2cb20e38fd6c2f67616", + "0xcea8ad8ff6608360c6c43b229e482664a5ce0f23090fab77acd12803c6094c03", + "0x223b4e206927956a87b2bd3be0b39f6176172c13d773b99e11b4d0ac8c1307d4", + "0x5079605c269d43ce5a544d19e7832911a862f74b0df14d7b604232869b39ed78", + "0xfb07e7779638fe0e542e6a55e3e4068d1a0a71e178637553e2abe5177c3a3d7d", + "0x93071e157ae565f7dbfea93c3da2e03caf192a8d97e1e4f752cad4b03dad40a8", + "0xb2838c6d8d70917ab69e952493e18b67e0e1f22745f3b30528bcb4346e47455b", + "0x68d509b78ca15e200209038efa53ab5fc30eef61b87bd1c3417fd4eab256c255", + "0xc3c2b03b5831d3cf7bfc8c96e38b1f5e1c3a19745b220cdc5f9eec15f6a1f9c1", + "0x16fed94ec0cf0aa4b075e19cb2b6ff780a6c11e9b1c60195b036c1b8622c0147", + "0xf47e6584af17667881494720b50dd063d0900b288438df7a37e2e91440aedd23" + ] +} \ No newline at end of file diff --git a/axiom-query/data/test/input_results_root.json b/axiom-query/data/test/input_results_root.json new file mode 100644 index 00000000..0c435cab --- /dev/null +++ b/axiom-query/data/test/input_results_root.json @@ -0,0 +1,671 @@ +{ + "encodedSubqueries": [ + "0x0004009165ed004400000036", + "0x0001009165ed00000001", + "0x0001009165ed00000047", + "0x0001009165ed0000000c", + "0x0001009165ed00000003", + "0x0001009165ed00000004", + "0x0001009165ed00000005", + "0x0005009165ed004400000066000000017f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b353984238705", + "0x0002009165eddac17f958d2ee523a2206206994597c13d831ec700000002", + "0x0003009165eddac17f958d2ee523a2206206994597c13d831ec7ac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b", + "0x0006009165eddac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000002010000000000000000000000000000000000000000000000000000000000000000", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036", + "0x0004009165ed004400000036" + ], + "numSubqueries": 11, + "promiseHeader": { + "results": [ + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 71 + }, + "value": "0x2a4a0345b08e0c50805158030213038e9205244c5100b7223068018ef1648009" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 12 + }, + "value": "0x43727578706f6f6c205050532f6e696365686173682d31000000000000000000" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 3 + }, + "value": "0x9ce4561dfadb4fb022debc7c013141a36a23d467f4268c0e620a7bba05b174f8" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 4 + }, + "value": "0xec4c8ec02281c196e3f882b4061c05f4f0843a0eaf72a3ee4715077220934bbc" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 5 + }, + "value": "0x0dfecab5d97a4da361b92c29157cd6a5089b55facee007eea16005bfa8d82694" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + } + ] + }, + "promiseAccount": { + "results": [ + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "fieldIdx": 2 + }, + "value": "0x3bddd93ffcce5169c2ce5d08b91f9fda3ed657dbf3b78beffb44dc833f6308a2" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "fieldIdx": 2 + }, + "value": "0x3bddd93ffcce5169c2ce5d08b91f9fda3ed657dbf3b78beffb44dc833f6308a2" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "fieldIdx": 2 + }, + "value": "0x3bddd93ffcce5169c2ce5d08b91f9fda3ed657dbf3b78beffb44dc833f6308a2" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "fieldIdx": 2 + }, + "value": "0x3bddd93ffcce5169c2ce5d08b91f9fda3ed657dbf3b78beffb44dc833f6308a2" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "fieldIdx": 2 + }, + "value": "0x3bddd93ffcce5169c2ce5d08b91f9fda3ed657dbf3b78beffb44dc833f6308a2" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "fieldIdx": 2 + }, + "value": "0x3bddd93ffcce5169c2ce5d08b91f9fda3ed657dbf3b78beffb44dc833f6308a2" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "fieldIdx": 2 + }, + "value": "0x3bddd93ffcce5169c2ce5d08b91f9fda3ed657dbf3b78beffb44dc833f6308a2" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "fieldIdx": 2 + }, + "value": "0x3bddd93ffcce5169c2ce5d08b91f9fda3ed657dbf3b78beffb44dc833f6308a2" + } + ] + }, + "promiseStorage": { + "results": [ + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "slot": "0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b" + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "slot": "0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b" + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "slot": "0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b" + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "slot": "0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b" + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "slot": "0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b" + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "slot": "0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b" + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "slot": "0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b" + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "slot": "0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b" + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + } + ] + }, + "promiseSolidityMapping": { + "results": [ + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "mappingSlot": "0x2", + "mappingDepth": 1, + "keys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "mappingSlot": "0x2", + "mappingDepth": 1, + "keys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "mappingSlot": "0x2", + "mappingDepth": 1, + "keys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "mappingSlot": "0x2", + "mappingDepth": 1, + "keys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "mappingSlot": "0x2", + "mappingDepth": 1, + "keys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "mappingSlot": "0x2", + "mappingDepth": 1, + "keys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "mappingSlot": "0x2", + "mappingDepth": 1, + "keys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + }, + { + "subquery": { + "blockNumber": 9528813, + "addr": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "mappingSlot": "0x2", + "mappingDepth": 1, + "keys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + "value": "0x000000000000000000000000000000000000000000000000000000000b532b80" + } + ] + }, + "promiseTx": { + "results": [ + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrCalldataIdx": 54 + }, + "value": "0x00000000000000000000000000000000000000000000000000000000013efd8b" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrCalldataIdx": 54 + }, + "value": "0x00000000000000000000000000000000000000000000000000000000013efd8b" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrCalldataIdx": 54 + }, + "value": "0x00000000000000000000000000000000000000000000000000000000013efd8b" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrCalldataIdx": 54 + }, + "value": "0x00000000000000000000000000000000000000000000000000000000013efd8b" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrCalldataIdx": 54 + }, + "value": "0x00000000000000000000000000000000000000000000000000000000013efd8b" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrCalldataIdx": 54 + }, + "value": "0x00000000000000000000000000000000000000000000000000000000013efd8b" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrCalldataIdx": 54 + }, + "value": "0x00000000000000000000000000000000000000000000000000000000013efd8b" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrCalldataIdx": 54 + }, + "value": "0x00000000000000000000000000000000000000000000000000000000013efd8b" + } + ] + }, + "promiseReceipt": { + "results": [ + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrLogIdx": 102, + "topicOrDataOrAddressIdx": 1, + "eventSchema": "0x7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b353984238705" + }, + "value": "0x000000000000000000000000263fc4d9eb6da1ed296ea6d189b41e546a188d8a" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrLogIdx": 102, + "topicOrDataOrAddressIdx": 1, + "eventSchema": "0x7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b353984238705" + }, + "value": "0x000000000000000000000000263fc4d9eb6da1ed296ea6d189b41e546a188d8a" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrLogIdx": 102, + "topicOrDataOrAddressIdx": 1, + "eventSchema": "0x7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b353984238705" + }, + "value": "0x000000000000000000000000263fc4d9eb6da1ed296ea6d189b41e546a188d8a" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrLogIdx": 102, + "topicOrDataOrAddressIdx": 1, + "eventSchema": "0x7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b353984238705" + }, + "value": "0x000000000000000000000000263fc4d9eb6da1ed296ea6d189b41e546a188d8a" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrLogIdx": 102, + "topicOrDataOrAddressIdx": 1, + "eventSchema": "0x7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b353984238705" + }, + "value": "0x000000000000000000000000263fc4d9eb6da1ed296ea6d189b41e546a188d8a" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrLogIdx": 102, + "topicOrDataOrAddressIdx": 1, + "eventSchema": "0x7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b353984238705" + }, + "value": "0x000000000000000000000000263fc4d9eb6da1ed296ea6d189b41e546a188d8a" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrLogIdx": 102, + "topicOrDataOrAddressIdx": 1, + "eventSchema": "0x7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b353984238705" + }, + "value": "0x000000000000000000000000263fc4d9eb6da1ed296ea6d189b41e546a188d8a" + }, + { + "subquery": { + "blockNumber": 9528813, + "txIdx": 68, + "fieldOrLogIdx": 102, + "topicOrDataOrAddressIdx": 1, + "eventSchema": "0x7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b353984238705" + }, + "value": "0x000000000000000000000000263fc4d9eb6da1ed296ea6d189b41e546a188d8a" + } + ] + }, + "promiseKeccak": { + "leaves": [ + { + "commit": "a60daac95d772a0f94dec507dd35e8bceb7f48aa5c404fd62127234b48e5b214", + "capacity": 0 + } + ], + "shards": [ + [ + 0, + { + "responses": [], + "capacity": 0 + } + ] + ] + }, + "keccakMerkleMaxHeight": 3 +} \ No newline at end of file diff --git a/axiom-query/data/test/input_results_root_for_agg.json b/axiom-query/data/test/input_results_root_for_agg.json new file mode 100644 index 00000000..20929eb8 --- /dev/null +++ b/axiom-query/data/test/input_results_root_for_agg.json @@ -0,0 +1,73 @@ +{ + "subqueries": { + "rows": [ + { + "key": [ + "0100000000000000000000000000000000000000000000000000000000000000", + "ed65910000000000000000000000000000000000000000000000000000000000", + "0100000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000" + ], + "value": [ + "1ad4ccb667b585ab7a5dc7dee84dcc1d00000000000000000000000000000000", + "4793d440fd42a1f013748a941b4512d300000000000000000000000000000000" + ] + }, + { + "key": [ + "0100000000000000000000000000000000000000000000000000000000000000", + "ed65910000000000000000000000000000000000000000000000000000000000", + "4700000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000" + ], + "value": [ + "8e03130203585180500c8eb045034a2a00000000000000000000000000000000", + "098064f18e01683022b700514c24059200000000000000000000000000000000" + ] + }, + { + "key": [ + "0100000000000000000000000000000000000000000000000000000000000000", + "ed65910000000000000000000000000000000000000000000000000000000000", + "0c00000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000" + ], + "value": [ + "63696e2f535050206c6f6f707875724300000000000000000000000000000000", + "000000000000000000312d687361686500000000000000000000000000000000" + ] + } + ] + }, + "num_subqueries": "0300000000000000000000000000000000000000000000000000000000000000" +} \ No newline at end of file diff --git a/axiom-query/data/test/input_verify_compute.json b/axiom-query/data/test/input_verify_compute.json new file mode 100644 index 00000000..c15f7cae --- /dev/null +++ b/axiom-query/data/test/input_verify_compute.json @@ -0,0 +1 @@ +{"sourceChainId":1,"subqueryResults":{"results":[{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":1,"encodedSubqueryData":"0x009165ed00000001"},"value":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"},{"subquery":{"subqueryType":1,"encodedSubqueryData":"0x009165ed00000047"},"value":"0x2a4a0345b08e0c50805158030213038e9205244c5100b7223068018ef1648009"},{"subquery":{"subqueryType":1,"encodedSubqueryData":"0x009165ed0000000c"},"value":"0x43727578706f6f6c205050532f6e696365686173682d31000000000000000000"},{"subquery":{"subqueryType":1,"encodedSubqueryData":"0x009165ed00000003"},"value":"0x9ce4561dfadb4fb022debc7c013141a36a23d467f4268c0e620a7bba05b174f8"},{"subquery":{"subqueryType":1,"encodedSubqueryData":"0x009165ed00000004"},"value":"0xec4c8ec02281c196e3f882b4061c05f4f0843a0eaf72a3ee4715077220934bbc"},{"subquery":{"subqueryType":1,"encodedSubqueryData":"0x009165ed00000005"},"value":"0x0dfecab5d97a4da361b92c29157cd6a5089b55facee007eea16005bfa8d82694"},{"subquery":{"subqueryType":5,"encodedSubqueryData":"0x009165ed004400000066000000017f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b353984238705"},"value":"0x000000000000000000000000263fc4d9eb6da1ed296ea6d189b41e546a188d8a"},{"subquery":{"subqueryType":2,"encodedSubqueryData":"0x009165eddac17f958d2ee523a2206206994597c13d831ec700000002"},"value":"0x3bddd93ffcce5169c2ce5d08b91f9fda3ed657dbf3b78beffb44dc833f6308a2"},{"subquery":{"subqueryType":3,"encodedSubqueryData":"0x009165eddac17f958d2ee523a2206206994597c13d831ec7ac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b"},"value":"0x000000000000000000000000000000000000000000000000000000000b532b80"},{"subquery":{"subqueryType":6,"encodedSubqueryData":"0x009165eddac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000002010000000000000000000000000000000000000000000000000000000000000000"},"value":"0x000000000000000000000000000000000000000000000000000000000b532b80"},{"subquery":{"subqueryType":7,"encodedSubqueryData":"0xf9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5a906bf5e62e43c23c867e0a5e0a0bb452d5be6fdfbc28adda198df4fa28de3000000000000000000000000000000000000000000000000000000000000002a92"},"value":"0x0000000000000000000000000000000000000000000000000000000000000001"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"},{"subquery":{"subqueryType":4,"encodedSubqueryData":"0x009165ed004400000036"},"value":"0x00000000000000000000000000000000000000000000000000000000013efd8b"}],"subqueryHashes":["0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0x162136872c2a5889ac1f6f816de9a0915382f52d308ccfb85bf51405f01f6e57","0xec9c61c069dc9edfb82f6e76435797487af1d8b8f040b26deda52deff1c9e73b","0xf36a01afa5be89a2e56cb5656e45a5547d917141f6e3c153b140213abb204059","0xf19a33edeb981cb5f27e1b3fb44e5fff804ff8b8cedc159eeffac35c581696a1","0x4c66a19802db648e516bab8397218bdece92a9ed5dda5ef82dc3febc219b37ae","0x8eeaf7da7945c33f68480eef5a7105d7fc3b6a9a2da69a674ed6566dadcabc2c","0xe04631a5d855c6d964d036cf6627553e37d83d976d901347d37b4bda18324476","0xef5efbe0d6fef44a2ea8bc264ceb32df78cac2dc0fa4f80d2869f275216c4400","0x18c1b820a02537b67bf11b8b99014de0de98dbcf62cc2265dfd336edd3fbf634","0xf3b2d0386f6b7d605219b6dc6daedeb55ff74c9a53161707dc604b561f51def8","0x0424577d4c484dab805c540fbbbcb47aa2601757bf82646f57dc0a83a88b522f","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd","0xf31f93d425de42ffc0eabe3b2c4fd395d293f9393447c68b86e88f9af7fd99fd"],"numSubqueries":12},"computeQuery":{"k":14,"resultLen":12,"vkey":["0x0001000009000100000004010000010080000000000000000000000000000000","0xe3fe8224331b3557a05ad320c01f63a0593442d266b5248d1bed7f0389dcb714","0xc3be708ada42161d4c12e749a136937caa30b64ee09e52cc5e2b4ec23ba15e6b","0x0000000000000000000000000000000000000000000000000000000000000080","0x0000000000000000000000000000000000000000000000000000000000000080","0x0000000000000000000000000000000000000000000000000000000000000080","0x0000000000000000000000000000000000000000000000000000000000000080","0x0000000000000000000000000000000000000000000000000000000000000080","0x49af813628396f071f616ba523301f8c8736c466ad88cf8c7671d522d679c30e","0x66d0923fd436b50a0609f98ca0176c4296853d479f8dacdd31b5c5f60fcbf148","0x524d4f75fb14f6dc479a40940f417095e0ab1b21983e25a1366fa71b38946701","0x39edb4057ce999b79f8e32e5713d1c9f0cbe08d9d4ea5431ffc32eda4bd45413","0x5800ca8057807ebd6066960dde35bc98100d9c687c1e22c7e9b813201fa27420","0x97bb2932ad41dfc883fa96bd0dee71fb8bf9df1da57743f8a938528bfd7d4461","0x66ba3930b9e45d5238b978e803d584d43caa982ea81fbce45b68e5e818845d09"],"computeProof":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013efd8b1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493472a4a0345b08e0c50805158030213038e9205244c5100b7223068018ef164800943727578706f6f6c205050532f6e696365686173682d310000000000000000009ce4561dfadb4fb022debc7c013141a36a23d467f4268c0e620a7bba05b174f8ec4c8ec02281c196e3f882b4061c05f4f0843a0eaf72a3ee4715077220934bbc0dfecab5d97a4da361b92c29157cd6a5089b55facee007eea16005bfa8d82694000000000000000000000000263fc4d9eb6da1ed296ea6d189b41e546a188d8a3bddd93ffcce5169c2ce5d08b91f9fda3ed657dbf3b78beffb44dc833f6308a2000000000000000000000000000000000000000000000000000000000b532b80000000000000000000000000000000000000000000000000000000000b532b800000000000000000000000000000000000000000000000000000000000000001af2f10698e0bf534944890e6dbb649cd429fb6bc3defe9ac9ee4f49ec607052b9c84b7214f5bb9241cc9b102dc9618d55ec2000248fd6ca701e1d4a4f7a4d80b3499db4c4829bcb0ce09a15e84b3d352f66d6d67278d9c068227d9f087623e08197fa9ad0076f2e0f14f124b89387d8cc8621a0c242016d960ffb8b03334812196b06e366f6a27add6277159ed1cb6f8f611c90e97fb249f0ec56fd71e629907814bc8c80937bf9424a84e39a71e31b6452a1bae25c21797eba7875aa42fd32767d4746cdc3ae15b379bf395e8c5d3332b35cd5f4b96940180fa97dc32af220d9e14884d71cf35eb1c18bc9364de6daa07852233e4e8d1e7070ac2b6a8c19055988770dfe02e912348f2871c01e935bec85cef43ab3959986e8f5d99d05249160e0d4b20c67a4dd4611b9a009d9858376db62b25885e604a737c51f1e97e4f12f623840f2786e2a150cedaf2b92f1687ff3e72f4701863382dc56ac0df89d7048f2c036e9c405f6d67a088705bd4db5767ce652e015562d4b4ca1f50706c250fb28881ed7fca8cf748bfeb9f0b7f1938f5a1de33b6c932fc667f66d474b8346dbcad2087121460dd67ce00bbab23c64680720f0e49579f7d59d208febd20d84c74efd8959479ccb345e6366a05317c7c0bc567b17e1cd84857a3fcac51b86b0e65771155f0b1a489da53c2ab4ce69aab4574986da02f5e4e70b982ae48137e17a07a07cd413b9b6002576046377b2ca4d08df5ff21bd0b79bbdbb4c633730728d7881c20b12649c8527a42c927aaa2f9a614fa5fe0c0184b4572ba73e16f7f1d072d9480012163232cb40009caf378e91f1fde01c7ba223e6a52f65074fa4406b11026dd16754ba0b9b790670a02fb8bc140fee9df7720709b169c5e48772a1ed87d9365a9139778e77e8831f7d51e15a34468a1546bc1bd40a1441c57792c138693d66c982f63168e8446eb3aa262a2ed47f740dbd4f958998619ac68a0a028d19620f410bdc38f1b13a4868a98d0d1508a1acbeb17e08f02c8105d32b49d2c2804c05b362828c7c15470f7bad947efee51bd7b78ebc2bebc38c8678c64e309dc6b88bc291dfed04c2e02c625ff69c73a4e1350902d1699fba8365f90c5be06b9e03a12b9836974e22a1d7194f4e462d7b1f29a1e153780364cb6fd8ecc9007d1006d8c8a83a31d3b4feaebb04eb5bebdc0e7687d51fca93cfcb8541f361b160b5087aec4be6eafc03ecb7c0d7dc2ad33189ba7b0f9a5ec21ab12e5eee89a08116f340a2c9e038a5e9462a9a8e3c90e558bc2f32b9e738add19ea0fe8a3311afaa1e18f666a898bfeb1d7c80fb166876d6aef7b8a74f6659c3f10ec7aace826e8ef26cc816d621ef94d83166b97f0b7d9bb2488e6819d88548f31fda0c6381693fa7c90cca6553affb2978437998307874b3df4dc8c3b8f097a4ef02f6b89026c511c5d286de882b8ca85ddde4f08150567e46932ad0dceceeeadb05ef9870a00000000000000000000000000000000000000000000000000000000000000004fd143e18cf011c6c85077eedb057ab7bd88f982fb81a0a8e0d413081a8e972e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cbfe3da08c70199a651ed512cd0b7ab568949408c01f0970b6797a869da2f602ebd22d790f509e213b7c1d3e6d2ef492d9de071663134476f224a264780e2903f6d0d2b408b9c2280a4db305d9ab0ae3878f5836bcc694fc52fc3e81f670e1241e32514ba37d05b9cc2a4aa18d3b99956c86b987774c2de31c2e5ec15070c20a9770441e9385c1aec8f514d6a605f6a04112a6b34ced6abae2a224aa5c5fc82c22ba6cceeb886d557fd11b13b324b47f6ffc9a95e118470df318885c526f9c0fe4822231c40eb33f3f93340ffa7c7fce2c817060f3b85a8aad28f4105a44ab2c34714529e33af2c14664c923e7ee5284837eb9c242f5fb43a1f5146f0526862ce034ccfd1bcf3202a0e33d16851b3781937e32094b8194811be7662c399b10155aff2c791d9a5e597473eef8c7919be05b3d7b3a230f2f9570a9a3170b5b28193794d63b34ebd25bbc860bd63d8402d983a3b4d8b19a479b3025f91259ec74230a006c55cb3f79317ae623ac3bb174c9f8efcf06a41e0839f9963275e326230df0c36ba5252a5aa29c23a52d4b302d351681e62a1dc20b5f01ba4e8153a9ed20e425018e59be7e1ed41e39553b2090b008b5711d39468b649f6e73fa19b74d2cbc12526810b827a67bd1f3326d5281d79d62272c5c39f56d247b64ee8667c703bf61eef1d4562e089d52b820226a20681377b9a1d70346eb686c79a16d8fb82e8ce57104d46a87f6f0b96744a326000114478be2ce3756e1a36ab108e3022f150e2d4228cfc4dedaddd5bc2d8e153d339f87cc7b838b5f1fc7df713a9bf1ee1779d3213850830253da82196461efb4c21369cc6f497ee4d3276bc088c9b5ea124989f3e8544044067a8adcb641d06f421bf54a5143c31a0166011e906c0079201b397b392b23cf7dbffe358934aa3b99ebdc4d50f3fa67b47c447df7653f1507f571cfeb4fc13e034120c9efebc5d9f1590c19e8f5526ce81f68e368aff1940005451f79ea387780cfef61c0cd30fd6807b1f59f4bca3211306293b688e9b4231731eb8adc8e8eaddbe02a7d07058617f8e09cbbef6fa6b9446ab3fb62359f1bc8d862693414bbb8548269cfffb32bdc05508d786c30838eae3d95c25b38be47d35b0293823a6065734a097881e4195a7ba3fc8e80df14a39afa405c2981015b"}} \ No newline at end of file diff --git a/axiom-query/data/test/input_verify_compute_for_agg.json b/axiom-query/data/test/input_verify_compute_for_agg.json new file mode 100644 index 00000000..a230042a --- /dev/null +++ b/axiom-query/data/test/input_verify_compute_for_agg.json @@ -0,0 +1 @@ +{"sourceChainId":1,"subqueryResults":{"results":[{"subquery":{"subqueryType":1,"encodedSubqueryData":"0x009165ed00000001"},"value":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"},{"subquery":{"subqueryType":1,"encodedSubqueryData":"0x009165ed00000047"},"value":"0x2a4a0345b08e0c50805158030213038e9205244c5100b7223068018ef1648009"},{"subquery":{"subqueryType":1,"encodedSubqueryData":"0x009165ed0000000c"},"value":"0x43727578706f6f6c205050532f6e696365686173682d31000000000000000000"}],"subqueryHashes":["0x162136872c2a5889ac1f6f816de9a0915382f52d308ccfb85bf51405f01f6e57","0xec9c61c069dc9edfb82f6e76435797487af1d8b8f040b26deda52deff1c9e73b","0xf36a01afa5be89a2e56cb5656e45a5547d917141f6e3c153b140213abb204059"],"numSubqueries":3},"computeQuery":{"k":14,"resultLen":2,"vkey":["0x0001000009000100000004010000010080000000000000000000000000000000","0x35b8ef309e3b65bfbeab8266c382fcd073284769cc8e15fde28ce260b0e02b28","0x7a75d7385d618442668530435a6926c8d39f686e78ca020a69969ec8be555615","0x0000000000000000000000000000000000000000000000000000000000000080","0x0000000000000000000000000000000000000000000000000000000000000080","0x0000000000000000000000000000000000000000000000000000000000000080","0x0000000000000000000000000000000000000000000000000000000000000080","0x0000000000000000000000000000000000000000000000000000000000000080","0xea4ec579288ca155462bd5dfd2222cf4a7adaa79b11b48f53ec2cdbdec87fc4b","0xe38d2d4d1d79a078ce281e41a3f4b9e5316909f5c7c5388141c00bcee2042424","0x9a8a73c61379755ce23a0e5370405cd4cc79d66af6d9d30e61a4ea3e8bd4ab47","0xc8822d094658052bcfc4e79e3e8341db5cb3ece6dc77a7d369f466f986334a03","0x7084bd1bce301a47e81c5d6d2f7ce4df819e6fe92a1426da80727e87b89f9818","0xfcc0f0a01464e41ebe5f5d15015e2a51fdfa9da60c8f944d6a68f420f2d7d162","0xb3dc5cc56d6cd5148c12906ea8715aeb14b627a43236083ecfd7786edc541641"],"computeProof":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493472a4a0345b08e0c50805158030213038e9205244c5100b7223068018ef164800987d532ace52383a0b5682956dc477135ed31253cbe2477b959f6bfac282e3214eccbd6ed1acad6639d66f291592c7391d8fd4124df300c73f5fd2fc5dd2d7c4e4b0ec319e7dad51d5fb8d5f8779d2940ecf0c7815ad11c015a81b56a86525222f8cadce02976c8348a7c190c269b949785faf65b2a94c6784d2e07469f33e014ce2c4a867c190b5a0888d1dbc3f7f5b038dcba717268c6690e8dff359aad134974229a9b1693f90114eb1a65fc978e2a4911d56bf422187bfdef4a98170fca457f392c45fd3af8f1c0ce10c771376df53b6d15635334e475806a11dac5d5bd056fdb6641762d4881e74e19fb05cd027eb7dcb696965a4fc5a41e9aa1519209161e7690e797276e9002a5359a935f300735c79fed6e8941b71eb148f0eb4ed354a30bef0c1077ed2e58ca5868cb9df86147707a4bfdd0f437efe84d549900c70a6db8f9a7a7c3056fc9e765a930d867ac5b7913844be1249b31c93dd329d1ed6a4cadc5227abbf36b3d67f91c1d5920c0073460897b5e68cf3731afd94a39d118e733de216e63a2598ff00f4421cdc139b327a19f15db4e28932fe302716e3415aae366692fe052fa7b3cd2ff1eb1ed8bd1bc3d31c009cabf5d797bb136b023021543d4830df7f33a82f64051d2ef06605e8051de8aa3ddfeff1c74fd11a5f16f08e6b763550ffc6c25c2c780eb193324007541cac9cb89b9102806f055d8fc6d67480da32621700ea91b3803e147721b65424d1fc9bb4ac1eb7fbcbca7c265053ce0a358d2fceb2119086a02d1987fbe94688f607b2164950ee9db16dadd142f0154996e00faffcab77c440b4b9190767a2c62984e3502c6ddae6640d8b37707054fc7066689eb908501ea79d689ee9bad83529e7ce6149aea0d073f4048c4087d4736bffd49f9797f9fb37b792ef529a04bc58b11465508bf84ca108e663c292b9156c487d115fedc0ae58fdfee0edd570e88a0653e3920ac319da42b89d90ce4c6b3ca4da385ac07fb24708a153e86dfe3d9b322ba755c1f62505fb282491c704132f2497b5a84d5c638ff6b0dec79da81aa711aaa6d255c4f82ffe869110a3834e70f4525ecd1c08c202427719d4cad2d5585a364753faa4f6a4ac2aed01f657bf176821809667c5e995788cb9bffb917688e54df15e990ca6ad9e7725d07b2ad33c7c5622fbfea8be4a4241876fb84180d8f86f54fa4897bc14f47acbc2e04b251109e995493a19a4feb9621df65f5ec42be7acfa16f1d174964761cdb081e54711bd452fd0eb1be3f719a2bb788bb5ebae76a71f7e65d4bb950ce16b71475982e553d3484e272f38b6bb18533b8903798ec4845f4c0cf0500048ebae4159886ab22dbfa14e6c4a5cc021e16f66861606aced41374d9b5f625d77ed0b613ada9227ac6f913d7628f2f3531d6ee046539129e47ceb75bd984fbfb700797133c4cee039803e18912bb82cb68b73fbb1dea7b884d17a5b36cf15ef64756890000000000000000000000000000000000000000000000000000000000000000009c4953811a956be91eb1d8718412027d9486f54e5523703e00d5dbe25526351e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000976fc938729f688d789caaeeedab85de6b34f9d6ddf4f2593e3f3ac09012102b9d9cab3f74331a33308ff77920ae33ab593b4ce2dbefed5c90daaa719fcaca02ba8e3afccb8cc4f322e0b2522582650c88db6541bdb8fd289ebf6abd266f2c10321eaf7e17244e0ee101b951729008e0e7e956c3002940030e981f4916236308e1f784bace2038f78f3a03fc73563817324ab570bd7b91e6dde2a5c0a293b105e1575a0fc52ca36948a3ede870349f170c1afb8388fa6d9d36e0721c99968e0b0388ca8b5addfb8bccee458b0dcafe1945abe84653eedc641b90f1b7dad84e18eb31d350adff47640f4d543907719f8ede887b44279148cc3018014792e39d060b857b4eb70c7b4659b3c8d08a146d9f150f6bbdc2439201c8ad449e9c1c910283637b97584e8e747eabad1c87e88ed4f14ef4c74e637d726d9928204d8ecc108bd1b612c2465b12d8338e34215e54f1306706fde6f54e2fe819d7a78472fb2330eb174b40c6450b9709d2cee0e8b59bd3760b0aa6db616345d47220676c020d2783ed73f1eab646d3efb3a0437fd4e2fee30ba6890a902b7c69f3c4b242590147799f8558feaa3363822e3742c0e9e25e294464e7eea0465645c5a6f9a320281e95c0e91823c5ce9a2d723b4d63f0fbf76ed6a82f1128dfa6b51a784a58a40c498f65bda7ce26cb4b910c85728befd794d28ab13cf4c6505aaf4813603b83180497afe193ae72aebc38ed1688065015a04bdb147b2af5d2d9deafd26c04bf1f6c0c9ff7855c145c7e0abea10b3d333a6d0b28db5db9900891153bd61597a81392a5506568a0852dc2fb5220a37d49de3913ec8c4f15e1affd945117655e922289a2b49f577309d897093e700991f767332b08c969d679a2a8173df4913ce5218690b276dd99b35aadadb0e97f6e9574af997cfb7ba5375f3deaffc3a8d1972aed9dfe7d96adedd065f1db4a78f88826e4bd5402c62d483f8e70d9a2b40e73094c0ed919da4a69384ecfe4c3c504834186e34f81fc278e159a7c5d5391a0e308b90ffbabe6372b1f33082234c8b0bfa62196b7800a3d18d9e0581ece59556e2d889c509b5d9787069306c5283d3b3c0729f7bbd1dad1a24534f6daad74b36c28cbed899959594d878e5a80e2e4a62020aa705c867b9ba02a027d979768dc3c18"}} \ No newline at end of file diff --git a/axiom-query/data/test/promise_results_header_for_agg.json b/axiom-query/data/test/promise_results_header_for_agg.json new file mode 100644 index 00000000..40ec3290 --- /dev/null +++ b/axiom-query/data/test/promise_results_header_for_agg.json @@ -0,0 +1,25 @@ +{ + "results": [ + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 1 + }, + "value": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 71 + }, + "value": "0x2a4a0345b08e0c50805158030213038e9205244c5100b7223068018ef1648009" + }, + { + "subquery": { + "blockNumber": 9528813, + "fieldIdx": 12 + }, + "value": "0x43727578706f6f6c205050532f6e696365686173682d31000000000000000000" + } + ] +} \ No newline at end of file diff --git a/axiom-query/data/test/promise_results_keccak_for_agg.json b/axiom-query/data/test/promise_results_keccak_for_agg.json new file mode 100644 index 00000000..021610b5 --- /dev/null +++ b/axiom-query/data/test/promise_results_keccak_for_agg.json @@ -0,0 +1 @@ +{"responses":[["0x0e12a985789e947d5c94b2f0dfee712cdff776434e0a8c13379a81ec9cabbd90fb07e7779638fe0e542e6a55e3e4068d1a0a71e178637553e2abe5177c3a3d7d",null],["0x19e99f81e6a4933a1dd41191733bd7b11ee9dbdf74f3a508e04514eb5053a57ea1aba88a1e5835420920b9de5a5c9489f57d8bee868c142f47f3716b9e1352c6",null],["0x1eeaaf21efadf32e39e4db63eedb3b791e5586e373bcdc783c42ea6a8028b48907449018b55a87abbc033d85102853995a41aeaa8544452dc2240aebda5e9cb3",null],["0x3020fe1044a376596e399087fc0e5cb8783a853e2c85c7206514dc2cf71e42f31b7b0f134de7422015a5421b9d4c0fbd370493f06144b53b5ca3ebf419756f00",null],["0x4755084f1c80a73395852c7ede586dddb3b8db7828e4ad2cf3259f693ac4f8750000000000000000000000000000000000000000000000000000000000000000",null],["0x477c055e69de14e3bbfe2af0389e6c3ac28ffb5b0cc8fa26b543ac47857fd64616fed94ec0cf0aa4b075e19cb2b6ff780a6c11e9b1c60195b036c1b8622c0147",null],["0x5079605c269d43ce5a544d19e7832911a862f74b0df14d7b604232869b39ed78a0d8feace83653ed55151dbf0959b0524c9e3187f928e41aab067daed472d269",null],["0x5459bcc28c212f4f7ba9b7075fad7c3f2292e83997007998dd27d5369d469616752448ae955fe7ab9d28a97fb104fccca709d7ef6c73fd814ae3fefb5d211d7b",null],["0x594f03e3c07dfc61489a41b9645c2715fa0cbeebd2c13d25b30add4bbfd315950000000000000000000000000000000000000000000000000000000000000000",null],["0x5a58cfc592d45892857d908e3c8c2da8c40b1cab2d4c941546ed9232114d1b373409945b63c2e1f8a04cc3236cf0d4ac7a340695c8f5641a526dc6c783ca91e0",null],["0x615ab700eca77f00034ff0b208a7c9e876cde7cd947aaebb2cb004ce461a1e43b2838c6d8d70917ab69e952493e18b67e0e1f22745f3b30528bcb4346e47455b",null],["0x655090a6192edeb76e76b01476071607739746e8197158c58ed6d3a001021e43c3c2b03b5831d3cf7bfc8c96e38b1f5e1c3a19745b220cdc5f9eec15f6a1f9c1",null],["0x68d509b78ca15e200209038efa53ab5fc30eef61b87bd1c3417fd4eab256c255c317e0c0c4ac8c49716549e55a7ab0252ba4116ea7f7a518fb1441d02ef1e248",null],["0x6d495f5672d5d7482e1063eb1e1381edb72cabfdfa2c218cc38461136cc513970000000000000000000000000000000000000000000000000000000000000000",null],["0x79a9b620dd7c6e8a40e973ae2193a378de5c45d9ff7917e3f31d01b019ac0527791c7933517944067f8dcf8cdbb4bf9c634d07c0f1f91ad3648920bc29a6884d",null],["0x79e3279ab366ab5dc5c1f8ad4aad3ce7689086e4f8e25572c835f43d4a826432250018a435d17b5473e05f884ea1af54be447730a201fd72a707da51a820345c",null],["0x82dd59d4c6cb74831ca6eebc0b9beb938a7c0e33e000c160264520780daecd8737d210b023995e28e43315bb1a98eeddde85a35b7321d7162f51c9425fd5f905",null],["0x833b246c8bdd751a108869f958b69cfaee4a03c036905b90c39bfc986bda80ee0000000000000000000000000000000000000000000000000000000000000000",null],["0x9071e895206e9ac84c8ae51e7180e9479827ca3288175f4615fdc724e28008d793071e157ae565f7dbfea93c3da2e03caf192a8d97e1e4f752cad4b03dad40a8",null],["0xa3e48aa8f6637e4a196bf2e5c51d63b766cee11645a1e2cb20e38fd6c2f676163eb43d4625448a26adb10215f7f092a6fc8622a21b35048c2b2ef1d991162d84",null],["0xa60d89ad021ef63b49250141377cc3e835945e5d4136ffc1909047c4ecca3af70000000000000000000000000000000000000000000000000000000000000000",null],["0xbb33fff29f3094bd9de4d3668fd20f2a45f8028c0c532f4e10b2368ab5c783aa22b2e40d32a936ed08307b41db986d35160fe5917c8d2609d27e7bd02f4b767e",null],["0xbe8c5ff2ada208b4577bd49d7660fdaf3e17983530f5d1d574126c5ee743de900000000000000000000000000000000000000000000000000000000000000000",null],["0xc364b3480c792d14112d17f167581d8fcf74b6d7805bc1ef1526f5fa7415be400000000000000000000000000000000000000000000000000000000000000000",null],["0xcea8ad8ff6608360c6c43b229e482664a5ce0f23090fab77acd12803c6094c03f7533e213fc68258934880ec7189125fc6071e01f20aa465e56f7c7ecfccb800",null],["0xd85f3204700a79681b53fd8304a44141bd2af2fe44b2265aed636c16ed6fe223c68b30cb54482f31743afe91abb6841704d275a6a378f7faa389d387c384d568",null],["0xdad303cf18a459358a346f84e5e3a3065256a3584769c3cfae5b1729e168ed52223b4e206927956a87b2bd3be0b39f6176172c13d773b99e11b4d0ac8c1307d4",null],["0xe2327533e28e723bb66ff3d0bfcb32bad0c070e8935d13c0a36a3126c8f3b7d082c76ea3624a6f4e2d62c5375b3dc0a86bf69810715bc7fddebc92937cd4b0e8",null],["0xf3d9b0595399d791bb7b2e91630175518483c8c517d1097e4b67b0ae78c1bb21eab78f0a8534330a079c8179e08f594fe8ed3483c2df1c46e7177e4839ab2114",null],["0xf47e6584af17667881494720b50dd063d0900b288438df7a37e2e91440aedd2388e177538a14ffca910f9c6b56f6bed99d1f748fa103093f372ecbe9db819e38",null],["0xf99a2e4fdc253c869f26e9e13e8794c41a3bebb7498fe90513ff1996378db3d2a7d918baae6f3c07d7d0cbfac129925e9080f61cd113b60dc3970edff82f7338",null],["0xf90218a0d85f3204700a79681b53fd8304a44141bd2af2fe44b2265aed636c16ed6fe223a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794249bdb4499bd7c683664c149276c1d86108e2137a09ce4561dfadb4fb022debc7c013141a36a23d467f4268c0e620a7bba05b174f8a0ec4c8ec02281c196e3f882b4061c05f4f0843a0eaf72a3ee4715077220934bbca00dfecab5d97a4da361b92c29157cd6a5089b55facee007eea16005bfa8d82694b9010019827c809158a49a418c22c32ac510b061e8807074412014a10a0000260c24e72a4a0345b08e0c50805158030213038e9205244c5100b7223068018ef164800936740064d6428223d415104ebcc1a16566206424a707d0514481a60080090a345e00072446d000210129c58c138a5822ac68620618e7acaacd34af1655343aca334011d04063a29203a5a2229c44407a9681ed3a60960330706201764a382a170e8c20810005ec000078c19589cfd08033508015e14a053022890b4e60b20a51349024866b0c00a7005b31410673c6c5144872f284b9e612bc180018d152f142801ba52780a3d2080d90ec9605c6fbcf4934019192443014440896b64f504df88707f26d7f80c9a2839165ed8398243083981192845e5040299743727578706f6f6c205050532f6e696365686173682d31a0174c6e45dc64f9ba8ff83f63eac9a51fd602da5118003689ae944986a309d682888ee012321dd76cd3",null],["0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000348bab8f4d2acdd4022f4b7becb8ba36a260bd9f699aa4df0a0660dc772ddcfcfff5804d8cd309c79ff26f19ecc6a9cef13556c9d5ddb18afae277d71ad7d4114f1a2ab29b70f47ff5c88dc7048bb23fcc4a59fbcc3b9f0e9529297afb28cb8a995310788441a017c79a1fc0edab3074e6d5495712b35e120c446301b8b60d8c0000000000000000000000000000000000000000000000000000000000000000c4933b4a3d6a41bf6c8009e24436a19e3e91a6f4198a0f498ade1179d29785000000000000000000000000000000000000000000000000000000000000000000dff0f5a47ed23e4dd682fa5b20c861c8d616f0124bf3366def9f620e1569ade60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004755084f1c80a73395852c7ede586dddb3b8db7828e4ad2cf3259f693ac4f875",null],["0x0001009165ed00000001",null],["0x0001009165ed0000000c",null],["0x0001009165ed00000047",null],["0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493472a4a0345b08e0c50805158030213038e9205244c5100b7223068018ef1648009",null],["0x0000000000000001162136872c2a5889ac1f6f816de9a0915382f52d308ccfb85bf51405f01f6e57ec9c61c069dc9edfb82f6e76435797487af1d8b8f040b26deda52deff1c9e73bf36a01afa5be89a2e56cb5656e45a5547d917141f6e3c153b140213abb204059",null],["0x0e00020f000100000900010000000401000001008000000000000000000000000000000035b8ef309e3b65bfbeab8266c382fcd073284769cc8e15fde28ce260b0e02b287a75d7385d618442668530435a6926c8d39f686e78ca020a69969ec8be55561500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080ea4ec579288ca155462bd5dfd2222cf4a7adaa79b11b48f53ec2cdbdec87fc4be38d2d4d1d79a078ce281e41a3f4b9e5316909f5c7c5388141c00bcee20424249a8a73c61379755ce23a0e5370405cd4cc79d66af6d9d30e61a4ea3e8bd4ab47c8822d094658052bcfc4e79e3e8341db5cb3ece6dc77a7d369f466f986334a037084bd1bce301a47e81c5d6d2f7ce4df819e6fe92a1426da80727e87b89f9818fcc0f0a01464e41ebe5f5d15015e2a51fdfa9da60c8f944d6a68f420f2d7d162b3dc5cc56d6cd5148c12906ea8715aeb14b627a43236083ecfd7786edc541641",null],["0x0200000000000000016d4b33819da0c6366eee8749885f19a752fa5aacb826ade03dd97528269c562c0e00020f000100000900010000000401000001008000000000000000000000000000000035b8ef309e3b65bfbeab8266c382fcd073284769cc8e15fde28ce260b0e02b287a75d7385d618442668530435a6926c8d39f686e78ca020a69969ec8be55561500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080ea4ec579288ca155462bd5dfd2222cf4a7adaa79b11b48f53ec2cdbdec87fc4be38d2d4d1d79a078ce281e41a3f4b9e5316909f5c7c5388141c00bcee20424249a8a73c61379755ce23a0e5370405cd4cc79d66af6d9d30e61a4ea3e8bd4ab47c8822d094658052bcfc4e79e3e8341db5cb3ece6dc77a7d369f466f986334a037084bd1bce301a47e81c5d6d2f7ce4df819e6fe92a1426da80727e87b89f9818fcc0f0a01464e41ebe5f5d15015e2a51fdfa9da60c8f944d6a68f420f2d7d162b3dc5cc56d6cd5148c12906ea8715aeb14b627a43236083ecfd7786edc541641000008a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493472a4a0345b08e0c50805158030213038e9205244c5100b7223068018ef164800987d532ace52383a0b5682956dc477135ed31253cbe2477b959f6bfac282e3214eccbd6ed1acad6639d66f291592c7391d8fd4124df300c73f5fd2fc5dd2d7c4e4b0ec319e7dad51d5fb8d5f8779d2940ecf0c7815ad11c015a81b56a86525222f8cadce02976c8348a7c190c269b949785faf65b2a94c6784d2e07469f33e014ce2c4a867c190b5a0888d1dbc3f7f5b038dcba717268c6690e8dff359aad134974229a9b1693f90114eb1a65fc978e2a4911d56bf422187bfdef4a98170fca457f392c45fd3af8f1c0ce10c771376df53b6d15635334e475806a11dac5d5bd056fdb6641762d4881e74e19fb05cd027eb7dcb696965a4fc5a41e9aa1519209161e7690e797276e9002a5359a935f300735c79fed6e8941b71eb148f0eb4ed354a30bef0c1077ed2e58ca5868cb9df86147707a4bfdd0f437efe84d549900c70a6db8f9a7a7c3056fc9e765a930d867ac5b7913844be1249b31c93dd329d1ed6a4cadc5227abbf36b3d67f91c1d5920c0073460897b5e68cf3731afd94a39d118e733de216e63a2598ff00f4421cdc139b327a19f15db4e28932fe302716e3415aae366692fe052fa7b3cd2ff1eb1ed8bd1bc3d31c009cabf5d797bb136b023021543d4830df7f33a82f64051d2ef06605e8051de8aa3ddfeff1c74fd11a5f16f08e6b763550ffc6c25c2c780eb193324007541cac9cb89b9102806f055d8fc6d67480da32621700ea91b3803e147721b65424d1fc9bb4ac1eb7fbcbca7c265053ce0a358d2fceb2119086a02d1987fbe94688f607b2164950ee9db16dadd142f0154996e00faffcab77c440b4b9190767a2c62984e3502c6ddae6640d8b37707054fc7066689eb908501ea79d689ee9bad83529e7ce6149aea0d073f4048c4087d4736bffd49f9797f9fb37b792ef529a04bc58b11465508bf84ca108e663c292b9156c487d115fedc0ae58fdfee0edd570e88a0653e3920ac319da42b89d90ce4c6b3ca4da385ac07fb24708a153e86dfe3d9b322ba755c1f62505fb282491c704132f2497b5a84d5c638ff6b0dec79da81aa711aaa6d255c4f82ffe869110a3834e70f4525ecd1c08c202427719d4cad2d5585a364753faa4f6a4ac2aed01f657bf176821809667c5e995788cb9bffb917688e54df15e990ca6ad9e7725d07b2ad33c7c5622fbfea8be4a4241876fb84180d8f86f54fa4897bc14f47acbc2e04b251109e995493a19a4feb9621df65f5ec42be7acfa16f1d174964761cdb081e54711bd452fd0eb1be3f719a2bb788bb5ebae76a71f7e65d4bb950ce16b71475982e553d3484e272f38b6bb18533b8903798ec4845f4c0cf0500048ebae4159886ab22dbfa14e6c4a5cc021e16f66861606aced41374d9b5f625d77ed0b613ada9227ac6f913d7628f2f3531d6ee046539129e47ceb75bd984fbfb700797133c4cee039803e18912bb82cb68b73fbb1dea7b884d17a5b36cf15ef64756890000000000000000000000000000000000000000000000000000000000000000009c4953811a956be91eb1d8718412027d9486f54e5523703e00d5dbe25526351e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000976fc938729f688d789caaeeedab85de6b34f9d6ddf4f2593e3f3ac09012102b9d9cab3f74331a33308ff77920ae33ab593b4ce2dbefed5c90daaa719fcaca02ba8e3afccb8cc4f322e0b2522582650c88db6541bdb8fd289ebf6abd266f2c10321eaf7e17244e0ee101b951729008e0e7e956c3002940030e981f4916236308e1f784bace2038f78f3a03fc73563817324ab570bd7b91e6dde2a5c0a293b105e1575a0fc52ca36948a3ede870349f170c1afb8388fa6d9d36e0721c99968e0b0388ca8b5addfb8bccee458b0dcafe1945abe84653eedc641b90f1b7dad84e18eb31d350adff47640f4d543907719f8ede887b44279148cc3018014792e39d060b857b4eb70c7b4659b3c8d08a146d9f150f6bbdc2439201c8ad449e9c1c910283637b97584e8e747eabad1c87e88ed4f14ef4c74e637d726d9928204d8ecc108bd1b612c2465b12d8338e34215e54f1306706fde6f54e2fe819d7a78472fb2330eb174b40c6450b9709d2cee0e8b59bd3760b0aa6db616345d47220676c020d2783ed73f1eab646d3efb3a0437fd4e2fee30ba6890a902b7c69f3c4b242590147799f8558feaa3363822e3742c0e9e25e294464e7eea0465645c5a6f9a320281e95c0e91823c5ce9a2d723b4d63f0fbf76ed6a82f1128dfa6b51a784a58a40c498f65bda7ce26cb4b910c85728befd794d28ab13cf4c6505aaf4813603b83180497afe193ae72aebc38ed1688065015a04bdb147b2af5d2d9deafd26c04bf1f6c0c9ff7855c145c7e0abea10b3d333a6d0b28db5db9900891153bd61597a81392a5506568a0852dc2fb5220a37d49de3913ec8c4f15e1affd945117655e922289a2b49f577309d897093e700991f767332b08c969d679a2a8173df4913ce5218690b276dd99b35aadadb0e97f6e9574af997cfb7ba5375f3deaffc3a8d1972aed9dfe7d96adedd065f1db4a78f88826e4bd5402c62d483f8e70d9a2b40e73094c0ed919da4a69384ecfe4c3c504834186e34f81fc278e159a7c5d5391a0e308b90ffbabe6372b1f33082234c8b0bfa62196b7800a3d18d9e0581ece59556e2d889c509b5d9787069306c5283d3b3c0729f7bbd1dad1a24534f6daad74b36c28cbed899959594d878e5a80e2e4a62020aa705c867b9ba02a027d979768dc3c18",null]],"capacity":200} \ No newline at end of file diff --git a/axiom-query/data/test/subquery_aggregation_for_agg.snark.json b/axiom-query/data/test/subquery_aggregation_for_agg.snark.json new file mode 100644 index 00000000..a4354b0d --- /dev/null +++ b/axiom-query/data/test/subquery_aggregation_for_agg.snark.json @@ -0,0 +1 @@ +{"inner":"FAAAAAAAAAAAABAAAAAAAAHBptBV4dis9eg0O8UVrhJF8CV9MapNnhZySpxsS2QweDNngjEeDi9bnQTa+jledHDmICW2AmSF4y30H09GFCpfLUSrvavoWuozp/Ad06Oy4EeZpE+/7J6vK4O/2LANIgAtAAAAAAAAAP1wJQgtQLtYxZ1UeINFm9U9TTxHDLPrfBE0D/b775EmPl1tgtomNWELsHXB2NowpSKuA5v8Nc86V3s76mtgXiYKXW5zBpg+qfIs9I5cAO15zGVMkQaks8/L3wVTXXewD63eNbh7H71R1ehsVUMzdMYqQrya2jANm8sCpWEmGoAYj4lY8NsGPShduinYDy1ye1qplMXRyStcM3yWs7YAEAwecBaSZ0SxzTfksz2EEQNJkGyq/nAWnZIoV4lvUgsOKS3WLLzQ4wLJpCQCZXrIQuRdM56nxWWp0LaUsyViOzAmvyeOK2O5LwT9V5DY/u0rapNtd02GEfCcbNwBCP6TQC7rDL+EbtDzzVWDP5YCgy0GokQLTp3O/oyrMKbhNKjlFEYfk2EQ97zzrhTm8QYa9xIS3yf4EerEn9s1RkKHNdZoctnsVhW6f6p5iJ9F+9czQ1mTPoFi9ufv6gmXAJZNoxP5x8xXE05gO/s8lbqqM2wVDPXNR6uLU3RVBE/ZmfdNQHLU/cg4l0uGr9n8i6uP4vk6N6+RAPjatx+ZEF2i+Y9SISuZvMNFmz2kv8R04D9ryazVRcAIh8hdDlEB7XS8ZgN+ARK5xcqSMghFTvgjb1nybM3o7K8ePpDswJjsCnk7Eh1DiSunJBYWC9vLT+IQLJIDG0n34hBvZNRA/SIGXetoAd4eXIR/A8rmwKj2/lgGYJHjmlRwZnO9x9NRIW4A3CPqo8mcwJ1xJfGlNoktJBFac4H0hJpGFqt0162FRDlNU0TTFzmbpwVo7WzIxntn4Z3Nx0I0V1bcFwmeaHIbi3VuRJyFDAsOjR2VenzfnVuvjfdWSZHOW54yyYscR6MFEGc4IASYPHbqnsOg3J/9rsxdWM/j69OB+Pu08RUIVJw0VlzTSvNwCFHzUnc8X/SQKyOIb6qaOVn00rd/wJRPZl8e9Uni4vN1+wRKt/3wz4tc3CFhs437VAgW/CeCZNndsQbLnsiQGhPGi3sU3o8jxRKRlNPT5zTuVN+k6i5cOWvDbf3xHP3lUnNh1bfsjzpfgqW/pD1th/+VL4bB6hAtqahIvcyMhcWCnfTrggKg8wTu0KtO2tfNlP6iy2BXBuFUIhoDz6Wfie7HG1PLzwD1atZa2bweRAmMrT6Juh0Fe3INa993dROftykQTjeAF0Xaub8bfuJU2Px/MpH5mv9qRdcc+ZnBTG9NaeT9g9+yfqlluLWkbwciWXUoF8mZhqORIRRIOeenKqdkXXeI/1WQC/OSFa8UUWIHLbQsO3+KOwOcU0PnJAReQHJOIr7Ce7+wCBLcgh28fcvZUcUNN0O36nwe+dBG5jMQW8fsOikVHtfS65XKTU+uwt+pJj1cNqjbsmlI64HrW2EFUKSmZXm7pe/wdKlecbJPYLT8tuSKfTD4aAMA4lz0hW1xY0x2SX+FfOX1xXWtfBlXdndicwTqz38YWqtbKM5BFVTIXSfiTPgFwfRjIOurzoPSrJSoTrjXw0S2aOUyeMyMAV1Ik2jRi7kHN2leNtyMAcRk853u9z6yWqj59phINK+9IhQC58m+pBcsIqpLVWTFr1RUnoFGnCtgcTQ52u+MktEd5XN8w49qEfYI+WV6CnfnNU4x7dSnLV1/l8trE6469JPp1X8pBGk8GZQrgRmeNaa3QzQ8WiwLDLgTUiQ39LSxc7qOeQoI+/KTc6ABoXtJoG5pZV08pOZcEVgVseuzKGxFsYim5NMJonhaZVgaEyZmt5nXJTX05wN0esw2QtLVU1YB23pw5Uc9e/VYaHOSh+/mwfvbAl1DSWdBHWOjK93KJD/4VYrtSmZadbTkL/zssKtAc4VIM0hMq86z0byeZWpmCh0SXb5u5PSAR2PGwlM7S5iAGkGKhR1ZctcwSRiVdPi6gC0yfCf1qPW1tHxhttHsy8c5zCOPSAEAAAAAAAAAEgAAAAAAAAADAAAAAAAAABYAAAAAAAAABgAAAAAAAAAQAAAAAAAAAAMAAAAAAAAAAQAAAAAAAAACAAAAAAAAAAEAAAAAAAAArwAAAAAAAAAuAAAAAAAAAAAAAAAuAAAAAAAAAAEAAAAuAAAAAAAAAAIAAAAuAAAAAAAAAAMAAAAvAAAAAAAAAAAAAAAvAAAAAAAAAAEAAAAvAAAAAAAAAAIAAAAvAAAAAAAAAAMAAAAwAAAAAAAAAAAAAAAwAAAAAAAAAAEAAAAwAAAAAAAAAAIAAAAwAAAAAAAAAAMAAAAxAAAAAAAAAAAAAAAxAAAAAAAAAAEAAAAxAAAAAAAAAAIAAAAxAAAAAAAAAAMAAAAyAAAAAAAAAAAAAAAyAAAAAAAAAAEAAAAyAAAAAAAAAAIAAAAyAAAAAAAAAAMAAAAzAAAAAAAAAAAAAAAzAAAAAAAAAAEAAAAzAAAAAAAAAAIAAAAzAAAAAAAAAAMAAAA0AAAAAAAAAAAAAAA0AAAAAAAAAAEAAAA0AAAAAAAAAAIAAAA0AAAAAAAAAAMAAAA1AAAAAAAAAAAAAAA1AAAAAAAAAAEAAAA1AAAAAAAAAAIAAAA1AAAAAAAAAAMAAAA2AAAAAAAAAAAAAAA2AAAAAAAAAAEAAAA2AAAAAAAAAAIAAAA2AAAAAAAAAAMAAAA3AAAAAAAAAAAAAAA3AAAAAAAAAAEAAAA3AAAAAAAAAAIAAAA3AAAAAAAAAAMAAAA4AAAAAAAAAAAAAAA4AAAAAAAAAAEAAAA4AAAAAAAAAAIAAAA4AAAAAAAAAAMAAAA5AAAAAAAAAAAAAAA5AAAAAAAAAAEAAAA5AAAAAAAAAAIAAAA5AAAAAAAAAAMAAAA6AAAAAAAAAAAAAAA6AAAAAAAAAAEAAAA6AAAAAAAAAAIAAAA6AAAAAAAAAAMAAAA7AAAAAAAAAAAAAAA7AAAAAAAAAAEAAAA7AAAAAAAAAAIAAAA7AAAAAAAAAAMAAAA8AAAAAAAAAAAAAAA8AAAAAAAAAAEAAAA8AAAAAAAAAAIAAAA8AAAAAAAAAAMAAAA9AAAAAAAAAAAAAAA9AAAAAAAAAAEAAAA9AAAAAAAAAAIAAAA9AAAAAAAAAAMAAAA+AAAAAAAAAAAAAAA+AAAAAAAAAAEAAAA+AAAAAAAAAAIAAAA+AAAAAAAAAAMAAAA/AAAAAAAAAAAAAAA/AAAAAAAAAAEAAAA/AAAAAAAAAAIAAAA/AAAAAAAAAAMAAABAAAAAAAAAAAAAAABAAAAAAAAAAAEAAABAAAAAAAAAAAIAAABAAAAAAAAAAAMAAABBAAAAAAAAAAAAAABCAAAAAAAAAAAAAABDAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAALAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAARAAAAAAAAAAAAAAASAAAAAAAAAAAAAAATAAAAAAAAAAAAAAAUAAAAAAAAAAAAAABZAAAAAAAAAAAAAAAVAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAXAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAZAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAbAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAdAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAhAAAAAAAAAAAAAAAiAAAAAAAAAAAAAAAjAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAlAAAAAAAAAAAAAAAmAAAAAAAAAAAAAAAnAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAApAAAAAAAAAAAAAAAqAAAAAAAAAAAAAAArAAAAAAAAAAAAAAAsAAAAAAAAAAAAAABKAAAAAAAAAAAAAABKAAAAAAAAAAEAAABKAAAAAAAAAPn///9LAAAAAAAAAAAAAABLAAAAAAAAAAEAAABLAAAAAAAAAPn///9MAAAAAAAAAAAAAABMAAAAAAAAAAEAAABMAAAAAAAAAPn///9NAAAAAAAAAAAAAABNAAAAAAAAAAEAAABNAAAAAAAAAPn///9OAAAAAAAAAAAAAABOAAAAAAAAAAEAAABOAAAAAAAAAPn///9PAAAAAAAAAAAAAABPAAAAAAAAAAEAAABPAAAAAAAAAPn///9QAAAAAAAAAAAAAABQAAAAAAAAAAEAAABQAAAAAAAAAPn///9RAAAAAAAAAAAAAABRAAAAAAAAAAEAAABRAAAAAAAAAPn///9SAAAAAAAAAAAAAABSAAAAAAAAAAEAAABSAAAAAAAAAPn///9TAAAAAAAAAAAAAABTAAAAAAAAAAEAAABTAAAAAAAAAPn///9UAAAAAAAAAAAAAABUAAAAAAAAAAEAAABUAAAAAAAAAPn///9VAAAAAAAAAAAAAABVAAAAAAAAAAEAAABWAAAAAAAAAAAAAABWAAAAAAAAAAEAAABEAAAAAAAAAAAAAABEAAAAAAAAAP////9FAAAAAAAAAAAAAABXAAAAAAAAAAAAAABXAAAAAAAAAAEAAABGAAAAAAAAAAAAAABGAAAAAAAAAP////9HAAAAAAAAAAAAAABYAAAAAAAAAAAAAABYAAAAAAAAAAEAAABIAAAAAAAAAAAAAABIAAAAAAAAAP////9JAAAAAAAAAAAAAACwAAAAAAAAAC4AAAAAAAAAAAAAAC4AAAAAAAAAAQAAAC4AAAAAAAAAAgAAAC4AAAAAAAAAAwAAAC8AAAAAAAAAAAAAAC8AAAAAAAAAAQAAAC8AAAAAAAAAAgAAAC8AAAAAAAAAAwAAADAAAAAAAAAAAAAAADAAAAAAAAAAAQAAADAAAAAAAAAAAgAAADAAAAAAAAAAAwAAADEAAAAAAAAAAAAAADEAAAAAAAAAAQAAADEAAAAAAAAAAgAAADEAAAAAAAAAAwAAADIAAAAAAAAAAAAAADIAAAAAAAAAAQAAADIAAAAAAAAAAgAAADIAAAAAAAAAAwAAADMAAAAAAAAAAAAAADMAAAAAAAAAAQAAADMAAAAAAAAAAgAAADMAAAAAAAAAAwAAADQAAAAAAAAAAAAAADQAAAAAAAAAAQAAADQAAAAAAAAAAgAAADQAAAAAAAAAAwAAADUAAAAAAAAAAAAAADUAAAAAAAAAAQAAADUAAAAAAAAAAgAAADUAAAAAAAAAAwAAADYAAAAAAAAAAAAAADYAAAAAAAAAAQAAADYAAAAAAAAAAgAAADYAAAAAAAAAAwAAADcAAAAAAAAAAAAAADcAAAAAAAAAAQAAADcAAAAAAAAAAgAAADcAAAAAAAAAAwAAADgAAAAAAAAAAAAAADgAAAAAAAAAAQAAADgAAAAAAAAAAgAAADgAAAAAAAAAAwAAADkAAAAAAAAAAAAAADkAAAAAAAAAAQAAADkAAAAAAAAAAgAAADkAAAAAAAAAAwAAADoAAAAAAAAAAAAAADoAAAAAAAAAAQAAADoAAAAAAAAAAgAAADoAAAAAAAAAAwAAADsAAAAAAAAAAAAAADsAAAAAAAAAAQAAADsAAAAAAAAAAgAAADsAAAAAAAAAAwAAADwAAAAAAAAAAAAAADwAAAAAAAAAAQAAADwAAAAAAAAAAgAAADwAAAAAAAAAAwAAAD0AAAAAAAAAAAAAAD0AAAAAAAAAAQAAAD0AAAAAAAAAAgAAAD0AAAAAAAAAAwAAAD4AAAAAAAAAAAAAAD4AAAAAAAAAAQAAAD4AAAAAAAAAAgAAAD4AAAAAAAAAAwAAAD8AAAAAAAAAAAAAAD8AAAAAAAAAAQAAAD8AAAAAAAAAAgAAAD8AAAAAAAAAAwAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAQAAAEAAAAAAAAAAAgAAAEAAAAAAAAAAAwAAAEEAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAEMAAAAAAAAAAAAAAEoAAAAAAAAAAAAAAEoAAAAAAAAAAQAAAEsAAAAAAAAAAAAAAEsAAAAAAAAAAQAAAEwAAAAAAAAAAAAAAEwAAAAAAAAAAQAAAE0AAAAAAAAAAAAAAE0AAAAAAAAAAQAAAE4AAAAAAAAAAAAAAE4AAAAAAAAAAQAAAE8AAAAAAAAAAAAAAE8AAAAAAAAAAQAAAFAAAAAAAAAAAAAAAFAAAAAAAAAAAQAAAFEAAAAAAAAAAAAAAFEAAAAAAAAAAQAAAFIAAAAAAAAAAAAAAFIAAAAAAAAAAQAAAFMAAAAAAAAAAAAAAFMAAAAAAAAAAQAAAFQAAAAAAAAAAAAAAFQAAAAAAAAAAQAAAFUAAAAAAAAAAAAAAFUAAAAAAAAAAQAAAFQAAAAAAAAA+f///1MAAAAAAAAA+f///1IAAAAAAAAA+f///1EAAAAAAAAA+f///1AAAAAAAAAA+f///08AAAAAAAAA+f///04AAAAAAAAA+f///00AAAAAAAAA+f///0wAAAAAAAAA+f///0sAAAAAAAAA+f///0oAAAAAAAAA+f///1YAAAAAAAAAAAAAAEQAAAAAAAAAAAAAAEUAAAAAAAAAAAAAAEQAAAAAAAAA/////1YAAAAAAAAAAQAAAFcAAAAAAAAAAAAAAEYAAAAAAAAAAAAAAEcAAAAAAAAAAAAAAEYAAAAAAAAA/////1cAAAAAAAAAAQAAAFgAAAAAAAAAAAAAAEgAAAAAAAAAAAAAAEkAAAAAAAAAAAAAAEgAAAAAAAAA/////1gAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAA0AAAAAAAAAAAAAAA4AAAAAAAAAAAAAAA8AAAAAAAAAAAAAABAAAAAAAAAAAAAAABEAAAAAAAAAAAAAABIAAAAAAAAAAAAAABMAAAAAAAAAAAAAABQAAAAAAAAAAAAAABUAAAAAAAAAAAAAABYAAAAAAAAAAAAAABcAAAAAAAAAAAAAABgAAAAAAAAAAAAAABkAAAAAAAAAAAAAABoAAAAAAAAAAAAAABsAAAAAAAAAAAAAABwAAAAAAAAAAAAAAB0AAAAAAAAAAAAAAB4AAAAAAAAAAAAAAB8AAAAAAAAAAAAAACAAAAAAAAAAAAAAACEAAAAAAAAAAAAAACIAAAAAAAAAAAAAACMAAAAAAAAAAAAAACQAAAAAAAAAAAAAACUAAAAAAAAAAAAAACYAAAAAAAAAAAAAACcAAAAAAAAAAAAAACgAAAAAAAAAAAAAACkAAAAAAAAAAAAAACoAAAAAAAAAAAAAACsAAAAAAAAAAAAAACwAAAAAAAAAAAAAAFoAAAAAAAAAAAAAAFkAAAAAAAAAAAAAAAEAAAAAAAAACAAAADsAAAAAAAAABgAAAAIAAAACAAAAAAAAAAAAAAAFAAAABQAAAAIAAAAuAAAAAAAAAAAAAAAGAAAAAgAAAC4AAAAAAAAAAQAAAAIAAAAuAAAAAAAAAAIAAAAEAAAAAgAAAC4AAAAAAAAAAwAAAAYAAAACAAAAAwAAAAAAAAAAAAAABQAAAAUAAAACAAAALwAAAAAAAAAAAAAABgAAAAIAAAAvAAAAAAAAAAEAAAACAAAALwAAAAAAAAACAAAABAAAAAIAAAAvAAAAAAAAAAMAAAAGAAAAAgAAAAQAAAAAAAAAAAAAAAUAAAAFAAAAAgAAADAAAAAAAAAAAAAAAAYAAAACAAAAMAAAAAAAAAABAAAAAgAAADAAAAAAAAAAAgAAAAQAAAACAAAAMAAAAAAAAAADAAAABgAAAAIAAAAFAAAAAAAAAAAAAAAFAAAABQAAAAIAAAAxAAAAAAAAAAAAAAAGAAAAAgAAADEAAAAAAAAAAQAAAAIAAAAxAAAAAAAAAAIAAAAEAAAAAgAAADEAAAAAAAAAAwAAAAYAAAACAAAABgAAAAAAAAAAAAAABQAAAAUAAAACAAAAMgAAAAAAAAAAAAAABgAAAAIAAAAyAAAAAAAAAAEAAAACAAAAMgAAAAAAAAACAAAABAAAAAIAAAAyAAAAAAAAAAMAAAAGAAAAAgAAAAcAAAAAAAAAAAAAAAUAAAAFAAAAAgAAADMAAAAAAAAAAAAAAAYAAAACAAAAMwAAAAAAAAABAAAAAgAAADMAAAAAAAAAAgAAAAQAAAACAAAAMwAAAAAAAAADAAAABgAAAAIAAAAIAAAAAAAAAAAAAAAFAAAABQAAAAIAAAA0AAAAAAAAAAAAAAAGAAAAAgAAADQAAAAAAAAAAQAAAAIAAAA0AAAAAAAAAAIAAAAEAAAAAgAAADQAAAAAAAAAAwAAAAYAAAACAAAACQAAAAAAAAAAAAAABQAAAAUAAAACAAAANQAAAAAAAAAAAAAABgAAAAIAAAA1AAAAAAAAAAEAAAACAAAANQAAAAAAAAACAAAABAAAAAIAAAA1AAAAAAAAAAMAAAAGAAAAAgAAAAoAAAAAAAAAAAAAAAUAAAAFAAAAAgAAADYAAAAAAAAAAAAAAAYAAAACAAAANgAAAAAAAAABAAAAAgAAADYAAAAAAAAAAgAAAAQAAAACAAAANgAAAAAAAAADAAAABgAAAAIAAAALAAAAAAAAAAAAAAAFAAAABQAAAAIAAAA3AAAAAAAAAAAAAAAGAAAAAgAAADcAAAAAAAAAAQAAAAIAAAA3AAAAAAAAAAIAAAAEAAAAAgAAADcAAAAAAAAAAwAAAAYAAAACAAAADAAAAAAAAAAAAAAABQAAAAUAAAACAAAAOAAAAAAAAAAAAAAABgAAAAIAAAA4AAAAAAAAAAEAAAACAAAAOAAAAAAAAAACAAAABAAAAAIAAAA4AAAAAAAAAAMAAAAGAAAAAgAAAA0AAAAAAAAAAAAAAAUAAAAFAAAAAgAAADkAAAAAAAAAAAAAAAYAAAACAAAAOQAAAAAAAAABAAAAAgAAADkAAAAAAAAAAgAAAAQAAAACAAAAOQAAAAAAAAADAAAABgAAAAIAAAAOAAAAAAAAAAAAAAAFAAAABQAAAAIAAAA6AAAAAAAAAAAAAAAGAAAAAgAAADoAAAAAAAAAAQAAAAIAAAA6AAAAAAAAAAIAAAAEAAAAAgAAADoAAAAAAAAAAwAAAAYAAAACAAAADwAAAAAAAAAAAAAABQAAAAUAAAACAAAAOwAAAAAAAAAAAAAABgAAAAIAAAA7AAAAAAAAAAEAAAACAAAAOwAAAAAAAAACAAAABAAAAAIAAAA7AAAAAAAAAAMAAAAGAAAAAgAAABAAAAAAAAAAAAAAAAUAAAAFAAAAAgAAADwAAAAAAAAAAAAAAAYAAAACAAAAPAAAAAAAAAABAAAAAgAAADwAAAAAAAAAAgAAAAQAAAACAAAAPAAAAAAAAAADAAAABgAAAAIAAAARAAAAAAAAAAAAAAAFAAAABQAAAAIAAAA9AAAAAAAAAAAAAAAGAAAAAgAAAD0AAAAAAAAAAQAAAAIAAAA9AAAAAAAAAAIAAAAEAAAAAgAAAD0AAAAAAAAAAwAAAAYAAAACAAAAEgAAAAAAAAAAAAAABQAAAAUAAAACAAAAPgAAAAAAAAAAAAAABgAAAAIAAAA+AAAAAAAAAAEAAAACAAAAPgAAAAAAAAACAAAABAAAAAIAAAA+AAAAAAAAAAMAAAAGAAAAAgAAABMAAAAAAAAAAAAAAAUAAAAFAAAAAgAAAD8AAAAAAAAAAAAAAAYAAAACAAAAPwAAAAAAAAABAAAAAgAAAD8AAAAAAAAAAgAAAAQAAAACAAAAPwAAAAAAAAADAAAABgAAAAIAAAAUAAAAAAAAAAAAAAAFAAAABQAAAAIAAABAAAAAAAAAAAAAAAAGAAAAAgAAAEAAAAAAAAAAAQAAAAIAAABAAAAAAAAAAAIAAAAEAAAAAgAAAEAAAAAAAAAAAwAAAAYAAAABAAAAAQAAAAAAAAAFAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAIAAABKAAAAAAAAAAAAAAAGAAAAAQAAAAEAAAD5////BQAAAAYAAAACAAAAVQAAAAAAAAAAAAAAAgAAAFUAAAAAAAAAAAAAAAQAAAACAAAAVQAAAAAAAAAAAAAABgAAAAEAAAABAAAAAAAAAAUAAAACAAAASwAAAAAAAAAAAAAABAAAAAIAAABKAAAAAAAAAPn///8GAAAAAQAAAAEAAAAAAAAABQAAAAIAAABMAAAAAAAAAAAAAAAEAAAAAgAAAEsAAAAAAAAA+f///wYAAAABAAAAAQAAAAAAAAAFAAAAAgAAAE0AAAAAAAAAAAAAAAQAAAACAAAATAAAAAAAAAD5////BgAAAAEAAAABAAAAAAAAAAUAAAACAAAATgAAAAAAAAAAAAAABAAAAAIAAABNAAAAAAAAAPn///8GAAAAAQAAAAEAAAAAAAAABQAAAAIAAABPAAAAAAAAAAAAAAAEAAAAAgAAAE4AAAAAAAAA+f///wYAAAABAAAAAQAAAAAAAAAFAAAAAgAAAFAAAAAAAAAAAAAAAAQAAAACAAAATwAAAAAAAAD5////BgAAAAEAAAABAAAAAAAAAAUAAAACAAAAUQAAAAAAAAAAAAAABAAAAAIAAABQAAAAAAAAAPn///8GAAAAAQAAAAEAAAAAAAAABQAAAAIAAABSAAAAAAAAAAAAAAAEAAAAAgAAAFEAAAAAAAAA+f///wYAAAABAAAAAQAAAAAAAAAFAAAAAgAAAFMAAAAAAAAAAAAAAAQAAAACAAAAUgAAAAAAAAD5////BgAAAAEAAAABAAAAAAAAAAUAAAACAAAAVAAAAAAAAAAAAAAABAAAAAIAAABTAAAAAAAAAPn///8GAAAAAQAAAAEAAAAAAAAABQAAAAIAAABVAAAAAAAAAAAAAAAEAAAAAgAAAFQAAAAAAAAA+f///wYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAABKAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAAQAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAAVAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAAuAAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAABYAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAAEoAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAABAAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAALgAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAoukz5btWDoclP5ZejolfW3FuyNSqJuxkyvDGIm5rIgkBAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAABLAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAALwAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAAXAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAAwAAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAABgAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAAEsAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAAvAAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAArNxDfun0nPHO9gaKHe0IRwpiPA4Fg0f4V6S/o1GCzEwEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAMAAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAUj4xq5sTidFOhU28jtFb1tSSpHbgwT9nApO96SPfrxgBAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAABMAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAMQAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAAZAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAAyAAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAABoAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAAEwAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAAxAAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAABC+4rasD/Dz8HxuI/kVUHnKOJwsy+4tz7Ipq8TujahAAEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAMgAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAnWkIkL756kQ9WGR4B9nzHyWhAdKNBZLUF19Uzg11uS4BAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAABNAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAMwAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAAbAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAA0AAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAABwAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAAE0AAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAAzAAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAAbQnFsr2M4U3LluNu4dr5N1VF8eCK3ev/6jElvkw5wDAEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAANAAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAS0hg2BrMj6FjByaMt1naWeNn9soexvLrRi7mtwaVgiABAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAABOAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAANQAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAAdAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAA2AAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAAB4AAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAAE4AAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAA1AAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAAn5TBIsH5NStIzBSS+EDLLioLaBlnTMOOp6oSDQSByEgEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAANgAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAFOAujNwn9ZgMFqDjXi6h/seM1vkURQrMkV7D9vgJRS4BAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAABPAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAANwAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAAfAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAA4AAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAACAAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAAE8AAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAA3AAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAAHZhgcDfiby3j6OKvK320SbAqjNEgcjfGiQBBxOFIIIwEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAOAAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAA4MAjESpKAW+jY3rCE49V6RziSZIa0RzJe5UqUyRkyREBAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAABQAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAOQAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAAhAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAA6AAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAACIAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAAFAAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAA5AAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAC2SIg9SgwjLCo0FUkZi4Namy0z6knxXB9d+OXgTPpDBQEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAOgAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAA4s8XdEcNARYunxXUde4e732Izj8wVmi5Iz9Tf9o2/xABAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAABRAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAOwAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAAjAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAA8AAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAACQAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAAFEAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAA7AAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAACL0BkSB0CVXe2uRI5pfq3FyEww7/NFrksCMjSQ+9anKQEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAPAAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAADR9iGWVq3PwOhkjSfj+J3Xy/FImn/WImP2tV4+qKcSoBAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAABSAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAPQAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAAlAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAA+AAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAACYAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAAFIAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAA9AAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAB0Pyh6QPldOZRSs6FufgXAxjQMn7oWXQjA8SE8EcoUCQEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAPgAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAADkOzbDb5WRHNt/xQYG375+np/7kVVNZ6J7EP59tQ/R0BAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAABTAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAPwAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAAnAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAABAAAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAACgAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAAFMAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAAA/AAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAAAps5LQ3aFG6+0AcVqatIQXw4C6b/mhaNGDY7dC5QIZHgEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAQAAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAwG1VK++txNZiTR4U3knvQjCZB926obpw1UyaGJ8RRhUBAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAABUAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAQQAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAApAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAABCAAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAACoAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAAFQAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAABBAAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAACrbvz6NMj+vzROHp6XxKTyn3wt4i5PGUkDLVExWAEuKQEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAAQgAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAipQBLV7GVNWBm3tJmzMJ7sdmXr16qF9sGmZs5JyF2CkBAAAAAAAAAAMAAAACAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAIAAABVAAAAAAAAAAEAAAAGAAAABQAAAAUAAAACAAAAQwAAAAAAAAAAAAAABgAAAAMAAAABAAAAAAAAAAIAAAArAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAFAAAABQAAAAIAAAAtAAAAAAAAAAAAAAAGAAAAAwAAAAEAAAAAAAAAAgAAACwAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAAAgAAAFUAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAIAAABDAAAAAAAAAAAAAAAGAAAABgAAAAMAAAABAAAAAAAAAAAAAADe/eEQsDLCSiUQOSjN9ARJHQ/R1Hjvi04REURQx1tBEQEAAAAAAAAAAwAAAAIAAAAAAAAABQAAAAUAAAACAAAALQAAAAAAAAAAAAAABgAAAAYAAAADAAAAAQAAAAAAAAAAAAAAGkKw3aKCZNuXuywdiKshySOghdQe068qT34BjgbTSAgBAAAAAAAAAAMAAAACAAAAAAAAAAYAAAABAAAAAQAAAAAAAAAFAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAIAAABWAAAAAAAAAAAAAAAGAAAAAQAAAAEAAAD5////BQAAAAYAAAACAAAAVgAAAAAAAAAAAAAAAgAAAFYAAAAAAAAAAAAAAAQAAAACAAAAVgAAAAAAAAAAAAAABgAAAAUAAAAFAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAEAAAABAAAA+f///wQAAAAFAAAABQAAAAUAAAAFAAAABQAAAAEAAAABAAAA+v///wEAAAABAAAA+////wEAAAABAAAA/P///wEAAAABAAAA/f///wEAAAABAAAA/v///wEAAAABAAAA/////wUAAAAGAAAABgAAAAIAAABWAAAAAAAAAAEAAAAFAAAAAgAAAEQAAAAAAAAAAAAAAAMAAAABAAAAAAAAAAUAAAACAAAARQAAAAAAAAAAAAAAAwAAAAIAAAAAAAAABAAAAAYAAAAGAAAAAgAAAFYAAAAAAAAAAAAAAAUAAAAIAAAAAQAAAAAAAAACAAAAQQAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAwAAAAEAAAAAAAAABQAAAAgAAAABAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAGAAAAAQAAAAEAAAAAAAAABQAAAAIAAABEAAAAAAAAAAAAAAAEAAAAAgAAAEUAAAAAAAAAAAAAAAYAAAAGAAAABQAAAAUAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAQAAAAEAAAD5////BAAAAAUAAAAFAAAABQAAAAUAAAAFAAAAAQAAAAEAAAD6////AQAAAAEAAAD7////AQAAAAEAAAD8////AQAAAAEAAAD9////AQAAAAEAAAD+////AQAAAAEAAAD/////BQAAAAIAAABEAAAAAAAAAAAAAAAEAAAAAgAAAEUAAAAAAAAAAAAAAAUAAAACAAAARAAAAAAAAAAAAAAABAAAAAIAAABEAAAAAAAAAP////8GAAAAAQAAAAEAAAAAAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAACAAAAVwAAAAAAAAAAAAAABgAAAAEAAAABAAAA+f///wUAAAAGAAAAAgAAAFcAAAAAAAAAAAAAAAIAAABXAAAAAAAAAAAAAAAEAAAAAgAAAFcAAAAAAAAAAAAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAABgAAAAYAAAACAAAAVwAAAAAAAAABAAAABQAAAAIAAABGAAAAAAAAAAAAAAADAAAAAQAAAAAAAAAFAAAAAgAAAEcAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAQAAAAGAAAABgAAAAIAAABXAAAAAAAAAAAAAAAFAAAACAAAAAEAAAAAAAAAAgAAAEIAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAMAAAABAAAAAAAAAAUAAAAIAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAwAAAAIAAAAAAAAABgAAAAEAAAABAAAAAAAAAAUAAAACAAAARgAAAAAAAAAAAAAABAAAAAIAAABHAAAAAAAAAAAAAAAGAAAABgAAAAUAAAAFAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAEAAAABAAAA+f///wQAAAAFAAAABQAAAAUAAAAFAAAABQAAAAEAAAABAAAA+v///wEAAAABAAAA+////wEAAAABAAAA/P///wEAAAABAAAA/f///wEAAAABAAAA/v///wEAAAABAAAA/////wUAAAACAAAARgAAAAAAAAAAAAAABAAAAAIAAABHAAAAAAAAAAAAAAAFAAAAAgAAAEYAAAAAAAAAAAAAAAQAAAACAAAARgAAAAAAAAD/////BgAAAAEAAAABAAAAAAAAAAUAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAgAAAFgAAAAAAAAAAAAAAAYAAAABAAAAAQAAAPn///8FAAAABgAAAAIAAABYAAAAAAAAAAAAAAACAAAAWAAAAAAAAAAAAAAABAAAAAIAAABYAAAAAAAAAAAAAAAGAAAABQAAAAUAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAQAAAAEAAAD5////BAAAAAUAAAAFAAAABQAAAAUAAAAFAAAAAQAAAAEAAAD6////AQAAAAEAAAD7////AQAAAAEAAAD8////AQAAAAEAAAD9////AQAAAAEAAAD+////AQAAAAEAAAD/////BQAAAAYAAAAGAAAAAgAAAFgAAAAAAAAAAQAAAAUAAAACAAAASAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAABQAAAAIAAABJAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAEAAAABgAAAAYAAAACAAAAWAAAAAAAAAAAAAAABQAAAAgAAAABAAAAAAAAAAIAAABDAAAAAAAAAAAAAAADAAAAAAAAAAAAAAADAAAAAQAAAAAAAAAFAAAACAAAAAEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAYAAAABAAAAAQAAAAAAAAAFAAAAAgAAAEgAAAAAAAAAAAAAAAQAAAACAAAASQAAAAAAAAAAAAAABgAAAAYAAAAFAAAABQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAABAAAAAQAAAPn///8EAAAABQAAAAUAAAAFAAAABQAAAAUAAAABAAAAAQAAAPr///8BAAAAAQAAAPv///8BAAAAAQAAAPz///8BAAAAAQAAAP3///8BAAAAAQAAAP7///8BAAAAAQAAAP////8FAAAAAgAAAEgAAAAAAAAAAAAAAAQAAAACAAAASQAAAAAAAAAAAAAABQAAAAIAAABIAAAAAAAAAAAAAAAEAAAAAgAAAEgAAAAAAAAA/////wMAAAADAAAAAAAAAAFVvVqq2RH/lNXbTDf3xg2SbmMxeiz1VgvoP25xfKYPFQAAAQAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAALAAAAAAAAAAEAAAAAAAAAEgAAAAAAAAAmrv1e1E3oxPWBKAAAAAAAAAAAAAAAAAAAAAAAAAAAAFILzHvZcKhpN+UxAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ6ObISmyLx+QBAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAGLU78KQFmgqBpgAAAAAAAAAAAAAAAAAAAAAAAAAAADyvELDGT+tyC2D2AAAAAAAAAAAAAAAAAAAAAAAAAAAAjZ2JLG4roPWnIwAAAAAAAAAAAAAAAAAAAAAAAAAAAACYXjoP1zywy40m3QAAAAAAAAAAAAAAAAAAAAAAAAAAAGFssvPbEy9a4csKAAAAAAAAAAAAAAAAAAAAAAAAAAAAUtt6jI3K9XW1EgAAAAAAAAAAAAAAAAAAAAAAAAAAAABcs8MPpaNkbYRc6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAS0MDn6a2DSc+BIAAAAAAAAAAAAAAAAAAAAAAAAAAAA1rK1nNC7W6LoBQAAAAAAAAAAAAAAAAAAAAAAAAAAAACJM2m7B+/iq6m+FbOkLamOAO6r11KteiAz931+kl/+DRzk2fx3vhng0DBaqpQFXzgIy7fnzFdjpGRXPBumkXQNrJTB3wNpxDNVAcj79O2pdVEDB/2JmFXcOGnVDZO1TyGZQBxqj9IE5CgI/ERnzNLYUBvGwwZengu8/ZDGLhKVJaxhY/0AFZuzFmDAkiYcG7kAAAAAAAAAAAAAAAAAAAAA8cMq/B7olRSm4wjcePBNzgAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAAI198xAY0ywrcBcUsNC+8D6jWDeQSQ1iloCWQkl36ptDqNFBBG2d89G8zxUnD9dzJnPP7gUF+VmYRHS/fPxOOxFDugYYnyYfFFQiNVNLV6GsfZsAiqRpsAYUmtyh6gZFYxxQWt/WCjLsgC9MqxWWUjfvEDsXFm4YNllZBfsiJzQWR795FoWYG0esdo+dIX1i8az0rRIlaPB/eJJEJSeYqhgVquHdhsH4LVBJsp1O69I56/mPISuii/6JUcR+VdagKva2eJqhJIztVne/CiBKpZRwmr8hTWlJL4e2BCgURMAM2YaJ+9u55ssK/SAneD0JQo4w9rELf/kY4dh/qXxsimMrWAAMqMdO6Scrg4YWLlUApTZ/ly6u03xwgPvclZ+1DX5lagNE57GUpTTow/YxNqBzQ3LGdLhkvpfJMkYpdr1MFsI+51vg2khOj2fJMGlJHobKFejIsxOxx3XfV/3FL1UaZawM0CrCdG1SRmQaAqZpfDlYyxIc6A9g5Xfj0ltgGjigxheQCdb6y//SyT+vl7GZgatbMbzazLEEFoD+hqARyihqfyOopHPqDiPmoEpzeg4Sq3z4RB2ZW8Fs0I+jUwdeTNUaNtl6gmoBDotbdY9SRGwn+V+hn5omPTEEoxlUYZX0ZzXOIrce7byki9wm7hpPdkS6YTiAKIXwj8MsP+dWlLA4LhvFfXdV7NRQhk1WiCVuGzrqyVjPee3Ntloxdx/Vo167MUF91ntd29kye04Hsy82eRGNrLwj+onc/v0PUGzkAdE5/WBF5qvygIva6dh3md3AwSHLPD/TvpFKDuRJT94coLS3h6OcKN3mufbMRno48AvPFsZaT8XU08ysjSZl6sf+nmo7xb6FXSw3IW1i6RGHDT8pMAnTGQhTtnjNHEga9E+TCpDeyvDDnu88r+Qh3DJ6uwiDVHy3S9UeY7ASRMj+ErwSx/L4+2pj9C7m0nB35oOnG8wR1sItGKGMpFen+a7vbejpNGC97/324HDZFpPh4N7ZXiYoKR+7Pg3gUpcfBX3Vqx10/xooI6EflQPvY6m4gACgcWukUcA6xDpb55I/hGFOC35SlsZjnsz8xUJWHFn46GOjj1TqbWMI0R2CE3Azb+W+/rSHGEhCRkdHIe//JFkuPu0AvElYHzR8KPwDi+Zr+FdbQKW4eEka2ZW2tT+gZpocutRoQo6sB6ZoPYndCpUvKXZJtVsXCjZjmqssLUvAOrGhsTJYqie60k2c6hmgWd7ptTiMg14zmDWx56AuRiHiqakfMn3RkfMXGIB5zng1JwQEVnHhTGmxw3GG9cdQKuboYKQLv2luYkBif+V6N1qqlt0NzNJR11n6CWUsodD+GJYQEFRDn+5jOVT5g8Oy2t2XhaKg6npAMfSFhaD77amJ8+q0WE3nFaqKQyfuPuhon7M88r1Iljp5KkAfdAcQ+wnssQKQQIh+IfER/aNRkct1UhJq5EgHKnp6Szpl1GRCoNBQMPrgfcnVLh1vj0Ezt0M3DZesO7+5x1XnWBuzSeGEBtiL1JEIQAtpSL8/2HBlP5jcX5GyKVAhawa6R+e8JC5xSgx5AgDRMw1Stw/6WklrrJRkAFoxqsA5pfHT4aquNIzaiRu6IXl9el0RdqglLnx8fDf0Cb0G4AZJbswkaEcW//bgj8uj/W7NRtqFHtpXR9+5Y3XSiTsS3TNPhKSJpDEEfklyUUjcPBgCpQOxFWiM9AXcL9neUNr5GXQ2wTc8ceZhP9tcGcCxdl9ExAHZOPy3u59+GXgq7GCv7sb5T9yGcYUQTCE5pNdKUkoxEpvjFZO6IYuHAM2S6QfdwUizBG8/1gZHM4YhkapQHUgjTTCmwLms40x9aHIkMFI49aGdGmcbvPY8Oi/rjwvWWjciBjdVHeriXpI3/SfMcb69Wmw+gvZiWHeZ3llNW2Ob+Bpk0AkOIHSBo7olh2T4+rCIEwAPqG0f9Doc9m9Wqz2iucxZfXBZpt6+VuFW4fUbdrJTxHKW6UON/ToC10gXKvURrAS9FNbCa74Eg7G/sWR+Rgzym3Gb9cn+XLsEDniHo9uq6RihXc4zLAoUgLeN+D5iw4w9OJ6QFAdIJ9MRDm5JeRmhnqVZz0zrvKj48Ff2KwqNdcZHEiLcajQ3fxVs8m7wi+00jrd1Sfziimohley/fE01BkmTPki6m14lAOcPPNx0M1e8+dNnaemRriqKBx3JXwPPE72adtbjyM8M9JQI5Ap23gdm5B76vimnWit3lhSYU0goHbBeku+wvyZ1PD8T1//9m4IeKK3eV59QIU4UHDG6AS0pjMBSrNyHIU1aYPikIbOrwnSG7WwNMBADVevzJGYQ/Kb0PRPzy44K11c8UBIVWcVwiVdEGbisJJN9b9lsNIArTx4Fs0z/LxbFWVqIEond+EA+V3WlCOmHfnQ9aoPVQkWt26KaGNZsKm6fliUMMKNRyRqbVEcM0reyJojAlEjBT8HdlEjgooAoH0ZfEVDZmLHg8i9iQbFkYmzAqEbs+Yz4HC0r9JwgDSVPt3++YQV4MBWEWTzx18o/jTvYZ/8QZcH1rCtvI4n/BdP/YQehcv8uLXHxpmR6RaqrSjJVP4qDtc5GDGvQIvcmacNZY/NTWdQhldweqmC/wNjjhH77X541xR7VteEcGitduVMCV+h9ou1z2QBgRMtdwAWgleZyZuNGkgXApGhQBoJ5llKodfnqAxUukLt7VBy1gmvSJHGPZMJrNEH4k/IV5YR7T9z9YeVFhSjjw5VQvt0j21OjMb5CmeJvEmpN4B2vEkvYeCrAKA61C2ETFDVZn/zZToGNOZPQKbsfpdmsBSc/jg942SXQfERodkFni/xMEdhDTb0D7fpZL6zXwoUazyXKcs4SBYh2aqxQnLe3kj1W7kQTz0j5VhgimcZIHhHD6x+e4GAxtb+rIDZFu3S6hrcBhQeIZLmMDnzqSdQgEeEQG7KGmD+71w8F4LKWRLc351I1KomXviJf9xpVp+Ar3NQF86HID54oxD1xxosYw57rUwrH7BDQ5QvVN+C0dgLcUearUbOW1AHGMUoXXYA4apHRvlsaUxBC7UC+MpgoGtdaTy1S7DIUOIRgCkVfGmZvkNK6xs+ebLMnU5nTjtMnde1AqibSjFg8TwimZYtFAQy5m0cyfSjzJ9+0DrZozAHKeUS09Kqx8JeZC/c8APjwxN+tZv0XDz0sZ54BstZ0Jm2Sk4Mk2MnD2lKJpf1Ss37V5eEqibihHQwFk7FSiWEBfA1al3kz7ixtUL9cAabS5HHxVH11a04o4dl0uU218RHVyxksYWoQPqOo0Dy/n7EJU82CcwN5IiwAYt/dDfN3DF4QAsRLPQBH598myRRrQVJbYXw3mVJNTzFTUyzoYHUpwnyE9rdE6Otpnl9TpGRNT40kng8h0sdKC+J/mxRTHAM3La5YQLMz9sKerZJQJ35jmX6OGTf8cZi12Wd1bj2zDZXlyZQ61mr97amQHVyJI2lcZ6u3BfgSc2+ltHkwJz0E81smoTZ8yJ4g8BgzrlD9dg3tZduEc5nNIf68x4wEYRj3ym8HZ3/dDy9lc7DeiTP23bzIVSzsntxyLsTIBa6UFG0Fgh4dTBOVOX+etEs3sq+G7aXsUep7RM50pgF0zeoA+MZ3JLIf7qB66hkMALwgYc1G4g4QJTLpP233D3jg+SKSo/N5E9HAX2XcO5N6HQ2dMaQN0GxoTQ4OzwfIXHneGtSWBITMqPGqKMehlOZQdU4U7mF9CRYoSAo6cpXi5v8qSiYPPFsV8rtHwg1yT9wPl0gvtw/n+SMtYrvbpbmvnQ+MXMi8MctlUh7BuPeqY+isww6fa0YBIT7H1I0yrP6FBP1dJ5hc4Iff/Iw3xtz0bKmOCLrV12URMPJhRsxQu0QhAh7h6MFT/SXz3dVipUs4VIzLKKDz9MbKCHjHLrqXAB09+9RvY/6t5SYYneAuRkJy5TyZ9lzztVqCkKrY3B3UFUHYZxEQXbqZjwhqlNRBZ67mLZlvTssa1ui+5Gboikkhdm2b36bnS8+gLub6GHASuMoPfWAoNC9lblRsxDyWFxI+3Xx+ehJiA5ef2fliJgHyId7N3TzOdAQvIHra/50EDNUIEbkewFrXGKQ4vDsS1xNuvq1Fvq+Zl0GVsl6y8vEAdrSYa2VjlV8bB/GBZzcumqXaOD/Su4aso59Pf13Wuywmffo9uldo60X6g4etdstPd4fsVQeYsnTHhBWcb4o7BN7UclMopZcpLXAdts84cO2z0DEt+bbSkdo8ytViw8AdU9p7FhwxxlX6+uWZgg7aK7QFvwMQvnxsfTbqvOX6mSBixnQuuV29d+7043w9KQOeavFPKJibHWgiZ0IkaPWGFm1wiBzWKP3ZuTrqTHo6eS/DucaalEXp/6AYbS6kN1swU61gppvLi7mIEsH4R3hS5v1uKNfcSH3gaAnuUI86Tgxq6OgLQvP9vVjFKJx+ivwGo0kUqsVRiCxPRin0k9lyBpyIH3mlhU027sdsul0XFyyUcmUD5LksMrhWZySeO1cg+5rH+sdxidK+VO8E4BvHi3HNbxQlTF8Ettm4zFgMaSQ+emfVwAwFfpayrV+SRjUUwwaZvgupcJePRCEKllBqJy807QAxlHpIWhO+YLJBNKrlJwlm8BFzsJ+GFWjpTW0JxoGbGJXJi+T4OIPfktZ8TNEjP5pR4hvENspDKVb6PCILrDscbcmhPkA7ZuKvO6itbfBDOw9dGGexgBhZTLtOHW2DetqmCt5FGVRtY/4NZ62qVHYaUJIIZzW5m2CHUYAUr55ejZuUW5ENA8tvN3UU/ZZATXX5j3BSa46jSqv/CRqVVnTBwoTnJ2WchDGFmTTp+QkO+aZMqc4999p2luxpDnY5cUA+RQyilRT4c53Fo05iIojogJgGXQG+XaC0UJYHgfIIoJNVzOg8PNNfSAxAsLPLCVc/7swsARXvAHoULgo3AhEgxsk9ycwuTX9ZcZAbRkxZMXXdj0lbHChbCkFSDp13XQgxRnU66T6gonIbAUyfSu4UbnFAGE+odf4tndYBrVvilAcAKTksYch7VdYfle8hpWqpEKZUYIi2s89IZC0+Vk4C3V+vd0a/XmIpSdRUJW4PGCy5Rz4+CfygqZ1vJc8Mpj9Iik/vUeOdMb8DemD0lk2EK1vSlNK1dyss6CsW+o7duSxDIReuNubiPzHA2GI/WJ2xBvegI4m3YMuu0iFkFfS8M7/hs0Jxdy27Ru/4qp5tJeYu+fxpjKQ1JibcL+4elUNlX/jUqSfmIQvNW7bqMA/zG5RzShv6H88rv/IgPWCRb+jYFMRqyeJlxWhwOY3pND/Pwr39d+lWyIN3/AaYPwvpH0qt6cFWHUCIbgNGl+OhGp7ARaad2UNkTRPcIhoHXbsTpRdC6zz/N2OticidAhGuj8PUa3dBhZn5zWgApXuQxm8M+730Yubs6BW5ZluT/dhzxctMnn95L4ProAptN1a/rbbduxy/0cgcTCDHUOEh/7DXsxo5snFvGOv4JtIhijtBzRA88uKIDqPpALAYsdCZQC726DR3rNLOrbkjSMN16NftZ/7LsrGTxZd8VFuxAciR802Y8UU89gPn/hucOqzJ3I6p/jH7+6EeBE9TqTr2AqhRebR6Ep69oAQbLisOQ85ZQZDBxazp/yYt0uBsLIZzvWL/2N7bgktjHXkj+QGu3kK6vSmK/S9N+oW3Cuctc5mFNe++eZOtUgqkGw8sNmPSP7Efu/KZyWnIXnpAP1hUAvyiLw3PxdQsJN7DCYOOZ4lkQZbgd/t+d7aetLVKat5NV8Nqm//wV65LY1MO3JzHPWpGQR5hHhMkF/mjHGaMyxaYnFxhRvqfVQs+hyyGvRQKqcZ2bdsTixx0uRCGYRHttiZPz7ybh62S5UkAEfxKYHj0uUbEDVTj1QRn1ZTzGJsNdf1Zt90aNSH05bYKtZnsEu51JSpi0ZSn6k3OMwhVm5bXHba9YcgrgG3RCCQCuaNeIGRNpWvuu5fxKI085bu8i/8bqkbz0boZwajzBb00XVSNQouJuP3O+xVWYi+FazpxBFX/WCvf6PJKk+MYMSM3+SrnTbDt+n8h7rl0/CmqqsZs46ymcjtF/z0BzQEMTczUP1emNULnhm+hM1+HXisC4RF8cKl4T3fyC8qdLyDdoNGmn72ProtOb3ZKAvd5v0vhXqJCh+ZJdWFN5IUCVl/HrbiEiZ+12gL8YWi9FYyc8e+K2eu65caLkU8bMiTMRKyNMTnZvIMNib8sP+jiM8pROnyMnhwJEE4Db7YqLibNg/vk/JQfVT1VJ1vSYJd6ikC85BtMX3tRj2cKeuMH67+InXwP6m46h9W8yDcfk6LbZrKnK1s+PE9rE1bdjAH+r7Eh3ITyg750/3NqJ8rL34GVwHztkwTCi4Bk0G3IFL2SoR0g3SuvzekfALHM9jgbOy9KESfAgGellA9+e4MoOZ9JCYNwVga9pfOTZiE+05KYOzk5Brp6WTFyakzWBSPUAHo365xorQ4Hy0Fyh4EDXFt/A5ulrRTwO9sba+MuLERPgQc7vFFFUEtyvmdzOoRWt4wf2oBWogOeRw/3y4UnUz8/AwU0IvG8pYoibikPm55+swJhlewtoL5QvtiGUiCbppg00EOFhNQVKsXjl3FFCg+/M9BWulBGQJWIAHquHWzqcTBuA8Qu6QZcyLRBEdFirGB1Hctx0VCcMgT5mAkRYz3JZIY79j+v1b4MmhN0VrG4wFdYx86sOOSb6FRHRS4EgcpfbUNN1RfgxvpvIhedXPAajeU3/QxZAIrp6N6tExpK9uZdBgfqRps1ufIAWMu19ZuxcGz53QK/m3skbEwGJaZVV2HpO+Oz0ANwxjldHK+ZRYwMrttQhMIrE3vpmQd23B8fH0+QBhSzx/p9MH2rsp8FJiKZVzib8xEc4TPZH8w0uvfLz8zmeQBaiqqJOs8YRUYgK602D3wnCj7tX5orKBDlE+t5Nbs0JnhOcthihVOTJQl3YjN6wMl1bz3AdwdyKto7rOZSlGEcefeApByXzQn7Tq8paXQbAbOPjHbXGoZfOVB7gFAZrhU4h/ltqrANEYobzevuduRYxCtrF+wQn8gurDfwhwZF8yPrEjS8Fs28y50oALOm2Yu9ps5rbCufQVvvjd0V8qTVgo0hYm7Cw9DGEc/eHpQrIb3kb+gnIMP2Mn7rXndqHIthtV7+6G2ft+3bxCbJxNHzoYIxgJcFKxADqFKPZrBgZab+DflJdKOccAwJZvVtzep5P9XTlSE/J+W0NT/iVOrMFkH8xfTzCcT/KK+hacUNCIjSXDGVLs4julUMlkefeKoxNlj6SBp211OUF5qnCNURN8B/2tsI1ZzuSiC03Z/LTo6Z35y0qTgsEmIfu0iWvHSNFbV6vQ4DW2aue8XOFxMvpB/TD3OoNULxl5pWt5GgT0C0oTbYDybJck6EfFh6BdfwnXzzJg2L3xNFhOm+1l3dzCuWzc8mpBGdY1YbZPGUiDdf6odUeHoBIM3XSV5wkLtQgOdzbQE4HLrOBcjXhjgpu+pZopiopkTvAaJGqYqJVyRi0Iw3FCIKBEsc6tmnO/bl43jB7SPZ5oVz7Nx8JvyTJ6LX3A0kQfOqEnEduT7zSSO0m11DkU+gBWgjdLTtp7z0cR6bISrERoxZBiIGdTDFER5Ch5yut71fWcdZ2XTjSE9BgnDRL86a32PKgbxyqS6aWpTTm/eWX3nN8vBAGcoe1YRJT1Us4wUqEq/H9vLo0DH5SQBdENBbJ8cjHyRTgImXUFGRlh+KjNwLeLz3FKoj1VVFKXy7Ch0PoQyo23adkjPZqM0VHpn8+zCp2Lk9eC1VLIn89CNcRZg3jPvVfPI9I3YxknggZya6Wv3d3oMa1x1ASjW6eringaLTbLaQhkbblAX0rxr9ST44FlN19+RFmXM1VrWLY4pzEwYU5U2uE16jjftPDFX4lkn1EkBZiSjNebUf5t4rOpcE/lhc5cQSZUIEZR8BFX/OBMObmswkgZcfTMdiFvS3PFxKmLuoHHOhmRwLayk9lmXw5Jso2ik1OOsu2ZpAekwXKvQZmcrK3hl7J2QrFdwJUE9ACWx6ufe8v7SwEOLtrxDXCCPxhT8HxUDg8TweO4hEVOzb1DcZgddKmgDMM9ZbXKzdUPjNAEvRgjq1OAXNumdYPjzM+P1U7P5n9Y1YKW4xV+AGlVHOxB+0RXniDoxroMiQrmZDGCydWMD1f+eLvjYStOI/rrWGjbFXvKAd+zpS1G2cJCpSZ3iVlSQ3FitCjo5kamEHGaYvkILHSSlatuZr7JqmgT7AYdTFwRnvNHRi76ZcUsOfGy8gzx1XDW4vVsr+guTTZOGhCxhVWtKxMJDrXgS+WP3Iyk+V8uQGu6uRJpn7GiuiUkeY86jG48OZKjVrfAVNf6By7sda4hPFVESVkRkKvFoEPEsIohFbWwKNirkav9xn2XOKxKZ7BbmpiNcjq4mSggfhBu2iR72T1sKhnaa1LIDMEmSyKg4wRqU+jIMTCo22uldDJQRBxX8YcgyTKKiK0/0PvvmDmBY/7x45m7m0gPlIGlXzBCKJs2SAjEANiFtsJ/61XgeQCbnUYss1r6wjw+7JFweuUpl3823RVpQ4DdkDQzR5Jk8qXzLtCxS5VfNLqQQ5Kr6TcJ2KuSwH5kacZMavEetJWQ90+id44En+I45ivXLpoT45NtjNQDZMqExGgVHGu7UbCDaYRsg9OjTK2tBLgGrS+3FIqlgGeAVAFkZPK7NEc/ATK1+LMSFz/1/n4iOOFR7pKqC0KfrmL1I0qkrkF1buly8MGE/BNeLhhavvobZJzs27F22RuskR369DHCpTD9BlGWWy2l0eDfD+3faY+zBMgBmlF7i/5MsqlGYhku94XqAacYJsnbEZoe6DUaF+TAqkI4siooJ5RbAAyL54ovqbiCax1V/w/TYv76ndJhj7t9T9UJSTIe6ns24vnP21IYYFA9jUbKGF6ydeDiaIXdBBd6yk/ux9wnAUrpupNjtFoDcCinYILPRC0fH5H8GlDZJhGehkWE6Cb7Y55xq+IeJ/HxlxbxjAO0D+KTHEsKwk8pyqErvAJ61EgyWrdIAoqnQCFOfAUGjN0jDc1a47Y6FC6ae/y4GNlMC2bTxbrv9v7MkTgszw9BAdlyh1aJhIaeuEGeyF8Fc5RWq1Jg9IYWphJCvy4H6Lzrbuxb7FSqH8EnepAlJWTvngz90JynDCxAvIDP3A354TMLpx7IOhNjV47mkIvGIusHmoo90zOKOlydQcU8qDM//MlZSZc7SENt0W4mr8NdzJwxrVkV02gACBYhKw+hyhy5s3IFf8XRX1z+Bxxn4Bg5dDh/bPxJ3/tHKbGX6c+/qyK3Nad/cbHm4DVnlsmn0GrVGjlHPNCciH3bgfi7NS9OLHYt2ktAd++fRqFDq+oArjqIK0NkIUUfIyGAZQ/k1VyeJwgYrw9Nh0ps8eAt/HsdwIG9rjnkmqsV46AYvuoFuMyMVwDvNbT1RJotXjExQSJrqK22RYyXRnAolmF3ZB8n442DIdniKmuurrOri3313cW3sSNE0AolX20lk=","aggVkHashIdx":1} \ No newline at end of file diff --git a/axiom-query/src/axiom_aggregation1/circuit.rs b/axiom-query/src/axiom_aggregation1/circuit.rs new file mode 100644 index 00000000..a0dffe47 --- /dev/null +++ b/axiom-query/src/axiom_aggregation1/circuit.rs @@ -0,0 +1,138 @@ +use anyhow::{bail, Result}; +use axiom_eth::{ + halo2_base::gates::{circuit::CircuitBuilderStage, GateChip}, + halo2_proofs::poly::kzg::commitment::ParamsKZG, + halo2curves::bn256::Bn256, + snark_verifier_sdk::{ + halo2::{aggregation::AggregationCircuit, POSEIDON_SPEC}, + SHPLONK, + }, + utils::{ + build_utils::pinning::aggregation::AggregationCircuitPinning, + component::types::{ComponentPublicInstances, PoseidonHasher}, + snark_verifier::{ + create_universal_aggregation_circuit, AggregationCircuitParams, NUM_FE_ACCUMULATOR, + }, + }, +}; + +use crate::{ + axiom_aggregation1::types::LogicalPublicInstanceAxiomAggregation, + subquery_aggregation::types::{ + LogicalPublicInstanceSubqueryAgg, SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX, + }, + verify_compute::types::LogicalPisVerifyComputeWithoutAccumulator, +}; + +use super::types::InputAxiomAggregation1; + +impl InputAxiomAggregation1 { + /// Builds general circuit + /// + /// Warning: this MUST return a circuit implementing `CircuitExt` with accumulator indices provided. + /// In particular, do not return `BaseCircuitBuilder`. + pub fn build( + self, + stage: CircuitBuilderStage, + circuit_params: AggregationCircuitParams, + kzg_params: &ParamsKZG, + ) -> Result { + let agg_vkey_hash_indices = vec![None, Some(SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX), None]; + let snarks = [self.snark_verify_compute, self.snark_subquery_agg, self.snark_keccak_agg]; + for (i, snark) in snarks.iter().enumerate() { + if snark.agg_vk_hash_idx != agg_vkey_hash_indices[i] { + bail!("[AxiomAggregation1] agg_vkey_hash_idx mismatch in snark {i}"); + } + } + let (mut circuit, previous_instances, agg_vkey_hash) = + create_universal_aggregation_circuit::( + stage, + circuit_params, + kzg_params, + snarks.map(|s| s.inner).to_vec(), + agg_vkey_hash_indices, + ); + + let builder = &mut circuit.builder; + let ctx = builder.main(0); + + let [pis_verify_compute, instances_subquery_agg, mut instances_keccak]: [_; 3] = + previous_instances.try_into().unwrap(); + let pis_verify_compute = ComponentPublicInstances::try_from(pis_verify_compute)?; + + let LogicalPisVerifyComputeWithoutAccumulator { + source_chain_id, + compute_results_hash, + query_hash, + query_schema, + results_root_poseidon: promise_results_root_poseidon, + promise_subquery_hashes, + } = pis_verify_compute.other.try_into()?; + + let LogicalPublicInstanceSubqueryAgg { + promise_keccak, + agg_vkey_hash: _, // already read in create_universal_aggregation_circuit + results_root_poseidon, + commit_subquery_hashes, + mmr_keccak, + } = instances_subquery_agg.try_into()?; + + log::debug!("promise_results_root_poseidon: {:?}", promise_results_root_poseidon.value()); + log::debug!("results_root_poseidon: {:?}", results_root_poseidon.value()); + log::debug!("promise_subquery_hashes: {:?}", promise_subquery_hashes.value()); + log::debug!("commit_subquery_hashes: {:?}", commit_subquery_hashes.value()); + ctx.constrain_equal(&promise_results_root_poseidon, &results_root_poseidon); + ctx.constrain_equal(&promise_subquery_hashes, &commit_subquery_hashes); + + let commit_keccak = instances_keccak.pop().unwrap(); + // Await keccak promises: + // * The promise_keccak from SubqueryAggregation should directly equal the output commit of keccak component + // * The promise_result_commit from VerifyCompute should equal poseidon_hash([commit_keccak]) + log::debug!( + "subquery_agg promise_keccak: {:?} commit_keccak: {:?}", + promise_keccak.value(), + commit_keccak.value() + ); + ctx.constrain_equal(&promise_keccak, &commit_keccak); + // ======== Create Poseidon hasher =========== + let gate = GateChip::default(); + let mut hasher = PoseidonHasher::new(POSEIDON_SPEC.clone()); + hasher.initialize_consts(ctx, &gate); + let hashed_commit_keccak = hasher.hash_fix_len_array(ctx, &gate, &[commit_keccak]); + log::debug!("hash(commit_keccak): {:?}", hashed_commit_keccak.value()); + log::debug!( + "verify_compute promise_commit: {:?}", + pis_verify_compute.promise_result_commit.value() + ); + ctx.constrain_equal(&pis_verify_compute.promise_result_commit, &hashed_commit_keccak); + + let logical_pis = LogicalPublicInstanceAxiomAggregation { + source_chain_id, + compute_results_hash, + query_hash, + query_schema, + blockhash_mmr_keccak: mmr_keccak, + agg_vkey_hash, + payee: None, + }; + + if builder.assigned_instances.len() != 1 { + bail!("should only have 1 instance column"); + } + assert_eq!(builder.assigned_instances[0].len(), NUM_FE_ACCUMULATOR); + builder.assigned_instances[0].extend(logical_pis.flatten()); + + Ok(circuit) + } + + /// Circuit for witness generation only + pub fn prover_circuit( + self, + pinning: AggregationCircuitPinning, + kzg_params: &ParamsKZG, + ) -> Result { + Ok(self + .build(CircuitBuilderStage::Prover, pinning.params, kzg_params)? + .use_break_points(pinning.break_points)) + } +} diff --git a/axiom-query/src/axiom_aggregation1/mod.rs b/axiom-query/src/axiom_aggregation1/mod.rs new file mode 100644 index 00000000..3a15bf97 --- /dev/null +++ b/axiom-query/src/axiom_aggregation1/mod.rs @@ -0,0 +1,14 @@ +//! # Axiom Aggregation 1 Circuit +//! +//! The first layer of Axiom aggregation. +//! +//! This aggregates the Subquery Aggregation circuit, the Verify Compute circuit, and the Keccak final aggregation circuit. +//! It checks that the commitments to subquery results and subquery hashes from Subquery Aggregation circuit +//! match those in the Verify Compute circuit. +//! It also checks that all Keccak commitments agree among the circuits. + +pub mod circuit; +pub mod types; + +#[cfg(test)] +pub mod tests; diff --git a/axiom-query/src/axiom_aggregation1/tests.rs b/axiom-query/src/axiom_aggregation1/tests.rs new file mode 100644 index 00000000..d463498b --- /dev/null +++ b/axiom-query/src/axiom_aggregation1/tests.rs @@ -0,0 +1,171 @@ +use std::fs::File; + +use anyhow::Result; +use axiom_eth::{ + halo2_base::{gates::circuit::CircuitBuilderStage, utils::fs::gen_srs}, + halo2curves::bn256::Fr, + keccak::types::OutputKeccakShard, + snark_verifier_sdk::{ + gen_pk, + halo2::{gen_snark_shplonk, read_snark}, + CircuitExt, + }, + utils::{ + build_utils::pinning::PinnableCircuit, + merkle_aggregation::InputMerkleAggregation, + snark_verifier::{AggregationCircuitParams, EnhancedSnark, NUM_FE_ACCUMULATOR}, + }, + zkevm_hashes::keccak::component::circuit::shard::{ + KeccakComponentShardCircuit, KeccakComponentShardCircuitParams, + }, +}; +use itertools::Itertools; +use test_log::test; + +use crate::axiom_aggregation1::types::FINAL_AGG_VKEY_HASH_IDX; + +use super::types::InputAxiomAggregation1; + +fn get_keccak_snark() -> Result { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + // single shard + let output_shard: OutputKeccakShard = serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/data/test/promise_results_keccak_for_agg.json" + ))?)?; + let k = 18u32; + let mut keccak_params = + KeccakComponentShardCircuitParams::new(k as usize, 109, output_shard.capacity, false); + keccak_params.base_circuit_params = + KeccakComponentShardCircuit::::calculate_base_circuit_params(&keccak_params); + + let params = gen_srs(k); + let keygen_circuit = + KeccakComponentShardCircuit::::new(vec![], keccak_params.clone(), false); + let pk = gen_pk(¶ms, &keygen_circuit, None); + let break_points = keygen_circuit.base_circuit_break_points(); + + let inputs = output_shard.responses.iter().map(|(k, _)| k.to_vec()).collect_vec(); + let prover_circuit = KeccakComponentShardCircuit::::new(inputs, keccak_params, true); + prover_circuit.set_base_circuit_break_points(break_points); + let snark_path = format!("{cargo_manifest_dir}/data/test/keccak_shard_for_agg.snark"); + let snark = gen_snark_shplonk(¶ms, &pk, prover_circuit, Some(snark_path)); + + let k = 20u32; + let params = gen_srs(k); + let agg_input = InputMerkleAggregation::new([EnhancedSnark::new(snark, None)]); + + let circuit_params = + AggregationCircuitParams { degree: k, lookup_bits: k as usize - 1, ..Default::default() }; + let mut keygen_circuit = + agg_input.clone().build(CircuitBuilderStage::Keygen, circuit_params, ¶ms)?; + keygen_circuit.calculate_params(Some(20)); + let name = "keccak_for_agg"; + let pinning_path = format!("{cargo_manifest_dir}/configs/test/{name}.json"); + let pk_path = format!("{cargo_manifest_dir}/data/test/{name}.pk"); + let snark_path = format!("{cargo_manifest_dir}/data/test/{name}.snark"); + let (pk, pinning) = keygen_circuit.create_pk(¶ms, pk_path, pinning_path)?; + + let prover_circuit = agg_input.prover_circuit(pinning, ¶ms)?; + let snark = gen_snark_shplonk(¶ms, &pk, prover_circuit, Some(snark_path)); + Ok(EnhancedSnark::new(snark, None)) +} + +fn get_test_input() -> Result { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + let verify_compute_snark = + read_snark(format!("{cargo_manifest_dir}/data/test/verify_compute_for_agg.snark"))?; + let snark_verify_compute = EnhancedSnark::new(verify_compute_snark, None); + + let snark_subquery_agg: EnhancedSnark = serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/data/test/subquery_aggregation_for_agg.snark.json" + ))?)?; + + let snark_keccak_agg = if let Ok(snark) = + read_snark(format!("{cargo_manifest_dir}/data/test/keccak_for_agg.snark")) + { + EnhancedSnark::new(snark, None) + } else { + get_keccak_snark()? + }; + + Ok(InputAxiomAggregation1 { snark_verify_compute, snark_subquery_agg, snark_keccak_agg }) +} + +#[test] +#[ignore = "prover"] +fn test_prover_axiom_agg1() -> anyhow::Result<()> { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + + let k = 22; + let params = gen_srs(k as u32); + + let input = get_test_input()?; + let mut keygen_circuit = input.clone().build( + CircuitBuilderStage::Keygen, + AggregationCircuitParams { degree: k as u32, lookup_bits: k - 1, ..Default::default() }, + ¶ms, + )?; + keygen_circuit.calculate_params(Some(20)); + let instance1 = keygen_circuit.instances(); + let abs_agg_vk_hash_idx = NUM_FE_ACCUMULATOR + FINAL_AGG_VKEY_HASH_IDX; + let name = "axiom_aggregation1_for_agg"; + let pinning_path = format!("{cargo_manifest_dir}/configs/test/{name}.json"); + let pk_path = format!("{cargo_manifest_dir}/data/test/{name}.pk"); + let (pk, pinning) = keygen_circuit.create_pk(¶ms, pk_path, pinning_path)?; + keygen_circuit.builder.clear(); + drop(keygen_circuit); + + #[cfg(all(feature = "keygen", not(debug_assertions)))] + { + // test keygen + use crate::subquery_aggregation::types::SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX; + use axiom_eth::halo2_proofs::{plonk::keygen_vk, SerdeFormat}; + use axiom_eth::snark_verifier_sdk::{halo2::gen_dummy_snark_from_protocol, SHPLONK}; + use axiom_eth::utils::build_utils::aggregation::get_dummy_aggregation_params; + let [dum_snark_verify_comp, mut dum_snark_sub_agg, dum_snark_keccak] = + [&input.snark_verify_compute, &input.snark_subquery_agg, &input.snark_keccak_agg] + .map(|s| gen_dummy_snark_from_protocol::(s.inner.protocol.clone())); + let subquery_abs_agg_vk_hash_idx = + NUM_FE_ACCUMULATOR + SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX; + // The correct one from the subquery agg circuit + let subquery_agg_vk_hash = + input.snark_subquery_agg.inner.instances[0][subquery_abs_agg_vk_hash_idx]; + // Put correct one into the dummy + dum_snark_sub_agg.instances[0][subquery_abs_agg_vk_hash_idx] = subquery_agg_vk_hash; + let input = InputAxiomAggregation1 { + snark_verify_compute: EnhancedSnark::new(dum_snark_verify_comp, None), + snark_subquery_agg: EnhancedSnark::new( + dum_snark_sub_agg, + Some(SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX), + ), + snark_keccak_agg: EnhancedSnark::new(dum_snark_keccak, None), + }; + let mut circuit = + input.build(CircuitBuilderStage::Keygen, get_dummy_aggregation_params(k), ¶ms)?; + circuit.calculate_params(Some(20)); + let vk = keygen_vk(¶ms, &circuit)?; + if pk.get_vk().to_bytes(SerdeFormat::RawBytes) != vk.to_bytes(SerdeFormat::RawBytes) { + panic!("vk mismatch"); + } + let instance2 = circuit.instances(); + assert_eq!( + instance1[0][abs_agg_vk_hash_idx], instance2[0][abs_agg_vk_hash_idx], + "agg vkey hash mismatch" + ); + } + + let mut prover_circuit = input.build(CircuitBuilderStage::Prover, pinning.params, ¶ms)?; + prover_circuit.set_break_points(pinning.break_points); + let instance3 = prover_circuit.instances(); + assert_eq!( + instance1[0][abs_agg_vk_hash_idx], instance3[0][abs_agg_vk_hash_idx], + "agg vkey hash mismatch" + ); + + let snark = gen_snark_shplonk(¶ms, &pk, prover_circuit, None::<&str>); + let snark = EnhancedSnark { inner: snark, agg_vk_hash_idx: Some(FINAL_AGG_VKEY_HASH_IDX) }; + + let snark_path = format!("{cargo_manifest_dir}/data/test/{name}.snark.json"); + serde_json::to_writer(File::create(snark_path)?, &snark)?; + Ok(()) +} diff --git a/axiom-query/src/axiom_aggregation1/types.rs b/axiom-query/src/axiom_aggregation1/types.rs new file mode 100644 index 00000000..e6af42ec --- /dev/null +++ b/axiom-query/src/axiom_aggregation1/types.rs @@ -0,0 +1,75 @@ +use std::iter; + +use axiom_codec::HiLo; +use axiom_eth::utils::snark_verifier::EnhancedSnark; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InputAxiomAggregation1 { + pub snark_verify_compute: EnhancedSnark, + pub snark_subquery_agg: EnhancedSnark, + /// Snark of aggregation circuit for keccak component shards + pub snark_keccak_agg: EnhancedSnark, +} + +const NUM_LOGICAL_INSTANCE_NO_PAYEE: usize = 1 + 1 + 2 + 2 + 2 + 2; +pub const NUM_LOGICAL_INSTANCE_WITH_PAYEE: usize = NUM_LOGICAL_INSTANCE_NO_PAYEE + 1; +pub const FINAL_AGG_VKEY_HASH_IDX: usize = NUM_LOGICAL_INSTANCE_NO_PAYEE - 1; + +/// The public instances of the AxiomAggregation1 and AxiomAggregation2 circuits, +/// excluding the accumulator at the beginning. +/// The `payee` field is only provided and exposed in AxiomAggregation2. +/// We use the same struct for both circuits for uniformity. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[serde(rename_all = "camelCase")] +pub struct LogicalPublicInstanceAxiomAggregation { + pub source_chain_id: T, + pub compute_results_hash: HiLo, + pub query_hash: HiLo, + pub query_schema: HiLo, + pub blockhash_mmr_keccak: HiLo, + pub agg_vkey_hash: T, + #[serde(skip_serializing_if = "Option::is_none")] + pub payee: Option, +} + +impl LogicalPublicInstanceAxiomAggregation { + pub fn flatten(&self) -> Vec { + iter::once(self.source_chain_id) + .chain(self.compute_results_hash.hi_lo()) + .chain(self.query_hash.hi_lo()) + .chain(self.query_schema.hi_lo()) + .chain(self.blockhash_mmr_keccak.hi_lo()) + .chain([self.agg_vkey_hash]) + .chain(self.payee) + .collect() + } +} + +impl TryFrom> for LogicalPublicInstanceAxiomAggregation { + type Error = anyhow::Error; + fn try_from(value: Vec) -> Result { + if value.len() != NUM_LOGICAL_INSTANCE_NO_PAYEE + && value.len() != NUM_LOGICAL_INSTANCE_NO_PAYEE + 1 + { + anyhow::bail!("invalid number of instances"); + } + let source_chain_id = value[0]; + let compute_results_hash = HiLo::from_hi_lo([value[1], value[2]]); + let query_hash = HiLo::from_hi_lo([value[3], value[4]]); + let query_schema = HiLo::from_hi_lo([value[5], value[6]]); + let blockhash_mmr_keccak = HiLo::from_hi_lo([value[7], value[8]]); + let agg_vkey_hash = value[9]; + let payee = value.get(NUM_LOGICAL_INSTANCE_NO_PAYEE).copied(); + Ok(Self { + source_chain_id, + compute_results_hash, + query_hash, + query_schema, + blockhash_mmr_keccak, + agg_vkey_hash, + payee, + }) + } +} diff --git a/axiom-query/src/axiom_aggregation2/circuit.rs b/axiom-query/src/axiom_aggregation2/circuit.rs new file mode 100644 index 00000000..6ec551d8 --- /dev/null +++ b/axiom-query/src/axiom_aggregation2/circuit.rs @@ -0,0 +1,80 @@ +use anyhow::{bail, Result}; +use axiom_codec::utils::native::encode_addr_to_field; +use axiom_eth::{ + halo2_base::gates::circuit::CircuitBuilderStage, + halo2_proofs::poly::kzg::commitment::ParamsKZG, + halo2curves::bn256::Bn256, + snark_verifier_sdk::{halo2::aggregation::AggregationCircuit, SHPLONK}, + utils::{ + build_utils::pinning::aggregation::AggregationCircuitPinning, + snark_verifier::{ + create_universal_aggregation_circuit, AggregationCircuitParams, EnhancedSnark, + NUM_FE_ACCUMULATOR, + }, + }, +}; +use ethers_core::types::Address; +use serde::{Deserialize, Serialize}; + +use crate::axiom_aggregation1::types::{ + LogicalPublicInstanceAxiomAggregation, FINAL_AGG_VKEY_HASH_IDX, +}; + +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InputAxiomAggregation2 { + pub payee: Address, + /// Snark from AxiomAggregation1 + pub snark_axiom_agg1: EnhancedSnark, +} + +impl InputAxiomAggregation2 { + pub fn build( + self, + stage: CircuitBuilderStage, + circuit_params: AggregationCircuitParams, + kzg_params: &ParamsKZG, + ) -> anyhow::Result { + if self.snark_axiom_agg1.agg_vk_hash_idx != Some(FINAL_AGG_VKEY_HASH_IDX) { + bail!("AxiomAggregation1 snark agg_vkey_hash_idx exception"); + } + let (mut circuit, mut previous_instances, agg_vkey_hash) = + create_universal_aggregation_circuit::( + stage, + circuit_params, + kzg_params, + vec![self.snark_axiom_agg1.inner], + vec![Some(FINAL_AGG_VKEY_HASH_IDX)], + ); + let instances = previous_instances.pop().unwrap(); + let prev_pis = LogicalPublicInstanceAxiomAggregation::try_from(instances)?; + + let builder = &mut circuit.builder; + let payee = builder.main(0).load_witness(encode_addr_to_field(&self.payee)); + + let logical_pis = LogicalPublicInstanceAxiomAggregation { + agg_vkey_hash, // use new agg_vkey_hash + payee: Some(payee), // previously there was no payee + ..prev_pis // re-expose previous public instances + }; + + if builder.assigned_instances.len() != 1 { + bail!("should only have 1 instance column"); + } + assert_eq!(builder.assigned_instances[0].len(), NUM_FE_ACCUMULATOR); + builder.assigned_instances[0].extend(logical_pis.flatten()); + + Ok(circuit) + } + + /// Circuit for witness generation only + pub fn prover_circuit( + self, + pinning: AggregationCircuitPinning, + kzg_params: &ParamsKZG, + ) -> Result { + Ok(self + .build(CircuitBuilderStage::Prover, pinning.params, kzg_params)? + .use_break_points(pinning.break_points)) + } +} diff --git a/axiom-query/src/axiom_aggregation2/integration_test.sh b/axiom-query/src/axiom_aggregation2/integration_test.sh new file mode 100644 index 00000000..0dc3c1c3 --- /dev/null +++ b/axiom-query/src/axiom_aggregation2/integration_test.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +if [ -z $PARAMS_DIR ]; then + echo "PARAMS_DIR not set" + exit 1 +fi + +repo_root=$(git rev-parse --show-toplevel) +cd $repo_root/axiom-query + +set -e + +rm -f data/test/*.pk +rm -f data/test/*.snark +export CARGO_PROFILE_DEV_DEBUG_ASSERTIONS=false +export RUST_LOG=debug +export RUST_BACKTRACE=1 + +cargo t test_mock_header_subquery +cargo t test_mock_results_root_header_only_for_agg -- --ignored +cargo t test_verify_compute_prepare_for_agg -- --ignored +cargo t test_merge_keccak_shards_for_agg -- --ignored + +cargo t test_prover_subquery_agg -- --ignored --nocapture +cargo t test_verify_compute_prover_for_agg -- --ignored --nocapture +cargo t test_prover_axiom_agg1 -- --ignored --nocapture +cargo t test_prover_axiom_agg2 --features revm -- --ignored --nocapture diff --git a/axiom-query/src/axiom_aggregation2/mod.rs b/axiom-query/src/axiom_aggregation2/mod.rs new file mode 100644 index 00000000..ca324414 --- /dev/null +++ b/axiom-query/src/axiom_aggregation2/mod.rs @@ -0,0 +1,10 @@ +//! # Axiom Aggregation 2 Circuit +//! +//! The second (and currently final) layer of Axiom aggregation. +//! This circuit aggregates the single snark of the Axiom Aggregation 1 circuit. It exposes the same +//! public instances but also adds a `payee` instance. + +pub mod circuit; + +#[cfg(test)] +pub mod tests; diff --git a/axiom-query/src/axiom_aggregation2/readme.md b/axiom-query/src/axiom_aggregation2/readme.md new file mode 100644 index 00000000..5e336784 --- /dev/null +++ b/axiom-query/src/axiom_aggregation2/readme.md @@ -0,0 +1,18 @@ +To run all the tests leading up to the final `AxiomAggregation2` test, run the following in the `axiom-query` directory: + +Remove any `data/test/*.snark` and `data/test/*.pk` files if you don't want caching. + +```bash +cargo t test_prover_subquery_agg -- --ignored --nocapture +cargo t test_verify_compute_prover_for_agg -- --ignored --nocapture +cargo t test_prover_axiom_agg1 -- --ignored --nocapture +cargo t test_prover_axiom_agg2 --features revm -- --ignored --nocapture +``` +If feature "keygen" is on, then you must run with `CARGO_PROFILE_DEV_DEBUG_ASSERTIONS=false` because we check keygen using dummy snarks that do not pass debug assertions. + +The final test will generate the EVM proof and try to run it against the snark verifier smart contract if you enable feature "revm". For the latter you need Solidity version 0.8.19 (the pragma is fixed to 0.8.19 in snark verifier). + +Note the computeProof was generated using the trusted setup [here ](https://docs.axiom.xyz/transparency-and-security/kzg-trusted-setup) so for aggregation circuits to be consistent you will also need to download the same trusted setup and either put it in `axiom-query/params` or set `PARAMS_DIR` environmental variable. + +## Regenerating test inputs +The tests above depend on certain pre-generated input files. To regenerate them and run all commands above, use the [integration_test.sh](./integration_test.sh) script. diff --git a/axiom-query/src/axiom_aggregation2/tests.rs b/axiom-query/src/axiom_aggregation2/tests.rs new file mode 100644 index 00000000..164c9536 --- /dev/null +++ b/axiom-query/src/axiom_aggregation2/tests.rs @@ -0,0 +1,138 @@ +use std::{fs::File, io::Write, path::Path, str::FromStr}; + +use anyhow::Result; +use axiom_eth::{ + halo2_base::{gates::circuit::CircuitBuilderStage, utils::fs::gen_srs}, + halo2_proofs::dev::MockProver, + snark_verifier_sdk::{ + evm::{encode_calldata, gen_evm_proof_shplonk, gen_evm_verifier_shplonk}, + halo2::aggregation::AggregationCircuit, + CircuitExt, + }, + utils::{ + build_utils::pinning::PinnableCircuit, + snark_verifier::{AggregationCircuitParams, EnhancedSnark, NUM_FE_ACCUMULATOR}, + }, +}; +use ethers_core::types::Address; +use hex::encode; + +use crate::axiom_aggregation1::types::FINAL_AGG_VKEY_HASH_IDX; + +use super::circuit::InputAxiomAggregation2; + +fn get_test_input() -> Result { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + let snark_axiom_agg1: EnhancedSnark = serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/data/test/axiom_aggregation1_for_agg.snark.json" + ))?)?; + + Ok(InputAxiomAggregation2 { + snark_axiom_agg1, + payee: Address::from_str("0x00000000000000000000000000000000deadbeef")?, + }) +} + +#[test] +#[ignore = "requires real SRS"] +fn test_mock_axiom_agg2() -> anyhow::Result<()> { + let k = 22; + let params = gen_srs(k as u32); + + let input = get_test_input()?; + let mut circuit = input.build( + CircuitBuilderStage::Mock, + AggregationCircuitParams { degree: k as u32, lookup_bits: k - 1, ..Default::default() }, + ¶ms, + )?; + circuit.calculate_params(Some(20)); + let instances = circuit.instances(); + MockProver::run(k as u32, &circuit, instances).unwrap().assert_satisfied(); + + Ok(()) +} + +// cargo t test_prover_axiom_agg2 --features revm -- --ignored --nocapture +// feature "revm" requires solc 0.8.19 installed +#[test] +#[ignore = "prover"] +fn test_prover_axiom_agg2() -> anyhow::Result<()> { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + + let k = 23; + let params = gen_srs(k as u32); + + let input = get_test_input()?; + let mut keygen_circuit = input.clone().build( + CircuitBuilderStage::Keygen, + AggregationCircuitParams { degree: k as u32, lookup_bits: k - 1, ..Default::default() }, + ¶ms, + )?; + keygen_circuit.calculate_params(Some(20)); + let instance1 = keygen_circuit.instances(); + let abs_agg_vk_hash_idx = NUM_FE_ACCUMULATOR + FINAL_AGG_VKEY_HASH_IDX; + let name = "axiom_aggregation2"; + let pinning_path = format!("{cargo_manifest_dir}/configs/test/{name}.json"); + let pk_path = format!("{cargo_manifest_dir}/data/test/{name}.pk"); + let (pk, pinning) = keygen_circuit.create_pk(¶ms, pk_path, pinning_path)?; + keygen_circuit.builder.clear(); + drop(keygen_circuit); + + #[cfg(all(feature = "keygen", not(debug_assertions)))] + { + // test keygen + use axiom_eth::halo2_proofs::{plonk::keygen_vk, SerdeFormat}; + use axiom_eth::snark_verifier_sdk::{halo2::gen_dummy_snark_from_protocol, SHPLONK}; + use axiom_eth::utils::build_utils::aggregation::get_dummy_aggregation_params; + let mut dum_snark_axiom_agg1 = + gen_dummy_snark_from_protocol::(input.snark_axiom_agg1.inner.protocol.clone()); + // The correct one from the subquery agg circuit + let axiom_agg1_agg_vk_hash = input.snark_axiom_agg1.inner.instances[0][abs_agg_vk_hash_idx]; + // Put correct one into the dummy + dum_snark_axiom_agg1.instances[0][abs_agg_vk_hash_idx] = axiom_agg1_agg_vk_hash; + let input = InputAxiomAggregation2 { + snark_axiom_agg1: EnhancedSnark::new( + dum_snark_axiom_agg1, + Some(FINAL_AGG_VKEY_HASH_IDX), + ), + payee: Default::default(), + }; + let mut circuit = + input.build(CircuitBuilderStage::Keygen, get_dummy_aggregation_params(k), ¶ms)?; + circuit.calculate_params(Some(20)); + let vk = keygen_vk(¶ms, &circuit)?; + if pk.get_vk().to_bytes(SerdeFormat::RawBytes) != vk.to_bytes(SerdeFormat::RawBytes) { + panic!("vk mismatch"); + } + let instance2 = circuit.instances(); + assert_eq!( + instance1[0][abs_agg_vk_hash_idx], instance2[0][abs_agg_vk_hash_idx], + "agg vkey hash mismatch" + ); + } + + let mut prover_circuit = input.build(CircuitBuilderStage::Prover, pinning.params, ¶ms)?; + prover_circuit.set_break_points(pinning.break_points); + let instance3 = prover_circuit.instances(); + assert_eq!( + instance1[0][abs_agg_vk_hash_idx], instance3[0][abs_agg_vk_hash_idx], + "agg vkey hash mismatch" + ); + + let sol_path = format!("{cargo_manifest_dir}/data/test/{name}.sol"); + let _solidity_code = gen_evm_verifier_shplonk::( + ¶ms, + pk.get_vk(), + prover_circuit.num_instance(), + Some(Path::new(&sol_path)), + ); + let instances = prover_circuit.instances(); + let proof = gen_evm_proof_shplonk(¶ms, &pk, prover_circuit, instances.clone()); + let evm_proof = encode(encode_calldata(&instances, &proof)); + let mut f = File::create(format!("{cargo_manifest_dir}/data/test/{name}.evm_proof"))?; + write!(f, "{evm_proof}")?; + #[cfg(feature = "revm")] + axiom_eth::snark_verifier_sdk::evm::evm_verify(_solidity_code, instances, proof); + + Ok(()) +} diff --git a/axiom-query/src/bin/keygen.rs b/axiom-query/src/bin/keygen.rs new file mode 100644 index 00000000..a7f78989 --- /dev/null +++ b/axiom-query/src/bin/keygen.rs @@ -0,0 +1,81 @@ +use std::{ + env, + fs::{self, File}, + path::PathBuf, +}; + +use anyhow::Context; +use axiom_eth::{ + snark_verifier_sdk::{evm::gen_evm_verifier_shplonk, halo2::aggregation::AggregationCircuit}, + utils::build_utils::keygen::read_srs_from_dir, +}; +use axiom_query::keygen::{ + agg::{common::AggTreePinning, SupportedAggPinning}, + ProvingKeySerializer, SupportedPinning, SupportedRecursiveIntent, +}; +use clap::Parser; + +#[derive(Parser, Debug)] +pub struct Cli { + #[arg(long = "srs-dir")] + pub srs_dir: PathBuf, + #[arg(long = "data-dir")] + pub data_dir: Option, + #[arg(long = "intent")] + pub intent_path: PathBuf, + /// Tag for proof tree file. + /// Defaults to the aggregate vkey hash (Fr, big-endian) if the circuit is a universal aggregation circuit, or to the root circuit ID otherwise. + #[arg(short, long = "tag")] + pub tag: Option, +} + +fn main() -> anyhow::Result<()> { + env_logger::try_init().unwrap(); + let cli = Cli::parse(); + let srs_dir = cli.srs_dir; + let data_dir = cli.data_dir.unwrap_or_else(|| { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + PathBuf::from(cargo_manifest_dir).join("data").join("playground") + }); + fs::create_dir_all(&data_dir).context("Create data dir")?; + // Directly deserializing from yaml doesn't work, but going to json first does?? + let intent_json: serde_json::Value = + serde_yaml::from_reader(File::open(&cli.intent_path).with_context(|| { + format!("Failed to open intent file {}", cli.intent_path.display()) + })?)?; + let intent: SupportedRecursiveIntent = serde_json::from_value(intent_json)?; + let (proof_node, pk, pinning) = intent.create_and_serialize_proving_key(&srs_dir, &data_dir)?; + println!("Circuit id: {}", proof_node.circuit_id); + + let tag = cli.tag.unwrap_or_else(|| { + if let Some((_, agg_vk_hash)) = pinning.agg_vk_hash_data() { + format!("{:?}", agg_vk_hash) + } else { + proof_node.circuit_id.clone() + } + }); + + let tree_path = data_dir.join(format!("{tag}.tree")); + let f = File::create(&tree_path).with_context(|| { + format!("Failed to create aggregation tree file {}", tree_path.display()) + })?; + serde_json::to_writer_pretty(f, &proof_node)?; + println!("Wrote aggregation tree to {}", tree_path.display()); + + if let SupportedPinning::Agg(SupportedAggPinning::AxiomAgg2(pinning)) = pinning { + log::debug!("Creating verifier contract"); + let num_instance = pinning.num_instance(); + let solc_path = data_dir.join(&proof_node.circuit_id).with_extension("sol"); + let k = pinning.params.agg_params.degree; + let kzg_params = read_srs_from_dir(&srs_dir, k)?; + gen_evm_verifier_shplonk::( + &kzg_params, + pk.get_vk(), + num_instance, + Some(&solc_path), + ); + println!("Axiom Aggregation 2 snark-verifier contract written to {}", solc_path.display()); + } + + Ok(()) +} diff --git a/axiom-query/src/bin/readme.md b/axiom-query/src/bin/readme.md new file mode 100644 index 00000000..c96855ff --- /dev/null +++ b/axiom-query/src/bin/readme.md @@ -0,0 +1,22 @@ +# Proving and Verifying Key Generation + +To recursively run proving key generation on all circuits in an aggregation tree specified by an intent file, you can install the keygen binary to your path via: + +```bash +cargo install --path axiom-query --force +``` +This builds `axiom-query-keygen` binary in release mode and installs it to your path. +Then run: +```bash +axiom-query-keygen --srs-dir --intent configs/templates/axiom_agg_2.yml --tag --data-dir +``` +to actually generate the proving keys. + +For faster compile times, you can run the keygen binary directly in dev mode (still with `opt-level=3`) via: +```bash +CARGO_PROFILE_DEV_DEBUG_ASSERTIONS=false cargo run --bin axiom-query-keygen -- --srs-dir --intent configs/templates/axiom_agg_2.yml --tag --data-dir +``` +Debug assertions needs to be **off** as we use dummy witnesses that do not pass certain debug assertions. + +* A file with full aggregation tree of circuit IDs will be output to a `.tree` file as a JSON. +* `` defaults to `` if not specified. diff --git a/axiom-query/src/bin/rename_snark_verifier.sh b/axiom-query/src/bin/rename_snark_verifier.sh new file mode 100644 index 00000000..74fb95e3 --- /dev/null +++ b/axiom-query/src/bin/rename_snark_verifier.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Check if a file name is provided +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +# File to be modified +FILE="$1" + +new_file="AxiomV2QueryVerifier.sol" + +# The first and third lines are empty +# Remove the first and third line from the file +sed '1d;3d' $FILE > $new_file + +# Replace 'contract Halo2Verifier' with 'contract AxiomV2QueryVerifier' +sed 's/contract Halo2Verifier/contract AxiomV2QueryVerifier/g' $new_file > $new_file.tmp && mv $new_file.tmp $new_file + +echo "Modifications complete. Written to $new_file" + +echo "To diff is:" +diff $FILE $new_file + +echo "Running forge fmt on $new_file" +forge fmt $new_file diff --git a/axiom-query/src/components/mod.rs b/axiom-query/src/components/mod.rs new file mode 100644 index 00000000..51eddade --- /dev/null +++ b/axiom-query/src/components/mod.rs @@ -0,0 +1,17 @@ +use axiom_eth::rlc::circuit::RlcCircuitParams; + +/// Computes results root (keccak), results root (poseidon), and subquery hashes by making promise calls to subquery circuits. +pub mod results; +/// The subquery circuits +pub mod subqueries; + +pub const MAX_MERKLE_TREE_HEIGHT_FOR_KECCAK_RESULTS: usize = 3; +/// Helper function for testing to create a dummy RlcCircuitParams +pub fn dummy_rlc_circuit_params(k: usize) -> RlcCircuitParams { + let mut circuit_params = RlcCircuitParams::default(); + circuit_params.base.k = k; + circuit_params.base.lookup_bits = Some(8); + circuit_params.base.num_instance_columns = 1; + circuit_params.num_rlc_columns = 1; + circuit_params +} diff --git a/axiom-query/src/components/results/circuit.rs b/axiom-query/src/components/results/circuit.rs new file mode 100644 index 00000000..18423d09 --- /dev/null +++ b/axiom-query/src/components/results/circuit.rs @@ -0,0 +1,201 @@ +use axiom_codec::constants::{MAX_SUBQUERY_OUTPUTS, NUM_SUBQUERY_TYPES}; +use axiom_components::ecdsa::ComponentTypeECDSA; +use axiom_eth::{ + component_type_list, + halo2_base::{ + gates::{GateInstructions, RangeInstructions}, + halo2_proofs::plonk::ConstraintSystem, + AssignedValue, + QuantumCell::Constant, + }, + keccak::{types::ComponentTypeKeccak, KeccakChip}, + rlc::circuit::builder::RlcCircuitBuilder, + utils::{ + build_utils::aggregation::CircuitMetadata, + component::{ + circuit::{ + ComponentBuilder, ComponentCircuitImpl, CoreBuilder, CoreBuilderOutput, + CoreBuilderOutputParams, CoreBuilderParams, + }, + promise_collector::PromiseCaller, + promise_loader::{ + combo::PromiseBuilderCombo, multi::MultiPromiseLoader, single::PromiseLoader, + }, + types::FixLenLogical, + utils::create_hasher, + }, + }, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::subqueries::{ + account::types::ComponentTypeAccountSubquery, + block_header::types::ComponentTypeHeaderSubquery, + receipt::types::ComponentTypeReceiptSubquery, + solidity_mappings::types::ComponentTypeSolidityNestedMappingSubquery, + storage::types::ComponentTypeStorageSubquery, transaction::types::ComponentTypeTxSubquery, + }, + Field, +}; + +use super::{ + results_root::get_results_root, + subquery_hash::get_subquery_hash, + types::{ + CircuitInputResultsRootShard, ComponentTypeResultsRoot, LogicalPublicInstanceResultsRoot, + RlcAdapterResultsRoot, SubqueryResultCall, VirtualComponentType, + }, +}; + +/// Core builder for results root component. +pub struct CoreBuilderResultsRoot { + pub input: Option>, + pub params: CoreParamsResultRoot, +} + +/// Specify the output format of ResultRoot component. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct CoreParamsResultRoot { + /// - `enabled_types[subquery_type as u16]` is true if the subquery type is enabled. + /// - `enabled_types[0]` corresponds to the Null type. It doesn't matter whether it's enabled or disabled; behavior remains the same. + pub enabled_types: [bool; NUM_SUBQUERY_TYPES], + /// Maximum total number of subquery results supported + pub capacity: usize, +} +impl CoreBuilderParams for CoreParamsResultRoot { + fn get_output_params(&self) -> CoreBuilderOutputParams { + CoreBuilderOutputParams::new(vec![]) + } +} + +/// Subquery dependencies of ResultsRoot component. +pub type SubqueryDependencies = component_type_list!( + F, + ComponentTypeHeaderSubquery, + ComponentTypeAccountSubquery, + ComponentTypeStorageSubquery, + ComponentTypeTxSubquery, + ComponentTypeReceiptSubquery, + ComponentTypeSolidityNestedMappingSubquery, + ComponentTypeECDSA +); + +pub type PromiseLoaderResultsRoot = PromiseBuilderCombo< + F, + PromiseLoader>, // keccak + MultiPromiseLoader< + F, + VirtualComponentType, + SubqueryDependencies, + RlcAdapterResultsRoot, + >, // Grouped subquery results +>; +pub type ComponentCircuitResultsRoot = + ComponentCircuitImpl, PromiseLoaderResultsRoot>; + +impl CircuitMetadata for CoreBuilderResultsRoot { + const HAS_ACCUMULATOR: bool = false; + fn num_instance(&self) -> Vec { + // currently implemented at the ComponentCircuitImpl level + unreachable!() + } +} + +impl ComponentBuilder for CoreBuilderResultsRoot { + type Params = CoreParamsResultRoot; + fn new(params: Self::Params) -> Self { + Self { input: None, params } + } + fn get_params(&self) -> Self::Params { + self.params.clone() + } + fn clear_witnesses(&mut self) {} + fn calculate_params(&mut self) -> Self::Params { + self.params.clone() + } + fn configure_with_params(_: &mut ConstraintSystem, _: Self::Params) {} +} + +impl CoreBuilder for CoreBuilderResultsRoot { + type CompType = ComponentTypeResultsRoot; + type PublicInstanceValue = LogicalPublicInstanceResultsRoot; + type PublicInstanceWitness = LogicalPublicInstanceResultsRoot>; + type CoreInput = CircuitInputResultsRootShard; + + fn feed_input(&mut self, mut input: Self::CoreInput) -> anyhow::Result<()> { + if input.subqueries.len() > self.params.capacity { + anyhow::bail!( + "Subquery results table is greater than capcaity - {} > {}", + input.subqueries.len(), + self.params.capacity + ); + } + let cap = self.params.capacity; + input.subqueries.rows.resize(cap, input.subqueries.rows[0]); + self.input = Some(input); + Ok(()) + } + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_caller: PromiseCaller, + ) -> CoreBuilderOutput { + let keccak = + KeccakChip::new_with_promise_collector(builder.range_chip(), promise_caller.clone()); + let range = keccak.range(); + let gate = &range.gate; + + // Assumption: we already have input when calling this function. + // TODO: automatically derive a dummy input from params. + let input = self.input.as_ref().unwrap(); + + let ctx = builder.base.main(0); + // assign subquery results + let subqueries = input.subqueries.assign(ctx).rows; + // Make virtual promise calls. + for subquery in &subqueries { + promise_caller + .call::, VirtualComponentType>( + ctx, + SubqueryResultCall(*subquery), + ) + .unwrap(); + } + + // compute subquery hashes (keccak) + let subquery_hashes = subqueries + .iter() + .map(|row| get_subquery_hash(ctx, &keccak, &row.key, &self.params.enabled_types)) + .collect_vec(); + + let num_subqueries = ctx.load_witness(input.num_subqueries); + range.check_less_than_safe(ctx, num_subqueries, subqueries.len() as u64 + 1); + + let mut poseidon = create_hasher(); + poseidon.initialize_consts(ctx, gate); + // compute resultsRootPoseidon + let results_root_poseidon = + get_results_root(ctx, range, &poseidon, &subqueries, num_subqueries); + + let commit_subquery_hashes = { + // take variable length list of `num_subqueries` subquery hashes and flat hash them all together + // the reason we use variable length is so the hash does not depend on `subquery_hashes.len()` and only on `num_subqueries`. this allows more flexibility in the VerifyCompute circuit + let to_commit = subquery_hashes.into_iter().flat_map(|hash| hash.hi_lo()).collect_vec(); + let len = gate.mul(ctx, num_subqueries, Constant(F::from(MAX_SUBQUERY_OUTPUTS as u64))); + poseidon.hash_var_len_array(ctx, range, &to_commit, len) + }; + + // This component is never called directly. + // The VerifyCompute circuit will read the other public instances and decommit them directly. + let pis = + LogicalPublicInstanceResultsRoot { results_root_poseidon, commit_subquery_hashes }; + + CoreBuilderOutput:: { + public_instances: pis.into_raw(), + virtual_table: vec![], + logical_results: vec![], + } + } +} diff --git a/axiom-query/src/components/results/mod.rs b/axiom-query/src/components/results/mod.rs new file mode 100644 index 00000000..7087c54d --- /dev/null +++ b/axiom-query/src/components/results/mod.rs @@ -0,0 +1,27 @@ +//! # Calculate Subquery Results Root and Subquery Hashes Component Circuit +//! +//! This component circuit is responsible for: +//! - Taking in **ordered** list of **ungrouped** subqueries with results and +//! checking the results are all valid with respect to promise commitments of +//! the individual subquery components, which are **grouped** by subquery type. +//! - Computing the subquery hashes of each subquery in the ordered list. + +use axiom_codec::constants::NUM_SUBQUERY_TYPES; + +/// Circuit and Component Implementation. +pub mod circuit; +/// Compute resultsRoot keccak +pub mod results_root; +/// Subquery hash computation handler +pub mod subquery_hash; +/// Contains the logic of joining virtual tables of different subquery types together into one big table. +/// The virtual tables are of different widths: rather than resizing them directly in-circuit, we only compute +/// the RLC of the resized tables, so that the result is a table with the RLCs of the resized tables, all joined together. +pub mod table; +/// Types +pub mod types; + +#[cfg(test)] +pub mod tests; + +pub const ENABLE_ALL_SUBQUERY_TYPES: [bool; NUM_SUBQUERY_TYPES] = [true; NUM_SUBQUERY_TYPES]; diff --git a/axiom-query/src/components/results/results_root.rs b/axiom-query/src/components/results/results_root.rs new file mode 100644 index 00000000..6eefdbe3 --- /dev/null +++ b/axiom-query/src/components/results/results_root.rs @@ -0,0 +1,137 @@ +use axiom_eth::{ + halo2_base::{ + gates::{GateInstructions, RangeChip, RangeInstructions}, + poseidon::hasher::PoseidonHasher, + safe_types::SafeBool, + utils::log2_ceil, + AssignedValue, Context, + }, + utils::circuit_utils::{log2_ceil as circuit_log2_ceil, unsafe_lt_mask}, +}; +use ethers_core::{types::H256, utils::keccak256}; +use itertools::Itertools; + +use crate::utils::codec::{get_num_fe_from_subquery_key, AssignedSubqueryResult}; +use crate::Field; + +/// The zip of `subquery_hashes` and `results` may be resized to some fixed length ordained +/// by the circuit. The true number of subqueries is given by `num_subqueries`, and we +/// want to compute the results root for those subqueries. +/// +/// ## Definitions +/// - `subqueryResultsRoot`: The Keccak Merkle root of the padded tree (pad by bytes32(0)) with +/// even index leaves given by `subqueryHash := keccak(type . subqueryData)` +/// and odd index leaves given by the result `value` (all encoded in bytes). +/// - `subqueryResultsPoseidonRoot`: see [get_results_root_poseidon]. +/// +/// Note: this is _almost_ the same as the Merkle root of the padded tree with leaves +/// given by `keccak(subqueryHash . value)`. The difference is that in the latter we must pad by +/// `keccak(bytes32(0) . bytes32(0))`. +/// +/// ## Assumptions +/// - `num_subqueries <= results.len()` +/// - `subquery_hashes` and `results` are non-empty of the same length. This length is known +/// at compile time. +// Likely `results.len()` will already be a power of 2, so not worth optimizing anything there +pub fn get_results_root( + ctx: &mut Context, + range: &RangeChip, + poseidon: &PoseidonHasher, + results: &[AssignedSubqueryResult], + num_subqueries: AssignedValue, +) -> AssignedValue { + let gate = range.gate(); + let subquery_mask = unsafe_lt_mask(ctx, gate, num_subqueries, results.len()); + + get_results_root_poseidon(ctx, range, poseidon, results, num_subqueries, &subquery_mask) +} + +/// The subquery data and results vector `subquery_results` may be resized to some fixed length +/// ordained by the circuit. The true number of subqueries is given by `num_subqueries`, and we +/// want to compute the Poseidon results root for those subqueries. +/// +/// ## Definition +/// `subqueryResultsPoseidonRoot`: The Poseidon Merkle root of the padded tree (pad by 0) with +/// leaves given by `poseidon(poseidon(type . fieldSubqueryData), value[..])`. +/// +/// ### Note +/// `value` consists of multiple field elements, so the above means +/// `poseidon([[subqueryHashPoseidon], value[..]].concat())` with +/// `subqueryHashPoseidon := poseidon(type . fieldSubqueryData)` and `fieldSubqueryData` is +/// **variable length**. +/// The length of `fieldSubqueryData` is: +/// * the flattened length of the `FieldSubquery**` struct when the subquery type is not SolidityNestedMapping. This can be gotten as `NUM_FE_ANY[subquery_type]`. +/// * When the subquery type is SolidityNestedMapping, the length is `NUM_FE_SOLIDITY_NESTED_MAPPING_WITHOUT_KEYS + 2 * mapping_depth`. In other words, it's the flattened length of `FieldSubquerySolidityNestedMapping` where we resize based on the variable length of the keys. +/// +/// ## Assumptions +/// - `subquery_results` is non-empty and its length is known at compile time. +/// - `num_subqueries <= results.len()` +/// - `subquery_mask[i] = i < num_subqueries ? 1 : 0` +/// - `subquery_hashes` and `results` are non-empty of the same length. This length is known +/// at compile time. +// Likely `results.len()` will already be a power of 2, so not worth optimizing anything there +pub fn get_results_root_poseidon( + ctx: &mut Context, + range: &RangeChip, + initialized_hasher: &PoseidonHasher, + subquery_results: &[AssignedSubqueryResult], + num_subqueries: AssignedValue, + subquery_mask: &[SafeBool], +) -> AssignedValue { + let gate = range.gate(); + + let tree_depth = log2_ceil(subquery_results.len() as u64); + let depth = circuit_log2_ceil(ctx, gate, num_subqueries, tree_depth + 1); + // `depth_indicator = idx_to_indicator(log2ceil(num_subqueries), log2ceil(subquery_results.len()) + 1)` + let depth_indicator = gate.idx_to_indicator(ctx, depth, tree_depth + 1); + + let const_zero = ctx.load_zero(); + + let mut leaves = Vec::with_capacity(1 << tree_depth); + for (subquery_result, &mask) in subquery_results.iter().zip_eq(subquery_mask) { + let key = &subquery_result.key; + let key_len = get_num_fe_from_subquery_key(ctx, gate, key); + let subquery_hash = initialized_hasher.hash_var_len_array(ctx, range, &key.0, key_len); + let concat = [&[subquery_hash], &subquery_result.value[..]].concat(); + let mut leaf = initialized_hasher.hash_fix_len_array(ctx, gate, &concat); + leaf = gate.mul(ctx, leaf, mask); + leaves.push(leaf); + } + leaves.resize(1 << tree_depth, const_zero); + + let mut layers = Vec::with_capacity(tree_depth + 1); + layers.push(leaves); + for i in 0..tree_depth { + let prev_layer = &layers[i]; + let layer = (0..(prev_layer.len() + 1) / 2) + .map(|j| { + initialized_hasher.hash_fix_len_array( + ctx, + gate, + &[prev_layer[2 * j], prev_layer[2 * j + 1]], + ) + }) + .collect(); + layers.push(layer); + } + + // The correct root is layers[log2ceil(num_subqueries)][0] + let root_candidates = layers.iter().map(|layer| layer[0]).collect_vec(); + gate.select_by_indicator(ctx, root_candidates, depth_indicator.to_vec()) +} + +// empty_root[idx] is the Merkle root of a tree of depth idx with bytes32(0)'s as leaves +fn generate_keccak_empty_roots(len: usize) -> Vec { + let mut empty_roots = Vec::with_capacity(len); + let mut root = H256::zero(); + empty_roots.push(root); + for _ in 1..len { + root = H256(keccak256([root.as_bytes(), root.as_bytes()].concat())); + empty_roots.push(root); + } + empty_roots +} + +lazy_static::lazy_static! { + pub static ref KECCAK_EMPTY_ROOTS: Vec = generate_keccak_empty_roots(32); +} diff --git a/axiom-query/src/components/results/subquery_hash.rs b/axiom-query/src/components/results/subquery_hash.rs new file mode 100644 index 00000000..c2bb32e8 --- /dev/null +++ b/axiom-query/src/components/results/subquery_hash.rs @@ -0,0 +1,142 @@ +use std::iter::{self, zip}; + +use axiom_codec::{ + constants::{NUM_SUBQUERY_TYPES, SUBQUERY_TYPE_BYTES}, + encoder::field_elements::{ + BYTES_PER_FE_ANY, FIELD_SOLIDITY_NESTED_MAPPING_DEPTH_IDX, + NUM_FE_SOLIDITY_NESTED_MAPPING_MIN, NUM_FE_SOLIDITY_NESTED_MAPPING_WITHOUT_KEYS, + }, + types::native::SubqueryType, +}; +use axiom_eth::{ + halo2_base::{ + gates::{GateInstructions, RangeChip, RangeInstructions}, + safe_types::{SafeTypeChip, VarLenBytesVec}, + Context, + QuantumCell::{Constant, Existing}, + }, + keccak::{types::KeccakVarLenQuery, KeccakChip}, + utils::uint_to_bytes_be, +}; +use itertools::Itertools; + +use crate::utils::codec::AssignedSubqueryKey; +use crate::Field; + +/// Bytes `encoded_subquery_key` includes the subquery type ([u16]) +pub fn get_subquery_hash( + ctx: &mut Context, + keccak: &KeccakChip, + key: &AssignedSubqueryKey, + enabled_types: &[bool; NUM_SUBQUERY_TYPES], +) -> KeccakVarLenQuery { + let range = keccak.range(); + + let encoded = transform_subquery_key_to_bytes(ctx, range, key, enabled_types); + + let mut min_len = BYTES_PER_FE_ANY + .iter() + .enumerate() + .filter(|&(subquery_type, _)| enabled_types[subquery_type]) + .map(|(subquery_type, bytes_per_fe)| { + if subquery_type == SubqueryType::SolidityNestedMapping as usize { + bytes_per_fe[..NUM_FE_SOLIDITY_NESTED_MAPPING_MIN].iter().sum::() + } else { + bytes_per_fe.iter().sum::() + } + }) + .min() + .unwrap_or(0); + min_len += SUBQUERY_TYPE_BYTES; + + let encoded_key = encoded.bytes().iter().map(|b| *b.as_ref()).collect(); + let res = keccak.keccak_var_len(ctx, encoded_key, *encoded.len(), min_len); + res +} + +/// Transform from field element encoding of subquery key into byte encoding +pub fn transform_subquery_key_to_bytes( + ctx: &mut Context, + range: &RangeChip, + key: &AssignedSubqueryKey, + enabled_types: &[bool; NUM_SUBQUERY_TYPES], +) -> VarLenBytesVec { + let subquery_type = key.0[0]; + // The key WITHOUT the subquery type + let subquery = &key.0[1..]; + + let last_one = enabled_types.iter().rposition(|&x| x).expect("No subqueries enabled"); + + let gate = range.gate(); + let type_indicator = gate.idx_to_indicator(ctx, subquery_type, last_one + 1); + + // max_bytes_per_fe[i] is the max number of bytes the i-th field element can represent + let max_bytes_per_fe = (0..subquery.len()) + .map(|i| { + BYTES_PER_FE_ANY + .iter() + .enumerate() + .filter(|&(subtype, _)| enabled_types[subtype]) + .map(|(_, bytes_per_fe)| *bytes_per_fe.get(i).unwrap_or(&0)) + .max() + .unwrap_or(0) + }) + .collect_vec(); + let max_total_bytes = max_bytes_per_fe.iter().sum::(); + + let byte_frags = zip(subquery, max_bytes_per_fe) + .map(|(fe, max_bytes)| uint_to_bytes_be(ctx, range, fe, max_bytes)) + .collect_vec(); + + // now I do the very dumb thing: just create the byte array for each type + let const_zero = SafeTypeChip::unsafe_to_byte(ctx.load_zero()); + let (encoded_bytes_by_type, byte_len_by_type): (Vec<_>, Vec<_>) = BYTES_PER_FE_ANY + .iter() + .take(last_one + 1) + .enumerate() + .map(|(subtype, &bytes_per_fe)| { + let bytes_per_fe = if enabled_types[subtype] { bytes_per_fe } else { &[] }; + // byte frags are big-endian, so take correct number of bytes from the end + let mut encoded = zip(&byte_frags, bytes_per_fe) + .flat_map(|(frag, &num_bytes)| { + frag[frag.len() - num_bytes..frag.len()].iter().copied() + }) + .collect_vec(); + assert!(encoded.len() <= max_total_bytes); + encoded.resize(max_total_bytes, const_zero); + + let byte_len = if !enabled_types[subtype] { + Constant(F::ZERO) + } else if subtype == SubqueryType::SolidityNestedMapping as usize { + // length depends on mapping_depth + let mapping_depth = subquery[FIELD_SOLIDITY_NESTED_MAPPING_DEPTH_IDX]; + let pre_len = bytes_per_fe[..NUM_FE_SOLIDITY_NESTED_MAPPING_WITHOUT_KEYS] + .iter() + .sum::(); + Existing(gate.mul_add( + ctx, + Constant(F::from(32)), + mapping_depth, + Constant(F::from(pre_len as u64)), + )) + } else { + Constant(F::from(bytes_per_fe.iter().sum::() as u64)) + }; + + (encoded, byte_len) + }) + .unzip(); + + let encoded_subquery = + gate.select_array_by_indicator(ctx, &encoded_bytes_by_type, &type_indicator); + let mut encoded_len = gate.select_by_indicator(ctx, byte_len_by_type, type_indicator); + + let encoded_type = uint_to_bytes_be(ctx, range, &subquery_type, SUBQUERY_TYPE_BYTES); + let encoded: Vec<_> = iter::empty() + .chain(encoded_type) + .chain(encoded_subquery.into_iter().map(SafeTypeChip::unsafe_to_byte)) + .collect(); + encoded_len = gate.add(ctx, encoded_len, Constant(F::from(SUBQUERY_TYPE_BYTES as u64))); + + VarLenBytesVec::new(encoded, encoded_len, max_total_bytes + SUBQUERY_TYPE_BYTES) +} diff --git a/axiom-query/src/components/results/table/join.rs b/axiom-query/src/components/results/table/join.rs new file mode 100644 index 00000000..c4f45ffb --- /dev/null +++ b/axiom-query/src/components/results/table/join.rs @@ -0,0 +1,115 @@ +//! We have virtual tables of different widths for different subquery types. +//! We virtually join them together to get a single table. This is virtual because +//! we actually only compute the RLC of the resulting joined table. + +use axiom_codec::{ + constants::{MAX_SUBQUERY_INPUTS, MAX_SUBQUERY_OUTPUTS}, + types::{field_elements::AnySubqueryResult, native::SubqueryType}, +}; +use axiom_eth::{ + halo2_base::{gates::GateInstructions, utils::ScalarField, AssignedValue}, + rlc::{chip::RlcChip, circuit::builder::RlcContextPair}, +}; + +#[derive(Clone, Debug)] +pub struct GroupedSubqueryResults { + /// This is a **constant** + pub subquery_type: SubqueryType, + /// This is a fixed length vector of (subquery, value) pairs. + /// Assumed that subquery[i].len() = subquery[j].len() for all i, j. + /// Assumed that value[i].len() = value[j].len() for all i, j. + pub results: Vec, Vec>>, +} + +impl GroupedSubqueryResults> { + /// The width of the key and value differs per subquery type, but we want to put them + /// into an ungrouped virtual table. Instead of resizing the key/values directly, we + /// optimize by computing the RLC of the resized key/values since that is all we need + /// to form the dynamic lookup table. + // + // Note: this assumes Solidity Nested Mapping subquery enforces that extra keys + // beyond mapping depth are 0 + pub fn into_rlc( + self, + (ctx_gate, ctx_rlc): RlcContextPair, + gate: &impl GateInstructions, + rlc: &RlcChip, + ) -> Vec<[AssignedValue; 1]> { + if self.results.is_empty() { + return vec![]; + } + let subquery_type = ctx_gate.load_constant(F::from(self.subquery_type as u64)); + let subquery_len = self.results[0].subquery.len(); + assert!(subquery_len <= MAX_SUBQUERY_INPUTS); + let val_len = self.results[0].value.len(); + assert!(val_len <= MAX_SUBQUERY_OUTPUTS); + // right now `value` is always HiLo, but we put this here in case of future generalizations + let val_multiplier = (val_len < MAX_SUBQUERY_OUTPUTS) + .then(|| rlc.rlc_pow_fixed(ctx_gate, gate, MAX_SUBQUERY_OUTPUTS - val_len)); + let subquery_multiplier = rlc.rlc_pow_fixed( + ctx_gate, + gate, + MAX_SUBQUERY_INPUTS + MAX_SUBQUERY_OUTPUTS - subquery_len, + ); + + self.results + .into_iter() + .map(|result| { + let subquery = result.subquery; + let value = result.value; + assert_eq!(subquery.len(), subquery_len); + assert_eq!(value.len(), val_len); + // the following has the same effect as: `RLC(value.resize(MAX_SUBQUERY_OUTPUTS, 0))` + let mut val_rlc = rlc.compute_rlc_fixed_len(ctx_rlc, value.clone()).rlc_val; + if let Some(multiplier) = val_multiplier { + val_rlc = gate.mul(ctx_gate, val_rlc, multiplier); + } + // key = subquery_type . subquery + let key = [vec![subquery_type], subquery].concat(); + // the following has the same effect as: `RLC([key.resize(SUBQUERY_KEY_LEN, 0)), value])` + // where `value` is already resized + // This is because RLC(a . b) = RLC(a) * gamma^|b| + RLC(b) + // if a, b have fixed lengths |a|, |b| respectively + let key_rlc = rlc.compute_rlc_fixed_len(ctx_rlc, key).rlc_val; + [gate.mul_add(ctx_gate, key_rlc, subquery_multiplier, val_rlc)] + }) + .collect() + } +} + +/* +// Unused for now + +/// `inputs` has fixed length known at compile time. +/// The true variable length of `inputs` is given by `var_len`, and +/// it is assumed that `min_len <= var_len <= inputs.len()`. +/// +/// Let `var_input = inputs[..var_len]`. This function has the effect of: +/// ```ignore +/// var_input.resize(0, padded_len) +/// compute_rlc_fixed(var_input) +/// ``` +/// What the function actually does is compute the RLC of `var_input` and multiply by +/// the correct power of the challenge `gamma`. +/// +/// The output is the RLC value. +/// +/// ## Assumptions +/// - `min_len <= var_len <= inputs.len()` +fn compute_rlc_right_pad_to_fixed( + (ctx_gate, ctx_rlc): RlcContextPair, + gate: &impl GateInstructions, + rlc: &RlcChip, + inputs: Vec>, + var_len: AssignedValue, + min_len: usize, + padded_len: usize, +) -> AssignedValue { + assert!(min_len <= inputs.len()); + let rlc_var = rlc.compute_rlc_with_min_len((ctx_gate, ctx_rlc), gate, inputs, var_len, min_len); + let shift_len = gate.sub(ctx_gate, Constant(F::from(padded_len as u64)), var_len); + let multiplier = + rlc.rlc_pow(ctx_gate, gate, shift_len, bit_length((padded_len - min_len) as u64)); + gate.mul(ctx_gate, rlc_var.rlc_val, multiplier) +} +*/ diff --git a/axiom-query/src/components/results/table/mod.rs b/axiom-query/src/components/results/table/mod.rs new file mode 100644 index 00000000..39ab9aae --- /dev/null +++ b/axiom-query/src/components/results/table/mod.rs @@ -0,0 +1,54 @@ +use axiom_codec::types::field_elements::FlattenedSubqueryResult; +use axiom_eth::{ + halo2_base::{AssignedValue, Context}, + rlc::chip::RlcChip, +}; +use serde::{Deserialize, Serialize}; + +use crate::{utils::codec::assign_flattened_subquery_result, Field}; + +pub(super) mod join; + +/// The _ordered_ subqueries with results. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct SubqueryResultsTable { + pub rows: Vec>, +} + +impl SubqueryResultsTable { + pub fn new(rows: Vec>) -> Self { + Self { rows } + } +} + +pub type AssignedSubqueryResultsTable = SubqueryResultsTable>; + +impl SubqueryResultsTable { + /// Nothing is constrained. Loaded as pure private witnesses. + pub fn assign(&self, ctx: &mut Context) -> AssignedSubqueryResultsTable { + let rows: Vec<_> = + self.rows.iter().map(|row| assign_flattened_subquery_result(ctx, row)).collect(); + SubqueryResultsTable { rows } + } + + pub fn len(&self) -> usize { + self.rows.len() + } + + pub fn is_empty(&self) -> bool { + self.rows.is_empty() + } +} + +impl AssignedSubqueryResultsTable { + pub fn to_rlc(&self, ctx_rlc: &mut Context, rlc: &RlcChip) -> Vec<[AssignedValue; 1]> { + self.rows + .iter() + .map(|row| { + let concat = row.to_fixed_array(); + let trace = rlc.compute_rlc_fixed_len(ctx_rlc, concat); + [trace.rlc_val] + }) + .collect() + } +} diff --git a/axiom-query/src/components/results/tests.rs b/axiom-query/src/components/results/tests.rs new file mode 100644 index 00000000..84d572da --- /dev/null +++ b/axiom-query/src/components/results/tests.rs @@ -0,0 +1,476 @@ +use std::{collections::HashMap, fs::File, str::FromStr}; + +use anyhow::Result; +use axiom_codec::{ + constants::NUM_SUBQUERY_TYPES, + encoder::field_elements::{NUM_FE_ANY, NUM_FE_SOLIDITY_NESTED_MAPPING_WITHOUT_KEYS}, + special_values::{ + RECEIPT_LOG_IDX_OFFSET, RECEIPT_TOPIC_IDX_OFFSET, TX_FUNCTION_SELECTOR_FIELD_IDX, + }, + types::{ + field_elements::{AnySubqueryResult, FlattenedSubqueryResult}, + native::{ + AccountSubquery, AnySubquery, HeaderSubquery, ReceiptSubquery, + SolidityNestedMappingSubquery, StorageSubquery, Subquery, SubqueryResult, SubqueryType, + TxSubquery, + }, + }, + utils::native::u256_to_h256, +}; +use axiom_components::ecdsa::{ + utils::testing::custom_parameters_ecdsa, ComponentTypeECDSA, ECDSAComponentNativeInput, +}; +use axiom_eth::{ + block_header::{EXTRA_DATA_INDEX, RECEIPT_ROOT_INDEX, STATE_ROOT_INDEX, TX_ROOT_INDEX}, + halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}, + keccak::{promise::generate_keccak_shards_from_calls, types::ComponentTypeKeccak}, + utils::build_utils::pinning::{CircuitPinningInstructions, Halo2CircuitPinning}, + utils::{ + component::{ + promise_loader::{ + comp_loader::SingleComponentLoaderParams, + multi::{ComponentTypeList, MultiPromiseLoaderParams}, + single::PromiseLoaderParams, + }, + utils::compute_poseidon, + ComponentCircuit, ComponentPromiseResultsInMerkle, ComponentType, + GroupedPromiseResults, + }, + encode_h256_to_hilo, + }, +}; +use ethers_core::types::{Address, Bytes, H256, U256}; +use hex::FromHex; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::{ + dummy_rlc_circuit_params, + results::{ + circuit::SubqueryDependencies, table::SubqueryResultsTable, + types::CircuitInputResultsRootShard, + }, + subqueries::{ + account::{types::ComponentTypeAccountSubquery, STORAGE_ROOT_INDEX}, + block_header::types::ComponentTypeHeaderSubquery, + common::{shard_into_component_promise_results, OutputSubqueryShard}, + receipt::types::ComponentTypeReceiptSubquery, + solidity_mappings::types::ComponentTypeSolidityNestedMappingSubquery, + storage::types::ComponentTypeStorageSubquery, + transaction::types::ComponentTypeTxSubquery, + }, + }, + Field, +}; + +use super::{ + circuit::{ComponentCircuitResultsRoot, CoreParamsResultRoot}, + types::{ + component_type_id_to_subquery_type, LogicOutputResultsRoot, + LogicalPublicInstanceResultsRoot, + }, +}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Hash, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct ComponentCapacities { + pub total: usize, + pub header: usize, + pub account: usize, + pub storage: usize, + pub tx: usize, + pub receipt: usize, + pub solidity_mapping: usize, + pub ecdsa: usize, + pub keccak: usize, +} +impl ComponentCapacities { + pub fn to_str_short(&self) -> String { + format!( + "{}_{}_{}_{}_{}_{}_{}_{}_{}", + self.total, + self.header, + self.account, + self.storage, + self.tx, + self.receipt, + self.solidity_mapping, + self.ecdsa, + self.keccak + ) + } +} + +pub fn get_test_input( + capacity: ComponentCapacities, +) -> Result<(CircuitInputResultsRootShard, LogicOutputResultsRoot, GroupedPromiseResults)> { + let mut extra_data = Vec::from_hex("43727578706f6f6c205050532f6e696365686173682d31").unwrap(); + extra_data.resize(32, 0u8); + let block_number = 9528813; + let mut header_subqueries = vec![ + ( + HeaderSubquery { block_number, field_idx: 1 }, + H256::from_str("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")?, + ), + ( + HeaderSubquery { block_number, field_idx: 71 }, + H256::from_str("0x2a4a0345b08e0c50805158030213038e9205244c5100b7223068018ef1648009")?, + ), + ( + HeaderSubquery { block_number, field_idx: EXTRA_DATA_INDEX as u32 }, + H256::from_slice(&extra_data), + ), + ( + HeaderSubquery { block_number, field_idx: STATE_ROOT_INDEX as u32 }, + H256::from_str("0x9ce4561dfadb4fb022debc7c013141a36a23d467f4268c0e620a7bba05b174f8")?, + ), + ( + HeaderSubquery { block_number, field_idx: TX_ROOT_INDEX as u32 }, + H256::from_str("0xec4c8ec02281c196e3f882b4061c05f4f0843a0eaf72a3ee4715077220934bbc")?, + ), + ( + HeaderSubquery { block_number, field_idx: RECEIPT_ROOT_INDEX as u32 }, + H256::from_str("0x0dfecab5d97a4da361b92c29157cd6a5089b55facee007eea16005bfa8d82694")?, + ), + ]; + + let addr = Address::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7")?; + let mut acct_subqueries = vec![( + AccountSubquery { block_number, addr, field_idx: STORAGE_ROOT_INDEX as u32 }, + H256::from_str("0x3bddd93ffcce5169c2ce5d08b91f9fda3ed657dbf3b78beffb44dc833f6308a2")?, + )]; + + let mut storage_subqueries = vec![( + StorageSubquery { + block_number, + addr, + slot: U256::from_str( + "0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b", + )?, + }, + u256_to_h256(&U256::from_str("0xb532b80")?), + )]; + + let mut solidity_mapping_subqueries = vec![( + SolidityNestedMappingSubquery { + block_number, + addr, + mapping_slot: U256::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000002", + )?, + mapping_depth: 1, + keys: vec![H256::zero()], + }, // balances https://evm.storage/eth/9528813/0xdac17f958d2ee523a2206206994597c13d831ec7/balances#map + u256_to_h256(&U256::from_str("0xb532b80")?), + )]; + + let mut tx_subqueries = vec![( + TxSubquery { + block_number, + tx_idx: 68, + field_or_calldata_idx: TX_FUNCTION_SELECTOR_FIELD_IDX as u32, + }, // https://etherscan.io/tx/0x9524b67377f7ff228fbe31c7edbfb4ba7bb374ceeac54030793b6727d1dc4505 + u256_to_h256(&U256::from_str("0x013efd8b")?), + )]; + + let mut receipt_subqueries = vec![( + ReceiptSubquery { + block_number, + tx_idx: 68, // https://etherscan.io/tx/0x9524b67377f7ff228fbe31c7edbfb4ba7bb374ceeac54030793b6727d1dc4505#eventlog + field_or_log_idx: RECEIPT_LOG_IDX_OFFSET as u32 + 2, + topic_or_data_or_address_idx: RECEIPT_TOPIC_IDX_OFFSET as u32 + 1, + event_schema: H256::from_str( + "0x7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b353984238705", + )?, + }, + H256::from(Address::from_str("0x263FC4d9Eb6da1ED296ea6d189b41e546a188D8A")?), + )]; + + let mut ecdsa_subqueries: Vec<(ECDSAComponentNativeInput, H256)> = + vec![(custom_parameters_ecdsa(3, 10898, 2).into(), u256_to_h256(&U256::from_str("1")?))]; + + header_subqueries.truncate(capacity.header); + acct_subqueries.truncate(capacity.account); + storage_subqueries.truncate(capacity.storage); + solidity_mapping_subqueries.truncate(capacity.solidity_mapping); + tx_subqueries.truncate(capacity.tx); + receipt_subqueries.truncate(capacity.receipt); + ecdsa_subqueries.truncate(capacity.ecdsa); + + fn append( + results: &mut Vec, + subqueries: &[(impl Into + Clone, H256)], + ) { + for (s, v) in subqueries { + results.push(SubqueryResult { subquery: s.clone().into(), value: v.0.into() }) + } + } + fn resize_with_first(v: &mut Vec, cap: usize) { + if cap > 0 { + v.resize(cap, v[0].clone()); + } else { + v.clear(); + } + } + let mut results = vec![]; + // put them in weird order just to test + append(&mut results, &tx_subqueries); + append(&mut results, &header_subqueries); + append(&mut results, &receipt_subqueries); + append(&mut results, &acct_subqueries); + append(&mut results, &storage_subqueries); + append(&mut results, &solidity_mapping_subqueries); + append(&mut results, &ecdsa_subqueries); + results.truncate(capacity.total); + let num_subqueries = results.len(); + resize_with_first(&mut results, capacity.total); + let _encoded_subqueries: Vec = + results.iter().map(|r| r.subquery.encode().into()).collect(); + let subquery_hashes: Vec = results.iter().map(|r| r.subquery.keccak()).collect(); + + fn prepare(results: Vec<(A, H256)>) -> OutputSubqueryShard { + let results = results.into_iter().map(|(s, v)| AnySubqueryResult::new(s, v)).collect_vec(); + OutputSubqueryShard { results } + } + resize_with_first(&mut header_subqueries, capacity.header); + resize_with_first(&mut acct_subqueries, capacity.account); + resize_with_first(&mut storage_subqueries, capacity.storage); + resize_with_first(&mut tx_subqueries, capacity.tx); + resize_with_first(&mut receipt_subqueries, capacity.receipt); + resize_with_first(&mut solidity_mapping_subqueries, capacity.solidity_mapping); + resize_with_first(&mut ecdsa_subqueries, capacity.ecdsa); + + let promise_header = prepare(header_subqueries); + let promise_account = prepare(acct_subqueries); + let promise_storage = prepare(storage_subqueries); + let promise_tx = prepare(tx_subqueries); + let promise_receipt = prepare(receipt_subqueries); + let promise_solidity_mapping = prepare(solidity_mapping_subqueries); + let promise_ecdsa = prepare(ecdsa_subqueries); + + let mut promise_results = HashMap::new(); + for (type_id, pr) in SubqueryDependencies::::get_component_type_ids().into_iter().zip_eq([ + shard_into_component_promise_results::>( + promise_header.convert_into(), + ), + shard_into_component_promise_results::>( + promise_account.convert_into(), + ), + shard_into_component_promise_results::>( + promise_storage.convert_into(), + ), + shard_into_component_promise_results::>( + promise_tx.convert_into(), + ), + shard_into_component_promise_results::>( + promise_receipt.convert_into(), + ), + shard_into_component_promise_results::>( + promise_solidity_mapping.convert_into(), + ), + shard_into_component_promise_results::>( + promise_ecdsa.convert_into(), + ), + ]) { + // filter out empty shards with capacity = 0. + if !pr.shards()[0].1.is_empty() { + promise_results.insert(type_id, pr); + } + } + + Ok(( + CircuitInputResultsRootShard:: { + subqueries: SubqueryResultsTable::::new( + results.clone().into_iter().map(|r| r.try_into().unwrap()).collect_vec(), + ), + num_subqueries: F::from(num_subqueries as u64), + }, + LogicOutputResultsRoot { results, subquery_hashes, num_subqueries }, + promise_results, + )) +} + +pub const fn test_capacity() -> ComponentCapacities { + ComponentCapacities { + total: 32, + header: 32, + account: 8, + storage: 8, + tx: 8, + receipt: 8, + solidity_mapping: 8, + ecdsa: 8, + keccak: 500, + } +} + +#[test] +fn test_mock_results_root() -> anyhow::Result<()> { + let k = 18; + let capacity = test_capacity(); + let (input, subquery_results, mut promise_results) = get_test_input(capacity)?; + + let mut enabled_types = [false; NUM_SUBQUERY_TYPES]; + let mut params_per_comp = HashMap::new(); + // reminder: input.promises order is deterministic, from ComponentTypeResultsRoot::subquery_type_ids() + for (component_type_id, results) in &promise_results { + if component_type_id == &ComponentTypeKeccak::::get_type_id() { + continue; + } + let subquery_type = component_type_id_to_subquery_type::(component_type_id).unwrap(); + enabled_types[subquery_type as usize] = true; + + // TODO: Shard capacity shoud come from .get_capactiy. + params_per_comp.insert( + component_type_id.clone(), + SingleComponentLoaderParams::new(0, vec![results.shards()[0].1.len()]), + ); + } + let promise_results_params = MultiPromiseLoaderParams { params_per_component: params_per_comp }; + let keccak_f_capacity = capacity.keccak; + + let circuit_params = dummy_rlc_circuit_params(k as usize); + let mut circuit = ComponentCircuitResultsRoot::new( + CoreParamsResultRoot { enabled_types, capacity: input.subqueries.len() }, + (PromiseLoaderParams::new_for_one_shard(keccak_f_capacity), promise_results_params), + circuit_params, + ); + circuit.feed_input(Box::new(input)).unwrap(); + circuit.calculate_params(); + promise_results.insert( + ComponentTypeKeccak::::get_type_id(), + ComponentPromiseResultsInMerkle::from_single_shard( + generate_keccak_shards_from_calls(&circuit, keccak_f_capacity) + .unwrap() + .into_logical_results(), + ), + ); + circuit.fulfill_promise_results(&promise_results).unwrap(); + let pis = circuit.get_public_instances(); + let LogicalPublicInstanceResultsRoot { results_root_poseidon, commit_subquery_hashes } = + pis.other.clone().try_into().unwrap(); + assert_eq!( + (results_root_poseidon, commit_subquery_hashes), + get_native_results_root_and_subquery_hash_commit(subquery_results.clone()) + ); + let instances: Vec = pis.into(); + MockProver::run(k as u32, &circuit, vec![instances]).unwrap().assert_satisfied(); + circuit.pinning().write("configs/test/results_root.json").unwrap(); + + Ok(()) +} + +/// Returns (poseidon_results_root, subquery_hash_commit) +// `subqueryResultsPoseidonRoot`: The Poseidon Merkle root of the padded tree (pad by 0) with +// leaves given by `poseidon(poseidon(type . fieldSubqueryData), value[..])`. +// +// ### Note +// `value` consists of multiple field elements, so the above means +// `poseidon([[subqueryHashPoseidon], value[..]].concat())` with +// `subqueryHashPoseidon := poseidon(type . fieldSubqueryData)` and `fieldSubqueryData` is +// **variable length**. +fn get_native_results_root_and_subquery_hash_commit(results: LogicOutputResultsRoot) -> (Fr, Fr) { + // note compute_poseidon re-creates poseidon spec, but it doesn't matter for test + let mut leaves = vec![]; + let mut to_commit = vec![]; + for result in results.results.into_iter().take(results.num_subqueries) { + to_commit.extend(encode_h256_to_hilo::(&result.subquery.keccak()).hi_lo()); + // NUM_FE_ANY[subquery_type] is the length of fieldSubqueryData + let unencoded_key_len = + get_num_fe_from_subquery(result.subquery.clone().try_into().unwrap()); + // add 1 for subquery type + let encoded_key_len = unencoded_key_len + 1; + let result = FlattenedSubqueryResult::::try_from(result).unwrap(); + let key_hash = compute_poseidon(&result.key[..encoded_key_len]); + let mut buf = vec![key_hash]; + buf.extend(result.value.0); + leaves.push(compute_poseidon(&buf)); + } + leaves.resize(leaves.len().next_power_of_two(), Fr::zero()); + while leaves.len() > 1 { + leaves = leaves.chunks(2).map(compute_poseidon).collect(); + } + (leaves[0], compute_poseidon(&to_commit)) +} + +fn get_num_fe_from_subquery(subquery: AnySubquery) -> usize { + match subquery { + AnySubquery::SolidityNestedMapping(subquery) => { + NUM_FE_SOLIDITY_NESTED_MAPPING_WITHOUT_KEYS + 2 * subquery.mapping_depth as usize + } + _ => { + let subquery_type = Subquery::from(subquery).subquery_type; + NUM_FE_ANY[subquery_type as usize] + } + } +} + +#[test] +#[ignore = "integration test"] +fn test_mock_results_root_header_only_for_agg() -> anyhow::Result<()> { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + + let k = 18; + let capacity = ComponentCapacities { + total: 3, + header: 3, + account: 0, + storage: 0, + tx: 0, + receipt: 0, + solidity_mapping: 0, + ecdsa: 0, + keccak: 200, + }; + let (input, output, mut promise_results) = get_test_input(capacity)?; + + let mut enabled_types = [false; NUM_SUBQUERY_TYPES]; + enabled_types[SubqueryType::Header as usize] = true; + let mut params_per_comp = HashMap::new(); + + params_per_comp.insert( + ComponentTypeHeaderSubquery::::get_type_id(), + SingleComponentLoaderParams::new(0, vec![3]), + ); + let promise_results_params = MultiPromiseLoaderParams { params_per_component: params_per_comp }; + + let keccak_f_capacity = capacity.keccak; + + let circuit_params = dummy_rlc_circuit_params(k as usize); + let mut circuit = ComponentCircuitResultsRoot::new( + CoreParamsResultRoot { enabled_types, capacity: input.subqueries.len() }, + (PromiseLoaderParams::new_for_one_shard(keccak_f_capacity), promise_results_params), + circuit_params, + ); + circuit.feed_input(Box::new(input.clone())).unwrap(); + circuit.calculate_params(); + + let keccak_shard = generate_keccak_shards_from_calls(&circuit, keccak_f_capacity)?; + serde_json::to_writer( + File::create(format!( + "{cargo_manifest_dir}/data/test/results_root_promise_results_keccak_for_agg.json" + ))?, + &keccak_shard, + )?; + promise_results.insert( + ComponentTypeKeccak::::get_type_id(), + ComponentPromiseResultsInMerkle::from_single_shard(keccak_shard.into_logical_results()), + ); + circuit.fulfill_promise_results(&promise_results).unwrap(); + let instances: Vec = circuit.get_public_instances().into(); + MockProver::run(k as u32, &circuit, vec![instances]).unwrap().assert_satisfied(); + + serde_json::to_writer( + File::create(format!("{cargo_manifest_dir}/data/test/input_result_root_for_agg.json"))?, + &input, + )?; + serde_json::to_writer( + File::create(format!("{cargo_manifest_dir}/data/test/output_result_root_for_agg.json"))?, + &output, + )?; + + circuit.pinning().write("configs/test/results_root_for_agg.json").unwrap(); + + Ok(()) +} diff --git a/axiom-query/src/components/results/types.rs b/axiom-query/src/components/results/types.rs new file mode 100644 index 00000000..7f5643ae --- /dev/null +++ b/axiom-query/src/components/results/types.rs @@ -0,0 +1,273 @@ +use std::{any::Any, marker::PhantomData}; + +use anyhow::Result; +use axiom_codec::{ + types::{ + field_elements::{AnySubqueryResult, FlattenedSubqueryResult}, + native::{SubqueryResult, SubqueryType}, + }, + HiLo, +}; +use axiom_eth::{ + halo2_base::{ + gates::{GateInstructions, RangeChip}, + AssignedValue, + }, + impl_flatten_conversion, + rlc::{chip::RlcChip, circuit::builder::RlcContextPair}, + utils::{ + build_utils::dummy::DummyFrom, + component::{ + circuit::CoreBuilderInput, + promise_loader::{ + flatten_witness_to_rlc, + multi::{ComponentTypeList, RlcAdapter}, + }, + types::{Flatten, LogicalEmpty}, + utils::into_key, + ComponentType, ComponentTypeId, LogicalResult, PromiseCallWitness, + TypelessLogicalInput, + }, + encode_h256_to_hilo, + }, +}; +use ethers_core::types::H256; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::{components::results::circuit::SubqueryDependencies, Field, RawField}; + +use super::{ + circuit::CoreParamsResultRoot, + table::{join::GroupedSubqueryResults, SubqueryResultsTable}, +}; + +/// Component type for ResultsRoot component. +pub struct ComponentTypeResultsRoot(PhantomData); + +/// Logic inputs to Data Results and Calculate Subquery Hashes Circuit +/// +/// Length of `data_query` fixed at compile time. True number of subqueries is `num_subqueries`. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CircuitInputResultsRootShard { + /// The **ordered** subqueries to be verified, _with_ claimed result values. + /// This table may have been resized to some fixed length, known at compile time. + /// The actual subqueries will be the first `num_subqueries` rows of the table. + pub subqueries: SubqueryResultsTable, + /// The number of true subqueries in the table. + pub num_subqueries: F, +} + +impl DummyFrom for CircuitInputResultsRootShard { + fn dummy_from(core_params: CoreParamsResultRoot) -> Self { + let subqueries = SubqueryResultsTable { + rows: vec![FlattenedSubqueryResult::default(); core_params.capacity], + }; + Self { subqueries, num_subqueries: F::ZERO } + } +} + +/// Lengths of `results` and `subquery_hashes` are always equal. +/// May include padding - `num_subqueries` is the actual number of subqueries. +#[derive(Clone, Debug, Default, Serialize, Deserialize, Hash)] +#[serde(rename_all = "camelCase")] +pub struct LogicOutputResultsRoot { + pub results: Vec, + pub subquery_hashes: Vec, + pub num_subqueries: usize, +} + +/// Lengths of `results` and `subquery_hashes` must be equal. +/// +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct CircuitOutputResultsRoot { + pub results: SubqueryResultsTable, + pub subquery_hashes: Vec>, + pub num_subqueries: usize, +} + +impl CircuitOutputResultsRoot { + /// Resize itself to `new_len` by repeating the first row. Crash if the table is empty. + pub fn resize_with_first(&mut self, new_len: usize) { + self.results.rows.resize(new_len, self.results.rows[0]); + self.subquery_hashes.resize(new_len, self.subquery_hashes[0]); + } +} + +// ==== Public Instances ==== +// 9999 means that the public instance takes a whole witness. +const NUM_BITS_PER_FE: [usize; 2] = [9999, 9999]; +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LogicalPublicInstanceResultsRoot { + pub results_root_poseidon: T, + pub commit_subquery_hashes: T, +} + +// All promise calls made by ResultsRoot component are Virtual: they will be managed by MultiPromiseLoader +// The ResultsRoot component should never to called directly, so it has no virtual table as output. +impl ComponentType for ComponentTypeResultsRoot { + type InputValue = LogicalEmpty; + type InputWitness = LogicalEmpty>; + type OutputValue = LogicalEmpty; + type OutputWitness = LogicalEmpty>; + type LogicalInput = LogicalEmpty; + + fn get_type_id() -> ComponentTypeId { + "axiom-query:ComponentTypeResultsRoot".to_string() + } + + fn logical_result_to_virtual_rows_impl( + _ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + unreachable!() + } + fn logical_input_to_virtual_rows_impl(_li: &Self::LogicalInput) -> Vec { + unreachable!() + } +} + +impl LogicOutputResultsRoot { + pub fn new( + results: Vec, + subquery_hashes: Vec, + num_subqueries: usize, + ) -> Self { + assert_eq!(results.len(), subquery_hashes.len()); + Self { results, subquery_hashes, num_subqueries } + } +} + +impl TryFrom for CircuitOutputResultsRoot { + type Error = std::io::Error; + fn try_from(output: LogicOutputResultsRoot) -> Result { + let LogicOutputResultsRoot { results, subquery_hashes, num_subqueries } = output; + let rows = results + .into_iter() + .map(FlattenedSubqueryResult::::try_from) + .collect::, _>>()?; + let results = SubqueryResultsTable { rows }; + let subquery_hashes = subquery_hashes + .into_iter() + .map(|subquery_hash| encode_h256_to_hilo(&subquery_hash)) + .collect(); + Ok(Self { results, subquery_hashes, num_subqueries }) + } +} + +// ============== LogicalPublicInstanceResultsRoot ============== +impl TryFrom> for LogicalPublicInstanceResultsRoot { + type Error = anyhow::Error; + + fn try_from(value: Vec) -> anyhow::Result { + let [results_root_poseidon, commit_subquery_hashes] = + value.try_into().map_err(|_| anyhow::anyhow!("invalid length"))?; + Ok(Self { results_root_poseidon, commit_subquery_hashes }) + } +} +impl LogicalPublicInstanceResultsRoot { + pub fn flatten(&self) -> [T; 2] { + [self.results_root_poseidon, self.commit_subquery_hashes] + } +} +impl_flatten_conversion!(LogicalPublicInstanceResultsRoot, NUM_BITS_PER_FE); + +// ======== Types for component implementation ========== + +/// RLC adapter for MultiPromiseLoader of results root component. +pub struct RlcAdapterResultsRoot(PhantomData); +impl RlcAdapter for RlcAdapterResultsRoot { + fn to_rlc( + ctx_pair: RlcContextPair, + gate: &impl GateInstructions, + rlc: &RlcChip, + component_type_id: &ComponentTypeId, + io_pairs: &[(Flatten>, Flatten>)], + ) -> Vec> { + let subquery_type = component_type_id_to_subquery_type::(component_type_id).unwrap(); + + let subqueries = GroupedSubqueryResults { + subquery_type, + results: io_pairs + .iter() + .map(|(i, o)| AnySubqueryResult { + subquery: i.fields.clone(), + value: o.fields.clone(), + }) + .collect_vec(), + }; + subqueries.into_rlc(ctx_pair, gate, rlc).concat() + } +} + +/// Virtual component type for MultiPromiseLoader +pub struct VirtualComponentType(PhantomData); + +impl ComponentType for VirtualComponentType { + type InputValue = FlattenedSubqueryResult; + type InputWitness = FlattenedSubqueryResult>; + type OutputValue = LogicalEmpty; + type OutputWitness = LogicalEmpty>; + type LogicalInput = LogicalEmpty; + + fn get_type_id() -> ComponentTypeId { + "axiom-query:VirtualComponentTypeResultsRoot".to_string() + } + + fn logical_result_to_virtual_rows_impl( + _ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + unreachable!() + } + fn logical_input_to_virtual_rows_impl(_li: &Self::LogicalInput) -> Vec { + unreachable!() + } +} + +#[derive(Clone, Copy, Debug)] +pub struct SubqueryResultCall(pub FlattenedSubqueryResult>); + +impl PromiseCallWitness for SubqueryResultCall { + fn get_component_type_id(&self) -> ComponentTypeId { + VirtualComponentType::::get_type_id() + } + fn get_capacity(&self) -> usize { + // virtual call so no consumption + 0 + } + fn to_rlc( + &self, + (_, rlc_ctx): RlcContextPair, + _range_chip: &RangeChip, + rlc_chip: &RlcChip, + ) -> AssignedValue { + flatten_witness_to_rlc(rlc_ctx, rlc_chip, &self.0.into()) + } + fn to_typeless_logical_input(&self) -> TypelessLogicalInput { + let f_a: Flatten> = self.0.into(); + let f_v: Flatten = f_a.into(); + let l_v: FlattenedSubqueryResult = f_v.try_into().unwrap(); + into_key(l_v) + } + fn get_mock_output(&self) -> Flatten { + let output_val: as ComponentType>::OutputValue = + Default::default(); + output_val.into() + } + fn as_any(&self) -> &dyn Any { + self + } +} + +// ======== Boilerplate conversions ======== + +/// Mapping from component type id to subquery type. +pub fn component_type_id_to_subquery_type( + type_id: &ComponentTypeId, +) -> Option { + // This cannot be static because of + let type_ids = SubqueryDependencies::::get_component_type_ids(); + type_ids + .iter() + .position(|id| id == type_id) + .map(|i| SubqueryType::try_from(i as u16 + 1).unwrap()) +} diff --git a/axiom-query/src/components/subqueries/account/circuit.rs b/axiom-query/src/components/subqueries/account/circuit.rs new file mode 100644 index 00000000..785947ab --- /dev/null +++ b/axiom-query/src/components/subqueries/account/circuit.rs @@ -0,0 +1,298 @@ +use std::iter::zip; + +use axiom_eth::{ + block_header::STATE_ROOT_INDEX, + halo2_base::{ + gates::{flex_gate::threads::parallelize_core, GateInstructions, RangeInstructions}, + safe_types::SafeTypeChip, + AssignedValue, Context, + QuantumCell::Constant, + }, + halo2_proofs::plonk::ConstraintSystem, + keccak::{types::ComponentTypeKeccak, KeccakChip}, + mpt::MPTChip, + rlc::circuit::builder::RlcCircuitBuilder, + rlc::circuit::builder::RlcContextPair, + rlp::RlpChip, + storage::{ + EthAccountWitness, EthStorageChip, ACCOUNT_STATE_FIELDS_MAX_BYTES, + ACCOUNT_STATE_FIELD_IS_VAR_LEN, NUM_ACCOUNT_STATE_FIELDS, + }, + utils::{ + build_utils::aggregation::CircuitMetadata, + bytes_be_to_uint, + circuit_utils::bytes::{pack_bytes_to_hilo, unsafe_mpt_root_to_hi_lo}, + component::{ + circuit::{ + ComponentBuilder, ComponentCircuitImpl, CoreBuilder, CoreBuilderOutput, + CoreBuilderOutputParams, CoreBuilderParams, + }, + promise_collector::PromiseCaller, + promise_loader::{combo::PromiseBuilderCombo, single::PromiseLoader}, + types::LogicalEmpty, + utils::create_hasher, + LogicalResult, + }, + constrain_vec_equal, encode_h256_to_hilo, + hilo::HiLo, + unsafe_bytes_to_assigned, + }, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::subqueries::{ + block_header::types::{ComponentTypeHeaderSubquery, FieldHeaderSubqueryCall}, + common::{extract_logical_results, extract_virtual_table}, + }, + utils::codec::{ + AssignedAccountSubquery, AssignedAccountSubqueryResult, AssignedHeaderSubquery, + }, + Field, +}; + +use super::{ + types::{CircuitInputAccountShard, CircuitInputAccountSubquery, ComponentTypeAccountSubquery}, + KECCAK_RLP_EMPTY_STRING, STORAGE_ROOT_INDEX, +}; + +pub struct CoreBuilderAccountSubquery { + input: Option>, + params: CoreParamsAccountSubquery, + payload: Option<(KeccakChip, Vec>)>, +} + +/// Specify the output format of AccountSubquery component. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct CoreParamsAccountSubquery { + /// The maximum number of subqueries of this type allowed in a single circuit. + pub capacity: usize, + /// The maximum depth of the state MPT trie supported by this circuit. + /// The depth is defined as the maximum length of an account proof, where the account proof always ends in a terminal leaf node. + /// + /// In production this will be set to 14 based on the MPT analysis from https://hackmd.io/@axiom/BJBledudT + pub max_trie_depth: usize, +} +impl CoreBuilderParams for CoreParamsAccountSubquery { + fn get_output_params(&self) -> CoreBuilderOutputParams { + CoreBuilderOutputParams::new(vec![self.capacity]) + } +} + +type CKeccak = ComponentTypeKeccak; +type CHeader = ComponentTypeHeaderSubquery; +pub type PromiseLoaderAccountSubquery = + PromiseBuilderCombo>, PromiseLoader>>; +pub type ComponentCircuitAccountSubquery = + ComponentCircuitImpl, PromiseLoaderAccountSubquery>; + +impl CircuitMetadata for CoreBuilderAccountSubquery { + const HAS_ACCUMULATOR: bool = false; + fn num_instance(&self) -> Vec { + unreachable!() + } +} + +impl ComponentBuilder for CoreBuilderAccountSubquery { + type Params = CoreParamsAccountSubquery; + fn new(params: Self::Params) -> Self { + Self { input: None, params, payload: None } + } + fn get_params(&self) -> Self::Params { + self.params.clone() + } + fn clear_witnesses(&mut self) { + self.payload = None; + } + fn calculate_params(&mut self) -> Self::Params { + self.params.clone() + } + fn configure_with_params(_: &mut ConstraintSystem, _: Self::Params) {} +} + +impl CoreBuilder for CoreBuilderAccountSubquery { + type CompType = ComponentTypeAccountSubquery; + type PublicInstanceValue = LogicalEmpty; + type PublicInstanceWitness = LogicalEmpty>; + type CoreInput = CircuitInputAccountShard; + + fn feed_input(&mut self, input: Self::CoreInput) -> anyhow::Result<()> { + for r in &input.requests { + if r.proof.acct_pf.max_depth != self.params.max_trie_depth { + anyhow::bail!("AccountSubquery: request MPT max depth {} does not match configured max depth {}", r.proof.acct_pf.max_depth, self.params.max_trie_depth); + } + } + self.input = Some(input); + Ok(()) + } + /// Includes computing the component commitment to the logical output (the subquery results). + /// **In addition** performs _promise calls_ to the Header Component to verify + /// all `(block_number, state_root)` pairs as additional "enriched" header subqueries. + /// These are checked against the supplied promise commitment using dynamic lookups + /// (behind the scenes) by `promise_caller`. + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_caller: PromiseCaller, + ) -> CoreBuilderOutput { + // preamble: to be removed + let keccak = + KeccakChip::new_with_promise_collector(builder.range_chip(), promise_caller.clone()); + let range_chip = keccak.range(); + let rlp = RlpChip::new(range_chip, None); + let mut poseidon = create_hasher(); + poseidon.initialize_consts(builder.base.main(0), keccak.gate()); + + // Assumption: we already have input when calling this function. + // TODO: automatically derive a dummy input from params. + let input = self.input.as_ref().unwrap(); + + let mpt = MPTChip::new(rlp, &keccak); + let chip = EthStorageChip::new(&mpt, None); + let base_builder = &mut builder.base; + // actual logic + let payload = + parallelize_core(base_builder.pool(0), input.requests.clone(), |ctx, subquery| { + handle_single_account_subquery_phase0(ctx, &chip, &subquery) + }); + + let vt = extract_virtual_table(payload.iter().map(|p| p.output)); + let lr: Vec> = + extract_logical_results(payload.iter().map(|p| p.output)); + + let ctx = base_builder.main(0); + // promise calls to header component: + // - for each block number in a subquery, we must make a promise call to check the state root of that block + let header_state_root_idx = ctx.load_constant(F::from(STATE_ROOT_INDEX as u64)); + for p in payload.iter() { + let block_number = p.output.subquery.block_number; + let state_root = p.state_root; + let header_subquery = + AssignedHeaderSubquery { block_number, field_idx: header_state_root_idx }; + let promise_state_root = promise_caller + .call::, ComponentTypeHeaderSubquery>( + ctx, + FieldHeaderSubqueryCall(header_subquery), + ) + .unwrap(); + constrain_vec_equal(ctx, &state_root.hi_lo(), &promise_state_root.hi_lo()); + } + self.payload = Some((keccak, payload)); + CoreBuilderOutput { public_instances: vec![], virtual_table: vt, logical_results: lr } + } + + fn virtual_assign_phase1(&mut self, builder: &mut RlcCircuitBuilder) { + let (keccak, payload) = self.payload.take().unwrap(); + // preamble + let range_chip = keccak.range(); + let rlc_chip = builder.rlc_chip(&range_chip.gate); + let rlp = RlpChip::new(range_chip, Some(&rlc_chip)); + let mpt = MPTChip::new(rlp, &keccak); + let chip = EthStorageChip::new(&mpt, None); + + // actual logic + builder.parallelize_phase1(payload, |(ctx_gate, ctx_rlc), payload| { + handle_single_account_subquery_phase1((ctx_gate, ctx_rlc), &chip, payload) + }); + } +} + +pub struct PayloadAccountSubquery { + pub account_witness: EthAccountWitness, + pub state_root: HiLo>, + pub output: AssignedAccountSubqueryResult, +} + +/// Assigns `subquery` to virtual cells and then handles the subquery to get result. +/// **Assumes** that the stateRoot is verified. Returns the assigned private witnesses of +/// `(block_number, state_root)`, to be looked up against Header Component promise. +pub fn handle_single_account_subquery_phase0( + ctx: &mut Context, + chip: &EthStorageChip, + subquery: &CircuitInputAccountSubquery, +) -> PayloadAccountSubquery { + let gate = chip.gate(); + let range = chip.range(); + let safe = SafeTypeChip::new(range); + // assign address as SafeBytes20 and also convert to single field element + let unsafe_address = unsafe_bytes_to_assigned(ctx, subquery.proof.addr.as_bytes()); + let address = safe.raw_bytes_to(ctx, unsafe_address); + // transmute SafeBytes20 to FixLenBytesVec + let addr = SafeTypeChip::unsafe_to_fix_len_bytes_vec(address.value().to_vec(), 20); + // convert bytes (160 bits) to single field element + let addr = bytes_be_to_uint(ctx, gate, addr.bytes(), 20); + // assign MPT proof + let mpt_proof = subquery.proof.acct_pf.clone().assign(ctx); + // convert state root to HiLo form to save for later. `parse_account_proof` will constrain these witnesses to be bytes + let state_root = unsafe_mpt_root_to_hi_lo(ctx, gate, &mpt_proof); + // Check the account MPT proof + let account_witness = chip.parse_account_proof_phase0(ctx, address, mpt_proof); + // get the value for subquery + let field_idx = ctx.load_witness(F::from(subquery.field_idx as u64)); + range.check_less_than_safe(ctx, field_idx, NUM_ACCOUNT_STATE_FIELDS as u64); + + // Left pad value types to 32 bytes and convert to HiLo + let mut account_fixed = zip(ACCOUNT_STATE_FIELDS_MAX_BYTES, ACCOUNT_STATE_FIELD_IS_VAR_LEN) + .zip(&account_witness.array_witness().field_witness) + .map(|((max_bytes, is_var_len), w)| { + let inputs = w.field_cells.clone(); + // if var len, then its either nonce or balance, which are value types + let fixed_bytes = if is_var_len { + let len = w.field_len; + let var_len_bytes = + SafeTypeChip::unsafe_to_var_len_bytes_vec(inputs, len, max_bytes); + assert!(var_len_bytes.max_len() <= 32); + var_len_bytes.left_pad_to_fixed(ctx, gate) + } else { + let len = inputs.len(); + SafeTypeChip::unsafe_to_fix_len_bytes_vec(inputs, len) + }; + let fixed_bytes = fixed_bytes.into_bytes(); + // known to be <=32 bytes + pack_bytes_to_hilo(ctx, gate, &fixed_bytes).hi_lo() + }) + .collect_vec(); + + let account_is_empty = account_witness.mpt_witness().slot_is_empty; + // If account does not exist, then return: + // - nonce = 0 + // - balance = 0 + // - storageRoot = null root hash = keccak(rlp("")) = keccak(0x80) = 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 + // - codeHash = 0 (see Test Case 2 of https://eips.ethereum.org/EIPS/eip-1052) + for (i, account_field) in account_fixed.iter_mut().enumerate() { + if i == STORAGE_ROOT_INDEX { + let null_root = encode_h256_to_hilo(&KECCAK_RLP_EMPTY_STRING).hi_lo(); + for (limb, null_limb) in account_field.iter_mut().zip(null_root) { + *limb = gate.select(ctx, Constant(null_limb), *limb, account_is_empty); + } + } else { + for limb in account_field.iter_mut() { + *limb = gate.mul_not(ctx, account_is_empty, *limb); + } + } + } + + let indicator = gate.idx_to_indicator(ctx, field_idx, account_fixed.len()); + let value = gate.select_array_by_indicator(ctx, &account_fixed, &indicator); + let value = HiLo::from_hi_lo(value.try_into().unwrap()); + + let block_number = ctx.load_witness(F::from(subquery.block_number)); + + PayloadAccountSubquery { + account_witness, + state_root, + output: AssignedAccountSubqueryResult { + subquery: AssignedAccountSubquery { block_number, addr, field_idx }, + value, + }, + } +} + +pub fn handle_single_account_subquery_phase1( + ctx: RlcContextPair, + chip: &EthStorageChip, + payload: PayloadAccountSubquery, +) { + chip.parse_account_proof_phase1(ctx, payload.account_witness); +} diff --git a/axiom-query/src/components/subqueries/account/mod.rs b/axiom-query/src/components/subqueries/account/mod.rs new file mode 100644 index 00000000..7bfd1cc5 --- /dev/null +++ b/axiom-query/src/components/subqueries/account/mod.rs @@ -0,0 +1,40 @@ +//! # Account Subqueries Circuit +//! +//! | Account State Field | Max bytes | +//! |-------------------------|-------------| +//! | nonce | ≤8 | +//! | balance | ≤12 | +//! | storageRoot | 32 | +//! | codeHash | 32 | +//! +//! Account subquery +//! - `blockNumber` (uint32) +//! - `addr` (address = bytes20) +//! - `fieldIdx` (uint32) +//! + +use std::str::FromStr; + +use ethers_core::types::H256; + +/// Circuit and Component Implementation. +pub mod circuit; +/// Types +pub mod types; + +#[cfg(test)] +pub mod tests; + +pub const STORAGE_ROOT_INDEX: usize = 2; + +lazy_static::lazy_static! { + /// keccak(rlp("")) = keccak(0x80) + pub static ref KECCAK_RLP_EMPTY_STRING: H256 = H256::from_str("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").unwrap(); +} + +#[cfg(test)] +#[test] +fn test_null_mpt_root() { + use ethers_core::utils::keccak256; + assert_eq!(KECCAK_RLP_EMPTY_STRING.as_bytes(), &keccak256(vec![0x80])); +} diff --git a/axiom-query/src/components/subqueries/account/tests.rs b/axiom-query/src/components/subqueries/account/tests.rs new file mode 100644 index 00000000..83deda7f --- /dev/null +++ b/axiom-query/src/components/subqueries/account/tests.rs @@ -0,0 +1,219 @@ +use std::{marker::PhantomData, str::FromStr}; + +use axiom_codec::types::{field_elements::AnySubqueryResult, native::HeaderSubquery}; +use axiom_eth::{ + block_header::STATE_ROOT_INDEX, + halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}, + keccak::{promise::generate_keccak_shards_from_calls, types::ComponentTypeKeccak}, + providers::{setup_provider, storage::json_to_mpt_input}, + utils::component::{ + promise_loader::single::PromiseLoaderParams, ComponentCircuit, + ComponentPromiseResultsInMerkle, ComponentType, + }, +}; +use ethers_core::types::{Address, Chain, H256}; +use ethers_providers::Middleware; +use futures::future::join_all; +use itertools::Itertools; +use rand::{rngs::StdRng, Rng}; +use rand_core::SeedableRng; +use tokio; + +use crate::components::{ + dummy_rlc_circuit_params, + subqueries::{ + block_header::{ + tests::get_latest_block_number, + types::{ComponentTypeHeaderSubquery, OutputHeaderShard}, + }, + common::shard_into_component_promise_results, + }, +}; + +use super::{ + circuit::{ComponentCircuitAccountSubquery, CoreParamsAccountSubquery}, + types::{CircuitInputAccountShard, CircuitInputAccountSubquery}, +}; + +pub const ACCOUNT_PROOF_MAX_DEPTH: usize = 13; + +async fn test_mock_account_subqueries( + k: u32, + network: Chain, + subqueries: Vec<(u64, &str, usize)>, // (blockNum, addr, fieldIdx) + keccak_f_capacity: usize, +) -> ComponentCircuitAccountSubquery { + let _ = env_logger::builder().is_test(true).try_init(); + + let _provider = setup_provider(network); + let provider = &_provider; + let requests = + join_all(subqueries.into_iter().map(|(block_num, addr, field_idx)| async move { + let addr = Address::from_str(addr).unwrap(); + let block = provider.get_block(block_num).await.unwrap().unwrap(); + let proof = provider.get_proof(addr, vec![], Some(block_num.into())).await.unwrap(); + let mut proof = json_to_mpt_input(proof, ACCOUNT_PROOF_MAX_DEPTH, 0); + proof.acct_pf.root_hash = block.state_root; + CircuitInputAccountSubquery { + block_number: block_num, + field_idx: field_idx as u32, + proof, + } + })) + .await; + + let mut promise_header = OutputHeaderShard { + results: requests + .iter() + .map(|r| AnySubqueryResult { + subquery: HeaderSubquery { + block_number: r.block_number as u32, + field_idx: STATE_ROOT_INDEX as u32, + }, + value: r.proof.acct_pf.root_hash, + }) + .collect(), + }; + // shard_into_component_promise_results::>(promise_header); + // add in an extra one just to test lookup table + promise_header.results.push(AnySubqueryResult { + subquery: HeaderSubquery { block_number: 0x9165ed, field_idx: 4 }, + value: H256::from_str("0xec4c8ec02281c196e3f882b4061c05f4f0843a0eaf72a3ee4715077220934bbc") + .unwrap(), + }); + + let header_capacity = promise_header.len(); + + let circuit_params = dummy_rlc_circuit_params(k as usize); + let mut circuit = ComponentCircuitAccountSubquery::new( + CoreParamsAccountSubquery { + capacity: requests.len(), + max_trie_depth: ACCOUNT_PROOF_MAX_DEPTH, + }, + ( + PromiseLoaderParams::new_for_one_shard(keccak_f_capacity), + PromiseLoaderParams::new_for_one_shard(header_capacity), + ), + circuit_params, + ); + + let input = CircuitInputAccountShard:: { requests, _phantom: PhantomData }; + circuit.feed_input(Box::new(input)).unwrap(); + circuit.calculate_params(); + + let promises = [ + ( + ComponentTypeKeccak::::get_type_id(), + ComponentPromiseResultsInMerkle::from_single_shard( + generate_keccak_shards_from_calls(&circuit, keccak_f_capacity) + .unwrap() + .into_logical_results(), + ), + ), + ( + ComponentTypeHeaderSubquery::::get_type_id(), + shard_into_component_promise_results::>( + promise_header.into(), + ), + ), + ] + .into_iter() + .collect(); + circuit.fulfill_promise_results(&promises).unwrap(); + + let instances: Vec = circuit.get_public_instances().into(); + MockProver::run(k, &circuit, vec![instances]).unwrap().assert_satisfied(); + circuit +} + +#[tokio::test] +async fn test_mock_account_subqueries_simple() { + let k = 18; + let subqueries = vec![ + (17143006, "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B", 0), + (17143000, "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", 1), + (15000000, "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", 2), + (15411056, "0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6", 3), + (18320885, "0x0000a26b00c1F0DF003000390027140000fAa719", 0), // Opensea: fees 3 + (18320885, "0x00005EA00Ac477B1030CE78506496e8C2dE24bf5", 0), // Seadrop + (18320885, "0x000000008924D42d98026C656545c3c1fb3ad31C", 0), // Seadrop factory + ]; + let _circuit = test_mock_account_subqueries(k, Chain::Mainnet, subqueries, 200).await; + // TODO: validate field values +} + +// this test takes a while +#[tokio::test] +async fn test_mock_account_subqueries_vanity() { + let k = 19; + let latest = 18320885; + let mut rng = StdRng::seed_from_u64(0); + let subqueries = (0..32) + .flat_map(|_| { + vec![ + ( + rng.gen_range(15303574..latest), + "0x0000a26b00c1F0DF003000390027140000fAa719", + rng.gen_range(0..4), + ), // Opensea: fees 3 + ( + rng.gen_range(15527904..latest), + "0x00005EA00Ac477B1030CE78506496e8C2dE24bf5", + rng.gen_range(0..4), + ), // Seadrop + ( + rng.gen_range(16836342..latest), + "0x000000008924D42d98026C656545c3c1fb3ad31C", + rng.gen_range(0..4), + ), // Seadrop factory + ] + }) + .collect_vec(); + let _circuit = test_mock_account_subqueries(k, Chain::Mainnet, subqueries, 2800).await; +} + +#[tokio::test] +async fn test_mock_account_subqueries_genesis() { + let network = Chain::Mainnet; + // address existing in block 0 + let address = "0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0"; + let mut rng = StdRng::seed_from_u64(1); + let latest = get_latest_block_number(network).await; + let mut subqueries: Vec<_> = + (0..7).map(|_| (rng.gen_range(0..latest), address, rng.gen_range(0..4))).collect(); + subqueries.push((0, address, 0)); + let _circuit = test_mock_account_subqueries(18, network, subqueries, 300).await; +} + +#[tokio::test] +async fn test_mock_eoa_account_subqueries() { + let network = Chain::Mainnet; + let subqueries: Vec<_> = vec![ + (18320885, "0x5cC0d3B4926D5430946Ea1b60eA2B27974485921", 0), + (18320885, "0x5cC0d3B4926D5430946Ea1b60eA2B27974485921", 1), + (18320885, "0x5cC0d3B4926D5430946Ea1b60eA2B27974485921", 2), + (18320885, "0x5cC0d3B4926D5430946Ea1b60eA2B27974485921", 3), + ]; + let _circuit = test_mock_account_subqueries(18, network, subqueries, 50).await; +} + +#[tokio::test] +async fn test_mock_empty_account_subqueries() { + let network = Chain::Mainnet; + let subqueries: Vec<_> = vec![ + (18320885, "0x000000008924D42d98026C656545c3c1fb3ad31B", 0), // empty account + (18320885, "0x000000008924D42d98026C656545c3c1fb3ad31B", 1), + (18320885, "0x000000008924D42d98026C656545c3c1fb3ad31B", 2), + (18320885, "0x000000008924D42d98026C656545c3c1fb3ad31B", 3), + ]; + let _circuit = test_mock_account_subqueries(18, network, subqueries, 50).await; +} + +// Goerli is dead +// #[tokio::test] +// async fn test_mock_empty_account_subqueries2() { +// let network = Chain::Goerli; +// // non-inclusion ends in extension node +// let subqueries: Vec<_> = vec![(9173678, "0x8dde5d4a8384f403f888e1419672d94c570440c9", 0)]; +// let _circuit = test_mock_account_subqueries(18, network, subqueries, 50).await; +// } diff --git a/axiom-query/src/components/subqueries/account/types.rs b/axiom-query/src/components/subqueries/account/types.rs new file mode 100644 index 00000000..9653449e --- /dev/null +++ b/axiom-query/src/components/subqueries/account/types.rs @@ -0,0 +1,129 @@ +//! Types are separated into: +//! - Circuit metadata that along with the circuit type determines the circuit configuration completely. +//! - Human readable _logical_ input and output to the circuit. These include private inputs and outputs that are only commited to in the public output. +//! - The in-circuit formatted versions of logical inputs and outputs. These include formatting in terms of field elements and accounting for all lengths needing to be fixed at compile time. +//! - We then provide conversion functions from human-readable to circuit formats. +//! - This circuit has no public instances (IO) other than the circuit's own component commitment and the promise commitments from any component calls. +use std::marker::PhantomData; + +use axiom_codec::{ + types::{field_elements::FieldAccountSubquery, native::AccountSubquery}, + HiLo, +}; +use axiom_eth::{ + halo2_base::AssignedValue, + impl_fix_len_call_witness, + providers::storage::json_to_mpt_input, + storage::circuit::EthStorageInput, + utils::{ + build_utils::dummy::DummyFrom, + component::{circuit::CoreBuilderInput, ComponentType, ComponentTypeId, LogicalResult}, + }, +}; +use ethers_core::types::{EIP1186ProofResponse, H256}; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::subqueries::common::OutputSubqueryShard, utils::codec::AssignedAccountSubquery, + Field, +}; + +use super::circuit::CoreParamsAccountSubquery; + +/// Identifier for the component type of this component circuit +pub struct ComponentTypeAccountSubquery(PhantomData); + +/// Human readable. +/// The output value of any account subquery is always `bytes32` right now. +pub type OutputAccountShard = OutputSubqueryShard; + +/// Circuit input for a shard of Account subqueries. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CircuitInputAccountShard { + /// Enriched subquery requests + pub requests: Vec, + pub _phantom: PhantomData, +} + +/// Circuit input for a single Account subquery. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CircuitInputAccountSubquery { + /// The block number to access the account state at. + pub block_number: u64, + /// Account proof formatted as MPT input. `proof.storage_pfs` will be empty. + /// It will contain the correct state root of the block. + pub proof: EthStorageInput, + pub field_idx: u32, +} + +impl DummyFrom for CircuitInputAccountShard { + fn dummy_from(core_params: CoreParamsAccountSubquery) -> Self { + let CoreParamsAccountSubquery { capacity, max_trie_depth } = core_params; + let request = { + let mut pf: EIP1186ProofResponse = + serde_json::from_str(GENESIS_ADDRESS_0_ACCOUNT_PROOF).unwrap(); + pf.storage_proof.clear(); + let proof = json_to_mpt_input(pf, max_trie_depth, 0); + CircuitInputAccountSubquery { block_number: 0, field_idx: 0, proof } + }; + Self { requests: vec![request; capacity], _phantom: PhantomData } + } +} + +/// The output value of any account subquery is always `bytes32` right now. +/// Vector has been resized to the capacity. +pub type CircuitOutputAccountShard = OutputSubqueryShard, HiLo>; + +impl_fix_len_call_witness!( + FieldAccountSubqueryCall, + FieldAccountSubquery, + ComponentTypeAccountSubquery +); + +// ===== The account component has no public instances other than the component commitment and promise commitments from external component calls ===== + +impl ComponentType for ComponentTypeAccountSubquery { + type InputValue = FieldAccountSubquery; + type InputWitness = AssignedAccountSubquery; + type OutputValue = HiLo; + type OutputWitness = HiLo>; + type LogicalInput = FieldAccountSubquery; + + fn get_type_id() -> ComponentTypeId { + "axiom-query:ComponentTypeAccountSubquery".to_string() + } + + fn logical_result_to_virtual_rows_impl( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + vec![(ins.input, ins.output)] + } + fn logical_input_to_virtual_rows_impl(li: &Self::LogicalInput) -> Vec { + vec![*li] + } +} + +impl From for CircuitOutputAccountShard { + fn from(output: OutputAccountShard) -> Self { + output.convert_into() + } +} + +pub const GENESIS_ADDRESS_0_ACCOUNT_PROOF: &str = r#"{"address":"0x0000000000000000000000000000000000000000","balance":"0x0","codeHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","storageHash":"0x0000000000000000000000000000000000000000000000000000000000000000","accountProof":["0xf90211a090dcaf88c40c7bbc95a912cbdde67c175767b31173df9ee4b0d733bfdd511c43a0babe369f6b12092f49181ae04ca173fb68d1a5456f18d20fa32cba73954052bda0473ecf8a7e36a829e75039a3b055e51b8332cbf03324ab4af2066bbd6fbf0021a0bbda34753d7aa6c38e603f360244e8f59611921d9e1f128372fec0d586d4f9e0a04e44caecff45c9891f74f6a2156735886eedf6f1a733628ebc802ec79d844648a0a5f3f2f7542148c973977c8a1e154c4300fec92f755f7846f1b734d3ab1d90e7a0e823850f50bf72baae9d1733a36a444ab65d0a6faaba404f0583ce0ca4dad92da0f7a00cbe7d4b30b11faea3ae61b7f1f2b315b61d9f6bd68bfe587ad0eeceb721a07117ef9fc932f1a88e908eaead8565c19b5645dc9e5b1b6e841c5edbdfd71681a069eb2de283f32c11f859d7bcf93da23990d3e662935ed4d6b39ce3673ec84472a0203d26456312bbc4da5cd293b75b840fc5045e493d6f904d180823ec22bfed8ea09287b5c21f2254af4e64fca76acc5cd87399c7f1ede818db4326c98ce2dc2208a06fc2d754e304c48ce6a517753c62b1a9c1d5925b89707486d7fc08919e0a94eca07b1c54f15e299bd58bdfef9741538c7828b5d7d11a489f9c20d052b3471df475a051f9dd3739a927c89e357580a4c97b40234aa01ed3d5e0390dc982a7975880a0a089d613f26159af43616fd9455bb461f4869bfede26f2130835ed067a8b967bfb80","0xf90211a06f499aafd2fcc95db6ac85b9ec36ce16b3747180d51b7ba72babdbceaef0cac8a034ffbe94cc9f4ac7e43bbd0ab875ce079e5d131f72f33974c09525bad37da4b4a026ac19ac1e99055b84ef53fad0ff4bf76a54af485b399dac5d91e55320941c16a0a33d103a92ff6f95c081309f83f474a009048614d5d40e14067dbae0cf9ed084a046a0e834a4f3482cb37f70f1f188d7c749c33fb8b94854b16dcebe840fc9390aa0a5a914013e15472dc3ae08f774e9d5ac3127419a2c81bec98963f40dde42ebaaa0b5740bdfa8ecf2b4d0b560f72474846788a3e19f9e0894c6bd2eb46255d222e9a04aa4e4ebe1930364ae283e8f1fa4de7ef1867a3f7fb89c23e068b807464eac14a0f84e5e71db73c15fc0bfa5566fae5e687e8eed398ef68e0d8229a7bc2eb333fda0551d35fa9c76d23bbbc1feb30a16e6ee1087c96aa2c31a8be297c4904c37373ba0f25b1be3ea53f222e17985dde60b04716bc342232874af3ad0af1652165138f2a0e50848e903b54f966851f4cbac1deb5b1d1beb42b4223379bb911f68001747f8a021d90bccf615ff6349cc5fdf8604ee52789c0e977fe12c2401b1cc229a9e7e47a0ade009f37dd2907895900d853eefbcf26af8f1665c8802804584241d825a6b49a09fe500ded938f686589ab2f42caad51341441980ea20e9fcb69e62b774c9990fa087888bb118be98fa5dfd57a76d0b59af08d7977fe87bad7c0d68ef82f2c9a92880","0xf901b1a0ec652a529bfb6f625879b961b8f7986b8294cfb1082d24b2c27f9a5b3fbccece80a088a3bacf48a0d00e3b36c3274ca2ab8d9d8f54c90e03724b3f6f5137c5a350c1a0a3c84954aad8408ed44eed138573a4db917e19d49e6cb716c14c7dedcb7a0051a069d3ae295c988b5e52f9d86b3aa85e9167a2d59a5ad47b6d1f8faaae9cd3aee4a0252dbbed1d3b713b43b6b8745d1d60605bbc4474746bfffe16375acbf42c0ec080a0a886f03399a8e32312b65d59b2a5d97ba7bb078aa5dab7aeb08d1fbd721a0944a0e9b89be70399650793c37b4aca1779e5adf4d8a07cea63dab9a9f5ef6b7dc66fa0b352a156bda0e42ce192bc430f8513e827b0aaa70002a21fef3a4df416be93e9a00665ba82ae23119a4a244be15e42e23589490995236c43bac11b5628613c337ba0b45176ce952dda9f523f244d921805f005c11b2027f53d12dda0e069278cf908a0eefa94d2ecf8946494c634277eac048823f35f7820d354c6e9352176c9b44e4da046443df5febce492f17eed4f98f2ad755fec20cd9eede857dc951757ef85b51aa0fc14ff2dbb3675d852fb37d7796eb1f303282d3466aef865a17da813d22bfc028080","0xf8718080a06f69700f636d81db1793bcee2562dcf0a4a06f2525fb2f55f5c00aab81b4b86880a00f13a02c0878c787e7f9fdcfbb3b3169b42c2a0595c7afecf86dbb46fbcb567b80808080a05c80e25e034b9cc0f079b1226a97c22434851b86c6b55be77eae89b096462afd80808080808080"],"storageProof":[{"key":"0x0","proof":[],"value":"0x0"}]}"#; + +#[cfg(test)] +mod test { + use axiom_eth::providers::setup_provider; + use ethers_core::types::{Chain, H256}; + use ethers_providers::Middleware; + + use super::GENESIS_ADDRESS_0_ACCOUNT_PROOF; + + #[tokio::test] + async fn test_dummy_account_proof() { + let provider = setup_provider(Chain::Mainnet); + let address = "0x0000000000000000000000000000000000000000"; + let proof = provider.get_proof(address, vec![H256::zero()], Some(0.into())).await.unwrap(); + assert_eq!(GENESIS_ADDRESS_0_ACCOUNT_PROOF, serde_json::to_string(&proof).unwrap()); + } +} diff --git a/axiom-query/src/components/subqueries/block_header/circuit.rs b/axiom-query/src/components/subqueries/block_header/circuit.rs new file mode 100644 index 00000000..7fe68301 --- /dev/null +++ b/axiom-query/src/components/subqueries/block_header/circuit.rs @@ -0,0 +1,372 @@ +use std::iter::zip; + +use axiom_codec::{ + constants::FIELD_IDX_BITS, + special_values::{ + HEADER_EXTRA_DATA_LEN_FIELD_IDX, HEADER_HASH_FIELD_IDX, HEADER_HEADER_SIZE_FIELD_IDX, + HEADER_LOGS_BLOOM_FIELD_IDX_OFFSET, + }, + HiLo, +}; +use axiom_eth::{ + block_header::{ + get_block_header_rlp_max_lens_from_extra, EthBlockHeaderChip, EthBlockHeaderWitness, + BLOCK_HEADER_FIELD_IS_VAR_LEN, EXTRA_DATA_INDEX, NUM_BLOCK_HEADER_FIELDS, + }, + halo2_base::{ + gates::{flex_gate::threads::parallelize_core, GateInstructions, RangeInstructions}, + safe_types::{SafeBool, SafeTypeChip}, + utils::bit_length, + AssignedValue, Context, + QuantumCell::Constant, + }, + halo2_proofs::plonk::ConstraintSystem, + keccak::{types::ComponentTypeKeccak, KeccakChip}, + rlc::circuit::builder::RlcCircuitBuilder, + rlc::circuit::builder::RlcContextPair, + rlp::RlpChip, + utils::{ + build_utils::aggregation::CircuitMetadata, + circuit_utils::extract_array_chunk, + component::{ + circuit::{ + ComponentBuilder, ComponentCircuitImpl, CoreBuilder, CoreBuilderOutput, + CoreBuilderOutputParams, CoreBuilderParams, + }, + promise_collector::PromiseCaller, + promise_loader::single::PromiseLoader, + types::FixLenLogical, + utils::create_hasher, + LogicalResult, + }, + }, + utils::{ + circuit_utils::{ + bytes::{pack_bytes_to_hilo, select_hi_lo}, + is_equal_usize, is_in_range, min_with_usize, unsafe_lt_mask, + }, + load_h256_to_safe_bytes32, unsafe_bytes_to_assigned, + }, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::subqueries::common::{extract_logical_results, extract_virtual_table}, + utils::codec::{AssignedHeaderSubquery, AssignedHeaderSubqueryResult}, + Field, +}; + +use super::{ + mmr_verify::{assign_mmr, verify_mmr_proof, AssignedMmr}, + types::{ + CircuitInputHeaderShard, CircuitInputHeaderSubquery, ComponentTypeHeaderSubquery, + LogicalPublicInstanceHeader, + }, +}; + +pub struct CoreBuilderHeaderSubquery { + input: Option>, + params: CoreParamsHeaderSubquery, + payload: Option<(KeccakChip, Vec>)>, +} + +/// Specify the output format of HeaderSubquery component. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct CoreParamsHeaderSubquery { + pub max_extra_data_bytes: usize, + pub capacity: usize, +} +impl CoreBuilderParams for CoreParamsHeaderSubquery { + fn get_output_params(&self) -> CoreBuilderOutputParams { + CoreBuilderOutputParams::new(vec![self.capacity]) + } +} + +/// For header circuit to read promise results. +pub type PromiseLoaderHeaderSubquery = PromiseLoader>; +pub type ComponentCircuitHeaderSubquery = + ComponentCircuitImpl, PromiseLoaderHeaderSubquery>; + +impl CircuitMetadata for CoreBuilderHeaderSubquery { + const HAS_ACCUMULATOR: bool = false; + fn num_instance(&self) -> Vec { + unreachable!() + } +} + +impl ComponentBuilder for CoreBuilderHeaderSubquery { + type Params = CoreParamsHeaderSubquery; + + fn new(params: Self::Params) -> Self { + Self { input: None, params, payload: None } + } + fn get_params(&self) -> Self::Params { + self.params.clone() + } + fn clear_witnesses(&mut self) { + self.payload = None; + } + fn calculate_params(&mut self) -> Self::Params { + self.params.clone() + } + fn configure_with_params(_: &mut ConstraintSystem, _: Self::Params) {} +} +impl CoreBuilder for CoreBuilderHeaderSubquery { + type CompType = ComponentTypeHeaderSubquery; + type PublicInstanceValue = LogicalPublicInstanceHeader; + type PublicInstanceWitness = LogicalPublicInstanceHeader>; + type CoreInput = CircuitInputHeaderShard; + fn feed_input(&mut self, input: Self::CoreInput) -> anyhow::Result<()> { + let (header_rlp_max_bytes, _) = + get_block_header_rlp_max_lens_from_extra(self.params.max_extra_data_bytes); + for request in &input.requests { + if request.header_rlp.len() != header_rlp_max_bytes { + anyhow::bail!("Header RLP length not resized correctly."); + } + } + self.input = Some(input); + Ok(()) + } + // No public instances are assigned inside this function. That is done automatically. + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_caller: PromiseCaller, + ) -> CoreBuilderOutput { + // preamble: to be removed + let keccak = + KeccakChip::new_with_promise_collector(builder.range_chip(), promise_caller.clone()); + let range_chip = keccak.range(); + let rlp = RlpChip::new(range_chip, None); + let mut poseidon = create_hasher(); + poseidon.initialize_consts(builder.base.main(0), keccak.gate()); + + // Assumption: we already have input when calling this function. + // TODO: automatically derive a dummy input from params. + let input = self.input.as_ref().unwrap(); + let base_builder = &mut builder.base; + // assign MMR and compute its keccakPacked + let ctx = base_builder.main(0); + let assigned_mmr = assign_mmr(ctx, range_chip, input.mmr); + let mmr_keccak = assigned_mmr.keccak(ctx, &keccak); + // handle subqueries + let pool = base_builder.pool(0); + let chip = EthBlockHeaderChip::new(rlp, self.params.max_extra_data_bytes); + let payload = parallelize_core(pool, input.requests.clone(), |ctx, subquery| { + handle_single_header_subquery_phase0(ctx, &chip, &keccak, &subquery, &assigned_mmr) + }); + + let vt = extract_virtual_table(payload.iter().map(|p| p.output)); + let lr: Vec> = + extract_logical_results(payload.iter().map(|p| p.output)); + + let logical_pis = + LogicalPublicInstanceHeader { mmr_keccak: HiLo::from_hi_lo(mmr_keccak.hi_lo()) }; + self.payload = Some((keccak, payload)); + + CoreBuilderOutput { + public_instances: logical_pis.into_raw(), + virtual_table: vt, + logical_results: lr, + } + } + + // There is no additional logic necessary for component commitments. + fn virtual_assign_phase1(&mut self, builder: &mut RlcCircuitBuilder) { + let (keccak, payload) = self.payload.take().unwrap(); + // preamble + let range_chip = keccak.range(); + let rlc_chip = builder.rlc_chip(&range_chip.gate); + let rlp = RlpChip::new(range_chip, Some(&rlc_chip)); + + // actual logic + let chip = EthBlockHeaderChip::new(rlp, self.params.max_extra_data_bytes); + builder.parallelize_phase1(payload, |(ctx_gate, ctx_rlc), payload| { + handle_single_header_subquery_phase1((ctx_gate, ctx_rlc), &chip, payload) + }); + } +} + +/// Non-value types are right padded with zeros. +/// "Value" type in the sense of [Solidity](https://docs.soliditylang.org/en/latest/types.html). Essentially everything that's not a variable length array. +/// We should conform to how Value types are left vs right padded with zeros in EVM memory: https://ethdebug.github.io/solidity-data-representation/#table-of-direct-types +/// There is currently no type in block header that is `bytesN` where `N < 32`. If there were, we would want to *right pad* those with zeros. +/// `bytes32` is neither left nor right padded. +/// +/// Currently `logsBloom` (fixed len 256 bytes) and `extraData` (var len bytes) are not left padded. +pub const BLOCK_HEADER_FIELD_SHOULD_LEFT_PAD: [bool; NUM_BLOCK_HEADER_FIELDS] = [ + true, true, true, true, true, true, false, true, true, true, true, true, false, true, true, + true, true, true, true, true, +]; + +pub struct PayloadHeaderSubquery { + pub header_witness: EthBlockHeaderWitness, + pub output: AssignedHeaderSubqueryResult, +} + +/// Assigns `subquery` to virtual cells and then handles the subquery to get result. +pub fn handle_single_header_subquery_phase0( + ctx: &mut Context, + chip: &EthBlockHeaderChip, + keccak: &KeccakChip, + subquery: &CircuitInputHeaderSubquery, + assigned_mmr: &AssignedMmr, +) -> PayloadHeaderSubquery { + let gate = chip.gate(); + let range = chip.range(); + let safe = SafeTypeChip::new(range); + let header_rlp = unsafe_bytes_to_assigned(ctx, &subquery.header_rlp); + // parse the header RLP + let header_witness = chip.decompose_block_header_phase0(ctx, keccak, &header_rlp); + + // verify MMR proof for this block + let block_number = header_witness.get_number_value(ctx, gate); + let block_hash = header_witness.block_hash.output_bytes.clone(); + let mmr_proof = (subquery.mmr_proof.iter()) + .map(|&node| load_h256_to_safe_bytes32(ctx, &safe, node)) + .collect(); + verify_mmr_proof(ctx, keccak, assigned_mmr, block_number, block_hash, mmr_proof, None); + + let field_idx = ctx.load_witness(F::from(subquery.field_idx as u64)); + range.range_check(ctx, field_idx, FIELD_IDX_BITS); + // if `field_idx` < `HEADER_HASH_IDX`, then it is an actual header field + let threshold = Constant(F::from(HEADER_HASH_FIELD_IDX as u64)); + let is_idx_in_header = range.is_less_than(ctx, field_idx, threshold, FIELD_IDX_BITS); + let header_idx = gate.mul(ctx, field_idx, is_idx_in_header); + + let (_, header_fields_max_bytes) = + get_block_header_rlp_max_lens_from_extra(chip.max_extra_data_bytes); + // Left pad value types to 32 bytes and convert to HiLo + let header_fixed = zip(BLOCK_HEADER_FIELD_IS_VAR_LEN, BLOCK_HEADER_FIELD_SHOULD_LEFT_PAD) + .zip_eq(&header_witness.rlp_witness.field_witness) + .enumerate() + .map(|(i, ((is_var_len, left_pad), w))| { + let inputs = w.field_cells.clone(); + let fixed_bytes = if is_var_len && left_pad { + let len = w.field_len; + let var_len_bytes = SafeTypeChip::unsafe_to_var_len_bytes_vec( + inputs, + len, + header_fields_max_bytes[i], + ); + assert!(var_len_bytes.max_len() <= 32); + var_len_bytes.left_pad_to_fixed(ctx, gate) + } else { + let len = inputs.len(); + // currently the only var len field that is not value type is `extraData` + SafeTypeChip::unsafe_to_fix_len_bytes_vec(inputs, len) + }; + let mut fixed_bytes = fixed_bytes.into_bytes(); + if fixed_bytes.len() > 32 { + assert!(!left_pad); + fixed_bytes.truncate(32); + } + // constrain `extraData` is 0s after length + if i == EXTRA_DATA_INDEX { + let mut len = w.field_len; + if chip.max_extra_data_bytes > 32 { + let max_bits = bit_length(chip.max_extra_data_bytes as u64); + len = min_with_usize(ctx, range, len, 32, max_bits); + } + let mask = unsafe_lt_mask(ctx, gate, len, 32); + for (byte, mask) in fixed_bytes.iter_mut().zip_eq(mask) { + *byte = SafeTypeChip::unsafe_to_byte(gate.mul(ctx, *byte, mask)); + } + } + + // Slightly more optimal to pack to 20 bytes for `beneficiary` but HiLo is cleaner, so we'll sacrifice the optimization + pack_bytes_to_hilo(ctx, gate, &fixed_bytes).hi_lo() + }) + .collect_vec(); + let header_indicator = gate.idx_to_indicator(ctx, header_idx, header_fixed.len()); + let value = gate.select_array_by_indicator(ctx, &header_fixed, &header_indicator); + let mut value = HiLo::from_hi_lo(value.try_into().unwrap()); + // time to handle special cases: + let [return_hash, return_size, return_extra_data_len] = + [HEADER_HASH_FIELD_IDX, HEADER_HEADER_SIZE_FIELD_IDX, HEADER_EXTRA_DATA_LEN_FIELD_IDX] + .map(|const_idx| is_equal_usize(ctx, gate, field_idx, const_idx)); + // return block hash + let block_hash = HiLo::from_hi_lo(header_witness.get_block_hash_hi_lo()); + value = select_hi_lo(ctx, gate, &block_hash, &value, return_hash); + // return block size in bytes + let block_size = HiLo::from_hi_lo([ctx.load_zero(), header_witness.rlp_witness.rlp_len]); + value = select_hi_lo(ctx, gate, &block_size, &value, return_size); + // return extra data length in bytes + let extra_data = header_witness.get_extra_data(); + let extra_data_len = HiLo::from_hi_lo([ctx.load_zero(), extra_data.field_len]); + value = select_hi_lo(ctx, gate, &extra_data_len, &value, return_extra_data_len); + + let (logs_bloom_buf, return_logs_bloom) = handle_logs_bloom( + ctx, + range, + &header_witness.get_logs_bloom().field_cells, + field_idx, + HEADER_LOGS_BLOOM_FIELD_IDX_OFFSET, + ); + value = select_hi_lo(ctx, gate, &logs_bloom_buf, &value, return_logs_bloom); + + // constrain that `field_idx` is valid: either + // - `field_idx` is less than true length of block header list + // - this means you cannot request a field such as `withdrawalsRoot` if the block is before EIP-4895 + // - or `field_idx` is one of the special return cases `header_idx` is less than true length of block header list + let is_valid_header_idx = + range.is_less_than(ctx, header_idx, header_witness.get_list_len(), FIELD_IDX_BITS); + // This sum is guaranteed to be 0 or 1: + let is_special_case = + gate.sum(ctx, [return_hash, return_size, return_extra_data_len, return_logs_bloom]); + let is_valid = gate.select(ctx, is_valid_header_idx, is_special_case, is_idx_in_header); + gate.assert_is_const(ctx, &is_valid, &F::ONE); + + PayloadHeaderSubquery { + header_witness, + output: AssignedHeaderSubqueryResult { + subquery: AssignedHeaderSubquery { block_number, field_idx }, + value, + }, + } +} + +pub fn handle_single_header_subquery_phase1( + ctx: RlcContextPair, + chip: &EthBlockHeaderChip, + payload: PayloadHeaderSubquery, +) { + chip.decompose_block_header_phase1(ctx, payload.header_witness); +} + +/// Returns `HiLo(logs_bloom_bytes[logs_bloom_idx..logs_bloom_idx + 32]), is_in_range` +/// where `logs_bloom_idx = field_idx - logs_bloom_field_idx_offset` and +/// `is_in_range = (0..8).contains(logs_bloom_idx)` +pub(crate) fn handle_logs_bloom( + ctx: &mut Context, + range: &impl RangeInstructions, + logs_bloom_bytes: &[AssignedValue], + field_idx: AssignedValue, + logs_bloom_field_idx_offset: usize, +) -> (HiLo>, SafeBool) { + let offset = logs_bloom_field_idx_offset; + let is_offset = is_in_range(ctx, range, field_idx, offset..offset + 8, FIELD_IDX_BITS); + let gate = range.gate(); + let mut shift = gate.sub(ctx, field_idx, Constant(F::from(offset as u64))); + shift = gate.mul(ctx, shift, *is_offset.as_ref()); + let buffer = extract_array_chunk(ctx, gate, logs_bloom_bytes, shift, 32); + let buffer = SafeTypeChip::unsafe_to_fix_len_bytes_vec(buffer, 32); + (pack_bytes_to_hilo(ctx, gate, buffer.bytes()), is_offset) +} + +#[cfg(test)] +mod test { + use axiom_eth::block_header::{EXTRA_DATA_INDEX, LOGS_BLOOM_INDEX}; + + use super::BLOCK_HEADER_FIELD_SHOULD_LEFT_PAD; + + #[test] + fn test_block_header_value_types() { + for (i, &is_value) in BLOCK_HEADER_FIELD_SHOULD_LEFT_PAD.iter().enumerate() { + if !is_value { + assert!(i == LOGS_BLOOM_INDEX || i == EXTRA_DATA_INDEX); + } + } + } +} diff --git a/axiom-query/src/components/subqueries/block_header/mmr_verify.rs b/axiom-query/src/components/subqueries/block_header/mmr_verify.rs new file mode 100644 index 00000000..f066d79f --- /dev/null +++ b/axiom-query/src/components/subqueries/block_header/mmr_verify.rs @@ -0,0 +1,166 @@ +use axiom_eth::{ + halo2_base::{ + gates::{GateInstructions, RangeChip, RangeInstructions}, + safe_types::{SafeBool, SafeBytes32, SafeTypeChip}, + utils::ScalarField, + AssignedValue, Context, + QuantumCell::{Constant, Existing}, + }, + keccak::{types::KeccakVarLenQuery, KeccakChip}, + utils::{is_zero_vec, load_h256_to_safe_bytes32}, +}; +use ethers_core::types::H256; +use itertools::Itertools; + +use crate::Field; + +use super::MMR_MAX_NUM_PEAKS; + +/// `mmr` is Merkle Mountain Range in *increasing* order of peak size. +/// +/// After construction it is guaranteed that: +/// * `mmr_num_blocks` 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_num_blocks`. +#[derive(Clone, Debug)] +pub struct AssignedMmr { + pub mmr: [SafeBytes32; MMR_MAX_NUM_PEAKS], + pub mmr_bits: [SafeBool; MMR_MAX_NUM_PEAKS], + pub mmr_num_blocks: AssignedValue, +} + +pub fn assign_mmr( + ctx: &mut Context, + range: &RangeChip, + mmr: [H256; MMR_MAX_NUM_PEAKS], +) -> AssignedMmr { + let safe = SafeTypeChip::new(range); + let gate = range.gate(); + let mmr = mmr.map(|peak| load_h256_to_safe_bytes32(ctx, &safe, peak)); + let mmr_bits = mmr + .iter() + .map(|peak| { + let no_peak = is_zero_vec(ctx, gate, peak.value()); + SafeTypeChip::unsafe_to_bool(gate.not(ctx, no_peak)) + }) + .collect_vec(); + let mmr_num_blocks = gate.inner_product( + ctx, + mmr_bits.iter().map(|bit| *bit.as_ref()), + gate.pow_of_two().iter().take(mmr_bits.len()).map(|x| Constant(*x)), + ); + let mmr_bits = mmr_bits.try_into().unwrap(); + AssignedMmr { mmr, mmr_bits, mmr_num_blocks } +} + +pub type AssignedMmrKeccak = KeccakVarLenQuery; + +impl AssignedMmr { + pub fn keccak( + &self, + ctx: &mut Context, + keccak_chip: &KeccakChip, + ) -> AssignedMmrKeccak { + let gate = keccak_chip.gate(); + // mmr_num_peaks = bit_length(mmr_num_blocks) = MMR_MAX_NUM_PEAKS - num_leading_zeros(mmr_num_blocks) + let mut is_leading = Constant(F::ONE); + let mut num_leading_zeros = ctx.load_zero(); + for bit in self.mmr_bits.iter().rev() { + // is_zero = 1 - bit + // is_leading = is_leading * (is_zero) + is_leading = Existing(gate.mul_not(ctx, *bit.as_ref(), is_leading)); + num_leading_zeros = gate.add(ctx, num_leading_zeros, is_leading); + } + let max_num_peaks = F::from(MMR_MAX_NUM_PEAKS as u64); + let num_peaks = gate.sub(ctx, Constant(max_num_peaks), num_leading_zeros); + let mmr_bytes = gate.mul(ctx, num_peaks, Constant(F::from(32u64))); + keccak_chip.keccak_var_len( + ctx, + self.mmr.iter().flat_map(|bytes| bytes.value().iter().copied()).collect(), + mmr_bytes, + 0, + ) + } +} + +/// `mmr` is a Merkle Mountan Range (MMR) of block hashes (bytes32). +/// It is a commitment to block hashes for blocks [0, mmr_num_blocks). +/// Given a `merkle_proof` of a block hash at index `list_id`, +/// we verify the merkle proof into the MMR. +/// +/// If `not_empty` is None, then we definitely enforce the Merkle proof check. +/// If `not_empty` is Some, then we conditionally enforce the Merkle proof check +/// depending on the boolean flag. +pub fn verify_mmr_proof( + ctx: &mut Context, + keccak: &KeccakChip, + assigned_mmr: &AssignedMmr, + list_id: AssignedValue, // the index in underlying list + leaf: SafeBytes32, // the leaf node at `list_id` in underlying list + merkle_proof: Vec>, + not_empty: Option>, // actually do the proof check +) { + let AssignedMmr { mmr, mmr_bits, mmr_num_blocks } = assigned_mmr; + assert!(!mmr.is_empty()); + let range = keccak.range(); + 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_num_blocks, 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.as_ref(), *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 = F::from(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.value().iter().chain(node.value())) + .zip_eq(node.value().iter().chain(cur.value())) + .map(|(a, b)| gate.select(ctx, *b, *a, side)) + .collect_vec(); + let hash = keccak.keccak_fixed_len(ctx, concat).output_bytes; + 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); + // H256 as bytes: + let peak = gate.select_array_by_indicator(ctx, mmr, &peak_indicator); + // H256 as bytes: + let proof_peak = gate.select_array_by_indicator(ctx, &intermediate_hashes, &peak_indicator); + // If Some, conditional selector on whether to check merkle proof validity + let not_empty: Option> = not_empty.map(|x| x.into()); + for (mut a, mut b) in peak.into_iter().zip_eq(proof_peak) { + if let Some(not_empty) = not_empty { + a = gate.mul(ctx, a, not_empty); + 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); + gate.sub_mul(ctx, Constant(F::ONE), diff, diff) +} diff --git a/axiom-query/src/components/subqueries/block_header/mod.rs b/axiom-query/src/components/subqueries/block_header/mod.rs new file mode 100644 index 00000000..c038efaf --- /dev/null +++ b/axiom-query/src/components/subqueries/block_header/mod.rs @@ -0,0 +1,46 @@ +//! # Block Header Subqueries Circuit +//! +//! | 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) | ≤32 or 0 | +//! | withdrawalsRoot (post-4895) | 32 or 0 | +//! +//! Header subquery +//! - `blockNumber` (uint32) +//! - `fieldIdx` (uint32) +//! - If the `fieldIdx` corresponds to `logsBloom`, +//! the `result` will be only the first 32 bytes. +//! We will add a special `LOGS_BLOOM_FIELD_IDX` so that if +//! `fieldIdx = LOGS_BLOOM_FIELD_IDX + logsBloomIdx`, +//! the result will be bytes `[32 * logsBloomIdx, 32 * logsBloomIdx + 32)` +//! for `logsBloomIdx` in `[0, 8)`. +//! +//! **Note:** We will always truncate `extraData` to 32 bytes +//! (for Goerli `extraData` can be longer, but we ignore the extra bytes). + +/// Circuit and Component Implementation. +pub mod circuit; +/// Verify all block hashes against a given Merkle Mountain Range. Used in [circuit] +pub mod mmr_verify; +/// Types +pub mod types; + +#[cfg(test)] +pub mod tests; + +pub const MMR_MAX_NUM_PEAKS: usize = 32; // assuming block number stays in u32, < 2^32 diff --git a/axiom-query/src/components/subqueries/block_header/tests.rs b/axiom-query/src/components/subqueries/block_header/tests.rs new file mode 100644 index 00000000..fbee9cbb --- /dev/null +++ b/axiom-query/src/components/subqueries/block_header/tests.rs @@ -0,0 +1,150 @@ +use std::{collections::HashMap, fs::File, marker::PhantomData}; + +use axiom_eth::{ + block_header::{ + get_block_header_extra_bytes_from_chain_id, get_block_header_rlp_max_lens_from_extra, + EXTRA_DATA_INDEX, + }, + halo2_base::halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}, + halo2_proofs::plonk::Circuit, + keccak::{promise::generate_keccak_shards_from_calls, types::ComponentTypeKeccak}, + providers::{block::get_block_rlp_from_num, setup_provider}, + utils::{ + build_utils::pinning::{CircuitPinningInstructions, Halo2CircuitPinning}, + component::{ + promise_loader::{ + comp_loader::SingleComponentLoaderParams, single::PromiseLoaderParams, + }, + ComponentCircuit, ComponentPromiseResultsInMerkle, ComponentType, + }, + }, +}; +use ethers_core::types::{Chain, H256}; +use ethers_providers::Middleware; +use serde_json::{Result, Value}; +use test_log::test; + +use crate::components::{ + dummy_rlc_circuit_params, + subqueries::block_header::circuit::{ComponentCircuitHeaderSubquery, CoreParamsHeaderSubquery}, +}; + +use super::{ + types::{CircuitInputHeaderShard, CircuitInputHeaderSubquery}, + MMR_MAX_NUM_PEAKS, +}; + +/// Return (params, input, promise results) +fn get_test_input() -> Result<(CoreParamsHeaderSubquery, CircuitInputHeaderShard)> { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + let v: Value = serde_json::from_reader( + File::open(format!("{cargo_manifest_dir}/data/test/input_mmr_proof_for_header.json")) + .unwrap(), + )?; + let historical_mmr: Vec = serde_json::from_value(v["historicalMmr"].clone())?; + // let block_hash: H256 = serde_json::from_value(v["blockHash"].clone())?; + // let logs_bloom: Bytes = serde_json::from_value(v["logsBloom"].clone())?; + // dbg!(ðers_core::utils::hex::encode(&logs_bloom[32..64])); + let mmr_proof: Vec = serde_json::from_value(v["mmrProof"].clone())?; + let mmr = [vec![H256::zero(); 10], historical_mmr].concat(); + + let chain_id = 1; + let block_number = 9528813; + + let max_extra_data_bytes = get_block_header_extra_bytes_from_chain_id(chain_id); + let (header_rlp_max_bytes, _) = get_block_header_rlp_max_lens_from_extra(max_extra_data_bytes); + let mut mmr_proof_fixed = [H256::zero(); MMR_MAX_NUM_PEAKS - 1]; + mmr_proof_fixed[..mmr_proof.len()].copy_from_slice(&mmr_proof); + + let provider = setup_provider(Chain::Mainnet); + let mut header_rlp = get_block_rlp_from_num(&provider, block_number); + header_rlp.resize(header_rlp_max_bytes, 0); + + let requests = vec![ + CircuitInputHeaderSubquery { + header_rlp: header_rlp.clone(), + mmr_proof: mmr_proof_fixed, + field_idx: 1, + }, + CircuitInputHeaderSubquery { + header_rlp: header_rlp.clone(), + mmr_proof: mmr_proof_fixed, + field_idx: 71, + }, + CircuitInputHeaderSubquery { + header_rlp, + mmr_proof: mmr_proof_fixed, + field_idx: EXTRA_DATA_INDEX as u32, + }, + ]; + let mut mmr_fixed = [H256::zero(); MMR_MAX_NUM_PEAKS]; + mmr_fixed[..mmr.len()].copy_from_slice(&mmr); + + Ok(( + CoreParamsHeaderSubquery { max_extra_data_bytes, capacity: requests.len() }, + CircuitInputHeaderShard:: { mmr: mmr_fixed, requests, _phantom: PhantomData }, + )) +} + +#[test] +fn test_mock_header_subquery() -> anyhow::Result<()> { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + let k = 18; + let (core_builder_params, input) = get_test_input().unwrap(); + let circuit_params = dummy_rlc_circuit_params(k as usize); + let keccak_capacity = 200; + let mut circuit = ComponentCircuitHeaderSubquery::::new( + core_builder_params, + PromiseLoaderParams { + comp_loader_params: SingleComponentLoaderParams::new(3, vec![keccak_capacity]), + }, + circuit_params, + ); + circuit.feed_input(Box::new(input.clone())).unwrap(); + + let mut promise_results = HashMap::new(); + let promise_keccak = generate_keccak_shards_from_calls(&circuit, keccak_capacity)?; + serde_json::to_writer( + File::create(format!( + "{cargo_manifest_dir}/data/test/header_promise_results_keccak_for_agg.json" + ))?, + &promise_keccak, + )?; + promise_results.insert( + ComponentTypeKeccak::::get_type_id(), + ComponentPromiseResultsInMerkle::from_single_shard(promise_keccak.into_logical_results()), + ); + circuit.calculate_params(); + circuit.fulfill_promise_results(&promise_results)?; + + let instances: Vec = circuit.get_public_instances().into(); + MockProver::run(k as u32, &circuit, vec![instances]).unwrap().assert_satisfied(); + + let comp_params = circuit.params(); + + serde_json::to_writer_pretty( + File::create(format!( + "{cargo_manifest_dir}/configs/test/header_subquery_core_params.json" + ))?, + &comp_params.0, + )?; + + serde_json::to_writer_pretty( + File::create(format!( + "{cargo_manifest_dir}/configs/test/header_subquery_loader_params.json" + ))?, + &comp_params.1, + )?; + + serde_json::to_writer_pretty( + File::create(format!("{cargo_manifest_dir}/data/test/input_header_for_agg.json"))?, + &input, + )?; + circuit.pinning().write("configs/test/header_subquery.json")?; + Ok(()) +} + +pub async fn get_latest_block_number(network: Chain) -> u64 { + let provider = setup_provider(network); + provider.get_block_number().await.unwrap().as_u64() +} diff --git a/axiom-query/src/components/subqueries/block_header/types.rs b/axiom-query/src/components/subqueries/block_header/types.rs new file mode 100644 index 00000000..20603969 --- /dev/null +++ b/axiom-query/src/components/subqueries/block_header/types.rs @@ -0,0 +1,163 @@ +//! Types are separated into: +//! - Circuit metadata that along with the circuit type determines the circuit configuration completely. +//! - Human readable _logical_ input and output to the circuit. These include private inputs and outputs that are only commited to in the public output. +//! - The in-circuit formatted versions of logical inputs and outputs. These include formatting in terms of field elements and accounting for all lengths needing to be fixed at compile time. +//! - We then provide conversion functions from human-readable to circuit formats. +//! - A struct for the public instances (IO) of the circuit, excluding the circuit's own component commitment and the promise commitments from any component calls. +//! - We then specify [TryFrom] and [From] implementations to describe how to "flatten" the public instance struct into a 1d array of field elements. +use std::marker::PhantomData; + +use axiom_codec::{ + types::{field_elements::FieldHeaderSubquery, native::HeaderSubquery}, + HiLo, +}; +use axiom_eth::{ + block_header::{get_block_header_rlp_max_lens_from_extra, GENESIS_BLOCK_RLP}, + halo2_base::AssignedValue, + impl_fix_len_call_witness, + utils::{ + build_utils::dummy::DummyFrom, + component::{ + circuit::CoreBuilderInput, + types::{FixLenLogical, Flatten}, + ComponentType, ComponentTypeId, LogicalResult, + }, + }, +}; +use ethers_core::types::H256; +use serde::{Deserialize, Serialize}; + +use crate::Field; +use crate::{ + components::subqueries::common::OutputSubqueryShard, utils::codec::AssignedHeaderSubquery, +}; + +use super::{circuit::CoreParamsHeaderSubquery, MMR_MAX_NUM_PEAKS}; + +/// Identifier for the component type of this component circuit +pub struct ComponentTypeHeaderSubquery(PhantomData); + +/// Human readable +pub type OutputHeaderShard = OutputSubqueryShard; + +/// Circuit input for a shard of Header subqueries. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CircuitInputHeaderShard { + // pub(crate) chain_id: u64, + /// Merkle Mountain Range of block hashes for blocks `[0, mmr_num_blocks)`, in *increasing* order of peak size. Resized with `H256::zero()` to a fixed max length, known at compile time. + pub mmr: [H256; MMR_MAX_NUM_PEAKS], + /// Enriched subquery requests + pub requests: Vec, + pub _phantom: PhantomData, +} + +/// Circuit input for a single Header subquery. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CircuitInputHeaderSubquery { + /// The full RLP encoded block header, resized to a specified `header_rlp_max_bytes`. + /// Length is known at compile time. + pub header_rlp: Vec, + /// `mmr_proof` is a Merkle proof of this header's `block_hash` into `mmr`. + pub mmr_proof: [H256; MMR_MAX_NUM_PEAKS - 1], + pub field_idx: u32, +} + +impl DummyFrom for CircuitInputHeaderShard { + fn dummy_from(core_params: CoreParamsHeaderSubquery) -> Self { + let CoreParamsHeaderSubquery { max_extra_data_bytes, capacity } = core_params; + + let (header_rlp_max_bytes, _) = + get_block_header_rlp_max_lens_from_extra(max_extra_data_bytes); + + let mut header_rlp = GENESIS_BLOCK_RLP.to_vec(); + header_rlp.resize(header_rlp_max_bytes, 0); + let input_subquery = CircuitInputHeaderSubquery { + header_rlp, + mmr_proof: [H256::zero(); MMR_MAX_NUM_PEAKS - 1], + field_idx: 0, + }; + + CircuitInputHeaderShard { + mmr: [H256::zero(); MMR_MAX_NUM_PEAKS], + requests: vec![input_subquery; capacity], + _phantom: PhantomData, + } + } +} + +/// The output value of any header subquery is always `bytes32` right now. +/// Vector has been resized to the capacity. +pub type CircuitOutputHeaderShard = OutputSubqueryShard, HiLo>; + +impl_fix_len_call_witness!( + FieldHeaderSubqueryCall, + FieldHeaderSubquery, + ComponentTypeHeaderSubquery +); + +/// Size in bits of public instances, excluding component commitments +const BITS_PER_PUBLIC_INSTANCE: [usize; 2] = [128, 128]; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LogicalPublicInstanceHeader { + pub mmr_keccak: HiLo, +} + +impl ComponentType for ComponentTypeHeaderSubquery { + type InputValue = FieldHeaderSubquery; + type InputWitness = AssignedHeaderSubquery; + type OutputValue = HiLo; + type OutputWitness = HiLo>; + type LogicalInput = FieldHeaderSubquery; + + fn get_type_id() -> ComponentTypeId { + "axiom-query:ComponentTypeHeaderSubquery".to_string() + } + + fn logical_result_to_virtual_rows_impl( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + vec![(ins.input, ins.output)] + } + fn logical_input_to_virtual_rows_impl(li: &Self::LogicalInput) -> Vec { + vec![*li] + } +} + +impl From for CircuitOutputHeaderShard { + fn from(output: OutputHeaderShard) -> Self { + output.convert_into() + } +} + +// ============== LogicalPublicInstanceHeader ============== +impl TryFrom> for LogicalPublicInstanceHeader { + type Error = anyhow::Error; + + fn try_from(value: Vec) -> Result { + if value.len() != BITS_PER_PUBLIC_INSTANCE.len() { + return Err(anyhow::anyhow!("incorrect length")); + } + Ok(Self { mmr_keccak: HiLo::from_hi_lo([value[0], value[1]]) }) + } +} + +impl TryFrom> for LogicalPublicInstanceHeader { + type Error = anyhow::Error; + + fn try_from(value: Flatten) -> Result { + if value.field_size != BITS_PER_PUBLIC_INSTANCE { + return Err(anyhow::anyhow!("invalid field size")); + } + value.fields.try_into() + } +} +impl From> for Flatten { + fn from(val: LogicalPublicInstanceHeader) -> Self { + Flatten { fields: val.mmr_keccak.hi_lo().to_vec(), field_size: &BITS_PER_PUBLIC_INSTANCE } + } +} +impl FixLenLogical for LogicalPublicInstanceHeader { + fn get_field_size() -> &'static [usize] { + &BITS_PER_PUBLIC_INSTANCE + } +} diff --git a/axiom-query/src/components/subqueries/common.rs b/axiom-query/src/components/subqueries/common.rs new file mode 100644 index 00000000..0a439dba --- /dev/null +++ b/axiom-query/src/components/subqueries/common.rs @@ -0,0 +1,96 @@ +use axiom_codec::types::field_elements::AnySubqueryResult; +use axiom_eth::{ + halo2_base::AssignedValue, + utils::component::{ + types::{FixLenLogical, Flatten}, + utils::get_logical_value, + ComponentPromiseResultsInMerkle, ComponentType, FlattenVirtualTable, LogicalResult, + }, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::Field; + +/// Generic type for output of a subquery shard circuit +#[derive(Clone, Default, Debug, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OutputSubqueryShard { + /// Vector is assumed to be resized to the capacity. + pub results: Vec>, +} + +impl OutputSubqueryShard { + pub fn len(&self) -> usize { + self.results.len() + } + + pub fn is_empty(&self) -> bool { + self.results.is_empty() + } + + pub fn into_flattened_pairs(self) -> Vec<(Flatten, Flatten)> + where + I: Into>, + O: Into>, + T: Copy, + { + self.results.into_iter().map(|r| (r.subquery.into(), r.value.into())).collect() + } + + // cannot do blanket From implementation because it conflicts with Rust std T: From impl + pub fn convert_into(self) -> OutputSubqueryShard + where + I: Into, + O: Into, + { + OutputSubqueryShard { + results: self + .results + .into_iter() + .map(|r| AnySubqueryResult { subquery: r.subquery.into(), value: r.value.into() }) + .collect(), + } + } +} + +/// Helper function to convert OutputSubqueryShard into ComponentPromiseResults +pub fn shard_into_component_promise_results>( + shard: OutputSubqueryShard, +) -> ComponentPromiseResultsInMerkle { + ComponentPromiseResultsInMerkle::from_single_shard( + shard + .results + .into_iter() + .map(|r| LogicalResult::::new(r.subquery, r.value)) + .collect_vec(), + ) +} + +pub(crate) fn extract_virtual_table< + F: Field, + S: Into>>, + T: Into>>, +>( + outputs: impl Iterator>, +) -> FlattenVirtualTable> { + outputs.map(|output| (output.subquery.into(), output.value.into())).collect() +} + +pub(crate) fn extract_logical_results< + F: Field, + S: FixLenLogical>, + FS: FixLenLogical, + T: ComponentType, +>( + outputs: impl Iterator>, +) -> Vec> { + outputs + .map(|output| { + LogicalResult::::new( + get_logical_value(&output.subquery), + get_logical_value(&output.value), + ) + }) + .collect() +} diff --git a/axiom-query/src/components/subqueries/mod.rs b/axiom-query/src/components/subqueries/mod.rs new file mode 100644 index 00000000..884aafd5 --- /dev/null +++ b/axiom-query/src/components/subqueries/mod.rs @@ -0,0 +1,8 @@ +pub mod account; +pub mod block_header; +/// Types common to all subqueries +pub mod common; +pub mod receipt; +pub mod solidity_mappings; +pub mod storage; +pub mod transaction; diff --git a/axiom-query/src/components/subqueries/receipt/circuit.rs b/axiom-query/src/components/subqueries/receipt/circuit.rs new file mode 100644 index 00000000..1e4e05c9 --- /dev/null +++ b/axiom-query/src/components/subqueries/receipt/circuit.rs @@ -0,0 +1,568 @@ +use std::iter::zip; + +use axiom_codec::{ + constants::FIELD_IDX_BITS, + encoder::field_elements::SUBQUERY_OUTPUT_BYTES, + special_values::{ + RECEIPT_ADDRESS_IDX, RECEIPT_BLOCK_NUMBER_FIELD_IDX, RECEIPT_DATA_IDX_OFFSET, + RECEIPT_LOGS_BLOOM_IDX_OFFSET, RECEIPT_LOG_IDX_OFFSET, RECEIPT_TX_INDEX_FIELD_IDX, + RECEIPT_TX_TYPE_FIELD_IDX, + }, + HiLo, +}; +use axiom_eth::{ + block_header::RECEIPT_ROOT_INDEX, + halo2_base::{ + gates::{flex_gate::threads::parallelize_core, GateInstructions, RangeInstructions}, + safe_types::{SafeBool, SafeByte, SafeTypeChip, VarLenBytesVec}, + utils::bit_length, + AssignedValue, Context, + QuantumCell::Constant, + }, + halo2_proofs::plonk::ConstraintSystem, + keccak::{types::ComponentTypeKeccak, KeccakChip}, + mpt::MPTChip, + receipt::{ + EthReceiptChip, EthReceiptChipParams, EthReceiptLogFieldWitness, EthReceiptLogWitness, + EthReceiptWitness, RECEIPT_NUM_FIELDS, + }, + rlc::circuit::builder::RlcCircuitBuilder, + rlc::circuit::builder::RlcContextPair, + rlp::RlpChip, + utils::{ + build_utils::aggregation::CircuitMetadata, + circuit_utils::{ + bytes::{pack_bytes_to_hilo, select_hi_lo_by_indicator, unsafe_mpt_root_to_hi_lo}, + extract_array_chunk_and_constrain_trailing_zeros, is_equal_usize, is_gte_usize, + is_lt_usize, min_with_usize, unsafe_constrain_trailing_zeros, + }, + component::{ + circuit::{ + ComponentBuilder, ComponentCircuitImpl, CoreBuilder, CoreBuilderOutput, + CoreBuilderOutputParams, CoreBuilderParams, + }, + promise_collector::PromiseCaller, + promise_loader::{combo::PromiseBuilderCombo, single::PromiseLoader}, + types::LogicalEmpty, + utils::create_hasher, + LogicalResult, + }, + constrain_vec_equal, is_zero_vec, unsafe_bytes_to_assigned, + }, +}; +use itertools::{zip_eq, Itertools}; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::subqueries::{ + block_header::{ + circuit::handle_logs_bloom, + types::{ComponentTypeHeaderSubquery, FieldHeaderSubqueryCall}, + }, + common::{extract_logical_results, extract_virtual_table}, + }, + utils::codec::{ + AssignedHeaderSubquery, AssignedReceiptSubquery, AssignedReceiptSubqueryResult, + }, + Field, +}; + +use super::{ + types::{CircuitInputReceiptShard, CircuitInputReceiptSubquery, ComponentTypeReceiptSubquery}, + DUMMY_LOG, +}; + +/// The fieldIdx for cumulativeGas +const CUMULATIVE_GAS_FIELD_IDX: usize = 2; + +pub struct CoreBuilderReceiptSubquery { + input: Option>, + params: CoreParamsReceiptSubquery, + payload: Option<(KeccakChip, Vec>)>, +} + +/// Specify the output format of ReceiptSubquery component. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct CoreParamsReceiptSubquery { + pub chip_params: EthReceiptChipParams, + /// The maximum number of subqueries of this type allowed in a single circuit. + pub capacity: usize, + /// The maximum depth of the receipt MPT trie supported by this circuit. + /// The depth is defined as the maximum length of a Merkle proof, where the proof always ends in a terminal node (if the proof ends in a branch, we extract the leaf and add it as a separate node). + /// + /// In practice this can always be set to 6, because + /// transaction index is within u16, so rlp(txIndex) is at most 3 bytes => 6 nibbles. + pub max_trie_depth: usize, +} +impl CoreBuilderParams for CoreParamsReceiptSubquery { + fn get_output_params(&self) -> CoreBuilderOutputParams { + CoreBuilderOutputParams::new(vec![self.capacity]) + } +} + +type CKeccak = ComponentTypeKeccak; +type CHeader = ComponentTypeHeaderSubquery; +/// Used for loading receipt subquery promise results. +pub type PromiseLoaderReceiptSubquery = + PromiseBuilderCombo>, PromiseLoader>>; +pub type ComponentCircuitReceiptSubquery = + ComponentCircuitImpl, PromiseLoaderReceiptSubquery>; + +impl CircuitMetadata for CoreBuilderReceiptSubquery { + const HAS_ACCUMULATOR: bool = false; + fn num_instance(&self) -> Vec { + unreachable!() + } +} + +impl ComponentBuilder for CoreBuilderReceiptSubquery { + type Params = CoreParamsReceiptSubquery; + + fn new(params: Self::Params) -> Self { + Self { input: None, params, payload: None } + } + fn get_params(&self) -> Self::Params { + self.params.clone() + } + fn clear_witnesses(&mut self) { + self.payload = None; + } + fn calculate_params(&mut self) -> Self::Params { + self.params.clone() + } + fn configure_with_params(_: &mut ConstraintSystem, _: Self::Params) {} +} + +impl CoreBuilder for CoreBuilderReceiptSubquery { + type CompType = ComponentTypeReceiptSubquery; + type PublicInstanceValue = LogicalEmpty; + type PublicInstanceWitness = LogicalEmpty>; + type CoreInput = CircuitInputReceiptShard; + + fn feed_input(&mut self, input: Self::CoreInput) -> anyhow::Result<()> { + self.input = Some(input); + Ok(()) + } + /// Includes computing the component commitment to the logical output (the subquery results). + /// **In addition** performs _promise calls_ to the Header Component to verify + /// all `(block_number, receipts_root)` pairs as additional "enriched" header subqueries. + /// These are checked against the supplied promise commitment using dynamic lookups + /// (behind the scenes) by `promise_caller`. + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_caller: PromiseCaller, + ) -> CoreBuilderOutput { + // preamble: to be removed + let keccak = + KeccakChip::new_with_promise_collector(builder.range_chip(), promise_caller.clone()); + let range_chip = keccak.range(); + let rlp = RlpChip::new(range_chip, None); + let mut poseidon = create_hasher(); + poseidon.initialize_consts(builder.base.main(0), keccak.gate()); + + // Assumption: we already have input when calling this function. + // TODO: automatically derive a dummy input from params. + let input = self.input.as_ref().unwrap(); + + let mpt = MPTChip::new(rlp, &keccak); + let chip = EthReceiptChip::new(&mpt, self.params.chip_params); + let base_builder = &mut builder.base; + // actual logic + let payload = + parallelize_core(base_builder.pool(0), input.requests.clone(), |ctx, subquery| { + handle_single_receipt_subquery_phase0(ctx, &chip, &subquery) + }); + + let vt = extract_virtual_table(payload.iter().map(|p| p.output)); + let lr: Vec> = + extract_logical_results(payload.iter().map(|p| p.output)); + + let ctx = base_builder.main(0); + // promise calls to header component: + // - for each block number in a subquery, we must make a promise call to check the transaction root of that block + let header_rc_root_idx = ctx.load_constant(F::from(RECEIPT_ROOT_INDEX as u64)); + for p in payload.iter() { + let block_number = p.output.subquery.block_number; + let rc_root = p.rc_root; + let header_subquery = + AssignedHeaderSubquery { block_number, field_idx: header_rc_root_idx }; + let promise_rc_root = promise_caller + .call::, ComponentTypeHeaderSubquery>( + ctx, + FieldHeaderSubqueryCall(header_subquery), + ) + .unwrap(); + constrain_vec_equal(ctx, &rc_root.hi_lo(), &promise_rc_root.hi_lo()); + } + self.payload = Some((keccak, payload)); + CoreBuilderOutput { public_instances: vec![], virtual_table: vt, logical_results: lr } + } + + fn virtual_assign_phase1(&mut self, builder: &mut RlcCircuitBuilder) { + let (keccak, payload) = self.payload.take().unwrap(); + // preamble + let range_chip = keccak.range(); + let rlc_chip = builder.rlc_chip(&range_chip.gate); + let rlp = RlpChip::new(range_chip, Some(&rlc_chip)); + let mpt = MPTChip::new(rlp, &keccak); + let chip = EthReceiptChip::new(&mpt, self.params.chip_params); + + // actual logic + builder.parallelize_phase1(payload, |(ctx_gate, ctx_rlc), payload| { + handle_single_receipt_subquery_phase1((ctx_gate, ctx_rlc), &chip, payload) + }); + } +} + +pub struct PayloadReceiptSubquery { + pub rc_witness: EthReceiptWitness, + pub parsed_log_witness: EthReceiptLogFieldWitness, + pub rc_root: HiLo>, + pub output: AssignedReceiptSubqueryResult, +} + +/// Assigns `subquery` to virtual cells and then handles the subquery to get result. +/// **Assumes** that the receiptsRoot is verified. Returns the assigned private witnesses of +/// `(block_number, receiptsRoot)`, to be looked up against Header Component promise. +pub fn handle_single_receipt_subquery_phase0( + ctx: &mut Context, + chip: &EthReceiptChip, + subquery: &CircuitInputReceiptSubquery, +) -> PayloadReceiptSubquery { + assert_eq!(chip.params.topic_num_bounds, (0, 4), "Always support all topics"); + let gate = chip.gate(); + let range = chip.range(); + // assign rc proof + let rc_proof = subquery.proof.clone().assign(ctx); + // convert receiptsRoot from bytes to HiLo for later. `parse_receipt_proof` will constrain these witnesses to be bytes + let rc_root = unsafe_mpt_root_to_hi_lo(ctx, gate, &rc_proof.proof); + // Check the receipt MPT proof + let rc_witness = chip.parse_receipt_proof_phase0(ctx, rc_proof); + gate.assert_is_const(ctx, &rc_witness.mpt_witness().slot_is_empty, &F::ZERO); // ensure slot is not empty + + let field_or_log_idx = ctx.load_witness(F::from(subquery.field_or_log_idx as u64)); + range.range_check(ctx, field_or_log_idx, FIELD_IDX_BITS); + let log_threshold = Constant(F::from(RECEIPT_LOG_IDX_OFFSET as u64)); + // if `field_idx` < `RECEIPT_NUM_FIELDS`, then it is an actual tx rlp list item. Note even though `field_idx` has both postState and status, we **do not** allow `field_idx = 4` for logs. That is what `is_log_idx` is for. + let is_idx_in_list = + is_lt_usize(ctx, range, field_or_log_idx, RECEIPT_NUM_FIELDS, FIELD_IDX_BITS); + // The cumulativeGas field is always in a receipt, regardless of EIP-658, whereas we only allow fieldIdx = 0 (status) if the block is after EIP-658 or allow fieldIdx = 1 (postState) if the block is before EIP-658 + let field_idx = gate.select( + ctx, + field_or_log_idx, + Constant(F::from(CUMULATIVE_GAS_FIELD_IDX as u64)), + is_idx_in_list, + ); + // if `field_idx` >= `RECEIPT_LOG_IDX_OFFSET`, then we want a log + // must be log_idx if field_or_log_idx >= RECEIPT_LOG_IDX_OFFSET + let is_log_idx = + is_gte_usize(ctx, range, field_or_log_idx, RECEIPT_LOG_IDX_OFFSET, FIELD_IDX_BITS); + // log_idx = field_or_log_idx - RECEIPT_LOG_IDX_OFFSET, wrapping sub + let mut log_idx = gate.sub(ctx, field_or_log_idx, log_threshold); + log_idx = gate.mul(ctx, log_idx, is_log_idx); + let num_logs = rc_witness.logs.list_len.expect("logs are var len"); + let is_valid_log_idx = range.is_less_than(ctx, log_idx, num_logs, FIELD_IDX_BITS); + let is_log_idx = SafeTypeChip::unsafe_to_bool(gate.and(ctx, is_log_idx, is_valid_log_idx)); + let log_idx = gate.mul(ctx, log_idx, is_log_idx); + + let tx_type = rc_witness.receipt_type; + + let rc_field_bytes = + extract_truncated_field(ctx, range, &rc_witness, field_idx, SUBQUERY_OUTPUT_BYTES); + + let logs_bloom_bytes = &rc_witness.value().field_witness[2].field_cells; + let (logs_bloom_value, is_logs_bloom_idx) = handle_logs_bloom( + ctx, + range, + logs_bloom_bytes, + field_or_log_idx, + RECEIPT_LOGS_BLOOM_IDX_OFFSET, + ); + + // === begin process logs === + // tda = topic_or_data_or_address; too much to type + let tda_idx = ctx.load_witness(F::from(subquery.topic_or_data_or_address_idx as u64)); + range.range_check(ctx, tda_idx, FIELD_IDX_BITS); + let is_topic = is_lt_usize(ctx, range, tda_idx, 4, FIELD_IDX_BITS); + let mut is_topic = gate.and(ctx, is_topic, is_log_idx); + let data_threshold = Constant(F::from(RECEIPT_DATA_IDX_OFFSET as u64)); + let is_data_idx = is_gte_usize(ctx, range, tda_idx, RECEIPT_DATA_IDX_OFFSET, FIELD_IDX_BITS); + let mut is_data_idx = gate.and(ctx, is_data_idx, is_log_idx); + let topic_idx = gate.mul(ctx, tda_idx, is_topic); + let data_idx = gate.sub(ctx, tda_idx, data_threshold); + let data_idx = gate.mul(ctx, data_idx, is_data_idx); + + let log = chip.extract_receipt_log(ctx, &rc_witness, log_idx); + let log_witness = conditional_parse_log_phase0(ctx, chip, log, is_log_idx); + // Get 32 bytes from data + let (data_bytes, is_valid_data) = + extract_data_section(ctx, range, &log_witness, data_idx, SUBQUERY_OUTPUT_BYTES); + is_data_idx = gate.and(ctx, is_data_idx, is_valid_data); + // Get the address + let addr = log_witness.address().to_vec(); + // Select the topic + let topics_bytes = log_witness.topics_bytes(); + assert_eq!(topics_bytes.len(), 4); + let topic_indicator = gate.idx_to_indicator(ctx, topic_idx, 4); + let topic = gate.select_array_by_indicator(ctx, &topics_bytes, &topic_indicator); + let is_valid_topic = + range.is_less_than(ctx, topic_idx, log_witness.num_topics(), FIELD_IDX_BITS); + is_topic = gate.and(ctx, is_topic, is_valid_topic); + // ---- event schema ---- + // if event_schema != bytes32(0) and `is_log_idx`, then we constrain `topic[0] == event_schema` + let event_schema = unsafe_bytes_to_assigned(ctx, subquery.event_schema.as_bytes()); + let no_constrain_event = is_zero_vec(ctx, gate, &event_schema); + let event_diff = + zip_eq(&topics_bytes[0], &event_schema).map(|(&a, &b)| gate.sub(ctx, a, b)).collect_vec(); + let mut event_eq = is_zero_vec(ctx, gate, &event_diff); + event_eq = gate.and(ctx, event_eq, is_log_idx); + let valid_event = gate.or(ctx, no_constrain_event, event_eq); + gate.assert_is_const(ctx, &valid_event, &F::ONE); + // ==== end process logs ==== + + let [is_tx_type, is_block_num, is_tx_idx] = + [RECEIPT_TX_TYPE_FIELD_IDX, RECEIPT_BLOCK_NUMBER_FIELD_IDX, RECEIPT_TX_INDEX_FIELD_IDX] + .map(|x| is_equal_usize(ctx, gate, field_or_log_idx, x)); + let is_addr = is_equal_usize(ctx, gate, tda_idx, RECEIPT_ADDRESS_IDX); + let is_addr = gate.and(ctx, is_addr, is_log_idx); + + let safe = SafeTypeChip::new(range); + let value_indicator = vec![ + is_idx_in_list.into(), + is_tx_type.into(), + is_block_num.into(), + is_tx_idx.into(), + is_logs_bloom_idx.into(), + is_topic, + is_addr, + is_data_idx, + ]; + // it must be exactly one of the above cases + let idx_check = gate.sum(ctx, value_indicator.clone()); + gate.assert_is_const(ctx, &idx_check, &F::ONE); + + let block_number = ctx.load_witness(F::from(subquery.block_number)); + let tx_idx = rc_witness.tx_idx; + let field_hilo = prep_field(ctx, gate, rc_field_bytes, field_idx); + let const_zero = ctx.load_zero(); + let from_lo = |lo| HiLo::from_hi_lo([const_zero, lo]); + + // unsafe because rlp has already constrained these to be bytes + let topic = SafeTypeChip::unsafe_to_fix_len_bytes_vec(topic, 32); + let addr = SafeTypeChip::unsafe_to_fix_len_bytes_vec(addr, 20); + let topic_hilo = pack_bytes_to_hilo(ctx, gate, topic.bytes()); + let addr_hilo = pack_bytes_to_hilo(ctx, gate, addr.bytes()); + let data_hilo = pack_bytes_to_hilo(ctx, gate, &data_bytes); + let hilos = vec![ + field_hilo, + from_lo(tx_type), + from_lo(block_number), + from_lo(tx_idx), + logs_bloom_value, + topic_hilo, + addr_hilo, + data_hilo, + ]; + let value = select_hi_lo_by_indicator(ctx, gate, &hilos, value_indicator); + // dbg!(value.hi_lo().map(|v| *v.value())); + let event_schema = safe.raw_to_fix_len_bytes_vec(ctx, event_schema, 32); + let event_schema = pack_bytes_to_hilo(ctx, gate, event_schema.bytes()); + PayloadReceiptSubquery { + rc_witness, + rc_root, + parsed_log_witness: log_witness, + output: AssignedReceiptSubqueryResult { + subquery: AssignedReceiptSubquery { + block_number, + tx_idx, + field_or_log_idx, + topic_or_data_or_address_idx: tda_idx, + event_schema, + }, + value, + }, + } +} + +pub fn handle_single_receipt_subquery_phase1( + (ctx_gate, ctx_rlc): RlcContextPair, + chip: &EthReceiptChip, + payload: PayloadReceiptSubquery, +) { + chip.parse_receipt_proof_phase1((ctx_gate, ctx_rlc), payload.rc_witness); + conditional_parse_log_phase1((ctx_gate, ctx_rlc), chip, payload.parsed_log_witness); +} + +/// Extracts the field at `field_idx` from the given rlp list decomposition of a transaction. +/// The field is truncated to the first `truncated_byte_len` bytes. +/// +/// We do not use `EthReceiptChip::extract_field` because without the truncation the +/// select operation can be very expensive if the `data` field is very long. +/// +/// We **ignore** `field_idx = 4` (logs) because it is handled separately. +pub fn extract_truncated_field( + ctx: &mut Context, + range: &impl RangeInstructions, + witness: &EthReceiptWitness, + field_idx: AssignedValue, + truncated_byte_len: usize, +) -> VarLenBytesVec { + let gate = range.gate(); + let rc_values = &witness.value().field_witness; + assert_eq!(rc_values.len(), RECEIPT_NUM_FIELDS); + let rc_values = &rc_values[..RECEIPT_NUM_FIELDS - 1]; + // | ReceiptField | `fieldIdx` | + // |------------------------|-------| + // | Status | 0 | + // | PostState | 1 | + // | CumulativeGas | 2 | + // | LogsBloom | 3 | + // | Logs | 4 | + // while the actual list index is: + // + // | `listIdx` | State Field | Type | Bytes | RLP Size (Bytes) | RLP Size (Bits) | + // | --------- | -------------- | --------- | -------- | -------- | -------- | + // | 0 | PostState | bytes32 | 32 | 33 | 264 | + // | 0 | Status | uint64 | $\leq 1$ | $\leq 33$ | $\leq 264$ | + // | 1 | Cumulative Gas | uint256 | $\leq 32$ | $\leq 33$ | $\leq 264$ | + // | 2 | Log Blooms | Bytes | 256 | 259 | 2072 | + // | 3 | Logs | List of Logs | variable | variable | variable | + // + // Before EIP-658, receipts hold the PostState hash (the intermediate state root hash) instead of the Status. + let get_status = gate.is_zero(ctx, field_idx); + let offset = gate.not(ctx, get_status); + let list_idx = gate.sub(ctx, field_idx, offset); + let indicator = gate.idx_to_indicator(ctx, list_idx, RECEIPT_NUM_FIELDS - 1); + let const_zero = ctx.load_zero(); + let mut field_bytes = (0..truncated_byte_len) + .map(|i| { + let entries = rc_values.iter().map(|w| *w.field_cells.get(i).unwrap_or(&const_zero)); + gate.select_by_indicator(ctx, entries, indicator.clone()) + }) + .collect_vec(); + let lens = rc_values.iter().map(|w| w.field_len); + let mut len = gate.select_by_indicator(ctx, lens, indicator); + // len = min(len, truncated_byte_len) + let max_bytes = rc_values.iter().map(|w| w.field_cells.len()).max().unwrap(); + let max_bits = bit_length(max_bytes as u64); + len = min_with_usize(ctx, range, len, truncated_byte_len, max_bits); + + unsafe_constrain_trailing_zeros(ctx, gate, &mut field_bytes, len); + + // constrain that postState is 32 bytes and status is less than 32 bytes + let is_post_state_or_status = gate.is_zero(ctx, list_idx); + let is_small = range.is_less_than_safe(ctx, len, 32); + // if is_post_state_or_status, then is_small and get_status must match + let diff = gate.sub(ctx, is_small, get_status); + let status_check = gate.mul(ctx, is_post_state_or_status, diff); + ctx.constrain_equal(&status_check, &const_zero); + + SafeTypeChip::unsafe_to_var_len_bytes_vec(field_bytes, len, truncated_byte_len) +} + +fn prep_field( + ctx: &mut Context, + gate: &impl GateInstructions, + field_bytes: VarLenBytesVec, + field_idx: AssignedValue, +) -> HiLo> { + let left_pad_indicator = [true, false, true, false, false].map(F::from).map(Constant); + let field_fixed = field_bytes.left_pad_to_fixed(ctx, gate); + let left_pad = gate.select_from_idx(ctx, left_pad_indicator, field_idx); + let value = zip(field_bytes.bytes(), field_fixed.bytes()) + .map(|(var, fixed)| gate.select(ctx, *fixed, *var, left_pad)) + .collect_vec(); + let value = SafeTypeChip::unsafe_to_fix_len_bytes_vec(value, SUBQUERY_OUTPUT_BYTES); + pack_bytes_to_hilo(ctx, gate, value.bytes()) +} + +/// Extracts a chunk of `log_data[data_idx * chunk_size.. (data_idx + 1) * chunk_size]` +/// and constrains trailing zeros. +/// Returns a flag indicating whether `data_idx * chunk_size < data_len`. +/// +/// Note: select operation can be very expensive if the `data` field is very long. +pub fn extract_data_section( + ctx: &mut Context, + range: &impl RangeInstructions, + witness: &EthReceiptLogFieldWitness, + data_idx: AssignedValue, + chunk_size: usize, +) -> (Vec>, SafeBool) { + let (chunk, is_valid) = extract_array_chunk_and_constrain_trailing_zeros( + ctx, + range, + witness.data_bytes(), + witness.data_len(), + data_idx, + chunk_size, + FIELD_IDX_BITS, + ); + let chunk = chunk.into_iter().map(SafeTypeChip::unsafe_to_byte).collect(); + (chunk, is_valid) +} + +/// The `witness` might not have a valid log. +/// When `parse_log_flag` is false, we parse a dummy log. +/// +/// # Assumptions +/// - When `parse_log_flag` is true, `witness` has a valid log. +pub fn conditional_parse_log_phase0( + ctx: &mut Context, + chip: &EthReceiptChip, + mut witness: EthReceiptLogWitness, + parse_log_flag: SafeBool, +) -> EthReceiptLogFieldWitness { + let gate = chip.gate(); + let log = &mut witness.log_bytes; + // we zip here because the RLP will parse based on the prefix so it should not matter what dummy values are beyond `DUMMY_LOG.len()` + for (byte, dummy_byte) in log.iter_mut().zip(DUMMY_LOG) { + let dummy_byte = F::from(dummy_byte as u64); + *byte = gate.select(ctx, *byte, Constant(dummy_byte), parse_log_flag); + } + parse_log_phase0(ctx, chip, witness) +} + +pub fn conditional_parse_log_phase1( + (ctx_gate, ctx_rlc): RlcContextPair, + chip: &EthReceiptChip, + witness: EthReceiptLogFieldWitness, +) { + parse_log_phase1((ctx_gate, ctx_rlc), chip, witness); +} + +/// ### Log Fields +/// +/// | State Field | Type | Bytes | RLP Size (Bytes) | RLP Size (Bits) | +/// | -------- | -------- | -------- | -------- | -------- | +/// | Address | address hash | 20 | 21 | 168 | +/// | List of 0 to 4 Topics | bytes32 | 32 | 33 | 264 | +/// | data | Bytes | variable | variable | variable | +pub fn parse_log_phase0( + ctx_gate: &mut Context, + chip: &EthReceiptChip, + witness: EthReceiptLogWitness, +) -> EthReceiptLogFieldWitness { + let (_, max_topics) = chip.params.topic_num_bounds; // in practice this will always be 4 + let max_data_byte_len = chip.params.max_data_byte_len; + let field_lengths = [20, max_topics * 33 + 3, max_data_byte_len]; + let log_list = + chip.rlp().decompose_rlp_array_phase0(ctx_gate, witness.log_bytes, &field_lengths, false); + let topics = log_list.field_witness[1].clone(); + let topics_list = chip.rlp().decompose_rlp_array_phase0( + ctx_gate, + topics.encoded_item, + &vec![32; max_topics], + true, + ); + EthReceiptLogFieldWitness { log_list, topics_list } +} + +pub fn parse_log_phase1( + (ctx_gate, ctx_rlc): RlcContextPair, + chip: &EthReceiptChip, + witness: EthReceiptLogFieldWitness, +) { + chip.rlp().decompose_rlp_array_phase1((ctx_gate, ctx_rlc), witness.log_list, false); + chip.rlp().decompose_rlp_array_phase1((ctx_gate, ctx_rlc), witness.topics_list, true); +} diff --git a/axiom-query/src/components/subqueries/receipt/mod.rs b/axiom-query/src/components/subqueries/receipt/mod.rs new file mode 100644 index 00000000..af7591ee --- /dev/null +++ b/axiom-query/src/components/subqueries/receipt/mod.rs @@ -0,0 +1,38 @@ +/*! +# Receipt Subqueries Circuit + +Receipt +* Raw EVM receipt layout reference: +* `blockNumber` (uint32) +* `txIdx` (uint16) +* `fieldOrLogIdx` (uint32) + * If in `[0, 4)` -- refers to the field. + * If the `fieldIdx = 3` corresponds to `logsBloom`, the `result` will be only the first 32 bytes. We will add a special `LOGS_BLOOM_FIELD_IDX` (70) so that if `fieldIdx = LOGS_BLOOM_FIELD_IDX + logsBloomIdx`, the result will be bytes `[32 * logsBloomIdx, 32 * logsBloomIdx + 32)` for `logsBloomIdx` in `[0, 8)`. + * The `fieldIdx` is defined as an enum: + | ReceiptField | `fieldIdx` | + |------------------------|-------| + | Status | 0 | + | PostState | 1 | + | CumulativeGas | 2 | + | LogsBloom | 3 | + | Logs | 4 | + * If in `[100, infty)` -- represents `100 + logIdx` + * As with Transaction, we will have a special `RECEIPT_TX_TYPE_FIELD_IDX` (51) for transaction type, `RECEIPT_BLOCK_NUMBER_FIELD_IDX` (52) for block number, and `RECEIPT_TX_IDX_FIELD_IDX` (53) for transaction index. + * [Nice to have **(not yet supported)**] We will later add a special `RECEIPT_TX_HASH_FIELD_IDX` (54) for the transaction hash. +* `topicOrDataOrAddressIdx` (uint32) + * If in `[0, 4)` -- refers to the topic. + * If equal to `50` -- refers to the address. + * If in `[100, infty)` -- represents `100 + dataIdx`, where for a given value of `dataIdx` we return bytes `[32 * dataIdx, 32 * dataIdx + 32)` of the data. This byte alignment is to return chunks of the ABI encoding. +* `eventSchema` (bytes32) -- Either `bytes32(0x0)` in which case it is a no-op, or the query **must** have `fieldOrLogIdx` in `[100, infty)` and constrains `topic[0]` of the log to equal `eventSchema`. + +*/ + +/// Circuit and Component Implementation. +pub mod circuit; +/// Types +pub mod types; + +#[cfg(test)] +pub mod tests; + +const DUMMY_LOG: [u8; 4] = [0xc3, 0x80, 0xc0, 0x80]; diff --git a/axiom-query/src/components/subqueries/receipt/tests.rs b/axiom-query/src/components/subqueries/receipt/tests.rs new file mode 100644 index 00000000..6f4bb452 --- /dev/null +++ b/axiom-query/src/components/subqueries/receipt/tests.rs @@ -0,0 +1,331 @@ +use std::{marker::PhantomData, str::FromStr}; + +use anyhow::anyhow; +use axiom_codec::{ + special_values::{ + RECEIPT_BLOCK_NUMBER_FIELD_IDX, RECEIPT_DATA_IDX_OFFSET, RECEIPT_LOGS_BLOOM_IDX_OFFSET, + RECEIPT_LOG_IDX_OFFSET, RECEIPT_TOPIC_IDX_OFFSET, RECEIPT_TX_INDEX_FIELD_IDX, + RECEIPT_TX_TYPE_FIELD_IDX, + }, + types::{ + field_elements::AnySubqueryResult, + native::{HeaderSubquery, ReceiptSubquery}, + }, +}; +use axiom_eth::{ + block_header::RECEIPT_ROOT_INDEX, + halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}, + keccak::{promise::generate_keccak_shards_from_calls, types::ComponentTypeKeccak}, + mpt::MPTInput, + providers::{ + receipt::{construct_rc_tries_from_full_blocks, get_block_with_receipts}, + setup_provider, + transaction::get_tx_key_from_index, + }, + receipt::{calc_max_val_len, EthReceiptChipParams, EthReceiptInput}, + utils::component::{ + promise_loader::single::PromiseLoaderParams, ComponentCircuit, + ComponentPromiseResultsInMerkle, ComponentType, + }, +}; +use cita_trie::Trie; +use ethers_core::types::{Chain, H256}; +use ethers_providers::Middleware; +use futures::future::join_all; +use itertools::Itertools; +use serde::Serialize; +use tokio; + +use crate::components::{ + dummy_rlc_circuit_params, + subqueries::{ + block_header::types::{ComponentTypeHeaderSubquery, OutputHeaderShard}, + common::shard_into_component_promise_results, + }, +}; + +use super::{ + circuit::{ComponentCircuitReceiptSubquery, CoreParamsReceiptSubquery}, + types::{CircuitInputReceiptShard, CircuitInputReceiptSubquery}, +}; + +/// transaction index is within u16, so rlp(txIndex) is at most 3 bytes => 6 nibbles +pub const RECEIPT_PROOF_MAX_DEPTH: usize = 6; + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct Params { + block_number: String, +} + +#[derive(Serialize)] +struct Request { + id: u8, + jsonrpc: String, + method: String, + params: Vec, +} + +async fn test_mock_receipt_subqueries( + k: u32, + network: Chain, + subqueries: Vec<(&str, usize, usize, &str)>, // txHash, field_or_log_idx, topic_or_data_or_address_idx, event_schema + max_data_byte_len: usize, + max_log_num: usize, +) -> ComponentCircuitReceiptSubquery { + let _ = env_logger::builder().is_test(true).try_init(); + + let _provider = setup_provider(network); + let provider = &_provider; + let requests = join_all(subqueries.into_iter().map( + |(tx_hash, field_idx, tda_idx, event_schema)| async move { + let tx_hash = H256::from_str(tx_hash).unwrap(); + let event_schema = H256::from_str(event_schema).unwrap(); + let tx = provider.get_transaction(tx_hash).await.unwrap().unwrap(); + // let rc = provider.get_transaction_receipt(tx_hash).await.unwrap().unwrap(); + // dbg!(rc.logs_bloom); + let block_number = tx.block_number.unwrap().as_u32(); + let tx_idx = tx.transaction_index.unwrap().as_u32() as u16; + ReceiptSubquery { + block_number, + tx_idx, + field_or_log_idx: field_idx as u32, + topic_or_data_or_address_idx: tda_idx as u32, + event_schema, + } + }, + )) + .await; + + let block_nums = requests.iter().map(|r| r.block_number as u64).sorted().dedup().collect_vec(); + let blocks = join_all(block_nums.iter().map(|&block_num| async move { + get_block_with_receipts(provider, block_num, None).await.unwrap() + })) + .await; + + let chip_params = EthReceiptChipParams { + max_data_byte_len, + max_log_num, + topic_num_bounds: (0, 4), + network: None, + }; + let receipt_rlp_max_byte_len = + calc_max_val_len(max_data_byte_len, max_log_num, chip_params.topic_num_bounds); + + let rc_tries = construct_rc_tries_from_full_blocks(blocks.clone()).unwrap(); + let mut requests_in_circuit = Vec::with_capacity(requests.len()); + for subquery in requests { + let block_number = subquery.block_number as u64; + let tx_idx = subquery.tx_idx as usize; + let tx_key = get_tx_key_from_index(tx_idx); + let db = rc_tries + .get(&block_number) + .ok_or_else(|| { + anyhow!("Subquery block number {block_number} not in provided full blocks") + }) + .unwrap(); + let trie = &db.trie; + let rc_rlps = &db.rc_rlps; + let proof = trie.get_proof(&tx_key).unwrap(); + let value = rc_rlps + .get(tx_idx) + .ok_or_else(|| anyhow!("Receipt index {tx_idx} not in block {block_number}")) + .unwrap(); + let mpt_proof = MPTInput { + path: (&tx_key).into(), + value: value.to_vec(), + root_hash: db.root, + proof, + slot_is_empty: false, + value_max_byte_len: receipt_rlp_max_byte_len, + max_depth: RECEIPT_PROOF_MAX_DEPTH, + max_key_byte_len: 3, + key_byte_len: Some(tx_key.len()), + }; + let rc_proof = EthReceiptInput { idx: tx_idx, proof: mpt_proof }; + requests_in_circuit.push(CircuitInputReceiptSubquery { + block_number, + proof: rc_proof, + field_or_log_idx: subquery.field_or_log_idx, + topic_or_data_or_address_idx: subquery.topic_or_data_or_address_idx, + event_schema: subquery.event_schema, + }); + } + + let promise_header = OutputHeaderShard { + results: blocks + .iter() + .map(|block| AnySubqueryResult { + subquery: HeaderSubquery { + block_number: block.number.as_u32(), + field_idx: RECEIPT_ROOT_INDEX as u32, + }, + value: block.receipts_root, + }) + .collect(), + }; + let keccak_f_capacity = 200; + let header_capacity = promise_header.len(); + + let circuit_params = dummy_rlc_circuit_params(k as usize); + let mut circuit = ComponentCircuitReceiptSubquery::new( + CoreParamsReceiptSubquery { + chip_params, + capacity: requests_in_circuit.len(), + max_trie_depth: RECEIPT_PROOF_MAX_DEPTH, + }, + ( + PromiseLoaderParams::new_for_one_shard(keccak_f_capacity), + PromiseLoaderParams::new_for_one_shard(header_capacity), + ), + circuit_params, + ); + + let input = + CircuitInputReceiptShard:: { requests: requests_in_circuit, _phantom: PhantomData }; + circuit.feed_input(Box::new(input)).unwrap(); + + circuit.calculate_params(); + let promises = [ + ( + ComponentTypeKeccak::::get_type_id(), + ComponentPromiseResultsInMerkle::from_single_shard( + generate_keccak_shards_from_calls(&circuit, keccak_f_capacity) + .unwrap() + .into_logical_results(), + ), + ), + ( + ComponentTypeHeaderSubquery::::get_type_id(), + shard_into_component_promise_results::>( + promise_header.into(), + ), + ), + ] + .into_iter() + .collect(); + circuit.fulfill_promise_results(&promises).unwrap(); + let instances: Vec = circuit.get_public_instances().into(); + MockProver::run(k, &circuit, vec![instances]).unwrap().assert_satisfied(); + circuit +} + +#[tokio::test] +async fn test_mock_receipt_subqueries_simple() { + let k = 18; + let zero = "0x0000000000000000000000000000000000000000000000000000000000000000"; + let subqueries = vec![ + ("0xa85fb48c6cd0b6013c91a3ea93ef73cd3c39845eb258f1d82ef4210c223594f4", 0, 0, zero), // status = fail + ( + "0xc830e27d1bbfc0ea7f9a86f3debb5d6c6105a6585a44589734b12cb678f843c4", + RECEIPT_LOGS_BLOOM_IDX_OFFSET + 1, + 0, + zero, + ), + ( + "0xc830e27d1bbfc0ea7f9a86f3debb5d6c6105a6585a44589734b12cb678f843c4", + RECEIPT_LOG_IDX_OFFSET, + RECEIPT_TOPIC_IDX_OFFSET + 1, + "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c", // Deposit (index_topic_1 address dst, uint256 wad) + ), + ( + "0xc830e27d1bbfc0ea7f9a86f3debb5d6c6105a6585a44589734b12cb678f843c4", + RECEIPT_LOG_IDX_OFFSET + 1, + RECEIPT_TOPIC_IDX_OFFSET + 1, + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // Transfer (index_topic_1 address src, index_topic_2 address dst, uint256 wad) + ), + ( + "0xc830e27d1bbfc0ea7f9a86f3debb5d6c6105a6585a44589734b12cb678f843c4", + RECEIPT_LOG_IDX_OFFSET + 1, + RECEIPT_DATA_IDX_OFFSET, + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + ), + ]; + test_mock_receipt_subqueries(k, Chain::Mainnet, subqueries, 200, 10).await; +} + +#[tokio::test] +async fn test_mock_receipt_subqueries_pre_eip658() { + let k = 18; + let zero = "0x0000000000000000000000000000000000000000000000000000000000000000"; + let subqueries = vec![ + ("0x5be4ebff5bb19b012d8db932e629eda0af068e392758719353db5e0895147f8e", 1, 0, zero), // postState + ("0x5be4ebff5bb19b012d8db932e629eda0af068e392758719353db5e0895147f8e", 2, 0, zero), // cumulativeGas + ("0x5be4ebff5bb19b012d8db932e629eda0af068e392758719353db5e0895147f8e", 3, 0, zero), // logsBloom + ( + "0x5be4ebff5bb19b012d8db932e629eda0af068e392758719353db5e0895147f8e", + RECEIPT_BLOCK_NUMBER_FIELD_IDX, + 0, + zero, + ), + ( + "0x5be4ebff5bb19b012d8db932e629eda0af068e392758719353db5e0895147f8e", + RECEIPT_TX_TYPE_FIELD_IDX, + 0, + zero, + ), + ( + "0x5be4ebff5bb19b012d8db932e629eda0af068e392758719353db5e0895147f8e", + RECEIPT_TX_INDEX_FIELD_IDX, + 0, + zero, + ), + ( + "0x5be4ebff5bb19b012d8db932e629eda0af068e392758719353db5e0895147f8e", + RECEIPT_LOGS_BLOOM_IDX_OFFSET + 7, + 0, + zero, + ), + ( + "0x5be4ebff5bb19b012d8db932e629eda0af068e392758719353db5e0895147f8e", + RECEIPT_LOG_IDX_OFFSET, + 0, + zero, + ), + ( + "0x5be4ebff5bb19b012d8db932e629eda0af068e392758719353db5e0895147f8e", + RECEIPT_LOG_IDX_OFFSET, + 50, + zero, + ), + ( + "0x5be4ebff5bb19b012d8db932e629eda0af068e392758719353db5e0895147f8e", + RECEIPT_LOG_IDX_OFFSET, + 100 + 63, + zero, + ), + ( + "0x5be4ebff5bb19b012d8db932e629eda0af068e392758719353db5e0895147f8e", + RECEIPT_LOG_IDX_OFFSET, + 100 + 30, + "0x92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd004", + ), + ]; + test_mock_receipt_subqueries(k, Chain::Mainnet, subqueries, 2048, 2).await; +} + +#[cfg(feature = "keygen")] +#[tokio::test] +#[ignore] +async fn test_generate_receipt_shard_pk() { + use axiom_eth::halo2_base::utils::{fs::read_params, halo2::ProvingKeyGenerator}; + + use crate::{global_constants::RECEIPT_TOPIC_BOUNDS, keygen::shard::ShardIntentReceipt}; + + let core_params = CoreParamsReceiptSubquery { + chip_params: EthReceiptChipParams { + max_data_byte_len: 256, + max_log_num: 10, + topic_num_bounds: RECEIPT_TOPIC_BOUNDS, + network: None, + }, + capacity: 4, + max_trie_depth: RECEIPT_PROOF_MAX_DEPTH, + }; + let loader_params = + (PromiseLoaderParams::new_for_one_shard(200), PromiseLoaderParams::new_for_one_shard(8)); + let k = 18; + let intent = ShardIntentReceipt { core_params, loader_params, k, lookup_bits: 8 }; + let kzg_params = read_params(k); + intent.create_pk_and_pinning(&kzg_params); +} diff --git a/axiom-query/src/components/subqueries/receipt/types.rs b/axiom-query/src/components/subqueries/receipt/types.rs new file mode 100644 index 00000000..ace2f394 --- /dev/null +++ b/axiom-query/src/components/subqueries/receipt/types.rs @@ -0,0 +1,136 @@ +//! Types are separated into: +//! - Circuit metadata that along with the circuit type determines the circuit configuration completely. +//! - Human readable _logical_ input and output to the circuit. These include private inputs and outputs that are only commited to in the public output. +//! - The in-circuit formatted versions of logical inputs and outputs. These include formatting in terms of field elements and accounting for all lengths needing to be fixed at compile time. +//! - We then provide conversion functions from human-readable to circuit formats. +//! - This circuit has no public instances (IO) other than the circuit's own component commitment and the promise commitments from any component calls. +use std::{marker::PhantomData, sync::Arc}; + +use axiom_codec::{ + types::{field_elements::FieldReceiptSubquery, native::ReceiptSubquery}, + HiLo, +}; +use axiom_eth::{ + halo2_base::AssignedValue, + impl_fix_len_call_witness, + mpt::MPTInput, + providers::receipt::{get_receipt_rlp, TransactionReceipt}, + receipt::{calc_max_val_len as rc_calc_max_val_len, EthReceiptInput}, + utils::{ + build_utils::dummy::DummyFrom, + component::{circuit::CoreBuilderInput, ComponentType, ComponentTypeId, LogicalResult}, + }, +}; +use cita_trie::{MemoryDB, PatriciaTrie, Trie}; +use ethers_core::types::H256; +use hasher::HasherKeccak; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::subqueries::common::OutputSubqueryShard, utils::codec::AssignedReceiptSubquery, + Field, +}; + +use super::circuit::CoreParamsReceiptSubquery; + +/// Identifier for the component type of this component circuit +pub struct ComponentTypeReceiptSubquery(PhantomData); + +/// Human readable. +/// The output value of any transaction subquery is always `bytes32` right now. +pub type OutputReceiptShard = OutputSubqueryShard; + +/// Circuit input for a shard of Receipt subqueries. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CircuitInputReceiptShard { + /// Enriched subquery requests + pub requests: Vec, + pub _phantom: PhantomData, +} + +/// Circuit input for a single Receipt subquery. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CircuitInputReceiptSubquery { + /// The block number to access the storage state at. + pub block_number: u64, + /// Transaction proof formatted as [axiom_eth::mpt::MPTInput]. Contains the transaction index. + pub proof: EthReceiptInput, + /// Special index to specify what subquery value to extract from the transaction. + pub field_or_log_idx: u32, + pub topic_or_data_or_address_idx: u32, + pub event_schema: H256, +} + +impl DummyFrom for CircuitInputReceiptShard { + fn dummy_from(core_params: CoreParamsReceiptSubquery) -> Self { + let CoreParamsReceiptSubquery { chip_params, capacity, max_trie_depth } = core_params; + let mut trie = + PatriciaTrie::new(Arc::new(MemoryDB::new(true)), Arc::new(HasherKeccak::new())); + let rc = TransactionReceipt { status: Some(0x1.into()), ..Default::default() }; + let rc_rlp = get_receipt_rlp(&rc).unwrap().to_vec(); + trie.insert(vec![0x80], rc_rlp.clone()).unwrap(); + let mpt_input = MPTInput { + path: (&[0x80]).into(), + value: rc_rlp, + root_hash: Default::default(), + proof: trie.get_proof(&[0x80]).unwrap(), + value_max_byte_len: rc_calc_max_val_len( + chip_params.max_data_byte_len, + chip_params.max_log_num, + chip_params.topic_num_bounds, + ), + max_depth: max_trie_depth, + slot_is_empty: false, + max_key_byte_len: 3, + key_byte_len: Some(1), + }; + let rc_pf = EthReceiptInput { idx: 0, proof: mpt_input }; + let dummy_subquery = CircuitInputReceiptSubquery { + block_number: 0, + proof: rc_pf, + field_or_log_idx: 0, + topic_or_data_or_address_idx: 0, + event_schema: H256::zero(), + }; + Self { requests: vec![dummy_subquery; capacity], _phantom: PhantomData } + } +} + +/// The output value of any storage subquery is always `bytes32` right now. +/// Vector has been resized to the capacity. +pub type CircuitOutputReceiptShard = OutputSubqueryShard, HiLo>; + +impl_fix_len_call_witness!( + FieldReceiptSubqueryCall, + FieldReceiptSubquery, + ComponentTypeReceiptSubquery +); + +// ===== The storage component has no public instances other than the component commitment and promise commitments from external component calls ===== + +impl ComponentType for ComponentTypeReceiptSubquery { + type InputValue = FieldReceiptSubquery; + type InputWitness = AssignedReceiptSubquery; + type OutputValue = HiLo; + type OutputWitness = HiLo>; + type LogicalInput = FieldReceiptSubquery; + + fn get_type_id() -> ComponentTypeId { + "axiom-query:ComponentTypeReceiptSubquery".to_string() + } + + fn logical_result_to_virtual_rows_impl( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + vec![(ins.input, ins.output)] + } + fn logical_input_to_virtual_rows_impl(li: &Self::LogicalInput) -> Vec { + vec![*li] + } +} + +impl From for CircuitOutputReceiptShard { + fn from(output: OutputReceiptShard) -> Self { + output.convert_into() + } +} diff --git a/axiom-query/src/components/subqueries/solidity_mappings/circuit.rs b/axiom-query/src/components/subqueries/solidity_mappings/circuit.rs new file mode 100644 index 00000000..1f02538f --- /dev/null +++ b/axiom-query/src/components/subqueries/solidity_mappings/circuit.rs @@ -0,0 +1,240 @@ +use axiom_codec::{ + constants::MAX_SOLIDITY_MAPPING_KEYS, + types::field_elements::FieldSolidityNestedMappingSubquery, HiLo, +}; +use axiom_eth::{ + halo2_base::{ + gates::flex_gate::threads::parallelize_core, safe_types::SafeBytes32, AssignedValue, + Context, + }, + halo2_proofs::plonk::ConstraintSystem, + keccak::{types::ComponentTypeKeccak, KeccakChip}, + mpt::MPTChip, + rlc::circuit::builder::RlcCircuitBuilder, + rlc::circuit::builder::RlcContextPair, + rlp::RlpChip, + solidity::{ + types::{NestedMappingWitness, SolidityType}, + SolidityChip, + }, + utils::{ + build_utils::aggregation::CircuitMetadata, + circuit_utils::bytes::safe_bytes32_to_hi_lo, + component::{ + circuit::{ + ComponentBuilder, ComponentCircuitImpl, CoreBuilder, CoreBuilderOutput, + CoreBuilderOutputParams, CoreBuilderParams, + }, + promise_collector::PromiseCaller, + promise_loader::{combo::PromiseBuilderCombo, single::PromiseLoader}, + types::LogicalEmpty, + utils::{create_hasher, get_logical_value}, + LogicalResult, + }, + uint_to_bytes_be, + }, +}; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::subqueries::storage::types::{ + ComponentTypeStorageSubquery, FieldStorageSubqueryCall, + }, + utils::codec::{AssignedSolidityNestedMappingSubquery, AssignedStorageSubquery}, + Field, +}; + +use super::types::{ + CircuitInputSolidityNestedMappingShard, ComponentTypeSolidityNestedMappingSubquery, +}; + +pub struct CoreBuilderSolidityNestedMappingSubquery { + input: Option>, + params: CoreParamsSolidityNestedMappingSubquery, + payload: Option<(KeccakChip, Vec>)>, +} + +/// Specify the output format of SolidityNestedMappingSubquery component. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct CoreParamsSolidityNestedMappingSubquery { + pub capacity: usize, +} +impl CoreBuilderParams for CoreParamsSolidityNestedMappingSubquery { + fn get_output_params(&self) -> CoreBuilderOutputParams { + CoreBuilderOutputParams::new(vec![self.capacity]) + } +} + +type CKeccak = ComponentTypeKeccak; +type CStorage = ComponentTypeStorageSubquery; +/// Used for loading solidity nested mapping promise results. +pub type PromiseLoaderSolidityNestedMappingSubquery = + PromiseBuilderCombo>, PromiseLoader>>; +pub type ComponentCircuitSolidityNestedMappingSubquery = ComponentCircuitImpl< + F, + CoreBuilderSolidityNestedMappingSubquery, + PromiseLoaderSolidityNestedMappingSubquery, +>; + +impl CircuitMetadata for CoreBuilderSolidityNestedMappingSubquery { + const HAS_ACCUMULATOR: bool = false; + fn num_instance(&self) -> Vec { + unreachable!() + } +} + +impl ComponentBuilder for CoreBuilderSolidityNestedMappingSubquery { + type Params = CoreParamsSolidityNestedMappingSubquery; + + fn new(params: Self::Params) -> Self { + Self { input: None, params, payload: None } + } + fn get_params(&self) -> Self::Params { + self.params.clone() + } + fn clear_witnesses(&mut self) { + self.payload = None; + } + fn calculate_params(&mut self) -> Self::Params { + self.params.clone() + } + fn configure_with_params(_: &mut ConstraintSystem, _: Self::Params) {} +} +impl CoreBuilder for CoreBuilderSolidityNestedMappingSubquery { + type CompType = ComponentTypeSolidityNestedMappingSubquery; + type PublicInstanceValue = LogicalEmpty; + type PublicInstanceWitness = LogicalEmpty>; + type CoreInput = CircuitInputSolidityNestedMappingShard; + + fn feed_input(&mut self, input: Self::CoreInput) -> anyhow::Result<()> { + self.input = Some(input); + Ok(()) + } + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_caller: PromiseCaller, + ) -> CoreBuilderOutput { + // preamble: to be removed + let keccak = + KeccakChip::new_with_promise_collector(builder.range_chip(), promise_caller.clone()); + let range_chip = keccak.range(); + let rlp = RlpChip::new(range_chip, None); + let mut poseidon = create_hasher(); + poseidon.initialize_consts(builder.base.main(0), keccak.gate()); + + // Assumption: we already have input when calling this function. + // TODO: automatically derive a dummy input from params. + let input = self.input.as_ref().unwrap(); + + let mpt = MPTChip::new(rlp, &keccak); + let chip = SolidityChip::new(&mpt, MAX_SOLIDITY_MAPPING_KEYS, 32); + let pool = &mut builder.base.pool(0); + let payload = parallelize_core(pool, input.requests.clone(), |ctx, subquery| { + handle_single_solidity_nested_mapping_subquery_phase0(ctx, &chip, &subquery) + }); + + let ctx = pool.main(); + let mut vt = Vec::with_capacity(payload.len()); + let mut lr = Vec::with_capacity(payload.len()); + // promise calls to header component: + for p in payload.iter() { + let block_number = p.subquery.block_number; + let addr = p.subquery.addr; + let slot = p.value_slot; + let storage_subquery = AssignedStorageSubquery { block_number, addr, slot }; + // promise call to get the value at the value_slot + let value = promise_caller + .call::, ComponentTypeStorageSubquery>( + ctx, + FieldStorageSubqueryCall(storage_subquery), + ) + .unwrap(); + vt.push((p.subquery.into(), value.into())); + lr.push(LogicalResult::::new( + get_logical_value(&p.subquery), + get_logical_value(&value), + )); + } + self.payload = Some((keccak, payload)); + CoreBuilderOutput { public_instances: vec![], virtual_table: vt, logical_results: lr } + } + + fn virtual_assign_phase1(&mut self, builder: &mut RlcCircuitBuilder) { + let (keccak, payload) = self.payload.take().unwrap(); + // preamble + let range_chip = keccak.range(); + let rlc_chip = builder.rlc_chip(&range_chip.gate); + let rlp = RlpChip::new(range_chip, Some(&rlc_chip)); + let mpt = MPTChip::new(rlp, &keccak); + let chip = SolidityChip::new(&mpt, MAX_SOLIDITY_MAPPING_KEYS, 32); + + // actual logic + builder.parallelize_phase1(payload, |(ctx_gate, ctx_rlc), payload| { + handle_single_solidity_nested_mapping_subquery_phase1( + (ctx_gate, ctx_rlc), + &chip, + payload, + ) + }); + } +} + +pub struct PayloadSolidityNestedMappingSubquery { + pub mapping_witness: NestedMappingWitness, + pub subquery: AssignedSolidityNestedMappingSubquery, + /// Storage slot with the actual value of the mapping + pub value_slot: HiLo>, +} + +/// Assigns `subquery` to virtual cells and then handles the subquery. +/// Calculates the correct raw EVM storage slot corresponding to the nested mapping. +/// We do not return the `value` here. Instead we use the `value` gotten by making a promise +/// call to the Storage Subqueries Component circuit at the returned `slot`. +pub fn handle_single_solidity_nested_mapping_subquery_phase0( + ctx: &mut Context, + chip: &SolidityChip, + subquery: &FieldSolidityNestedMappingSubquery, +) -> PayloadSolidityNestedMappingSubquery { + let gate = chip.gate(); + let range = chip.range(); + // assign `mapping_slot` as HiLo + let mapping_slot = subquery.mapping_slot.assign(ctx); + // convert to `SafeBytes32` + let mapping_slot_bytes = SafeBytes32::try_from( + mapping_slot.hi_lo().map(|u| uint_to_bytes_be(ctx, range, &u, 16)).concat(), + ) + .unwrap(); + let keys_hilo = subquery.keys.map(|key| key.assign(ctx)); + let keys = keys_hilo.map(|k| { + SolidityType::Value( + SafeBytes32::try_from(k.hi_lo().map(|u| uint_to_bytes_be(ctx, range, &u, 16)).concat()) + .unwrap(), + ) + }); + let mapping_depth = ctx.load_witness(subquery.mapping_depth); + let mapping_witness = + chip.slot_for_nested_mapping_phase0(ctx, mapping_slot_bytes, keys, mapping_depth); + let value_slot = safe_bytes32_to_hi_lo(ctx, gate, &mapping_witness.slot); + + // Assign the rest of the subquery as witnesses + let addr = ctx.load_witness(subquery.addr); + let block_number = ctx.load_witness(subquery.block_number); + let subquery = AssignedSolidityNestedMappingSubquery { + block_number, + addr, + mapping_slot, + mapping_depth, + keys: keys_hilo, + }; + + PayloadSolidityNestedMappingSubquery { mapping_witness, subquery, value_slot } +} + +pub fn handle_single_solidity_nested_mapping_subquery_phase1( + ctx: RlcContextPair, + chip: &SolidityChip, + payload: PayloadSolidityNestedMappingSubquery, +) { + chip.slot_for_nested_mapping_phase1(ctx, payload.mapping_witness); +} diff --git a/axiom-query/src/components/subqueries/solidity_mappings/mod.rs b/axiom-query/src/components/subqueries/solidity_mappings/mod.rs new file mode 100644 index 00000000..46c67d10 --- /dev/null +++ b/axiom-query/src/components/subqueries/solidity_mappings/mod.rs @@ -0,0 +1,16 @@ +//! # Solidity Nested Mapping Subqueries Circuit +//! +//! SolidityNestedMapping +//! - `blockNumber` (uint32) +//! - `addr` (address = bytes20) +//! - `mappingSlot` (uint256) +//! - `mappingDepth` (uint8) -- in `(0, 4]` +//! - `keys` \[key0, key1, key2, key3\] (bytes32\[4\]) + +/// Circuit and Component Implementation. +pub mod circuit; +/// Types +pub mod types; + +#[cfg(test)] +pub mod tests; diff --git a/axiom-query/src/components/subqueries/solidity_mappings/tests.rs b/axiom-query/src/components/subqueries/solidity_mappings/tests.rs new file mode 100644 index 00000000..430a63fd --- /dev/null +++ b/axiom-query/src/components/subqueries/solidity_mappings/tests.rs @@ -0,0 +1,158 @@ +use std::str::FromStr; + +use axiom_codec::{ + types::{ + field_elements::AnySubqueryResult, + native::{SolidityNestedMappingSubquery, StorageSubquery}, + }, + utils::native::u256_to_h256, +}; +use axiom_eth::{ + halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}, + keccak::{promise::generate_keccak_shards_from_calls, types::ComponentTypeKeccak}, + providers::setup_provider, + utils::component::{ + promise_loader::single::PromiseLoaderParams, ComponentCircuit, + ComponentPromiseResultsInMerkle, ComponentType, + }, +}; +use ethers_core::{ + types::{Address, Chain, H256, U256}, + utils::keccak256, +}; +use ethers_providers::Middleware; +use futures::future::join_all; +use tokio; + +use crate::components::{ + dummy_rlc_circuit_params, + subqueries::{ + common::shard_into_component_promise_results, + storage::types::{ComponentTypeStorageSubquery, OutputStorageShard}, + }, +}; + +use super::{ + circuit::{ + ComponentCircuitSolidityNestedMappingSubquery, CoreParamsSolidityNestedMappingSubquery, + }, + types::CircuitInputSolidityNestedMappingShard, +}; + +async fn test_mock_storage_subqueries( + k: u32, + network: Chain, + subqueries: Vec<(u64, &str, &str, Vec)>, // (blockNum, addr, mappingSlot, mappingKeys) +) -> ComponentCircuitSolidityNestedMappingSubquery { + let _ = env_logger::builder().is_test(true).try_init(); + + let _provider = setup_provider(network); + let provider = &_provider; + let (requests, storage_results): (Vec<_>, Vec<_>) = + join_all(subqueries.into_iter().map(|(block_num, addr, mapping_slot, keys)| async move { + let addr = Address::from_str(addr).unwrap(); + let mapping_slot = U256::from_str(mapping_slot).unwrap(); + let mut slot = u256_to_h256(&mapping_slot); + for key in keys.iter() { + slot = H256(keccak256(&[key.as_bytes(), slot.as_bytes()].concat())); + } + let proof = provider.get_proof(addr, vec![slot], Some(block_num.into())).await.unwrap(); + let depth = keys.len(); + let value = u256_to_h256(&proof.storage_proof[0].value); + ( + SolidityNestedMappingSubquery { + block_number: block_num as u32, + addr, + mapping_slot, + mapping_depth: depth as u8, + keys, + }, + AnySubqueryResult { + subquery: StorageSubquery { + block_number: block_num as u32, + addr, + slot: U256::from_big_endian(&slot.0), + }, + value, + }, + ) + })) + .await + .into_iter() + .unzip(); + + let promise_storage = OutputStorageShard { results: storage_results }; + let storage_capacity = promise_storage.results.len(); + let keccak_f_capacity = 200; + + let circuit_params = dummy_rlc_circuit_params(k as usize); + let mut circuit = ComponentCircuitSolidityNestedMappingSubquery::new( + CoreParamsSolidityNestedMappingSubquery { capacity: requests.len() }, + ( + PromiseLoaderParams::new_for_one_shard(keccak_f_capacity), + PromiseLoaderParams::new_for_one_shard(storage_capacity), + ), + circuit_params, + ); + + let input = CircuitInputSolidityNestedMappingShard:: { + requests: requests.into_iter().map(|r| r.into()).collect(), + }; + circuit.feed_input(Box::new(input)).unwrap(); + circuit.calculate_params(); + + let promises = [ + ( + ComponentTypeKeccak::::get_type_id(), + ComponentPromiseResultsInMerkle::from_single_shard( + generate_keccak_shards_from_calls(&circuit, keccak_f_capacity) + .unwrap() + .into_logical_results(), + ), + ), + ( + ComponentTypeStorageSubquery::::get_type_id(), + shard_into_component_promise_results::>( + promise_storage.into(), + ), + ), + ] + .into_iter() + .collect(); + circuit.fulfill_promise_results(&promises).unwrap(); + + let instances: Vec = circuit.get_public_instances().into(); + MockProver::run(k, &circuit, vec![instances]).unwrap().assert_satisfied(); + circuit +} + +#[tokio::test] +async fn test_mock_solidity_nested_mapping_uni_v3_factory() { + let k = 18; + let usdc_eth_500 = vec![ + Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap().into(), + Address::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap().into(), + H256::from_low_u64_be(500), + ]; + let eth_usdc_500 = vec![ + Address::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap().into(), + Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap().into(), + H256::from_low_u64_be(500), + ]; + + let subqueries = vec![ + ( + 17143006, + "0x1F98431c8aD98523631AE4a59f267346ea31F984", + "0x05", /*getPool*/ + usdc_eth_500, + ), + ( + 18331196, + "0x1F98431c8aD98523631AE4a59f267346ea31F984", + "0x05", /*getPool*/ + eth_usdc_500, + ), + ]; + test_mock_storage_subqueries(k, Chain::Mainnet, subqueries).await; +} diff --git a/axiom-query/src/components/subqueries/solidity_mappings/types.rs b/axiom-query/src/components/subqueries/solidity_mappings/types.rs new file mode 100644 index 00000000..f0a4087d --- /dev/null +++ b/axiom-query/src/components/subqueries/solidity_mappings/types.rs @@ -0,0 +1,99 @@ +//! Types are separated into: +//! - Circuit metadata that along with the circuit type determines the circuit configuration completely. +//! - Human readable _logical_ input and output to the circuit. These include private inputs and outputs that are only commited to in the public output. +//! - The in-circuit formatted versions of logical inputs and outputs. These include formatting in terms of field elements and accounting for all lengths needing to be fixed at compile time. +//! - We then provide conversion functions from human-readable to circuit formats. +//! - This circuit has no public instances (IO) other than the circuit's own component commitment and the promise commitments from any component calls. +use std::marker::PhantomData; + +use axiom_codec::{ + types::{ + field_elements::FieldSolidityNestedMappingSubquery, native::SolidityNestedMappingSubquery, + }, + HiLo, +}; +use axiom_eth::{ + halo2_base::AssignedValue, + impl_fix_len_call_witness, + utils::{ + build_utils::dummy::DummyFrom, + component::{circuit::CoreBuilderInput, ComponentType, ComponentTypeId, LogicalResult}, + }, +}; +use ethers_core::types::H256; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::subqueries::common::OutputSubqueryShard, + utils::codec::AssignedSolidityNestedMappingSubquery, Field, RawField, +}; + +use super::circuit::CoreParamsSolidityNestedMappingSubquery; + +/// Identifier for the component type of this component circuit +pub struct ComponentTypeSolidityNestedMappingSubquery(PhantomData); + +/// Human readable. +/// The output value of any solidity nested mapping subquery is always `bytes32` right now. +pub type OutputSolidityNestedMappingShard = + OutputSubqueryShard; + +/// Circuit input for a shard of Solidity Nested Mapping subqueries. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CircuitInputSolidityNestedMappingShard { + /// Enriched subquery requests + pub requests: Vec>, +} + +impl DummyFrom + for CircuitInputSolidityNestedMappingShard +{ + fn dummy_from(core_params: CoreParamsSolidityNestedMappingSubquery) -> Self { + let CoreParamsSolidityNestedMappingSubquery { capacity } = core_params; + let dummy_subquery = + FieldSolidityNestedMappingSubquery { mapping_depth: F::ONE, ..Default::default() }; + Self { requests: vec![dummy_subquery; capacity] } + } +} + +/// The output value of any storage subquery is always `bytes32` right now. +/// Vector has been resized to the capacity. +pub type CircuitOutputSolidityNestedMappingShard = + OutputSubqueryShard, HiLo>; + +impl_fix_len_call_witness!( + FieldSolidityNestedMappingSubqueryCall, + FieldSolidityNestedMappingSubquery, + ComponentTypeSolidityNestedMappingSubquery +); + +// ===== This component has no public instances other than the output commitment and promise commitments from external component calls ===== + +impl ComponentType for ComponentTypeSolidityNestedMappingSubquery { + type InputValue = FieldSolidityNestedMappingSubquery; + type InputWitness = AssignedSolidityNestedMappingSubquery; + type OutputValue = HiLo; + type OutputWitness = HiLo>; + type LogicalInput = FieldSolidityNestedMappingSubquery; + + fn get_type_id() -> ComponentTypeId { + "axiom-query:ComponentTypeSolidityNestedMappingSubquery".to_string() + } + + fn logical_result_to_virtual_rows_impl( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + vec![(ins.input, ins.output)] + } + fn logical_input_to_virtual_rows_impl(li: &Self::LogicalInput) -> Vec { + vec![*li] + } +} + +impl From + for CircuitOutputSolidityNestedMappingShard +{ + fn from(output: OutputSolidityNestedMappingShard) -> Self { + output.convert_into() + } +} diff --git a/axiom-query/src/components/subqueries/storage/circuit.rs b/axiom-query/src/components/subqueries/storage/circuit.rs new file mode 100644 index 00000000..e692333c --- /dev/null +++ b/axiom-query/src/components/subqueries/storage/circuit.rs @@ -0,0 +1,260 @@ +use axiom_codec::{utils::native::encode_addr_to_field, HiLo}; +use axiom_eth::{ + halo2_base::{ + gates::{flex_gate::threads::parallelize_core, GateInstructions}, + safe_types::SafeTypeChip, + AssignedValue, Context, + }, + halo2_proofs::plonk::ConstraintSystem, + keccak::{types::ComponentTypeKeccak, KeccakChip}, + mpt::MPTChip, + rlc::circuit::builder::{RlcCircuitBuilder, RlcContextPair}, + rlp::RlpChip, + storage::{EthStorageChip, EthStorageWitness}, + utils::{ + build_utils::aggregation::CircuitMetadata, + circuit_utils::bytes::{ + pack_bytes_to_hilo, safe_bytes32_to_hi_lo, unsafe_mpt_root_to_hi_lo, + }, + component::{ + circuit::{ + ComponentBuilder, ComponentCircuitImpl, CoreBuilder, CoreBuilderOutput, + CoreBuilderOutputParams, CoreBuilderParams, + }, + promise_collector::PromiseCaller, + promise_loader::{combo::PromiseBuilderCombo, single::PromiseLoader}, + types::LogicalEmpty, + utils::create_hasher, + LogicalResult, + }, + constrain_vec_equal, unsafe_bytes_to_assigned, + }, + zkevm_hashes::util::eth_types::ToBigEndian, +}; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::subqueries::{ + account::{ + types::{ComponentTypeAccountSubquery, FieldAccountSubqueryCall}, + STORAGE_ROOT_INDEX, + }, + common::{extract_logical_results, extract_virtual_table}, + }, + utils::codec::{ + AssignedAccountSubquery, AssignedStorageSubquery, AssignedStorageSubqueryResult, + }, + Field, +}; + +use super::types::{ + CircuitInputStorageShard, CircuitInputStorageSubquery, ComponentTypeStorageSubquery, +}; + +pub struct CoreBuilderStorageSubquery { + input: Option>, + params: CoreParamsStorageSubquery, + payload: Option<(KeccakChip, Vec>)>, +} + +/// Specify the output format of StorageSubquery component. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct CoreParamsStorageSubquery { + /// The maximum number of subqueries of this type allowed in a single circuit. + pub capacity: usize, + /// The maximum depth of the storage MPT trie supported by this circuit. + /// The depth is defined as the maximum length of an storage proof, where the storage proof always ends in a terminal leaf node. + /// + /// In production this will be set to 13 based on the MPT analysis from https://hackmd.io/@axiom/BJBledudT + pub max_trie_depth: usize, +} +impl CoreBuilderParams for CoreParamsStorageSubquery { + fn get_output_params(&self) -> CoreBuilderOutputParams { + CoreBuilderOutputParams::new(vec![self.capacity]) + } +} + +type CKeccak = ComponentTypeKeccak; +type CAccount = ComponentTypeAccountSubquery; +/// Used for loading storage promise results. +pub type PromiseLoaderStorageSubquery = + PromiseBuilderCombo>, PromiseLoader>>; +pub type ComponentCircuitStorageSubquery = + ComponentCircuitImpl, PromiseLoaderStorageSubquery>; + +impl CircuitMetadata for CoreBuilderStorageSubquery { + const HAS_ACCUMULATOR: bool = false; + fn num_instance(&self) -> Vec { + unreachable!() + } +} + +impl ComponentBuilder for CoreBuilderStorageSubquery { + type Params = CoreParamsStorageSubquery; + + fn new(params: Self::Params) -> Self { + Self { input: None, params, payload: None } + } + fn get_params(&self) -> Self::Params { + self.params.clone() + } + fn clear_witnesses(&mut self) { + self.payload = None; + } + fn calculate_params(&mut self) -> Self::Params { + self.params.clone() + } + fn configure_with_params(_: &mut ConstraintSystem, _: Self::Params) {} +} + +impl CoreBuilder for CoreBuilderStorageSubquery { + type CompType = ComponentTypeStorageSubquery; + type PublicInstanceValue = LogicalEmpty; + type PublicInstanceWitness = LogicalEmpty>; + type CoreInput = CircuitInputStorageShard; + + fn feed_input(&mut self, input: Self::CoreInput) -> anyhow::Result<()> { + for r in &input.requests { + if r.proof.storage_pfs.len() != 1 { + anyhow::bail!( + "InvalidInput: each storage proof input must have exactly one storage proof" + ); + } + if r.proof.storage_pfs[0].2.max_depth != self.params.max_trie_depth { + anyhow::bail!("StorageSubquery: request MPT max depth {} does not match configured max depth {}", r.proof.storage_pfs[0].2.max_depth, self.params.max_trie_depth); + } + } + self.input = Some(input); + Ok(()) + } + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_caller: PromiseCaller, + ) -> CoreBuilderOutput { + // preamble: to be removed + let keccak = + KeccakChip::new_with_promise_collector(builder.range_chip(), promise_caller.clone()); + let range_chip = keccak.range(); + let rlp = RlpChip::new(range_chip, None); + let mut poseidon = create_hasher(); + poseidon.initialize_consts(builder.base.main(0), keccak.gate()); + + // Assumption: we already have input when calling this function. + // TODO: automatically derive a dummy input from params. + let input = self.input.as_ref().unwrap(); + + let mpt = MPTChip::new(rlp, &keccak); + let chip = EthStorageChip::new(&mpt, None); + let pool = &mut builder.base.pool(0); + // actual logic + let payload = parallelize_core(pool, input.requests.clone(), |ctx, subquery| { + handle_single_storage_subquery_phase0(ctx, &chip, &subquery) + }); + + let vt = extract_virtual_table(payload.iter().map(|p| p.output)); + let lr: Vec> = + extract_logical_results(payload.iter().map(|p| p.output)); + + let ctx = pool.main(); + // promise calls to header component: + let account_storage_hash_idx = ctx.load_constant(F::from(STORAGE_ROOT_INDEX as u64)); + for p in payload.iter() { + let block_number = p.output.subquery.block_number; + let addr = p.output.subquery.addr; + let storage_root = p.storage_root; + let account_subquery = + AssignedAccountSubquery { block_number, addr, field_idx: account_storage_hash_idx }; + let promise_storage_root = promise_caller + .call::, ComponentTypeAccountSubquery>( + ctx, + FieldAccountSubqueryCall(account_subquery), + ) + .unwrap(); + constrain_vec_equal(ctx, &storage_root.hi_lo(), &promise_storage_root.hi_lo()); + } + self.payload = Some((keccak, payload)); + CoreBuilderOutput { public_instances: vec![], virtual_table: vt, logical_results: lr } + } + + fn virtual_assign_phase1(&mut self, builder: &mut RlcCircuitBuilder) { + let (keccak, payload) = self.payload.take().unwrap(); + // preamble + let range_chip = keccak.range(); + let rlc_chip = builder.rlc_chip(&range_chip.gate); + let rlp = RlpChip::new(range_chip, Some(&rlc_chip)); + let mpt = MPTChip::new(rlp, &keccak); + let chip = EthStorageChip::new(&mpt, None); + + // actual logic + builder.parallelize_phase1(payload, |(ctx_gate, ctx_rlc), payload| { + handle_single_storage_subquery_phase1((ctx_gate, ctx_rlc), &chip, payload) + }); + } +} + +pub struct PayloadStorageSubquery { + pub storage_witness: EthStorageWitness, + pub storage_root: HiLo>, + pub output: AssignedStorageSubqueryResult, +} + +/// Assigns `subquery` to virtual cells and then handles the subquery to get result. +/// **Assumes** that the storageHash is verified. Returns the assigned private witnesses of +/// `(block_number, address, storage_hash)`, to be looked up against Account Component promise. +pub fn handle_single_storage_subquery_phase0( + ctx: &mut Context, + chip: &EthStorageChip, + subquery: &CircuitInputStorageSubquery, +) -> PayloadStorageSubquery { + let gate = chip.gate(); + let range = chip.range(); + let safe = SafeTypeChip::new(range); + // assign address (H160) as single field element + let addr = ctx.load_witness(encode_addr_to_field(&subquery.proof.addr)); + // should have already validated input so storage_pfs has length 1 + let (slot, _value, mpt_proof) = subquery.proof.storage_pfs[0].clone(); + // assign `slot` as `SafeBytes32` + let unsafe_slot = unsafe_bytes_to_assigned(ctx, &slot.to_be_bytes()); + let slot_bytes = safe.raw_bytes_to(ctx, unsafe_slot); + // convert slot to HiLo to save for later + let slot = safe_bytes32_to_hi_lo(ctx, gate, &slot_bytes); + + // assign storage proof + let mpt_proof = mpt_proof.assign(ctx); + // convert storageRoot from bytes to HiLo for later. `parse_storage_proof` will constrain these witnesses to be bytes + let storage_root = unsafe_mpt_root_to_hi_lo(ctx, gate, &mpt_proof); + // Check the storage MPT proof + let storage_witness = chip.parse_storage_proof_phase0(ctx, slot_bytes, mpt_proof); + // Left pad value to 32 bytes and convert to HiLo + let value = { + let w = storage_witness.value_witness(); + let inputs = w.field_cells.clone(); + let len = w.field_len; + let var_len_bytes = SafeTypeChip::unsafe_to_var_len_bytes_vec(inputs, len, 32); + let fixed_bytes = var_len_bytes.left_pad_to_fixed(ctx, gate); + pack_bytes_to_hilo(ctx, gate, fixed_bytes.bytes()) + }; + // set slot value to uint256(0) when the slot does not exist in the storage trie + let slot_is_empty = storage_witness.mpt_witness().slot_is_empty; + let value = HiLo::from_hi_lo(value.hi_lo().map(|x| gate.mul_not(ctx, slot_is_empty, x))); + + let block_number = ctx.load_witness(F::from(subquery.block_number)); + + PayloadStorageSubquery { + storage_witness, + storage_root, + output: AssignedStorageSubqueryResult { + subquery: AssignedStorageSubquery { block_number, addr, slot }, + value, + }, + } +} + +pub fn handle_single_storage_subquery_phase1( + ctx: RlcContextPair, + chip: &EthStorageChip, + payload: PayloadStorageSubquery, +) { + chip.parse_storage_proof_phase1(ctx, payload.storage_witness); +} diff --git a/axiom-query/src/components/subqueries/storage/mod.rs b/axiom-query/src/components/subqueries/storage/mod.rs new file mode 100644 index 00000000..771e0362 --- /dev/null +++ b/axiom-query/src/components/subqueries/storage/mod.rs @@ -0,0 +1,15 @@ +//! # Storage Subqueries Circuit +//! +//! Storage subquery +//! - `blockNumber` (uint32) +//! - `addr` (address = bytes20) +//! - `slot` (uint256) +//! + +/// Circuit and Component Implementation. +pub mod circuit; +/// Types +pub mod types; + +#[cfg(test)] +pub mod tests; diff --git a/axiom-query/src/components/subqueries/storage/tests.rs b/axiom-query/src/components/subqueries/storage/tests.rs new file mode 100644 index 00000000..3b6f6a03 --- /dev/null +++ b/axiom-query/src/components/subqueries/storage/tests.rs @@ -0,0 +1,208 @@ +use std::{marker::PhantomData, str::FromStr}; + +use axiom_codec::types::{field_elements::AnySubqueryResult, native::AccountSubquery}; +use axiom_eth::{ + halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}, + keccak::{promise::generate_keccak_shards_from_calls, types::ComponentTypeKeccak}, + mpt::KECCAK_RLP_EMPTY_STRING, + providers::{setup_provider, storage::json_to_mpt_input}, + utils::component::{ + promise_loader::single::PromiseLoaderParams, ComponentCircuit, + ComponentPromiseResultsInMerkle, ComponentType, + }, +}; +use ethers_core::{ + types::{Address, Chain, H256}, + utils::keccak256, +}; +use ethers_providers::Middleware; +use futures::future::join_all; +use itertools::Itertools; +use rand::{rngs::StdRng, Rng}; +use rand_core::SeedableRng; +use tokio; + +use crate::components::{ + dummy_rlc_circuit_params, + subqueries::{ + account::{ + types::{ComponentTypeAccountSubquery, OutputAccountShard}, + STORAGE_ROOT_INDEX, + }, + block_header::tests::get_latest_block_number, + common::shard_into_component_promise_results, + storage::types::CircuitInputStorageSubquery, + }, +}; + +use super::{ + circuit::{ComponentCircuitStorageSubquery, CoreParamsStorageSubquery}, + types::CircuitInputStorageShard, +}; + +pub const STORAGE_PROOF_MAX_DEPTH: usize = 13; + +async fn test_mock_storage_subqueries( + k: u32, + network: Chain, + subqueries: Vec<(u64, &str, H256)>, // (blockNum, addr, slot) +) -> ComponentCircuitStorageSubquery { + let _ = env_logger::builder().is_test(true).try_init(); + + let _provider = setup_provider(network); + let provider = &_provider; + let (requests, storage_hashes): (Vec, Vec) = + join_all(subqueries.into_iter().map(|(block_num, addr, slot)| async move { + let addr = Address::from_str(addr).unwrap(); + let proof = provider.get_proof(addr, vec![slot], Some(block_num.into())).await.unwrap(); + let storage_hash = if proof.storage_hash.is_zero() { + // RPC provider may give zero storage hash for empty account, but the correct storage hash should be the null root = keccak256(0x80) + H256::from_slice(&KECCAK_RLP_EMPTY_STRING) + } else { + proof.storage_hash + }; + assert_eq!(proof.storage_proof.len(), 1, "Storage proof should have length 1 exactly"); + let proof = json_to_mpt_input(proof, 0, STORAGE_PROOF_MAX_DEPTH); + (CircuitInputStorageSubquery { block_number: block_num, proof }, storage_hash) + })) + .await + .into_iter() + .unzip(); + + let promise_account = OutputAccountShard { + results: requests + .iter() + .zip_eq(storage_hashes) + .map(|(r, storage_hash)| AnySubqueryResult { + subquery: AccountSubquery { + block_number: r.block_number as u32, + field_idx: STORAGE_ROOT_INDEX as u32, + addr: r.proof.addr, + }, + value: storage_hash, + }) + .collect(), + }; + + let keccak_f_capacity = 1200; + let account_capacity = promise_account.results.len(); + + let circuit_params = dummy_rlc_circuit_params(k as usize); + let mut circuit = ComponentCircuitStorageSubquery::new( + CoreParamsStorageSubquery { + capacity: requests.len(), + max_trie_depth: STORAGE_PROOF_MAX_DEPTH, + }, + ( + PromiseLoaderParams::new_for_one_shard(keccak_f_capacity), + PromiseLoaderParams::new_for_one_shard(account_capacity), + ), + circuit_params, + ); + + let input = CircuitInputStorageShard:: { requests, _phantom: PhantomData }; + circuit.feed_input(Box::new(input)).unwrap(); + circuit.calculate_params(); + let promises = [ + ( + ComponentTypeKeccak::::get_type_id(), + ComponentPromiseResultsInMerkle::from_single_shard( + generate_keccak_shards_from_calls(&circuit, keccak_f_capacity) + .unwrap() + .into_logical_results(), + ), + ), + ( + ComponentTypeAccountSubquery::::get_type_id(), + shard_into_component_promise_results::>( + promise_account.into(), + ), + ), + ] + .into_iter() + .collect(); + circuit.fulfill_promise_results(&promises).unwrap(); + + let instances: Vec = circuit.get_public_instances().into(); + MockProver::run(k, &circuit, vec![instances]).unwrap().assert_satisfied(); + circuit +} + +#[tokio::test] +async fn test_mock_storage_subqueries_slot0() { + let k = 18; + let subqueries = vec![ + (17143006, "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B", H256::zero()), + (17143000, "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", H256::zero()), + (16356350, "0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB", H256::zero()), + (15411056, "0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6", H256::zero()), + ]; + test_mock_storage_subqueries(k, Chain::Mainnet, subqueries).await; +} + +#[tokio::test] +async fn test_mock_storage_subqueries_uni_v3() { + let k = 18; + let network = Chain::Mainnet; + let mut rng = StdRng::seed_from_u64(0); + let address = "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"; // uniswap v3 eth-usdc 5bps pool + let latest = get_latest_block_number(network).await; + let subqueries = [0, 1, 2, 8] + .map(|x| (rng.gen_range(12376729..latest), address, H256::from_low_u64_be(x))) + .to_vec(); + test_mock_storage_subqueries(k, network, subqueries).await; +} + +#[tokio::test] +async fn test_mock_storage_subqueries_mapping() { + let k = 19; + let network = Chain::Mainnet; + let mut rng = StdRng::seed_from_u64(0); + let address = "0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB"; // cryptopunks + let slots = (0..8).map(|x| { + let mut bytes = [0u8; 64]; + bytes[31] = x; + bytes[63] = 10; + H256::from_slice(&keccak256(bytes)) + }); + let latest = get_latest_block_number(Chain::Mainnet).await; + let subqueries: Vec<_> = + slots.map(|slot| (rng.gen_range(3914495..latest), address, slot)).collect(); + test_mock_storage_subqueries(k, network, subqueries).await; +} + +// some of the slots will be empty, we test that the value returned is 0 +#[tokio::test] +async fn test_mock_storage_subqueries_empty() { + let k = 20; + let network = Chain::Mainnet; + let mut rng = StdRng::seed_from_u64(0); + let address = "0xbb9bc244d798123fde783fcc1c72d3bb8c189413"; // TheDAO Token + + // don't use random for re-producibility + let latest = 18317207; // get_latest_block_number(Chain::Mainnet).await; + let subqueries: Vec<_> = (0..64) + .map(|_| (rng.gen_range(1428757..latest), address, H256::random_using(&mut rng))) + .collect(); + test_mock_storage_subqueries(k, network, subqueries[19..20].to_vec()).await; +} + +#[tokio::test] +async fn test_mock_storage_subqueries_empty_precompile() { + let k = 18; + let subqueries = vec![ + (17143006, "0x0000000000000000000000000000000000000000", H256::zero()), + (17143000, "0x0000000000000000000000000000000000000001", H256::from_low_u64_be(1)), + (16356350, "0x0000000000000000000000000000000000000002", H256::from_low_u64_be(2)), + (15411056, "0x0000000000000000000000000000000000000003", H256::from_low_u64_be(3)), + ]; + test_mock_storage_subqueries(k, Chain::Mainnet, subqueries).await; +} + +#[tokio::test] +async fn test_mock_storage_subqueries_empty_account() { + let k = 18; + let subqueries = + vec![(4_000_000, "0xF57252Fc4ff36D8d10B0b83d8272020D2B8eDd55", H256::from_low_u64_be(295))]; + test_mock_storage_subqueries(k, Chain::Sepolia, subqueries).await; +} diff --git a/axiom-query/src/components/subqueries/storage/types.rs b/axiom-query/src/components/subqueries/storage/types.rs new file mode 100644 index 00000000..f96fadf9 --- /dev/null +++ b/axiom-query/src/components/subqueries/storage/types.rs @@ -0,0 +1,112 @@ +//! Types are separated into: +//! - Circuit metadata that along with the circuit type determines the circuit configuration completely. +//! - Human readable _logical_ input and output to the circuit. These include private inputs and outputs that are only commited to in the public output. +//! - The in-circuit formatted versions of logical inputs and outputs. These include formatting in terms of field elements and accounting for all lengths needing to be fixed at compile time. +//! - We then provide conversion functions from human-readable to circuit formats. +//! - This circuit has no public instances (IO) other than the circuit's own component commitment and the promise commitments from any component calls. +use std::marker::PhantomData; + +use axiom_codec::{ + types::{field_elements::FieldStorageSubquery, native::StorageSubquery}, + HiLo, +}; +use axiom_eth::{ + halo2_base::AssignedValue, + impl_fix_len_call_witness, + providers::storage::json_to_mpt_input, + storage::circuit::EthStorageInput, + utils::{ + build_utils::dummy::DummyFrom, + component::{circuit::CoreBuilderInput, ComponentType, ComponentTypeId, LogicalResult}, + }, +}; +use ethers_core::types::{EIP1186ProofResponse, H256}; +use serde::{Deserialize, Serialize}; + +use crate::utils::codec::AssignedStorageSubquery; +use crate::{ + components::subqueries::{ + account::types::GENESIS_ADDRESS_0_ACCOUNT_PROOF, common::OutputSubqueryShard, + }, + Field, +}; + +use super::circuit::CoreParamsStorageSubquery; + +/// Identifier for the component type of this component circuit +pub struct ComponentTypeStorageSubquery(PhantomData); + +/// Human readable. +/// The output value of any storage subquery is always `bytes32` right now. +pub type OutputStorageShard = OutputSubqueryShard; + +/// Circuit input for a shard of Storage subqueries. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CircuitInputStorageShard { + /// Enriched subquery requests + pub requests: Vec, + pub _phantom: PhantomData, +} + +/// Circuit input for a single Storage subquery. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CircuitInputStorageSubquery { + /// The block number to access the storage state at. + pub block_number: u64, + /// Storage proof formatted as MPT input. It will contain the account address. + /// ### Warning + /// `proof.acct_pf` will be empty and `proof` will **not** have state_root set. + pub proof: EthStorageInput, +} + +impl DummyFrom for CircuitInputStorageShard { + fn dummy_from(core_params: CoreParamsStorageSubquery) -> Self { + let CoreParamsStorageSubquery { capacity, max_trie_depth } = core_params; + let request = { + let pf: EIP1186ProofResponse = + serde_json::from_str(GENESIS_ADDRESS_0_ACCOUNT_PROOF).unwrap(); + let proof = json_to_mpt_input(pf, 0, max_trie_depth); + CircuitInputStorageSubquery { block_number: 0, proof } + }; + Self { requests: vec![request; capacity], _phantom: PhantomData } + } +} + +/// The output value of any storage subquery is always `bytes32` right now. +/// Vector has been resized to the capacity. +pub type CircuitOutputStorageShard = OutputSubqueryShard, HiLo>; + +impl_fix_len_call_witness!( + FieldStorageSubqueryCall, + FieldStorageSubquery, + ComponentTypeStorageSubquery +); + +// ===== The storage component has no public instances other than the component commitment and promise commitments from external component calls ===== + +impl ComponentType for ComponentTypeStorageSubquery { + type InputValue = FieldStorageSubquery; + type InputWitness = AssignedStorageSubquery; + type OutputValue = HiLo; + type OutputWitness = HiLo>; + type LogicalInput = FieldStorageSubquery; + + fn get_type_id() -> ComponentTypeId { + "axiom-query:ComponentTypeStorageSubquery".to_string() + } + + fn logical_result_to_virtual_rows_impl( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + vec![(ins.input, ins.output)] + } + fn logical_input_to_virtual_rows_impl(li: &Self::LogicalInput) -> Vec { + vec![*li] + } +} + +impl From for CircuitOutputStorageShard { + fn from(output: OutputStorageShard) -> Self { + output.convert_into() + } +} diff --git a/axiom-query/src/components/subqueries/transaction/circuit.rs b/axiom-query/src/components/subqueries/transaction/circuit.rs new file mode 100644 index 00000000..1b74860f --- /dev/null +++ b/axiom-query/src/components/subqueries/transaction/circuit.rs @@ -0,0 +1,524 @@ +use std::iter::zip; + +use axiom_codec::{ + constants::FIELD_IDX_BITS, + encoder::field_elements::SUBQUERY_OUTPUT_BYTES, + special_values::{ + TX_BLOCK_NUMBER_FIELD_IDX, TX_CALLDATA_HASH_FIELD_IDX, TX_CALLDATA_IDX_OFFSET, + TX_CONTRACT_DATA_IDX_OFFSET, TX_CONTRACT_DEPLOY_SELECTOR_VALUE, TX_DATA_LENGTH_FIELD_IDX, + TX_FUNCTION_SELECTOR_FIELD_IDX, TX_NO_CALLDATA_SELECTOR_VALUE, TX_TX_INDEX_FIELD_IDX, + TX_TX_TYPE_FIELD_IDX, + }, + HiLo, +}; +use axiom_eth::{ + block_header::TX_ROOT_INDEX, + halo2_base::{ + gates::{flex_gate::threads::parallelize_core, GateInstructions, RangeInstructions}, + safe_types::{SafeBool, SafeTypeChip, VarLenBytesVec}, + utils::bit_length, + AssignedValue, Context, + QuantumCell::Constant, + }, + halo2_proofs::plonk::ConstraintSystem, + keccak::{types::ComponentTypeKeccak, KeccakChip}, + mpt::MPTChip, + rlc::circuit::builder::RlcCircuitBuilder, + rlc::circuit::builder::RlcContextPair, + rlp::RlpChip, + transaction::{ + EthTransactionChip, EthTransactionChipParams, EthTransactionWitness, TRANSACTION_MAX_FIELDS, + }, + utils::{ + build_utils::aggregation::CircuitMetadata, + bytes_be_to_uint, + circuit_utils::{ + bytes::{pack_bytes_to_hilo, select_hi_lo, unsafe_mpt_root_to_hi_lo}, + extract_array_chunk_and_constrain_trailing_zeros, is_equal_usize, is_gte_usize, + is_in_range, is_lt_usize, min_with_usize, unsafe_constrain_trailing_zeros, + }, + component::{ + circuit::{ + ComponentBuilder, ComponentCircuitImpl, CoreBuilder, CoreBuilderOutput, + CoreBuilderOutputParams, CoreBuilderParams, + }, + promise_collector::PromiseCaller, + promise_loader::{combo::PromiseBuilderCombo, single::PromiseLoader}, + types::LogicalEmpty, + utils::create_hasher, + LogicalResult, + }, + constrain_vec_equal, + }, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::subqueries::{ + block_header::types::{ComponentTypeHeaderSubquery, FieldHeaderSubqueryCall}, + common::{extract_logical_results, extract_virtual_table}, + }, + utils::codec::{AssignedHeaderSubquery, AssignedTxSubquery, AssignedTxSubqueryResult}, + Field, +}; + +use super::{ + types::{CircuitInputTxShard, CircuitInputTxSubquery, ComponentTypeTxSubquery}, + TX_DATA_FIELD_IDX, +}; + +pub struct CoreBuilderTxSubquery { + input: Option>, + params: CoreParamsTxSubquery, + payload: Option<(KeccakChip, Vec>)>, +} + +/// Specify the output format of TxSubquery component. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct CoreParamsTxSubquery { + pub chip_params: EthTransactionChipParams, + /// The maximum number of subqueries of this type allowed in a single circuit. + pub capacity: usize, + /// The maximum depth of the transactions MPT trie supported by this circuit. + /// The depth is defined as the maximum length of a Merkle proof, where the proof always ends in a terminal node (if the proof ends in a branch, we extract the leaf and add it as a separate node). + /// + /// In practice this can always be set to 6, because + /// transaction index is within u16, so rlp(txIndex) is at most 3 bytes => 6 nibbles. + pub max_trie_depth: usize, +} +impl CoreBuilderParams for CoreParamsTxSubquery { + fn get_output_params(&self) -> CoreBuilderOutputParams { + CoreBuilderOutputParams::new(vec![self.capacity]) + } +} + +type CKeccak = ComponentTypeKeccak; +type CHeader = ComponentTypeHeaderSubquery; +pub type PromiseLoaderTxSubquery = + PromiseBuilderCombo>, PromiseLoader>>; +pub type ComponentCircuitTxSubquery = + ComponentCircuitImpl, PromiseLoaderTxSubquery>; + +impl CircuitMetadata for CoreBuilderTxSubquery { + const HAS_ACCUMULATOR: bool = false; + fn num_instance(&self) -> Vec { + unreachable!() + } +} + +impl ComponentBuilder for CoreBuilderTxSubquery { + type Params = CoreParamsTxSubquery; + + fn new(params: Self::Params) -> Self { + Self { input: None, params, payload: None } + } + fn get_params(&self) -> Self::Params { + self.params.clone() + } + fn clear_witnesses(&mut self) { + self.payload = None; + } + fn calculate_params(&mut self) -> Self::Params { + self.params.clone() + } + fn configure_with_params(_: &mut ConstraintSystem, _: Self::Params) {} +} + +impl CoreBuilder for CoreBuilderTxSubquery { + type CompType = ComponentTypeTxSubquery; + type PublicInstanceValue = LogicalEmpty; + type PublicInstanceWitness = LogicalEmpty>; + type CoreInput = CircuitInputTxShard; + + fn feed_input(&mut self, input: Self::CoreInput) -> anyhow::Result<()> { + for r in &input.requests { + if r.proof.proof.max_depth != self.params.max_trie_depth { + anyhow::bail!( + "TxSubquery: request proof max depth {} does not match the configured max_depth {}", + r.proof.proof.max_depth, + self.params.max_trie_depth + ); + } + } + self.input = Some(input); + Ok(()) + } + /// Includes computing the component commitment to the logical output (the subquery results). + /// **In addition** performs _promise calls_ to the Header Component to verify + /// all `(block_number, transaction_root)` pairs as additional "enriched" header subqueries. + /// These are checked against the supplied promise commitment using dynamic lookups + /// (behind the scenes) by `promise_caller`. + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_caller: PromiseCaller, + ) -> CoreBuilderOutput { + // preamble: to be removed + let keccak = + KeccakChip::new_with_promise_collector(builder.range_chip(), promise_caller.clone()); + let range_chip = keccak.range(); + let rlp = RlpChip::new(range_chip, None); + let mut poseidon = create_hasher(); + poseidon.initialize_consts(builder.base.main(0), keccak.gate()); + + // Assumption: we already have input when calling this function. + // TODO: automatically derive a dummy input from params. + let input = self.input.as_ref().unwrap(); + + let mpt = MPTChip::new(rlp, &keccak); + let chip = EthTransactionChip::new(&mpt, self.params.chip_params); + let base_builder = &mut builder.base; + // actual logic + let payload = + parallelize_core(base_builder.pool(0), input.requests.clone(), |ctx, subquery| { + handle_single_tx_subquery_phase0(ctx, &chip, &subquery) + }); + + let vt = extract_virtual_table(payload.iter().map(|p| p.output)); + let lr: Vec> = + extract_logical_results(payload.iter().map(|p| p.output)); + let ctx = base_builder.main(0); + + // promise calls to header component: + // - for each block number in a subquery, we must make a promise call to check the transaction root of that block + let header_tx_root_idx = ctx.load_constant(F::from(TX_ROOT_INDEX as u64)); + for p in payload.iter() { + let block_number = p.output.subquery.block_number; + let tx_root = p.tx_root; + let header_subquery = + AssignedHeaderSubquery { block_number, field_idx: header_tx_root_idx }; + let promise_tx_root = promise_caller + .call::, ComponentTypeHeaderSubquery>( + ctx, + FieldHeaderSubqueryCall(header_subquery), + ) + .unwrap(); + constrain_vec_equal(ctx, &tx_root.hi_lo(), &promise_tx_root.hi_lo()); + } + self.payload = Some((keccak, payload)); + CoreBuilderOutput { public_instances: vec![], virtual_table: vt, logical_results: lr } + } + + fn virtual_assign_phase1(&mut self, builder: &mut RlcCircuitBuilder) { + let (keccak, payload) = self.payload.take().unwrap(); + // preamble + let range_chip = keccak.range(); + let rlc_chip = builder.rlc_chip(&range_chip.gate); + let rlp = RlpChip::new(range_chip, Some(&rlc_chip)); + let mpt = MPTChip::new(rlp, &keccak); + let chip = EthTransactionChip::new(&mpt, self.params.chip_params); + + // actual logic + builder.parallelize_phase1(payload, |(ctx_gate, ctx_rlc), payload| { + handle_single_tx_subquery_phase1((ctx_gate, ctx_rlc), &chip, payload) + }); + } +} + +pub struct PayloadTxSubquery { + pub tx_witness: EthTransactionWitness, + pub tx_root: HiLo>, + pub output: AssignedTxSubqueryResult, +} + +/// Assigns `subquery` to virtual cells and then handles the subquery to get result. +/// **Assumes** that the transactionsRoot is verified. Returns the assigned private witnesses of +/// `(block_number, transactionsRoot)`, to be looked up against Header Component promise. +pub fn handle_single_tx_subquery_phase0( + ctx: &mut Context, + chip: &EthTransactionChip, + subquery: &CircuitInputTxSubquery, +) -> PayloadTxSubquery { + let gate = chip.gate(); + let range = chip.range(); + // assign tx proof + let tx_proof = subquery.proof.clone().assign(ctx); + // convert transactionsRoot from bytes to HiLo for later. `parse_transaction_proof` will constrain these witnesses to be bytes + let tx_root = unsafe_mpt_root_to_hi_lo(ctx, gate, &tx_proof.proof); + // Check the transaction MPT proof + let tx_witness = chip.parse_transaction_proof_phase0(ctx, tx_proof); + let tx_type = tx_witness.transaction_type; + range.check_less_than_safe(ctx, tx_type, 3); + let tx_type_indicator: [_; 3] = gate.idx_to_indicator(ctx, tx_type, 3).try_into().unwrap(); + // index of `data` in rlp list + let data_list_idx = + gate.select_by_indicator(ctx, [5, 6, 7].map(|x| Constant(F::from(x))), tx_type_indicator); + // We always need the `data` field, so extract it + // this function also asserts that the MPT proof is an inclusion proof + let data = chip.extract_field(ctx, tx_witness, data_list_idx); + let tx_witness = data.transaction_witness; // pass through + // the byte length of `data` + let data_len = data.len; + let data = data.field_bytes; + + let field_or_calldata_idx = ctx.load_witness(F::from(subquery.field_or_calldata_idx as u64)); + range.range_check(ctx, field_or_calldata_idx, FIELD_IDX_BITS); + // if `field_idx` < `TX_CALLDATA_IDX_OFFSET`, then it is an actual tx rlp list item + let is_idx_in_list = + is_lt_usize(ctx, range, field_or_calldata_idx, TX_TX_TYPE_FIELD_IDX, FIELD_IDX_BITS); + // if `field_or_calldata_idx` is not `field_idx`, set it to 1. (Don't put 0 because that's invalid field_idx for type 0 tx) + let field_idx = gate.select(ctx, field_or_calldata_idx, Constant(F::ONE), is_idx_in_list); + + let list_idx = v2_map_field_idx_by_tx_type(ctx, range, field_idx, tx_type_indicator); + let value = extract_truncated_field(ctx, range, &tx_witness, list_idx, SUBQUERY_OUTPUT_BYTES); + // we should left pad with 0s to fixed *unless* `field_idx == TX_DATA_FIELD_IDX` (or accessList but that is not supported) + let value_fixed = value.left_pad_to_fixed(ctx, gate); + let is_not_value_type = + gate.is_equal(ctx, field_idx, Constant(F::from(TX_DATA_FIELD_IDX as u64))); + let value = zip(value.bytes(), value_fixed.bytes()) + .map(|(var, fixed)| gate.select(ctx, *var, *fixed, is_not_value_type)) + .collect_vec(); + let value = SafeTypeChip::unsafe_to_fix_len_bytes_vec(value, SUBQUERY_OUTPUT_BYTES); + let mut value = pack_bytes_to_hilo(ctx, gate, value.bytes()); + + let block_number = ctx.load_witness(F::from(subquery.block_number)); + let tx_idx = tx_witness.idx; + // time to handle special cases: + let [return_tx_type, return_block_num, return_tx_index, return_function_selector, return_calldata_hash, return_data_length] = + [ + TX_TX_TYPE_FIELD_IDX, + TX_BLOCK_NUMBER_FIELD_IDX, + TX_TX_INDEX_FIELD_IDX, + TX_FUNCTION_SELECTOR_FIELD_IDX, + TX_CALLDATA_HASH_FIELD_IDX, + TX_DATA_LENGTH_FIELD_IDX, + ] + .map(|const_idx| is_equal_usize(ctx, gate, field_or_calldata_idx, const_idx)); + let const_zero = ctx.load_zero(); + let from_lo = |lo| HiLo::from_hi_lo([const_zero, lo]); + value = select_hi_lo(ctx, gate, &from_lo(tx_type), &value, return_tx_type); + value = select_hi_lo(ctx, gate, &from_lo(block_number), &value, return_block_num); + value = select_hi_lo(ctx, gate, &from_lo(tx_idx), &value, return_tx_index); + + // function selector case + // index of `to` in rlp list + let to_list_idx = + gate.select_by_indicator(ctx, [3, 4, 5].map(|x| Constant(F::from(x))), tx_type_indicator); + // the byte length of `to` + let to_len = gate.select_from_idx( + ctx, + tx_witness.value_witness().field_witness.iter().map(|w| w.field_len), + to_list_idx, + ); + let function_selector = { + // if `to_len == 0` && `data_len != 0` return `TX_CONTRACT_DEPLOY_SELECTOR` + let mut is_contract_deploy = gate.is_zero(ctx, to_len); + let empty_data = gate.is_zero(ctx, data_len); + is_contract_deploy = gate.mul_not(ctx, empty_data, is_contract_deploy); + // if `return_function_selector == 1` then `is_contractor_deploy || empty_data == 1` + let no_sel = gate.add(ctx, is_contract_deploy, empty_data); + let ret1 = gate.select( + ctx, + Constant(F::from(TX_CONTRACT_DEPLOY_SELECTOR_VALUE as u64)), + Constant(F::from(TX_NO_CALLDATA_SELECTOR_VALUE as u64)), + is_contract_deploy, + ); + + let len_gte_4 = is_gte_usize(ctx, range, data_len, 4, 32); + let selector_bytes = + data[..4].iter().map(|b| SafeTypeChip::unsafe_to_byte(*b)).collect_vec(); + let ret2 = bytes_be_to_uint(ctx, gate, &selector_bytes, 4); + // valid if no_sel || len_gte4 + let mut is_valid = gate.or(ctx, no_sel, len_gte_4); + is_valid = gate.select(ctx, is_valid, Constant(F::ONE), return_function_selector); + gate.assert_is_const(ctx, &is_valid, &F::ONE); + gate.select(ctx, ret1, ret2, no_sel) + }; + value = select_hi_lo(ctx, gate, &from_lo(function_selector), &value, return_function_selector); + value = select_hi_lo(ctx, gate, &from_lo(data_len), &value, return_data_length); + + let (data_buf, return_data) = handle_data(ctx, range, &data, data_len, field_or_calldata_idx); + value = select_hi_lo(ctx, gate, &data_buf, &value, return_data); + // dbg!(return_data.as_ref().value()); + + // calldata hash case + // IMPORTANT: we set the length to 0 so it doesn't actually request a keccak unless `return_calldata_hash == 1` + let tmp_data_len = gate.mul(ctx, data_len, return_calldata_hash); + let data_hash = chip.keccak().keccak_var_len(ctx, data.clone(), tmp_data_len, 0).hi_lo(); + value = select_hi_lo(ctx, gate, &HiLo::from_hi_lo(data_hash), &value, return_calldata_hash); + // dbg!(field_or_calldata_idx.value()); + // dbg!(value.hi_lo().map(|v| *v.value())); + + // is valid if either: + // - `is_idx_in_list` and `field_idx` is validated (validation done by `v2_map_field_idx_by_tx_type`) + // - `field_or_calldata_idx` is one of the special cases (individual validation was done per case) + // This sum is guaranteed to be 0 or 1 because the cases are mutually exclusive: + let is_special_case = gate.sum( + ctx, + [ + return_tx_type, + return_block_num, + return_tx_index, + return_function_selector, + return_calldata_hash, + return_data_length, + return_data, + ], + ); + let is_valid = gate.or(ctx, is_idx_in_list, is_special_case); + gate.assert_is_const(ctx, &is_valid, &F::ONE); + + PayloadTxSubquery { + tx_witness, + tx_root, + output: AssignedTxSubqueryResult { + subquery: AssignedTxSubquery { block_number, tx_idx, field_or_calldata_idx }, + value, + }, + } +} + +pub fn handle_single_tx_subquery_phase1( + ctx: RlcContextPair, + chip: &EthTransactionChip, + payload: PayloadTxSubquery, +) { + chip.parse_transaction_proof_phase1(ctx, payload.tx_witness); +} + +fn handle_data( + ctx: &mut Context, + range: &impl RangeInstructions, + data_bytes: &[AssignedValue], + data_len: AssignedValue, + field_idx: AssignedValue, +) -> (HiLo>, SafeBool) { + let gate = range.gate(); + let in_calldata_range = is_in_range( + ctx, + range, + field_idx, + TX_CALLDATA_IDX_OFFSET..TX_CONTRACT_DATA_IDX_OFFSET, + FIELD_IDX_BITS, + ); + let in_contract_data_range = + is_gte_usize(ctx, range, field_idx, TX_CONTRACT_DATA_IDX_OFFSET, FIELD_IDX_BITS); + + let calldata_shift = gate.sub(ctx, field_idx, Constant(F::from(TX_CALLDATA_IDX_OFFSET as u64))); + let contract_data_shift = + gate.sub(ctx, field_idx, Constant(F::from(TX_CONTRACT_DATA_IDX_OFFSET as u64))); + let mut shift = gate.select(ctx, calldata_shift, contract_data_shift, in_calldata_range); + // shift by 4 if in_calldata_range + let buffer = (0..data_bytes.len()) + .map(|i| { + if i + 4 < data_bytes.len() { + gate.select(ctx, data_bytes[i + 4], data_bytes[i], in_calldata_range) + } else { + gate.mul_not(ctx, in_calldata_range, data_bytes[i]) + } + }) + .collect_vec(); + let is_valid_calldata = + is_gte_usize(ctx, range, data_len, 4, bit_length(data_bytes.len() as u64)); + let mut buffer_len = gate.sub_mul(ctx, data_len, Constant(F::from(4)), in_calldata_range); + // if !is_valid_calldata && in_calldata_range, then set buffer_len = 0 (otherwise it's negative and will overflow) + let buffer_len_is_negative = gate.mul_not(ctx, is_valid_calldata, in_calldata_range); + buffer_len = gate.mul_not(ctx, buffer_len_is_negative, buffer_len); + // is_in_range = (in_calldata_range && is_valid_calldata) || in_contract_data_range + let is_in_range = + gate.mul_add(ctx, in_calldata_range, is_valid_calldata, in_contract_data_range); + shift = gate.mul(ctx, shift, is_in_range); + let (buffer, is_lt_len) = extract_array_chunk_and_constrain_trailing_zeros( + ctx, + range, + &buffer, + buffer_len, + shift, + 32, + FIELD_IDX_BITS, + ); + + let is_in_range = SafeTypeChip::unsafe_to_bool(gate.and(ctx, is_in_range, is_lt_len)); + let buffer = SafeTypeChip::unsafe_to_fix_len_bytes_vec(buffer, 32); + (pack_bytes_to_hilo(ctx, gate, buffer.bytes()), is_in_range) +} + +/// Extracts the field at `field_idx` from the given rlp list decomposition of a transaction. +/// The field is truncated to the first `truncated_byte_len` bytes. +/// +/// We do not use `EthTransactionChip::extract_field` because without the truncation the +/// select operation can be very expensive if the `data` field is very long. +pub fn extract_truncated_field( + ctx: &mut Context, + range: &impl RangeInstructions, + witness: &EthTransactionWitness, + list_idx: AssignedValue, + truncated_byte_len: usize, +) -> VarLenBytesVec { + let gate = range.gate(); + let tx_values = &witness.value_witness().field_witness; + let indicator = gate.idx_to_indicator(ctx, list_idx, TRANSACTION_MAX_FIELDS); + assert_eq!(tx_values.len(), TRANSACTION_MAX_FIELDS); + let const_zero = ctx.load_zero(); + let mut field_bytes = (0..truncated_byte_len) + .map(|i| { + let entries = tx_values.iter().map(|w| *w.field_cells.get(i).unwrap_or(&const_zero)); + gate.select_by_indicator(ctx, entries, indicator.clone()) + }) + .collect_vec(); + let lens = tx_values.iter().map(|w| w.field_len); + let mut len = gate.select_by_indicator(ctx, lens, indicator); + // len = min(len, truncated_byte_len) + let max_bytes = tx_values.iter().map(|w| w.field_cells.len()).max().unwrap(); + let max_bits = bit_length(max_bytes as u64); + len = min_with_usize(ctx, range, len, truncated_byte_len, max_bits); + + unsafe_constrain_trailing_zeros(ctx, gate, &mut field_bytes, len); + + SafeTypeChip::unsafe_to_var_len_bytes_vec(field_bytes, len, truncated_byte_len) +} + +// spreadsheet is easiest way to explain: https://docs.google.com/spreadsheets/d/1KoNZTr5vkcPTekzUzXCo0EycsbhuC6z-Z0vMtD4_9us/edit?usp=sharing +/// Constrains that `tx_type` is in [0,2]. +/// We do not allow accessList in V2. +fn v2_map_field_idx_by_tx_type( + ctx: &mut Context, + range: &impl RangeInstructions, + field_idx: AssignedValue, + tx_type_indicator: [AssignedValue; 3], +) -> AssignedValue { + let max_bits = FIELD_IDX_BITS; + let gate = range.gate(); + let const_invalid = Constant(F::from(99)); + // a lot of hardcoded stuff + let in_range = range.is_less_than(ctx, field_idx, Constant(F::from(12)), max_bits); + let is_gte_4 = is_gte_usize(ctx, range, field_idx, 4, max_bits); + let is_gas_price = gate.is_equal(ctx, field_idx, Constant(F::from(8))); + let type_0 = { + let is_gte_9 = is_gte_usize(ctx, range, field_idx, 9, max_bits); + let shift_three = is_gte_9.into(); + let is_lt_9 = gate.not(ctx, is_gte_9); + let shift_two = gate.and(ctx, is_gte_4, is_lt_9); + let shift_one = gate.is_equal(ctx, field_idx, Constant(F::ONE)); + let mut is_valid = gate.sum(ctx, [shift_one, shift_two, shift_three]); + is_valid = gate.and(ctx, is_valid, in_range); + let mut idx = field_idx; + // the three shifts are mutually exclusive + idx = gate.sub_mul(ctx, idx, shift_three, Constant(F::from(3))); + idx = gate.sub_mul(ctx, idx, shift_two, Constant(F::from(2))); + idx = gate.sub_mul(ctx, idx, shift_one, Constant(F::ONE)); + idx = gate.select(ctx, Constant(F::from(1)), idx, is_gas_price); + gate.select(ctx, idx, const_invalid, is_valid) + }; + let type_1 = { + let shift_one = is_gte_4; + let is_max_priority_fee = gate.is_equal(ctx, field_idx, Constant(F::from(2))); + let is_max_fee = gate.is_equal(ctx, field_idx, Constant(F::from(3))); + let mut is_valid = gate.mul_not(ctx, is_max_priority_fee, in_range); + is_valid = gate.mul_not(ctx, is_max_fee, is_valid); + let mut idx = gate.sub_mul(ctx, field_idx, shift_one, Constant(F::ONE)); + idx = gate.select(ctx, Constant(F::from(2)), idx, is_gas_price); + gate.select(ctx, idx, const_invalid, is_valid) + }; + let type_2 = { + let is_valid = gate.mul_not(ctx, is_gas_price, in_range); + gate.select(ctx, field_idx, const_invalid, is_valid) + }; + + let true_idx = gate.select_by_indicator(ctx, [type_0, type_1, type_2], tx_type_indicator); + let is_invalid = gate.is_equal(ctx, true_idx, const_invalid); + gate.assert_is_const(ctx, &is_invalid, &F::ZERO); + + true_idx +} diff --git a/axiom-query/src/components/subqueries/transaction/mod.rs b/axiom-query/src/components/subqueries/transaction/mod.rs new file mode 100644 index 00000000..5efdfa6e --- /dev/null +++ b/axiom-query/src/components/subqueries/transaction/mod.rs @@ -0,0 +1,53 @@ +/*! +# Transaction Subqueries Circuit + +Transaction subquery +* Raw EVM transaction layout reference: +* `blockNumber` (uint32) +* `txIdx` (uint16) +* `fieldOrCalldataIdx` (uint32) + * If in `[0, 100)` -- refers to the `fieldIdx` (**see below for definition**) + * If in `[100, infty)` -- represents `100 + calldataIdx`, where for a given value of `calldataIdx`, we return bytes `[4 + 32 * calldataIdx, 4 + 32 * calldataIdx + 32)`. This byte alignment is to return chunks of the ABI encoding. + * If in `[100000, infty)` -- represents `100000 + contractDataIdx`, where for a given value of `contractDataIdx`, we return bytes`[32 * contractDataIdx, 32 * contractDataIdx + 32)` + * Should be indexed so that the same `fieldIdx` represents the same thing across transaction types. + * We will add a special `TX_TYPE_FIELD_IDX` (51) for the transaction type. + * We will add a special `BLOCK_NUMBER_FIELD_IDX` (52) for the block number. + * We will add a special `TX_IDX_FIELD_IDX` (53) for the transaction index. + * We will add a special `FUNCTION_SELECTOR_FIELD_IDX` (54) for the function selector, which will return either the function selector (first 4 bytes of calldata) or one of 2 special values: + * If it’s a pure EOA transfer, then it will return `NO_CALLDATA_SELECTOR` (bytes32(61)). + * If it’s a contract deploy transaction, it will return `CONTRACT_DEPLOY_SELECTOR` (bytes32(60)) + * The `CALLDATA_FIELD_IDX` (55) corresponding to calldata will return the Keccak hash of the calldata. + * We will exclude access to access lists in V2. + * [Nice to have **(not yet supported)**] We will later add a special `TX_TX_HASH_FIELD_IDX` (56) for the transaction hash. + +The `fieldIdx` is defined as an enum that is **independent of transaction type**: + +| TxField | `fieldIdx` | +|------------------------|-------| +| ChainId | 0 | +| Nonce | 1 | +| MaxPriorityFeePerGas | 2 | +| MaxFeePerGas | 3 | +| GasLimit | 4 | +| To | 5 | +| Value | 6 | +| Data | 7 | +| GasPrice | 8 | +| v | 9 | +| r | 10 | +| s | 11 | + +This numbering is chosen so that for a Type 2 (EIP-1559) Transaction, the `fieldIdx` corresponds to the index of the field in the RLP list representing the transaction, with the exception that `accessList` at `fieldIdx = 8` is disabled in V2. For other transaction types, we still use the table above to determine the `fieldIdx` corresponding to a transaction field, and the ZK circuit will re-map to the correct RLP list index. +*/ + +/// Circuit and Component Implementation. +pub mod circuit; +/// Types +pub mod types; + +#[cfg(test)] +pub mod tests; + +/// The `fieldIdx` corresponding to the `data` field in a transaction. Note this definition is _independent_ +/// of the transaction type -- it is specific to the subquery spec. +const TX_DATA_FIELD_IDX: usize = 7; diff --git a/axiom-query/src/components/subqueries/transaction/tests.rs b/axiom-query/src/components/subqueries/transaction/tests.rs new file mode 100644 index 00000000..504c5dcb --- /dev/null +++ b/axiom-query/src/components/subqueries/transaction/tests.rs @@ -0,0 +1,251 @@ +use std::{marker::PhantomData, str::FromStr}; + +use axiom_codec::{ + special_values::{TX_CALLDATA_IDX_OFFSET, TX_FUNCTION_SELECTOR_FIELD_IDX}, + types::{ + field_elements::AnySubqueryResult, + native::{HeaderSubquery, TxSubquery}, + }, +}; +use axiom_eth::{ + block_header::TX_ROOT_INDEX, + halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}, + keccak::{promise::generate_keccak_shards_from_calls, types::ComponentTypeKeccak}, + mpt::MPTInput, + providers::{ + setup_provider, + transaction::{ + construct_tx_tries_from_full_blocks, get_tx_key_from_index, BlockWithTransactions, + }, + }, + transaction::{calc_max_val_len, EthTransactionChipParams, EthTransactionProof}, + utils::component::{ + promise_loader::single::PromiseLoaderParams, ComponentCircuit, + ComponentPromiseResultsInMerkle, ComponentType, + }, +}; +use cita_trie::Trie; +use ethers_core::types::{Chain, H256}; +use ethers_providers::Middleware; +use futures::future::join_all; +use itertools::Itertools; +use tokio; + +use crate::components::{ + dummy_rlc_circuit_params, + subqueries::{ + block_header::types::{ComponentTypeHeaderSubquery, OutputHeaderShard}, + common::shard_into_component_promise_results, + }, +}; + +use super::{ + circuit::{ComponentCircuitTxSubquery, CoreParamsTxSubquery}, + types::{CircuitInputTxShard, CircuitInputTxSubquery}, +}; + +/// transaction index is within u16, so rlp(txIndex) is at most 3 bytes => 6 nibbles +pub const TRANSACTION_PROOF_MAX_DEPTH: usize = 6; + +async fn test_mock_tx_subqueries( + k: u32, + network: Chain, + subqueries: Vec<(&str, usize)>, // txHash, field_or_calldata_idx + max_data_byte_len: usize, + max_access_list_len: usize, +) -> ComponentCircuitTxSubquery { + let _ = env_logger::builder().is_test(true).try_init(); + + let _provider = setup_provider(network); + let provider = &_provider; + let requests = join_all(subqueries.into_iter().map(|(tx_hash, field_idx)| async move { + let tx_hash = H256::from_str(tx_hash).unwrap(); + let tx = provider.get_transaction(tx_hash).await.unwrap().unwrap(); + let block_number = tx.block_number.unwrap().as_u32(); + let tx_idx = tx.transaction_index.unwrap().as_u32() as u16; + TxSubquery { block_number, tx_idx, field_or_calldata_idx: field_idx as u32 } + })) + .await; + + let block_nums = requests.iter().map(|r| r.block_number).sorted().dedup().collect_vec(); + let blocks = join_all(block_nums.iter().map(|&block_num| async move { + let block = provider.get_block_with_txs(block_num as u64).await.unwrap().unwrap(); + BlockWithTransactions::try_from(block).unwrap() + })) + .await; + + let promise_header = OutputHeaderShard { + results: blocks + .iter() + .map(|block| AnySubqueryResult { + subquery: HeaderSubquery { + block_number: block.number.as_u32(), + field_idx: TX_ROOT_INDEX as u32, + }, + value: block.transactions_root, + }) + .collect(), + }; + + let enable_types = [true, true, true]; + let tx_rlp_max_byte_len = + calc_max_val_len(max_data_byte_len, max_access_list_len, enable_types); + + let tx_tries = construct_tx_tries_from_full_blocks(blocks).unwrap(); + let mut requests_in_circuit = Vec::with_capacity(requests.len()); + for subquery in requests { + let block_number = subquery.block_number as u64; + let tx_idx = subquery.tx_idx as usize; + let tx_key = get_tx_key_from_index(tx_idx); + let db = tx_tries.get(&block_number).unwrap(); + let trie = &db.trie; + let tx_rlps = &db.tx_rlps; + let proof = trie.get_proof(&tx_key).unwrap(); + let value = tx_rlps.get(tx_idx).unwrap(); + let mpt_proof = MPTInput { + path: (&tx_key).into(), + value: value.to_vec(), + root_hash: db.root, + proof, + slot_is_empty: false, + value_max_byte_len: tx_rlp_max_byte_len, + max_depth: TRANSACTION_PROOF_MAX_DEPTH, + max_key_byte_len: 3, + key_byte_len: Some(tx_key.len()), + }; + let tx_proof = EthTransactionProof { tx_index: tx_idx, proof: mpt_proof }; + requests_in_circuit.push(CircuitInputTxSubquery { + block_number, + proof: tx_proof, + field_or_calldata_idx: subquery.field_or_calldata_idx, + }); + } + + let keccak_f_capacity = 200; + let header_capacity = promise_header.results.len(); + + let circuit_params = dummy_rlc_circuit_params(k as usize); + + let chip_params = EthTransactionChipParams { + max_data_byte_len, + max_access_list_len, + enable_types: [true, true, true], + network: None, + }; + let mut circuit = ComponentCircuitTxSubquery::new( + CoreParamsTxSubquery { + chip_params, + capacity: requests_in_circuit.len(), + max_trie_depth: TRANSACTION_PROOF_MAX_DEPTH, + }, + ( + PromiseLoaderParams::new_for_one_shard(keccak_f_capacity), + PromiseLoaderParams::new_for_one_shard(header_capacity), + ), + circuit_params, + ); + + let input = CircuitInputTxShard:: { requests: requests_in_circuit, _phantom: PhantomData }; + circuit.feed_input(Box::new(input)).unwrap(); + circuit.calculate_params(); + + let promises = [ + ( + ComponentTypeKeccak::::get_type_id(), + ComponentPromiseResultsInMerkle::from_single_shard( + generate_keccak_shards_from_calls(&circuit, keccak_f_capacity) + .unwrap() + .into_logical_results(), + ), + ), + ( + ComponentTypeHeaderSubquery::::get_type_id(), + shard_into_component_promise_results::>( + promise_header.into(), + ), + ), + ] + .into_iter() + .collect(); + circuit.fulfill_promise_results(&promises).unwrap(); + + let instances: Vec = circuit.get_public_instances().into(); + MockProver::run(k, &circuit, vec![instances]).unwrap().assert_satisfied(); + circuit +} + +#[tokio::test] +async fn test_mock_tx_subqueries_simple() { + let k = 18; + let subqueries = vec![ + ( + "0x7311924b41863b7d6dbd129dc0890903432a30af831f24697a5c5b522be5088f", + TX_FUNCTION_SELECTOR_FIELD_IDX, + ), // transfer + ( + "0x6a74ccb57011073104c89da78b02229e12c8a4160c5fe1c8278c6a73913ca289", + TX_FUNCTION_SELECTOR_FIELD_IDX, + ), + ( + "0x6a74ccb57011073104c89da78b02229e12c8a4160c5fe1c8278c6a73913ca289", + TX_CALLDATA_IDX_OFFSET, + ), + ( + "0x6a74ccb57011073104c89da78b02229e12c8a4160c5fe1c8278c6a73913ca289", + TX_CALLDATA_IDX_OFFSET + 1, + ), + ( + "0x9524b67377f7ff228fbe31c7edbfb4ba7bb374ceeac54030793b6727d1dc4505", + TX_FUNCTION_SELECTOR_FIELD_IDX, + ), + ]; + test_mock_tx_subqueries(k, Chain::Mainnet, subqueries, 200, 0).await; +} + +#[tokio::test] +async fn test_mock_tx_subqueries_legacy_vrs() { + let k = 18; + let subqueries = vec![ + ("0xb522100fc065547683da6b3fa5e755e721ffe5cd80f73153327cd5f403e6223c", 8), + ("0xb522100fc065547683da6b3fa5e755e721ffe5cd80f73153327cd5f403e6223c", 9), + ("0xb522100fc065547683da6b3fa5e755e721ffe5cd80f73153327cd5f403e6223c", 10), + ("0xb522100fc065547683da6b3fa5e755e721ffe5cd80f73153327cd5f403e6223c", 11), + ]; + test_mock_tx_subqueries(k, Chain::Sepolia, subqueries, 4000, 0).await; +} + +#[tokio::test] +async fn test_mock_tx_subqueries_eip4844() { + let k = 18; + let subqueries = vec![( + "0x740bbfb65e00b16e496757dfb1c8df1eea101cf9f559f519096d25904b7fa79b", + TX_FUNCTION_SELECTOR_FIELD_IDX, + )]; + test_mock_tx_subqueries(k, Chain::Mainnet, subqueries, 200, 0).await; +} + +#[cfg(feature = "keygen")] +#[tokio::test] +#[ignore] +async fn test_generate_tx_shard_pk() { + use axiom_eth::halo2_base::utils::{fs::read_params, halo2::ProvingKeyGenerator}; + + use crate::keygen::shard::ShardIntentTx; + + let core_params = CoreParamsTxSubquery { + chip_params: EthTransactionChipParams { + max_data_byte_len: 512, + max_access_list_len: 0, + enable_types: [true; 3], + network: None, + }, + capacity: 4, + max_trie_depth: TRANSACTION_PROOF_MAX_DEPTH, + }; + let loader_params = + (PromiseLoaderParams::new_for_one_shard(200), PromiseLoaderParams::new_for_one_shard(8)); + let k = 18; + let intent = ShardIntentTx { core_params, loader_params, k, lookup_bits: 8 }; + let kzg_params = read_params(k); + intent.create_pk_and_pinning(&kzg_params); +} diff --git a/axiom-query/src/components/subqueries/transaction/types.rs b/axiom-query/src/components/subqueries/transaction/types.rs new file mode 100644 index 00000000..b282cb8e --- /dev/null +++ b/axiom-query/src/components/subqueries/transaction/types.rs @@ -0,0 +1,123 @@ +//! Types are separated into: +//! - Circuit metadata that along with the circuit type determines the circuit configuration completely. +//! - Human readable _logical_ input and output to the circuit. These include private inputs and outputs that are only commited to in the public output. +//! - The in-circuit formatted versions of logical inputs and outputs. These include formatting in terms of field elements and accounting for all lengths needing to be fixed at compile time. +//! - We then provide conversion functions from human-readable to circuit formats. +//! - This circuit has no public instances (IO) other than the circuit's own component commitment and the promise commitments from any component calls. +use std::{marker::PhantomData, sync::Arc}; + +use axiom_codec::{ + types::{field_elements::FieldTxSubquery, native::TxSubquery}, + HiLo, +}; +use axiom_eth::{ + halo2_base::AssignedValue, + impl_fix_len_call_witness, + mpt::MPTInput, + transaction::{calc_max_val_len as tx_calc_max_val_len, EthTransactionProof}, + utils::{ + build_utils::dummy::DummyFrom, + component::{circuit::CoreBuilderInput, ComponentType, ComponentTypeId, LogicalResult}, + }, +}; +use cita_trie::{MemoryDB, PatriciaTrie, Trie}; +use ethers_core::types::{Transaction, H256}; +use hasher::HasherKeccak; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::subqueries::common::OutputSubqueryShard, utils::codec::AssignedTxSubquery, Field, +}; + +use super::circuit::CoreParamsTxSubquery; + +/// Identifier for the component type of this component circuit +pub struct ComponentTypeTxSubquery(PhantomData); + +/// Human readable. +/// The output value of any transaction subquery is always `bytes32` right now. +pub type OutputTxShard = OutputSubqueryShard; + +/// Circuit input for a shard of Tx subqueries. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CircuitInputTxShard { + /// Enriched subquery requests + pub requests: Vec, + pub _phantom: PhantomData, +} + +/// Circuit input for a single Tx subquery. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CircuitInputTxSubquery { + /// The block number to access the storage state at. + pub block_number: u64, + /// Transaction proof formatted as [axiom_eth::mpt::MPTInput]. Contains the transaction index. + pub proof: EthTransactionProof, + /// Special index to specify what subquery value to extract from the transaction. + pub field_or_calldata_idx: u32, +} + +impl DummyFrom for CircuitInputTxShard { + fn dummy_from(core_params: CoreParamsTxSubquery) -> Self { + let CoreParamsTxSubquery { chip_params, capacity, max_trie_depth } = core_params; + let mut trie = + PatriciaTrie::new(Arc::new(MemoryDB::new(true)), Arc::new(HasherKeccak::new())); + let tx = Transaction::default(); + let tx_rlp = tx.rlp().to_vec(); + trie.insert(vec![0x80], tx_rlp.clone()).unwrap(); + let mpt_input = MPTInput { + path: (&[0x80]).into(), + value: tx_rlp, + root_hash: Default::default(), + proof: trie.get_proof(&[0x80]).unwrap(), + value_max_byte_len: tx_calc_max_val_len( + chip_params.max_data_byte_len, + chip_params.max_access_list_len, + chip_params.enable_types, + ), + max_depth: max_trie_depth, + slot_is_empty: false, + max_key_byte_len: 3, + key_byte_len: Some(1), + }; + let tx_pf = EthTransactionProof { tx_index: 0, proof: mpt_input }; + let dummy_subquery = + CircuitInputTxSubquery { block_number: 0, proof: tx_pf, field_or_calldata_idx: 0 }; + Self { requests: vec![dummy_subquery; capacity], _phantom: PhantomData } + } +} + +/// The output value of any storage subquery is always `bytes32` right now. +/// Vector has been resized to the capacity. +pub type CircuitOutputTxShard = OutputSubqueryShard, HiLo>; + +impl_fix_len_call_witness!(FieldTxSubqueryCall, FieldTxSubquery, ComponentTypeTxSubquery); + +// ===== The storage component has no public instances other than the component commitment and promise commitments from external component calls ===== + +impl ComponentType for ComponentTypeTxSubquery { + type InputValue = FieldTxSubquery; + type InputWitness = AssignedTxSubquery; + type OutputValue = HiLo; + type OutputWitness = HiLo>; + type LogicalInput = FieldTxSubquery; + + fn get_type_id() -> ComponentTypeId { + "axiom-query:ComponentTypeTxSubquery".to_string() + } + + fn logical_result_to_virtual_rows_impl( + ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + vec![(ins.input, ins.output)] + } + fn logical_input_to_virtual_rows_impl(li: &Self::LogicalInput) -> Vec { + vec![*li] + } +} + +impl From for CircuitOutputTxShard { + fn from(output: OutputTxShard) -> Self { + output.convert_into() + } +} diff --git a/axiom-query/src/global_constants.rs b/axiom-query/src/global_constants.rs new file mode 100644 index 00000000..7c039809 --- /dev/null +++ b/axiom-query/src/global_constants.rs @@ -0,0 +1,2 @@ +pub const ENABLE_ALL_TX_TYPES: [bool; 3] = [true, true, true]; +pub const RECEIPT_TOPIC_BOUNDS: (usize, usize) = (0, 4); diff --git a/axiom-query/src/keygen/agg/axiom_agg_1.rs b/axiom-query/src/keygen/agg/axiom_agg_1.rs new file mode 100644 index 00000000..d78d6813 --- /dev/null +++ b/axiom-query/src/keygen/agg/axiom_agg_1.rs @@ -0,0 +1,200 @@ +use std::{collections::BTreeMap, path::Path, sync::Arc}; + +use axiom_eth::{ + halo2_base::gates::circuit::CircuitBuilderStage, + halo2_proofs::{plonk::ProvingKey, poly::kzg::commitment::ParamsKZG}, + halo2curves::bn256::{Bn256, G1Affine}, + snark_verifier::verifier::plonk::PlonkProtocol, + snark_verifier_sdk::{ + halo2::{ + aggregation::AggregationConfigParams, + utils::{ + AggregationDependencyIntent, AggregationDependencyIntentOwned, + KeygenAggregationCircuitIntent, + }, + }, + Snark, + }, + utils::{ + build_utils::{ + aggregation::get_dummy_aggregation_params, + keygen::{compile_agg_dep_to_protocol, read_srs_from_dir}, + pinning::aggregation::AggTreeId, + }, + snark_verifier::EnhancedSnark, + }, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +use crate::{ + axiom_aggregation1::types::{InputAxiomAggregation1, FINAL_AGG_VKEY_HASH_IDX}, + keygen::{ + shard::{keccak::ShardIntentKeccak, CircuitIntentVerifyCompute}, + ProvingKeySerializer, SupportedPinning, + }, + subquery_aggregation::types::SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX, +}; + +use super::{ + common::{parse_agg_intent, ForceBasicConfigParams}, + impl_keygen_intent_for_aggregation, impl_pkey_serializer_for_aggregation, + single_type::IntentTreeSingleType, + subquery_agg::RecursiveSubqueryAggIntent, +}; + +/// ** !! IMPORTANT !! ** +/// Do not change the order of this enum, which determines how inputs are parsed. +// Determines order of circuit IDs in `to_agg` +#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum AxiomAgg1InputSnark { + VerifyCompute, + SubqueryAgg, + Keccak, +} + +#[serde_as] +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct AxiomAgg1Params { + /// The compiled verification keys of the dependency circuits to aggregate. + /// Since Axiom Aggregation 1 is universal aggregation, we remove the `domain` and `preprocessed` from `PlonkProtocol` since those + /// are loaded as witnesses. + #[serde_as(as = "BTreeMap<_, axiom_eth::utils::snark_verifier::Base64Bytes>")] + pub to_agg: BTreeMap>, + pub agg_params: AggregationConfigParams, +} + +/// Only implements [ProvingKeySerializer] and not [KeygenCircuitIntent]. +#[derive(Serialize, Deserialize)] +pub struct RecursiveAxiomAgg1Intent { + pub intent_verify_compute: CircuitIntentVerifyCompute, + pub intent_subquery_agg: RecursiveSubqueryAggIntent, + pub intent_keccak: IntentTreeSingleType, + pub k: u32, + /// For different versions of this circuit to be aggregated by the same universal aggregation circuit, + /// we may wish to force configure the circuit to have a certain number of columns without auto-configuration. + #[serde(skip_serializing_if = "Option::is_none")] + pub force_params: Option, +} + +/// Non-recursive intent. Currently only used internally as an intermediary for recursive intent. +/// This will implement [KeygenCircuitIntent] where the pinning is not "wrapped" into an enum. +/// The pinning type is `GenericAggPinning`. +#[derive(Clone, Debug)] +struct AxiomAgg1Intent { + // This is from bad UX; only svk = kzg_params.get_g()[0] is used + pub kzg_params: Arc>, + /// For passing to AxiomAgg1Params via macro + pub to_agg: BTreeMap>, + /// Circuit intents for snarks to be aggregated by Axiom Aggregation 1 + pub deps: BTreeMap, + /// The log_2 domain size of the current aggregation circuit + pub k: u32, + /// For different versions of this circuit to be aggregated by the same universal aggregation circuit, + /// we may wish to force configure the circuit to have a certain number of columns without auto-configuration. + pub force_params: Option, +} + +impl AxiomAgg1Intent { + pub fn children(&self) -> Vec { + self.deps.values().map(|(tree, _)| tree.clone()).collect() + } +} + +impl KeygenAggregationCircuitIntent for AxiomAgg1Intent { + fn intent_of_dependencies(&self) -> Vec { + self.deps.values().map(|(_, d)| d.into()).collect() + } + fn build_keygen_circuit_from_snarks(self, snarks: Vec) -> Self::AggregationCircuit { + let mut deps = BTreeMap::from_iter(self.deps.keys().cloned().zip_eq(snarks)); + let snark_verify_compute = deps.remove(&AxiomAgg1InputSnark::VerifyCompute).unwrap(); + let snark_subquery_agg = deps.remove(&AxiomAgg1InputSnark::SubqueryAgg).unwrap(); + let snark_keccak_agg = deps.remove(&AxiomAgg1InputSnark::Keccak).unwrap(); + + // No agg_vkey_hash_idx because the compute_snark is separately tagged by the query_schema + let snark_verify_compute = EnhancedSnark::new(snark_verify_compute, None); + let snark_subquery_agg = + EnhancedSnark::new(snark_subquery_agg, Some(SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX)); + let snark_keccak_agg = EnhancedSnark::new(snark_keccak_agg, None); + + let input = + InputAxiomAggregation1 { snark_verify_compute, snark_subquery_agg, snark_keccak_agg }; + let mut force = false; + let agg_params = if let Some(force_params) = self.force_params { + force = true; + force_params.into_agg_params(self.k) + } else { + get_dummy_aggregation_params(self.k as usize) + }; + let mut circuit = + input.build(CircuitBuilderStage::Keygen, agg_params, &self.kzg_params).unwrap(); + if !force { + circuit.calculate_params(Some(20)); + } + circuit + } +} + +impl_keygen_intent_for_aggregation!( + AxiomAgg1Intent, + AxiomAgg1Params, + Some(FINAL_AGG_VKEY_HASH_IDX) +); +impl_pkey_serializer_for_aggregation!(AxiomAgg1Intent, AxiomAgg1Params, AxiomAgg1); + +impl ProvingKeySerializer for RecursiveAxiomAgg1Intent { + fn create_and_serialize_proving_key( + self, + params_dir: &Path, + data_dir: &Path, + ) -> anyhow::Result<(AggTreeId, ProvingKey, SupportedPinning)> { + let mut deps = BTreeMap::new(); + fn process_intent( + key: AxiomAgg1InputSnark, + intent: impl ProvingKeySerializer, + params_dir: &Path, + data_dir: &Path, + deps: &mut BTreeMap, + ) -> anyhow::Result<()> { + let (tree_id, pk, pinning) = + intent.create_and_serialize_proving_key(params_dir, data_dir)?; + deps.insert(key, (tree_id, parse_agg_intent(pk.get_vk(), pinning))); + Ok(()) + } + process_intent( + AxiomAgg1InputSnark::VerifyCompute, + self.intent_verify_compute, + params_dir, + data_dir, + &mut deps, + )?; + process_intent( + AxiomAgg1InputSnark::SubqueryAgg, + self.intent_subquery_agg, + params_dir, + data_dir, + &mut deps, + )?; + process_intent( + AxiomAgg1InputSnark::Keccak, + self.intent_keccak, + params_dir, + data_dir, + &mut deps, + )?; + let kzg_params = Arc::new(read_srs_from_dir(params_dir, self.k)?); + let to_agg = deps + .iter() + .map(|(&k, (_, dep))| (k, compile_agg_dep_to_protocol(&kzg_params, dep, true))) + .collect(); + let axiom_agg1_intent = AxiomAgg1Intent { + to_agg, + deps, + k: self.k, + kzg_params, + force_params: self.force_params, + }; + axiom_agg1_intent.create_and_serialize_proving_key(params_dir, data_dir) + } +} diff --git a/axiom-query/src/keygen/agg/axiom_agg_2.rs b/axiom-query/src/keygen/agg/axiom_agg_2.rs new file mode 100644 index 00000000..937de69e --- /dev/null +++ b/axiom-query/src/keygen/agg/axiom_agg_2.rs @@ -0,0 +1,143 @@ +use std::{path::Path, sync::Arc}; + +use axiom_eth::{ + halo2_base::gates::circuit::CircuitBuilderStage, + halo2_proofs::{plonk::ProvingKey, poly::kzg::commitment::ParamsKZG}, + halo2curves::bn256::{Bn256, G1Affine}, + snark_verifier::verifier::plonk::PlonkProtocol, + snark_verifier_sdk::{ + halo2::{ + aggregation::AggregationConfigParams, + utils::{ + AggregationDependencyIntent, AggregationDependencyIntentOwned, + KeygenAggregationCircuitIntent, + }, + }, + Snark, + }, + utils::{ + build_utils::{ + aggregation::get_dummy_aggregation_params, + keygen::{compile_agg_dep_to_protocol, read_srs_from_dir}, + pinning::aggregation::AggTreeId, + }, + snark_verifier::EnhancedSnark, + }, +}; +use ethers_core::types::Address; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +use crate::{ + axiom_aggregation1::types::FINAL_AGG_VKEY_HASH_IDX, + axiom_aggregation2::circuit::InputAxiomAggregation2, + keygen::{ProvingKeySerializer, SupportedPinning}, +}; + +use super::{ + axiom_agg_1::RecursiveAxiomAgg1Intent, + common::{parse_agg_intent, ForceBasicConfigParams}, + impl_keygen_intent_for_aggregation, impl_pkey_serializer_for_aggregation, +}; + +#[serde_as] +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct AxiomAgg2Params { + /// The compiled verification key of the dependency circuit to aggregate. + /// Since Axiom Aggregation 2 is universal aggregation, we remove the `domain` and `preprocessed` from `PlonkProtocol` since those + /// are loaded as witnesses. + #[serde_as(as = "Box")] + pub to_agg: Box>, + pub agg_params: AggregationConfigParams, +} + +/// Only implements [ProvingKeySerializer] and not [KeygenCircuitIntent]. +#[derive(Serialize, Deserialize)] +pub struct RecursiveAxiomAgg2Intent { + pub axiom_agg1_intent: RecursiveAxiomAgg1Intent, + pub k: u32, + /// Force the number of columns in the axiom agg 2 circuit. + #[serde(skip_serializing_if = "Option::is_none")] + pub force_params: Option, +} + +/// Non-recursive intent. Currently only used internally as an intermediary for recursive intent. +/// This will implement [KeygenCircuitIntent] where the pinning is not "wrapped" into an enum. +/// The pinning type is `GenericAggPinning`. +#[derive(Clone, Debug)] +struct AxiomAgg2Intent { + // This is from bad UX; only svk = kzg_params.get_g()[0] is used + pub kzg_params: Arc>, + /// Circuit ID of the Axiom Aggregation 1 circuit + pub child: AggTreeId, + /// For passing to AxiomAgg2Params via macro + pub to_agg: Box>, + /// Circuit intent for Axiom Aggregation 1 circuit to be aggregated by Axiom Aggregation 2 + pub axiom_agg1_intent: AggregationDependencyIntentOwned, + /// The log_2 domain size of the current aggregation circuit + pub k: u32, + /// Force the number of columns in the axiom agg 2 circuit. + pub force_params: Option, +} + +impl AxiomAgg2Intent { + pub fn children(&self) -> Vec { + vec![self.child.clone()] + } +} + +impl KeygenAggregationCircuitIntent for AxiomAgg2Intent { + fn intent_of_dependencies(&self) -> Vec { + vec![(&self.axiom_agg1_intent).into()] + } + fn build_keygen_circuit_from_snarks(self, mut snarks: Vec) -> Self::AggregationCircuit { + let snark_axiom_agg1 = snarks.pop().unwrap(); + let snark_axiom_agg1 = EnhancedSnark::new(snark_axiom_agg1, Some(FINAL_AGG_VKEY_HASH_IDX)); + + let payee = Address::zero(); // dummy + let input = InputAxiomAggregation2 { snark_axiom_agg1, payee }; + let mut force = false; + let agg_params = if let Some(force_params) = self.force_params { + force = true; + force_params.into_agg_params(self.k) + } else { + get_dummy_aggregation_params(self.k as usize) + }; + let mut circuit = + input.build(CircuitBuilderStage::Keygen, agg_params, &self.kzg_params).unwrap(); + if !force { + circuit.calculate_params(Some(20)); + } + circuit + } +} + +impl_keygen_intent_for_aggregation!( + AxiomAgg2Intent, + AxiomAgg2Params, + Some(FINAL_AGG_VKEY_HASH_IDX) +); +impl_pkey_serializer_for_aggregation!(AxiomAgg2Intent, AxiomAgg2Params, AxiomAgg2); + +impl ProvingKeySerializer for RecursiveAxiomAgg2Intent { + fn create_and_serialize_proving_key( + self, + params_dir: &Path, + data_dir: &Path, + ) -> anyhow::Result<(AggTreeId, ProvingKey, SupportedPinning)> { + let (agg1_tree_id, pk, pinning) = + self.axiom_agg1_intent.create_and_serialize_proving_key(params_dir, data_dir)?; + let axiom_agg1_intent = parse_agg_intent(pk.get_vk(), pinning); + let kzg_params = Arc::new(read_srs_from_dir(params_dir, self.k)?); + let to_agg = compile_agg_dep_to_protocol(&kzg_params, &axiom_agg1_intent, true); + let axiom_agg2_intent = AxiomAgg2Intent { + kzg_params, + child: agg1_tree_id, + axiom_agg1_intent, + to_agg: Box::new(to_agg), + k: self.k, + force_params: self.force_params, + }; + axiom_agg2_intent.create_and_serialize_proving_key(params_dir, data_dir) + } +} diff --git a/axiom-query/src/keygen/agg/common.rs b/axiom-query/src/keygen/agg/common.rs new file mode 100644 index 00000000..61263052 --- /dev/null +++ b/axiom-query/src/keygen/agg/common.rs @@ -0,0 +1,105 @@ +use axiom_eth::{ + halo2_proofs::plonk::VerifyingKey, + halo2curves::{ + bn256::{Fr, G1Affine}, + ff::PrimeField, + }, + snark_verifier_sdk::halo2::{ + aggregation::AggregationConfigParams, utils::AggregationDependencyIntentOwned, + }, + utils::build_utils::pinning::aggregation::GenericAggPinning, +}; +use enum_dispatch::enum_dispatch; +use hex::FromHex; +use serde::{Deserialize, Serialize}; + +/// Fields of [`AggregationConfigParams`] besides `degree` and `lookup_bits`. +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct ForceBasicConfigParams { + pub num_advice: usize, + pub num_lookup_advice: usize, + pub num_fixed: usize, +} + +impl ForceBasicConfigParams { + pub fn into_agg_params(self, k: u32) -> AggregationConfigParams { + AggregationConfigParams { + degree: k, + lookup_bits: k as usize - 1, + num_advice: self.num_advice, + num_lookup_advice: self.num_lookup_advice, + num_fixed: self.num_fixed, + } + } +} + +// enum_dispatch cannot cross crates, so we need to define the trait here +/// Trait for a pinning for a node in the aggregation tree. It may or may not be for an aggregation circuit. +#[enum_dispatch] +pub trait AggTreePinning { + fn num_instance(&self) -> Vec; + fn accumulator_indices(&self) -> Option>; + /// Aggregate vk hash, if universal aggregation circuit. + /// * ((i, j), agg_vkey_hash), where the hash is located at (i, j) in the public instance columns + fn agg_vk_hash_data(&self) -> Option<((usize, usize), axiom_eth::halo2curves::bn256::Fr)>; +} + +pub fn parse_agg_intent( + vk: &VerifyingKey, + pinning: impl AggTreePinning, +) -> AggregationDependencyIntentOwned { + let num_instance = pinning.num_instance(); + let accumulator_indices = pinning.accumulator_indices(); + AggregationDependencyIntentOwned { + vk: vk.clone(), + num_instance, + accumulator_indices, + agg_vk_hash_data: pinning.agg_vk_hash_data(), + } +} + +impl AggTreePinning for GenericAggPinning { + fn num_instance(&self) -> Vec { + self.num_instance.clone() + } + fn accumulator_indices(&self) -> Option> { + Some(self.accumulator_indices.clone()) + } + fn agg_vk_hash_data(&self) -> Option<((usize, usize), Fr)> { + // agg_vk_hash in pinning is represented in big endian for readability + self.agg_vk_hash_data.as_ref().map(|((i, j), hash_str)| { + assert_eq!(&hash_str[..2], "0x"); + let mut bytes_be = Vec::from_hex(&hash_str[2..]).unwrap(); + bytes_be.reverse(); + let bytes_le = bytes_be; + let agg_vkey_hash = Fr::from_repr(bytes_le.try_into().unwrap()).unwrap(); + ((*i, *j), agg_vkey_hash) + }) + } +} + +#[cfg(test)] +#[test] +fn test_parse_agg_intent() { + use axiom_eth::{ + halo2curves::{bn256::G2Affine, ff::Field}, + utils::build_utils::{ + aggregation::get_dummy_aggregation_params, pinning::aggregation::GenericAggParams, + }, + }; + use rand::rngs::StdRng; + use rand_core::SeedableRng; + + let params = GenericAggParams { to_agg: vec![], agg_params: get_dummy_aggregation_params(10) }; + let mut rng = StdRng::seed_from_u64(0); + let agg_vk_hash = Fr::random(&mut rng); + let agg_pinning = GenericAggPinning { + params, + num_instance: vec![1], + accumulator_indices: (0..12).map(|j| (0, j)).collect(), + agg_vk_hash_data: Some(((0, 0), format!("{:?}", agg_vk_hash))), + dk: (G1Affine::generator(), G2Affine::generator(), G2Affine::random(&mut rng)).into(), + break_points: Default::default(), + }; + assert_eq!(agg_pinning.agg_vk_hash_data(), Some(((0, 0), agg_vk_hash))); +} diff --git a/axiom-query/src/keygen/agg/mod.rs b/axiom-query/src/keygen/agg/mod.rs new file mode 100644 index 00000000..b8002d86 --- /dev/null +++ b/axiom-query/src/keygen/agg/mod.rs @@ -0,0 +1,127 @@ +use axiom_eth::utils::build_utils::pinning::aggregation::{GenericAggParams, GenericAggPinning}; +use enum_dispatch::enum_dispatch; +use serde::{Deserialize, Serialize}; + +use self::{ + axiom_agg_1::AxiomAgg1Params, axiom_agg_2::AxiomAgg2Params, common::AggTreePinning, + subquery_agg::SubqueryAggParams, +}; + +pub mod axiom_agg_1; +pub mod axiom_agg_2; +pub mod common; +pub mod single_type; +pub mod subquery_agg; + +/// ** !! IMPORTANT !! ** +/// Enum names are used to deserialize the pinning file. Please be careful if you need renaming. +#[derive(Serialize, Deserialize, Clone)] +#[enum_dispatch(AggTreePinning)] +pub enum SupportedAggPinning { + AxiomAgg1(GenericAggPinning), + AxiomAgg2(GenericAggPinning), + SingleTypeAggregation(GenericAggPinning), + SubqueryAggregation(GenericAggPinning), +} + +/// # Assumptions +/// * $agg_params must have exactly the fields `to_agg, svk, agg_params`. +/// * $agg_intent must have a `to_agg` field of exactly the same type as $agg_params. +macro_rules! impl_keygen_intent_for_aggregation { + ($agg_intent:ty, $agg_params:ident, $agg_vk_hash_idx:expr) => { + impl + axiom_eth::halo2_base::utils::halo2::KeygenCircuitIntent< + axiom_eth::halo2curves::bn256::Fr, + > for $agg_intent + { + type ConcreteCircuit = + axiom_eth::snark_verifier_sdk::halo2::aggregation::AggregationCircuit; + /// We omit here tags (e.g., hash of vkeys) of the dependencies, they should be recorded separately. + /// * The first coordinate is the `svk = kzg_params.get_g()[0]` of the KZG trusted setup used. + /// * The second coordinate is `num_instance`. + type Pinning = + axiom_eth::utils::build_utils::pinning::aggregation::GenericAggPinning<$agg_params>; + fn get_k(&self) -> u32 { + self.k + } + fn build_keygen_circuit(self) -> Self::ConcreteCircuit { + self.build_keygen_circuit_shplonk() + } + fn get_pinning_after_keygen( + self, + kzg_params: &axiom_eth::halo2_proofs::poly::kzg::commitment::ParamsKZG< + axiom_eth::halo2curves::bn256::Bn256, + >, + circuit: &Self::ConcreteCircuit, + ) -> Self::Pinning { + use axiom_eth::halo2_proofs::poly::commitment::ParamsProver; + use axiom_eth::snark_verifier_sdk::CircuitExt; + use axiom_eth::utils::build_utils::pinning::{ + aggregation::GenericAggPinning, CircuitPinningInstructions, + }; + use axiom_eth::utils::snark_verifier::NUM_FE_ACCUMULATOR; + let pinning = circuit.pinning(); + let agg_params = $agg_params { to_agg: self.to_agg, agg_params: pinning.params }; + let svk = kzg_params.get_g()[0]; + let dk = (svk, kzg_params.g2(), kzg_params.s_g2()); + let agg_vk_hash_data = $agg_vk_hash_idx.map(|idx| { + let i = 0; + let j = NUM_FE_ACCUMULATOR + idx; + let agg_vk_hash = format!("{:?}", circuit.instances()[i][j]); + ((i, j), agg_vk_hash) + }); + GenericAggPinning { + params: agg_params, + num_instance: circuit.num_instance(), + accumulator_indices: Self::ConcreteCircuit::accumulator_indices().unwrap(), + agg_vk_hash_data, + dk: dk.into(), + break_points: pinning.break_points, + } + } + } + }; +} + +/// A macro to auto-implement ProvingKeySerializer, where you specify which enum variant of SupportedAggPinning you want to use via $agg_pinning_name. +/// # Assumptions +/// * $agg_intent must have a `children()` function that returns `Vec` to give the child aggregation trees. +macro_rules! impl_pkey_serializer_for_aggregation { + ($agg_intent:ty, $agg_params:ty, $agg_pinning_name:ident) => { + impl crate::keygen::ProvingKeySerializer for $agg_intent { + fn create_and_serialize_proving_key( + self, + params_dir: &std::path::Path, + data_dir: &std::path::Path, + ) -> anyhow::Result<( + axiom_eth::utils::build_utils::pinning::aggregation::AggTreeId, + axiom_eth::halo2_proofs::plonk::ProvingKey, + crate::keygen::SupportedPinning, + )> { + use crate::keygen::{agg::SupportedAggPinning, SupportedPinning}; + use axiom_eth::halo2_base::utils::halo2::{ + KeygenCircuitIntent, ProvingKeyGenerator, + }; + use axiom_eth::utils::build_utils::{ + keygen::{read_srs_from_dir, write_pk_and_pinning}, + pinning::aggregation::{AggTreeId, GenericAggPinning}, + }; + let k = self.get_k(); + let children = self.children(); + let kzg_params = read_srs_from_dir(params_dir, k)?; + let (pk, pinning_json) = self.create_pk_and_pinning(&kzg_params); + let pinning: GenericAggPinning<$agg_params> = serde_json::from_value(pinning_json)?; + let aggregate_vk_hash = pinning.agg_vk_hash_data.as_ref().map(|x| x.1.clone()); + let pinning = + SupportedPinning::Agg(SupportedAggPinning::$agg_pinning_name(pinning)); + let circuit_id = + write_pk_and_pinning(data_dir, &pk, &serde_json::to_value(&pinning)?)?; + let tree_id = AggTreeId { circuit_id, children, aggregate_vk_hash }; + Ok((tree_id, pk, pinning)) + } + } + }; +} + +pub(crate) use impl_keygen_intent_for_aggregation; +pub(crate) use impl_pkey_serializer_for_aggregation; diff --git a/axiom-query/src/keygen/agg/single_type.rs b/axiom-query/src/keygen/agg/single_type.rs new file mode 100644 index 00000000..d2ede6c3 --- /dev/null +++ b/axiom-query/src/keygen/agg/single_type.rs @@ -0,0 +1,111 @@ +use std::{path::Path, sync::Arc}; + +use axiom_eth::{ + halo2_base::utils::halo2::ProvingKeyGenerator, + halo2_proofs::plonk::ProvingKey, + halo2curves::bn256::G1Affine, + utils::{ + build_utils::{ + keygen::{read_srs_from_dir, write_pk_and_pinning}, + pinning::aggregation::{AggTreeId, GenericAggParams, GenericAggPinning}, + }, + merkle_aggregation::keygen::AggIntentMerkle, + }, +}; +use enum_dispatch::enum_dispatch; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +use crate::keygen::{ + shard::{ + ShardIntentAccount, ShardIntentHeader, ShardIntentReceipt, ShardIntentResultsRoot, + ShardIntentSolidityMapping, ShardIntentStorage, ShardIntentTx, + }, + ProvingKeySerializer, ShardIntentECDSA, SupportedPinning, +}; + +use super::{common::parse_agg_intent, SupportedAggPinning}; + +#[derive(Serialize, Deserialize)] +#[enum_dispatch(ProvingKeySerializer)] +pub enum SupportedIntentTreeSingleType { + Header(IntentTreeSingleType), + Account(IntentTreeSingleType), + Storage(IntentTreeSingleType), + Tx(IntentTreeSingleType), + Receipt(IntentTreeSingleType), + SolidityMapping(IntentTreeSingleType), + ECDSA(IntentTreeSingleType), + ResultsRoot(IntentTreeSingleType), +} + +/// Node in tree of aggregation intents. This is a complete tree +/// where the leaves all have the same configuration intent. +/// +/// If we have a recursive chain `node0 -> node1 -> ... -> node_{m-1} -> leaf`, +/// then the tree has depth `m` and `node0.num_children * node1.num_children * ... * node_{m-1}.num_children` total leaves. +#[derive(Clone, Serialize, Deserialize)] +pub enum IntentTreeSingleType { + Leaf(LeafIntent), + Node(IntentNodeSingleType), +} + +/// Node in aggregation tree which has `num_children` children (dependencies). +/// Each child has the same intent `child_intent`. +/// This struct is typeless and needs to be deserialized. +/// +/// Typical use case: deserialize `child_intent` to `IntentTreeSingleType` +/// for recursive struct. +#[derive(Clone, Serialize, Deserialize)] +pub struct IntentNodeSingleType { + /// log_2 domain size of the current aggregation circuit + pub k: u32, + /// Must be a power of 2 + pub num_children: usize, + pub child_intent: serde_json::Value, +} + +impl ProvingKeySerializer for IntentTreeSingleType +where + LeafIntent: ProvingKeySerializer + DeserializeOwned, +{ + /// ## Assumptions + /// - All pinnings have a "num_instance" field. + fn create_and_serialize_proving_key( + self, + params_dir: &Path, + data_dir: &Path, + ) -> anyhow::Result<(AggTreeId, ProvingKey, SupportedPinning)> { + match self { + IntentTreeSingleType::Leaf(intent) => { + intent.create_and_serialize_proving_key(params_dir, data_dir) + } + IntentTreeSingleType::Node(node) => { + let child_intent: IntentTreeSingleType = + serde_json::from_value(node.child_intent).unwrap(); + // Recursive call + let (child_id, child_pk, child_pinning) = + child_intent.create_and_serialize_proving_key(params_dir, data_dir)?; + let child_intent = parse_agg_intent(child_pk.get_vk(), child_pinning); + let kzg_params = Arc::new(read_srs_from_dir(params_dir, node.k)?); + let agg_intent = AggIntentMerkle { + kzg_params: kzg_params.clone(), + to_agg: vec![child_id.clone(); node.num_children], + deps: vec![child_intent; node.num_children], + k: node.k, + }; + let (pk, pinning_json) = agg_intent.create_pk_and_pinning(&kzg_params); + let pinning: GenericAggPinning = + serde_json::from_value(pinning_json)?; + let pinning = + SupportedPinning::Agg(SupportedAggPinning::SingleTypeAggregation(pinning)); + let circuit_id = write_pk_and_pinning(data_dir, &pk, &pinning)?; + let tree_id = AggTreeId { + circuit_id, + children: vec![child_id; node.num_children], + aggregate_vk_hash: None, + }; + Ok((tree_id, pk, pinning)) + } + } + } +} diff --git a/axiom-query/src/keygen/agg/subquery_agg.rs b/axiom-query/src/keygen/agg/subquery_agg.rs new file mode 100644 index 00000000..330d32e5 --- /dev/null +++ b/axiom-query/src/keygen/agg/subquery_agg.rs @@ -0,0 +1,203 @@ +use std::{collections::BTreeMap, path::Path, sync::Arc}; + +use axiom_eth::{ + halo2_base::gates::circuit::CircuitBuilderStage, + halo2_proofs::{plonk::ProvingKey, poly::kzg::commitment::ParamsKZG}, + halo2curves::bn256::{Bn256, Fr, G1Affine}, + snark_verifier::verifier::plonk::PlonkProtocol, + snark_verifier_sdk::{ + halo2::{ + aggregation::AggregationConfigParams, + utils::{ + AggregationDependencyIntent, AggregationDependencyIntentOwned, + KeygenAggregationCircuitIntent, + }, + }, + Snark, + }, + utils::{ + build_utils::{ + aggregation::get_dummy_aggregation_params, + keygen::{compile_agg_dep_to_protocol, read_srs_from_dir}, + pinning::aggregation::{AggTreeId, GenericAggPinning}, + }, + snark_verifier::EnhancedSnark, + }, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +use crate::{ + keygen::{ProvingKeySerializer, SupportedPinning}, + subquery_aggregation::types::{ + InputSubqueryAggregation, SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX, + }, +}; + +use super::{ + common::{parse_agg_intent, ForceBasicConfigParams}, + impl_keygen_intent_for_aggregation, impl_pkey_serializer_for_aggregation, + single_type::SupportedIntentTreeSingleType, +}; + +/// ** !! IMPORTANT !! ** +/// Do not change the order of this enum, which determines how inputs are parsed. +// Determines order of circuit IDs in `to_agg` +#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum SubqueryAggInputSnark { + Header, + Account, + Storage, + Tx, + Receipt, + SolidityMapping, + ECDSA, + ResultsRoot, +} + +#[serde_as] +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct SubqueryAggParams { + /// The compiled verification keys of the dependency circuits to aggregate. + /// Since Subquery Aggregation is universal aggregation, we remove the `domain` and `preprocessed` from `PlonkProtocol` since those + /// are loaded as witness es. + #[serde_as(as = "BTreeMap<_, axiom_eth::utils::snark_verifier::Base64Bytes>")] + pub to_agg: BTreeMap>, + pub agg_params: AggregationConfigParams, +} + +pub type SubqueryAggPinning = GenericAggPinning; + +/// Only implements [ProvingKeySerializer] and not [KeygenCircuitIntent]. +#[derive(Serialize, Deserialize)] +pub struct RecursiveSubqueryAggIntent { + pub deps: Vec, + pub k: u32, + /// For different versions of this circuit to be aggregated by the same universal aggregation circuit, + /// we may wish to force configure the circuit to have a certain number of columns without auto-configuration. + #[serde(skip_serializing_if = "Option::is_none")] + pub force_params: Option, +} + +/// Non-recursive intent. Currently only used internally as an intermediary for recursive intent. +/// This will implement [KeygenCircuitIntent] where the pinning is not "wrapped" into an enum. +/// The pinning type is `GenericAggPinning`. +#[derive(Clone, Debug)] +struct SubqueryAggIntent { + // This is from bad UX; only svk = kzg_params.get_g()[0] is used + pub kzg_params: Arc>, + /// For passing to SubqueryAggParams via macro + pub to_agg: BTreeMap>, + /// Circuit intents for subset of circuit types that are enabled + pub deps: BTreeMap, + /// The log_2 domain size of the current aggregation circuit + pub k: u32, + /// For different versions of this circuit to be aggregated by the same universal aggregation circuit, + /// we may wish to force configure the circuit to have a certain number of columns without auto-configuration. + pub force_params: Option, +} + +impl SubqueryAggIntent { + pub fn children(&self) -> Vec { + self.deps.values().map(|(tree, _)| tree.clone()).collect() + } +} + +impl KeygenAggregationCircuitIntent for SubqueryAggIntent { + fn intent_of_dependencies(&self) -> Vec { + self.deps.values().map(|(_, d)| d.into()).collect() + } + fn build_keygen_circuit_from_snarks(self, snarks: Vec) -> Self::AggregationCircuit { + let mut deps = BTreeMap::from_iter(self.deps.keys().cloned().zip_eq(snarks)); + let mut remove_and_wrap = + |k: &SubqueryAggInputSnark| deps.remove(k).map(|s| EnhancedSnark::new(s, None)); + // TODO: don't do manual conversion + let snark_header = remove_and_wrap(&SubqueryAggInputSnark::Header); + let snark_results_root = remove_and_wrap(&SubqueryAggInputSnark::ResultsRoot); + let snark_account = remove_and_wrap(&SubqueryAggInputSnark::Account); + let snark_storage = remove_and_wrap(&SubqueryAggInputSnark::Storage); + let snark_tx = remove_and_wrap(&SubqueryAggInputSnark::Tx); + let snark_receipt = remove_and_wrap(&SubqueryAggInputSnark::Receipt); + let snark_solidity_mapping = remove_and_wrap(&SubqueryAggInputSnark::SolidityMapping); + let snark_ecdsa = remove_and_wrap(&SubqueryAggInputSnark::ECDSA); + let promise_commit_keccak = Fr::zero(); // just a dummy + + let input = InputSubqueryAggregation { + snark_header: snark_header.unwrap(), + snark_account, + snark_storage, + snark_solidity_mapping, + snark_tx, + snark_receipt, + snark_ecdsa, + promise_commit_keccak, + snark_results_root: snark_results_root.unwrap(), + }; + let mut force = false; + let agg_params = if let Some(force_params) = self.force_params { + force = true; + force_params.into_agg_params(self.k) + } else { + get_dummy_aggregation_params(self.k as usize) + }; + let mut circuit = + input.build(CircuitBuilderStage::Keygen, agg_params, &self.kzg_params).unwrap(); + if !force { + circuit.calculate_params(Some(20)); + } + circuit + } +} + +impl_keygen_intent_for_aggregation!( + SubqueryAggIntent, + SubqueryAggParams, + Some(SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX) +); +impl_pkey_serializer_for_aggregation!(SubqueryAggIntent, SubqueryAggParams, SubqueryAggregation); + +fn get_key(supported: &SupportedIntentTreeSingleType) -> SubqueryAggInputSnark { + type S = SupportedIntentTreeSingleType; + type I = SubqueryAggInputSnark; + match supported { + S::Header(_) => I::Header, + S::Account(_) => I::Account, + S::Storage(_) => I::Storage, + S::Tx(_) => I::Tx, + S::Receipt(_) => I::Receipt, + S::SolidityMapping(_) => I::SolidityMapping, + S::ResultsRoot(_) => I::ResultsRoot, + S::ECDSA(_) => I::ECDSA, + } +} + +impl ProvingKeySerializer for RecursiveSubqueryAggIntent { + fn create_and_serialize_proving_key( + self, + params_dir: &Path, + data_dir: &Path, + ) -> anyhow::Result<(AggTreeId, ProvingKey, SupportedPinning)> { + let mut deps = BTreeMap::new(); + for intent in self.deps { + let key = get_key(&intent); + let (child_tree_id, pk, pinning) = + intent.create_and_serialize_proving_key(params_dir, data_dir)?; + let intent = parse_agg_intent(pk.get_vk(), pinning); + assert!(deps.insert(key, (child_tree_id, intent)).is_none()); + } + let kzg_params = Arc::new(read_srs_from_dir(params_dir, self.k)?); + let to_agg = deps + .iter() + .map(|(&k, (_, dep))| (k, compile_agg_dep_to_protocol(&kzg_params, dep, true))) + .collect(); + let subquery_agg_intent = SubqueryAggIntent { + to_agg, + deps, + k: self.k, + kzg_params, + force_params: self.force_params, + }; + subquery_agg_intent.create_and_serialize_proving_key(params_dir, data_dir) + } +} diff --git a/axiom-query/src/keygen/mod.rs b/axiom-query/src/keygen/mod.rs new file mode 100644 index 00000000..3b7d4963 --- /dev/null +++ b/axiom-query/src/keygen/mod.rs @@ -0,0 +1,60 @@ +//! Module with utility functions to generate proving keys and verifying keys for production circuits. + +use std::path::Path; + +use axiom_eth::{ + halo2_proofs::plonk::ProvingKey, halo2curves::bn256::G1Affine, + utils::build_utils::pinning::aggregation::AggTreeId, +}; +use enum_dispatch::enum_dispatch; +use serde::{Deserialize, Serialize}; + +use self::{ + agg::{ + axiom_agg_1::RecursiveAxiomAgg1Intent, axiom_agg_2::RecursiveAxiomAgg2Intent, + common::AggTreePinning, single_type::*, subquery_agg::RecursiveSubqueryAggIntent, + SupportedAggPinning, + }, + shard::{keccak::ShardIntentKeccak, *}, +}; + +pub mod agg; +pub mod shard; + +pub type CircuitId = String; + +#[derive(Serialize, Deserialize)] +#[enum_dispatch(ProvingKeySerializer)] +pub enum SupportedRecursiveIntent { + Subquery(SupportedIntentTreeSingleType), + VerifyCompute(CircuitIntentVerifyCompute), + SubqueryAgg(RecursiveSubqueryAggIntent), + Keccak(IntentTreeSingleType), + AxiomAgg1(RecursiveAxiomAgg1Intent), + AxiomAgg2(RecursiveAxiomAgg2Intent), +} + +/// ** !! IMPORTANT !! ** +/// Enum names are used to deserialize the pinning file. Please be careful if you need renaming. +#[derive(Serialize, Deserialize, Clone)] +#[enum_dispatch(AggTreePinning)] +pub enum SupportedPinning { + Shard(SupportedShardPinning), + Agg(SupportedAggPinning), +} + +// We only serialize to [SupportedPinning] so the JSON will have the name of the pinning in it. +/// Trait specific to this crate for keygen since it uses enum_dispatch and must return a pinning in enum [SupportedPinning]. +#[enum_dispatch] +pub trait ProvingKeySerializer: Sized { + /// Recursively creates and serializes proving keys and pinnings. + /// + /// Computes `circuit_id` as the blake3 hash of the halo2 VerifyingKey written to bytes. Writes proving key to `circuit_id.pk`, verifying key to `circuit_id.vk` and pinning to `circuit_id.json` in the `data_dir` directory. + /// + /// Returns the `circuit_id, proving_key, pinning`. + fn create_and_serialize_proving_key( + self, + params_dir: &Path, + data_dir: &Path, + ) -> anyhow::Result<(AggTreeId, ProvingKey, SupportedPinning)>; +} diff --git a/axiom-query/src/keygen/shard/keccak.rs b/axiom-query/src/keygen/shard/keccak.rs new file mode 100644 index 00000000..4fe36fc3 --- /dev/null +++ b/axiom-query/src/keygen/shard/keccak.rs @@ -0,0 +1,95 @@ +use axiom_eth::{ + halo2_base::utils::halo2::KeygenCircuitIntent, + halo2_proofs::{ + plonk::Circuit, + poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG}, + }, + halo2curves::bn256::Bn256, + rlc::virtual_region::RlcThreadBreakPoints, + utils::{ + component::circuit::{CoreBuilderOutputParams, CoreBuilderParams}, + keccak::get_keccak_unusable_rows_from_capacity, + }, + zkevm_hashes::{ + keccak::component::circuit::shard::{ + KeccakComponentShardCircuit, KeccakComponentShardCircuitParams, + }, + util::eth_types::Field as RawField, + }, +}; +use serde::{Deserialize, Serialize}; + +use super::{ComponentShardPinning, Fr}; + +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct ShardIntentKeccak { + pub core_params: CoreParamsKeccak, + pub k: u32, +} + +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct CoreParamsKeccak { + pub capacity: usize, +} + +// Not sure this is needed +impl CoreBuilderParams for CoreParamsKeccak { + fn get_output_params(&self) -> CoreBuilderOutputParams { + CoreBuilderOutputParams::new(vec![self.capacity]) + } +} + +impl KeygenCircuitIntent for ShardIntentKeccak { + type ConcreteCircuit = KeccakComponentShardCircuit; + type Pinning = ComponentShardPinning; + + fn get_k(&self) -> u32 { + self.k + } + fn build_keygen_circuit(self) -> Self::ConcreteCircuit { + let circuit_params = KeccakComponentShardCircuitParams::new( + self.k as usize, + 0, // unusable_rows will be recalculated later in tuning + self.core_params.capacity, + false, + ); + let mut circuit = KeccakComponentShardCircuit::new(vec![], circuit_params, false); + tune_keccak_component_shard_circuit(&mut circuit); + circuit + } + fn get_pinning_after_keygen( + self, + kzg_params: &ParamsKZG, + circuit: &Self::ConcreteCircuit, + ) -> Self::Pinning { + let break_points = circuit.base_circuit_break_points(); + assert_eq!(break_points.len(), 1); + let svk = kzg_params.get_g()[0]; + let dk = (svk, kzg_params.g2(), kzg_params.s_g2()); + ComponentShardPinning { + params: circuit.params(), + num_instance: vec![1], // keccak component shard only has 1 instance + break_points: RlcThreadBreakPoints { base: break_points, rlc: vec![] }, + dk: dk.into(), + } + } +} + +/// Finds and sets optimal configuration parameters for [KeccakComponentShardCircuit]. +pub fn tune_keccak_component_shard_circuit( + circuit: &mut KeccakComponentShardCircuit, +) { + let circuit_params = circuit.params(); + let k = circuit_params.k(); + let capacity = circuit_params.capacity(); + let (unusable, _) = get_keccak_unusable_rows_from_capacity(k, capacity); + let mut circuit_params = KeccakComponentShardCircuitParams::new( + k, + unusable, + capacity, + circuit_params.publish_raw_outputs(), + ); + circuit_params.base_circuit_params = + KeccakComponentShardCircuit::::calculate_base_circuit_params(&circuit_params); + *circuit.params_mut() = circuit_params; +} diff --git a/axiom-query/src/keygen/shard/mod.rs b/axiom-query/src/keygen/shard/mod.rs new file mode 100644 index 00000000..d8b24a28 --- /dev/null +++ b/axiom-query/src/keygen/shard/mod.rs @@ -0,0 +1,314 @@ +use std::{any::TypeId, path::Path}; + +use axiom_components::{ + ecdsa::ECDSAComponent, framework::promise_loader::empty::EmptyPromiseLoader, + scaffold::BasicComponentScaffoldImpl, +}; +use axiom_eth::{ + halo2_base::{ + gates::circuit::CircuitBuilderStage, + utils::halo2::{KeygenCircuitIntent, ProvingKeyGenerator}, + }, + halo2_proofs::{ + plonk::{Circuit, ProvingKey}, + poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG}, + }, + halo2curves::bn256::{Bn256, Fr, G1Affine}, + rlc::virtual_region::RlcThreadBreakPoints, + snark_verifier::pcs::kzg::KzgDecidingKey, + snark_verifier_sdk::CircuitExt, + utils::{ + build_utils::{ + aggregation::CircuitMetadata, + dummy::DummyFrom, + keygen::{get_dummy_rlc_circuit_params, read_srs_from_dir, write_pk_and_pinning}, + pinning::aggregation::AggTreeId, + }, + component::{ + circuit::{ + ComponentCircuitImpl, CoreBuilder, CoreBuilderInput, CoreBuilderParams, + PromiseBuilder, + }, + promise_loader::utils::DummyPromiseBuilder, + ComponentCircuit, + }, + }, + zkevm_hashes::keccak::component::circuit::shard::KeccakComponentShardCircuit, +}; +use enum_dispatch::enum_dispatch; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +use crate::{ + components::{ + results::circuit::{ + ComponentCircuitResultsRoot, CoreBuilderResultsRoot, PromiseLoaderResultsRoot, + }, + subqueries::{ + account::circuit::{ + ComponentCircuitAccountSubquery, CoreBuilderAccountSubquery, + PromiseLoaderAccountSubquery, + }, + block_header::circuit::{ + ComponentCircuitHeaderSubquery, CoreBuilderHeaderSubquery, + PromiseLoaderHeaderSubquery, + }, + receipt::circuit::{ + ComponentCircuitReceiptSubquery, CoreBuilderReceiptSubquery, + PromiseLoaderReceiptSubquery, + }, + solidity_mappings::circuit::{ + ComponentCircuitSolidityNestedMappingSubquery, + CoreBuilderSolidityNestedMappingSubquery, + PromiseLoaderSolidityNestedMappingSubquery, + }, + storage::circuit::{ + ComponentCircuitStorageSubquery, CoreBuilderStorageSubquery, + PromiseLoaderStorageSubquery, + }, + transaction::circuit::{ + ComponentCircuitTxSubquery, CoreBuilderTxSubquery, PromiseLoaderTxSubquery, + }, + }, + }, + verify_compute::circuit::{ + ComponentCircuitVerifyCompute, CoreBuilderVerifyCompute, PromiseLoaderVerifyCompute, + }, +}; + +use self::keccak::ShardIntentKeccak; + +use super::{agg::common::AggTreePinning, ProvingKeySerializer, SupportedPinning}; + +/// Keccak component shard requires special treatment. +pub mod keccak; + +pub type ShardIntentHeader = + ComponentShardCircuitIntent, PromiseLoaderHeaderSubquery>; +pub type ShardIntentAccount = + ComponentShardCircuitIntent, PromiseLoaderAccountSubquery>; +pub type ShardIntentStorage = + ComponentShardCircuitIntent, PromiseLoaderStorageSubquery>; +pub type ShardIntentTx = + ComponentShardCircuitIntent, PromiseLoaderTxSubquery>; +pub type ShardIntentReceipt = + ComponentShardCircuitIntent, PromiseLoaderReceiptSubquery>; +pub type ShardIntentSolidityMapping = ComponentShardCircuitIntent< + CoreBuilderSolidityNestedMappingSubquery, + PromiseLoaderSolidityNestedMappingSubquery, +>; +pub type ShardIntentECDSA = ComponentShardCircuitIntent< + BasicComponentScaffoldImpl>, + EmptyPromiseLoader, +>; +pub type ShardIntentResultsRoot = + ComponentShardCircuitIntent, PromiseLoaderResultsRoot>; +// You should never shard verify compute, but the struct is the same. +pub type CircuitIntentVerifyCompute = + ComponentShardCircuitIntent; + +pub type ECDSAComponentImpl = ComponentCircuitImpl< + Fr, + BasicComponentScaffoldImpl>, + EmptyPromiseLoader, +>; + +#[derive(Clone, Serialize, Deserialize)] +#[enum_dispatch(AggTreePinning)] +pub enum SupportedShardPinning { + ShardHeader(ComponentShardPinning>), + ShardAccount(ComponentShardPinning>), + ShardStorage(ComponentShardPinning>), + ShardTx(ComponentShardPinning>), + ShardReceipt(ComponentShardPinning>), + ShardSolidityMapping(ComponentShardPinning>), + ShardECDSA(ComponentShardPinning), + ShardResultsRoot(ComponentShardPinning>), + ShardKeccak(ComponentShardPinning>), + ShardVerifyCompute(ComponentShardPinning), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ComponentShardCircuitIntent, P: PromiseBuilder> +where + C::Params: CoreBuilderParams, +{ + pub core_params: C::Params, + pub loader_params: P::Params, + pub k: u32, + #[serde(default = "default_lookup_bits")] + pub lookup_bits: usize, +} + +impl Clone for ComponentShardCircuitIntent +where + C: CoreBuilder, + P: PromiseBuilder, + C::Params: CoreBuilderParams + Clone, + P::Params: Clone, +{ + fn clone(&self) -> Self { + Self { + core_params: self.core_params.clone(), + loader_params: self.loader_params.clone(), + k: self.k, + lookup_bits: self.lookup_bits, + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ComponentShardPinning> { + pub params: C::Params, + /// Number of instances in each instance column + pub num_instance: Vec, + /// g1 generator, g2 generator, s_g2 (s is generator of trusted setup). + /// Together with domain size `2^k`, this commits to the trusted setup used. + /// This is all that's needed to verify the final ecpairing check on the KZG proof. + pub dk: KzgDecidingKey, + pub break_points: RlcThreadBreakPoints, +} + +impl> Clone for ComponentShardPinning +where + C::Params: Clone, +{ + fn clone(&self) -> Self { + Self { + params: self.params.clone(), + num_instance: self.num_instance.clone(), + break_points: self.break_points.clone(), + dk: self.dk.clone(), + } + } +} + +impl KeygenCircuitIntent for ComponentShardCircuitIntent +where + C: CoreBuilder + CircuitMetadata + 'static, + C::Params: CoreBuilderParams, + C::CoreInput: DummyFrom, + P: DummyPromiseBuilder, +{ + type ConcreteCircuit = ComponentCircuitImpl; + type Pinning = ComponentShardPinning; + + fn get_k(&self) -> u32 { + self.k + } + + /// ## Panics + /// In any situation where creating the keygen circuit fails. + fn build_keygen_circuit(self) -> Self::ConcreteCircuit { + let Self { core_params, loader_params, k, mut lookup_bits } = self; + if TypeId::of::() == TypeId::of::() { + // VerifyCompute is aggregation circuit, so optimal lookup bits is `k - 1` + lookup_bits = k as usize - 1; + log::debug!("Verify Compute lookup bits: {lookup_bits}"); + } + let rlc_params = get_dummy_rlc_circuit_params(k as usize, lookup_bits); + let mut circuit = ComponentCircuitImpl::::new_from_stage( + CircuitBuilderStage::Keygen, + core_params.clone(), + loader_params, + rlc_params, + ); + let default_input = C::CoreInput::dummy_from(core_params); + circuit.feed_input(Box::new(default_input)).unwrap(); + circuit.promise_builder.borrow_mut().fulfill_dummy_promise_results(); + circuit.calculate_params(); + + circuit + } + + fn get_pinning_after_keygen( + self, + kzg_params: &ParamsKZG, + circuit: &Self::ConcreteCircuit, + ) -> Self::Pinning { + let circuit_params = circuit.params(); + let break_points = circuit.rlc_builder.borrow().break_points(); + // get public instances + circuit.clear_witnesses(); + circuit.virtual_assign_phase0().unwrap(); + let num_instance = + circuit.rlc_builder.borrow().base.assigned_instances.iter().map(|x| x.len()).collect(); + circuit.clear_witnesses(); // prevent drop warning + let svk = kzg_params.get_g()[0]; + let dk = (svk, kzg_params.g2(), kzg_params.s_g2()); + ComponentShardPinning { params: circuit_params, num_instance, break_points, dk: dk.into() } + } +} + +fn default_lookup_bits() -> usize { + 8 +} + +impl ProvingKeySerializer for ComponentShardCircuitIntent +where + C: CoreBuilder + CircuitMetadata + 'static, + C::Params: CoreBuilderParams + Clone, + C::CoreInput: DummyFrom, + P: DummyPromiseBuilder, + P::Params: Clone, + ComponentShardPinning>: + Serialize + DeserializeOwned + Into, +{ + fn create_and_serialize_proving_key( + self, + params_dir: &Path, + data_dir: &Path, + ) -> anyhow::Result<(AggTreeId, ProvingKey, SupportedPinning)> { + let k = self.get_k(); + let kzg_params = read_srs_from_dir(params_dir, k)?; + let (pk, pinning_json) = self.create_pk_and_pinning(&kzg_params); + let pinning: >::Pinning = + serde_json::from_value(pinning_json)?; + let pinning: SupportedShardPinning = pinning.into(); + let pinning = SupportedPinning::Shard(pinning); + let circuit_id = write_pk_and_pinning(data_dir, &pk, &serde_json::to_value(&pinning)?)?; + // ** !! Warning !! ** + // Currently all shard component circuits are **leaves** in the aggregation tree. + // This implementation would need to change if that changes. + // + // VerifyCompute is an aggregation circuit but does not have children as an aggregation tree because the snark to be aggregated is part of the input. + // Thus all shard circuits are leaves in the aggregation tree. + let leaf_id = AggTreeId { circuit_id, children: vec![], aggregate_vk_hash: None }; + Ok((leaf_id, pk, pinning)) + } +} +// special case (for now) +// if we need to do this more than twice, we should make a macro +impl ProvingKeySerializer for ShardIntentKeccak { + fn create_and_serialize_proving_key( + self, + params_dir: &Path, + data_dir: &Path, + ) -> anyhow::Result<(AggTreeId, ProvingKey, SupportedPinning)> { + let k = self.get_k(); + let kzg_params = read_srs_from_dir(params_dir, k)?; + let (pk, pinning_json) = self.create_pk_and_pinning(&kzg_params); + let pinning: >::Pinning = + serde_json::from_value(pinning_json)?; + let pinning: SupportedShardPinning = pinning.into(); + let pinning = SupportedPinning::Shard(pinning); + let circuit_id = write_pk_and_pinning(data_dir, &pk, &serde_json::to_value(&pinning)?)?; + let leaf_id = AggTreeId { circuit_id, children: vec![], aggregate_vk_hash: None }; + Ok((leaf_id, pk, pinning)) + } +} + +impl> AggTreePinning for ComponentShardPinning { + fn num_instance(&self) -> Vec { + self.num_instance.clone() + } + fn accumulator_indices(&self) -> Option> { + C::accumulator_indices() + } + // ** !! Assertion !! ** + // No ComponentShardPinning has non-None agg_vk_hash_data. + // While VerifyCompute is a universal aggregation circuit, the compute snark's + // vkey is committed to separately in the querySchema. + fn agg_vk_hash_data(&self) -> Option<((usize, usize), Fr)> { + None + } +} diff --git a/axiom-query/src/lib.rs b/axiom-query/src/lib.rs new file mode 100644 index 00000000..9bda4ee5 --- /dev/null +++ b/axiom-query/src/lib.rs @@ -0,0 +1,24 @@ +#![feature(io_error_other)] +#![feature(associated_type_defaults)] + +pub use axiom_codec; +pub use axiom_eth; + +use axiom_eth::halo2_proofs::halo2curves::ff; +pub use axiom_eth::{Field, RawField}; + +pub mod axiom_aggregation1; +pub mod axiom_aggregation2; +/// Components Complex +pub mod components; +/// Global configuration constants +pub mod global_constants; +pub mod subquery_aggregation; +pub mod utils; +pub mod verify_compute; + +#[cfg(feature = "keygen")] +pub mod keygen; + +/// This means we can concatenate arrays with individual max length 2^32. +pub const DEFAULT_RLC_CACHE_BITS: usize = 32; diff --git a/axiom-query/src/subquery_aggregation/circuit.rs b/axiom-query/src/subquery_aggregation/circuit.rs new file mode 100644 index 00000000..8b6771b7 --- /dev/null +++ b/axiom-query/src/subquery_aggregation/circuit.rs @@ -0,0 +1,251 @@ +use std::collections::HashMap; + +use anyhow::{bail, Result}; +use axiom_components::ecdsa::ComponentTypeECDSA; +use axiom_eth::{ + halo2_base::gates::{circuit::CircuitBuilderStage, GateChip}, + halo2_proofs::poly::kzg::commitment::ParamsKZG, + halo2curves::bn256::Bn256, + snark_verifier_sdk::{halo2::aggregation::AggregationCircuit, SHPLONK}, + utils::{ + build_utils::pinning::aggregation::AggregationCircuitPinning, + component::{ + promise_loader::multi::ComponentTypeList, types::ComponentPublicInstances, + utils::create_hasher, ComponentType, + }, + snark_verifier::{ + create_universal_aggregation_circuit, AggregationCircuitParams, NUM_FE_ACCUMULATOR, + }, + }, +}; +use itertools::{zip_eq, Itertools}; + +use crate::components::{ + results::{circuit::SubqueryDependencies, types::LogicalPublicInstanceResultsRoot}, + subqueries::{ + account::types::ComponentTypeAccountSubquery, + block_header::types::{ComponentTypeHeaderSubquery, LogicalPublicInstanceHeader}, + receipt::types::ComponentTypeReceiptSubquery, + solidity_mappings::types::ComponentTypeSolidityNestedMappingSubquery, + storage::types::ComponentTypeStorageSubquery, + transaction::types::ComponentTypeTxSubquery, + }, +}; + +use super::types::{InputSubqueryAggregation, LogicalPublicInstanceSubqueryAgg, F}; + +impl InputSubqueryAggregation { + /// Builds general circuit + /// + /// Warning: this MUST return a circuit implementing `CircuitExt` with accumulator indices provided. + /// In particular, do not return `BaseCircuitBuilder`. + pub fn build( + self, + stage: CircuitBuilderStage, + circuit_params: AggregationCircuitParams, + kzg_params: &ParamsKZG, + ) -> Result { + // dependency checks + if self.snark_storage.is_some() && self.snark_account.is_none() { + bail!("Storage snark requires Account snark"); + } + if self.snark_solidity_mapping.is_some() && self.snark_storage.is_none() { + bail!("SolidityMapping snark requires Storage snark"); + } + const NUM_SNARKS: usize = 8; + let snarks = vec![ + Some(self.snark_header), + self.snark_account, + self.snark_storage, + self.snark_tx, + self.snark_receipt, + self.snark_solidity_mapping, + self.snark_ecdsa, + Some(self.snark_results_root), + ]; + let snarks_enabled = snarks.iter().map(|s| s.is_some()).collect_vec(); + let subquery_type_ids = [ + ComponentTypeHeaderSubquery::::get_type_id(), + ComponentTypeAccountSubquery::::get_type_id(), + ComponentTypeStorageSubquery::::get_type_id(), + ComponentTypeTxSubquery::::get_type_id(), + ComponentTypeReceiptSubquery::::get_type_id(), + ComponentTypeSolidityNestedMappingSubquery::::get_type_id(), + as axiom_components::framework::ComponentType>::get_type_id(), + ]; + if snarks.iter().flatten().any(|s| s.agg_vk_hash_idx.is_some()) { + bail!("[SubqueryAggregation] No snark should be universal."); + } + let snarks = snarks.into_iter().flatten().map(|s| s.inner).collect_vec(); + let agg_vkey_hash_indices = vec![None; snarks.len()]; + let (mut circuit, previous_instances, agg_vkey_hash) = + create_universal_aggregation_circuit::( + stage, + circuit_params, + kzg_params, + snarks, + agg_vkey_hash_indices, + ); + + let builder = &mut circuit.builder; + let ctx = builder.main(0); + + // Parse aggregated component public instances + let mut previous_instances = previous_instances.into_iter(); + let mut get_next_pis = + || ComponentPublicInstances::try_from(previous_instances.next().unwrap()); + let mut pis = Vec::with_capacity(NUM_SNARKS); + for snark_enabled in snarks_enabled { + if snark_enabled { + pis.push(Some(get_next_pis()?)); + } else { + pis.push(None); + } + } + let pis_header = pis[0].clone().unwrap(); + let pis_results = pis.pop().unwrap().unwrap(); + + // Load promise commit keccak as a public input + let promise_keccak = ctx.load_witness(self.promise_commit_keccak); + // ======== Create Poseidon hasher =========== + let gate = GateChip::default(); + let mut hasher = create_hasher(); + hasher.initialize_consts(ctx, &gate); + // Insert subquery output commits + // Unclear if this is a necessary precaution, but we store based on `subquery_type_ids` so the order does not depend on ordering in other modules + let mut subquery_commits = HashMap::new(); + // Insert subquery promise commits + let mut subquery_promises = HashMap::new(); + for (type_id, pi) in zip_eq(subquery_type_ids, &pis) { + if let Some(pi) = pi { + subquery_commits.insert(type_id.clone(), pi.output_commit); + subquery_promises.insert(type_id, pi.promise_result_commit); + } + } + // Hash each subquery output commit with the `promise_commit_keccak`, to be compared with subquery promises later. + // This matches the promise public output computation in `ComponentCircuitImpl::generate_public_instances`. + // The dependencies of a non-Header subquery circuit are always [Keccak, ] + // We only need to calculate the hash for components that are called: Header, Account, Storage. Currently Tx, Receipt, SolidityNestedMapping are not called. + let mut hashed_commits = HashMap::new(); + for type_id in [ + ComponentTypeHeaderSubquery::::get_type_id(), + ComponentTypeAccountSubquery::::get_type_id(), + ComponentTypeStorageSubquery::::get_type_id(), + ] { + if let Some(output_commit) = subquery_commits.get(&type_id) { + hashed_commits.insert( + type_id, + hasher.hash_fix_len_array(ctx, &gate, &[promise_keccak, *output_commit]), + ); + } + } + + // ======== Manually check all promise calls between subqueries: ======= + // Header calls Keccak + { + let hashed_commit_keccak = hasher.hash_fix_len_array(ctx, &gate, &[promise_keccak]); + let header_promise_commit = + subquery_promises[&ComponentTypeHeaderSubquery::::get_type_id()]; + log::debug!("hash(promise_keccak): {:?}", hashed_commit_keccak.value()); + log::debug!("header_promise_commit: {:?}", header_promise_commit.value()); + ctx.constrain_equal(&hashed_commit_keccak, &header_promise_commit); + } + // Below when we say promise_header and commit_header, we actually mean promise_keccak_header and commit_keccak_header because both have been hashed with a promise_keccak. + // Account calls Keccak & Header + if let Some(promise_header) = + subquery_promises.get(&ComponentTypeAccountSubquery::::get_type_id()) + { + let commit_header = hashed_commits[&ComponentTypeHeaderSubquery::::get_type_id()]; + log::debug!("account:commit_header: {:?}", commit_header.value()); + log::debug!("account:promise_header: {:?}", promise_header.value()); + ctx.constrain_equal(&commit_header, promise_header); + } + // Storage calls Keccak & Account + if let Some(promise_account) = + subquery_promises.get(&ComponentTypeStorageSubquery::::get_type_id()) + { + let commit_account = hashed_commits[&ComponentTypeAccountSubquery::::get_type_id()]; + log::debug!("storage:commit_account: {:?}", commit_account.value()); + log::debug!("storage:promise_account: {:?}", promise_account.value()); + ctx.constrain_equal(&commit_account, promise_account); + } + // Tx calls Keccak & Header + if let Some(promise_header) = + subquery_promises.get(&ComponentTypeTxSubquery::::get_type_id()) + { + let commit_header = hashed_commits[&ComponentTypeHeaderSubquery::::get_type_id()]; + log::debug!("tx:commit_header: {:?}", commit_header.value()); + log::debug!("tx:promise_header: {:?}", promise_header.value()); + ctx.constrain_equal(&commit_header, promise_header); + } + // Receipt calls Keccak & Header + if let Some(promise_header) = + subquery_promises.get(&ComponentTypeReceiptSubquery::::get_type_id()) + { + let commit_header = hashed_commits[&ComponentTypeHeaderSubquery::::get_type_id()]; + log::debug!("receipt:commit_header: {:?}", commit_header.value()); + log::debug!("receipt:promise_header: {:?}", promise_header.value()); + ctx.constrain_equal(&commit_header, promise_header); + } + // SolidityNestedMapping calls Keccak & Storage + if let Some(promise_storage) = + subquery_promises.get(&ComponentTypeSolidityNestedMappingSubquery::::get_type_id()) + { + let commit_storage = hashed_commits[&ComponentTypeStorageSubquery::::get_type_id()]; + log::debug!("solidity_nested_mapping:commit_storage: {:?}", commit_storage.value()); + log::debug!("solidity_nested_mapping:promise_storage: {:?}", promise_storage.value()); + ctx.constrain_equal(&commit_storage, promise_storage); + } + + // Get keccakPacked(blockhashMmr) + let LogicalPublicInstanceHeader { mmr_keccak } = pis_header.other.try_into()?; + + // ======== results root ========= + // MUST match order in `InputResultsRootShard::build` + let type_ids = SubqueryDependencies::::get_component_type_ids(); + // We now collect the promises from snarks in the order they were commited to in ResultsRoot + let mut results_deps_commits = Vec::new(); + results_deps_commits.push(promise_keccak); + for t_id in &type_ids { + if let Some(commit) = subquery_commits.get(t_id) { + results_deps_commits.push(*commit); + } + } + + let results_promise_commit = hasher.hash_fix_len_array(ctx, &gate, &results_deps_commits); + + log::debug!("results_promise_commit: {:?}", results_promise_commit.value()); + log::debug!("promise_result_commit: {:?}", pis_results.promise_result_commit.value()); + ctx.constrain_equal(&results_promise_commit, &pis_results.promise_result_commit); + + // We have implicitly checked all Components use the same `promise_keccak` above. + + let LogicalPublicInstanceResultsRoot { results_root_poseidon, commit_subquery_hashes } = + pis_results.other.try_into().unwrap(); + + let logical_pis = LogicalPublicInstanceSubqueryAgg { + promise_keccak, + agg_vkey_hash, + results_root_poseidon, + commit_subquery_hashes, + mmr_keccak, + }; + if builder.assigned_instances.len() != 1 { + bail!("should only have 1 instance column"); + } + assert_eq!(builder.assigned_instances[0].len(), NUM_FE_ACCUMULATOR); + builder.assigned_instances[0].extend(logical_pis.flatten()); + + Ok(circuit) + } + + pub fn prover_circuit( + self, + pinning: AggregationCircuitPinning, + kzg_params: &ParamsKZG, + ) -> Result { + Ok(self + .build(CircuitBuilderStage::Prover, pinning.params, kzg_params)? + .use_break_points(pinning.break_points)) + } +} diff --git a/axiom-query/src/subquery_aggregation/mod.rs b/axiom-query/src/subquery_aggregation/mod.rs new file mode 100644 index 00000000..53a0e142 --- /dev/null +++ b/axiom-query/src/subquery_aggregation/mod.rs @@ -0,0 +1,13 @@ +//! # Subquery Aggregation Circuit +//! +//! Aggregation all subquery circuits and resultsRoot circuit. +//! Currently these are all of the components _except_ the keccak component. +//! +//! The reasoning is that the keccak component should be aggregated separately +//! and run in parallel to the Subquery Aggregation Circuit and all other components. + +pub mod circuit; +pub mod types; + +#[cfg(test)] +pub mod tests; diff --git a/axiom-query/src/subquery_aggregation/tests.rs b/axiom-query/src/subquery_aggregation/tests.rs new file mode 100644 index 00000000..eb54bc39 --- /dev/null +++ b/axiom-query/src/subquery_aggregation/tests.rs @@ -0,0 +1,353 @@ +use std::{ + collections::HashMap, + env, + fs::File, + io::{Read, Write}, +}; + +use axiom_codec::{ + constants::NUM_SUBQUERY_TYPES, + types::native::{HeaderSubquery, SubqueryType}, +}; +use axiom_eth::{ + halo2_base::{ + gates::circuit::CircuitBuilderStage, + halo2_proofs::{ + dev::MockProver, + halo2curves::bn256::{Bn256, Fr}, + poly::kzg::commitment::ParamsKZG, + }, + utils::fs::{gen_srs, read_params}, + }, + keccak::types::{ComponentTypeKeccak, OutputKeccakShard}, + snark_verifier_sdk::{halo2::gen_snark_shplonk, CircuitExt}, + utils::{ + build_utils::pinning::{Halo2CircuitPinning, PinnableCircuit, RlcCircuitPinning}, + component::{ + circuit::ComponentBuilder, + promise_loader::{ + comp_loader::SingleComponentLoaderParams, multi::MultiPromiseLoaderParams, + single::PromiseLoaderParams, + }, + ComponentCircuit, ComponentPromiseResultsInMerkle, ComponentType, + GroupedPromiseResults, + }, + snark_verifier::{AggregationCircuitParams, EnhancedSnark, NUM_FE_ACCUMULATOR}, + }, +}; +use ethers_core::types::H256; +use test_log::test; + +use crate::components::{ + results::{ + circuit::{ComponentCircuitResultsRoot, CoreParamsResultRoot}, + types::CircuitInputResultsRootShard, + }, + subqueries::{ + block_header::{ + circuit::{ + ComponentCircuitHeaderSubquery, CoreParamsHeaderSubquery, + PromiseLoaderHeaderSubquery, + }, + types::{CircuitInputHeaderShard, ComponentTypeHeaderSubquery}, + }, + common::{shard_into_component_promise_results, OutputSubqueryShard}, + }, +}; + +use super::types::{InputSubqueryAggregation, SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX}; + +fn generate_snark + PinnableCircuit>( + name: &'static str, + params: &ParamsKZG, + keygen_circuit: C, + load_prover_circuit: &impl Fn(RlcCircuitPinning) -> C, +) -> anyhow::Result { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + let pinning_path = format!("{cargo_manifest_dir}/configs/test/{name}.json"); + let pk_path = format!("{cargo_manifest_dir}/data/test/{name}.pk"); + let (pk, pinning) = keygen_circuit.create_pk(params, pk_path, pinning_path)?; + let vk = pk.get_vk(); + let mut vk_file = File::create(format!("data/test/{name}.vk"))?; + vk.write(&mut vk_file, axiom_eth::halo2_proofs::SerdeFormat::RawBytes)?; + let mut vk_file = File::create(format!("data/test/{name}.vk.txt"))?; + write!(vk_file, "{:?}", vk.pinned())?; + + let component_circuit = load_prover_circuit(pinning); + + let snark_path = format!("data/test/{name}.snark"); + let snark = gen_snark_shplonk(params, &pk, component_circuit, Some(snark_path)); + Ok(EnhancedSnark { inner: snark, agg_vk_hash_idx: None }) +} + +fn read_header_pinning( +) -> anyhow::Result<(CoreParamsHeaderSubquery, PromiseLoaderParams, RlcCircuitPinning)> { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + let header_core_params: CoreParamsHeaderSubquery = serde_json::from_reader(File::open( + format!("{cargo_manifest_dir}/configs/test/header_subquery_core_params.json"), + )?)?; + let header_promise_params: as ComponentBuilder>::Params = + serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/configs/test/header_subquery_loader_params.json" + ))?)?; + let header_rlc_params: RlcCircuitPinning = serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/configs/test/header_subquery.json" + ))?)?; + Ok((header_core_params, header_promise_params, header_rlc_params)) +} + +fn generate_header_snark( + params: &ParamsKZG, +) -> anyhow::Result<(EnhancedSnark, GroupedPromiseResults)> { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + + let mut promise_results = HashMap::new(); + let promise_keccak: OutputKeccakShard = serde_json::from_reader( + File::open(format!("{cargo_manifest_dir}/data/test/promise_results_keccak_for_agg.json")) + .unwrap(), + )?; + let promise_header: OutputSubqueryShard = serde_json::from_reader( + File::open(format!("{cargo_manifest_dir}/data/test/promise_results_header_for_agg.json")) + .unwrap(), + )?; + let keccak_merkle = ComponentPromiseResultsInMerkle::::from_single_shard( + promise_keccak.into_logical_results(), + ); + promise_results.insert(ComponentTypeKeccak::::get_type_id(), keccak_merkle); + promise_results.insert( + ComponentTypeHeaderSubquery::::get_type_id(), + shard_into_component_promise_results::>( + promise_header.convert_into(), + ), + ); + + let header_input: CircuitInputHeaderShard = serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/data/test/input_header_for_agg.json" + ))?)?; + let (header_core_params, header_promise_params, header_rlc_params) = read_header_pinning()?; + let header_circuit = ComponentCircuitHeaderSubquery::::new( + header_core_params.clone(), + header_promise_params.clone(), + header_rlc_params.params, + ); + header_circuit.feed_input(Box::new(header_input.clone())).unwrap(); + header_circuit.fulfill_promise_results(&promise_results).unwrap(); + + let header_snark = + generate_snark("header_subquery_for_agg", params, header_circuit, &|pinning| { + let circuit = ComponentCircuitHeaderSubquery::::prover( + header_core_params.clone(), + header_promise_params.clone(), + pinning, + ); + circuit.feed_input(Box::new(header_input.clone())).unwrap(); + circuit.fulfill_promise_results(&promise_results).unwrap(); + circuit + })?; + Ok((header_snark, promise_results)) +} + +fn get_test_input(params: &ParamsKZG) -> anyhow::Result { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + + let (header_snark, promise_results) = generate_header_snark(params)?; + let keccak_commit = + promise_results.get(&ComponentTypeKeccak::::get_type_id()).unwrap().leaves()[0].commit; + + let results_input: CircuitInputResultsRootShard = serde_json::from_reader(File::open( + format!("{cargo_manifest_dir}/data/test/input_results_root_for_agg.json"), + )?)?; + + let result_rlc_pinning: RlcCircuitPinning = serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/configs/test/results_root_for_agg.json" + ))?)?; + + let mut enabled_types = [false; NUM_SUBQUERY_TYPES]; + enabled_types[SubqueryType::Header as usize] = true; + let mut params_per_comp = HashMap::new(); + params_per_comp.insert( + ComponentTypeHeaderSubquery::::get_type_id(), + SingleComponentLoaderParams::new(0, vec![3]), + ); + let promise_results_params = MultiPromiseLoaderParams { params_per_component: params_per_comp }; + + let mut results_circuit = ComponentCircuitResultsRoot::::new( + CoreParamsResultRoot { enabled_types, capacity: results_input.subqueries.len() }, + (PromiseLoaderParams::new_for_one_shard(200), promise_results_params.clone()), + result_rlc_pinning.params, + ); + results_circuit.feed_input(Box::new(results_input.clone()))?; + results_circuit.fulfill_promise_results(&promise_results).unwrap(); + results_circuit.calculate_params(); + + let results_snark = + generate_snark("results_root_for_agg", params, results_circuit, &|pinning| { + let results_circuit = ComponentCircuitResultsRoot::::prover( + CoreParamsResultRoot { enabled_types, capacity: results_input.subqueries.len() }, + (PromiseLoaderParams::new_for_one_shard(200), promise_results_params.clone()), + pinning, + ); + results_circuit.feed_input(Box::new(results_input.clone())).unwrap(); + results_circuit.fulfill_promise_results(&promise_results).unwrap(); + results_circuit + })?; + + Ok(InputSubqueryAggregation { + snark_header: header_snark, + snark_results_root: results_snark, + snark_account: None, + snark_storage: None, + snark_solidity_mapping: None, + snark_tx: None, + snark_receipt: None, + snark_ecdsa: None, + promise_commit_keccak: keccak_commit, + }) +} + +#[test] +fn test_mock_subquery_agg() -> anyhow::Result<()> { + let k = 19; + let params = gen_srs(k as u32); + + let input = get_test_input(¶ms)?; + let mut agg_circuit = input.build( + CircuitBuilderStage::Mock, + AggregationCircuitParams { + degree: k as u32, + num_advice: 0, + num_lookup_advice: 0, + num_fixed: 0, + lookup_bits: 8, + }, + //rlc_circuit_params.base.try_into().unwrap(), + ¶ms, + )?; + agg_circuit.calculate_params(Some(9)); + let instances = agg_circuit.instances(); + MockProver::run(k as u32, &agg_circuit, instances).unwrap().assert_satisfied(); + Ok(()) +} + +#[test] +#[ignore = "prover"] +fn test_generate_header_snark() -> anyhow::Result<()> { + let k = 18; + let params = read_params(k); + generate_header_snark(¶ms)?; + Ok(()) +} + +#[cfg(feature = "keygen")] +#[test] +#[ignore = "use axiom srs"] +fn test_generate_header_pk() -> anyhow::Result<()> { + use crate::keygen::shard::ShardIntentHeader; + use axiom_eth::halo2_base::utils::halo2::ProvingKeyGenerator; + let k = 18; + let params = read_params(k); + // Generate the snark and pk using a real input + generate_header_snark(¶ms)?; + + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + let name = "header_subquery_for_agg"; + let pk_path = format!("{cargo_manifest_dir}/data/test/{name}.pk"); + let mut buf1 = Vec::new(); + let mut f = File::open(pk_path)?; + f.read_to_end(&mut buf1)?; + + let (core_params, loader_params, rlc_pinning) = read_header_pinning()?; + let intent = ShardIntentHeader { + core_params, + loader_params, + k: rlc_pinning.k() as u32, + lookup_bits: rlc_pinning.params.base.lookup_bits.unwrap_or(0), + }; + let (pk, _) = intent.create_pk_and_pinning(¶ms); + let mut buf2 = Vec::new(); + pk.write(&mut buf2, axiom_eth::halo2_proofs::SerdeFormat::RawBytesUnchecked)?; + + if buf1 != buf2 { + panic!("proving key mismatch"); + } + Ok(()) +} + +#[test] +#[ignore = "prover"] +fn test_prover_subquery_agg() -> anyhow::Result<()> { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + + let k = 20; + let params = gen_srs(k as u32); + + let input = get_test_input(¶ms)?; + let mut keygen_circuit = input.clone().build( + CircuitBuilderStage::Keygen, + AggregationCircuitParams { degree: k as u32, lookup_bits: k - 1, ..Default::default() }, + ¶ms, + )?; + keygen_circuit.calculate_params(Some(20)); + let instance1 = keygen_circuit.instances(); + let abs_agg_vk_hash_idx = SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX + NUM_FE_ACCUMULATOR; + let name = "subquery_aggregation_for_agg"; + let pinning_path = format!("{cargo_manifest_dir}/configs/test/{name}.json"); + let pk_path = format!("{cargo_manifest_dir}/data/test/{name}.pk"); + let (pk, pinning) = keygen_circuit.create_pk(¶ms, pk_path, pinning_path)?; + + #[cfg(feature = "keygen")] + { + // test keygen + use axiom_eth::halo2_proofs::{plonk::keygen_vk, SerdeFormat}; + use axiom_eth::snark_verifier_sdk::{halo2::gen_dummy_snark_from_protocol, SHPLONK}; + use axiom_eth::utils::build_utils::aggregation::get_dummy_aggregation_params; + let [dum_snark_header, dum_snark_results] = + [&input.snark_header, &input.snark_results_root].map(|s| { + EnhancedSnark::new( + gen_dummy_snark_from_protocol::(s.inner.protocol.clone()), + None, + ) + }); + let input = InputSubqueryAggregation { + snark_header: dum_snark_header, + snark_results_root: dum_snark_results, + snark_account: None, + snark_storage: None, + snark_solidity_mapping: None, + snark_tx: None, + snark_receipt: None, + snark_ecdsa: None, + promise_commit_keccak: Default::default(), + }; + let mut circuit = + input.build(CircuitBuilderStage::Keygen, get_dummy_aggregation_params(k), ¶ms)?; + circuit.calculate_params(Some(20)); + let vk = keygen_vk(¶ms, &circuit)?; + if pk.get_vk().to_bytes(SerdeFormat::RawBytes) != vk.to_bytes(SerdeFormat::RawBytes) { + panic!("vk mismatch"); + } + let instance2 = circuit.instances(); + assert_eq!( + instance1[0][abs_agg_vk_hash_idx], instance2[0][abs_agg_vk_hash_idx], + "agg vkey hash mismatch" + ); + } + + let mut prover_circuit = input.build(CircuitBuilderStage::Prover, pinning.params, ¶ms)?; + prover_circuit.set_break_points(pinning.break_points); + + let snark = gen_snark_shplonk(¶ms, &pk, prover_circuit, None::<&str>); + let instance3 = snark.instances.clone(); + let snark = EnhancedSnark { + inner: snark, + agg_vk_hash_idx: Some(SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX), + }; + assert_eq!( + instance1[0][abs_agg_vk_hash_idx], instance3[0][abs_agg_vk_hash_idx], + "agg vkey hash mismatch" + ); + + let snark_path = format!("{cargo_manifest_dir}/data/test/{name}.snark.json"); + serde_json::to_writer(File::create(snark_path)?, &snark)?; + Ok(()) +} diff --git a/axiom-query/src/subquery_aggregation/types.rs b/axiom-query/src/subquery_aggregation/types.rs new file mode 100644 index 00000000..02b499fe --- /dev/null +++ b/axiom-query/src/subquery_aggregation/types.rs @@ -0,0 +1,118 @@ +use std::iter; +use std::marker::PhantomData; + +use anyhow::anyhow; +use axiom_codec::HiLo; +use axiom_eth::{ + halo2_base::AssignedValue, + impl_flatten_conversion, + utils::{ + component::{types::LogicalEmpty, ComponentType, ComponentTypeId, LogicalResult}, + snark_verifier::EnhancedSnark, + }, +}; +use serde::{Deserialize, Serialize}; + +pub(crate) type F = axiom_eth::halo2curves::bn256::Fr; + +#[derive(Clone, Debug, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InputSubqueryAggregation { + /// Header snark always required + pub snark_header: EnhancedSnark, + + #[serde(skip_serializing_if = "Option::is_none")] + pub snark_account: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub snark_storage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub snark_solidity_mapping: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub snark_tx: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub snark_receipt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub snark_ecdsa: Option, + + /// The keccak commit is provided as a public input. + /// The SubqueryAggregation circuit will check that all subquery component circuits use the same promise commit for keccak. + /// It will not check the promise commit itself. That will be done by AxiomAggregation1, which aggregates this SubqueryAggregation circuit. + pub promise_commit_keccak: F, + + /// Results root snark always required + pub snark_results_root: EnhancedSnark, +} + +pub struct ComponentTypeSubqueryAgg { + _phatnom: PhantomData, +} +impl ComponentType for ComponentTypeSubqueryAgg { + type InputValue = LogicalEmpty; + type InputWitness = LogicalEmpty>; + type OutputValue = LogicalEmpty; + type OutputWitness = LogicalEmpty>; + type LogicalInput = LogicalEmpty; + + fn get_type_id() -> ComponentTypeId { + "axiom-query:ComponentTypeSubqueryAgg".to_string() + } + + fn logical_result_to_virtual_rows_impl( + _ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + unreachable!() + } + fn logical_input_to_virtual_rows_impl(_li: &Self::LogicalInput) -> Vec { + unreachable!() + } +} + +/// The public instances **without** the accumulator +const FIELD_SIZE_PUBLIC_INSTANCES: [usize; 6] = [ + 9999, // promise_keccak + 9999, // agg_vk_hash + 9999, // results_root_poseidon + 9999, // commit_subquery_hashes + 128, 128, // mmr_keccak +]; +pub const SUBQUERY_AGGREGATION_AGG_VKEY_HASH_IDX: usize = 1; + +/// Public instances **without** the accumulator (accumulator is 12 field elements) +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LogicalPublicInstanceSubqueryAgg { + pub promise_keccak: T, + pub agg_vkey_hash: T, + pub results_root_poseidon: T, + pub commit_subquery_hashes: T, + pub mmr_keccak: HiLo, +} + +impl TryFrom> for LogicalPublicInstanceSubqueryAgg { + type Error = anyhow::Error; + fn try_from(value: Vec) -> Result { + let [promise_keccak, agg_vkey_hash, results_root_poseidon, commit_subquery_hashes, mmr_hi, mmr_lo] = + value + .try_into() + .map_err(|_| anyhow!("LogicalPublicInstanceSubqueryAgg invalid length"))?; + Ok(Self { + promise_keccak, + agg_vkey_hash, + results_root_poseidon, + commit_subquery_hashes, + mmr_keccak: HiLo::from_hi_lo([mmr_hi, mmr_lo]), + }) + } +} + +impl LogicalPublicInstanceSubqueryAgg { + pub fn flatten(&self) -> Vec { + iter::empty() + .chain([self.promise_keccak, self.agg_vkey_hash]) + .chain([self.results_root_poseidon, self.commit_subquery_hashes]) + .chain(self.mmr_keccak.hi_lo()) + .collect() + } +} + +// This is not used: +impl_flatten_conversion!(LogicalPublicInstanceSubqueryAgg, FIELD_SIZE_PUBLIC_INSTANCES); diff --git a/axiom-query/src/utils/client_circuit/default_circuit.rs b/axiom-query/src/utils/client_circuit/default_circuit.rs new file mode 100644 index 00000000..18f78fea --- /dev/null +++ b/axiom-query/src/utils/client_circuit/default_circuit.rs @@ -0,0 +1,109 @@ +use std::marker::PhantomData; + +use axiom_eth::{ + halo2_base::{ + gates::circuit::{BaseCircuitParams, BaseConfig}, + utils::ScalarField, + }, + halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + halo2curves::bn256::{Bn256, Fr, G1Affine}, + plonk::{keygen_vk_custom, Circuit, ConstraintSystem, Error, VerifyingKey}, + poly::kzg::commitment::ParamsKZG, + }, + rlc::circuit::{RlcCircuitParams, RlcConfig}, +}; + +use super::metadata::AxiomV2CircuitMetadata; + +/// We only care about evaluations (custom gates) but not the domain, so we use a very small dummy +pub(super) const DUMMY_K: u32 = 7; + +/// Dummy circuit just to get the correct constraint system corresponding +/// to the circuit metadata. +#[derive(Clone)] +struct DummyAxiomCircuit { + metadata: AxiomV2CircuitMetadata, + _marker: PhantomData, +} + +/// An enum to choose between a circuit with only basic columns and gates or a circuit that in addition has RLC columns and gates. +/// The distinction is that even when `RlcConfig` has `num_rlc_columns` set to 0, it will always have the challenge `gamma`. +/// Therefore we use this enum to more clearly distinguish between the two cases. +#[derive(Clone, Debug)] +pub enum MaybeRlcConfig { + Rlc(RlcConfig), + Base(BaseConfig), +} + +// For internal use only +impl Circuit for DummyAxiomCircuit { + type Config = MaybeRlcConfig; + type FloorPlanner = SimpleFloorPlanner; + type Params = AxiomV2CircuitMetadata; + + fn without_witnesses(&self) -> Self { + self.clone() + } + + fn params(&self) -> Self::Params { + self.metadata.clone() + } + + fn configure_with_params( + meta: &mut ConstraintSystem, + metadata: Self::Params, + ) -> Self::Config { + let num_phase = metadata.num_challenge.len(); + assert!(num_phase == 1 || num_phase == 2, "only support 2 phases for now"); + let base_circuit_params = BaseCircuitParams { + k: DUMMY_K as usize, + num_advice_per_phase: metadata + .num_advice_per_phase + .iter() + .map(|x| *x as usize) + .collect(), + num_fixed: metadata.num_fixed as usize, + num_lookup_advice_per_phase: metadata + .num_lookup_advice_per_phase + .iter() + .map(|x| *x as usize) + .collect(), + lookup_bits: Some(DUMMY_K as usize - 1), // doesn't matter because we replace fixed commitments later + num_instance_columns: metadata.num_instance.len(), + }; + if num_phase == 1 { + assert!(metadata.num_rlc_columns == 0, "rlc columns only allowed in phase1"); + // Note that BaseConfig ignores lookup bits if there are no lookup advice columns + MaybeRlcConfig::Base(BaseConfig::configure(meta, base_circuit_params)) + } else { + let rlc_circuit_params = RlcCircuitParams { + base: base_circuit_params, + num_rlc_columns: metadata.num_rlc_columns as usize, + }; + MaybeRlcConfig::Rlc(RlcConfig::configure(meta, rlc_circuit_params)) + } + } + + fn configure(_: &mut ConstraintSystem) -> Self::Config { + unreachable!("must use configure_with_params") + } + + fn synthesize( + &self, + _config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region(|| "dummy", |_region| Ok(())) + } +} + +/// For internal use only, num_instance will be replaced later +pub(crate) fn dummy_vk_from_metadata( + params: &ParamsKZG, + metadata: AxiomV2CircuitMetadata, +) -> anyhow::Result> { + let dummy_circuit = DummyAxiomCircuit:: { metadata, _marker: PhantomData }; + let vk = keygen_vk_custom(params, &dummy_circuit, false)?; + Ok(vk) +} diff --git a/axiom-query/src/utils/client_circuit/metadata.rs b/axiom-query/src/utils/client_circuit/metadata.rs new file mode 100644 index 00000000..db0e6263 --- /dev/null +++ b/axiom-query/src/utils/client_circuit/metadata.rs @@ -0,0 +1,155 @@ +use anyhow::bail; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use ethers_core::types::H256; +use serde::{Deserialize, Serialize}; + +/// All configuration parameters of an Axiom Client Circuit that are +/// hard-coded into the Verify Compute Circuit (which is an Aggregation Circuit with Universality::Full). +/// +/// This metadata is only for a circuit built using `RlcCircuitBuilder` +/// or `BaseCircuitBuilder`, where the circuit _may_ be an aggregation circuit. +#[derive(Clone, Debug, Default, Serialize, Deserialize, Hash)] +#[serde(rename_all = "camelCase")] +pub struct AxiomV2CircuitMetadata { + /// Version byte for domain separation on version of Axiom client, halo2-lib, snark-verifier (for example if we switch to mv_lookup) + /// If `version = x`, this should be thought of as Axiom Query v2.x + pub version: u8, + /// Number of instances in each instance polynomial + pub num_instance: Vec, + /// Number of challenges to squeeze from transcript after each phase. + /// This `num_challenge` counts only the challenges used inside the circuit - it excludes challenges that are part of the halo2 system. + /// The full challenges, which is what `plonk_protocol.num_challenge` stores, is: + /// ```ignore + /// [ + /// my_phase0_challenges, + /// ... + /// [..my_phasen_challenges, theta], + /// [beta, gamma], + /// [alpha], + /// ] + /// ``` + pub num_challenge: Vec, + + /// Boolean for whether this is an Aggregation Circuit which has a KZG accumulator in the public instances. If true, it must be the first 12 instances. + pub is_aggregation: bool, + + // RlcCircuitParams: + /// The number of advice columns per phase + pub num_advice_per_phase: Vec, + /// The number of special advice columns that have range lookup enabled per phase + pub num_lookup_advice_per_phase: Vec, + /// Number of advice columns for the RLC custom gate + pub num_rlc_columns: u16, + /// The number of fixed columns **only** for constants + pub num_fixed: u8, + + // This is specific to the current Verify Compute Circuit implementation and provided just for data availability: + /// The maximum number of user outputs. Used to determine where to split off `compute_snark`'s public instances between user outputs and data subqueries. + /// This does **not** include the old accumulator elliptic curve points if + /// the `compute_snark` is from an aggregation circuit. + pub max_outputs: u16, +} + +impl AxiomV2CircuitMetadata { + pub fn encode(&self) -> anyhow::Result { + let mut encoded = vec![]; + encoded.write_u8(self.version)?; + + encoded.write_u8(self.num_instance.len().try_into()?)?; + for &num_instance in &self.num_instance { + encoded.write_u32::(num_instance)?; + } + + let num_phase = self.num_challenge.len(); + if num_phase == 0 { + bail!("num_challenge must be non-empty") + } + encoded.write_u8(num_phase.try_into()?)?; + for &num_challenge in &self.num_challenge { + encoded.write_u8(num_challenge)?; + } + + encoded.write_u8(self.is_aggregation as u8)?; + + // encode RlcCircuitParams: + if self.num_advice_per_phase.len() > num_phase { + bail!("num_advice_per_phase must be <= num_phase") + } + let mut num_advice_cols = self.num_advice_per_phase.clone(); + num_advice_cols.resize(num_phase, 0); + for num_advice_col in num_advice_cols { + encoded.write_u16::(num_advice_col)?; + } + + if self.num_lookup_advice_per_phase.len() > num_phase { + bail!("num_lookup_advice_per_phase must be <= num_phase") + } + let mut num_lookup_advice_cols = self.num_lookup_advice_per_phase.clone(); + num_lookup_advice_cols.resize(num_phase, 0); + for num_lookup_advice_col in num_lookup_advice_cols { + encoded.write_u8(num_lookup_advice_col)?; + } + + encoded.write_u16::(self.num_rlc_columns)?; + encoded.write_u8(self.num_fixed)?; + + encoded.write_u16::(self.max_outputs)?; + + if encoded.len() > 32 { + bail!("circuit metadata cannot be packed into bytes32") + } + encoded.resize(32, 0); + Ok(H256::from_slice(&encoded)) + } +} + +pub fn decode_axiom_v2_circuit_metadata(encoded: H256) -> anyhow::Result { + let mut reader = &encoded[..]; + let version = reader.read_u8()?; + if version != 0 { + bail!("invalid version") + } + let num_instance_len = reader.read_u8()? as usize; + let mut num_instance = Vec::with_capacity(num_instance_len); + for _ in 0..num_instance_len { + num_instance.push(reader.read_u32::()?); + } + let num_phase = reader.read_u8()? as usize; + let mut num_challenge = Vec::with_capacity(num_phase); + for _ in 0..num_phase { + num_challenge.push(reader.read_u8()?); + } + + let is_aggregation = reader.read_u8()?; + if is_aggregation > 1 { + bail!("is_aggregation is not boolean"); + } + let is_aggregation = is_aggregation == 1; + + // decode RlcCircuitParams: + let mut num_advice_per_phase = Vec::with_capacity(num_phase); + for _ in 0..num_phase { + num_advice_per_phase.push(reader.read_u16::()?); + } + let mut num_lookup_advice_per_phase = Vec::with_capacity(num_phase); + for _ in 0..num_phase { + num_lookup_advice_per_phase.push(reader.read_u8()?); + } + + let num_rlc_columns = reader.read_u16::()?; + let num_fixed = reader.read_u8()?; + + let max_outputs = reader.read_u16::()?; + + Ok(AxiomV2CircuitMetadata { + version, + num_instance, + num_challenge, + num_advice_per_phase, + num_lookup_advice_per_phase, + num_rlc_columns, + num_fixed, + is_aggregation, + max_outputs, + }) +} diff --git a/axiom-query/src/utils/client_circuit/mod.rs b/axiom-query/src/utils/client_circuit/mod.rs new file mode 100644 index 00000000..516b4949 --- /dev/null +++ b/axiom-query/src/utils/client_circuit/mod.rs @@ -0,0 +1,12 @@ +pub mod default_circuit; +/// Client circuit metadata type and encoding/decoding +pub mod metadata; +pub mod vkey; + +// Notes: + +// - USER_MAX_OUTPUTS is the number of logical outputs from the user +// - each output is assumed to consist of USER_RESULT_BYTES bytes, this is the format they get it in solidity +// - in ZK circuit, each output is represented using USER_RESULT_FIELD_ELEMENTS field elements +// - this means the user outputs take up `USER_MAX_OUTPUTS * USER_RESULT_FIELD_ELEMENTS` public instance cells +// - currently there may be hardcoded assumptions that results are bytes32, represented as 2 field elements in HiLo form, but the idea is the use the generic constants as much as possible diff --git a/axiom-query/src/utils/client_circuit/vkey.rs b/axiom-query/src/utils/client_circuit/vkey.rs new file mode 100644 index 00000000..a09d31cc --- /dev/null +++ b/axiom-query/src/utils/client_circuit/vkey.rs @@ -0,0 +1,77 @@ +use std::io::Read; + +use anyhow::Result; +use axiom_codec::utils::reader::{read_curve_compressed, read_field_le, read_h256}; +use axiom_eth::{ + halo2_proofs::poly::kzg::commitment::ParamsKZG, + halo2curves::{ + bn256::{Bn256, G1Affine}, + CurveAffine, + }, + snark_verifier::{ + system::halo2::{compile, Config}, + util::arithmetic::{root_of_unity, Domain}, + verifier::plonk::PlonkProtocol, + }, + snark_verifier_sdk::{halo2::aggregation::AggregationCircuit, CircuitExt}, +}; +use rand::{rngs::StdRng, SeedableRng}; +use serde::{Deserialize, Serialize}; + +use super::{ + default_circuit::{dummy_vk_from_metadata, DUMMY_K}, + metadata::{decode_axiom_v2_circuit_metadata, AxiomV2CircuitMetadata}, +}; + +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] +pub struct OnchainVerifyingKey { + pub circuit_metadata: AxiomV2CircuitMetadata, + pub transcript_initial_state: C::Scalar, + pub preprocessed: Vec, +} + +impl OnchainVerifyingKey { + pub fn read(reader: &mut impl Read) -> Result + where + C::Scalar: axiom_codec::Field, + { + let encoded_circuit_metadata = read_h256(reader)?; + let circuit_metadata = decode_axiom_v2_circuit_metadata(encoded_circuit_metadata)?; + + let transcript_initial_state = read_field_le(reader)?; + let mut preprocessed = Vec::new(); + while let Ok(point) = read_curve_compressed(reader) { + preprocessed.push(point); + } + Ok(OnchainVerifyingKey { circuit_metadata, preprocessed, transcript_initial_state }) + } +} + +impl OnchainVerifyingKey { + // @dev Remark: PlonkProtocol fields are public so we can perform "surgery" on them, whereas halo2 VerifyingKey has all fields private so we can't. + pub fn into_plonk_protocol(self, k: usize) -> Result> { + let OnchainVerifyingKey { circuit_metadata, transcript_initial_state, preprocessed } = self; + // We can make a dummy trusted setup here because we replace the fixed commitments afterwards + let kzg_params = ParamsKZG::::setup(DUMMY_K, StdRng::seed_from_u64(0)); + let dummy_vk = dummy_vk_from_metadata(&kzg_params, circuit_metadata.clone())?; + let num_instance = circuit_metadata.num_instance.iter().map(|x| *x as usize).collect(); + let acc_indices = circuit_metadata + .is_aggregation + .then(|| AggregationCircuit::accumulator_indices().unwrap()); + let mut protocol = compile( + &kzg_params, + &dummy_vk, + Config::kzg().with_num_instance(num_instance).with_accumulator_indices(acc_indices), + ); + // See [snark_verifier::system::halo2::compile] to see how [PlonkProtocol] is constructed + // These are the parts of `protocol` that are different for different vkeys or different `k` + protocol.domain = Domain::new(k, root_of_unity(k)); + protocol.domain_as_witness = None; + protocol.preprocessed = preprocessed; + protocol.transcript_initial_state = Some(transcript_initial_state); + // Do not MSM public instances (P::QUERY_INSTANCE should be false) + protocol.instance_committing_key = None; + protocol.linearization = None; + Ok(protocol) + } +} diff --git a/axiom-query/src/utils/codec.rs b/axiom-query/src/utils/codec.rs new file mode 100644 index 00000000..28783232 --- /dev/null +++ b/axiom-query/src/utils/codec.rs @@ -0,0 +1,70 @@ +use axiom_codec::{ + encoder::field_elements::{ + FIELD_ENCODED_SOLIDITY_NESTED_MAPPING_DEPTH_IDX, NUM_FE_ANY, + NUM_FE_SOLIDITY_NESTED_MAPPING_WITHOUT_KEYS, + }, + types::{field_elements::*, native::SubqueryType}, +}; +use axiom_eth::halo2_base::{ + gates::GateInstructions, + utils::ScalarField, + AssignedValue, Context, + QuantumCell::{Constant, Existing}, +}; + +pub type AssignedSubqueryKey = SubqueryKey>; +pub type AssignedSubqueryOutput = SubqueryOutput>; + +pub type AssignedHeaderSubquery = FieldHeaderSubquery>; +pub type AssignedAccountSubquery = FieldAccountSubquery>; +pub type AssignedStorageSubquery = FieldStorageSubquery>; +pub type AssignedTxSubquery = FieldTxSubquery>; +pub type AssignedReceiptSubquery = FieldReceiptSubquery>; +pub type AssignedSolidityNestedMappingSubquery = + FieldSolidityNestedMappingSubquery>; + +pub type AssignedHeaderSubqueryResult = FieldHeaderSubqueryResult>; +pub type AssignedAccountSubqueryResult = FieldAccountSubqueryResult>; +pub type AssignedStorageSubqueryResult = FieldStorageSubqueryResult>; +pub type AssignedTxSubqueryResult = FieldTxSubqueryResult>; +pub type AssignedReceiptSubqueryResult = FieldReceiptSubqueryResult>; +pub type AssignedSolidityNestedMappingSubqueryResult = + FieldSolidityNestedMappingSubqueryResult>; + +pub type AssignedSubqueryResult = FlattenedSubqueryResult>; + +pub fn assign_flattened_subquery_result( + ctx: &mut Context, + f: &FlattenedSubqueryResult, +) -> AssignedSubqueryResult { + let key = f.key.0.map(|x| ctx.load_witness(x)); + let value = f.value.0.map(|x| ctx.load_witness(x)); + FlattenedSubqueryResult::new(SubqueryKey(key), SubqueryOutput(value)) +} + +/// Parses the subquery type and determines the actual number of field elements in the +/// field element encoding, **including** the subquery type. +/// +/// This is `1` more than the flattened length of `FieldSubquery**` when the subquery type is not SolidityNestedMapping. When the subquery type is SolidityNestedMapping, the number of field elements is `1 + NUM_FE_SOLIDITY_NESTED_MAPPING_WITHOUT_KEYS + 2 * mapping_depth`. +pub fn get_num_fe_from_subquery_key( + ctx: &mut Context, + gate: &impl GateInstructions, + key: &AssignedSubqueryKey, +) -> AssignedValue { + // num_fe_by_type will include 1 field element for the type itself, while NUM_FE_ANY does not + let num_fe_by_type: Vec<_> = NUM_FE_ANY + .iter() + .enumerate() + .map(|(subtype, &num_fe)| { + if subtype == SubqueryType::SolidityNestedMapping as usize { + let num_without_keys = + Constant(F::from(NUM_FE_SOLIDITY_NESTED_MAPPING_WITHOUT_KEYS as u64 + 1)); + let mapping_depth = key.0[FIELD_ENCODED_SOLIDITY_NESTED_MAPPING_DEPTH_IDX]; + Existing(gate.mul_add(ctx, mapping_depth, Constant(F::from(2)), num_without_keys)) + } else { + Constant(F::from(num_fe as u64 + 1)) + } + }) + .collect(); + gate.select_from_idx(ctx, num_fe_by_type, key.0[0]) +} diff --git a/axiom-query/src/utils/mod.rs b/axiom-query/src/utils/mod.rs new file mode 100644 index 00000000..710a5fde --- /dev/null +++ b/axiom-query/src/utils/mod.rs @@ -0,0 +1,4 @@ +/// Utilities to reconstruct the halo2 VerifyingKey and proof of a client circuit from abridged on-chain data +pub mod client_circuit; +/// Utilities halo2 functions related to codec +pub mod codec; diff --git a/axiom-query/src/verify_compute/README.md b/axiom-query/src/verify_compute/README.md new file mode 100644 index 00000000..ee90bc15 --- /dev/null +++ b/axiom-query/src/verify_compute/README.md @@ -0,0 +1,214 @@ +# Verify Compute Circuit + +## Query Schema + +The Verify Compute circuit is an `AggregationCircuit` with `VerifierUniverality::Full` that verifies a user supplied `compute_snark`. +Unlike internal universal aggregation circuits that hash part of the verifying key of the aggregated snark into `aggVkeyHash`, here we uniquely tag the verifying key of +`compute_snark` by calculating a `query_schema` inside the Verify Compute circuit and exposing it as a public output. + +In the Axiom V2 Query protocol, we will use _different_ Verify Compute circuits in different aggregation strategies. The guarantee is that: + +> If a `compute_snark` is verified by the Axiom V2 Query protocol, then its `query_schema` is a **unique** identifier for the circuit that was used to create the `compute_snark`. + +We explain below how this guarantee is achieved. + +### Background + +The `halo2` [`VerifyingKey`](https://github.com/axiom-crypto/halo2/blob/f335ffc4440620e3afaa5ba3373764b60a528c51/halo2_proofs/src/plonk.rs#L47) contains the full context of a concrete circuit and the `halo2` proof protocol ensures that if a proof verifies against a `VerifyingKey` and a trusted setup, then the proof is valid precisely for the concrete circuit the `VerifyingKey` was constructed from. This is what is used when you verify a proof directly in Rust using `halo2`. + +The `snark-verifier` crate re-formats the `VerifyingKey` struct into the [`PlonkProtocol`](https://github.com/axiom-crypto/snark-verifier/blob/5c5791fb27c48b004c93d5a4e168f971d4350ce5/snark-verifier/src/verifier/plonk/protocol.rs#L54) struct using the [`compile`](https://github.com/axiom-crypto/snark-verifier/blob/5c5791fb27c48b004c93d5a4e168f971d4350ce5/snark-verifier/src/system/halo2.rs#L82) function. This struct still contains the full context of the circuit used to generate a proof and is all that is needed to verify a proof. + +An aggregation circuit created using `snark-verifier-sdk` will verify a given snark against its `PlonkProtocol`, but defer the final pairing check in the KZG opening by RLC-ing the `G1Affine` points in the pairing check into a running `KzgAccumulator`, which is added to the public instances of the aggregation circuit. Only the final EVM or Rust native verifier will read these `G1Affine` points from the public instances and do the final pairing check. + +In a plain aggregation circuit with `VerifierUniversality::None`, all parts of the `PlonkProtocol` are loaded as constants in the aggregation circuit. In an aggregation circuit with `VerifierUniversality::Full`, the following parts of `PlonkProtocol` are loaded as witnesses: + +- the `log_2` domain size `k` +- the transcript initial state (optional starting value of the transcript) +- the `preprocessed` commitments to the fixed columns and commitment to the permutation argument (sigma polynomials) + +Even with `VerifierUniversality::Full`, the following properties of the `PlonkProtocol` of the snark(s) to be aggregated are hard-coded into the aggregation circuit, meaning they are loaded either explicitly or implicitly as constants and changes to these parameters determine different aggregation circuits: + +- the number of instance columns and number of instances per column +- the number of challenges and number of challenge phases +- the number of advice columns (in each phase) +- the custom gates used by the circuit +- whether the snark to be aggregated has a `KzgAccumulator` in its public instances that needs a deferred pairing check, and if so, what public instance indices they are located at + +Here is the `PlonkProtocol` struct in full: + +```rust +pub struct PlonkProtocol +where + C: CurveAffine, + L: Loader, +{ + // Loaded as witnesses in `Universality::Full`: + /// Working domain. + pub domain: Domain, + /// Prover and verifier common initial state to write to transcript if any. + pub transcript_initial_state: Option, + /// Commitments of preprocessed polynomials. + pub preprocessed: Vec, + + // Always loaded as constants: + /// Number of instances in each instance polynomial. + pub num_instance: Vec, + /// Number of witness polynomials in each phase. + pub num_witness: Vec, + /// Number of challenges to squeeze from transcript after each phase. + pub num_challenge: Vec, + /// Evaluations to read from transcript. + pub evaluations: Vec, + /// [`crate::pcs::PolynomialCommitmentScheme`] queries to verify. + pub queries: Vec, + /// Structure of quotient polynomial. + pub quotient: QuotientPolynomial, + /// Instance polynomials committing key if any. + pub instance_committing_key: Option>, + /// Linearization strategy. + pub linearization: Option, + /// Indices (instance polynomial index, row) of encoded accumulators + pub accumulator_indices: Vec>, +} +``` + +In our use cases the `instance_committing_key` and `linearization` are always `None` so we do not discuss them. + +### Encoded Query Schema + +We define + +```javascript +encoded_query_schema = solidityPacked( + ["uint8", "uint16", "uint8", "bytes32", "bytes32[]"], + [k, result_len, onchain_vkey_len, onchain_vkey] +); + +query_schema = keccak(encoded_query_schema); +``` + +where `k` is the `log_2` size of the domain (number of rows in the circuit), `result_len` is the length of `compute_results` in the public instances of `compute_snark`, where `onchain_vkey_len = onchain_vkey.len()` for `onchain_vkey: bytes32[]`. + +We will define `onchain_vkey` so that it is a serialization of `PlonkProtocol` **under the assumption** that the `PlonkProtocol` is from a circuit created using a `halo2-base` `BaseCircuitBuilder` or `axiom-eth` `RlcCircuitBuilder`, where the circuit _may_ be an aggregation circuit. This `onchain_vkey` is submitted on-chain as calldata as part of a `sendQuery` call, so it is designed to be the minimal context required to reconstruct the `PlonkProtocol` of the `compute_snark` to be verified. + +We define the `onchain_vkey: bytes32[]` as the concatenation of the following fields: + +- `encoded_circuit_metadata: bytes32` - this is loaded as a constant (see below) +- `transcript_initial_state: bytes32` (`Fr`) - this is loaded as a witness +- `preprocessed: bytes32[]` (`Vec`) - this is loaded as a witness + +The parts of `PlonkProtocol` that are loaded as witnesses in the Verify Compute circuit are all included in the `encoded_query_schema`. We now explain how the constant parts are accounted for. + +We define `encoded_circuit_metadata` as an encoding into a `bytes32` of: + +- `version: u8` - a reserved byte for versioning in case `halo2-base` or `halo2` changes +- `num_instance: Vec` +- `RlcCircuitParams` except `k` +- `is_aggregation: bool` + +This encoding is done outside of the circuit, and the encoded `bytes32` is loaded _as a constant_ in the Verify Compute circuit. This can be done because all variables that are encoded are constant in the circuit. +To ensure that what is encoded in `encoded_circuit_metadata` indeed matches what is used in the Verify Compute circuit, we generate the `PlonkProtocol` from the `encoded_circuit_metadata` and the provided `compute_snark` itself. + +Explicitly, `encoded_circuit_metadata` will be a packing of the following struct into a `bytes32`: + +```rust +pub struct AxiomV2CircuitMetadata { + /// Version byte for domain separation on version of Axiom client, halo2-lib, snark-verifier. + /// If `version = x`, this should be thought of as Axiom Query v2.x + pub version: u8, + /// Number of instances in each instance polynomial + pub num_instance: Vec, + /// Number of challenges to squeeze from transcript after each phase. + pub num_challenge: Vec, + + /// Boolean for whether this is an Aggregation Circuit which has a KZG accumulator in the public instances. If true, it must be the first 12 instances. + pub is_aggregation: bool, + + // RlcCircuitParams: + /// The number of advice columns per phase + pub num_advice_per_phase: Vec, + /// The number of special advice columns that have range lookup enabled per phase + pub num_lookup_advice_per_phase: Vec, + /// Number of advice columns for the RLC custom gate + pub num_rlc_columns: u16, + /// The number of fixed columns + pub num_fixed: u8, + + // This is specific to the current Verify Compute Circuit implementation and provided just for data availability: + /// The maximum number of user outputs. Used to determine where to split off `compute_snark`'s public instances between user outputs and data subqueries. + /// This does **not** include the old accumulator elliptic curve points if + /// the `compute_snark` is from an aggregation circuit. + pub user_max_outputs: usize, +} +``` + +The actual encoding is defined [here](../utils/client_circuit/metadata.rs). + +> The Axiom V2 Query protocol will only create Verify Compute circuits that verify snarks created using `RlcCircuitBuilder` or `BaseCircuitBuilder`, where the circuits _may_ be aggregation circuits. + +This can be checked by inspection of the circuits used in production. + +Given this, the `encoded_query_schema` contains all information needed to reconstruct `PlonkProtocol`. Therefore `query_schema` is a unique identifier for `PlonkProtocol` and hence the circuit that `compute_snark` came from. + +## Query Hash + +The Verify Compute Circuit also computes the commitment to the entire query by calculating + +```javascript +query_hash = keccak(encoded_query); +encoded_query = solidityPacked( + ["uint8", "uint64", "bytes32", "bytes"], + [version, source_chain_id, data_query_hash, encoded_compute_query] +); +encoded_compute_query = solidityPacked( + ["bytes", "uint32", "bytes"], + [encoded_query_schema, proof_len, compute_proof] +); +``` + +where `proof_len = compute_proof.len()` in bytes and + +```javascript +compute_proof = solidityPacked( + ["bytes32[2]", "bytes32[]", "bytes"], + [compute_accumulator, compute_results, compute_proof_transcript] +); +``` + +where + +- `compute_accumulator` is either `[bytes32(0), bytes32(0)]` if there is no accumulator, or `[lhs, rhs]` where `lhs, rhs` are compressed `G1Affine` points as `bytes32` representing the KZG accumulator, +- `compute_results` are the public instances of the `compute_snark`, converted from hi-lo form to `bytes32`, that cannot be recovered from data subqueries. The length of `compute_results` equals `result_len`. +- `compute_proof_transcript` is the actual `Vec` halo2 proof. + +## Data Query Format + +Each data query consists of the fields: + +```rust +pub struct AxiomV2DataQuery { + pub source_chain_id: u64, + pub subqueries: Vec, +} +``` + +and each subquery consists of the fields: + +```rust +pub struct Subquery { + /// uint16 type of subquery + pub subquery_type: SubqueryType, + /// Subquery data encoded, _without_ the subquery type. Length is variable and **not** resized. + pub encoded_subquery_data: Bytes, +} +``` + +where `SubqueryType` is an enum represented as `uint16`. + +We commit to the data query by: + +- `dataQueryHash` (bytes32): The Keccak hash of `sourceChainId` concatenated with the array with entries given by: + - `subquery_hash = keccak(solidityPacked(["uint16", "bytes"], [subquery_type, encoded_subquery_data])` + +The individual `subquery_hash`s are already computed by the [Results Root Circuit](../components/results/), so +the Verify Compute Circuit just concatenates them all and computes `keccak` of the concatenation. diff --git a/axiom-query/src/verify_compute/circuit.rs b/axiom-query/src/verify_compute/circuit.rs new file mode 100644 index 00000000..4daeed1a --- /dev/null +++ b/axiom-query/src/verify_compute/circuit.rs @@ -0,0 +1,363 @@ +use std::iter::zip; + +use anyhow::bail; +use axiom_codec::{ + constants::{MAX_SUBQUERY_OUTPUTS, USER_RESULT_FIELD_ELEMENTS, USER_RESULT_LEN_BYTES}, + types::field_elements::SUBQUERY_RESULT_LEN, + HiLo, +}; +use axiom_eth::{ + halo2_base::{ + gates::{GateInstructions, RangeInstructions}, + halo2_proofs::halo2curves::bn256::Fr, + safe_types::{SafeBytes32, SafeTypeChip}, + AssignedValue, + QuantumCell::Constant, + }, + halo2_proofs::plonk::ConstraintSystem, + keccak::{types::ComponentTypeKeccak, KeccakChip}, + rlc::{ + circuit::builder::RlcCircuitBuilder, + types::{AssignedVarLenVec, ConcatVarFixedArrayWitness}, + }, + snark_verifier_sdk::{ + halo2::aggregation::{ + aggregate_snarks, AggregationCircuit, PreprocessedAndDomainAsWitness, + SnarkAggregationOutput, VerifierUniversality, + }, + CircuitExt, SHPLONK, + }, + utils::{ + build_utils::aggregation::CircuitMetadata, + circuit_utils::unsafe_lt_mask, + component::{ + circuit::{ComponentBuilder, ComponentCircuitImpl, CoreBuilder, CoreBuilderOutput}, + promise_collector::PromiseCaller, + promise_loader::single::PromiseLoader, + types::FixLenLogical, + utils::create_hasher, + NUM_COMPONENT_OWNED_INSTANCES, + }, + enforce_conditional_equality, + snark_verifier::NUM_FE_ACCUMULATOR, + uint_to_bytes_be, + }, +}; +use itertools::{zip_eq, EitherOrBoth, Itertools}; + +use crate::{ + components::results::results_root::get_results_root_poseidon, ff::Field as _, + utils::client_circuit::vkey::OnchainVerifyingKey, + verify_compute::types::LogicalPublicInstanceVerifyCompute, +}; + +use super::{ + query_hash::{ + encode_compute_query_phase1, encode_query_schema, get_data_query_hash, get_query_hash_v2, + get_query_schema_hash, + }, + types::{CircuitInputVerifyCompute, ComponentTypeVerifyCompute, CoreParamsVerifyCompute}, +}; + +type F = Fr; // Specialize to Fr for aggregation + +pub struct CoreBuilderVerifyCompute { + input: Option, + params: CoreParamsVerifyCompute, + payload: Option<(KeccakChip, ConcatVarFixedArrayWitness)>, +} + +pub type PromiseLoaderVerifyCompute = PromiseLoader>; +pub type ComponentCircuitVerifyCompute = + ComponentCircuitImpl; + +impl CircuitMetadata for CoreBuilderVerifyCompute { + const HAS_ACCUMULATOR: bool = true; + /// IMPORTANT: Unlike most aggregation circuits, the accumulator indices here DO NOT start from 0 because the first [NUM_COMPONENT_OWNED_INSTANCES] are owned by [`ComponentCircuitImpl`]. + fn accumulator_indices() -> Option> { + Some((0..NUM_FE_ACCUMULATOR).map(|i| (0, NUM_COMPONENT_OWNED_INSTANCES + i)).collect()) + } + fn num_instance(&self) -> Vec { + // For reference only, overriden by `num_instance` in `ComponentCircuitImpl` + vec![ + NUM_COMPONENT_OWNED_INSTANCES + NUM_FE_ACCUMULATOR + super::types::NUM_LOGICAL_INSTANCE, + ] + } +} + +impl CoreBuilderVerifyCompute { + pub fn client_max_outputs(&self) -> usize { + self.params.client_metadata().max_outputs as usize + } +} + +impl ComponentBuilder for CoreBuilderVerifyCompute { + type Params = CoreParamsVerifyCompute; + + fn new(params: Self::Params) -> Self { + Self { input: None, params, payload: None } + } + fn get_params(&self) -> Self::Params { + self.params.clone() + } + fn clear_witnesses(&mut self) { + self.payload = None; + } + fn calculate_params(&mut self) -> Self::Params { + self.params.clone() + } + fn configure_with_params(_: &mut ConstraintSystem, _: Self::Params) {} +} + +impl CoreBuilder for CoreBuilderVerifyCompute { + type CompType = ComponentTypeVerifyCompute; + type PublicInstanceValue = LogicalPublicInstanceVerifyCompute; + type PublicInstanceWitness = LogicalPublicInstanceVerifyCompute>; + type CoreInput = CircuitInputVerifyCompute; + + /// Checks that the circuit params (fixed in circuit) match the parts of the inputs that are witnesses (variable in circuit). + /// + /// If `nonempty_compute_query == false`, then `compute_snark` must be a dummy snark that will verify. + fn feed_input(&mut self, mut input: Self::CoreInput) -> anyhow::Result<()> { + let cap = self.params.subquery_results_capacity(); + let len = input.subquery_results.results.len(); + if cap < len { + bail!("Feed CircuitInputVerifyCompute Error: length of subquery_results {len} is greater than subquery_results_capacity {cap}"); + } + input.subquery_results.resize_with_first(cap); + if self.params.preprocessed_len() != input.compute_snark().protocol.preprocessed.len() { + bail!("Feed CircuitInputVerifyCompute Error: preprocessed_len does not match compute_snark"); + } + // Enforce that the PlonkProtocol in compute_snark **must** be consistent with circuit_params.circuit_metadata + // circuit_metadata is fixed, but transcript_initial_state and preprocessed may be variable + let compute_snark = &mut input.compute_snark; + let client_vk = OnchainVerifyingKey { + circuit_metadata: self.params.client_metadata().clone(), + transcript_initial_state: compute_snark.protocol.transcript_initial_state.unwrap(), + preprocessed: std::mem::take(&mut compute_snark.protocol.preprocessed), + }; + compute_snark.protocol = client_vk.into_plonk_protocol(compute_snark.protocol.domain.k)?; + + self.input = Some(input); + Ok(()) + } + + fn virtual_assign_phase0( + &mut self, + builder: &mut RlcCircuitBuilder, + promise_caller: PromiseCaller, + ) -> CoreBuilderOutput { + // preamble + let keccak_chip = + KeccakChip::new_with_promise_collector(builder.range_chip(), promise_caller.clone()); + let keccak = &keccak_chip; + let range = keccak.range(); + let gate = range.gate(); + let safe = SafeTypeChip::new(range); + + // Assumption: we already have input when calling this function. + let input = self.input.as_ref().unwrap(); + // verify compute + let pool = builder.base.pool(0); + let SnarkAggregationOutput { + mut preprocessed, + mut previous_instances, + accumulator, + mut proof_transcripts, + } = aggregate_snarks::( + pool, + range, + self.params.svk().into(), + [input.compute_snark().clone()], + VerifierUniversality::Full, + ); + let ctx = builder.base.main(0); + let source_chain_id = ctx.load_witness(F::from(input.source_chain_id)); + let ne_cq = safe.load_bool(ctx, input.nonempty_compute_query); + // get information from the compute snark + let PreprocessedAndDomainAsWitness { mut preprocessed, k } = preprocessed.pop().unwrap(); + let transcript_init_state = preprocessed.pop().unwrap(); + let compute_proof_transcript = proof_transcripts.pop().unwrap(); + let mut compute_instances = previous_instances.pop().unwrap(); + // check if `compute_snark` was an aggregation circuit. if it is, remove the old accumulator from the compute_snark public instances + let compute_accumulator = { + let acc_indices = &input.compute_snark().protocol.accumulator_indices; + if acc_indices.is_empty() { + None + } else { + assert_eq!(acc_indices.len(), 1); + // For uniformity, we only allow aggregation circuit accumulator indices to be (0,0), ..., (0, 4 * LIMBS - 1) + assert_eq!(&acc_indices[0], &AggregationCircuit::accumulator_indices().unwrap()); + Some(compute_instances.drain(0..NUM_FE_ACCUMULATOR).collect_vec()) + } + }; + let mut compute_results = compute_instances; + let query_instances = + compute_results.split_off(USER_RESULT_FIELD_ELEMENTS * self.client_max_outputs()); + let result_len = ctx.load_witness(F::from(input.result_len as u64)); + const RESULT_LEN_BITS: usize = 8 * USER_RESULT_LEN_BYTES; + range.range_check(ctx, result_len, RESULT_LEN_BITS); + // Note: result_len is the length in number of HiLos, so `compute_results` should be thought of as having variable length `2 * result_len` + let compute_results = AssignedVarLenVec { values: compute_results, len: result_len }; + // get query schema + let encoded_query_schema = encode_query_schema( + ctx, + range, + k, + result_len, + self.params.client_metadata(), + transcript_init_state, + &preprocessed, + ); + let query_schema = get_query_schema_hash(ctx, keccak, &encoded_query_schema, ne_cq); + // load subquery hashes + let subquery_hashes = &input.subquery_results.subquery_hashes; + let (subquery_hashes, subquery_hashes_hilo): (Vec<_>, Vec<_>) = subquery_hashes + .iter() + .map(|subquery_hash| { + let hilo = subquery_hash.hi_lo(); + let hilo = hilo.map(|x| ctx.load_witness(x)); + let bytes = hilo.map(|x| uint_to_bytes_be(ctx, range, &x, 16)).concat(); + (SafeBytes32::try_from(bytes).unwrap(), hilo) + }) + .unzip(); + // get data query hash + let num_subqueries = input.subquery_results.num_subqueries as u64; + let num_subqueries = ctx.load_witness(F::from(num_subqueries)); + let total_subquery_capacity = input.subquery_results.results.len() as u64; + range.check_less_than_safe(ctx, num_subqueries, total_subquery_capacity + 1); + let (data_query_hash, encoded_source_chain_id) = + get_data_query_hash(ctx, keccak, source_chain_id, &subquery_hashes, num_subqueries); + // get query hash + let (query_hash, concat_proof_witness) = get_query_hash_v2( + ctx, + keccak, + &encoded_source_chain_id, + &data_query_hash, + &encoded_query_schema, + compute_accumulator, + &compute_results, + compute_proof_transcript, + ne_cq, + ); + + // result_len should be <= user.max_outputs if computeQuery non-empty, otherwise <= num_subqueries + let max_res_len = gate.select( + ctx, + Constant(F::from(self.client_max_outputs() as u64)), + num_subqueries, + ne_cq, + ); + let max_res_len_p1 = gate.inc(ctx, max_res_len); + range.check_less_than(ctx, result_len, max_res_len_p1, RESULT_LEN_BITS); + // Load subquery results from custom promise call + let table = input.subquery_results.results.assign(ctx); + // If user query nonempty, then table must match `query_instances` + assert_eq!(query_instances.len() % SUBQUERY_RESULT_LEN, 0); + let user_subqueries = query_instances.chunks_exact(SUBQUERY_RESULT_LEN); + // we will force user subquery to be 0 for index >= num_subqueries. Due to re-sizing logic in ResultsRoot circuit, the table.rows might not be 0 for those indices. + let subquery_mask = + unsafe_lt_mask(ctx, gate, num_subqueries, total_subquery_capacity as usize); + for (i, it) in user_subqueries.zip_longest(&table.rows).enumerate() { + match it { + EitherOrBoth::Both(user, row) => { + let key = &row.key.0; + let out = &row.value.0; + for (&usr, &res) in zip(user, key.iter().chain(out.iter())) { + let res = gate.mul(ctx, res, subquery_mask[i]); + enforce_conditional_equality(ctx, gate, usr, res, ne_cq); + } + } + EitherOrBoth::Left(user) => { + // If for some reason we allow promise table to be shorter than user max subqueries, the extra user subqueries should all be Null + for v in user { + gate.assert_is_const(ctx, v, &Fr::ZERO); + } + } + EitherOrBoth::Right(_) => { + // It is OK to have more promised subquery results than user subqueries + break; + } + } + } + + // user results are bytes32 in hi-lo form, we need to compute keccak hash of the bytes32 concatenated + // length in bytes + let result_byte_len = gate.mul(ctx, result_len, Constant(F::from(32))); + // The compute results are the `compute_results` if non-empty computeQuery. Otherwise they are the first `result_len` output values from the subquery results table + // We account for the fact that `compute_results` and `table.rows` can have different compile-time lengths + let mut compute_results_bytes = vec![]; + for it in + compute_results.values.chunks_exact(USER_RESULT_FIELD_ELEMENTS).zip_longest(&table.rows) + { + // hi-lo form + let words = match it { + EitherOrBoth::Both(user, subquery_res) => zip_eq(user, &subquery_res.value.0) + .map(|(&user, &val)| gate.select(ctx, user, val, *ne_cq.as_ref())) + .collect_vec(), + EitherOrBoth::Left(user) => user.to_vec(), + EitherOrBoth::Right(subquery_res) => subquery_res.value.0.to_vec(), + }; + for word in &words { + // this performs the 128 bit range check: + compute_results_bytes.extend(uint_to_bytes_be(ctx, range, word, 16)); + } + } + // we checked result_len <= computeQuery.k != 0 ? user.max_outputs : num_subqueries above + // so the above selection is safe + let compute_results_hash = keccak.keccak_var_len( + ctx, + compute_results_bytes.into_iter().map(From::from).collect(), + result_byte_len, + 0, + ); + + // not optimal: recomputing spec even though PromiseLoader also uses hasher + let mut poseidon = create_hasher(); + poseidon.initialize_consts(ctx, range.gate()); + // generate promise commit from table and subquery_hashes + let results_root_poseidon = { + assert_eq!(table.rows.len(), total_subquery_capacity as usize); + get_results_root_poseidon( + ctx, + range, + &poseidon, + &table.rows, + num_subqueries, + &subquery_mask, + ) + }; + let promise_subquery_hashes = { + // this exactly matches what is done in resultsRoot component circuit. + // however `max_num_subqueries` here does not need to match the `total_subquery_capacity` in ResultsRoot circuit + let to_commit = subquery_hashes_hilo.into_iter().flatten().collect_vec(); + let len = gate.mul(ctx, num_subqueries, Constant(F::from(MAX_SUBQUERY_OUTPUTS as u64))); + poseidon.hash_var_len_array(ctx, range, &to_commit, len) + }; + + let logical_pis = LogicalPublicInstanceVerifyCompute { + accumulator, + source_chain_id, + compute_results_hash: HiLo::from_hi_lo(compute_results_hash.hi_lo()), + query_hash: HiLo::from_hi_lo(query_hash.hi_lo()), + query_schema: HiLo::from_hi_lo(query_schema.hi_lo()), + results_root_poseidon, + promise_subquery_hashes, + }; + self.payload = Some((keccak_chip, concat_proof_witness)); + + CoreBuilderOutput { + public_instances: logical_pis.into_raw(), + virtual_table: vec![], + logical_results: vec![], + } + } + + fn virtual_assign_phase1(&mut self, builder: &mut RlcCircuitBuilder) { + let (keccak, payload) = self.payload.take().unwrap(); + let gate = keccak.gate(); + let rlc = builder.rlc_chip(gate); + let rlc_pair = builder.rlc_ctx_pair(); + encode_compute_query_phase1(rlc_pair, gate, &rlc, payload); + } +} diff --git a/axiom-query/src/verify_compute/mod.rs b/axiom-query/src/verify_compute/mod.rs new file mode 100644 index 00000000..e0bfd5a4 --- /dev/null +++ b/axiom-query/src/verify_compute/mod.rs @@ -0,0 +1,16 @@ +//! # Verify Compute Circuit +//! +//! Verifies the user proof (`computeQuery`) and calculates the query hash +//! by de-commiting subquery results and subquery hashes from two public instances. +//! +//! The Axiom Aggregation Circuit **must** check that these public instances agree +//! with the public instances from the Subquery Aggregation Circuit. +//! +pub mod circuit; +/// Compute `dataQueryHash` and `queryHash` +pub mod query_hash; +pub mod types; +pub mod utils; + +#[cfg(test)] +pub mod tests; diff --git a/axiom-query/src/verify_compute/query_hash.rs b/axiom-query/src/verify_compute/query_hash.rs new file mode 100644 index 00000000..5ea85b85 --- /dev/null +++ b/axiom-query/src/verify_compute/query_hash.rs @@ -0,0 +1,400 @@ +use std::iter; + +use axiom_codec::{constants::SOURCE_CHAIN_ID_BYTES, HiLo}; +use axiom_eth::{ + halo2_base::{ + gates::{GateInstructions, RangeChip, RangeInstructions}, + safe_types::{SafeBool, SafeByte, SafeBytes32, SafeTypeChip}, + AssignedValue, Context, + QuantumCell::Constant, + }, + keccak::{types::KeccakVarLenQuery, KeccakChip}, + snark_verifier::loader::halo2::halo2_ecc::bigint::{big_is_zero, OverflowInteger}, + snark_verifier_sdk::{halo2::aggregation::AssignedTranscriptObject, BITS, LIMBS}, + utils::{uint_to_bytes_be, uint_to_bytes_le}, +}; +use itertools::Itertools; + +use crate::Field; + +pub(super) use bn254_specific::*; + +/// `subquery_hashes` has been resized with dummies to `max_num_subqueries` length (known as compile time). This represents a variable length array of hashes with true length given by `num_subqueries`. +/// +/// ## Output +/// - `data_query_hash` (bytes32) +/// - `encoded_source_chain_id` (big-endian bytes) +pub fn get_data_query_hash( + ctx: &mut Context, + keccak: &KeccakChip, + source_chain_id: AssignedValue, // u64 + subquery_hashes: &[SafeBytes32], + num_subqueries: AssignedValue, +) -> (KeccakVarLenQuery, Vec>) { + let range = keccak.range(); + let encoded_source_chain_id = + uint_to_bytes_be(ctx, range, &source_chain_id, SOURCE_CHAIN_ID_BYTES); // u64 + let encoded: Vec<_> = iter::empty() + .chain(encoded_source_chain_id.iter().map(|b| *b.as_ref())) + .chain(subquery_hashes.iter().flat_map(|subquery_hash| subquery_hash.value().to_vec())) + .collect(); + let encoded_len = range.gate.mul_add( + ctx, + Constant(F::from(32)), + num_subqueries, + Constant(F::from(SOURCE_CHAIN_ID_BYTES as u64)), + ); + ( + keccak.keccak_var_len(ctx, encoded, encoded_len, SOURCE_CHAIN_ID_BYTES), + encoded_source_chain_id, + ) +} + +// Everything involving vkey or proof are specific to BN254: +// Make module public for docs. +pub mod bn254_specific { + use axiom_codec::constants::{ENCODED_K_BYTES, USER_PROOF_LEN_BYTES, USER_RESULT_LEN_BYTES}; + use axiom_eth::{ + halo2_base::safe_types::VarLenBytesVec, + halo2curves::bn256::Fr, + rlc::{ + chip::RlcChip, + circuit::builder::RlcContextPair, + concat_array::{concat_var_fixed_array_phase0, concat_var_fixed_array_phase1}, + types::{AssignedVarLenVec, ConcatVarFixedArrayWitness}, + }, + utils::circuit_utils::bytes::encode_const_u8_to_safe_bytes, + }; + + use crate::utils::client_circuit::metadata::AxiomV2CircuitMetadata; + + use super::*; + + type F = Fr; + + /// Length of `encoded_query_schema` and `compute_proof_transcript` are known at compile time. + /// keccak(version . source_chain_id . data_query_hash . encoded_compute_query) + #[allow(clippy::too_many_arguments)] + pub fn get_query_hash_v2( + ctx: &mut Context, + keccak: &KeccakChip, + encoded_source_chain_id: &[SafeByte], // 8 big-endian bytes + data_query_hash: &KeccakVarLenQuery, + encoded_query_schema: &[SafeByte], + compute_accumulator: Option>>, + compute_results: &AssignedVarLenVec, + compute_proof_transcript: Vec, + nonempty_compute_query: SafeBool, + ) -> (KeccakVarLenQuery, ConcatVarFixedArrayWitness) { + let range = keccak.range(); + let gate = range.gate(); + let encoded_version = encode_const_u8_to_safe_bytes(ctx, axiom_codec::VERSION); + let data_query_hash = data_query_hash.output_bytes.value(); + + let (encoded_compute_query, concat_proof_witness) = encode_compute_query_phase0( + ctx, + range, + encoded_query_schema, + compute_accumulator, + compute_results, + compute_proof_transcript, + ); + // if nonempty_compute_query = false, then `encoded_compute_query` should equal `[0u8]` + let nonempty = *nonempty_compute_query.as_ref(); + let true_k = gate.mul(ctx, nonempty, encoded_compute_query.bytes()[0]); + // the minimum length of encoded_compute_query, if computeQuery is empty + let min_encoded_compute_query_len = ENCODED_K_BYTES + USER_RESULT_LEN_BYTES; + assert!(encoded_compute_query.max_len() >= min_encoded_compute_query_len); + let ne_compute_len = gate.sub( + ctx, + *encoded_compute_query.len(), + Constant(F::from(min_encoded_compute_query_len as u64)), + ); + // version . source_chain_id . data_query_hash + // uint8 . uint64 . bytes32 => 1 + 8 + 32 + let min_len = (1 + SOURCE_CHAIN_ID_BYTES + 32 + min_encoded_compute_query_len) as u64; + let encoded_len = gate.mul_add(ctx, ne_compute_len, nonempty, Constant(F::from(min_len))); + let concatenated = iter::empty() + .chain(encoded_version.iter().chain(encoded_source_chain_id).map(|b| *b.as_ref())) + .chain(data_query_hash.iter().copied()) + .chain([true_k]) + .chain(encoded_compute_query.bytes().iter().skip(1).map(|b| *b.as_ref())) + .collect_vec(); + ( + keccak.keccak_var_len(ctx, concatenated, encoded_len, min_len as usize), + concat_proof_witness, + ) + } + + /// Length of `encoded_query_schema` is assumed to be constant at compile time. + /// If `nonempty_compute_query` is false, return bytes32(0). + pub fn get_query_schema_hash( + ctx: &mut Context, + keccak: &KeccakChip, + encoded_query_schema: &[SafeByte], + nonempty_compute_query: SafeBool, + ) -> HiLo> { + let range = keccak.range(); + let encoded = encoded_query_schema.iter().map(|b| *b.as_ref()).collect(); + let query_schema = keccak.keccak_fixed_len(ctx, encoded); + let query_schema = query_schema.hi_lo(); + let query_schema = query_schema.map(|x| range.gate.mul(ctx, x, nonempty_compute_query)); + HiLo::from_hi_lo(query_schema) + } + + /// Length of `encoded_query_schema`, `compute_proof_transcript` are known at compile time. + /// `compute_results` is a variable length array of HiLo field elements (two field elements represent a `bytes32`). + /// + /// We define `compute_proof = solidityPacked(["bytes32[2]", "bytes32[]", "bytes"], [compute_accumulator, compute_results, compute_proof_transcript])` where `compute_results` is of length `result_len` (as `bytes32[]`). + /// Here `compute_accumulator` is either `[bytes32(0), bytes32(0)]` if no accumulator exists, or `[lhs, rhs]` where `lhs, rhs` are the `G1Affine` points, each compressed to `bytes32`. + /// See [axiom_codec::decoder::native::decode_compute_snark] and [axiom_codec::types::native::AxiomV2ComputeSnark]. + /// + /// We encode the concatenation of `solidityPacked(["bytes", "uint32", "bytes"], [encoded_query_schema, proof_len, compute_proof])` + /// where `proof_len` is the [u32] length of `compute_proof` in bytes. + /// + /// ## Special Note + /// - `compute_results` is actually _not_ all public instances of `proof_transcript`. We skip the instances corresponding to subquery results. + /// ## Note on endian-ness + /// - The encoding of `proof_transcript` is governed by the inherit implementations of `to_repr` + /// in `halo2curves`. These happen to be **little-endian** for BN254. + /// - _However_, the encoding of `compute_results` is chosen to match + /// [axiom_eth::snark_verifier::loader::evm::encode_calldata], where `compute_results` encodes field elements + /// as **big-endian**. + /// Since it's harder to change `halo2curves` and `snark_verifier`, + /// we will just have to deal with this inconsistency. + /// - The [u32] `proof_len` is encoded to [USER_PROOF_LEN_BYTES] bytes in **big endian** since + /// this is more compatible with Solidity packing. + pub fn encode_compute_query_phase0( + ctx: &mut Context, + range: &RangeChip, + encoded_query_schema: &[SafeByte], + compute_accumulator: Option>>, + compute_results: &AssignedVarLenVec, + compute_proof_transcript: Vec, + ) -> (VarLenBytesVec, ConcatVarFixedArrayWitness) { + let gate = range.gate(); + // compute results length in bytes + let mut compute_results_len = gate.mul(ctx, compute_results.len, Constant(F::from(32))); + // compute results conversion from HiLo to bytes + let mut compute_results = compute_results + .values + .iter() + .flat_map(|fe| uint_to_bytes_be(ctx, range, fe, 16)) + .collect_vec(); + let mut max_len = compute_results.len(); + // if `compute_accumulator` exists, encode as `bytes32 . bytes32` (`CompressedG1Affine, CompressedG1Affine`) and prepend to `user_outputs` + // else encode as `bytes32(0) . bytes32(0)` + const NUM_ACCUMULATOR_BYTES: usize = 2 * 32; + let encoded_compute_accumulator = if let Some(acc) = compute_accumulator { + // KzgAccumulator from snark-verifier consists of `lhs, rhs` which are two G1Affine points for the lhs and rhs of pairing check + let [lhs_x, lhs_y, rhs_x, rhs_y]: [_; 4] = acc + .chunks_exact(LIMBS) + .map(|limbs| { + let limbs: [_; LIMBS] = limbs.to_vec().try_into().unwrap(); + limbs + }) + .collect_vec() + .try_into() + .unwrap(); + let lhs = compress_bn254_g1_affine_point_to_bytes(ctx, range, lhs_x, lhs_y); + let rhs = compress_bn254_g1_affine_point_to_bytes(ctx, range, rhs_x, rhs_y); + [lhs, rhs].concat() + } else { + let const_zero_byte = encode_const_u8_to_safe_bytes(ctx, 0)[0]; + vec![const_zero_byte; NUM_ACCUMULATOR_BYTES] + }; + compute_results_len = + gate.add(ctx, compute_results_len, Constant(F::from(NUM_ACCUMULATOR_BYTES as u64))); + compute_results = [encoded_compute_accumulator, compute_results].concat(); + max_len += NUM_ACCUMULATOR_BYTES; + let compute_results = VarLenBytesVec::new(compute_results, compute_results_len, max_len); + let encoded_proof = encode_proof_to_bytes(ctx, range, compute_proof_transcript); + let concat_witness = concat_var_fixed_array_phase0( + ctx, + gate, + compute_results.into(), + encoded_proof.into_iter().map(From::from).collect(), + ); + // encoded_compute_accumulator . compute_results (no accumulator) . compute_proof_transcript + let compute_proof = concat_witness + .concat + .values + .iter() + .map(|byte| SafeTypeChip::unsafe_to_byte(*byte)) + .collect_vec(); + let proof_len = concat_witness.concat.len; + let encoded_pf_len = uint_to_bytes_be(ctx, range, &proof_len, USER_PROOF_LEN_BYTES); + // The full concatenation is variable length, but `encoded_query_schema`, `encoded_pf_len` are fixed length, so only variable-ness is in `compute_proof` + let concat_full = [encoded_query_schema.to_vec(), encoded_pf_len, compute_proof].concat(); + let prefix_len = encoded_query_schema.len() + USER_PROOF_LEN_BYTES; + let concat_len = gate.add(ctx, proof_len, Constant(F::from(prefix_len as u64))); + let max_len = concat_full.len(); + let concat_var = VarLenBytesVec::new(concat_full, concat_len, max_len); + /*{ + let len = concat_var.len().value().get_lower_32() as usize; + println!("encodedComputeQuery"); + let enc = get_bytes(&concat_var.bytes()[..len]); + dbg!(ethers_core::types::H256(keccak256(&enc[..]))); + }*/ + (concat_var, concat_witness) + } + + pub fn encode_compute_query_phase1( + rlc_pair: RlcContextPair, + gate: &impl GateInstructions, + rlc: &RlcChip, + concat_witness: ConcatVarFixedArrayWitness, + ) { + concat_var_fixed_array_phase1(rlc_pair, gate, rlc, concat_witness); + } + + /// Returns `solidityPacked(["uint8", "uint16", "uint8", "bytes32[]"], [k, result_len, partial_vkey_len, encoded_partial_vkey])` where `partial_vkey_len` is the length of `encoded_partial_vkey` as `bytes32[]`. + /// + /// We need the `result_len` encoded correctly even if `k = 0`. + /// + /// ## Notes + /// - `partial_vkey_len` is known at compile time. + /// - `is_aggregation` needs to be part of the `encoded_partial_vkey` because it specifies + /// whether the VerifyCompute circuit will RLC the accumulator from the public instances of the compute snark into the new accumulator of the VerifyCompute circuit. + pub fn encode_query_schema( + ctx: &mut Context, + range: &RangeChip, + k: AssignedValue, + result_len: AssignedValue, + circuit_metadata: &AxiomV2CircuitMetadata, + transcript_initial_state: AssignedValue, + preprocessed: &[AssignedValue], + ) -> Vec> { + let encoded_k = uint_to_bytes_be(ctx, range, &k, ENCODED_K_BYTES); // u8 + let encoded_result_len = uint_to_bytes_be(ctx, range, &result_len, USER_RESULT_LEN_BYTES); // u16 + let encoded_onchain_vkey = encode_onchain_vkey( + ctx, + range, + circuit_metadata, + transcript_initial_state, + preprocessed, + ); + assert_eq!(encoded_onchain_vkey.len() % 32, 0); + let onchain_vkey_len = encoded_onchain_vkey.len() / 32; + let encoded_vkey_length = + encode_const_u8_to_safe_bytes(ctx, onchain_vkey_len.try_into().unwrap()).to_vec(); + + [encoded_k, encoded_result_len, encoded_vkey_length, encoded_onchain_vkey].concat() + } + + /// Encode in virtual cells. + /// ## Output + /// - The encoding in bytes: `solidityPacked(["uint256", "bytes32", "bytes32[]"], [is_aggregation, transcript_initial_state, preprocessed])`. This is interpretted by the smart contract as `vkey: bytes32[]`. + /// - Field and curve elements are encoded in **little endian**. + /// - `is_aggregation` (boolean) is encoded as `uint256(is_aggregation)` in **big endian**. + /// - It is wasteful to use 32 bytes, but the smart contract expects `vkey` to be `bytes32[]` + /// ## Assumptions + /// - Length of `preprocessed` is assumed to be constant at compile time. + /// - `transcript_initial_state` is a **field element**. + /// - `preprocessed` is an array of field elements that represent an array of BN254 G1Affine points + /// that form the fixed commitments of the vkey. These are non-native encoded with Fq points + /// as big integers (ProperCrtUint) with [LIMBS] limbs of [BITS] bits each. + /// See [compress_bn254_g1_affine_point_to_bytes] for further details. This depends on the + /// specifics of [`snark_verifier_sdk`](axiom_eth::snark_verifier_sdk) and the exact compression method of BN254, so we keep it + /// as a private function. + pub fn encode_onchain_vkey( + ctx: &mut Context, + range: &RangeChip, + circuit_metadata: &AxiomV2CircuitMetadata, + transcript_initial_state: AssignedValue, + preprocessed: &[AssignedValue], + ) -> Vec> { + let encoded_circuit_metadata = circuit_metadata.encode().unwrap().to_fixed_bytes(); + // Load encoded_circuit_metadata as **constant** bytes32 + let encoded_circuit_metadata_const = encoded_circuit_metadata + .map(|b| SafeTypeChip::unsafe_to_byte(ctx.load_constant(F::from(b as u64)))); + + let encoded_init_state = uint_to_bytes_le(ctx, range, &transcript_initial_state, 32); + assert_eq!(preprocessed.len() % (2 * LIMBS), 0); + let encoded_preprocessed = preprocessed + .chunks_exact(2 * LIMBS) + .flat_map(|chunk| { + let x = chunk[..LIMBS].try_into().unwrap(); + let y = chunk[LIMBS..].try_into().unwrap(); + compress_bn254_g1_affine_point_to_bytes(ctx, range, x, y) + }) + .collect_vec(); + [encoded_circuit_metadata_const.to_vec(), encoded_init_state, encoded_preprocessed].concat() + } + + /// The proof transcript is given as a sequence of either [Fr] field elements or G1Affine points. + /// They are encoded in **little-endian** as described in [encode_partial_vkey]. + /// + /// ## Output + /// - Returns encoded proof bytes in **little endian** + pub fn encode_proof_to_bytes( + ctx: &mut Context, + range: &RangeChip, + proof: Vec, + ) -> Vec> { + proof + .into_iter() + .flat_map(|obj| match obj { + AssignedTranscriptObject::Scalar(scalar) => { + uint_to_bytes_le(ctx, range, &scalar, 32) + } + AssignedTranscriptObject::EcPoint(point) => { + let x = point.x().limbs().try_into().unwrap(); + let y = point.y().limbs().try_into().unwrap(); + compress_bn254_g1_affine_point_to_bytes(ctx, range, x, y) + } + }) + .collect() + } + + /// Takes a BN254 G1Affine point, which is non-native encoded with each coordinate as a + /// ProperCrtUint with [LIMBS] limbs with [BITS] bits each and compresses it to 32 bytes + /// exactly following https://github.com/axiom-crypto/halo2curves/blob/main/src/derive/curve.rs#L138 + /// + /// This relies on both the exact format of the input from [snark_verifier_sdk] and also + /// the exact compression method for BN254 in `halo2curves`, so this is a private function. + /// + /// The compression uses the fact that bn254::Fq has exactly 254 bits, which is 2 bits less than 256. + /// + /// ## Output + /// - Returns bytes in **little endian**. + /// ## Assumptions + /// - `(x, y)` is a point on BN254 G1Affine. + /// - `x, y` are in little endian limbs. + pub fn compress_bn254_g1_affine_point_to_bytes( + ctx: &mut Context, + range: &RangeChip, + x: [AssignedValue; LIMBS], + y: [AssignedValue; LIMBS], + ) -> Vec> { + let gate = range.gate(); + let x_is_zero = big_is_zero::positive(gate, ctx, OverflowInteger::new(x.to_vec(), BITS)); + let y_is_zero = big_is_zero::positive(gate, ctx, OverflowInteger::new(y.to_vec(), BITS)); + let is_identity = gate.and(ctx, x_is_zero, y_is_zero); + + // boolean + let y_last_bit = range.get_last_bit(ctx, y[0], BITS); + + // luckily right now BITS is multiple of 8, so limb conversion to bytes is a little easier + if BITS % 8 != 0 { + panic!("BITS must be a multiple of 8") + } + // even though BITS * LIMBS > 256, we know the whole bigint fits in 256 bits, so we truncate to 32 bytes + let mut x_bytes = x + .iter() + .flat_map(|limb| uint_to_bytes_le(ctx, range, limb, BITS / 8)) + .take(32) + .collect_vec(); + // last_byte is guaranteed to be 254 % 8 = 6 bits + let mut last_byte = *x_bytes.last().unwrap().as_ref(); + // sign = y_last_bit << 6 + // last_byte |= sign + last_byte = gate.mul_add(ctx, y_last_bit, Constant(Fr::from(1 << 6)), last_byte); + // if is_identity, then answer should be [0u8; 32] with last byte = 0b1000_0000 + // if is_identity, then x_bytes should be all 0s, and y_last_bit = 0, so all we have to do is |= 0b1000_0000 = 1 << 7 + last_byte = gate.mul_add(ctx, is_identity, Constant(Fr::from(1 << 7)), last_byte); + *x_bytes.last_mut().unwrap() = SafeTypeChip::unsafe_to_byte(last_byte); + + x_bytes + } +} diff --git a/axiom-query/src/verify_compute/tests/aggregation.rs b/axiom-query/src/verify_compute/tests/aggregation.rs new file mode 100644 index 00000000..8348c02b --- /dev/null +++ b/axiom-query/src/verify_compute/tests/aggregation.rs @@ -0,0 +1,259 @@ +use axiom_codec::{ + constants::USER_MAX_OUTPUTS, + decoder::native::decode_compute_snark, + encoder::native::{get_query_hash_v2, get_query_schema_hash}, + types::native::{AxiomV2ComputeQuery, AxiomV2ComputeSnark, AxiomV2DataQuery}, + utils::native::{decode_hilo_to_h256, encode_h256_to_hilo}, + HiLo, +}; +use axiom_eth::{ + halo2_base::{ + gates::circuit::CircuitBuilderStage, + halo2_proofs::{dev::MockProver, poly::commitment::ParamsProver}, + utils::fs::gen_srs, + }, + halo2curves::bn256::{Fr, G1Affine}, + snark_verifier::pcs::{ + kzg::{KzgAccumulator, LimbsEncoding}, + AccumulatorEncoding, + }, + snark_verifier_sdk::{ + gen_pk, + halo2::{ + aggregation::{AggregationCircuit, VerifierUniversality}, + gen_snark_shplonk, + }, + NativeLoader, Snark, BITS, LIMBS, SHPLONK, + }, + utils::{ + build_utils::pinning::CircuitPinningInstructions, + component::ComponentCircuit, + snark_verifier::{AggregationCircuitParams, NUM_FE_ACCUMULATOR}, + }, +}; + +use ethers_core::{types::H256, utils::keccak256}; +use itertools::Itertools; +#[cfg(test)] +use test_log::test; + +use crate::{ + components::results::types::{CircuitOutputResultsRoot, LogicOutputResultsRoot}, + utils::client_circuit::metadata::AxiomV2CircuitMetadata, + verify_compute::{ + tests::utils::dummy_compute_snark, + types::{ + CircuitInputVerifyCompute, CoreParamsVerifyCompute, LogicalPublicInstanceVerifyCompute, + }, + utils::{ + get_onchain_vk_from_protocol, verify_snark, write_onchain_vkey, UserCircuitParams, + DEFAULT_CLIENT_METADATA, DEFAULT_USER_PARAMS, + }, + }, +}; + +use super::{get_test_result, prepare_mock_circuit, prepare_prover_circuit, test_compute_circuit}; + +fn test_compute_app_snark( + k: u32, + user_params: UserCircuitParams, + subquery_results: LogicOutputResultsRoot, + result_len: u16, +) -> Snark { + let app_params = gen_srs(k); + let compute_app = test_compute_circuit(k, user_params, subquery_results, result_len as usize); + let pk = gen_pk(&app_params, &compute_app, None); + gen_snark_shplonk(&app_params, &pk, compute_app, None::<&str>) +} + +// this one doesn't have instances in special format +// Note: when `snark` has many public instances, the aggregation circuit needs to do many Poseidon hashes. There are ways to mitigate this if you only reformat the aggregation circuit's public instances to be the correct format: there is no strict requirement on the public instances of the snark to be aggregated +pub fn test_aggregation_circuit( + agg_circuit_params: AggregationCircuitParams, + snark: Snark, +) -> AggregationCircuit { + let params = gen_srs(agg_circuit_params.degree); + let mut circuit = AggregationCircuit::new::( + CircuitBuilderStage::Mock, + agg_circuit_params, + ¶ms, + [snark], + VerifierUniversality::None, + ); + circuit.expose_previous_instances(false); + circuit +} + +fn get_metadata(agg_circuit_params: AggregationCircuitParams) -> AxiomV2CircuitMetadata { + AxiomV2CircuitMetadata { + version: 0, + num_instance: vec![DEFAULT_CLIENT_METADATA.num_instance[0] + NUM_FE_ACCUMULATOR as u32], + num_challenge: vec![0], + is_aggregation: true, + num_advice_per_phase: vec![agg_circuit_params.num_advice as u16], + num_lookup_advice_per_phase: vec![agg_circuit_params.num_lookup_advice as u8], + num_rlc_columns: 0, + num_fixed: agg_circuit_params.num_fixed as u8, + max_outputs: USER_MAX_OUTPUTS as u16, + } +} + +fn get_test_input( + app_snark: Snark, + source_chain_id: u64, + subquery_results: LogicOutputResultsRoot, + result_len: u16, + agg_k: u32, +) -> (CoreParamsVerifyCompute, CircuitInputVerifyCompute) { + let agg_circuit_params = AggregationCircuitParams { + degree: agg_k, + num_advice: 20, + num_lookup_advice: 2, + num_fixed: 1, + lookup_bits: agg_k as usize - 1, + }; + let agg_params = gen_srs(agg_k); + let subquery_results = CircuitOutputResultsRoot::::try_from(subquery_results).unwrap(); + let client_metadata = get_metadata(agg_circuit_params); + + let agg_compute_snark = { + let agg_circuit = test_aggregation_circuit(agg_circuit_params, app_snark); + // let stats = agg_circuit.builder.statistics(); + // dbg!(stats.gate.total_advice_per_phase); + // dbg!(stats.gate.total_fixed); + // dbg!(stats.total_lookup_advice_per_phase); + let pk = gen_pk(&agg_params, &agg_circuit, None); + gen_snark_shplonk(&agg_params, &pk, agg_circuit, None::<&str>) + }; + dbg!(&client_metadata); + let core_params = CoreParamsVerifyCompute::new( + subquery_results.results.len(), + agg_params.get_g()[0], + client_metadata, + agg_compute_snark.protocol.preprocessed.len(), + ); + println!("agg_compute_snark.protocol.preprocessed.len(): {}", core_params.preprocessed_len()); + + ( + core_params, + CircuitInputVerifyCompute::new( + source_chain_id, + subquery_results, + true, + result_len, + agg_compute_snark, + ), + ) +} + +// this test involves creating aggregation snark with real prover so it is heavy, but we should still include it in the CI +#[test] +fn test_verify_compute_agg_mock() -> anyhow::Result<()> { + let (_input_results, data_results) = get_test_result::(); + let num_subqueries = data_results.num_subqueries; + let result_len = num_subqueries as u16; + + let compute_app_snark = + test_compute_app_snark(14, DEFAULT_USER_PARAMS, data_results.clone(), result_len); + let source_chain_id = 1; + let agg_k = 20; + let (core_params, input) = + get_test_input(compute_app_snark, source_chain_id, data_results.clone(), result_len, agg_k); + + // additional preparation for instance checks later + let subqueries = + data_results.results[..num_subqueries].iter().map(|r| r.subquery.clone()).collect(); + let agg_compute_snark = input.compute_snark(); + let data_query = AxiomV2DataQuery { source_chain_id, subqueries }; + let compute_vkey = get_onchain_vk_from_protocol( + &agg_compute_snark.protocol, + core_params.client_metadata().clone(), + ); + let agg_instances = &agg_compute_snark.instances[0]; + let KzgAccumulator { lhs, rhs } = + as AccumulatorEncoding>::from_repr( + &agg_instances[..NUM_FE_ACCUMULATOR].iter().collect_vec(), + ) + .unwrap(); + let compute_results = agg_instances[NUM_FE_ACCUMULATOR..] + .chunks(2) + .take(result_len as usize) + .map(|c| decode_hilo_to_h256(HiLo::from_hi_lo([c[0], c[1]]))) + .collect_vec(); + let compute_snark = AxiomV2ComputeSnark { + kzg_accumulator: Some((lhs, rhs)), + compute_results, + proof_transcript: agg_compute_snark.proof.clone(), + }; + let compute_proof = compute_snark.encode()?.into(); + let compute_query = AxiomV2ComputeQuery { + k: agg_k as u8, + result_len, + vkey: write_onchain_vkey(&compute_vkey).unwrap(), + compute_proof, + }; + + let circuit_k = 19u32; + let keccak_f_capacity = 200; + let circuit = prepare_mock_circuit(core_params, circuit_k as usize, keccak_f_capacity, input); + let instances = circuit.get_public_instances(); + + // check instances: + // check query hash and query schema calculation + let logic_pis = instances.other.clone(); + let LogicalPublicInstanceVerifyCompute { + query_hash, query_schema, compute_results_hash, .. + } = logic_pis.try_into()?; + let native_query_schema = + get_query_schema_hash(compute_query.k, compute_query.result_len, &compute_query.vkey)?; + assert_eq!(&query_schema, &encode_h256_to_hilo(&native_query_schema)); + let native_query_hash = get_query_hash_v2(source_chain_id, &data_query, &compute_query)?; + // dbg!(native_query_hash); + assert_eq!(&query_hash, &encode_h256_to_hilo(&native_query_hash)); + let compute_snark = + decode_compute_snark(&mut &compute_query.compute_proof[..], result_len, true)?; + let encode_results = compute_snark.compute_results.iter().map(|r| r.0.to_vec()).concat(); + let native_results_hash = H256(keccak256(encode_results)); + assert_eq!(&compute_results_hash, &encode_h256_to_hilo(&native_results_hash)); + + // actually run mockprover + MockProver::run(circuit_k, &circuit, vec![instances.into()]).unwrap().assert_satisfied(); + Ok(()) +} + +#[test] +fn test_verify_compute_agg_prover() -> anyhow::Result<()> { + let (_input_results, data_results) = get_test_result::(); + let num_subqueries = data_results.num_subqueries; + let result_len = num_subqueries as u16; + + // === create proving key for the VerifyCompute circuit === + // we test that only the aggregation circuit's shape matters, whereas the original snark to be aggregated can have different shapes + let mut user_params = DEFAULT_USER_PARAMS; + user_params.num_advice_cols += 1; + let app_k = 12; + let agg_k = 20; + let compute_app_snark = dummy_compute_snark(&gen_srs(app_k), user_params, "./data"); + let (core_params, input) = get_test_input(compute_app_snark, 0, data_results.clone(), 0, agg_k); + + let circuit_k = 19u32; + let circuit = prepare_mock_circuit(core_params, circuit_k as usize, 200, input); + + let kzg_params = gen_srs(circuit_k); + let pk = gen_pk(&kzg_params, &circuit, None); + let rlc_pinning = circuit.pinning(); + // ===== end proving key generation ==== + + let compute_app_snark = + test_compute_app_snark(14, DEFAULT_USER_PARAMS, data_results.clone(), result_len); + let (core_params, input) = + get_test_input(compute_app_snark, 1, data_results, result_len, agg_k); + + let circuit = prepare_prover_circuit(core_params, rlc_pinning, 200, input); + + let snark = gen_snark_shplonk(&kzg_params, &pk, circuit, None::<&str>); + let dk = (kzg_params.get_g()[0], kzg_params.g2(), kzg_params.s_g2()); + verify_snark(&dk.into(), &snark)?; + + Ok(()) +} diff --git a/axiom-query/src/verify_compute/tests/mod.rs b/axiom-query/src/verify_compute/tests/mod.rs new file mode 100644 index 00000000..d9b5cbd3 --- /dev/null +++ b/axiom-query/src/verify_compute/tests/mod.rs @@ -0,0 +1,455 @@ +#![allow(clippy::field_reassign_with_default)] +use std::{collections::HashMap, fs::File, panic::catch_unwind}; + +use axiom_codec::{ + constants::*, + decoder::native::decode_compute_snark, + encoder::native::{get_query_hash_v2, get_query_schema_hash}, + types::{ + field_elements::{FieldSubqueryResult, SUBQUERY_KEY_LEN}, + native::{AxiomV2ComputeQuery, AxiomV2DataQuery}, + }, + utils::native::encode_h256_to_hilo, +}; +use axiom_eth::{ + halo2_base::{ + gates::circuit::builder::BaseCircuitBuilder, + halo2_proofs::dev::MockProver, + utils::fs::{gen_srs, read_params}, + }, + halo2_proofs::poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG}, + halo2curves::bn256::{Bn256, Fr}, + keccak::{ + promise::generate_keccak_shards_from_calls, + types::{ComponentTypeKeccak, OutputKeccakShard}, + }, + utils::{ + build_utils::pinning::RlcCircuitPinning, + component::{ + promise_loader::single::PromiseLoaderParams, ComponentCircuit, + ComponentPromiseResultsInMerkle, ComponentType, + }, + }, + zkevm_hashes::keccak::vanilla::keccak_packed_multi::get_num_keccak_f, +}; + +use ethers_core::{ + types::{Bytes, H256}, + utils::keccak256, +}; +use hex::FromHex; +use itertools::Itertools; +#[cfg(test)] +use test_log::test; + +use crate::{ + components::{ + dummy_rlc_circuit_params, + results::{ + self, + tests::test_capacity, + types::{ + CircuitInputResultsRootShard, CircuitOutputResultsRoot, LogicOutputResultsRoot, + }, + }, + }, + verify_compute::{ + tests::utils::{default_compute_snark, InputVerifyCompute}, + types::{CircuitInputVerifyCompute, LogicalPublicInstanceVerifyCompute}, + utils::{reconstruct_snark_from_compute_query, DEFAULT_CLIENT_METADATA}, + utils::{verify_snark, UserCircuitParams, DEFAULT_USER_PARAMS}, + }, + Field, +}; + +use super::{circuit::ComponentCircuitVerifyCompute, types::CoreParamsVerifyCompute}; + +use utils::get_base_input; + +/// needs to be large enough to fit results and data instances +pub const DUMMY_USER_K: u32 = 14; + +/// Test when computeSnark is aggregation circuit +pub mod aggregation; +/// test prove module +pub mod prove; +/// Testing specific utils +pub mod utils; + +pub fn get_test_result() -> (CircuitInputResultsRootShard, LogicOutputResultsRoot) { + let mut capacity = test_capacity(); + if capacity.total > USER_MAX_OUTPUTS { + capacity.total = USER_MAX_OUTPUTS; + } + let (input, output, _) = results::tests::get_test_input(capacity).unwrap(); + (input, output) +} + +pub fn test_compute_circuit( + k: u32, + user_params: UserCircuitParams, + subquery_results: LogicOutputResultsRoot, + result_len: usize, +) -> BaseCircuitBuilder { + let circuit_params = user_params.base_circuit_params(k as usize); + let mut builder = BaseCircuitBuilder::new(false).use_params(circuit_params); + // let range = builder.range_chip(); + + let ctx = builder.main(0); + + let mut compute_results = vec![]; + let mut data_instances = vec![]; + for result in subquery_results.results.into_iter().take(subquery_results.num_subqueries) { + let result = FieldSubqueryResult::::try_from(result).unwrap(); + let data_instance = ctx.assign_witnesses(result.to_fixed_array()); + compute_results.extend(data_instance[SUBQUERY_KEY_LEN..][..2].to_vec()); + data_instances.extend(data_instance); + } + assert!(compute_results.len() >= 2 * result_len); + compute_results.truncate(2 * result_len); + compute_results.resize_with(2 * USER_MAX_OUTPUTS, || ctx.load_witness(F::ZERO)); + + let mut assigned_instance = compute_results; + assigned_instance.extend(data_instances); + assigned_instance + .resize_with(DEFAULT_USER_PARAMS.num_instances(), || ctx.load_witness(F::ZERO)); + builder.assigned_instances[0] = assigned_instance; + + builder +} + +fn prepare_mock_circuit( + core_params: CoreParamsVerifyCompute, + k: usize, + keccak_f_capacity: usize, + input: CircuitInputVerifyCompute, +) -> ComponentCircuitVerifyCompute { + let mut rlc_params = dummy_rlc_circuit_params(k); + rlc_params.base.lookup_bits = Some(k - 1); + let loader_params = PromiseLoaderParams::new_for_one_shard(keccak_f_capacity); + let mut circuit = ComponentCircuitVerifyCompute::new(core_params, loader_params, rlc_params); + circuit.feed_input(Box::new(input)).unwrap(); + circuit.calculate_params(); + let promise_results = HashMap::from_iter([( + ComponentTypeKeccak::::get_type_id(), + ComponentPromiseResultsInMerkle::from_single_shard( + generate_keccak_shards_from_calls(&circuit, keccak_f_capacity) + .unwrap() + .into_logical_results(), + ), + )]); + circuit.fulfill_promise_results(&promise_results).unwrap(); + circuit +} + +fn prepare_prover_circuit( + core_params: CoreParamsVerifyCompute, + rlc_pinning: RlcCircuitPinning, + keccak_f_capacity: usize, + input: CircuitInputVerifyCompute, +) -> ComponentCircuitVerifyCompute { + let loader_params = PromiseLoaderParams::new_for_one_shard(keccak_f_capacity); + let circuit = ComponentCircuitVerifyCompute::prover(core_params, loader_params, rlc_pinning); + circuit.feed_input(Box::new(input)).unwrap(); + let promise_results = HashMap::from_iter([( + ComponentTypeKeccak::::get_type_id(), + ComponentPromiseResultsInMerkle::from_single_shard( + generate_keccak_shards_from_calls(&circuit, keccak_f_capacity) + .unwrap() + .into_logical_results(), + ), + )]); + circuit.fulfill_promise_results(&promise_results).unwrap(); + circuit +} + +#[test] +fn test_verify_no_compute_mock() { + let (_, subquery_results) = get_test_result::(); + let result_len = 2; // test different from numSubqueries + let num_subqueries = subquery_results.num_subqueries; + + let source_chain_id = 1; + let empty_cq = AxiomV2ComputeQuery { + k: 0, + result_len: result_len as u16, + vkey: vec![], + compute_proof: Bytes::from([]), + }; + let logic_input = + InputVerifyCompute { source_chain_id, subquery_results, compute_query: empty_cq.clone() }; + + let circuit_k = 19u32; + + let (core_params, input) = + CircuitInputVerifyCompute::reconstruct(logic_input.clone(), &gen_srs(DUMMY_USER_K)) + .unwrap(); + let circuit = prepare_mock_circuit(core_params, circuit_k as usize, 200, input); + let instances = circuit.get_public_instances(); + + let subqueries = logic_input.subquery_results.results[..num_subqueries] + .iter() + .map(|r| r.subquery.clone()) + .collect(); + let data_query = AxiomV2DataQuery { source_chain_id, subqueries }; + + // check query hash and query schema calculation + let logic_pis = instances.other.clone(); + let LogicalPublicInstanceVerifyCompute { + query_hash, query_schema, compute_results_hash, .. + } = logic_pis.try_into().unwrap(); + assert_eq!(&query_schema, &encode_h256_to_hilo(&H256::zero())); + let native_query_hash = get_query_hash_v2(source_chain_id, &data_query, &empty_cq).unwrap(); + assert_eq!(&query_hash, &encode_h256_to_hilo(&native_query_hash)); + let encode_results = logic_input.subquery_results.results[..result_len] + .iter() + .map(|r| r.value.to_vec()) + .concat(); + let native_results_hash = H256(keccak256(encode_results)); + assert_eq!(&compute_results_hash, &encode_h256_to_hilo(&native_results_hash)); + MockProver::run(circuit_k, &circuit, vec![instances.into()]).unwrap().assert_satisfied(); +} + +#[test] +fn test_verify_compute_mock() -> anyhow::Result<()> { + let (_input_results, data_results) = get_test_result::(); + let result_len = data_results.num_subqueries; + + let app_k = 14; + let app_params = gen_srs(app_k); + let logic_input = get_base_input( + &app_params, + USER_MAX_OUTPUTS, + test_compute_circuit(app_k, DEFAULT_USER_PARAMS, data_results.clone(), result_len), + data_results, + 1, + result_len, + )?; + // serde_json::to_writer(File::create("data/test/input_results_root.json")?, &input_results)?; + serde_json::to_writer(File::create("data/test/input_verify_compute.json")?, &logic_input)?; + + let circuit_k = 19u32; + + let (core_params, input) = + CircuitInputVerifyCompute::reconstruct(logic_input.clone(), &app_params)?; + let circuit = prepare_mock_circuit(core_params, circuit_k as usize, 200, input); + let instances = circuit.get_public_instances(); + + let source_chain_id = logic_input.source_chain_id; + let num_subqueries = logic_input.subquery_results.num_subqueries; + let subqueries = logic_input.subquery_results.results[..num_subqueries] + .iter() + .map(|r| r.subquery.clone()) + .collect(); + let data_query = AxiomV2DataQuery { source_chain_id, subqueries }; + let compute_query = logic_input.compute_query; + /*dbg!(data_query.keccak()); + { + let vkey = compute_query.vkey.chunks(32).map(H256::from_slice).collect_vec(); + dbg!(vkey); + } + */ + // dbg!(compute_query.compute_proof.len()); + // dbg!(&Bytes::from(compute_query.encode().unwrap())); + // dbg!(compute_query.keccak()); + + // check query hash and query schema calculation + let logic_pis = instances.other.clone(); + let LogicalPublicInstanceVerifyCompute { + query_hash, query_schema, compute_results_hash, .. + } = logic_pis.try_into()?; + let native_query_schema = + get_query_schema_hash(compute_query.k, result_len as u16, &compute_query.vkey)?; + assert_eq!(&query_schema, &encode_h256_to_hilo(&native_query_schema)); + let native_query_hash = get_query_hash_v2(source_chain_id, &data_query, &compute_query)?; + // dbg!(native_query_hash); + assert_eq!(&query_hash, &encode_h256_to_hilo(&native_query_hash)); + let compute_snark = + decode_compute_snark(&mut &compute_query.compute_proof[..], result_len as u16, false)?; + let encode_results = compute_snark.compute_results.iter().map(|r| r.0.to_vec()).concat(); + let native_results_hash = H256(keccak256(encode_results)); + assert_eq!(&compute_results_hash, &encode_h256_to_hilo(&native_results_hash)); + MockProver::run(circuit_k, &circuit, vec![instances.into()]).unwrap().assert_satisfied(); + Ok(()) +} + +#[test] +fn test_verify_compute_prover_full() -> anyhow::Result<()> { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + let (_, data_results) = get_test_result::(); + let result_len = data_results.num_subqueries; + let max_num_subqueries = data_results.results.len(); + + let app_k = 14; + let app_params = gen_srs(app_k); + let logic_input = get_base_input( + &app_params, + USER_MAX_OUTPUTS, + test_compute_circuit(app_k, DEFAULT_USER_PARAMS, data_results.clone(), result_len), + data_results, + 1, + result_len, + )?; + let mut f = File::create(format!("{cargo_manifest_dir}/data/test/input_verify_compute.json",))?; + serde_json::to_writer(&mut f, &logic_input)?; + let res = catch_unwind(|| { + prove::verify_compute_prover( + logic_input.clone(), + max_num_subqueries, + "verify_compute", + None, + 200, + ) + .unwrap() + }); + std::fs::remove_file(format!("{cargo_manifest_dir}/data/test/verify_compute.pk")).ok(); + std::fs::remove_file(format!("{cargo_manifest_dir}/data/test/verify_compute.snark")).ok(); + let (snark, _, _) = res.unwrap(); + let dk = (app_params.get_g()[0], app_params.g2(), app_params.s_g2()); + verify_snark(&dk.into(), &snark)?; + + Ok(()) +} + +#[test] +#[ignore = "integration test"] +fn test_verify_compute_prepare_for_agg() -> anyhow::Result<()> { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + let data_results: LogicOutputResultsRoot = serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/data/test/output_result_root_for_agg.json", + ))?)?; + let result_len = 2; + + let app_k = 14; + let app_params = read_params(app_k); + let logic_input = get_base_input( + &app_params, + USER_MAX_OUTPUTS, + test_compute_circuit(app_k, DEFAULT_USER_PARAMS, data_results.clone(), result_len), + data_results, + 1, + result_len, + )?; + serde_json::to_writer( + File::create(format!("{cargo_manifest_dir}/data/test/input_verify_compute_for_agg.json"))?, + &logic_input, + )?; + + let circuit_k = 19u32; + let (core_params, input) = + CircuitInputVerifyCompute::reconstruct(logic_input.clone(), &app_params)?; + let keccak_cap = 200; + let circuit = prepare_mock_circuit(core_params, circuit_k as usize, keccak_cap, input); + let keccak_shard = generate_keccak_shards_from_calls(&circuit, keccak_cap)?; + serde_json::to_writer( + File::create(format!( + "{cargo_manifest_dir}/data/test/verify_compute_promise_results_keccak_for_agg.json" + ))?, + &keccak_shard, + )?; + Ok(()) +} + +#[test] +#[ignore = "integration test"] +fn test_merge_keccak_shards_for_agg() -> anyhow::Result<()> { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + let header_keccak: OutputKeccakShard = serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/data/test/header_promise_results_keccak_for_agg.json" + ))?)?; + let results_root_keccak: OutputKeccakShard = serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/data/test/results_root_promise_results_keccak_for_agg.json" + ))?)?; + let verify_compute_keccak: OutputKeccakShard = serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/data/test/verify_compute_promise_results_keccak_for_agg.json" + ))?)?; + let responses = std::iter::empty() + .chain(header_keccak.responses) + .chain(results_root_keccak.responses) + .chain(verify_compute_keccak.responses) + .collect::>(); + let mut used_cap = 0; + for r in &responses { + used_cap += get_num_keccak_f(r.0.len()); + } + assert!(used_cap <= 200); + let merged = OutputKeccakShard { responses, capacity: 200 }; + serde_json::to_writer( + File::create(format!( + "{cargo_manifest_dir}/data/test/promise_results_keccak_for_agg.json" + ))?, + &merged, + )?; + Ok(()) +} + +#[test] +#[ignore = "integration test"] +fn test_verify_compute_prover_for_agg() -> anyhow::Result<()> { + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + let logic_input: InputVerifyCompute = serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/data/test/input_verify_compute_for_agg.json" + ))?)?; + let promise_keccak: OutputKeccakShard = serde_json::from_reader(File::open(format!( + "{cargo_manifest_dir}/data/test/promise_results_keccak_for_agg.json" + ))?)?; + let keccak_cap = promise_keccak.capacity; + prove::verify_compute_prover( + logic_input.clone(), + logic_input.subquery_results.results.len(), + "verify_compute_for_agg", + Some(promise_keccak), + keccak_cap, + )?; + Ok(()) +} + +#[test] +fn test_circuit_metadata_encode() { + assert_eq!( + DEFAULT_CLIENT_METADATA.encode().unwrap().as_bytes(), + &Vec::from_hex("0001000009000100000004010000010080000000000000000000000000000000").unwrap() + ); +} + +impl CircuitInputVerifyCompute { + /// **Assumptions:** + /// - The generator `params_for_dummy.get_g()[0]` should match that of the trusted setup used to generate `input.compute_query` if there is a compute query. + /// - If there is no compute query (so compute_query.k == 0), then a dummy compute snark is generated using `params_for_dummy` with [DEFAULT_CLIENT_METADATA]. + pub fn reconstruct( + input: InputVerifyCompute, + params_for_dummy: &ParamsKZG, + ) -> anyhow::Result<(CoreParamsVerifyCompute, Self)> { + let InputVerifyCompute { source_chain_id, subquery_results, compute_query } = input; + + let compute_query_result_len = compute_query.result_len; + let nonempty_compute_query = compute_query.k != 0; + let (compute_snark, client_metadata) = if compute_query.k == 0 { + (default_compute_snark(params_for_dummy), DEFAULT_CLIENT_METADATA.clone()) + } else { + reconstruct_snark_from_compute_query(subquery_results.clone(), compute_query)? + }; + let subquery_results = CircuitOutputResultsRoot::try_from(subquery_results)?; + let circuit_params = CoreParamsVerifyCompute::new( + subquery_results.results.len(), + params_for_dummy.get_g()[0], + client_metadata, + compute_snark.protocol.preprocessed.len(), + ); + println!( + "compute_snark.protocol.preprocessed.len(): {}", + circuit_params.preprocessed_len() + ); + + Ok(( + circuit_params, + Self::new( + source_chain_id, + subquery_results, + nonempty_compute_query, + compute_query_result_len, + compute_snark, + ), + )) + } +} diff --git a/axiom-query/src/verify_compute/tests/prove.rs b/axiom-query/src/verify_compute/tests/prove.rs new file mode 100644 index 00000000..ebc323ad --- /dev/null +++ b/axiom-query/src/verify_compute/tests/prove.rs @@ -0,0 +1,112 @@ +use std::{collections::HashMap, fs}; + +use axiom_codec::constants::USER_MAX_OUTPUTS; +use axiom_eth::{ + halo2_base::utils::fs::gen_srs, + keccak::{ + promise::generate_keccak_shards_from_calls, + types::{ComponentTypeKeccak, OutputKeccakShard}, + }, + snark_verifier_sdk::{halo2::gen_snark_shplonk, Snark}, + utils::{ + build_utils::pinning::{PinnableCircuit, RlcCircuitPinning}, + component::{ + promise_loader::single::PromiseLoaderParams, ComponentCircuit, + ComponentPromiseResultsInMerkle, ComponentType, + }, + }, +}; + +use crate::{ + components::results::types::LogicOutputResultsRoot, + verify_compute::{ + circuit::ComponentCircuitVerifyCompute, + tests::{prepare_mock_circuit, utils::get_base_input, DUMMY_USER_K}, + utils::default_compute_circuit, + }, +}; + +use super::{super::types::CircuitInputVerifyCompute, InputVerifyCompute}; + +pub fn verify_compute_prover( + mut logic_input: InputVerifyCompute, + max_num_subqueries: usize, + name: &str, + promise_keccak: Option, + keccak_capacity: usize, +) -> anyhow::Result<(Snark, RlcCircuitPinning, OutputKeccakShard)> { + type I = CircuitInputVerifyCompute; + + let default_params = gen_srs(DUMMY_USER_K); + let output_results = LogicOutputResultsRoot { + results: vec![Default::default(); max_num_subqueries], + subquery_hashes: vec![Default::default(); max_num_subqueries], + num_subqueries: 0, + }; + let input = get_base_input( + &default_params, + USER_MAX_OUTPUTS, + default_compute_circuit(14), + output_results, + 0, + 0, + )?; + let (core_params, input) = I::reconstruct(input, &default_params)?; + + let circuit_k = 19u32; + + let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR"); + fs::create_dir_all(format!("{cargo_manifest_dir}/configs/test")).unwrap(); + fs::create_dir_all(format!("{cargo_manifest_dir}/data/test")).unwrap(); + let pinning_path = format!("{cargo_manifest_dir}/configs/test/{name}.json"); + let pk_path = format!("{cargo_manifest_dir}/data/test/{name}.pk"); + let params = gen_srs(circuit_k); + + let circuit = + prepare_mock_circuit(core_params.clone(), circuit_k as usize, keccak_capacity, input); + let (pk, pinning) = circuit.create_pk(¶ms, pk_path, pinning_path)?; + + let loader_params = PromiseLoaderParams::new_for_one_shard(keccak_capacity); + #[cfg(all(feature = "keygen", not(debug_assertions)))] + { + use crate::keygen::shard::CircuitIntentVerifyCompute; + use axiom_eth::halo2_base::utils::halo2::KeygenCircuitIntent; + use axiom_eth::halo2_proofs::{plonk::keygen_vk, SerdeFormat}; + // check keygen + let intent = CircuitIntentVerifyCompute { + core_params, + loader_params: loader_params.clone(), + k: circuit_k, + lookup_bits: circuit_k as usize - 1, + }; + let circuit = intent.build_keygen_circuit(); + let vk = keygen_vk(¶ms, &circuit)?; + if pk.get_vk().to_bytes(SerdeFormat::RawBytes) != vk.to_bytes(SerdeFormat::RawBytes) { + panic!("vk mismatch"); + } + } + + let first = logic_input.subquery_results.results[0].clone(); + logic_input.subquery_results.results.resize(max_num_subqueries, first); + let first = logic_input.subquery_results.subquery_hashes[0]; + logic_input.subquery_results.subquery_hashes.resize(max_num_subqueries, first); + let (core_params, input) = I::reconstruct(logic_input, &default_params)?; + let circuit = + ComponentCircuitVerifyCompute::prover(core_params, loader_params, pinning.clone()); + circuit.feed_input(Box::new(input)).unwrap(); + if let Some(promise_keccak) = &promise_keccak { + assert_eq!(promise_keccak.capacity, keccak_capacity); + } + let promise_keccak = promise_keccak + .unwrap_or_else(|| generate_keccak_shards_from_calls(&circuit, keccak_capacity).unwrap()); + let promise_results = HashMap::from_iter([( + ComponentTypeKeccak::::get_type_id(), + ComponentPromiseResultsInMerkle::from_single_shard( + promise_keccak.clone().into_logical_results(), + ), + )]); + circuit.fulfill_promise_results(&promise_results).unwrap(); + + let snark_path = format!("{cargo_manifest_dir}/data/test/{name}.snark"); + Ok((gen_snark_shplonk(¶ms, &pk, circuit, Some(snark_path)), pinning, promise_keccak)) +} diff --git a/axiom-query/src/verify_compute/tests/utils.rs b/axiom-query/src/verify_compute/tests/utils.rs new file mode 100644 index 00000000..1e05e513 --- /dev/null +++ b/axiom-query/src/verify_compute/tests/utils.rs @@ -0,0 +1,138 @@ +use std::path::{Path, PathBuf}; + +use axiom_codec::{ + constants::USER_INSTANCE_COLS, + types::native::{AxiomV2ComputeQuery, AxiomV2ComputeSnark}, + utils::native::decode_hilo_to_h256, + HiLo, +}; +use axiom_eth::{ + halo2_base::{ + gates::circuit::builder::BaseCircuitBuilder, + halo2_proofs::{ + halo2curves::bn256::{Bn256, Fr}, + poly::{commitment::Params, kzg::commitment::ParamsKZG}, + }, + }, + halo2_proofs::{plonk::Circuit, poly::commitment::ParamsProver}, + rlc::circuit::RlcCircuitParams, + snark_verifier::pcs::kzg::KzgDecidingKey, + snark_verifier_sdk::{ + gen_pk, + halo2::{gen_snark_shplonk, read_snark}, + Snark, + }, +}; +use ethers_core::types::Bytes; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::results::types::LogicOutputResultsRoot, + verify_compute::utils::{ + dummy_compute_circuit, get_metadata_from_protocol, get_onchain_vk_from_vk, + write_onchain_vkey, UserCircuitParams, DEFAULT_USER_PARAMS, + }, +}; + +// For now we assume LogicOutputResultsRoot knows the true number of ordered subqueries +// This might change if we have multiple pages of LogicOutputResultsRoot + +#[derive(Clone, Debug, Serialize, Deserialize, Hash)] +#[serde(rename_all = "camelCase")] +pub struct InputVerifyCompute { + pub source_chain_id: u64, + pub subquery_results: LogicOutputResultsRoot, + pub compute_query: AxiomV2ComputeQuery, +} + +// We do not directly convert to CircuitInputVerifyCompute because we need +// to test the reconstruction from AxiomV2ComputeQuery back into CircuitInputVerifyCompute + +/// Prepares input for `client_circuit` that is created with [BaseCircuitBuilder]. +/// `client_circuit` is [BaseCircuitBuilder] populated with witnesses and fixed/copy constraints. +pub fn get_base_input( + params: &ParamsKZG, + max_outputs: usize, + client_circuit: BaseCircuitBuilder, + subquery_results: LogicOutputResultsRoot, + source_chain_id: u64, + result_len: usize, +) -> anyhow::Result { + assert!(!client_circuit.witness_gen_only()); + let client_circuit_params = client_circuit.params(); + let pk = gen_pk(params, &client_circuit, None); + let compute_snark = gen_snark_shplonk(params, &pk, client_circuit, None::<&str>); + + let client_metadata = get_metadata_from_protocol( + &compute_snark.protocol, + RlcCircuitParams { base: client_circuit_params, num_rlc_columns: 0 }, + max_outputs, + )?; + + let onchain_vk = get_onchain_vk_from_vk(pk.get_vk(), client_metadata); + let vkey = write_onchain_vkey(&onchain_vk)?; + + let instances = &compute_snark.instances; + assert_eq!(instances.len(), USER_INSTANCE_COLS); + let instances = &instances[0]; + let compute_results = instances + .iter() + .chunks(2) + .into_iter() + .take(result_len) + .map(|hilo| { + let hilo = hilo.collect_vec(); + assert_eq!(hilo.len(), 2); + decode_hilo_to_h256(HiLo::from_hi_lo([*hilo[0], *hilo[1]])) + }) + .collect(); + let compute_snark = AxiomV2ComputeSnark { + kzg_accumulator: None, + compute_results, + proof_transcript: compute_snark.proof, + }; + let compute_proof = Bytes::from(compute_snark.encode().unwrap()); + let compute_query = AxiomV2ComputeQuery { + k: params.k() as u8, + result_len: result_len as u16, + vkey, + compute_proof, + }; + Ok(InputVerifyCompute { source_chain_id, subquery_results, compute_query }) +} + +/// Create a dummy snark that **will verify** successfully. +pub fn dummy_compute_snark( + kzg_params: &ParamsKZG, + user_params: UserCircuitParams, + cache_dir: impl AsRef, +) -> Snark { + // tag for caching the dummy + let tag = { + // UserCircuitParams and KzgDecidingKey are enough to tag the dummy snark; we don't need `k` + let mut hasher = blake3::Hasher::new(); + hasher.update(&serde_json::to_vec(&user_params).unwrap()); + // hash num instance in case we change the format + hasher.update(&user_params.num_instances().to_be_bytes()); + let dk: KzgDecidingKey = + (kzg_params.get_g()[0], kzg_params.g2(), kzg_params.s_g2()).into(); + hasher.update(&serde_json::to_vec(&dk).unwrap()); + let id = hasher.finalize(); + cache_dir.as_ref().join(format!("{id}.snark")) + }; + if let Ok(snark) = read_snark(&tag) { + return snark; + } + let circuit = dummy_compute_circuit(user_params, kzg_params.k()); + let pk = gen_pk(kzg_params, &circuit, None); + gen_snark_shplonk(kzg_params, &pk, circuit, Some(tag)) +} + +pub fn default_compute_snark(params: &ParamsKZG) -> Snark { + let mut cache_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + cache_dir.push("data"); + cache_dir.push("default_compute_snark"); + std::fs::create_dir_all(&cache_dir).unwrap(); + dummy_compute_snark(params, DEFAULT_USER_PARAMS, &cache_dir) +} diff --git a/axiom-query/src/verify_compute/types.rs b/axiom-query/src/verify_compute/types.rs new file mode 100644 index 00000000..2bf6bf4f --- /dev/null +++ b/axiom-query/src/verify_compute/types.rs @@ -0,0 +1,256 @@ +use std::iter; + +use anyhow::bail; +use axiom_codec::{types::field_elements::FlattenedSubqueryResult, HiLo}; +use axiom_eth::{ + halo2_base::AssignedValue, + halo2curves::bn256::{Fr, G1Affine}, + impl_flatten_conversion, + snark_verifier_sdk::{halo2::gen_dummy_snark_from_protocol, Snark, SHPLONK}, + utils::{ + build_utils::dummy::DummyFrom, + component::{ + circuit::{CoreBuilderOutputParams, CoreBuilderParams}, + types::LogicalEmpty, + ComponentType, ComponentTypeId, LogicalResult, + }, + snark_verifier::NUM_FE_ACCUMULATOR, + }, +}; +use getset::{CopyGetters, Getters}; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::results::{table::SubqueryResultsTable, types::CircuitOutputResultsRoot}, + utils::client_circuit::{metadata::AxiomV2CircuitMetadata, vkey::OnchainVerifyingKey}, +}; + +/// Identifier for the component type of Verify Compute Circuit +pub struct ComponentTypeVerifyCompute; + +/// Configuration parameters for Verify Compute Circuit that determine +/// the circuit, **independent** of the variable inputs. +/// +/// Even when `nonempty_compute_query == false` (no compute query), +/// the `circuit_params.client_metadata` needs to be set to a valid +/// client circuit configuration. +#[derive(Clone, Debug, Default, Serialize, Deserialize, Getters, CopyGetters)] +pub struct CoreParamsVerifyCompute { + /// Capacity: max number of subquery results + #[getset(get_copy = "pub")] + subquery_results_capacity: usize, + /// Succinct verifying key should be the generator `g()[0]` of the KZG trusted setup used to generate the vkey. + #[getset(get_copy = "pub")] + svk: G1Affine, // Svk type doesn't derive Serialize + /// Client circuit on-chain vkey + #[getset(get = "pub")] + client_metadata: AxiomV2CircuitMetadata, + /// Length of `preprocessed` in `PlonkProtocol` + #[getset(get_copy = "pub")] + preprocessed_len: usize, +} + +impl CoreParamsVerifyCompute { + pub fn new( + subquery_results_capacity: usize, + svk: G1Affine, + client_metadata: AxiomV2CircuitMetadata, + preprocessed_len: usize, + ) -> Self { + Self { subquery_results_capacity, svk, client_metadata, preprocessed_len } + } +} +impl CoreBuilderParams for CoreParamsVerifyCompute { + /// No component output + fn get_output_params(&self) -> CoreBuilderOutputParams { + CoreBuilderOutputParams::new(vec![]) + } +} + +/// Logic inputs to Verify Compute Circuit +/// Deserialization is specialized to [Fr] for now. +/// +/// ## Compute Snark +/// The Verify Compute Circuit should only depend on the number of columns and custom gates / lookup arguments +/// of `compute_snark`, not on the fixed commitments or domain size `2^k`. +#[derive(Clone, Debug, Serialize, Deserialize, Getters)] +pub struct CircuitInputVerifyCompute { + /// Chain ID of the chain the EVM data is from + pub source_chain_id: u64, + /// Used for lookups, length may be padded with dummy subqueries + pub subquery_results: CircuitOutputResultsRoot, + /// If `nonempty_compute_query == false`, then `compute_snark` is a dummy snark. + pub nonempty_compute_query: bool, + /// The number of user results + pub result_len: u16, + /// The client snark. + /// + /// When there is no compute query (`nonempty_compute_query == false`), + /// this must be a dummy snark matching `circuit_params.client_metadata` that will still verify. + #[getset(get = "pub")] + pub(super) compute_snark: Snark, +} + +impl CircuitInputVerifyCompute { + /// If `nonempty_compute_query == false`, then `compute_snark` must be a dummy snark that will verify. + pub fn new( + source_chain_id: u64, + subquery_results: CircuitOutputResultsRoot, + nonempty_compute_query: bool, + result_len: u16, + compute_snark: Snark, + ) -> Self { + Self { + source_chain_id, + subquery_results, + nonempty_compute_query, + result_len, + compute_snark, + } + } +} + +impl DummyFrom for CircuitInputVerifyCompute { + fn dummy_from(core_params: CoreParamsVerifyCompute) -> Self { + let subquery_results_capacity = core_params.subquery_results_capacity(); + let onchain_vk = OnchainVerifyingKey { + circuit_metadata: core_params.client_metadata().clone(), + transcript_initial_state: Default::default(), + preprocessed: vec![G1Affine::default(); core_params.preprocessed_len()], + }; + // k is loaded as witness so it shouldn't matter + let k = 7; + let protocol = onchain_vk.into_plonk_protocol(k).unwrap(); + let compute_snark = gen_dummy_snark_from_protocol::(protocol); + let results = SubqueryResultsTable { + rows: vec![FlattenedSubqueryResult::default(); subquery_results_capacity], + }; + let subquery_hashes = vec![HiLo::default(); subquery_results_capacity]; + + let subquery_results = + CircuitOutputResultsRoot { results, subquery_hashes, num_subqueries: 0 }; + Self::new(0, subquery_results, true, 0, compute_snark) + } +} + +pub(super) const NUM_LOGICAL_INSTANCE_WITHOUT_ACC: usize = 1 + 2 + 2 + 2 + 1 + 1; +pub(super) const NUM_LOGICAL_INSTANCE: usize = + NUM_FE_ACCUMULATOR + NUM_LOGICAL_INSTANCE_WITHOUT_ACC; +const NUM_BITS_PER_FE: [usize; NUM_LOGICAL_INSTANCE] = get_num_bits_per_fe(); +// 9999 means that the public instance takes a whole witness +// Accumulators *must* take whole witnesses. +const fn get_num_bits_per_fe() -> [usize; NUM_LOGICAL_INSTANCE] { + let mut bits_per = [9999; NUM_LOGICAL_INSTANCE]; + bits_per[NUM_FE_ACCUMULATOR] = 64; + bits_per[NUM_FE_ACCUMULATOR + 1] = 128; + bits_per[NUM_FE_ACCUMULATOR + 2] = 128; + bits_per[NUM_FE_ACCUMULATOR + 3] = 128; + bits_per[NUM_FE_ACCUMULATOR + 4] = 128; + bits_per[NUM_FE_ACCUMULATOR + 5] = 128; + bits_per[NUM_FE_ACCUMULATOR + 6] = 128; + bits_per +} +/// The public instances of the circuit, **excluding** the component owned instances +/// for output commit and promise commit. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LogicalPublicInstanceVerifyCompute { + pub accumulator: Vec, + pub source_chain_id: T, + pub compute_results_hash: HiLo, + pub query_hash: HiLo, + pub query_schema: HiLo, + pub results_root_poseidon: T, + pub promise_subquery_hashes: T, +} +/// [LogicalPublicInstanceVerifyCompute] with `accumulator` removed. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LogicalPisVerifyComputeWithoutAccumulator { + pub source_chain_id: T, + pub compute_results_hash: HiLo, + pub query_hash: HiLo, + pub query_schema: HiLo, + pub results_root_poseidon: T, + pub promise_subquery_hashes: T, +} + +type F = Fr; +/// Verify Compute has no virtual table as output +impl ComponentType for ComponentTypeVerifyCompute { + type InputValue = LogicalEmpty; + type InputWitness = LogicalEmpty>; + type OutputValue = LogicalEmpty; + type OutputWitness = LogicalEmpty>; + type LogicalInput = LogicalEmpty; + + fn get_type_id() -> ComponentTypeId { + "axiom-query:ComponentTypeVerifyCompute".to_string() + } + fn logical_result_to_virtual_rows_impl( + _ins: &LogicalResult, + ) -> Vec<(Self::InputValue, Self::OutputValue)> { + unreachable!() + } + fn logical_input_to_virtual_rows_impl(_li: &Self::LogicalInput) -> Vec { + unreachable!() + } +} + +// ============== LogicalPublicInstanceVerifyCompute ============== +impl LogicalPublicInstanceVerifyCompute { + pub fn flatten(self) -> Vec { + iter::empty() + .chain(self.accumulator) + .chain(Some(self.source_chain_id)) + .chain(self.compute_results_hash.hi_lo()) + .chain(self.query_hash.hi_lo()) + .chain(self.query_schema.hi_lo()) + .chain([self.results_root_poseidon, self.promise_subquery_hashes]) + .collect() + } +} + +impl TryFrom> for LogicalPublicInstanceVerifyCompute { + type Error = anyhow::Error; + + fn try_from(mut value: Vec) -> anyhow::Result { + if value.len() != NUM_LOGICAL_INSTANCE { + bail!("wrong number of logical public instances") + }; + let accumulator = value.drain(..NUM_FE_ACCUMULATOR).collect(); + let drained: LogicalPisVerifyComputeWithoutAccumulator = value.try_into().unwrap(); + Ok(Self { + accumulator, + source_chain_id: drained.source_chain_id, + compute_results_hash: drained.compute_results_hash, + query_hash: drained.query_hash, + query_schema: drained.query_schema, + results_root_poseidon: drained.results_root_poseidon, + promise_subquery_hashes: drained.promise_subquery_hashes, + }) + } +} +impl TryFrom> for LogicalPisVerifyComputeWithoutAccumulator { + type Error = anyhow::Error; + + fn try_from(value: Vec) -> anyhow::Result { + if value.len() != NUM_LOGICAL_INSTANCE_WITHOUT_ACC { + bail!("wrong number of logical public instances without accumulator") + }; + let source_chain_id = value[0]; + let compute_results_hash = HiLo::from_hi_lo([value[1], value[2]]); + let query_hash = HiLo::from_hi_lo([value[3], value[4]]); + let query_schema = HiLo::from_hi_lo([value[5], value[6]]); + let results_root_poseidon = value[7]; + let promise_subquery_hashes = value[8]; + Ok(Self { + source_chain_id, + compute_results_hash, + query_hash, + query_schema, + results_root_poseidon, + promise_subquery_hashes, + }) + } +} + +impl_flatten_conversion!(LogicalPublicInstanceVerifyCompute, NUM_BITS_PER_FE); diff --git a/axiom-query/src/verify_compute/utils.rs b/axiom-query/src/verify_compute/utils.rs new file mode 100644 index 00000000..f1fcc67c --- /dev/null +++ b/axiom-query/src/verify_compute/utils.rs @@ -0,0 +1,333 @@ +use std::{hash::Hash, io::Write}; + +use anyhow::{anyhow, bail}; +use axiom_codec::{ + constants::{ + USER_ADVICE_COLS, USER_FIXED_COLS, USER_INSTANCE_COLS, USER_LOOKUP_ADVICE_COLS, + USER_MAX_OUTPUTS, USER_MAX_SUBQUERIES, USER_RESULT_FIELD_ELEMENTS, + }, + decoder::native::decode_compute_snark, + types::{ + field_elements::SUBQUERY_RESULT_LEN, + native::{AxiomV2ComputeQuery, AxiomV2ComputeSnark}, + }, + utils::writer::{write_curve_compressed, write_field_le}, + HiLo, +}; +use axiom_eth::{ + halo2_base::{ + gates::circuit::{builder::BaseCircuitBuilder, BaseCircuitParams}, + halo2_proofs::{ + halo2curves::{ + bn256::{Bn256, Fr}, + ff::PrimeField, + serde::SerdeObject, + CurveAffine, + }, + plonk::VerifyingKey, + }, + utils::ScalarField, + }, + halo2curves::{bn256::G1Affine, ff::Field as _}, + rlc::circuit::RlcCircuitParams, + snark_verifier::{ + pcs::kzg::KzgDecidingKey, + system::halo2::transcript_initial_state, + util::arithmetic::fe_to_limbs, + verifier::{ + plonk::{PlonkProof, PlonkProtocol}, + SnarkVerifier, + }, + }, + snark_verifier_sdk::{ + halo2::{aggregation::AggregationCircuit, PoseidonTranscript, POSEIDON_SPEC}, + CircuitExt, NativeLoader, PlonkVerifier, Snark, BITS, LIMBS, SHPLONK, + }, +}; +use ethers_core::types::H256; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use crate::{ + components::results::types::{CircuitOutputResultsRoot, LogicOutputResultsRoot}, + utils::client_circuit::{metadata::AxiomV2CircuitMetadata, vkey::OnchainVerifyingKey}, + Field, +}; + +/// Need to provide RlcCircuitParams for additional context, otherwise you have +/// to parse the RlcCircuitParams data from the custom gate information in `protocol` +pub fn get_metadata_from_protocol( + protocol: &PlonkProtocol, + rlc_params: RlcCircuitParams, + max_outputs: usize, +) -> anyhow::Result { + let num_advice_per_phase = + rlc_params.base.num_advice_per_phase.iter().map(|x| *x as u16).collect(); + let num_lookup_advice_per_phase = + rlc_params.base.num_lookup_advice_per_phase.iter().map(|x| *x as u8).collect(); + let num_rlc_columns = rlc_params.num_rlc_columns as u16; + let num_fixed = rlc_params.base.num_fixed as u8; + let mut metadata = AxiomV2CircuitMetadata { + version: 0, + num_advice_per_phase, + num_lookup_advice_per_phase, + num_rlc_columns, + num_fixed, + max_outputs: max_outputs as u16, + ..Default::default() + }; + + if protocol.num_instance.len() != 1 { + bail!("Only one instance column supported right now"); + } + metadata.num_instance = protocol.num_instance.iter().map(|&x| x as u32).collect(); + let mut num_challenge_incl_system = protocol.num_challenge.clone(); + // This `num_challenge` counts only the challenges used inside the circuit - it excludes challenges that are part of the halo2 system. + // The full challenges, which is what `plonk_protocol.num_challenge` stores, is: + // ```ignore + // [ + // my_phase0_challenges, + // ... + // [..my_phasen_challenges, theta], + // [beta, gamma], + // [alpha], + // ] + // ``` + if num_challenge_incl_system.pop() != Some(1) { + bail!("last challenge must be [alpha]"); + } + if num_challenge_incl_system.pop() != Some(2) { + bail!("second last challenge must be [beta, gamma]"); + } + let last_challenge = num_challenge_incl_system.last_mut(); + if last_challenge.is_none() { + bail!("num_challenge must have at least 3 challenges"); + } + let last_challenge = last_challenge.unwrap(); + if *last_challenge == 0 { + bail!("third last challenge must include theta"); + } + *last_challenge -= 1; + let num_challenge: Vec = num_challenge_incl_system.iter().map(|x| *x as u8).collect(); + if num_challenge != vec![0] && num_challenge != vec![1, 0] { + log::debug!("num_challenge: {:?}", num_challenge); + bail!("Only phase0 BaseCircuitBuilder or phase0+1 RlcCircuitBuilder supported right now"); + } + metadata.num_challenge = num_challenge; + + metadata.is_aggregation = if protocol.accumulator_indices.is_empty() { + false + } else { + if protocol.accumulator_indices.len() != 1 + || protocol.accumulator_indices[0] != AggregationCircuit::accumulator_indices().unwrap() + { + bail!("invalid accumulator indices"); + } + true + }; + + Ok(metadata) +} + +/// Reference implementation. Actually done by axiom-sdk-client +pub fn write_onchain_vkey(vkey: &OnchainVerifyingKey) -> anyhow::Result> +where + C: CurveAffine + SerdeObject, + C::Scalar: Field + SerdeObject, +{ + let metadata = vkey.circuit_metadata.encode()?; + + let tmp = C::Repr::default(); + let compressed_curve_bytes = tmp.as_ref().len(); + let tmp = ::Repr::default(); + let field_bytes = tmp.as_ref().len(); + let mut writer = + Vec::with_capacity(field_bytes + vkey.preprocessed.len() * compressed_curve_bytes); + + writer.write_all(&metadata.to_fixed_bytes())?; + write_field_le(&mut writer, vkey.transcript_initial_state)?; + for &point in &vkey.preprocessed { + write_curve_compressed(&mut writer, point)?; + } + Ok(writer.chunks_exact(32).map(H256::from_slice).collect()) +} + +/// Requires additional context about the Axiom circuit, in the form of the `circuit_metadata`. +pub fn get_onchain_vk_from_vk( + vk: &VerifyingKey, + circuit_metadata: AxiomV2CircuitMetadata, +) -> OnchainVerifyingKey { + let preprocessed = vk + .fixed_commitments() + .iter() + .chain(vk.permutation().commitments().iter()) + .cloned() + .map(Into::into) + .collect(); + let transcript_initial_state = transcript_initial_state(vk); + OnchainVerifyingKey { circuit_metadata, preprocessed, transcript_initial_state } +} + +pub fn get_onchain_vk_from_protocol( + protocol: &PlonkProtocol, + circuit_metadata: AxiomV2CircuitMetadata, +) -> OnchainVerifyingKey { + let preprocessed = protocol.preprocessed.clone(); + let transcript_initial_state = protocol.transcript_initial_state.unwrap(); + OnchainVerifyingKey { circuit_metadata, preprocessed, transcript_initial_state } +} + +pub fn reconstruct_snark_from_compute_query( + subquery_results: LogicOutputResultsRoot, + compute_query: AxiomV2ComputeQuery, +) -> anyhow::Result<(Snark, AxiomV2CircuitMetadata)> { + let subquery_results = CircuitOutputResultsRoot::::try_from(subquery_results)?; + let vkey = compute_query.vkey.into_iter().flat_map(|u| u.0).collect_vec(); + let mut reader = &vkey[..]; + let onchain_vk = OnchainVerifyingKey::::read(&mut reader)?; + let client_metadata = onchain_vk.circuit_metadata.clone(); + let k = compute_query.k as usize; + let protocol = onchain_vk.into_plonk_protocol(k)?; + + // === Begin reconstruct proof transcript: === + if client_metadata.num_instance.len() != 1 { + bail!("Only one instance column supported right now"); + } + let num_instance = client_metadata.num_instance[0] as usize; + + // We assume that the true number of user requested subqueries is `num_subqueries` + let num_subqueries = subquery_results.num_subqueries; + let result_len = compute_query.result_len as usize; + let max_outputs = client_metadata.max_outputs as usize; + if result_len > max_outputs { + bail!("user_output_len exceeds user max outputs"); + } + // compute proof only has the user outputs, not the user subquery requests + let mut reader = &compute_query.compute_proof[..]; + let AxiomV2ComputeSnark { compute_results, proof_transcript, kzg_accumulator } = + decode_compute_snark( + &mut reader, + compute_query.result_len, + client_metadata.is_aggregation, + )?; + let mut instance = Vec::with_capacity(num_instance); + if let Some((lhs, rhs)) = kzg_accumulator { + instance.extend( + [lhs.x, lhs.y, rhs.x, rhs.y].into_iter().flat_map(fe_to_limbs::<_, Fr, LIMBS, BITS>), + ); + } + let mut compute_results = + compute_results.into_iter().flat_map(|out| HiLo::from(out).hi_lo()).collect_vec(); + // safety check that user outputs are hardcoded to HiLo for now + assert_eq!(compute_results.len(), result_len * USER_RESULT_FIELD_ELEMENTS); + compute_results + .resize((client_metadata.max_outputs as usize) * USER_RESULT_FIELD_ELEMENTS, Fr::ZERO); + instance.extend(compute_results); + + // fill in public instances corresponding to subqueries + for result in &subquery_results.results.rows[..num_subqueries] { + instance.extend(result.to_fixed_array()); + } + if instance.len() > num_instance { + bail!("Num subqueries exceeds num_instance limit"); + } + instance.resize(num_instance, Fr::ZERO); + let snark = Snark::new(protocol, vec![instance], proof_transcript); + Ok((snark, client_metadata)) +} + +/// This verifies snark with poseidon transcript and **importantly** also checks the +/// kzg accumulator from the public instances, if `snark` is aggregation circuit +pub fn verify_snark(dk: &KzgDecidingKey, snark: &Snark) -> anyhow::Result<()> { + let mut transcript = + PoseidonTranscript::::from_spec(snark.proof(), POSEIDON_SPEC.clone()); + let proof: PlonkProof<_, _, SHPLONK> = + PlonkVerifier::read_proof(dk, &snark.protocol, &snark.instances, &mut transcript) + .map_err(|_| anyhow!("Failed to read PlonkProof"))?; + PlonkVerifier::verify(dk, &snark.protocol, &snark.instances, &proof) + .map_err(|_| anyhow!("PlonkVerifier failed"))?; + Ok(()) +} + +lazy_static::lazy_static! { + pub static ref DEFAULT_CLIENT_METADATA: AxiomV2CircuitMetadata = AxiomV2CircuitMetadata { + version: 0, + num_advice_per_phase: vec![USER_ADVICE_COLS as u16], + num_lookup_advice_per_phase: vec![USER_LOOKUP_ADVICE_COLS as u8], + num_rlc_columns: 0, + num_fixed: USER_FIXED_COLS as u8, + num_instance: vec![ + (USER_MAX_OUTPUTS * USER_RESULT_FIELD_ELEMENTS + USER_MAX_SUBQUERIES * SUBQUERY_RESULT_LEN) + as u32, + ], + num_challenge: vec![0], + max_outputs: USER_MAX_OUTPUTS as u16, + is_aggregation: false, + }; +} + +/// Fully describes the configuration of a user provided circuit written using [`halo2_base`](axiom_eth::halo2_base) or [`snark_verifier_sdk`](axiom_eth::snark_verifier_sdk). +#[derive(Clone, Copy, Debug, Hash, Serialize, Deserialize)] +pub struct UserCircuitParams { + pub num_advice_cols: usize, + pub num_lookup_advice_cols: usize, + pub num_fixed_cols: usize, + /// Max number of bytes32 the user can output. + /// This will be `2 * USER_MAX_OUTPUTS` field elements as public instances. + pub max_outputs: usize, + /// Maximum number of subqueries a user can request. + pub max_subqueries: usize, +} + +impl UserCircuitParams { + /// Total public instances of the user circuit. + /// We start with + /// - user outputs (bytes32 in HiLo, 2 field elements each) and then + /// - add the "flattened" user subqueries with results + /// + /// Currently we assume user circuit has a single instance column. + pub fn num_instances(&self) -> usize { + self.max_outputs * USER_RESULT_FIELD_ELEMENTS + self.max_subqueries * SUBQUERY_RESULT_LEN + } + + pub fn base_circuit_params(&self, k: usize) -> BaseCircuitParams { + BaseCircuitParams { + k, + num_advice_per_phase: vec![self.num_advice_cols], + num_lookup_advice_per_phase: vec![self.num_lookup_advice_cols], + num_fixed: self.num_fixed_cols, + lookup_bits: Some(k - 1), + num_instance_columns: USER_INSTANCE_COLS, + } + } +} + +pub const DEFAULT_USER_PARAMS: UserCircuitParams = UserCircuitParams { + num_advice_cols: USER_ADVICE_COLS, + num_lookup_advice_cols: USER_LOOKUP_ADVICE_COLS, + num_fixed_cols: USER_FIXED_COLS, + max_outputs: USER_MAX_OUTPUTS, + max_subqueries: USER_MAX_SUBQUERIES, +}; + +/// Creates a default snark for a [axiom_eth::halo2_base] circuit with a fixed configuration, +/// using the given trusted setup. The log2 domain size `params.k()` can be variable. +/// Used to get fixed constraint and gate information. +pub fn dummy_compute_circuit( + user_params: UserCircuitParams, + k: u32, +) -> BaseCircuitBuilder { + let circuit_params = user_params.base_circuit_params(k as usize); + let mut builder = BaseCircuitBuilder::new(false).use_params(circuit_params); + + let ctx = builder.main(0); + let dummy_instances = ctx.assign_witnesses(vec![F::ZERO; user_params.num_instances()]); + assert_eq!(builder.assigned_instances.len(), USER_INSTANCE_COLS); + builder.assigned_instances[0] = dummy_instances; + + builder +} + +pub fn default_compute_circuit(k: u32) -> BaseCircuitBuilder { + dummy_compute_circuit(DEFAULT_USER_PARAMS, k) +} diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 00000000..ee2d639b --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly-2023-08-12 \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..f5a13f37 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 100 +use_small_heuristics = "Max" \ No newline at end of file diff --git a/trusted_setup_s3.sh b/trusted_setup_s3.sh new file mode 100644 index 00000000..5d23075a --- /dev/null +++ b/trusted_setup_s3.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +for k in {5..23} # 25} +do + wget "https://axiom-crypto.s3.amazonaws.com/challenge_0085/kzg_bn254_${k}.srs" +done + +mv *.srs params/