From d8f1bbf45624a38024746b781b0b1ad4996920f7 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Sat, 10 Jan 2026 15:21:19 +0000 Subject: [PATCH 01/38] build(evm): update generated verifier pragma to Solidity 0.8.30 Align generated verifier source with Solidity 0.8.30 in the EVM codegen template. - Change the emitted contract pragma in SolidityAssemblyCode from 0.8.19 to 0.8.30. --- snark-verifier/src/loader/evm/code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snark-verifier/src/loader/evm/code.rs b/snark-verifier/src/loader/evm/code.rs index eee4fb3e..c1cc8f8e 100644 --- a/snark-verifier/src/loader/evm/code.rs +++ b/snark-verifier/src/loader/evm/code.rs @@ -21,7 +21,7 @@ impl SolidityAssemblyCode { " // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.30; contract Halo2Verifier {{ fallback(bytes calldata) external returns (bytes memory) {{ From bcd1f54c1947551f5de471b87feb6da970d8dcae Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Mon, 19 Jan 2026 07:58:32 +0000 Subject: [PATCH 02/38] test(evm): raise local limits for large-verifier gas estimation Adjust local EVM execution helpers so oversized verifier contracts can be deployed and profiled in simulation. - Add serde_json dependency used by EVM utility flows. - Silence non-critical solc stderr output during bytecode compilation. - Relax revm code-size and gas-limit constraints in deploy_and_call for high-bytecode verifier benchmarks. --- snark-verifier/Cargo.toml | 2 +- snark-verifier/src/loader/evm/util.rs | 1 + snark-verifier/src/loader/evm/util/executor.rs | 11 +++++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/snark-verifier/Cargo.toml b/snark-verifier/Cargo.toml index ee322718..4303cb6a 100644 --- a/snark-verifier/Cargo.toml +++ b/snark-verifier/Cargo.toml @@ -19,7 +19,7 @@ hex = "0.4" rand = "0.8" serde = { version = "1.0", features = ["derive"] } pairing = { version = "0.23" } - +serde_json = "1.0" # Use halo2-base as non-optional dependency because it re-exports halo2_proofs, halo2curves, and poseidon, using different repos based on feature flag "halo2-axiom" or "halo2-pse" # halo2-base = { version = "=0.4.1", default-features = false } halo2-base = { git = "https://github.com/axiom-crypto/halo2-lib.git", tag = "v0.5.0-git", default-features = false } diff --git a/snark-verifier/src/loader/evm/util.rs b/snark-verifier/src/loader/evm/util.rs index 8bf92dd1..5f14753e 100644 --- a/snark-verifier/src/loader/evm/util.rs +++ b/snark-verifier/src/loader/evm/util.rs @@ -108,6 +108,7 @@ pub fn compile_solidity(code: &str) -> Vec { let mut cmd = Command::new("solc") .stdin(Stdio::piped()) .stdout(Stdio::piped()) + .stderr(Stdio::null()) // <-- silence solc warnings .arg("--bin") .arg("-") .spawn() diff --git a/snark-verifier/src/loader/evm/util/executor.rs b/snark-verifier/src/loader/evm/util/executor.rs index eaa08633..03d18c77 100644 --- a/snark-verifier/src/loader/evm/util/executor.rs +++ b/snark-verifier/src/loader/evm/util/executor.rs @@ -13,13 +13,20 @@ use revm::{ pub fn deploy_and_call(deployment_code: Vec, calldata: Vec) -> Result { let mut evm = Context::mainnet().with_db(InMemoryDB::default()).build_mainnet(); + // allow oversized verifier in local sim + evm.cfg.limit_contract_code_size = Some(usize::MAX); + evm.cfg.limit_contract_initcode_size = Some(usize::MAX); // also set this + + // avoid tx gas-limit > block gas-limit validation + evm.block.gas_limit = u64::MAX; + + // and DON'T use u64::MAX in the tx itself let tx = TxEnv { - gas_limit: u64::MAX, + gas_limit: 100_000_000, // pick something huge but sane kind: TxKind::Create, data: deployment_code.into(), ..Default::default() }; - let result = evm.transact_commit(tx).unwrap(); let contract = match result { ExecutionResult::Success { From 6ceb111961904867368433df87e2be8a88d02238 Mon Sep 17 00:00:00 2001 From: Julien Coolen Date: Tue, 17 Feb 2026 16:41:52 +0000 Subject: [PATCH 03/38] chore(vendor): vendor halo2-lib snapshot and route halo2-base/ecc locally Vendor the halo2-lib workspace under vendor/halo2-lib and route both axiom-crypto and jtcoolen halo2-lib sources to local halo2-base/halo2-ecc paths. This commit keeps the raw upstream snapshot state before applying the halo2-base BLS patches. --- Cargo.lock | 1241 +++++--- Cargo.toml | 14 + vendor/halo2-lib/.github/workflows/ci.yml | 69 + vendor/halo2-lib/.gitignore | 26 + vendor/halo2-lib/CHANGELOG.md | 4 + vendor/halo2-lib/Cargo.lock | 2791 +++++++++++++++++ vendor/halo2-lib/Cargo.toml | 45 + vendor/halo2-lib/LICENSE | 21 + vendor/halo2-lib/README.md | 310 ++ vendor/halo2-lib/SECURITY.md | 5 + .../audits/v0.4.1-spearbit-report.pdf | Bin 0 -> 100237 bytes vendor/halo2-lib/halo2-base/Cargo.toml | 85 + vendor/halo2-lib/halo2-base/README.md | 127 + .../halo2-base/benches/inner_product.rs | 78 + vendor/halo2-lib/halo2-base/benches/mul.rs | 69 + .../halo2-base/examples/inner_product.rs | 39 + .../gates/tests/prop_test.txt | 11 + .../halo2-base/src/gates/circuit/builder.rs | 386 +++ .../halo2-base/src/gates/circuit/mod.rs | 229 ++ .../halo2-base/src/gates/flex_gate/mod.rs | 1266 ++++++++ .../src/gates/flex_gate/threads/mod.rs | 18 + .../gates/flex_gate/threads/multi_phase.rs | 162 + .../gates/flex_gate/threads/parallelize.rs | 29 + .../gates/flex_gate/threads/single_phase.rs | 322 ++ vendor/halo2-lib/halo2-base/src/gates/mod.rs | 13 + .../halo2-base/src/gates/range/mod.rs | 651 ++++ .../halo2-base/src/gates/tests/flex_gate.rs | 225 ++ .../halo2-base/src/gates/tests/general.rs | 125 + .../src/gates/tests/idx_to_indicator.rs | 118 + .../halo2-base/src/gates/tests/mod.rs | 9 + .../halo2-base/src/gates/tests/neg_prop.rs | 266 ++ .../halo2-base/src/gates/tests/pos_prop.rs | 380 +++ .../halo2-base/src/gates/tests/range.rs | 108 + .../halo2-base/src/gates/tests/utils.rs | 184 ++ vendor/halo2-lib/halo2-base/src/lib.rs | 486 +++ .../halo2-base/src/poseidon/hasher/mds.rs | 172 + .../halo2-base/src/poseidon/hasher/mod.rs | 361 +++ .../halo2-base/src/poseidon/hasher/spec.rs | 176 ++ .../halo2-base/src/poseidon/hasher/state.rs | 251 ++ .../poseidon/hasher/tests/compatibility.rs | 117 + .../src/poseidon/hasher/tests/hasher.rs | 357 +++ .../src/poseidon/hasher/tests/mod.rs | 39 + .../src/poseidon/hasher/tests/state.rs | 129 + .../halo2-lib/halo2-base/src/poseidon/mod.rs | 114 + .../halo2-base/src/safe_types/bytes.rs | 227 ++ .../halo2-base/src/safe_types/mod.rs | 324 ++ .../halo2-base/src/safe_types/primitives.rs | 59 + .../halo2-base/src/safe_types/tests/bytes.rs | 235 ++ .../halo2-base/src/safe_types/tests/mod.rs | 2 + .../src/safe_types/tests/safe_type.rs | 257 ++ .../halo2-lib/halo2-base/src/utils/halo2.rs | 183 ++ vendor/halo2-lib/halo2-base/src/utils/mod.rs | 623 ++++ .../halo2-lib/halo2-base/src/utils/testing.rs | 264 ++ .../src/virtual_region/copy_constraints.rs | 181 ++ .../halo2-base/src/virtual_region/lookups.rs | 156 + .../src/virtual_region/lookups/basic.rs | 209 ++ .../halo2-base/src/virtual_region/manager.rs | 16 + .../halo2-base/src/virtual_region/mod.rs | 15 + .../virtual_region/tests/lookups/memory.rs | 254 ++ .../src/virtual_region/tests/lookups/mod.rs | 1 + .../src/virtual_region/tests/mod.rs | 1 + vendor/halo2-lib/halo2-ecc/Cargo.toml | 63 + vendor/halo2-lib/halo2-ecc/benches/README.md | 45 + .../halo2-ecc/benches/fixed_base_msm.rs | 127 + vendor/halo2-lib/halo2-ecc/benches/fp_mul.rs | 111 + vendor/halo2-lib/halo2-ecc/benches/msm.rs | 150 + .../configs/bn254/bench_ec_add.config | 5 + .../configs/bn254/bench_fixed_msm.config | 12 + .../configs/bn254/bench_fixed_msm.t.config | 2 + .../halo2-ecc/configs/bn254/bench_msm.config | 13 + .../configs/bn254/bench_msm.t.config | 2 + .../configs/bn254/bench_pairing.config | 9 + .../configs/bn254/bench_pairing.t.config | 1 + .../configs/bn254/ec_add_circuit.config | 1 + .../configs/bn254/fixed_msm_circuit.config | 1 + .../configs/bn254/msm_circuit.config | 1 + .../configs/bn254/pairing_circuit.config | 1 + .../configs/secp256k1/bench_ecdsa.config | 9 + .../configs/secp256k1/bench_ecdsa.t.config | 1 + .../configs/secp256k1/ecdsa_circuit.config | 1 + .../halo2-ecc/src/bigint/add_no_carry.rs | 37 + .../halo2-ecc/src/bigint/big_is_equal.rs | 29 + .../halo2-ecc/src/bigint/big_is_zero.rs | 53 + .../halo2-ecc/src/bigint/big_less_than.rs | 17 + .../halo2-ecc/src/bigint/carry_mod.rs | 191 ++ .../src/bigint/check_carry_mod_to_zero.rs | 125 + .../src/bigint/check_carry_to_zero.rs | 86 + vendor/halo2-lib/halo2-ecc/src/bigint/mod.rs | 306 ++ .../halo2-ecc/src/bigint/mul_no_carry.rs | 49 + .../halo2-ecc/src/bigint/negative.rs | 11 + .../src/bigint/scalar_mul_and_add_no_carry.rs | 58 + .../src/bigint/scalar_mul_no_carry.rs | 39 + .../halo2-lib/halo2-ecc/src/bigint/select.rs | 50 + .../src/bigint/select_by_indicator.rs | 69 + vendor/halo2-lib/halo2-ecc/src/bigint/sub.rs | 79 + .../halo2-ecc/src/bigint/sub_no_carry.rs | 34 + .../halo2-ecc/src/bn254/final_exp.rs | 403 +++ vendor/halo2-lib/halo2-ecc/src/bn254/mod.rs | 16 + .../halo2-lib/halo2-ecc/src/bn254/pairing.rs | 525 ++++ .../halo2-ecc/src/bn254/tests/ec_add.rs | 111 + .../src/bn254/tests/fixed_base_msm.rs | 115 + .../halo2-ecc/src/bn254/tests/mod.rs | 55 + .../halo2-ecc/src/bn254/tests/msm.rs | 112 + .../src/bn254/tests/msm_sum_infinity.rs | 69 + .../tests/msm_sum_infinity_fixed_base.rs | 69 + .../halo2-ecc/src/bn254/tests/pairing.rs | 109 + vendor/halo2-lib/halo2-ecc/src/ecc/ecdsa.rs | 105 + .../halo2-lib/halo2-ecc/src/ecc/fixed_base.rs | 219 ++ .../halo2-ecc/src/ecc/fixed_base_pippenger.rs | 260 ++ vendor/halo2-lib/halo2-ecc/src/ecc/mod.rs | 1194 +++++++ .../halo2-lib/halo2-ecc/src/ecc/pippenger.rs | 339 ++ vendor/halo2-lib/halo2-ecc/src/ecc/tests.rs | 68 + vendor/halo2-lib/halo2-ecc/src/fields/fp.rs | 472 +++ vendor/halo2-lib/halo2-ecc/src/fields/fp12.rs | 251 ++ vendor/halo2-lib/halo2-ecc/src/fields/fp2.rs | 132 + vendor/halo2-lib/halo2-ecc/src/fields/mod.rs | 312 ++ .../src/fields/tests/fp/assert_eq.rs | 77 + .../halo2-ecc/src/fields/tests/fp/mod.rs | 87 + .../halo2-ecc/src/fields/tests/fp12/mod.rs | 40 + .../halo2-ecc/src/fields/tests/mod.rs | 2 + .../halo2-lib/halo2-ecc/src/fields/vector.rs | 505 +++ vendor/halo2-lib/halo2-ecc/src/lib.rs | 16 + .../halo2-lib/halo2-ecc/src/secp256k1/mod.rs | 12 + .../halo2-ecc/src/secp256k1/tests/ecdsa.rs | 146 + .../src/secp256k1/tests/ecdsa_tests.rs | 59 + .../halo2-ecc/src/secp256k1/tests/mod.rs | 109 + vendor/halo2-lib/rust-toolchain | 1 + vendor/halo2-lib/rustfmt.toml | 3 + 128 files changed, 21931 insertions(+), 485 deletions(-) create mode 100644 vendor/halo2-lib/.github/workflows/ci.yml create mode 100644 vendor/halo2-lib/.gitignore create mode 100644 vendor/halo2-lib/CHANGELOG.md create mode 100644 vendor/halo2-lib/Cargo.lock create mode 100644 vendor/halo2-lib/Cargo.toml create mode 100644 vendor/halo2-lib/LICENSE create mode 100644 vendor/halo2-lib/README.md create mode 100644 vendor/halo2-lib/SECURITY.md create mode 100644 vendor/halo2-lib/audits/v0.4.1-spearbit-report.pdf create mode 100644 vendor/halo2-lib/halo2-base/Cargo.toml create mode 100644 vendor/halo2-lib/halo2-base/README.md create mode 100644 vendor/halo2-lib/halo2-base/benches/inner_product.rs create mode 100644 vendor/halo2-lib/halo2-base/benches/mul.rs create mode 100644 vendor/halo2-lib/halo2-base/examples/inner_product.rs create mode 100644 vendor/halo2-lib/halo2-base/proptest-regressions/gates/tests/prop_test.txt create mode 100644 vendor/halo2-lib/halo2-base/src/gates/circuit/builder.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/circuit/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/flex_gate/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/multi_phase.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/parallelize.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/single_phase.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/range/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/tests/flex_gate.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/tests/general.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/tests/idx_to_indicator.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/tests/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/tests/neg_prop.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/tests/pos_prop.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/tests/range.rs create mode 100644 vendor/halo2-lib/halo2-base/src/gates/tests/utils.rs create mode 100644 vendor/halo2-lib/halo2-base/src/lib.rs create mode 100644 vendor/halo2-lib/halo2-base/src/poseidon/hasher/mds.rs create mode 100644 vendor/halo2-lib/halo2-base/src/poseidon/hasher/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/poseidon/hasher/spec.rs create mode 100644 vendor/halo2-lib/halo2-base/src/poseidon/hasher/state.rs create mode 100644 vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/compatibility.rs create mode 100644 vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/hasher.rs create mode 100644 vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/state.rs create mode 100644 vendor/halo2-lib/halo2-base/src/poseidon/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/safe_types/bytes.rs create mode 100644 vendor/halo2-lib/halo2-base/src/safe_types/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/safe_types/primitives.rs create mode 100644 vendor/halo2-lib/halo2-base/src/safe_types/tests/bytes.rs create mode 100644 vendor/halo2-lib/halo2-base/src/safe_types/tests/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/safe_types/tests/safe_type.rs create mode 100644 vendor/halo2-lib/halo2-base/src/utils/halo2.rs create mode 100644 vendor/halo2-lib/halo2-base/src/utils/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/utils/testing.rs create mode 100644 vendor/halo2-lib/halo2-base/src/virtual_region/copy_constraints.rs create mode 100644 vendor/halo2-lib/halo2-base/src/virtual_region/lookups.rs create mode 100644 vendor/halo2-lib/halo2-base/src/virtual_region/lookups/basic.rs create mode 100644 vendor/halo2-lib/halo2-base/src/virtual_region/manager.rs create mode 100644 vendor/halo2-lib/halo2-base/src/virtual_region/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/virtual_region/tests/lookups/memory.rs create mode 100644 vendor/halo2-lib/halo2-base/src/virtual_region/tests/lookups/mod.rs create mode 100644 vendor/halo2-lib/halo2-base/src/virtual_region/tests/mod.rs create mode 100644 vendor/halo2-lib/halo2-ecc/Cargo.toml create mode 100644 vendor/halo2-lib/halo2-ecc/benches/README.md create mode 100644 vendor/halo2-lib/halo2-ecc/benches/fixed_base_msm.rs create mode 100644 vendor/halo2-lib/halo2-ecc/benches/fp_mul.rs create mode 100644 vendor/halo2-lib/halo2-ecc/benches/msm.rs create mode 100644 vendor/halo2-lib/halo2-ecc/configs/bn254/bench_ec_add.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/bn254/bench_fixed_msm.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/bn254/bench_fixed_msm.t.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/bn254/bench_msm.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/bn254/bench_msm.t.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/bn254/bench_pairing.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/bn254/bench_pairing.t.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/bn254/ec_add_circuit.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/bn254/fixed_msm_circuit.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/bn254/msm_circuit.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/bn254/pairing_circuit.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/secp256k1/bench_ecdsa.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/secp256k1/bench_ecdsa.t.config create mode 100644 vendor/halo2-lib/halo2-ecc/configs/secp256k1/ecdsa_circuit.config create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/add_no_carry.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/big_is_equal.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/big_is_zero.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/big_less_than.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/carry_mod.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/check_carry_to_zero.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/mod.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/mul_no_carry.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/negative.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/scalar_mul_and_add_no_carry.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/scalar_mul_no_carry.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/select.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/select_by_indicator.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/sub.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bigint/sub_no_carry.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bn254/final_exp.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bn254/mod.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bn254/pairing.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bn254/tests/ec_add.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bn254/tests/fixed_base_msm.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bn254/tests/mod.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bn254/tests/msm.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bn254/tests/msm_sum_infinity.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bn254/tests/msm_sum_infinity_fixed_base.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/bn254/tests/pairing.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/ecc/ecdsa.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/ecc/fixed_base.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/ecc/fixed_base_pippenger.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/ecc/mod.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/ecc/pippenger.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/ecc/tests.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/fields/fp.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/fields/fp12.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/fields/fp2.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/fields/mod.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/fields/tests/fp/assert_eq.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/fields/tests/fp/mod.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/fields/tests/fp12/mod.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/fields/tests/mod.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/fields/vector.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/lib.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/secp256k1/mod.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/secp256k1/tests/ecdsa.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/secp256k1/tests/ecdsa_tests.rs create mode 100644 vendor/halo2-lib/halo2-ecc/src/secp256k1/tests/mod.rs create mode 100644 vendor/halo2-lib/rust-toolchain create mode 100644 vendor/halo2-lib/rustfmt.toml diff --git a/Cargo.lock b/Cargo.lock index 49c72b95..a284b065 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,40 +1,40 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.15", + "getrandom 0.3.4", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -55,85 +55,103 @@ dependencies = [ "alloy-rlp", "crc", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "alloy-eip2930" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b82752a889170df67bbb36d42ca63c531eb16274f0d7299ae2a680facba17bd" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" dependencies = [ "alloy-primitives", "alloy-rlp", + "borsh", "serde", ] [[package]] name = "alloy-eip7702" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d4769c6ffddca380b0070d71c8b7f30bed375543fe76bb2f74ec0acf4b7cd16" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" dependencies = [ "alloy-primitives", "alloy-rlp", + "borsh", "k256", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eip7928" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3231de68d5d6e75332b7489cfcc7f4dfabeba94d990a10e4b923af0e6623540" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", ] [[package]] name = "alloy-eips" -version = "1.0.23" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5937e2d544e9b71000942d875cbc57965b32859a666ea543cc57aae5a06d602d" +checksum = "be47bf1b91674a5f394b9ed3c691d764fb58ba43937f1371550ff4bc8e59c295" dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", + "alloy-eip7928", "alloy-primitives", "alloy-rlp", "alloy-serde", "auto_impl", + "borsh", "c-kzg", "derive_more", "either", "serde", + "serde_with 3.16.1", "sha2 0.10.9", + "thiserror 2.0.18", ] [[package]] name = "alloy-primitives" -version = "1.2.0" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a326d47106039f38b811057215a92139f46eef7983a4b77b10930a0ea5685b1e" +checksum = "3b88cf92ed20685979ed1d8472422f0c6c2d010cec77caf63aaa7669cc1a7bc2" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", "derive_more", - "foldhash", - "hashbrown 0.15.2", - "indexmap 2.9.0", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "indexmap 2.13.0", "itoa", "k256", "keccak-asm", "paste", "proptest", - "rand 0.9.0", + "rand 0.9.2", + "rapidhash", "ruint", "rustc-hash 2.1.1", "serde", "sha3 0.10.8", - "tiny-keccak", ] [[package]] name = "alloy-rlp" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" +checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -142,32 +160,26 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" +checksum = "ce8849c74c9ca0f5a03da1c865e3eb6f768df816e67dd3721a398a8a7e398011" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "alloy-serde" -version = "1.0.23" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1722bc30feef87cc0fa824e43c9013f9639cc6c037be7be28a31361c788be2" +checksum = "feb73325ee881e42972a5a7bc85250f6af89f92c6ad1222285f74384a203abeb" dependencies = [ "alloy-primitives", "serde", "serde_json", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -185,9 +197,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "ark-bls12-381" @@ -226,7 +238,7 @@ dependencies = [ "ark-std 0.5.0", "educe", "fnv", - "hashbrown 0.15.2", + "hashbrown 0.15.5", "itertools 0.13.0", "num-bigint", "num-integer", @@ -319,7 +331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] @@ -357,7 +369,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] @@ -372,7 +384,7 @@ dependencies = [ "ark-std 0.5.0", "educe", "fnv", - "hashbrown 0.15.2", + "hashbrown 0.15.5", ] [[package]] @@ -446,7 +458,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] @@ -510,26 +522,26 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "az" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -537,7 +549,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -560,9 +572,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bincode" @@ -590,15 +602,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitcoin-io" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin_hashes" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", "hex-conservative", @@ -612,12 +624,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" -dependencies = [ - "serde", -] +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bitvec" @@ -627,16 +636,15 @@ checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", - "serde", "tap", "wyz", ] [[package]] name = "blake2b_simd" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" dependencies = [ "arrayref", "arrayvec", @@ -670,9 +678,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blst" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" dependencies = [ "cc", "glob", @@ -680,11 +688,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byte-slice-cast" @@ -694,9 +725,9 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -706,18 +737,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] [[package]] name = "c-kzg" -version = "2.1.1" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" +checksum = "e00bf4b112b07b505472dbefd19e37e53307e2bfed5a79e0cc161d58ccd0e687" dependencies = [ "blst", "cc", @@ -742,26 +773,32 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.19" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ - "android-tzdata", "iana-time-zone", "num-traits", "serde", @@ -797,18 +834,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.35" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.35" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstyle", "clap_lex", @@ -816,9 +853,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colored" @@ -832,15 +869,14 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.14.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" dependencies = [ "cfg-if", "cpufeatures", - "hex", "proptest", - "serde", + "serde_core", ] [[package]] @@ -851,9 +887,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ "const_format_proc_macros", ] @@ -871,9 +907,18 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "core-foundation-sys" @@ -883,9 +928,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpp_demangle" -version = "0.4.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" dependencies = [ "cfg-if", ] @@ -901,9 +946,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.2.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -1038,7 +1083,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "crossterm_winapi", "libc", "mio", @@ -1059,9 +1104,9 @@ dependencies = [ [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" @@ -1091,8 +1136,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -1106,7 +1161,21 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.100", + "syn 2.0.114", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", ] [[package]] @@ -1115,9 +1184,20 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] @@ -1131,9 +1211,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "zeroize", @@ -1141,12 +1221,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -1162,33 +1242,35 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case", "proc-macro2", "quote", - "syn 2.0.100", + "rustc_version 0.4.1", + "syn 2.0.114", "unicode-xid", ] @@ -1213,6 +1295,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ecdsa" version = "0.16.9" @@ -1236,7 +1324,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] @@ -1244,9 +1332,6 @@ name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -dependencies = [ - "serde", -] [[package]] name = "elliptic-curve" @@ -1269,22 +1354,22 @@ dependencies = [ [[package]] name = "enum-ordinalize" -version = "4.3.0" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" dependencies = [ "enum-ordinalize-derive", ] [[package]] name = "enum-ordinalize-derive" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] @@ -1295,12 +1380,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1365,6 +1450,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "findshlibs" version = "0.10.2" @@ -1401,6 +1492,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "funty" version = "2.0.0" @@ -1409,9 +1506,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -1420,59 +1517,59 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", ] [[package]] name = "getset" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "gmp-mpfr-sys" -version = "1.6.5" +version = "1.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d61197a68f6323b9afa616cf83d55d69191e1bf364d4eb7d35ae18defe776" +checksum = "60f8970a75c006bb2f8ae79c6768a116dd215fa8346a87aed99bf9d82ca43394" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1488,19 +1585,20 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] name = "halo2-axiom" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f0ca78d12ac5c893f286d7cdfe3869290305ab8cac376e2592cdc8396da102" +checksum = "0aee3f8178b78275038e5ea0e2577140056d2c4c87fccaf6777dc0a8eebe455a" dependencies = [ "blake2b_simd", "crossbeam", @@ -1520,9 +1618,9 @@ dependencies = [ [[package]] name = "halo2-base" -version = "0.5.0" -source = "git+https://github.com/axiom-crypto/halo2-lib.git?tag=v0.5.0-git#2d4b70b8558439a908dea708f4721394b1ae3ef6" +version = "0.5.1" dependencies = [ + "ark-std 0.3.0", "getset", "halo2-axiom", "halo2_proofs", @@ -1533,6 +1631,7 @@ dependencies = [ "num-integer", "num-traits", "poseidon-primitives", + "rand 0.8.5", "rand_chacha 0.3.1", "rayon", "rustc-hash 1.1.0", @@ -1542,8 +1641,7 @@ dependencies = [ [[package]] name = "halo2-ecc" -version = "0.5.0" -source = "git+https://github.com/axiom-crypto/halo2-lib.git?tag=v0.5.0-git#2d4b70b8558439a908dea708f4721394b1ae3ef6" +version = "0.5.1" dependencies = [ "halo2-base", "itertools 0.11.0", @@ -1602,9 +1700,8 @@ dependencies = [ [[package]] name = "halo2curves-axiom" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8309e4638b4f1bcf6613d72265a84074d26034c35edc5d605b5688e580b8b8" +version = "0.7.2" +source = "git+https://github.com/jtcoolen/halo2curves.git?rev=edeb29e7964f1d24bb3bc0d2a5f9417de92a572a#edeb29e7964f1d24bb3bc0d2a5f9417de92a572a" dependencies = [ "blake2b_simd", "digest 0.10.7", @@ -1636,14 +1733,24 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", "serde", + "serde_core", ] [[package]] @@ -1654,15 +1761,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -1675,9 +1776,9 @@ dependencies = [ [[package]] name = "hex-conservative" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ "arrayvec", ] @@ -1693,9 +1794,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1738,7 +1839,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] @@ -1754,20 +1855,24 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.16.1", "serde", + "serde_core", ] [[package]] name = "indoc" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] [[package]] name = "inferno" @@ -1776,7 +1881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" dependencies = [ "ahash", - "indexmap 2.9.0", + "indexmap 2.13.0", "is-terminal", "itoa", "log", @@ -1789,13 +1894,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ - "hermit-abi 0.5.0", + "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1827,9 +1932,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jemalloc-sys" @@ -1853,9 +1958,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -1885,9 +1990,9 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -1904,15 +2009,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.171" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libm" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libsecp256k1" @@ -1962,25 +2067,24 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" @@ -1988,7 +2092,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.5", ] [[package]] @@ -2003,24 +2107,24 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -2033,7 +2137,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -2084,9 +2188,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-format" @@ -2141,40 +2245,41 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -2220,9 +2325,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.7.4" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fde3d0718baf5bc92f577d652001da0f8d54cd03a7974e118d04fc888dc23d" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" dependencies = [ "arrayvec", "bitvec", @@ -2236,21 +2341,21 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.7.4" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581c837bb6b9541ce7faa9377c20616e4fb7650f6b0f68bc93c827ee504fb7b3" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -2258,15 +2363,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -2294,12 +2399,11 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pest" -version = "2.8.0" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", - "thiserror 2.0.12", "ucd-trie", ] @@ -2311,7 +2415,6 @@ checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", - "serde", ] [[package]] @@ -2334,7 +2437,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] @@ -2401,7 +2504,7 @@ dependencies = [ "lazy_static", "log", "rand 0.8.5", - "rand_xorshift", + "rand_xorshift 0.3.0", "thiserror 1.0.69", ] @@ -2439,7 +2542,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.24", + "zerocopy", ] [[package]] @@ -2464,9 +2567,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] @@ -2490,32 +2593,31 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.6.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.9.1", - "lazy_static", + "bitflags 2.10.0", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_xorshift", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift 0.4.0", "regex-syntax", "rusty-fork", "tempfile", @@ -2539,18 +2641,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" @@ -2571,14 +2673,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", "serde", - "zerocopy 0.8.24", ] [[package]] @@ -2598,7 +2699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2607,16 +2708,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.4", "serde", ] @@ -2629,13 +2730,31 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71ec30b38a417407efe7676bad0ca6b78f995f810185ece9af3bd5dc561185a9" +dependencies = [ + "rustversion", +] + [[package]] name = "ratatui" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ebc917cfb527a566c37ecb94c7e3fd098353516fb4eb6bea17015ade0182425" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "cassowary", "crossterm 0.27.0", "indoc", @@ -2649,9 +2768,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -2659,9 +2778,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -2669,18 +2788,38 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -2690,9 +2829,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -2701,9 +2840,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "revm" @@ -2726,12 +2865,11 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "6.1.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6922f7f4fbc15ca61ea459711ff75281cc875648c797088c34e4e064de8b8a7c" +checksum = "66c52031b73cae95d84cd1b07725808b5fd1500da3e5e24574a3b2dc13d9f16d" dependencies = [ "bitvec", - "once_cell", "phf", "revm-primitives", "serde", @@ -2771,9 +2909,9 @@ dependencies = [ [[package]] name = "revm-database" -version = "7.0.2" +version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61495e01f01c343dd90e5cb41f406c7081a360e3506acf1be0fc7880bfb04eb" +checksum = "39a276ed142b4718dcf64bc9624f474373ed82ef20611025045c3fb23edbef9c" dependencies = [ "alloy-eips", "revm-bytecode", @@ -2785,9 +2923,9 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "7.0.2" +version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20628d6cd62961a05f981230746c16854f903762d01937f13244716530bf98f" +checksum = "8c523c77e74eeedbac5d6f7c092e3851dbe9c7fec6f418b85992bd79229db361" dependencies = [ "auto_impl", "either", @@ -2858,7 +2996,6 @@ dependencies = [ "ark-serialize 0.5.0", "arrayref", "aurora-engine-modexp", - "blst", "c-kzg", "cfg-if", "k256", @@ -2874,22 +3011,23 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "20.1.0" +version = "20.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66145d3dc61c0d6403f27fc0d18e0363bb3b7787e67970a05c71070092896599" +checksum = "5aa29d9da06fe03b249b6419b33968ecdf92ad6428e2f012dc57bcd619b5d94e" dependencies = [ "alloy-primitives", "num_enum", + "once_cell", "serde", ] [[package]] name = "revm-state" -version = "7.0.2" +version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc830a0fd2600b91e371598e3d123480cd7bb473dd6def425a51213aa6c6d57" +checksum = "1f64fbacb86008394aaebd3454f9643b7d5a782bd251135e17c5b33da592d84d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "revm-bytecode", "revm-primitives", "serde", @@ -2907,9 +3045,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" dependencies = [ "bytemuck", ] @@ -2935,9 +3073,9 @@ dependencies = [ [[package]] name = "rug" -version = "1.27.0" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4207e8d668e5b8eb574bda8322088ccd0d7782d3d03c7e8d562e82ed82bdcbc3" +checksum = "de190ec858987c79cad4da30e19e546139b3339331282832af004d0ea7829639" dependencies = [ "az", "gmp-mpfr-sys", @@ -2947,13 +3085,14 @@ dependencies = [ [[package]] name = "ruint" -version = "1.14.0" +version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78a46eb779843b2c4f21fac5773e25d6d5b7c8f0922876c91541790d2ca27eef" +checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", "ark-ff 0.4.2", + "ark-ff 0.5.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -2964,10 +3103,10 @@ dependencies = [ "primitive-types", "proptest", "rand 0.8.5", - "rand 0.9.0", + "rand 0.9.2", "rlp", "ruint-macro", - "serde", + "serde_core", "valuable", "zeroize", ] @@ -2980,9 +3119,9 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" @@ -3017,33 +3156,33 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.26", + "semver 1.0.27", ] [[package]] name = "rustix" -version = "1.0.5" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rusty-fork" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" dependencies = [ "fnv", "quick-error", @@ -3051,12 +3190,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -3066,6 +3199,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3093,7 +3250,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ "bitcoin_hashes", - "rand 0.9.0", + "rand 0.9.2", "secp256k1-sys", ] @@ -3117,9 +3274,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "semver-parser" @@ -3132,10 +3289,11 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -3148,28 +3306,38 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.13.0", "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -3184,7 +3352,26 @@ dependencies = [ "indexmap 1.9.3", "serde", "serde_json", - "serde_with_macros", + "serde_with_macros 2.3.3", + "time", +] + +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros 3.16.1", "time", ] @@ -3194,10 +3381,22 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" dependencies = [ - "darling", + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] @@ -3248,9 +3447,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" dependencies = [ "cc", "cfg-if", @@ -3264,9 +3463,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -3274,9 +3473,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", @@ -3285,10 +3484,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -3304,15 +3504,15 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "snark-verifier" @@ -3322,6 +3522,7 @@ dependencies = [ "crossterm 0.25.0", "halo2-base", "halo2-ecc", + "halo2curves-axiom", "hex", "itertools 0.11.0", "lazy_static", @@ -3366,7 +3567,7 @@ dependencies = [ "ratatui", "serde", "serde_json", - "serde_with", + "serde_with 2.3.3", "snark-verifier", ] @@ -3388,9 +3589,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -3429,7 +3630,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] @@ -3440,9 +3641,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.15.3" +version = "12.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb426702a1ee7c1d2ebf3b6fac2e67fde84f6d6396e581826e3f055d1bffb2a4" +checksum = "751a2823d606b5d0a7616499e4130a516ebd01a44f39811be2b9600936509c23" dependencies = [ "debugid", "memmap2", @@ -3452,9 +3653,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.15.3" +version = "12.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4671b7ae11875cb9c34348d2df6c5d1edd51f4c98ec45f591acb593ac8af8e0" +checksum = "79b237cfbe320601dd24b4ac817a5b68bb28f5508e33f08d42be0682cadc8ac9" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -3474,9 +3675,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -3491,15 +3692,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.19.1" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3520,7 +3721,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] @@ -3531,7 +3732,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", "test-case-core", ] @@ -3546,11 +3747,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.18", ] [[package]] @@ -3561,18 +3762,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] @@ -3586,30 +3787,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -3636,26 +3837,39 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.13.0", "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ "winnow", ] [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -3664,20 +3878,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -3694,9 +3908,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -3724,9 +3938,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" @@ -3758,9 +3972,13 @@ dependencies = [ [[package]] name = "uuid" -version = "1.16.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "valuable" @@ -3795,50 +4013,37 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.100", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3846,31 +4051,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.100", - "wasm-bindgen-backend", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -3894,11 +4099,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3909,9 +4114,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", @@ -3922,46 +4127,46 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] @@ -3984,6 +4189,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -4008,13 +4231,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4027,6 +4267,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4039,6 +4285,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4051,12 +4303,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4069,6 +4333,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4081,6 +4351,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4093,6 +4369,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4105,23 +4387,26 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "wyz" @@ -4134,60 +4419,46 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.24" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ - "zerocopy-derive 0.8.24", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/Cargo.toml b/Cargo.toml index 53c98f1a..c7de372c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,17 @@ incremental = false [profile.flamegraph] inherits = "release" debug = true +#[patch.crates-io] +#halo2curves-axiom = { git = "https://github.com/jtcoolen/halo2curves.git" } +#halo2-base = { git = "https://github.com/jtcoolen/halo2-lib.git", branch = "main" } + +[patch."https://github.com/axiom-crypto/halo2-lib.git"] +halo2-base = { path = "vendor/halo2-lib/halo2-base" } +halo2-ecc = { path = "vendor/halo2-lib/halo2-ecc" } + +[patch."https://github.com/jtcoolen/halo2-lib.git"] +halo2-base = { path = "vendor/halo2-lib/halo2-base" } +halo2-ecc = { path = "vendor/halo2-lib/halo2-ecc" } + +[patch.crates-io] +halo2curves-axiom = { git = "https://github.com/jtcoolen/halo2curves.git", rev = "edeb29e7964f1d24bb3bc0d2a5f9417de92a572a" } diff --git a/vendor/halo2-lib/.github/workflows/ci.yml b/vendor/halo2-lib/.github/workflows/ci.yml new file mode 100644 index 00000000..609d5f0a --- /dev/null +++ b/vendor/halo2-lib/.github/workflows/ci.yml @@ -0,0 +1,69 @@ +name: Tests + +on: + push: + branches: ["main"] + pull_request: + branches: ["main", "develop", "community-edition", "release-*"] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest-64core-256ram + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run halo2-base tests + working-directory: "halo2-base" + run: | + cargo test + - name: Run halo2-ecc tests (mock prover) + working-directory: "halo2-ecc" + run: | + cargo test --lib -- --skip bench + - name: Run halo2-ecc tests (real prover) + working-directory: "halo2-ecc" + run: | + mv configs/bn254/bench_fixed_msm.t.config configs/bn254/bench_fixed_msm.config + mv configs/bn254/bench_msm.t.config configs/bn254/bench_msm.config + mv configs/bn254/bench_pairing.t.config configs/bn254/bench_pairing.config + mv configs/secp256k1/bench_ecdsa.t.config configs/secp256k1/bench_ecdsa.config + cargo test --release -- --nocapture bench_secp256k1_ecdsa + cargo test --release -- --nocapture bench_fixed_base_msm + cargo test --release -- --nocapture bench_msm + cargo test --release -- --nocapture bench_pairing + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - 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: cargo fmt --all -- --check + + - name: Run clippy + run: cargo clippy --all --all-targets -- -D warnings + + - name: Generate Cargo.lock + run: cargo generate-lockfile + + - name: Run cargo audit + uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/vendor/halo2-lib/.gitignore b/vendor/halo2-lib/.gitignore new file mode 100644 index 00000000..f1af6fd0 --- /dev/null +++ b/vendor/halo2-lib/.gitignore @@ -0,0 +1,26 @@ +# 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 + +# Local IDE configs +.idea/ +.vscode/ +======= +/target + +*.png + +/halo2_ecc/src/bn254/data/ +/halo2_ecc/src/secp256k1/data/ + +**/params/* +**/params/ + +**/.DS_Store diff --git a/vendor/halo2-lib/CHANGELOG.md b/vendor/halo2-lib/CHANGELOG.md new file mode 100644 index 00000000..ab67d01e --- /dev/null +++ b/vendor/halo2-lib/CHANGELOG.md @@ -0,0 +1,4 @@ +# v0.3.0 + +- Remove `PlonkPlus` strategy for `GateInstructions` to reduce code complexity. + - Because this strategy involved 1 selector AND 1 fixed column per advice column, it seems hard to justify it will lead to better peformance for the prover or verifier. diff --git a/vendor/halo2-lib/Cargo.lock b/vendor/halo2-lib/Cargo.lock new file mode 100644 index 00000000..89ccf243 --- /dev/null +++ b/vendor/halo2-lib/Cargo.lock @@ -0,0 +1,2791 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "colored", + "num-traits", + "rand", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core-text" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types", + "libc", +] + +[[package]] +name = "cpp_demangle" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-macro" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "288a8f36b28a19d7dbd572c76006c0a0eba1f3bf912a254dda211c6f665e7ffc" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + +[[package]] +name = "derive_builder" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" +dependencies = [ + "darling", + "derive_builder_core", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "dwrote" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70182709525a3632b2ba96b6569225467b18ecb4a77f46d255f713a6bebf05fd" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "font-kit" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64b34f4efd515f905952d91bc185039863705592c0c53ae6d979805dd154520" +dependencies = [ + "bitflags 2.8.0", + "byteorder", + "core-foundation", + "core-graphics", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "getset" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded738faa0e88d3abc9d1a13cb11adc2073c400969eeb8793cf7132589959fc" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "halo2-axiom" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aee3f8178b78275038e5ea0e2577140056d2c4c87fccaf6777dc0a8eebe455a" +dependencies = [ + "ark-std", + "blake2b_simd", + "crossbeam", + "ff", + "group", + "halo2curves-axiom", + "itertools 0.11.0", + "maybe-rayon", + "pairing", + "rand", + "rand_core", + "rayon", + "rustc-hash", + "sha3 0.10.8", + "tracing", +] + +[[package]] +name = "halo2-base" +version = "0.5.0" +dependencies = [ + "ark-std", + "criterion", + "criterion-macro", + "env_logger 0.10.2", + "getset", + "halo2-axiom", + "halo2_proofs", + "itertools 0.11.0", + "jemallocator", + "log", + "mimalloc", + "num-bigint", + "num-integer", + "num-traits", + "plotters", + "poseidon-primitives", + "pprof", + "proptest", + "pse-poseidon", + "rand", + "rand_chacha", + "rayon", + "rustc-hash", + "serde", + "serde_json", + "test-case", + "test-log", +] + +[[package]] +name = "halo2-ecc" +version = "0.5.0" +dependencies = [ + "ark-std", + "criterion", + "criterion-macro", + "env_logger 0.10.2", + "halo2-base", + "itertools 0.11.0", + "num-bigint", + "num-integer", + "num-traits", + "plotters", + "pprof", + "rand", + "rand_chacha", + "rand_core", + "rayon", + "serde", + "serde_json", + "test-case", + "test-log", +] + +[[package]] +name = "halo2_proofs" +version = "0.3.0" +source = "git+https://github.com/privacy-scaling-explorations/halo2.git?tag=v0.3.0#73408a140737d8336490452193b21f5a7a94e7de" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "halo2curves", + "plotters", + "rand_chacha", + "rand_core", + "rayon", + "sha3 0.9.1", + "tabbycat", + "tracing", +] + +[[package]] +name = "halo2curves" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db81d01d0bbfec9f624d7590fc6929ee2537a64ec1e080d8f8c9e2d2da291405" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "hex", + "lazy_static", + "num-bigint", + "num-traits", + "pairing", + "pasta_curves", + "paste", + "rand", + "rand_core", + "rayon", + "serde", + "serde_arrays", + "static_assertions", + "subtle", +] + +[[package]] +name = "halo2curves-axiom" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8309e4638b4f1bcf6613d72265a84074d26034c35edc5d605b5688e580b8b8" +dependencies = [ + "blake2b_simd", + "digest 0.10.7", + "ff", + "group", + "hex", + "lazy_static", + "num-bigint", + "num-traits", + "pairing", + "pasta_curves", + "paste", + "rand", + "rand_core", + "rayon", + "serde", + "serde_arrays", + "sha2", + "static_assertions", + "subtle", + "unroll", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-traits", + "png", +] + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inferno" +version = "0.11.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" +dependencies = [ + "ahash", + "indexmap", + "is-terminal", + "itoa", + "log", + "num-format", + "once_cell", + "quick-xml", + "rgb", + "str_stack", +] + +[[package]] +name = "is-terminal" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jemalloc-sys" +version = "0.5.4+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6c1946e1cea1788cbfde01c993b52a10e2da07f4bac608228d1bed20bfebf2" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "jemallocator" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0de374a9f8e63150e6f5e8a60cc14c668226d7a347d8aee1a45766e3c4dd3bc" +dependencies = [ + "jemalloc-sys", + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.48.5", +] + +[[package]] +name = "libmimalloc-sys" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23aa6811d3bd4deb8a84dde645f943476d13b248d818edcf8ce0b2f37f036b44" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.8.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "mimalloc" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68914350ae34959d83f732418d51e2427a794055d0b9529f48259ac07af65633" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "rand", +] + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "hex", + "lazy_static", + "rand", + "serde", + "static_assertions", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf07ef4804cfa9aea3b04a7bbdd5a40031dbb6b4f2cbaf2b011666c80c5b4f2" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "chrono", + "font-kit", + "image", + "lazy_static", + "num-traits", + "pathfinder_geometry", + "plotters-backend", + "plotters-bitmap", + "plotters-svg", + "ttf-parser", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-bitmap" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ce181e3f6bf82d6c1dc569103ca7b1bd964c60ba03d7e6cdfbb3e3eb7f7405" +dependencies = [ + "gif", + "image", + "plotters-backend", +] + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "poseidon-primitives" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4aaeda7a092e21165cc5f0cbc738e72a46f31c03c3cbd87b71ceae9d2d93bc" +dependencies = [ + "bitvec", + "ff", + "lazy_static", + "log", + "rand", + "rand_xorshift", + "thiserror", +] + +[[package]] +name = "pprof" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5c97c51bd34c7e742402e216abdeb44d415fbe6ae41d56b114723e953711cb" +dependencies = [ + "backtrace", + "cfg-if", + "criterion", + "findshlibs", + "inferno", + "libc", + "log", + "nix", + "once_cell", + "parking_lot", + "smallvec", + "symbolic-demangle", + "tempfile", + "thiserror", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.8.0", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.8.5", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "pse-poseidon" +version = "0.2.0" +source = "git+https://github.com/axiom-crypto/pse-poseidon.git#19d3b09481bda0e95e7c005906365d070fceb752" +dependencies = [ + "halo2curves-axiom", + "subtle", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-xml" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.8.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_arrays" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38636132857f68ec3d5f3eb121166d2af33cb55174c4d5ff645db6165cbef0fd" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "serde_json" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str_stack" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" + +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "symbolic-common" +version = "12.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a4dfe4bbeef59c1f32fc7524ae7c95b9e1de5e79a43ce1604e181081d71b0c" +dependencies = [ + "debugid", + "memmap2", + "stable_deref_trait", + "uuid", +] + +[[package]] +name = "symbolic-demangle" +version = "12.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cf6a95abff97de4d7ff3473f33cacd38f1ddccad5c1feab435d6760300e3b6" +dependencies = [ + "cpp_demangle", + "rustc-demangle", + "symbolic-common", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tabbycat" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c45590f0f859197b4545be1b17b2bc3cc7bb075f7d1cc0ea1dc6521c0bf256a3" +dependencies = [ + "anyhow", + "derive_builder", + "regex", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom 0.3.1", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", + "test-case-core", +] + +[[package]] +name = "test-log" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f46083d221181166e5b6f6b1e5f1d499f3a76888826e6cb1d057554157cd0f" +dependencies = [ + "env_logger 0.11.6", + "test-log-macros", + "tracing-subscriber", +] + +[[package]] +name = "test-log-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "unroll" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad948c1cb799b1a70f836077721a92a35ac177d4daddf4c20a633786d4cf618" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.98", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] diff --git a/vendor/halo2-lib/Cargo.toml b/vendor/halo2-lib/Cargo.toml new file mode 100644 index 00000000..89876ad5 --- /dev/null +++ b/vendor/halo2-lib/Cargo.toml @@ -0,0 +1,45 @@ +[workspace] +members = ["halo2-base", "halo2-ecc"] +resolver = "2" + +[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 = false +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 = "unwind" +incremental = false + +# For performance profiling +[profile.flamegraph] +inherits = "release" +debug = true + +[patch."https://github.com/axiom-crypto/halo2-lib.git"] +halo2-base = { path = "../halo2-lib/halo2-base" } +halo2-ecc = { path = "../halo2-lib/halo2-ecc" } + +[patch.crates-io] +halo2-base = { path = "../halo2-lib/halo2-base" } +halo2-ecc = { path = "../halo2-lib/halo2-ecc" } diff --git a/vendor/halo2-lib/LICENSE b/vendor/halo2-lib/LICENSE new file mode 100644 index 00000000..69ec60b5 --- /dev/null +++ b/vendor/halo2-lib/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/vendor/halo2-lib/README.md b/vendor/halo2-lib/README.md new file mode 100644 index 00000000..d920f0db --- /dev/null +++ b/vendor/halo2-lib/README.md @@ -0,0 +1,310 @@ +# halo2-lib + +This repository aims to provide basic primitives for writing zero-knowledge proof circuits using the [Halo 2](https://zcash.github.io/halo2/) proving stack. To discuss or collaborate, join our community on [Telegram](https://t.me/halo2lib). + +## Getting Started + +For a brief introduction to zero-knowledge proofs (ZK), see this [doc](https://docs.axiom.xyz/zero-knowledge-proofs/introduction-to-zk). + +Halo 2 is written in Rust, so you need to [install](https://www.rust-lang.org/tools/install) Rust to use this library: + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +Clone this repo and start off in the `halo2-lib` directory. + +```bash +git clone https://github.com/axiom-crypto/halo2-lib.git +cd halo2-lib +``` + +## Status + +As of June 2023, `halo2-lib` v0.3.0 and later are recommended for production use. It completed external audits conducted by [Spearbit](https://spearbit.com/) (available [here](https://github.com/axiom-crypto/halo2-lib/blob/main/audits/v0.4.1-spearbit-report.pdf)) and [Trail of Bits](https://www.trailofbits.com/) (available [here](https://github.com/trailofbits/publications/blob/948e6e08db485b22bba2d53a48bb6a7d0f8d694b/reviews/2023-06-axiom-halo2libraries-securityreview.pdf) and [here](https://github.com/trailofbits/publications/blob/948e6e08db485b22bba2d53a48bb6a7d0f8d694b/reviews/2023-10-axiom-halo2libraryupgrades-securityreview.pdf)). + +## Security + +See [SECURITY.md](https://github.com/axiom-crypto/halo2-lib/blob/main/SECURITY.md). + +## Projects built with `halo2-lib` + +- [Axiom](https://github.com/axiom-crypto/axiom-eth) -- Prove facts about Ethereum on-chain data via aggregate block header, account, and storage proofs. +- [Proof of Email](https://github.com/zkemail/) -- Prove facts about emails with the same trust assumption as the email domain. + - [halo2-regex](https://github.com/zkemail/halo2-regex) + - [halo2-zk-email](https://github.com/zkemail/halo2-zk-email) + - [halo2-base64](https://github.com/zkemail/halo2-base64) + - [halo2-rsa](https://github.com/zkemail/halo2-rsa/tree/feat/new_bigint) +- [halo2-fri-gadget](https://github.com/maxgillett/halo2-fri-gadget) -- FRI verifier in halo2. +- [eth-voice-recovery](https://github.com/SoraSuegami/voice_recovery_circuit) -- Verify the voice recovery process. +- [zkEVM signature verification circuit](https://github.com/scroll-tech/zkevm-circuits/tree/develop/zkevm-circuits/src/sig_circuit.rs) +- [zkEVM tx-circuit](https://github.com/scroll-tech/zkevm-circuits/tree/develop/zkevm-circuits/src/tx_circuit) +- [webauthn-halo2](https://github.com/zkwebauthn/webauthn-halo2) -- Proving and verifying WebAuthn with halo2. +- [Fixed Point Arithmetic](https://github.com/DCMMC/halo2-scaffold/tree/main/src/gadget) -- Fixed point arithmetic library in halo2. +- [Spectre](https://github.com/ChainSafe/Spectre) -- Verifying Beacon chain headers via Altair lightclient protocol +- [halo2-nn-wasm](https://github.com/metavind/halo2-nn-wasm) -- Neural network in halo2 for WASM. +- [halo2-cairo](https://github.com/odyssey2077/halo2-cairo) -- Prove Cairo program execution in halo2. +- [indexed-merkle-tree](https://github.com/aerius-labs/indexed-merkle-tree-halo2) -- Indexed Merkle Tree operations in halo2. +- [zkCert](https://github.com/zkCert/halo2-zkcert) -- Verify a chain of x509 certificates in halo2. +- [zk-dcap-verifier](https://github.com/CliqueOfficial/zk-dcap-verifier) -- On-chain DCAP attestation verification. +- [polymon_gan](https://github.com/Modulus-Labs/polymon_gan) -- Prover for the Polymon GAN network. +- [MynaWallet](https://github.com/MynaWallet/monorepo/tree/develop/packages/halo2-circuits) -- Verifies RSA signatures signed by Myna Card (Japan's ID Card). +- [zk-face-circuit](https://github.com/team-byof/zk-face-circuit) -- Face Wallet Verification system for Ethereum wallets. +- [halo2-lib-secp256r1](https://github.com/CliqueOfficial/halo2-lib-secp256r1) + +## halo2-base + +This crate provides an additional API for writing circuits in Halo 2 using our [simple vertical gate](https://docs.axiom.xyz/zero-knowledge-proofs/getting-started-with-halo2#halo2-lib). It also provides basic functions built using this API. The provided methods can be found in [`GateInstructions`](https://axiom-crypto.github.io/halo2-lib/halo2_base/gates/trait.GateInstructions.html) and [`RangeInstructions`](https://axiom-crypto.github.io/halo2-lib/halo2_base/gates/trait.RangeInstructions.html). The latter are operations that require using a lookup table for range checks. + +- Read the [Rust docs](https://docs.rs/halo2-base/0.4.1/halo2_base/) for this crate. +- To get started with Halo 2 and to learn how to build using the `halo2-base` API, see the [Getting Started](https://docs.axiom.xyz/zero-knowledge-proofs/getting-started-with-halo2) guide. + +To run some basic tests, run the following command: + +```bash +cargo test -- --nocapture test_gates +cargo test -- --nocapture test_range +``` + +(Rust tests by default do not display stdout, so we use `--nocapture` to enable streaming stdout.) +These tests use the `MockProver` to check circuits are properly constrained, however it does not mimic a true production proving setup. + +For benchmarks of native field multiplication and inner product where a production proving setup is run, run the following command: + +```bash +cargo bench --bench mul +cargo bench --bench inner_product +``` + +These benchmarks use the `criterion` crate to run `create_proof` 10 times for statistical analysis. Note the benchmark circuits perform more than a one multiplication / inner product per circuit. + +## halo2-ecc + +This crate uses `halo2-base` to provide a library of elliptic curve cryptographic primitives. In particular, we support elliptic curves over base fields that are larger than the scalar field used in the proving system (e.g., `F_r` for bn254 when using Halo 2 with a KZG backend). + +- [Rust docs](https://axiom-crypto.github.io/halo2-lib/halo2_ecc/index.html) + +### Features + +We recommend ignoring this section and using the default features if you are new to Rust. +The default features are: "jemallocator", "halo2-axiom", "display". +You can turn off "display" for a very small performance increase, where certain statistics about the circuit are not +computed and printed. + +**Exactly one** of "halo2-axiom" or "halo2-pse" feature should be turned on at all times. + +- The "halo2-axiom" feature uses our [`halo2_proofs`](https://github.com/axiom-crypto/halo2) which is a fork of the [PSE one](https://github.com/privacy-scaling-explorations/halo2) which we have slightly optimized for proving speed. +- The "halo2-pse" feature uses the Privacy Scaling Explorations [`halo2_proofs`](https://github.com/privacy-scaling-explorations/halo2) which is the most stable and has the most reviewers. + +We guarantee that the proofs generated by the two forks are identical. + +#### Memory allocator + +The "jemallocator" feature uses the [jemallocator](https://crates.io/crates/jemallocator) crate for memory allocation. +You can turn it off to use the system allocator. Or use feature "mimalloc" to use the [mimalloc](https://crates.io/crates/mimalloc) crate. We have found the performance of these allocators heavily depends on what machine you are running on. + +### Modules + +- `bigint`: Provides support for optimized big integer arithmetic in ZK. +- `fields`: Provides common functions for prime field arithmetic, optimized for prime fields that are larger than the scalar field used in the proving system. + - `fp2`: Field operations over certain quadratic extension fields. + - `fp12`: Field operations over certain degree `12` extension fields (designed with BN254 and BLS12-381 in mind). +- `ecc`: Library of elliptic curve cryptographic primitives, currently for short Weierstrass curves over base fields compatible with `fields` module (in particular field extension are allowed). + - Elliptic curve addition and doubling. + - Scalar multiplication and multiscalar multiplication (MSM, multiexp). Implementations are ZK-optimized, using windowed methods and Pippenger's algorithm when appropriate. + - ECDSA signature verification. +- `secp256k1`: Specialization of the `ecc` module for the secp256k1 curve. + - `test_secp256k1_ecdsa` and `bench_secp256k1_ecdsa` show how to implement ECDSA signature verification for secp256k1. (More details below.) +- `bn254`: Specialization of the `ecc` module for the BN254 curve. + - `final_exp` and `pairing` modules together implement the optimal Ate pairing for BN254 in ZK. The implementation has been optimized for the specifics of BN curves, but can be easily adapted to BLS curves (coming soon!). + +### Tests with `MockProver` + +**Do not run `cargo test` without any filters.** +Some of the tests are actually benchmarks, and will take a long time to run. + +#### Setup + +All tests should be run in the `halo2-lib/halo2-ecc` directory. +Some tests read files from specific directories, so they will not work if +you are in the `halo2-lib` root directory. + +For benchmarks below, you can symlink a `params` folder within `halo2-ecc` directory with previously generated universal trusted setup files. Otherwise, the benchmarks will generate a new random setup and save them in the `params` directory. **Warning:** These trusted setups are generated using a _known_ random seed, so they are not secure. They should NOT be used in production. +For more a production suitable trusted setup, see [KZG Trusted Setup](https://docs.axiom.xyz/docs/transparency-and-security/kzg-trusted-setup). + +Tests can be run in the same way as in the previous [section](#halo2-base). The available commands are: + +```bash +cargo test -- --nocapture test_fp +cargo test -- --nocapture test_fp12 +cargo test -- --nocapture test_ecc +cargo test -- --nocapture test_secp256k1_ecdsa +cargo test -- --nocapture test_ec_add # for BN254 +cargo test -- --nocapture test_fixed_base_msm # for BN254 +cargo test -- --nocapture test_msm # for BN254 +cargo test -- --nocapture test_pairing # for BN254 +``` + +### Configurable Circuits + +A special features of circuits written using `halo2-base` is that any such circuit can be configured to have a different number of rows vs. columns, while keeping the total number of cells roughly the same. Different configurations make sense for different circumstances. For example, more rows vs. columns always leads to a cheaper gas cost for on-chain verification, but often at the cost of slower proving speed. For a rough mental model, see [Cost Modeling](https://docs.axiom.xyz/zero-knowledge-proofs/getting-started-with-halo2#cost-modeling). + +In some of the tests above, the circuit configuration is read from a file. You can change the configuration by changing the numbers in the file. If some numbers are too small, the test will panic because there are not enough cells to construct the circuit. If you put numbers that are too large, the test will display suggestions for what the optimal numbers should be. +In a future version we will have the circuits auto-configure themselves. + +The benchmark config files below also give a list of possible configurations you can put in a test config files. + +The test config file locations are (relative to `halo2-ecc` directory): +| Test | Config File | +| --- | --- | +| `test_secp256k1_ecdsa` | `src/secp256k1/configs/ecdsa_circuit.config` | +| `test_ec_add` | `src/bn254/configs/ec_add_circuit.config` | +| `test_fixed_base_msm` | `src/bn254/configs/fixed_msm_circuit.config` | +| `test_msm` | `src/bn254/configs/msm_circuit.config` | +| `test_pairing` | `src/bn254/configs/pairing_circuit.config` | + +## Benchmarks + +We have tests that are actually benchmarks using the production Halo2 prover. +As mentioned [above](#Configurable-Circuits), there are different configurations for each circuit that lead to _very_ different proving times. The following benchmarks will take a list of possible configurations and benchmark each one. The results are saved in a file in the `results` directory. We currently supply the configuration lists, which should provide optimal configurations for a given circuit degree `k` (however you can check versus the stdout suggestions to see if they really are optimal!). + +We run the benchmarks in `--release` mode for maximum speed. + +#### Commands + +The available benchmark commands are: + +```bash +cargo test --release -- --nocapture bench_secp256k1_ecdsa +cargo test --release -- --nocapture bench_ec_add +cargo test --release -- --nocapture bench_fixed_base_msm +cargo test --release -- --nocapture bench_msm +cargo test --release -- --nocapture bench_pairing +``` + +The locations of the config and result files (relative to `halo2-ecc` directory) are: +| Benchmark | Config File | Results File | +| --- | --- | --- | +| `bench_secp256k1_ecdsa` | `src/secp256k1/configs/bench_ecdsa.config` | `src/secp256k1/results/ecdsa_bench.csv` | +| `bench_ec_add` | `src/bn254/configs/bench_ec_add.config` | `src/bn254/results/ec_add_bench.csv` | +| `bench_fixed_base_msm` | `src/bn254/configs/bench_fixed_msm.config` | `src/bn254/results/fixed_msm_bench.csv` | +| `bench_msm` | `src/bn254/configs/bench_msm.config` | `src/bn254/results/msm_bench.csv` | +| `bench_pairing` | `src/bn254/configs/bench_pairing.config` | `src/bn254/results/pairing_bench.csv` | + +To speed up benching time you can remove certain lines from the `.config` file for configurations you don't want to bench. + +#### Criterion Benchmarks + +To run more accurate benchmarks using the `criterion` crate, you can run the following commands: + +```bash +cargo bench --bench msm +cargo bench --bench fixed_base_msm +cargo bench --bench fp_mul +``` + +This run the same proof generation over 10 runs and collect the average. Each circuit has a fixed configuration chosen for optimal speed. These benchmarks are mostly for use in performance optimization. + +### Secp256k1 ECDSA + +We provide benchmarks for ECDSA signature verification for the Secp256k1 curve on several different machines. All machines only use CPUs. + +On AWS EC2 instances r6a.8xl (AMD, x86) and r6g.8xl (Graviton, arm64), both with 32 CPU cores, 256 GB RAM, the bench is run using + +``` +cargo test --release --no-default-features --features "halo2-axiom, jemallocator" -- --nocapture bench_secp256k1_ecdsa +``` + +To optimize memory allocation to prioritize CPU utilization, +we [tune jemallocator](https://github.com/jemalloc/jemalloc/blob/dev/TUNING.md) with + +```bash +export JEMALLOC_SYS_WITH_MALLOC_CONF="background_thread:true,metadata_thp:always,dirty_decay_ms:100000,muzzy_decay_ms:100000,narenas:1,abort_conf:true" +``` + +(in practice this did not make a big difference). + +On a M2 Max Macbook Pro (12 CPU cores, 96 GB RAM) we ran the bench using + +``` +cargo test --release --no-default-features --features "halo2-axiom, mimalloc" -- --nocapture bench_secp256k1_ecdsa +``` + +(the performance of "mimalloc" vs "jemallocator" was similar). + +The other columns provide information about the [PLONKish arithmetization](https://docs.axiom.xyz/zero-knowledge-proofs/getting-started-with-halo2#plonkish-arithmetization). + +| `k` | Num Advice | Num Lookup Advice | Num Fixed | Proof Time (M2 Max) | Proof Time (r6a.8xl) | Proof Time (r6g.8xl) | +| --- | ---------- | ----------------- | --------- | ------------------- | -------------------- | -------------------- | +| 11 | 291 | 53 | 4 | 3.5s | 7.3s | 7.2s | +| 12 | 139 | 24 | 2 | 2.6s | 3.3s | 5.3s | +| 13 | 68 | 12 | 1 | 2.2s | 2.6s | 4.7s | +| 14 | 34 | 6 | 1 | 2.1s | 2.4s | 4.5s | +| 15 | 17 | 3 | 1 | `1.98s` ⚡ | 2.28s | 4.5s | +| 16 | 8 | 2 | 1 | 2.3s | 2.5s | 5.2s | +| 17 | 4 | 1 | 1 | 2.7s | 2.9s | 6s | +| 18 | 2 | 1 | 1 | 4.4s | 4.7s | 9.5s | +| 19 | 1 | 1 | 1 | 7.6s | 7.6s | 16s | + +The r6a has a higher clock speed than the r6g. + +### BN254 Pairing + +We provide benchmarks of the optimal Ate pairing for BN254 on several different machines. All machines only use CPUs. + +On AWS EC2 instances r6a.8xl (AMD, x86) and r6g.8xl (Graviton, arm64), both with 32 CPU cores, 256 GB RAM, the bench is run using + +``` +cargo test --release --no-default-features --features "halo2-axiom, jemallocator" -- --nocapture bench_pairing +``` + +To optimize memory allocation to prioritize CPU utilization, +we [tune jemallocator](https://github.com/jemalloc/jemalloc/blob/dev/TUNING.md) with + +```bash +export JEMALLOC_SYS_WITH_MALLOC_CONF="background_thread:true,metadata_thp:always,dirty_decay_ms:100000,muzzy_decay_ms:100000,narenas:1,abort_conf:true" +``` + +(in practice this did not make a big difference). + +On a M2 Max Macbook Pro (12 CPU cores, 96 GB RAM) we ran the bench using + +``` +cargo test --release --no-default-features --features "halo2-axiom, mimalloc" -- --nocapture bench_pairing +``` + +(the performance of "mimalloc" vs "jemallocator" was similar). + +The other columns provide information about the [PLONKish arithmetization](https://docs.axiom.xyz/zero-knowledge-proofs/getting-started-with-halo2#plonkish-arithmetization). + +| `k` | Num Advice | Num Lookup Advice | Num Fixed | Proof Time (M2 Max) | Proof Time (r6a.8xl) | Proof Time (r6g.8xl) | +| --- | ---------- | ----------------- | --------- | ------------------- | -------------------- | -------------------- | +| 14 | 211 | 27 | 1 | 11.8s | 16.9s | 24.8s | +| 15 | 105 | 14 | 1 | 10.4s | 12.7s | 23.6s | +| 16 | 50 | 6 | 1 | `9.5s` ⚡ | 10.96s | 21.6s | +| 17 | 25 | 3 | 1 | 9.7s | 11.2s | 22.7s | +| 18 | 13 | 2 | 1 | 11.9s | 13.5s | 27.3s | +| 19 | 6 | 1 | 1 | 14.8s | 15.3s | 30.6s | +| 20 | 3 | 1 | 1 | 23.7s | 23.8s | 48.1s | +| 21 | 2 | 1 | 1 | 40.3s | 40.8s | 82.5s | +| 22 | 1 | 1 | 1 | 69.1s | 66.9s | 135s | + +The r6a has a higher clock speed than the r6g. We hypothesize that the Apple Silicon integrated memory leads to the faster performance on the M2 Max. + +### BN254 MSM + +We provide benchmarks of multi-scalar multiplication (MSM, multi-exp) with a batch size of `100` for BN254. + +On a M2 Max Macbook Pro (12 CPU cores, 96 GB RAM) we ran the bench using + +``` +cargo test --release --no-default-features --features "halo2-axiom, mimalloc" -- --nocapture bench_msm +``` + +| `k` | Num Advice | Num Lookup Advice | Num Fixed | Proof Time (M2 Max) | +| --- | ---------- | ----------------- | --------- | ------------------- | +| 17 | 84 | 11 | 1 | `27.8s` ⚡ | +| 18 | 42 | 6 | 1 | 29.95s | +| 19 | 20 | 3 | 1 | 32.6s | +| 20 | 11 | 2 | 1 | 41.3s | +| 21 | 6 | 1 | 1 | 51.9s | diff --git a/vendor/halo2-lib/SECURITY.md b/vendor/halo2-lib/SECURITY.md new file mode 100644 index 00000000..416c8188 --- /dev/null +++ b/vendor/halo2-lib/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Report a Vulnerability + +Contact [security@axiom.xyz](mailto:security@axiom.xyz). diff --git a/vendor/halo2-lib/audits/v0.4.1-spearbit-report.pdf b/vendor/halo2-lib/audits/v0.4.1-spearbit-report.pdf new file mode 100644 index 0000000000000000000000000000000000000000..64fc8c7509671cbf62284b8b6b6eedd1909ad350 GIT binary patch literal 100237 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 Bs>=WX literal 0 HcmV?d00001 diff --git a/vendor/halo2-lib/halo2-base/Cargo.toml b/vendor/halo2-lib/halo2-base/Cargo.toml new file mode 100644 index 00000000..63e0f8a0 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/Cargo.toml @@ -0,0 +1,85 @@ +[package] +name = "halo2-base" +version = "0.5.1" +authors = ["Intrinsic Technologies"] +license = "MIT" +edition = "2021" +repository = "https://github.com/axiom-crypto/halo2-lib" +readme = "README.md" +description = "Embedded domain specific language (eDSL) for writing circuits with the [`halo2`](https://github.com/axiom-crypto/halo2) API. It simplifies circuit programming to declaring constraints over a single advice and selector column and provides built-in circuit tuning and support for multi-threaded witness generation." +rust-version = "1.73.0" + +[dependencies] +itertools = "0.11" +num-bigint = { version = "0.4", features = ["rand"] } +num-integer = "0.1" +num-traits = "0.2" +rand_chacha = "0.3" +rustc-hash = "1.1" +rayon = "1.8" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +log = "0.4" +getset = "0.1.2" +ark-std = { version = "0.3.0", features = ["print-trace"], optional = true } + +# Use Axiom's custom halo2 monorepo for faster proving when feature = "halo2-axiom" is on +halo2_proofs_axiom = { version = "0.5", package = "halo2-axiom", optional = true } +# Use PSE halo2 and halo2curves for compatibility when feature = "halo2-pse" is on +halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v0.3.0", features = [ + "circuit-params", + "derive_serde", +], optional = true } + +# This is Scroll's audited poseidon circuit. We only use it for the Native Poseidon spec. We do not use the halo2 circuit at all (and it wouldn't even work because the halo2_proofs tag is not compatbile). +# We forked it to upgrade to ff v0.13 and removed the circuit module +poseidon-rs = { package = "poseidon-primitives", version = "=0.2.0" } +# plotting circuit layout +plotters = { version = "0.3.0", optional = true } + +# test-utils +rand = { version = "0.8", optional = true } + +[dev-dependencies] +ark-std = { version = "0.3.0", features = ["print-trace"] } +rand = "0.8" +pprof = { version = "0.13", features = ["criterion", "flamegraph"] } +criterion = "0.5.1" +criterion-macro = "0.4" +test-case = "3.1.0" +test-log = "0.2.12" +env_logger = "0.10.0" +proptest = "1.1.0" +# native poseidon for testing +pse-poseidon = { git = "https://github.com/axiom-crypto/pse-poseidon.git" } + +# memory allocation +[target.'cfg(not(target_env = "msvc"))'.dependencies] +jemallocator = { version = "0.5", optional = true } + +mimalloc = { version = "0.1", default-features = false, optional = true } + +[features] +default = ["halo2-axiom", "display", "test-utils"] +asm = ["halo2_proofs_axiom?/asm"] +dev-graph = [ + "halo2_proofs?/dev-graph", + "plotters", +] # only works with halo2-pse for now +halo2-pse = ["halo2_proofs/circuit-params"] +halo2-axiom = ["halo2_proofs_axiom"] +display = [] +profile = ["halo2_proofs_axiom?/profile"] +test-utils = ["dep:rand", "ark-std"] + +[[bench]] +name = "mul" +harness = false + +[[bench]] +name = "inner_product" +harness = false + +[[example]] +name = "inner_product" +required-features = ["test-utils"] diff --git a/vendor/halo2-lib/halo2-base/README.md b/vendor/halo2-lib/halo2-base/README.md new file mode 100644 index 00000000..94cbbc58 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/README.md @@ -0,0 +1,127 @@ +# `halo2-base` + +`halo2-base` provides an embedded domain specific language (eDSL) for writing circuits with the [`halo2`](https://github.com/axiom-crypto/halo2) API. It simplifies circuit programming to declaring constraints over a single advice and selector column and provides built-in circuit tuning and support for multi-threaded witness generation. + +For further details, see the [Rust docs](https://axiom-crypto.github.io/halo2-lib/halo2_base/). + +## Virtual Region Managers + +The core framework under which `halo2-base` operates is that of _virtual cell management_. We perform witness generation in a virtual region (outside of the low-level raw halo2 `Circuit::synthesize`) and only at the very end map it to a "raw/physical" region in halo2's Plonkish arithmetization. + +We formalize this into a new trait `VirtualRegionManager`. Any `VirtualRegionManager` is associated with some subset of columns (more generally, a physical Halo2 region). It can manage its own virtual region however it wants, but it must provide a deterministic way to map the virtual region to the physical region. + +We have the following examples of virtual region managers: + +- `SinglePhaseCoreManager`: this is associated with our `BasicGateConfig` which is a simple [vertical custom gate](https://docs.axiom.xyz/zero-knowledge-proofs/getting-started-with-halo2#simplified-interface), in a single halo2 challenge phase. It manages a virtual region with a bunch of virtual columns (these are the `Context`s). One can think of all virtual columns as being concatenated into a single big column. Then given the target number of rows in the physical circuit, it will chunk the single virtual column appropriately into multiple physical columns. +- `CopyConstraintManager`: this is a global manager to allow virtual cells from different regions to be referenced. Virtual cells are referred to as `AssignedValue`. Despite the name (which is from historical reasons), these values are not actually assigned into the physical circuit. `AssignedValue`s are virtual cells. Instead they keep track of a tag for which virtual region they belong to, and some other identifying tag that loosely maps to a CPU thread. When a virtual cell is referenced and used, a copy is performed and the `CopyConstraintManager` keeps track of the equality. After the virtual cells are all physically assigned, this manager will impose the equality constraints on the physical cells. + - This manager also keeps track of constants that are used, deduplicates them, and assigns all constants into dedicated fixed columns. It also imposes the equality constraints between advice cells and the fixed cells. + - It is **very important** that all virtual region managers reference the same `CopyConstraintManager` to ensure that all copy constraints are managed properly. The `CopyConstraintManager` must also be raw assigned at the end of `Circuit::synthesize` to ensure the copy constraints are actually communicated to the raw halo2 API. +- `LookupAnyManager`: for any kind of lookup argument (either into a fixed table or dynamic table), we do not want to enable this lookup argument on every column of the circuit since enabling lookup is expensive. Instead, we allocate special advice columns (with no selector) where the lookup argument is always on. When we want to look up certain values, we copy them over to the special advice cells. This also means that the physical location of the cells you want to look up can be unstructured. + +The virtual regions are also designed to be able to interact with raw halo2 sub-circuits. The overall architecture of a circuit that may use virtual regions managed by `halo2-lib` alongside raw halo2 sub-circuits looks as follows: + +![Virtual regions with raw sub-circuit](https://user-images.githubusercontent.com/31040440/263155207-c5246cb1-f7f5-4214-920c-d4ae34c19e9c.png) + +## [`BaseCircuitBuilder`](./src/gates/circuit/mod.rs) + +A circuit builder in `halo2-lib` is a collection of virtual region managers with an associated raw halo2 configuration of columns and custom gates. The precise configuration of these columns and gates can potentially be tuned after witness generation has been performed. We do not yet codify the notion of a circuit builder into a trait. + +The core circuit builder used throughout `halo2-lib` is the `BaseCircuitBuilder`. It is associated to `BaseConfig`, which consists of instance columns together with either `FlexGateConfig` or `RangeConfig`: `FlexGateConfig` is used when no functionality involving bit range checks (usually necessary for less than comparisons on numbers) is needed, otherwise `RangeConfig` consists of `FlexGateConfig` together with a fixed lookup table for range checks. + +The basic construction of `BaseCircuitBuilder` is as follows: + +```rust +let k = 10; // your circuit will have 2^k rows +let witness_gen_only = false; // constraints are ignored if set to true +let mut builder = BaseCircuitBuilder::new(witness_gen_only).use_k(k); +// If you need to use range checks, a good default is to set `lookup_bits` to 1 less than `k` +let lookup_bits = k - 1; +builder.set_lookup_bits(lookup_bits); // this can be skipped if you are not using range checks. The program will panic if `lookup_bits` is not set when you need range checks. + +// this is the struct holding basic our eDSL API functions +let gate = GateChip::default(); +// if you need RangeChip, construct it with: +let range = builder.range_chip(); // this will panic if `builder` did not set `lookup_bits` +{ + // basic usage: + let ctx = builder.main(0); // this is "similar" to spawning a new thread. 0 refers to the halo2 challenge phase + // do your computations +} +// `builder` now contains all information from witness generation and constraints of your circuit +let unusable_rows = 9; // this is usually enough, set to 20 or higher if program panics +// This tunes your circuit to find the optimal configuration +builder.calculate_params(Some(unusable_rows)); + +// Now you can mock prove or prove your circuit: +// If you have public instances, you must either provide them yourself or extract from `builder.assigned_instances`. +MockProver::run(k as u32, &builder, instances).unwrap().assert_satisfied(); +``` + +### Proving mode + +`witness_gen_only` is set to `true` if we only care about witness generation and not about circuit constraints, otherwise it is set to false. This should **not** be set to `true` during mock proving or **key generation**. When this flag is `true`, we perform certain optimizations that are only valid when we don't care about constraints or selectors. This should only be done in the context of real proving, when a proving key has already been created. + +## [**Context**](src/lib.rs) + +`Context` holds all information of an execution trace (circuit and its witness values). `Context` represents a "virtual column" that stores unassigned constraint information in the Halo2 backend. Storing the circuit information in a `Context` rather than assigning it directly to the Halo2 backend allows for the pre-computation of circuit parameters and preserves the underlying circuit information allowing for its rearrangement into multiple columns for parallelization in the Halo2 backend. + +During `synthesize()`, the advice values of all `Context`s are concatenated into a single "virtual column" that is split into multiple true `Column`s at `break_points` each representing a different sub-section of the "virtual column". During circuit synthesis, all cells are assigned to Halo2 `AssignedCell`s in a single `Region` within Halo2's backend. + +For parallel witness generation, multiple `Context`s are created for each parallel operation. After parallel witness generation, these `Context`'s are combined to form a single "virtual column" as above. Note that while the witness generation can be multi-threaded, the ordering of the contents in each `Context`, and the order of the `Context`s themselves, must be deterministic. + +**Warning:** If you create your own `Context` in a new virtual region not provided by our libraries, you must ensure that the `type_id: &str` of the context is a globally unique identifier for the virtual region, distinct from the other `type_id` strings used to identify other virtual regions. We suggest that you either include your crate name as a prefix in the `type_id` or use [`module_path!`](https://doc.rust-lang.org/std/macro.module_path.html) to generate a prefix. +In the future we will introduce a macro to check this uniqueness at compile time. + +### [**AssignedValue**](./src/lib.rs): + +Despite the name, an `AssignedValue` is a **virtual cell**. It contains the actual witness value as well as a pointer to the location of the virtual cell within a virtual region. The pointer is given by type `ContextCell`. We only store the pointer when not in witness generation only mode as an optimization. + +```rust ignore +pub struct AssignedValue { + pub value: Assigned, + pub cell: Option, +} +``` + +### [**Assigned**](./src/plonk/assigned.rs) + +`Assigned` is not a ZK or circuit-related type. +`Assigned` is a wrapper enum for a field element which stores the value as a fraction and marks it for batched inversion using [Montgomery's trick](https://zcash.github.io/halo2/background/fields.html#montgomerys-trick). Performing batched inversion allows for the computation of the inverse of all marked values with a single inversion operation. + +```rust ignore +pub enum Assigned { + /// The field element zero. + Zero, + /// A value that does not require inversion to evaluate. + Trivial(F), + /// A value stored as a fraction to enable batch inversion. + Rational(F, F), +} +``` + +## [**QuantumCell**](./src/lib.rs) + +`QuantumCell` is a helper enum that abstracts the scenarios in which a value is assigned to the advice column in `halo2-base`. Without `QuantumCell`, assigning existing or constant values to the advice column requires manually specifying the enforced constraints on top of assigning the value leading to bloated code. `QuantumCell` handles these technical operations, all a developer needs to do is specify which enum option in `QuantumCell` the value they are adding corresponds to. + +```rust ignore +pub enum QuantumCell { + Existing(AssignedValue), + Witness(F), + WitnessFraction(Assigned), + Constant(F), +} +``` + +QuantumCell contains the following enum variants. + +- **Existing**: + Assigns a value to the advice column that exists within the advice column. The value is an existing value from some previous part of your computation already in the advice column in the form of an `AssignedValue`. When you add an existing cell into the table a new cell will be assigned into the advice column with value equal to the existing value. An equality constraint will then be added between the new cell and the "existing" cell so the Verifier has a guarantee that these two cells are always equal. + +- **Witness**: + Assigns an entirely new witness value into the advice column, such as a private input. When `assign_cell()` is called the value is wrapped in as an `Assigned::Trivial()` which marks it for exclusion from batch inversion. + +- **WitnessFraction**: + Assigns an entirely new witness value to the advice column. `WitnessFraction` exists for optimization purposes and accepts Assigned values wrapped in `Assigned::Rational()` marked for batch inverion (see [Assigned](#assigned)). + +- **Constant**: + A value that is a "known" constant. A "known" refers to known at circuit creation time to both the Prover and Verifier. When you assign a constant value there exists another secret Fixed column in the circuit constraint table whose values are fixed at circuit creation time. When you assign a Constant value, you are adding this value to the Fixed column, adding the value as a witness to the Advice column, and then imposing an equality constraint between the two corresponding cells in the Fixed and Advice columns. diff --git a/vendor/halo2-lib/halo2-base/benches/inner_product.rs b/vendor/halo2-lib/halo2-base/benches/inner_product.rs new file mode 100644 index 00000000..45f503b9 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/benches/inner_product.rs @@ -0,0 +1,78 @@ +use halo2_base::gates::circuit::{builder::RangeCircuitBuilder, CircuitBuilderStage}; +use halo2_base::gates::flex_gate::{GateChip, GateInstructions}; +use halo2_base::halo2_proofs::{ + arithmetic::Field, + dev::MockProver, + halo2curves::bn256::{Bn256, Fr}, + plonk::*, + poly::kzg::commitment::ParamsKZG, +}; +use halo2_base::utils::testing::gen_proof; +use halo2_base::utils::ScalarField; +use halo2_base::{Context, QuantumCell::Existing}; +use itertools::Itertools; +use rand::rngs::OsRng; + +use criterion::{criterion_group, criterion_main}; +use criterion::{BenchmarkId, Criterion}; + +use pprof::criterion::{Output, PProfProfiler}; +// Thanks to the example provided by @jebbow in his article +// https://www.jibbow.com/posts/criterion-flamegraphs/ + +const K: u32 = 19; + +fn inner_prod_bench(ctx: &mut Context, a: Vec, b: Vec) { + assert_eq!(a.len(), b.len()); + let a = ctx.assign_witnesses(a); + let b = ctx.assign_witnesses(b); + + let chip = GateChip::default(); + for _ in 0..(1 << K) / 16 - 10 { + chip.inner_product(ctx, a.clone(), b.clone().into_iter().map(Existing)); + } +} + +fn bench(c: &mut Criterion) { + let k = 19u32; + // create circuit for keygen + let mut builder = + RangeCircuitBuilder::from_stage(CircuitBuilderStage::Keygen).use_k(k as usize); + inner_prod_bench(builder.main(0), vec![Fr::zero(); 5], vec![Fr::zero(); 5]); + let config_params = builder.calculate_params(Some(20)); + + // check the circuit is correct just in case + MockProver::run(k, &builder, vec![]).unwrap().assert_satisfied(); + + let params = ParamsKZG::::setup(k, OsRng); + let vk = keygen_vk(¶ms, &builder).expect("vk should not fail"); + let pk = keygen_pk(¶ms, vk, &builder).expect("pk should not fail"); + + let break_points = builder.break_points(); + drop(builder); + + let mut group = c.benchmark_group("plonk-prover"); + group.sample_size(10); + group.bench_with_input( + BenchmarkId::new("inner_product", k), + &(¶ms, &pk), + |bencher, &(params, pk)| { + bencher.iter(|| { + let mut builder = + RangeCircuitBuilder::prover(config_params.clone(), break_points.clone()); + let a = (0..5).map(|_| Fr::random(OsRng)).collect_vec(); + let b = (0..5).map(|_| Fr::random(OsRng)).collect_vec(); + inner_prod_bench(builder.main(0), a, b); + gen_proof(params, pk, builder); + }) + }, + ); + group.finish(); +} + +criterion_group! { + name = benches; + config = Criterion::default().with_profiler(PProfProfiler::new(10, Output::Flamegraph(None))); + targets = bench +} +criterion_main!(benches); diff --git a/vendor/halo2-lib/halo2-base/benches/mul.rs b/vendor/halo2-lib/halo2-base/benches/mul.rs new file mode 100644 index 00000000..ee239abd --- /dev/null +++ b/vendor/halo2-lib/halo2-base/benches/mul.rs @@ -0,0 +1,69 @@ +use halo2_base::gates::circuit::{builder::RangeCircuitBuilder, CircuitBuilderStage}; +use halo2_base::gates::flex_gate::{GateChip, GateInstructions}; +use halo2_base::halo2_proofs::{ + halo2curves::bn256::{Bn256, Fr}, + halo2curves::ff::Field, + plonk::*, + poly::kzg::commitment::ParamsKZG, +}; +use halo2_base::utils::testing::gen_proof; +use halo2_base::utils::ScalarField; +use halo2_base::Context; +use rand::rngs::OsRng; + +use criterion::{criterion_group, criterion_main}; +use criterion::{BenchmarkId, Criterion}; + +use pprof::criterion::{Output, PProfProfiler}; +// Thanks to the example provided by @jebbow in his article +// https://www.jibbow.com/posts/criterion-flamegraphs/ + +const K: u32 = 9; + +fn mul_bench(ctx: &mut Context, inputs: [F; 2]) { + let [a, b]: [_; 2] = ctx.assign_witnesses(inputs).try_into().unwrap(); + let chip = GateChip::default(); + + for _ in 0..120 { + chip.mul(ctx, a, b); + } +} + +fn bench(c: &mut Criterion) { + // create circuit for keygen + let mut builder = + RangeCircuitBuilder::from_stage(CircuitBuilderStage::Keygen).use_k(K as usize); + mul_bench(builder.main(0), [Fr::zero(); 2]); + let config_params = builder.calculate_params(Some(9)); + + let params = ParamsKZG::::setup(K, OsRng); + let vk = keygen_vk(¶ms, &builder).expect("vk should not fail"); + let pk = keygen_pk(¶ms, vk, &builder).expect("pk should not fail"); + + let break_points = builder.break_points(); + + let a = Fr::random(OsRng); + let b = Fr::random(OsRng); + // native multiplication 120 times + c.bench_with_input( + BenchmarkId::new("native mul", K), + &(¶ms, &pk, [a, b]), + |bencher, &(params, pk, inputs)| { + bencher.iter(|| { + let mut builder = + RangeCircuitBuilder::prover(config_params.clone(), break_points.clone()); + // do the computation + mul_bench(builder.main(0), inputs); + + gen_proof(params, pk, builder); + }) + }, + ); +} + +criterion_group! { + name = benches; + config = Criterion::default().with_profiler(PProfProfiler::new(10, Output::Flamegraph(None))); + targets = bench +} +criterion_main!(benches); diff --git a/vendor/halo2-lib/halo2-base/examples/inner_product.rs b/vendor/halo2-lib/halo2-base/examples/inner_product.rs new file mode 100644 index 00000000..c1413211 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/examples/inner_product.rs @@ -0,0 +1,39 @@ +#![cfg(feature = "test-utils")] +use halo2_base::gates::flex_gate::{GateChip, GateInstructions}; +use halo2_base::gates::RangeInstructions; +use halo2_base::halo2_proofs::{arithmetic::Field, halo2curves::bn256::Fr}; +use halo2_base::utils::testing::base_test; +use halo2_base::utils::ScalarField; +use halo2_base::{Context, QuantumCell::Existing}; +use itertools::Itertools; +use rand::rngs::OsRng; + +const K: u32 = 19; + +fn inner_prod_bench( + ctx: &mut Context, + gate: &GateChip, + a: Vec, + b: Vec, +) { + assert_eq!(a.len(), b.len()); + let a = ctx.assign_witnesses(a); + let b = ctx.assign_witnesses(b); + + for _ in 0..(1 << K) / 16 - 10 { + gate.inner_product(ctx, a.clone(), b.clone().into_iter().map(Existing)); + } +} + +fn main() { + base_test().k(12).bench_builder( + (vec![Fr::ZERO; 5], vec![Fr::ZERO; 5]), + ( + (0..5).map(|_| Fr::random(OsRng)).collect_vec(), + (0..5).map(|_| Fr::random(OsRng)).collect_vec(), + ), + |pool, range, (a, b)| { + inner_prod_bench(pool.main(), range.gate(), a, b); + }, + ); +} diff --git a/vendor/halo2-lib/halo2-base/proptest-regressions/gates/tests/prop_test.txt b/vendor/halo2-lib/halo2-base/proptest-regressions/gates/tests/prop_test.txt new file mode 100644 index 00000000..aa4e1000 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/proptest-regressions/gates/tests/prop_test.txt @@ -0,0 +1,11 @@ +# 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 8489bbcc3439950355c90ecbc92546a66e4b57eae0a3856e7a4ccb59bf74b4ce # shrinks to k = 1, len = 1, idx = 0, witness_vals = [0x0000000000000000000000000000000000000000000000000000000000000000] +cc b18c4f5e502fe36dbc2471f89a6ffb389beaf473b280e844936298ab1cf9b74e # shrinks to (k, len, idx, witness_vals) = (8, 2, 1, [0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000001]) +cc 4528fb02e7227f85116c2a16aef251b9c3b6d9c340ddb50b936c2140d7856cc4 # shrinks to inputs = ([], []) +cc 79bfe42c93b5962a38b2f831f1dd438d8381a24a6ce15bfb89a8562ce9af0a2d # shrinks to (k, len, idx, witness_vals) = (8, 62, 0, [0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000]) +cc d0e10a06108cb58995a8ae77a91b299fb6230e9e6220121c48f2488e5d199e82 # shrinks to input = (0x000000000000000000000000000000000000000000000000070a95cb0607bef9, 4096) diff --git a/vendor/halo2-lib/halo2-base/src/gates/circuit/builder.rs b/vendor/halo2-lib/halo2-base/src/gates/circuit/builder.rs new file mode 100644 index 00000000..94a24345 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/circuit/builder.rs @@ -0,0 +1,386 @@ +use std::sync::{Arc, Mutex}; + +use getset::{Getters, MutGetters, Setters}; +use itertools::Itertools; + +use crate::{ + gates::{ + circuit::CircuitBuilderStage, + flex_gate::{ + threads::{GateStatistics, MultiPhaseCoreManager, SinglePhaseCoreManager}, + MultiPhaseThreadBreakPoints, MAX_PHASE, + }, + range::RangeConfig, + RangeChip, + }, + halo2_proofs::{ + circuit::{Layouter, Region}, + plonk::{Column, Instance}, + }, + utils::ScalarField, + virtual_region::{ + copy_constraints::{CopyConstraintManager, SharedCopyConstraintManager}, + lookups::LookupAnyManager, + manager::VirtualRegionManager, + }, + AssignedValue, Context, +}; + +use super::BaseCircuitParams; + +/// Keeping the naming `RangeCircuitBuilder` for backwards compatibility. +pub type RangeCircuitBuilder = BaseCircuitBuilder; + +/// A circuit builder is a collection of virtual region managers that together assign virtual +/// regions into a single physical circuit. +/// +/// [BaseCircuitBuilder] is a circuit builder to create a circuit where the columns correspond to [super::BaseConfig]. +/// This builder can hold multiple threads, but the `Circuit` implementation only evaluates the first phase. +/// The user will have to implement a separate `Circuit` with multi-phase witness generation logic. +/// +/// This is used to manage the virtual region corresponding to [super::FlexGateConfig] and (optionally) [RangeConfig]. +/// This can be used even if only using [`GateChip`](crate::gates::flex_gate::GateChip) without [RangeChip]. +/// +/// The circuit will have `NI` public instance (aka public inputs+outputs) columns. +#[derive(Clone, Debug, Getters, MutGetters, Setters)] +pub struct BaseCircuitBuilder { + /// Virtual region for each challenge phase. These cannot be shared across threads while keeping circuit deterministic. + #[getset(get = "pub", get_mut = "pub", set = "pub")] + pub(super) core: MultiPhaseCoreManager, + /// The range lookup manager + #[getset(get = "pub", get_mut = "pub", set = "pub")] + pub(super) lookup_manager: [LookupAnyManager; MAX_PHASE], + /// Configuration parameters for the circuit shape + pub config_params: BaseCircuitParams, + /// The assigned instances to expose publicly at the end of circuit synthesis + pub assigned_instances: Vec>>, +} + +impl Default for BaseCircuitBuilder { + /// Quick start default circuit builder which can be used for MockProver, Keygen, and real prover. + /// For best performance during real proof generation, we recommend using [BaseCircuitBuilder::prover] instead. + fn default() -> Self { + Self::new(false) + } +} + +impl BaseCircuitBuilder { + /// Creates a new [BaseCircuitBuilder] with all default managers. + /// * `witness_gen_only`: + /// * If true, the builder only does witness asignments and does not store constraint information -- this should only be used for the real prover. + /// * If false, the builder also imposes constraints (selectors, fixed columns, copy constraints). Primarily used for keygen and mock prover (but can also be used for real prover). + /// + /// By default, **no** circuit configuration parameters have been set. + /// These should be set separately using `use_params`, or `use_k`, `use_lookup_bits`, and `calculate_params`. + /// + /// Upon construction, there are no public instances (aka all witnesses are private). + /// The intended usage is that _before_ calling `synthesize`, witness generation can be done to populate + /// assigned instances, which are supplied as `assigned_instances` to this struct. + /// The `Circuit` implementation for this struct will then expose these instances and constrain + /// them using the Halo2 API. + pub fn new(witness_gen_only: bool) -> Self { + let core = MultiPhaseCoreManager::new(witness_gen_only); + let lookup_manager = [(); MAX_PHASE] + .map(|_| LookupAnyManager::new(witness_gen_only, core.copy_manager.clone())); + Self { core, lookup_manager, config_params: Default::default(), assigned_instances: vec![] } + } + + /// Creates a new [MultiPhaseCoreManager] depending on the stage of circuit building. If the stage is [CircuitBuilderStage::Prover], the [MultiPhaseCoreManager] is used for witness generation only. + pub fn from_stage(stage: CircuitBuilderStage) -> Self { + Self::new(stage.witness_gen_only()).unknown(stage == CircuitBuilderStage::Keygen) + } + + /// Creates a new [BaseCircuitBuilder] with a pinned circuit configuration given by `config_params` and `break_points`. + pub fn prover( + config_params: BaseCircuitParams, + break_points: MultiPhaseThreadBreakPoints, + ) -> Self { + Self::new(true).use_params(config_params).use_break_points(break_points) + } + + /// Sets the copy manager to the given one in all shared references. + pub fn set_copy_manager(&mut self, copy_manager: SharedCopyConstraintManager) { + for lm in &mut self.lookup_manager { + lm.set_copy_manager(copy_manager.clone()); + } + self.core.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 + } + + /// Deep clone of `self`, where the underlying object of shared references in [SharedCopyConstraintManager] and [LookupAnyManager] are cloned. + pub fn deep_clone(&self) -> Self { + let cm: CopyConstraintManager = self.core.copy_manager.lock().unwrap().clone(); + let cm_ref = Arc::new(Mutex::new(cm)); + let mut clone = self.clone().use_copy_manager(cm_ref.clone()); + for lm in &mut clone.lookup_manager { + *lm = lm.deep_clone(cm_ref.clone()); + } + clone + } + + /// The log_2 size of the lookup table, if using. + pub fn lookup_bits(&self) -> Option { + self.config_params.lookup_bits + } + + /// Set lookup bits + pub fn set_lookup_bits(&mut self, lookup_bits: usize) { + self.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 + } + + /// Sets new `k` = log2 of domain + pub fn set_k(&mut self, k: usize) { + self.config_params.k = k; + } + + /// Returns new with `k` set + pub fn use_k(mut self, k: usize) -> Self { + self.set_k(k); + self + } + + /// Set the number of instance columns. This resizes `self.assigned_instances`. + pub fn set_instance_columns(&mut self, num_instance_columns: usize) { + self.config_params.num_instance_columns = num_instance_columns; + while self.assigned_instances.len() < num_instance_columns { + self.assigned_instances.push(vec![]); + } + assert_eq!(self.assigned_instances.len(), num_instance_columns); + } + + /// Returns new with `self.assigned_instances` resized to specified number of instance columns. + pub fn use_instance_columns(mut self, num_instance_columns: usize) -> Self { + self.set_instance_columns(num_instance_columns); + self + } + + /// Set config params + pub fn set_params(&mut self, params: BaseCircuitParams) { + self.set_instance_columns(params.num_instance_columns); + self.config_params = params; + } + + /// Returns new with config params + pub fn use_params(mut self, params: BaseCircuitParams) -> Self { + self.set_params(params); + self + } + + /// The break points of the circuit. + pub fn break_points(&self) -> MultiPhaseThreadBreakPoints { + self.core + .phase_manager + .iter() + .map(|pm| pm.break_points.borrow().as_ref().expect("break points not set").clone()) + .collect() + } + + /// Sets the break points of the circuit. + pub fn set_break_points(&mut self, break_points: MultiPhaseThreadBreakPoints) { + if break_points.is_empty() { + return; + } + self.core.touch(break_points.len() - 1); + for (pm, bp) in self.core.phase_manager.iter().zip_eq(break_points) { + *pm.break_points.borrow_mut() = Some(bp); + } + } + + /// Returns new with break points + pub fn use_break_points(mut self, break_points: MultiPhaseThreadBreakPoints) -> Self { + self.set_break_points(break_points); + self + } + + /// Returns if the circuit is only used for witness generation. + pub fn witness_gen_only(&self) -> bool { + self.core.witness_gen_only() + } + + /// Creates a new [MultiPhaseCoreManager] 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(mut self, use_unknown: bool) -> Self { + self.core = self.core.unknown(use_unknown); + self + } + + /// Clears state and copies, effectively resetting the circuit builder. + pub fn clear(&mut self) { + self.core.clear(); + for lm in &mut self.lookup_manager { + lm.clear(); + } + self.assigned_instances.iter_mut().for_each(|c| c.clear()); + } + + /// Returns a mutable reference to the [Context] of a gate thread. Spawns a new thread for the given phase, if none exists. + /// * `phase`: The challenge phase (as an index) of the gate thread. + pub fn main(&mut self, phase: usize) -> &mut Context { + self.core.main(phase) + } + + /// Returns [SinglePhaseCoreManager] with the virtual region with all core threads in the given phase. + pub fn pool(&mut self, phase: usize) -> &mut SinglePhaseCoreManager { + self.core.phase_manager.get_mut(phase).unwrap() + } + + /// 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, phase: usize) -> &mut Context { + self.core.new_thread(phase) + } + + /// Returns some statistics about the virtual region. + pub fn statistics(&self) -> RangeStatistics { + let gate = self.core.statistics(); + let total_lookup_advice_per_phase = self.total_lookup_advice_per_phase(); + RangeStatistics { gate, total_lookup_advice_per_phase } + } + + fn total_lookup_advice_per_phase(&self) -> Vec { + self.lookup_manager.iter().map(|lm| lm.total_rows()).collect() + } + + /// Auto-calculates configuration parameters for the circuit and sets them. + /// + /// * `k`: The number of in the circuit (i.e. numeber of rows = 2k) + /// * `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. + /// * `lookup_bits`: The fixed lookup table will consist of [0, 2lookup_bits) + pub fn calculate_params(&mut self, minimum_rows: Option) -> BaseCircuitParams { + let k = self.config_params.k; + let ni = self.config_params.num_instance_columns; + assert_ne!(k, 0, "k must be set"); + let max_rows = (1 << k) - minimum_rows.unwrap_or(0); + let gate_params = self.core.calculate_params(k, minimum_rows); + let total_lookup_advice_per_phase = self.total_lookup_advice_per_phase(); + let num_lookup_advice_per_phase = total_lookup_advice_per_phase + .iter() + .map(|count| count.div_ceil(max_rows)) + .collect::>(); + + let params = BaseCircuitParams { + k: gate_params.k, + num_advice_per_phase: gate_params.num_advice_per_phase, + num_fixed: gate_params.num_fixed, + num_lookup_advice_per_phase, + lookup_bits: self.lookup_bits(), + num_instance_columns: ni, + }; + self.config_params = params.clone(); + #[cfg(feature = "display")] + { + println!("Total range check advice cells to lookup per phase: {total_lookup_advice_per_phase:?}"); + log::debug!("Auto-calculated config params:\n {params:#?}"); + } + params + } + + /// Copies `assigned_instances` to the instance columns. Should only be called at the very end of + /// `synthesize` after virtual `assigned_instances` have been assigned to physical circuit. + pub fn assign_instances( + &self, + instance_columns: &[Column], + mut layouter: impl Layouter, + ) { + if !self.core.witness_gen_only() { + // expose public instances + for (instances, instance_col) in self.assigned_instances.iter().zip_eq(instance_columns) + { + for (i, instance) in instances.iter().enumerate() { + let cell = instance.cell.unwrap(); + let copy_manager = self.core.copy_manager.lock().unwrap(); + let cell = + copy_manager.assigned_advices.get(&cell).expect("instance not assigned"); + layouter.constrain_instance(*cell, *instance_col, i); + } + } + } + } + + /// Creates a new [RangeChip] sharing the same [LookupAnyManager]s as `self`. + pub fn range_chip(&self) -> RangeChip { + RangeChip::new( + self.config_params.lookup_bits.expect("lookup bits not set"), + self.lookup_manager.clone(), + ) + } + + /// Copies the queued cells to be range looked up in phase `phase` to special advice lookup columns + /// using [LookupAnyManager]. + /// + /// ## Special case + /// Just for [RangeConfig], we have special handling for the case where there is a single (physical) + /// advice column in [super::FlexGateConfig]. In this case, `RangeConfig` does not create extra lookup advice columns, + /// the single advice column has lookup enabled, and there is a selector to toggle when lookup should + /// be turned on. + pub fn assign_lookups_in_phase( + &self, + config: &RangeConfig, + region: &mut Region, + phase: usize, + ) { + let lookup_manager = self.lookup_manager.get(phase).expect("too many phases"); + if lookup_manager.total_rows() == 0 { + return; + } + if let Some(q_lookup) = config.q_lookup.get(phase).and_then(|q| *q) { + // if q_lookup is Some, that means there should be a single advice column and it has lookup enabled + assert_eq!(config.gate.basic_gates[phase].len(), 1); + if !self.witness_gen_only() { + let cells_to_lookup = lookup_manager.cells_to_lookup.lock().unwrap(); + for advice in cells_to_lookup.iter().flat_map(|(_, advices)| advices) { + let cell = advice[0].cell.as_ref().unwrap(); + let copy_manager = self.core.copy_manager.lock().unwrap(); + let acell = copy_manager.assigned_advices[cell]; + assert_eq!( + acell.column, + config.gate.basic_gates[phase][0].value.into(), + "lookup column does not match" + ); + q_lookup.enable(region, acell.row_offset).unwrap(); + } + } + } else { + let lookup_cols = config + .lookup_advice + .get(phase) + .expect("No special lookup advice columns") + .iter() + .map(|c| [*c]) + .collect_vec(); + lookup_manager.assign_raw(&lookup_cols, region); + } + let _ = lookup_manager.assigned.set(()); + } +} + +/// Basic statistics +pub struct RangeStatistics { + /// Number of advice cells for the basic gate and total constants used + pub gate: GateStatistics, + /// Total special advice cells that need to be looked up, per phase + pub total_lookup_advice_per_phase: Vec, +} + +impl AsRef> for BaseCircuitBuilder { + fn as_ref(&self) -> &BaseCircuitBuilder { + self + } +} + +impl AsMut> for BaseCircuitBuilder { + fn as_mut(&mut self) -> &mut BaseCircuitBuilder { + self + } +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/circuit/mod.rs b/vendor/halo2-lib/halo2-base/src/gates/circuit/mod.rs new file mode 100644 index 00000000..8b3e6f60 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/circuit/mod.rs @@ -0,0 +1,229 @@ +use serde::{Deserialize, Serialize}; + +use crate::utils::ScalarField; +use crate::{ + halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector}, + }, + virtual_region::manager::VirtualRegionManager, +}; + +use self::builder::BaseCircuitBuilder; + +use super::flex_gate::{FlexGateConfig, FlexGateConfigParams}; +use super::range::RangeConfig; + +/// Module that helps auto-build circuits +pub mod builder; + +/// A struct defining the configuration parameters for a halo2-base circuit +/// - this is used to configure [BaseConfig]. +#[derive(Clone, Default, Debug, Hash, Serialize, Deserialize)] +pub struct BaseCircuitParams { + // Keeping FlexGateConfigParams expanded for backwards compatibility + /// Specifies the number of rows in the circuit to be 2k + pub k: usize, + /// The number of advice columns per phase + pub num_advice_per_phase: Vec, + /// The number of fixed columns + pub num_fixed: usize, + /// The number of bits that can be ranged checked using a special lookup table with values [0, 2lookup_bits), if using. + /// The number of special advice columns that have range lookup enabled per phase + pub num_lookup_advice_per_phase: Vec, + /// This is `None` if no lookup table is used. + pub lookup_bits: Option, + /// Number of public instance columns + #[serde(default)] + pub num_instance_columns: usize, +} + +impl BaseCircuitParams { + fn gate_params(&self) -> FlexGateConfigParams { + FlexGateConfigParams { + k: self.k, + num_advice_per_phase: self.num_advice_per_phase.clone(), + num_fixed: self.num_fixed, + } + } +} + +/// Configuration with [`BaseConfig`] with `NI` public instance columns. +#[derive(Clone, Debug)] +pub struct BaseConfig { + /// The underlying private gate/range configuration + pub base: MaybeRangeConfig, + /// The public instance column + pub instance: Vec>, +} + +/// Smart Halo2 circuit config that has different variants depending on whether you need range checks or not. +/// The difference is that to enable range checks, the Halo2 config needs to add a lookup table. +#[derive(Clone, Debug)] +pub enum MaybeRangeConfig { + /// Config for a circuit that does not use range checks + WithoutRange(FlexGateConfig), + /// Config for a circuit that does use range checks + WithRange(RangeConfig), +} + +impl BaseConfig { + /// Generates a new `BaseConfig` depending on `params`. + /// - It will generate a `RangeConfig` is `params` has `lookup_bits` not None **and** `num_lookup_advice_per_phase` are not all empty or zero (i.e., if `params` indicates that the circuit actually requires a lookup table). + /// - Otherwise it will generate a `FlexGateConfig`. + pub fn configure(meta: &mut ConstraintSystem, params: BaseCircuitParams) -> Self { + let total_lookup_advice_cols = params.num_lookup_advice_per_phase.iter().sum::(); + let base = if params.lookup_bits.is_some() && total_lookup_advice_cols != 0 { + // We only add a lookup table if lookup bits is not None + MaybeRangeConfig::WithRange(RangeConfig::configure( + meta, + params.gate_params(), + ¶ms.num_lookup_advice_per_phase, + params.lookup_bits.unwrap(), + )) + } else { + MaybeRangeConfig::WithoutRange(FlexGateConfig::configure(meta, params.gate_params())) + }; + let instance = (0..params.num_instance_columns) + .map(|_| { + let inst = meta.instance_column(); + meta.enable_equality(inst); + inst + }) + .collect(); + Self { base, instance } + } + + /// Returns the inner [`FlexGateConfig`] + pub fn gate(&self) -> &FlexGateConfig { + match &self.base { + MaybeRangeConfig::WithoutRange(config) => config, + MaybeRangeConfig::WithRange(config) => &config.gate, + } + } + + /// Returns the fixed columns for constants + pub fn constants(&self) -> &Vec> { + match &self.base { + MaybeRangeConfig::WithoutRange(config) => &config.constants, + MaybeRangeConfig::WithRange(config) => &config.gate.constants, + } + } + + /// Returns a slice of the selector column to enable lookup -- this is only in the situation where there is a single advice column of any kind -- per phase + /// Returns empty slice if there are no lookups enabled. + pub fn q_lookup(&self) -> &[Option] { + match &self.base { + MaybeRangeConfig::WithoutRange(_) => &[], + MaybeRangeConfig::WithRange(config) => &config.q_lookup, + } + } + + /// Updates the number of usable rows in the circuit. Used if you mutate [ConstraintSystem] after `BaseConfig::configure` is called. + pub fn set_usable_rows(&mut self, usable_rows: usize) { + match &mut self.base { + MaybeRangeConfig::WithoutRange(config) => config.max_rows = usable_rows, + MaybeRangeConfig::WithRange(config) => config.gate.max_rows = usable_rows, + } + } + + /// Initialization of config at very beginning of `synthesize`. + /// Loads fixed lookup table, if using. + pub fn initialize(&self, layouter: &mut impl Layouter) { + // only load lookup table if we are actually doing lookups + if let MaybeRangeConfig::WithRange(config) = &self.base { + config.load_lookup_table(layouter).expect("load lookup table should not fail"); + } + } +} + +impl Circuit for BaseCircuitBuilder { + type Config = BaseConfig; + type FloorPlanner = SimpleFloorPlanner; + type Params = BaseCircuitParams; + + fn params(&self) -> Self::Params { + self.config_params.clone() + } + + /// Creates a new instance of the [BaseCircuitBuilder] without witnesses by setting the witness_gen_only flag to false + fn without_witnesses(&self) -> Self { + unimplemented!() + } + + /// Configures a new circuit using [`BaseCircuitParams`] + fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config { + BaseConfig::configure(meta, params) + } + + fn configure(_: &mut ConstraintSystem) -> Self::Config { + unreachable!("You must use configure_with_params"); + } + + /// Performs the actual computation on the circuit (e.g., witness generation), populating the lookup table and filling in all the advice values for a particular proof. + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + // only load lookup table if we are actually doing lookups + if let MaybeRangeConfig::WithRange(config) = &config.base { + config.load_lookup_table(&mut layouter).expect("load lookup table should not fail"); + } + // Only FirstPhase (phase 0) + layouter + .assign_region( + || "BaseCircuitBuilder generated circuit", + |mut region| { + let usable_rows = config.gate().max_rows; + self.core.phase_manager[0].assign_raw( + &(config.gate().basic_gates[0].clone(), usable_rows), + &mut region, + ); + // Only assign cells to lookup if we're sure we're doing range lookups + if let MaybeRangeConfig::WithRange(config) = &config.base { + self.assign_lookups_in_phase(config, &mut region, 0); + } + // Impose equality constraints + if !self.core.witness_gen_only() { + self.core.copy_manager.assign_raw(config.constants(), &mut region); + } + Ok(()) + }, + ) + .unwrap(); + + self.assign_instances(&config.instance, layouter.namespace(|| "expose")); + Ok(()) + } +} + +/// Defines stage of circuit building. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CircuitBuilderStage { + /// Keygen phase + Keygen, + /// Prover Circuit + Prover, + /// Mock Circuit + Mock, +} + +impl CircuitBuilderStage { + /// Returns true if the circuit is used for witness generation only. + pub fn witness_gen_only(&self) -> bool { + matches!(self, CircuitBuilderStage::Prover) + } +} + +impl AsRef> for BaseConfig { + fn as_ref(&self) -> &BaseConfig { + self + } +} + +impl AsMut> for BaseConfig { + fn as_mut(&mut self) -> &mut BaseConfig { + self + } +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/flex_gate/mod.rs b/vendor/halo2-lib/halo2-base/src/gates/flex_gate/mod.rs new file mode 100644 index 00000000..f78b11b7 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/flex_gate/mod.rs @@ -0,0 +1,1266 @@ +use crate::{ + halo2_proofs::{ + plonk::{ + Advice, Assigned, Column, ConstraintSystem, FirstPhase, Fixed, SecondPhase, Selector, + ThirdPhase, + }, + poly::Rotation, + }, + utils::ScalarField, + AssignedValue, Context, + QuantumCell::{self, Constant, Existing, Witness, WitnessFraction}, +}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use std::{ + iter::{self}, + marker::PhantomData, +}; + +pub mod threads; + +/// Vector of thread advice column break points +pub type ThreadBreakPoints = Vec; +/// Vector of vectors tracking the thread break points across different halo2 phases +pub type MultiPhaseThreadBreakPoints = Vec; + +/// The maximum number of phases in halo2. +pub(super) const MAX_PHASE: usize = 3; + +/// # Vertical Gate Strategy: +/// `q_0 * (a + b * c - d) = 0` +/// where +/// * `a = value[0], b = value[1], c = value[2], d = value[3]` +/// * `q = q_enable[0]` +/// * `q` is either 0 or 1 so this is just a simple selector +/// +/// We chose `a + b * c` instead of `a * b + c` to allow "chaining" of gates, i.e., the output of one gate because `a` in the next gate. +/// +/// A configuration for a basic gate chip describing the selector, and advice column values. +#[derive(Clone, Debug)] +pub struct BasicGateConfig { + /// [Selector] column that stores selector values that are used to activate gates in the advice column. + // `q_enable` will have either length 1 or 2, depending on the strategy + pub q_enable: Selector, + /// [Column] that stores the advice values of the gate. + pub value: Column, + /// Marker for the field type. + _marker: PhantomData, +} + +impl BasicGateConfig { + /// Constructor + pub fn new(q_enable: Selector, value: Column) -> Self { + Self { q_enable, value, _marker: PhantomData } + } + + /// Instantiates a new [BasicGateConfig]. + /// + /// Assumes `phase` is in the range [0, MAX_PHASE). + /// * `meta`: [ConstraintSystem] used for the gate + /// * `phase`: The phase to add the gate to + pub fn configure(meta: &mut ConstraintSystem, phase: u8) -> Self { + let value = match phase { + 0 => meta.advice_column_in(FirstPhase), + 1 => meta.advice_column_in(SecondPhase), + 2 => meta.advice_column_in(ThirdPhase), + _ => panic!("Currently BasicGate only supports {MAX_PHASE} phases"), + }; + meta.enable_equality(value); + + let q_enable = meta.selector(); + + let config = Self { q_enable, value, _marker: PhantomData }; + config.create_gate(meta); + config + } + + /// Wrapper for [ConstraintSystem].create_gate(name, meta) creates a gate form [q * (a + b * c - out)]. + /// * `meta`: [ConstraintSystem] used for the gate + fn create_gate(&self, meta: &mut ConstraintSystem) { + meta.create_gate("1 column a + b * c = out", |meta| { + let q = meta.query_selector(self.q_enable); + + let a = meta.query_advice(self.value, Rotation::cur()); + let b = meta.query_advice(self.value, Rotation::next()); + let c = meta.query_advice(self.value, Rotation(2)); + let out = meta.query_advice(self.value, Rotation(3)); + + vec![q * (a + b * c - out)] + }) + } +} + +/// A Config struct defining the parameters for [FlexGateConfig] +#[derive(Clone, Default, Debug, Serialize, Deserialize)] +pub struct FlexGateConfigParams { + /// Specifies the number of rows in the circuit to be 2k + pub k: usize, + /// The number of advice columns per phase + pub num_advice_per_phase: Vec, + /// The number of fixed columns + pub num_fixed: usize, +} + +/// Defines a configuration for a flex gate chip describing the selector, and advice column values for the chip. +#[derive(Clone, Debug)] +pub struct FlexGateConfig { + /// A [Vec] of [BasicGateConfig] that define gates for each halo2 phase. + pub basic_gates: Vec>>, + /// A [Vec] of [Fixed] [Column]s for allocating constant values. + pub constants: Vec>, + /// Max number of usable rows in the circuit. + pub max_rows: usize, +} + +impl FlexGateConfig { + /// Generates a new [FlexGateConfig] + /// + /// * `meta`: [ConstraintSystem] of the circuit + /// * `params`: see [FlexGateConfigParams] + pub fn configure(meta: &mut ConstraintSystem, params: FlexGateConfigParams) -> Self { + // create fixed (constant) columns and enable equality constraints + let mut constants = Vec::with_capacity(params.num_fixed); + for _i in 0..params.num_fixed { + let c = meta.fixed_column(); + meta.enable_equality(c); + // meta.enable_constant(c); + constants.push(c); + } + + let mut basic_gates = vec![]; + for (phase, &num_columns) in params.num_advice_per_phase.iter().enumerate() { + let config = + (0..num_columns).map(|_| BasicGateConfig::configure(meta, phase as u8)).collect(); + basic_gates.push(config); + } + log::debug!("Poisoned rows after FlexGateConfig::configure {}", meta.minimum_rows()); + Self { + basic_gates, + constants, + // Warning: this needs to be updated if you create more advice columns after this `FlexGateConfig` is created + max_rows: (1 << params.k) - meta.minimum_rows(), + } + } +} + +/// Trait that defines basic arithmetic operations for a gate. +pub trait GateInstructions { + /// Returns a slice of the [ScalarField] field elements 2^i for i in 0..F::NUM_BITS. + fn pow_of_two(&self) -> &[F]; + + /// Constrains and returns `a + b * 1 = out`. + /// + /// Defines a vertical gate of form | a | b | 1 | a + b | where (a + b) = out. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] value + /// * `b`: [QuantumCell] value to add to 'a` + fn add( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + ) -> AssignedValue { + let a = a.into(); + let b = b.into(); + let out_val = *a.value() + b.value(); + ctx.assign_region_last([a, b, Constant(F::ONE), Witness(out_val)], [0]) + } + + /// Constrains and returns `out = a + 1`. + /// + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] value + fn inc(&self, ctx: &mut Context, a: impl Into>) -> AssignedValue { + self.add(ctx, a, Constant(F::ONE)) + } + + /// Constrains and returns `a + b * (-1) = out`. + /// + /// Defines a vertical gate of form | a - b | b | 1 | a |, where (a - b) = out. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] value + /// * `b`: [QuantumCell] value to subtract from 'a' + fn sub( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + ) -> AssignedValue { + let a = a.into(); + let b = b.into(); + let out_val = *a.value() - b.value(); + // slightly better to not have to compute -F::ONE since F::ONE is cached + ctx.assign_region([Witness(out_val), b, Constant(F::ONE), a], [0]); + ctx.get(-4) + } + + /// Constrains and returns `out = a - 1`. + /// + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] value + fn dec(&self, ctx: &mut Context, a: impl Into>) -> AssignedValue { + self.sub(ctx, a, Constant(F::ONE)) + } + + /// Constrains and returns `a - b * c = out`. + /// + /// Defines a vertical gate of form | a - b * c | b | c | a |, where (a - b * c) = out. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] value to subtract 'b * c' from + /// * `b`: [QuantumCell] value + /// * `c`: [QuantumCell] value + fn sub_mul( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + c: impl Into>, + ) -> AssignedValue { + let a = a.into(); + let b = b.into(); + let c = c.into(); + let out_val = *a.value() - *b.value() * c.value(); + ctx.assign_region_last([Witness(out_val), b, c, a], [0]); + ctx.get(-4) + } + + /// Constrains and returns `a * (-1) = out`. + /// + /// Defines a vertical gate of form | a | -a | 1 | 0 |, where (-a) = out. + /// * `ctx`: the [Context] to add the constraints to + /// * `a`: [QuantumCell] value to negate + fn neg(&self, ctx: &mut Context, a: impl Into>) -> AssignedValue { + let a = a.into(); + let out_val = -*a.value(); + ctx.assign_region([a, Witness(out_val), Constant(F::ONE), Constant(F::ZERO)], [0]); + ctx.get(-3) + } + + /// Constrains and returns `0 + a * b = out`. + /// + /// Defines a vertical gate of form | 0 | a | b | a * b |, where (a * b) = out. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] value + /// * `b`: [QuantumCell] value to multiply 'a' by + fn mul( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + ) -> AssignedValue { + let a = a.into(); + let b = b.into(); + let out_val = *a.value() * b.value(); + ctx.assign_region_last([Constant(F::ZERO), a, b, Witness(out_val)], [0]) + } + + /// Constrains and returns `a * b + c = out`. + /// + /// Defines a vertical gate of form | c | a | b | a * b + c |, where (a * b + c) = out. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] value + /// * `b`: [QuantumCell] value to multiply 'a' by + /// * `c`: [QuantumCell] value to add to 'a * b' + fn mul_add( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + c: impl Into>, + ) -> AssignedValue { + let a = a.into(); + let b = b.into(); + let c = c.into(); + let out_val = *a.value() * b.value() + c.value(); + ctx.assign_region_last([c, a, b, Witness(out_val)], [0]) + } + + /// Constrains and returns `(1 - a) * b = b - a * b`. + /// + /// Defines a vertical gate of form | (1 - a) * b | a | b | b |, where (1 - a) * b = out. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] value + /// * `b`: [QuantumCell] value to multiply 'a' by + fn mul_not( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + ) -> AssignedValue { + let a = a.into(); + let b = b.into(); + let out_val = (F::ONE - a.value()) * b.value(); + ctx.assign_region_smart([Witness(out_val), a, b, b], [0], [(2, 3)], []); + ctx.get(-4) + } + + /// Constrains that x is boolean (e.g. 0 or 1). + /// + /// Defines a vertical gate of form | 0 | x | x | x |. + /// * `ctx`: [Context] to add the constraints to + /// * `x`: [QuantumCell] value to constrain + fn assert_bit(&self, ctx: &mut Context, x: AssignedValue) { + ctx.assign_region([Constant(F::ZERO), Existing(x), Existing(x), Existing(x)], [0]); + } + + /// Constrains and returns a / b = out. + /// + /// Defines a vertical gate of form | 0 | a / b | b | a |, where a / b = out. + /// + /// Assumes `b != 0`. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] value + /// * `b`: [QuantumCell] value to divide 'a' by + fn div_unsafe( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + ) -> AssignedValue { + let a = a.into(); + let b = b.into(); + // TODO: if really necessary, make `c` of type `Assigned` + // this would require the API using `Assigned` instead of `F` everywhere, so leave as last resort + let c = b.value().invert().unwrap() * a.value(); + ctx.assign_region([Constant(F::ZERO), Witness(c), b, a], [0]); + ctx.get(-3) + } + + /// Constrains that `a` is equal to `constant` value. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] value + /// * `constant`: constant value to constrain `a` to be equal to + fn assert_is_const(&self, ctx: &mut Context, a: &AssignedValue, constant: &F) { + if !ctx.witness_gen_only { + ctx.copy_manager.lock().unwrap().constant_equalities.push((*constant, a.cell.unwrap())); + } + } + + /// Constrains and returns the inner product of ``. + /// + /// Assumes 'a' and 'b' are the same length. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: Iterator of [QuantumCell] values + /// * `b`: Iterator of [QuantumCell] values to take inner product of `a` by + fn inner_product( + &self, + ctx: &mut Context, + a: impl IntoIterator, + b: impl IntoIterator>, + ) -> AssignedValue + where + QA: Into>; + + /// Returns the inner product of `` and the last element of `a` after it has been assigned. + /// + /// **NOT** encouraged for general usage. + /// This is a low-level function, where you want to avoid first assigning `a` and then copying the last element into the + /// correct cell for this computation. + /// + /// Assumes 'a' and 'b' are the same length. + /// * `ctx`: [Context] of the circuit + /// * `a`: Iterator of [QuantumCell]s + /// * `b`: Iterator of [QuantumCell]s to take inner product of `a` by + fn inner_product_left_last( + &self, + ctx: &mut Context, + a: impl IntoIterator, + b: impl IntoIterator>, + ) -> (AssignedValue, AssignedValue) + where + QA: Into>; + + /// Returns `(, a_assigned)`. See `inner_product` for more details. + /// + /// **NOT** encouraged for general usage. + /// This is a low-level function, useful for when you want to simultaneously compute an inner product while assigning + /// private witnesses for the first time. This avoids first assigning `a` and then copying into the correct cells + /// for this computation. We do not return the assignments of `a` in `inner_product` as an optimization to avoid + /// the memory allocation of having to collect the vectors. + /// + /// Assumes 'a' and 'b' are the same length. + fn inner_product_left( + &self, + ctx: &mut Context, + a: impl IntoIterator, + b: impl IntoIterator>, + ) -> (AssignedValue, Vec>) + where + QA: Into>; + + /// Calculates and constrains the inner product. + /// + /// Returns the assignment trace where `output[i]` has the running sum `sum_{j=0..=i} a[j] * b[j]`. + /// + /// Assumes 'a' and 'b' are the same length. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: Iterator of [QuantumCell] values + /// * `b`: Iterator of [QuantumCell] values to calculate the partial sums of the inner product of `a` by. + fn inner_product_with_sums<'thread, QA>( + &self, + ctx: &'thread mut Context, + a: impl IntoIterator, + b: impl IntoIterator>, + ) -> Box> + 'thread> + where + QA: Into>; + + /// Constrains and returns the sum of [QuantumCell]'s in iterator `a`. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: Iterator of [QuantumCell] values to sum + fn sum(&self, ctx: &mut Context, a: impl IntoIterator) -> AssignedValue + where + Q: Into>, + { + let mut a = a.into_iter().peekable(); + let start = a.next(); + if start.is_none() { + return ctx.load_zero(); + } + let start = start.unwrap().into(); + if a.peek().is_none() { + return ctx.assign_region_last([start], []); + } + let (len, hi) = a.size_hint(); + assert_eq!(Some(len), hi); + + let mut sum = *start.value(); + let cells = iter::once(start).chain(a.flat_map(|a| { + let a = a.into(); + sum += a.value(); + [a, Constant(F::ONE), Witness(sum)] + })); + ctx.assign_region_last(cells, (0..len).map(|i| 3 * i as isize)) + } + + /// Calculates and constrains the sum of the elements of `a`. + /// + /// Returns the assignment trace where `output[i]` has the running sum `sum_{j=0..=i} a[j]`. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: Iterator of [QuantumCell] values to sum + fn partial_sums<'thread, Q>( + &self, + ctx: &'thread mut Context, + a: impl IntoIterator, + ) -> Box> + 'thread> + where + Q: Into>, + { + let mut a = a.into_iter().peekable(); + let start = a.next(); + if start.is_none() { + return Box::new(iter::once(ctx.load_zero())); + } + let start = start.unwrap().into(); + if a.peek().is_none() { + return Box::new(iter::once(ctx.assign_region_last([start], []))); + } + let (len, hi) = a.size_hint(); + assert_eq!(Some(len), hi); + + let mut sum = *start.value(); + let cells = iter::once(start).chain(a.flat_map(|a| { + let a = a.into(); + sum += a.value(); + [a, Constant(F::ONE), Witness(sum)] + })); + ctx.assign_region(cells, (0..len).map(|i| 3 * i as isize)); + Box::new((0..=len).rev().map(|i| ctx.get(-1 - 3 * (i as isize)))) + } + + /// Calculates and constrains the accumulated product of 'a' and 'b' i.e. `x_i = b_1 * (a_1...a_{i - 1}) + /// + b_2 * (a_2...a_{i - 1}) + /// + ... + /// + b_i` + /// + /// Returns the assignment trace where `output[i]` is the running accumulated product x_i. + /// + /// Assumes 'a' and 'b' are the same length. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: Iterator of [QuantumCell] values + /// * `b`: Iterator of [QuantumCell] values to take the accumulated product of `a` by + fn accumulated_product( + &self, + ctx: &mut Context, + a: impl IntoIterator, + b: impl IntoIterator, + ) -> Vec> + where + QA: Into>, + QB: Into>, + { + let mut b = b.into_iter(); + let mut a = a.into_iter(); + let b_first = b.next(); + if let Some(b_first) = b_first { + let b_first = ctx.assign_region_last([b_first], []); + std::iter::successors(Some(b_first), |x| { + a.next().zip(b.next()).map(|(a, b)| self.mul_add(ctx, Existing(*x), a, b)) + }) + .collect() + } else { + vec![] + } + } + + /// Constrains and returns the sum of products of `coeff * (a * b)` defined in `values` plus a variable `var` e.g. + /// `x = var + values[0].0 * (values[0].1 * values[0].2) + values[1].0 * (values[1].1 * values[1].2) + ... + values[n].0 * (values[n].1 * values[n].2)`. + /// * `ctx`: [Context] to add the constraints to. + /// * `values`: Iterator of tuples `(coeff, a, b)` where `coeff` is a field element, `a` and `b` are [QuantumCell]'s. + /// * `var`: [QuantumCell] that represents the value of a variable added to the sum. + fn sum_products_with_coeff_and_var( + &self, + ctx: &mut Context, + values: impl IntoIterator, QuantumCell)>, + var: QuantumCell, + ) -> AssignedValue; + + /// Constrains and returns `a || b`, assuming `a` and `b` are boolean. + /// + /// Defines a vertical gate of form `| 1 - b | 1 | b | 1 | b | a | 1 - b | out |`, where `out = a + b - a * b`. + /// * `ctx`: [Context] to add the constraints to. + /// * `a`: [QuantumCell] that contains a boolean value. + /// * `b`: [QuantumCell] that contains a boolean value. + fn or( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + ) -> AssignedValue { + let a = a.into(); + let b = b.into(); + let not_b_val = F::ONE - b.value(); + let out_val = *a.value() + b.value() - *a.value() * b.value(); + let cells = [ + Witness(not_b_val), + Constant(F::ONE), + b, + Constant(F::ONE), + b, + a, + Witness(not_b_val), + Witness(out_val), + ]; + ctx.assign_region_smart(cells, [0, 4], [(0, 6), (2, 4)], []); + ctx.last().unwrap() + } + + /// Constrains and returns `a & b`, assumeing `a` and `b` are boolean. + /// + /// Defines a vertical gate of form | 0 | a | b | out |, where out = a * b. + /// * `ctx`: [Context] to add the constraints to. + /// * `a`: [QuantumCell] that contains a boolean value. + /// * `b`: [QuantumCell] that contains a boolean value. + fn and( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + ) -> AssignedValue { + self.mul(ctx, a, b) + } + + /// Constrains and returns `!a` assumeing `a` is boolean. + /// + /// Defines a vertical gate of form | 1 - a | a | 1 | 1 |, where 1 - a = out. + /// * `ctx`: [Context] to add the constraints to. + /// * `a`: [QuantumCell] that contains a boolean value. + fn not(&self, ctx: &mut Context, a: impl Into>) -> AssignedValue { + self.sub(ctx, Constant(F::ONE), a) + } + + /// Constrains and returns `sel ? a : b` assuming `sel` is boolean. + /// + /// Defines a vertical gate of form `| 1 - sel | sel | 1 | a | 1 - sel | sel | 1 | b | out |`, where out = sel * a + (1 - sel) * b. + /// * `ctx`: [Context] to add the constraints to. + /// * `a`: [QuantumCell] that contains a boolean value. + /// * `b`: [QuantumCell] that contains a boolean value. + /// * `sel`: [QuantumCell] that contains a boolean value. + fn select( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + sel: impl Into>, + ) -> AssignedValue; + + /// Constains and returns `a || (b && c)`, assuming `a`, `b` and `c` are boolean. + /// + /// Defines a vertical gate of form `| 1 - b c | b | c | 1 | a - 1 | 1 - b c | out | a - 1 | 1 | 1 | a |`, where out = a + b * c - a * b * c. + /// * `ctx`: [Context] to add the constraints to. + /// * `a`: [QuantumCell] that contains a boolean value. + /// * `b`: [QuantumCell] that contains a boolean value. + /// * `c`: [QuantumCell] that contains a boolean value. + fn or_and( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + c: impl Into>, + ) -> AssignedValue; + + /// Constrains and returns an indicator vector from a slice of boolean values, where `output[idx] = 1` iff idx = (the number represented by `bits` in binary little endian), otherwise `output[idx] = 0`. + /// * `ctx`: [Context] to add the constraints to + /// * `bits`: slice of [QuantumCell]'s that contains boolean values + /// + /// # Assumptions + /// * `bits` is non-empty + fn bits_to_indicator( + &self, + ctx: &mut Context, + bits: &[AssignedValue], + ) -> Vec> { + let k = bits.len(); + assert!(k > 0, "bits_to_indicator: bits must be non-empty"); + + // (inv_last_bit, last_bit) = (1, 0) if bits[k - 1] = 0 + let (inv_last_bit, last_bit) = { + ctx.assign_region( + [ + Witness(F::ONE - bits[k - 1].value()), + Existing(bits[k - 1]), + Constant(F::ONE), + Constant(F::ONE), + ], + [0], + ); + (ctx.get(-4), ctx.get(-3)) + }; + let mut indicator = Vec::with_capacity(2 * (1 << k) - 2); + let mut offset = 0; + indicator.push(inv_last_bit); + indicator.push(last_bit); + for (idx, bit) in bits.iter().rev().enumerate().skip(1) { + for old_idx in 0..(1 << idx) { + // inv_prod_val = (1 - bit) * indicator[offset + old_idx] + let inv_prod_val = (F::ONE - bit.value()) * indicator[offset + old_idx].value(); + ctx.assign_region( + [ + Witness(inv_prod_val), + Existing(indicator[offset + old_idx]), + Existing(*bit), + Existing(indicator[offset + old_idx]), + ], + [0], + ); + indicator.push(ctx.get(-4)); + + // prod = bit * indicator[offset + old_idx] + let prod = self.mul(ctx, Existing(indicator[offset + old_idx]), Existing(*bit)); + indicator.push(prod); + } + offset += 1 << idx; + } + indicator.split_off((1 << k) - 2) + } + + /// Constrains and returns a [Vec] `indicator` of length `len`, where `indicator[i] == 1 if i == idx otherwise 0`, if `idx >= len` then `indicator` is all zeros. + /// + /// Assumes `len` is greater than 0. + /// * `ctx`: [Context] to add the constraints to + /// * `idx`: [QuantumCell] index of the indicator vector to be set to 1 + /// * `len`: length of the `indicator` vector + fn idx_to_indicator( + &self, + ctx: &mut Context, + idx: impl Into>, + len: usize, + ) -> Vec> { + let mut idx = idx.into(); + (0..len) + .map(|i| { + // need to use assigned idx after i > 0 so equality constraint holds + if i == 0 { + // unroll `is_zero` to make sure if `idx == Witness(_)` it is replaced by `Existing(_)` in later iterations + let x = idx.value(); + let (is_zero, inv) = if x.is_zero_vartime() { + (F::ONE, Assigned::Trivial(F::ONE)) + } else { + (F::ZERO, Assigned::Rational(F::ONE, *x)) + }; + let cells = [ + Witness(is_zero), + idx, + WitnessFraction(inv), + Constant(F::ONE), + Constant(F::ZERO), + idx, + Witness(is_zero), + Constant(F::ZERO), + ]; + ctx.assign_region_smart(cells, [0, 4], [(0, 6), (1, 5)], []); // note the two `idx` need to be constrained equal: (1, 5) + idx = Existing(ctx.get(-3)); // replacing `idx` with Existing cell so future loop iterations constrain equality of all `idx`s + ctx.get(-2) + } else { + self.is_equal(ctx, idx, Constant(F::from(i as u64))) + } + }) + .collect() + } + + /// Constrains the inner product of `a` and `indicator` and returns `a[idx]` (e.g. the value of `a` at `idx`). + /// + /// Assumes that `a` and `indicator` are non-empty iterators of the same length, the values of `indicator` are boolean, + /// and that `indicator` has at most one `1` bit. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: Iterator of [QuantumCell]'s that contains field elements + /// * `indicator`: Iterator of [AssignedValue]'s where `indicator[i] == 1` if `i == idx`, otherwise `0` + fn select_by_indicator( + &self, + ctx: &mut Context, + a: impl IntoIterator, + indicator: impl IntoIterator>, + ) -> AssignedValue + where + Q: Into>, + { + let mut sum = F::ZERO; + let a = a.into_iter(); + let (len, hi) = a.size_hint(); + assert_eq!(Some(len), hi); + + let cells = + std::iter::once(Constant(F::ZERO)).chain(a.zip(indicator).flat_map(|(a, ind)| { + let a = a.into(); + sum = if ind.value().is_zero_vartime() { sum } else { *a.value() }; + [a, Existing(ind), Witness(sum)] + })); + ctx.assign_region_last(cells, (0..len).map(|i| 3 * i as isize)) + } + + /// Constrains and returns `cells[idx]` if `idx < cells.len()`, otherwise return 0. + /// + /// Assumes that `cells` and `idx` are non-empty iterators of the same length. + /// * `ctx`: [Context] to add the constraints to + /// * `cells`: Iterator of [QuantumCell]s to select from + /// * `idx`: [QuantumCell] with value `idx` where `idx` is the index of the cell to be selected + fn select_from_idx( + &self, + ctx: &mut Context, + cells: impl IntoIterator, + idx: impl Into>, + ) -> AssignedValue + where + Q: Into>, + { + let cells = cells.into_iter(); + let (len, hi) = cells.size_hint(); + assert_eq!(Some(len), hi); + + let ind = self.idx_to_indicator(ctx, idx, len); + self.select_by_indicator(ctx, cells, ind) + } + + /// `array2d` is an array of fixed length arrays. + /// Assumes: + /// * `array2d.len() == indicator.len()` + /// * `array2d[i].len() == array2d[j].len()` for all `i,j`. + /// * the values of `indicator` are boolean and that `indicator` has at most one `1` bit. + /// * the lengths of `array2d` and `indicator` are the same. + /// + /// Returns the "dot product" of `array2d` with `indicator` as a fixed length (1d) array of length `array2d[0].len()`. + fn select_array_by_indicator( + &self, + ctx: &mut Context, + array2d: &[AR], + indicator: &[AssignedValue], + ) -> Vec> + where + AR: AsRef<[AV]>, + AV: AsRef>, + { + (0..array2d[0].as_ref().len()) + .map(|j| { + self.select_by_indicator( + ctx, + array2d.iter().map(|array_i| *array_i.as_ref()[j].as_ref()), + indicator.iter().copied(), + ) + }) + .collect() + } + + /// Constrains that a cell is equal to 0 and returns `1` if `a = 0`, otherwise `0`. + /// + /// Defines a vertical gate of form `| out | a | inv | 1 | 0 | a | out | 0 |`, where out = 1 if a = 0, otherwise out = 0. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] value to be constrained + fn is_zero(&self, ctx: &mut Context, a: AssignedValue) -> AssignedValue { + let x = a.value(); + let (is_zero, inv) = if x.is_zero_vartime() { + (F::ONE, Assigned::Trivial(F::ONE)) + } else { + (F::ZERO, Assigned::Rational(F::ONE, *x)) + }; + + let cells = [ + Witness(is_zero), + Existing(a), + WitnessFraction(inv), + Constant(F::ONE), + Constant(F::ZERO), + Existing(a), + Witness(is_zero), + Constant(F::ZERO), + ]; + ctx.assign_region_smart(cells, [0, 4], [(0, 6)], []); + ctx.get(-2) + } + + /// Constrains that the value of two cells are equal: b - a = 0, returns `1` if `a = b`, otherwise `0`. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] value + /// * `b`: [QuantumCell] value to compare to `a` + fn is_equal( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + ) -> AssignedValue { + let diff = self.sub(ctx, a, b); + self.is_zero(ctx, diff) + } + + /// Constrains and returns little-endian bit vector representation of `a`. + /// + /// Assumes `range_bits >= bit_length(a)`. + /// * `a`: [QuantumCell] of the value to convert + /// * `range_bits`: range of bits needed to represent `a` + fn num_to_bits( + &self, + ctx: &mut Context, + a: AssignedValue, + range_bits: usize, + ) -> Vec>; + + /// Constrains and computes `a``exp` where both `a, exp` are witnesses. The exponent is computed in the native field `F`. + /// + /// Constrains that `exp` has at most `max_bits` bits. + fn pow_var( + &self, + ctx: &mut Context, + a: AssignedValue, + exp: AssignedValue, + max_bits: usize, + ) -> AssignedValue; + + /// Performs and constrains Lagrange interpolation on `coords` and evaluates the resulting polynomial at `x`. + /// + /// Given pairs `coords[i] = (x_i, y_i)`, let `f` be the unique degree `len(coords) - 1` polynomial such that `f(x_i) = y_i` for all `i`. + /// + /// Returns: + /// (f(x), Prod_i(x - x_i)) + /// * `ctx`: [Context] to add the constraints to + /// * `coords`: immutable reference to a slice of tuples of [AssignedValue]s representing the points to interpolate over such that `coords[i] = (x_i, y_i)` + /// * `x`: x-coordinate of the point to evaluate `f` at + /// + /// # Assumptions + /// * `coords` is non-empty + fn lagrange_and_eval( + &self, + ctx: &mut Context, + coords: &[(AssignedValue, AssignedValue)], + x: AssignedValue, + ) -> (AssignedValue, AssignedValue) { + assert!(!coords.is_empty(), "coords should not be empty"); + let mut z = self.sub(ctx, Existing(x), Existing(coords[0].0)); + for coord in coords.iter().skip(1) { + let sub = self.sub(ctx, Existing(x), Existing(coord.0)); + z = self.mul(ctx, Existing(z), Existing(sub)); + } + let mut eval = None; + for i in 0..coords.len() { + // compute (x - x_i) * Prod_{j != i} (x_i - x_j) + let mut denom = self.sub(ctx, Existing(x), Existing(coords[i].0)); + for j in 0..coords.len() { + if i == j { + continue; + } + let sub = self.sub(ctx, coords[i].0, coords[j].0); + denom = self.mul(ctx, denom, sub); + } + // TODO: batch inversion + let is_zero = self.is_zero(ctx, denom); + self.assert_is_const(ctx, &is_zero, &F::ZERO); + + // y_i / denom + let quot = self.div_unsafe(ctx, coords[i].1, denom); + eval = if let Some(eval) = eval { + let eval = self.add(ctx, eval, quot); + Some(eval) + } else { + Some(quot) + }; + } + let out = self.mul(ctx, eval.unwrap(), z); + (out, z) + } +} + +/// A chip that implements the [GateInstructions] trait supporting basic arithmetic operations. +#[derive(Clone, Debug)] +pub struct GateChip { + /// The field elements 2^i for i in 0..F::NUM_BITS. + pub pow_of_two: Vec, + /// To avoid Montgomery conversion in `F::from` for common small numbers, we keep a cache of field elements. + pub field_element_cache: Vec, +} + +impl Default for GateChip { + fn default() -> Self { + Self::new() + } +} + +impl GateChip { + /// Returns a new [GateChip] with some precomputed values. This can be called out of circuit and has no extra dependencies. + pub fn new() -> Self { + let mut pow_of_two = Vec::with_capacity(F::NUM_BITS as usize); + let two = F::from(2); + pow_of_two.push(F::ONE); + pow_of_two.push(two); + for _ in 2..F::NUM_BITS { + pow_of_two.push(two * pow_of_two.last().unwrap()); + } + let field_element_cache = (0..1024).map(|i| F::from(i)).collect(); + + Self { pow_of_two, field_element_cache } + } + + /// Calculates and constrains the inner product of ``. + /// If the first element of `b` is `Constant(F::ONE)`, then an optimization is performed to save 3 cells. + /// + /// Returns `true` if `b` start with `Constant(F::ONE)`, and `false` otherwise. + /// + /// Assumes `a` and `b` are the same length. + /// * `ctx`: [Context] of the circuit + /// * `a`: Iterator of [QuantumCell] values + /// * `b`: Iterator of [QuantumCell] values to take inner product of `a` by + fn inner_product_simple( + &self, + ctx: &mut Context, + a: impl IntoIterator, + b: impl IntoIterator>, + ) -> bool + where + QA: Into>, + { + let mut sum; + let mut a = a.into_iter(); + let mut b = b.into_iter().peekable(); + + let b_starts_with_one = matches!(b.peek(), Some(Constant(c)) if c == &F::ONE); + let cells = if b_starts_with_one { + b.next(); + let start_a = a.next().unwrap().into(); + sum = *start_a.value(); + iter::once(start_a) + } else { + sum = F::ZERO; + iter::once(Constant(F::ZERO)) + } + .chain(a.zip(b).flat_map(|(a, b)| { + let a = a.into(); + sum += *a.value() * b.value(); + [a, b, Witness(sum)] + })); + + if ctx.witness_gen_only() { + ctx.assign_region(cells, vec![]); + } else { + let cells = cells.collect::>(); + let lo = cells.len(); + let len = lo / 3; + ctx.assign_region(cells, (0..len).map(|i| 3 * i as isize)); + }; + b_starts_with_one + } +} + +impl GateInstructions for GateChip { + /// Returns a slice of the [ScalarField] elements 2i for i in 0..F::NUM_BITS. + fn pow_of_two(&self) -> &[F] { + &self.pow_of_two + } + + /// Constrains and returns the inner product of ``. + /// If the first element of `b` is `Constant(F::ONE)`, then an optimization is performed to save 3 cells. + /// + /// Assumes 'a' and 'b' are the same length. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: Iterator of [QuantumCell] values + /// * `b`: Iterator of [QuantumCell] values to take inner product of `a` by + fn inner_product( + &self, + ctx: &mut Context, + a: impl IntoIterator, + b: impl IntoIterator>, + ) -> AssignedValue + where + QA: Into>, + { + self.inner_product_simple(ctx, a, b); + ctx.last().unwrap() + } + + /// Returns the inner product of `` and the last element of `a` after it has been assigned. + /// + /// **NOT** encouraged for general usage. + /// This is a low-level function, where you want to avoid first assigning `a` and then copying the last element into the + /// correct cell for this computation. + /// + /// Assumes 'a' and 'b' are the same length. + /// * `ctx`: [Context] of the circuit + /// * `a`: Iterator of [QuantumCell]s + /// * `b`: Iterator of [QuantumCell]s to take inner product of `a` by + fn inner_product_left_last( + &self, + ctx: &mut Context, + a: impl IntoIterator, + b: impl IntoIterator>, + ) -> (AssignedValue, AssignedValue) + where + QA: Into>, + { + let a = a.into_iter(); + let (len, hi) = a.size_hint(); + assert_eq!(Some(len), hi); + let row_offset = ctx.advice.len(); + let b_starts_with_one = self.inner_product_simple(ctx, a, b); + let a_last = if b_starts_with_one { + if len == 1 { + ctx.get(row_offset as isize) + } else { + ctx.get((row_offset + 1 + 3 * (len - 2)) as isize) + } + } else { + ctx.get((row_offset + 1 + 3 * (len - 1)) as isize) + }; + (ctx.last().unwrap(), a_last) + } + + /// Returns `(, a_assigned)`. See `inner_product` for more details. + /// + /// **NOT** encouraged for general usage. + /// This is a low-level function, useful for when you want to simultaneously compute an inner product while assigning + /// private witnesses for the first time. This avoids first assigning `a` and then copying into the correct cells + /// for this computation. We do not return the assignments of `a` in `inner_product` as an optimization to avoid + /// the memory allocation of having to collect the vectors. + /// + /// We do not return `b_assigned` because if `b` starts with `Constant(F::ONE)`, the first element of `b` is not assigned. + /// + /// Assumes 'a' and 'b' are the same length. + fn inner_product_left( + &self, + ctx: &mut Context, + a: impl IntoIterator, + b: impl IntoIterator>, + ) -> (AssignedValue, Vec>) + where + QA: Into>, + { + let a = a.into_iter().collect_vec(); + let len = a.len(); + let row_offset = ctx.advice.len(); + let b_starts_with_one = self.inner_product_simple(ctx, a, b); + let a_assigned = (0..len) + .map(|i| { + if b_starts_with_one { + if i == 0 { + ctx.get(row_offset as isize) + } else { + ctx.get((row_offset + 1 + 3 * (i - 1)) as isize) + } + } else { + ctx.get((row_offset + 1 + 3 * i) as isize) + } + }) + .collect_vec(); + (ctx.last().unwrap(), a_assigned) + } + + /// Calculates and constrains the inner product. + /// + /// Returns the assignment trace where `output[i]` has the running sum `sum_{j=0..=i} a[j] * b[j]`. + /// + /// Assumes 'a' and 'b' are the same length. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: Iterator of [QuantumCell] values + /// * `b`: Iterator of [QuantumCell] values to calculate the partial sums of the inner product of `a` by + fn inner_product_with_sums<'thread, QA>( + &self, + ctx: &'thread mut Context, + a: impl IntoIterator, + b: impl IntoIterator>, + ) -> Box> + 'thread> + where + QA: Into>, + { + let row_offset = ctx.advice.len(); + let b_starts_with_one = self.inner_product_simple(ctx, a, b); + if b_starts_with_one { + Box::new((row_offset..ctx.advice.len()).step_by(3).map(|i| ctx.get(i as isize))) + } else { + // in this case the first assignment is 0 so we skip it + Box::new((row_offset..ctx.advice.len()).step_by(3).skip(1).map(|i| ctx.get(i as isize))) + } + } + + /// Constrains and returns the sum of products of `coeff * (a * b)` defined in `values` plus a variable `var` e.g. + /// `x = var + values[0].0 * (values[0].1 * values[0].2) + values[1].0 * (values[1].1 * values[1].2) + ... + values[n].0 * (values[n].1 * values[n].2)`. + /// * `ctx`: [Context] to add the constraints to + /// * `values`: Iterator of tuples `(coeff, a, b)` where `coeff` is a field element, `a` and `b` are [QuantumCell]'s + /// * `var`: [QuantumCell] that represents the value of a variable added to the sum + fn sum_products_with_coeff_and_var( + &self, + ctx: &mut Context, + values: impl IntoIterator, QuantumCell)>, + var: QuantumCell, + ) -> AssignedValue { + // Create an iterator starting with `var` and + let (a, b): (Vec<_>, Vec<_>) = std::iter::once((var, Constant(F::ONE))) + .chain(values.into_iter().filter_map(|(c, va, vb)| { + if c == F::ONE { + Some((va, vb)) + } else if c != F::ZERO { + let prod = self.mul(ctx, va, vb); + Some((QuantumCell::Existing(prod), Constant(c))) + } else { + None + } + })) + .unzip(); + self.inner_product(ctx, a, b) + } + + /// Constrains and returns `sel ? a : b` assuming `sel` is boolean. + /// + /// Defines a vertical gate of form `| 1 - sel | sel | 1 | a | 1 - sel | sel | 1 | b | out |`, where out = sel * a + (1 - sel) * b. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] that contains a boolean value + /// * `b`: [QuantumCell] that contains a boolean value + /// * `sel`: [QuantumCell] that contains a boolean value + fn select( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + sel: impl Into>, + ) -> AssignedValue { + let a = a.into(); + let b = b.into(); + let sel = sel.into(); + let diff_val = *a.value() - b.value(); + let out_val = diff_val * sel.value() + b.value(); + // | a - b | 1 | b | a | + // | b | sel | a - b | out | + let cells = [ + Witness(diff_val), + Constant(F::ONE), + b, + a, + b, + sel, + Witness(diff_val), + Witness(out_val), + ]; + ctx.assign_region_smart(cells, [0, 4], [(0, 6), (2, 4)], []); + ctx.last().unwrap() + } + + /// Constains and returns `a || (b && c)`, assuming `a`, `b` and `c` are boolean. + /// + /// Defines a vertical gate of form `| 1 - b c | b | c | 1 | a - 1 | 1 - b c | out | a - 1 | 1 | 1 | a |`, where out = a + b * c - a * b * c. + /// * `ctx`: [Context] to add the constraints to + /// * `a`: [QuantumCell] that contains a boolean value + /// * `b`: [QuantumCell] that contains a boolean value + /// * `c`: [QuantumCell] that contains a boolean value + fn or_and( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + c: impl Into>, + ) -> AssignedValue { + let a = a.into(); + let b = b.into(); + let c = c.into(); + let bc_val = *b.value() * c.value(); + let not_bc_val = F::ONE - bc_val; + let not_a_val = *a.value() - F::ONE; + let out_val = bc_val + a.value() - bc_val * a.value(); + let cells = [ + Witness(not_bc_val), + b, + c, + Constant(F::ONE), + Witness(not_a_val), + Witness(not_bc_val), + Witness(out_val), + Witness(not_a_val), + Constant(F::ONE), + Constant(F::ONE), + a, + ]; + ctx.assign_region_smart(cells, [0, 3, 7], [(4, 7), (0, 5)], []); + ctx.get(-5) + } + + /// Constrains and returns little-endian bit vector representation of `a`. + /// + /// Assumes `range_bits >= number of bits in a`. + /// * `a`: [QuantumCell] of the value to convert + /// * `range_bits`: range of bits needed to represent `a`. Assumes `range_bits > 0`. + fn num_to_bits( + &self, + ctx: &mut Context, + a: AssignedValue, + range_bits: usize, + ) -> Vec> { + let bits = a.value().to_u64_limbs(range_bits, 1).into_iter().map(|x| Witness(F::from(x))); + + let mut bit_cells = Vec::with_capacity(range_bits); + let row_offset = ctx.advice.len(); + let acc = self.inner_product( + ctx, + bits, + self.pow_of_two[..range_bits].iter().map(|c| Constant(*c)), + ); + ctx.constrain_equal(&a, &acc); + debug_assert!(range_bits > 0); + bit_cells.push(ctx.get(row_offset as isize)); + for i in 1..range_bits { + bit_cells.push(ctx.get((row_offset + 1 + 3 * (i - 1)) as isize)); + } + + for bit_cell in &bit_cells { + self.assert_bit(ctx, *bit_cell); + } + bit_cells + } + + /// Constrains and computes `a^exp` where both `a, exp` are witnesses. The exponent is computed in the native field `F`. + /// + /// Constrains that `exp` has at most `max_bits` bits. + fn pow_var( + &self, + ctx: &mut Context, + a: AssignedValue, + exp: AssignedValue, + max_bits: usize, + ) -> AssignedValue { + let exp_bits = self.num_to_bits(ctx, exp, max_bits); + // standard square-and-mul approach + let mut acc = ctx.load_constant(F::ONE); + for (i, bit) in exp_bits.into_iter().rev().enumerate() { + if i > 0 { + // square + acc = self.mul(ctx, acc, acc); + } + let mul = self.mul(ctx, acc, a); + acc = self.select(ctx, mul, acc, bit); + } + acc + } +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/mod.rs b/vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/mod.rs new file mode 100644 index 00000000..675f57ab --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/mod.rs @@ -0,0 +1,18 @@ +//! Module for managing the virtual region corresponding to [super::FlexGateConfig] +//! +//! In the virtual region we have virtual columns. Each virtual column is referred to as a "thread" +//! because it can be generated in a separate CPU thread. The virtual region manager will collect all +//! threads together, virtually concatenate them all together back into a single virtual column, and +//! then assign this virtual column to multiple physical Halo2 columns according to the provided configuration parameters. +//! +//! Supports multiple phases. + +/// Thread builder for multiple phases +mod multi_phase; +mod parallelize; +/// Thread builder for a single phase +pub mod single_phase; + +pub use multi_phase::{GateStatistics, MultiPhaseCoreManager}; +pub use parallelize::parallelize_core; +pub use single_phase::SinglePhaseCoreManager; diff --git a/vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/multi_phase.rs b/vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/multi_phase.rs new file mode 100644 index 00000000..56b3ff6b --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/multi_phase.rs @@ -0,0 +1,162 @@ +use getset::CopyGetters; +use itertools::Itertools; + +use crate::{ + gates::{circuit::CircuitBuilderStage, flex_gate::FlexGateConfigParams}, + utils::ScalarField, + virtual_region::copy_constraints::SharedCopyConstraintManager, + Context, +}; + +use super::SinglePhaseCoreManager; + +/// Virtual region manager for [`FlexGateConfig`](super::super::FlexGateConfig) in multiple phases. +#[derive(Clone, Debug, Default, CopyGetters)] +pub struct MultiPhaseCoreManager { + /// Virtual region for each challenge phase. These cannot be shared across threads while keeping circuit deterministic. + pub phase_manager: 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")] + use_unknown: bool, +} + +impl MultiPhaseCoreManager { + /// Creates a new [MultiPhaseCoreManager] with a default [SinglePhaseCoreManager] in phase 0. + /// Creates an empty [SharedCopyConstraintManager] and sets `witness_gen_only` flag. + /// * `witness_gen_only`: If true, the [MultiPhaseCoreManager] is used for witness generation only. + /// * If true, the gate thread builder only does witness asignments and does not store constraint information -- this should only be used for the real prover. + /// * If false, the gate thread builder is used for keygen and mock prover (it can also be used for real prover) and the builder stores circuit information (e.g. copy constraints, fixed columns, enabled selectors). + /// * These values are fixed for the circuit at key generation time, and they do not need to be re-computed by the prover in the actual proving phase. + pub fn new(witness_gen_only: bool) -> Self { + let copy_manager = SharedCopyConstraintManager::default(); + let phase_manager = + vec![SinglePhaseCoreManager::new(witness_gen_only, copy_manager.clone())]; + Self { phase_manager, witness_gen_only, use_unknown: false, copy_manager } + } + + /// Creates a new [MultiPhaseCoreManager] depending on the stage of circuit building. If the stage is [CircuitBuilderStage::Prover], the [MultiPhaseCoreManager] is used for witness generation only. + pub fn from_stage(stage: CircuitBuilderStage) -> Self { + Self::new(stage.witness_gen_only()).unknown(stage == CircuitBuilderStage::Keygen) + } + + /// Mutates `self` to use the given copy manager in all phases and all threads. + pub fn set_copy_manager(&mut self, copy_manager: SharedCopyConstraintManager) { + for pm in &mut self.phase_manager { + pm.set_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 + } + + /// Creates a new [MultiPhaseCoreManager] with `use_unknown` flag set. + /// * `use_unknown`: If true, during key generation witness values are replaced with `Value::unknown()` for safety. + pub fn unknown(mut self, use_unknown: bool) -> Self { + self.use_unknown = use_unknown; + for pm in &mut self.phase_manager { + pm.use_unknown = use_unknown; + } + self + } + + /// Clears all threads in all phases and copy manager. + pub fn clear(&mut self) { + for pm in &mut self.phase_manager { + pm.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. + /// * `phase`: The challenge phase (as an index) of the gate thread. + pub fn main(&mut self, phase: usize) -> &mut Context { + self.touch(phase); + self.phase_manager[phase].main() + } + + /// 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, phase: usize) -> &mut Context { + self.touch(phase); + self.phase_manager[phase].new_thread() + } + + /// Returns a mutable reference to the [SinglePhaseCoreManager] of a given `phase`. + pub fn in_phase(&mut self, phase: usize) -> &mut SinglePhaseCoreManager { + self.phase_manager.get_mut(phase).unwrap() + } + + /// Populate `self` up to Phase `phase` (inclusive) + pub(crate) fn touch(&mut self, phase: usize) { + while self.phase_manager.len() <= phase { + let _phase = self.phase_manager.len(); + let pm = SinglePhaseCoreManager::new(self.witness_gen_only, self.copy_manager.clone()) + .in_phase(_phase); + self.phase_manager.push(pm); + } + } + + /// Returns some statistics about the virtual region. + pub fn statistics(&self) -> GateStatistics { + let total_advice_per_phase = + self.phase_manager.iter().map(|pm| pm.total_advice()).collect::>(); + + let total_fixed: usize = self + .copy_manager + .lock() + .unwrap() + .constant_equalities + .iter() + .map(|(c, _)| *c) + .sorted() + .dedup() + .count(); + + GateStatistics { total_advice_per_phase, total_fixed } + } + + /// Auto-calculates configuration parameters for the circuit + /// + /// * `k`: The number of in the circuit (i.e. numeber of rows = 2k) + /// * `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(&self, k: usize, minimum_rows: Option) -> FlexGateConfigParams { + let max_rows = (1 << k) - minimum_rows.unwrap_or(0); + let stats = self.statistics(); + // we do a rough estimate by taking ceil(advice_cells_per_phase / 2^k ) + // if this is too small, manual configuration will be needed + let num_advice_per_phase = stats + .total_advice_per_phase + .iter() + .map(|count| count.div_ceil(max_rows)) + .collect::>(); + let num_fixed = (stats.total_fixed + (1 << k) - 1) >> k; + + let params = FlexGateConfigParams { num_advice_per_phase, num_fixed, k }; + #[cfg(feature = "display")] + { + for (phase, num_advice) in stats.total_advice_per_phase.iter().enumerate() { + println!("Gate Chip | Phase {phase}: {num_advice} advice cells",); + } + println!("Total {} fixed cells", stats.total_fixed); + log::debug!("Auto-calculated config params:\n {params:#?}"); + } + params + } +} + +/// Basic statistics +pub struct GateStatistics { + /// Total advice cell count per phase + pub total_advice_per_phase: Vec, + /// Total distinct constants used + pub total_fixed: usize, +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/parallelize.rs b/vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/parallelize.rs new file mode 100644 index 00000000..cc2754b0 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/parallelize.rs @@ -0,0 +1,29 @@ +use rayon::prelude::*; + +use crate::{utils::ScalarField, Context}; + +use super::SinglePhaseCoreManager; + +/// Utility function to parallelize an operation involving [`Context`]s. +pub fn parallelize_core( + builder: &mut SinglePhaseCoreManager, // leaving `builder` for historical reasons, `pool` is a better name + input: Vec, + f: FR, +) -> Vec +where + F: ScalarField, + T: Send, + R: Send, + FR: Fn(&mut Context, T) -> R + Send + Sync, +{ + // to prevent concurrency issues with context id, we generate all the ids first + let thread_count = builder.thread_count(); + let mut ctxs = + (0..input.len()).map(|i| builder.new_context(thread_count + i)).collect::>(); + let outputs: Vec<_> = + input.into_par_iter().zip(ctxs.par_iter_mut()).map(|(input, ctx)| f(ctx, input)).collect(); + // we collect the new threads to ensure they are a FIXED order, otherwise the circuit will not be deterministic + builder.threads.append(&mut ctxs); + + outputs +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/single_phase.rs b/vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/single_phase.rs new file mode 100644 index 00000000..8176c4cc --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/flex_gate/threads/single_phase.rs @@ -0,0 +1,322 @@ +use std::cell::RefCell; + +use getset::CopyGetters; + +use crate::{ + gates::{ + circuit::CircuitBuilderStage, + flex_gate::{BasicGateConfig, ThreadBreakPoints}, + }, + utils::halo2::{raw_assign_advice, raw_constrain_equal}, + utils::ScalarField, + virtual_region::copy_constraints::{CopyConstraintManager, SharedCopyConstraintManager}, + Context, ContextCell, +}; +use crate::{ + halo2_proofs::circuit::{Region, Value}, + virtual_region::manager::VirtualRegionManager, +}; + +/// Virtual region manager for [`Vec`] in a single challenge phase. +/// This is the core manager for [Context]s. +#[derive(Clone, Debug, Default, CopyGetters)] +pub struct SinglePhaseCoreManager { + /// 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, + /// The challenge phase the virtual regions will map to. + #[getset(get_copy = "pub", set)] + pub(crate) phase: usize, + /// A very simple computation graph for the basic vertical gate. Must be provided as a "pinning" + /// when running the production prover. + pub break_points: RefCell>, +} + +impl SinglePhaseCoreManager { + /// Creates a new [SinglePhaseCoreManager] and spawns a main thread. + /// * `witness_gen_only`: If true, the [SinglePhaseCoreManager] is used for witness generation only. + /// * If true, the gate thread builder only does witness asignments and does not store constraint information -- this should only be used for the real prover. + /// * If false, the gate thread builder is used for keygen and mock prover (it can also be used for real prover) and the builder stores circuit information (e.g. copy constraints, fixed columns, enabled selectors). + /// * These values are fixed for the circuit at key generation time, and they do not need to be re-computed by the prover in the actual proving phase. + pub fn new(witness_gen_only: bool, copy_manager: SharedCopyConstraintManager) -> Self { + Self { + threads: vec![], + witness_gen_only, + use_unknown: false, + phase: 0, + copy_manager, + ..Default::default() + } + } + + /// Sets the phase to `phase` + pub fn in_phase(self, phase: usize) -> Self { + Self { phase, ..self } + } + + /// Creates a new [SinglePhaseCoreManager] depending on the stage of circuit building. If the stage is [CircuitBuilderStage::Prover], the [SinglePhaseCoreManager] 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 [SinglePhaseCoreManager] 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 } + } + + /// Mutates `self` to use the given copy manager everywhere, including in all threads. + pub fn set_copy_manager(&mut self, copy_manager: SharedCopyConstraintManager) { + self.copy_manager = copy_manager.clone(); + for ctx in &mut self.threads { + ctx.copy_manager = copy_manager.clone(); + } + } + + /// 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 + } + + /// Clears all threads and copy manager + pub fn clear(&mut self) { + self.threads = vec![]; + 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() + } + + /// A distinct tag for this particular type of virtual manager, which is different for each phase. + pub fn type_of(&self) -> &'static str { + match self.phase { + 0 => "halo2-base:SinglePhaseCoreManager:FirstPhase", + 1 => "halo2-base:SinglePhaseCoreManager:SecondPhase", + 2 => "halo2-base:SinglePhaseCoreManager:ThirdPhase", + _ => panic!("Unsupported phase"), + } + } + + /// Creates new context but does not append to `self.threads` + pub fn new_context(&self, context_id: usize) -> Context { + Context::new( + self.witness_gen_only, + self.phase, + self.type_of(), + 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 SinglePhaseCoreManager { + type Config = (Vec>, usize); // usize = usable_rows + type Assignment = (); + + fn assign_raw(&self, (config, usable_rows): &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, config, region, break_points); + } else { + let mut copy_manager = self.copy_manager.lock().unwrap(); + let break_points = assign_with_constraints::( + &self.threads, + config, + region, + &mut copy_manager, + *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); + } + } + } +} + +/// Assigns all virtual `threads` to the physical columns in `basic_gates` and returns the break points. +/// Also enables corresponding selectors and adds raw assigned cells to the `copy_manager`. +/// This function should be called either during proving & verifier key generation or when running MockProver. +/// +/// For proof generation, see [assign_witnesses]. +/// +/// This is generic for a "vertical" custom gate that uses a single column and `ROTATIONS` contiguous rows in that column. +/// +/// ⚠️ Right now we only support "overlaps" where you can have the gate enabled at `offset` and `offset + ROTATIONS - 1`, but not at `offset + delta` where `delta < ROTATIONS - 1`. +/// +/// # Inputs +/// - `max_rows`: The number of rows that can be used for the assignment. This is the number of rows that are not blinded for zero-knowledge. +/// - If `use_unknown` is true, then the advice columns will be assigned as unknowns. +/// +/// # Assumptions +/// - All `basic_gates` are in the same phase. +pub fn assign_with_constraints( + threads: &[Context], + basic_gates: &[BasicGateConfig], + region: &mut Region, + copy_manager: &mut CopyConstraintManager, + max_rows: usize, + use_unknown: bool, +) -> ThreadBreakPoints { + let mut break_points = vec![]; + let mut gate_index = 0; + let mut row_offset = 0; + for ctx in threads { + if ctx.advice.is_empty() { + continue; + } + let mut basic_gate = basic_gates + .get(gate_index) + .unwrap_or_else(|| panic!("NOT ENOUGH ADVICE COLUMNS. Perhaps blinding factors were not taken into account. The max non-poisoned rows is {max_rows}")); + assert_eq!(ctx.selector.len(), ctx.advice.len()); + + for (i, (advice, &q)) in ctx.advice.iter().zip(ctx.selector.iter()).enumerate() { + let column = basic_gate.value; + let value = if use_unknown { Value::unknown() } else { Value::known(advice) }; + #[cfg(feature = "halo2-axiom")] + let cell = region.assign_advice(column, row_offset, value).cell(); + #[cfg(not(feature = "halo2-axiom"))] + let cell = region + .assign_advice(|| "", column, row_offset, || value.map(|v| *v)) + .unwrap() + .cell(); + if let Some(old_cell) = copy_manager + .assigned_advices + .insert(ContextCell::new(ctx.type_id, ctx.context_id, i), cell) + { + assert!( + old_cell.row_offset == cell.row_offset && old_cell.column == cell.column, + "Trying to overwrite virtual cell with a different raw cell" + ); + } + + // If selector enabled and row_offset is valid add break point, account for break point overlap, and enforce equality constraint for gate outputs. + // ⚠️ This assumes overlap is of form: gate enabled at `i - delta` and `i`, where `delta = ROTATIONS - 1`. We currently do not support `delta < ROTATIONS - 1`. + if (q && row_offset + ROTATIONS > max_rows) || row_offset >= max_rows - 1 { + break_points.push(row_offset); + row_offset = 0; + gate_index += 1; + + // safety check: make sure selector is not enabled on `i - delta` for `0 < delta < ROTATIONS - 1` + if ROTATIONS > 1 && i + 2 >= ROTATIONS { + for delta in 1..ROTATIONS - 1 { + assert!( + !ctx.selector[i - delta], + "We do not support overlaps with delta = {delta}" + ); + } + } + // when there is a break point, because we may have two gates that overlap at the current cell, we must copy the current cell to the next column for safety + basic_gate = basic_gates + .get(gate_index) + .unwrap_or_else(|| panic!("NOT ENOUGH ADVICE COLUMNS. Perhaps blinding factors were not taken into account. The max non-poisoned rows is {max_rows}")); + let column = basic_gate.value; + #[cfg(feature = "halo2-axiom")] + let ncell = region.assign_advice(column, row_offset, value); + #[cfg(not(feature = "halo2-axiom"))] + let ncell = + region.assign_advice(|| "", column, row_offset, || value.map(|v| *v)).unwrap(); + raw_constrain_equal(region, ncell.cell(), cell); + } + + if q { + basic_gate + .q_enable + .enable(region, row_offset) + .expect("enable selector should not fail"); + } + + row_offset += 1; + } + } + break_points +} + +/// Assigns all virtual `threads` to the physical columns in `basic_gates` according to a precomputed "computation graph" +/// given by `break_points`. (`break_points` tells the assigner when to move to the next column.) +/// +/// This function does not impose **any** constraints. It only assigns witnesses to advice columns, and should be called +/// only during proof generation. +/// +/// # Assumptions +/// - All `basic_gates` are in the same phase. +pub fn assign_witnesses( + threads: &[Context], + basic_gates: &[BasicGateConfig], + region: &mut Region, + break_points: &ThreadBreakPoints, +) { + if basic_gates.is_empty() { + assert_eq!( + threads.iter().map(|ctx| ctx.advice.len()).sum::(), + 0, + "Trying to assign threads in a phase with no columns" + ); + return; + } + + let mut break_points = break_points.clone().into_iter(); + let mut break_point = break_points.next(); + + let mut gate_index = 0; + let mut column = basic_gates[gate_index].value; + let mut row_offset = 0; + + for ctx in threads { + // Assign advice values to the advice columns in each [Context] + for advice in &ctx.advice { + raw_assign_advice(region, column, row_offset, Value::known(advice)); + + if break_point == Some(row_offset) { + break_point = break_points.next(); + row_offset = 0; + gate_index += 1; + column = basic_gates[gate_index].value; + + raw_assign_advice(region, column, row_offset, Value::known(advice)); + } + + row_offset += 1; + } + } +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/mod.rs b/vendor/halo2-lib/halo2-base/src/gates/mod.rs new file mode 100644 index 00000000..749ee834 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/mod.rs @@ -0,0 +1,13 @@ +/// Module providing tools to create a circuit using our gates +pub mod circuit; +/// Module implementing our simple custom gate and common functions using it +pub mod flex_gate; +/// Module using a single lookup table for range checks +pub mod range; + +/// Tests +#[cfg(test)] +pub mod tests; + +pub use flex_gate::{GateChip, GateInstructions}; +pub use range::{RangeChip, RangeInstructions}; diff --git a/vendor/halo2-lib/halo2-base/src/gates/range/mod.rs b/vendor/halo2-lib/halo2-base/src/gates/range/mod.rs new file mode 100644 index 00000000..e1adb7ab --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/range/mod.rs @@ -0,0 +1,651 @@ +use crate::{ + gates::flex_gate::{FlexGateConfig, GateInstructions, MAX_PHASE}, + halo2_proofs::{ + circuit::{Layouter, Value}, + plonk::{ + Advice, Column, ConstraintSystem, Error, SecondPhase, Selector, TableColumn, ThirdPhase, + }, + poly::Rotation, + }, + utils::{ + biguint_to_fe, bit_length, decompose_fe_to_u64_limbs, fe_to_biguint, BigPrimeField, + ScalarField, + }, + virtual_region::lookups::LookupAnyManager, + AssignedValue, Context, + QuantumCell::{self, Constant, Existing, Witness}, +}; + +use super::flex_gate::{FlexGateConfigParams, GateChip}; + +use getset::Getters; +use num_bigint::BigUint; +use num_integer::Integer; +use num_traits::One; +use std::{cmp::Ordering, ops::Shl}; + +/// Configuration for Range Chip +#[derive(Clone, Debug)] +pub struct RangeConfig { + /// Underlying Gate Configuration + pub gate: FlexGateConfig, + /// Special advice (witness) Columns used only for lookup tables. + /// + /// Each phase of a halo2 circuit has a distinct lookup_advice column. + /// + /// * If `gate` has only 1 advice column, lookups are enabled for that column, in which case `lookup_advice` is empty + /// * If `gate` has more than 1 advice column some number of user-specified `lookup_advice` columns are added + /// * In this case, we don't need a selector so `q_lookup` is empty + pub lookup_advice: Vec>>, + /// Selector values for the lookup table. + pub q_lookup: Vec>, + /// Column for lookup table values. + pub lookup: TableColumn, + /// Defines the number of bits represented in the lookup table [0,2^lookup_bits). + lookup_bits: usize, +} + +impl RangeConfig { + /// Generates a new [RangeConfig] with the specified parameters. + /// + /// If `num_columns` is 0, then we assume you do not want to perform any lookups in that phase. + /// + /// Panics if `lookup_bits` > 28. + /// * `meta`: [ConstraintSystem] of the circuit + /// * `gate_params`: see [FlexGateConfigParams] + /// * `num_lookup_advice`: Number of `lookup_advice` [Column]s in each phase + /// * `lookup_bits`: Number of bits represented in the LookUp table [0,2^lookup_bits) + pub fn configure( + meta: &mut ConstraintSystem, + gate_params: FlexGateConfigParams, + num_lookup_advice: &[usize], + lookup_bits: usize, + ) -> Self { + assert!(lookup_bits <= F::S as usize); + // sanity check: only create lookup table if there are lookup_advice columns + assert!(!num_lookup_advice.is_empty(), "You are creating a RangeConfig but don't seem to need a lookup table, please double-check if you're using lookups correctly. Consider setting lookup_bits = None in BaseConfigParams"); + + let lookup = meta.lookup_table_column(); + + let gate = FlexGateConfig::configure(meta, gate_params.clone()); + + // For now, we apply the same range lookup table to each phase + let mut q_lookup = Vec::new(); + let mut lookup_advice = Vec::new(); + for (phase, &num_columns) in num_lookup_advice.iter().enumerate() { + let num_advice = *gate_params.num_advice_per_phase.get(phase).unwrap_or(&0); + let mut columns = Vec::new(); + // If num_columns is set to 0, then we assume you do not want to perform any lookups in that phase. + // Disable this optimization in phase > 0 because you might set selectors based a cell from other columns. + if phase == 0 && num_advice == 1 && num_columns != 0 { + q_lookup.push(Some(meta.complex_selector())); + } else { + q_lookup.push(None); + for _ in 0..num_columns { + let a = match phase { + 0 => meta.advice_column(), + 1 => meta.advice_column_in(SecondPhase), + 2 => meta.advice_column_in(ThirdPhase), + _ => panic!("Currently RangeConfig only supports {MAX_PHASE} phases"), + }; + meta.enable_equality(a); + columns.push(a); + } + } + lookup_advice.push(columns); + } + + let mut config = Self { lookup_advice, q_lookup, lookup, lookup_bits, gate }; + config.create_lookup(meta); + + log::debug!("Poisoned rows after RangeConfig::configure {}", meta.minimum_rows()); + config.gate.max_rows = (1 << gate_params.k) - meta.minimum_rows(); + assert!( + (1 << lookup_bits) <= config.gate.max_rows, + "lookup table is too large for the circuit degree plus blinding factors!" + ); + + config + } + + /// Returns the number of bits represented in the lookup table [0,2^lookup_bits). + pub fn lookup_bits(&self) -> usize { + self.lookup_bits + } + + /// Instantiates the lookup table of the circuit. + /// * `meta`: [ConstraintSystem] of the circuit + fn create_lookup(&self, meta: &mut ConstraintSystem) { + for (phase, q_l) in self.q_lookup.iter().enumerate() { + if let Some(q) = q_l { + meta.lookup("lookup", |meta| { + let q = meta.query_selector(*q); + // there should only be 1 advice column in phase `phase` + let a = + meta.query_advice(self.gate.basic_gates[phase][0].value, Rotation::cur()); + vec![(q * a, self.lookup)] + }); + } + } + //if multiple columns + for la in self.lookup_advice.iter().flat_map(|advices| advices.iter()) { + meta.lookup("lookup wo selector", |meta| { + let a = meta.query_advice(*la, Rotation::cur()); + vec![(a, self.lookup)] + }); + } + } + + /// Loads the lookup table into the circuit using the provided `layouter`. + /// * `layouter`: layouter for the circuit + pub fn load_lookup_table(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + layouter.assign_table( + || format!("{} bit lookup", self.lookup_bits), + |mut table| { + for idx in 0..(1u32 << self.lookup_bits) { + table.assign_cell( + || "lookup table", + self.lookup, + idx as usize, + || Value::known(F::from(idx as u64)), + )?; + } + Ok(()) + }, + )?; + Ok(()) + } +} + +/// Trait that implements methods to constrain a field element number `x` is within a range of bits. +pub trait RangeInstructions { + /// The type of Gate used within the instructions. + type Gate: GateInstructions; + + /// Returns the type of gate used. + fn gate(&self) -> &Self::Gate; + + /// Returns the number of bits the lookup table represents. + fn lookup_bits(&self) -> usize; + + /// Checks and constrains that `a` lies in the range [0, 2range_bits). + /// + /// Inputs: + /// * `a`: [AssignedValue] value to be range checked + /// * `range_bits`: number of bits in the range + fn range_check(&self, ctx: &mut Context, a: AssignedValue, range_bits: usize); + + /// Constrains that 'a' is less than 'b'. + /// + /// Assumes that `a` and `b` have bit length <= num_bits bits. + /// + /// Note: This may fail silently if a or b have more than num_bits. + /// * a: [QuantumCell] value to check + /// * b: upper bound expressed as a [QuantumCell] + /// * num_bits: number of bits used to represent the values of `a` and `b` + fn check_less_than( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + num_bits: usize, + ); + + /// Performs a range check that `a` has at most `ceil(b.bits() / lookup_bits) * lookup_bits` bits and then constrains that `a` is less than `b`. + /// + /// * a: [AssignedValue] value to check + /// * b: upper bound expressed as a [u64] value + /// + /// ## Assumptions + /// * `ceil(b.bits() / lookup_bits) * lookup_bits <= F::CAPACITY` + fn check_less_than_safe(&self, ctx: &mut Context, a: AssignedValue, b: u64) { + let range_bits = bit_length(b).div_ceil(self.lookup_bits()) * self.lookup_bits(); + + self.range_check(ctx, a, range_bits); + self.check_less_than(ctx, a, Constant(F::from(b)), range_bits) + } + + /// Performs a range check that `a` has at most `ceil(b.bits() / lookup_bits) * lookup_bits` bits and then constrains that `a` is less than `b`. + /// + /// * a: [AssignedValue] value to check + /// * b: upper bound expressed as a [BigUint] value + /// + /// ## Assumptions + /// * `ceil(b.bits() / lookup_bits) * lookup_bits <= F::CAPACITY` + fn check_big_less_than_safe(&self, ctx: &mut Context, a: AssignedValue, b: BigUint) + where + F: BigPrimeField, + { + let range_bits = (b.bits() as usize).div_ceil(self.lookup_bits()) * self.lookup_bits(); + + self.range_check(ctx, a, range_bits); + self.check_less_than(ctx, a, Constant(biguint_to_fe(&b)), range_bits) + } + + /// Constrains whether `a` is in `[0, b)`, and returns 1 if `a` < `b`, otherwise 0. + /// + /// Assumes that`a` and `b` are known to have <= num_bits bits. + /// * a: first [QuantumCell] to compare + /// * b: second [QuantumCell] to compare + /// * num_bits: number of bits to represent the values + fn is_less_than( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + num_bits: usize, + ) -> AssignedValue; + + /// Performs a range check that `a` has at most `ceil(bit_length(b) / lookup_bits) * lookup_bits` and then returns whether `a` is in `[0,b)`. + /// + /// Returns 1 if `a` < `b`, otherwise 0. + /// + /// * a: [AssignedValue] value to check + /// * b: upper bound as [u64] value + fn is_less_than_safe( + &self, + ctx: &mut Context, + a: AssignedValue, + b: u64, + ) -> AssignedValue { + let range_bits = bit_length(b).div_ceil(self.lookup_bits()) * self.lookup_bits(); + + self.range_check(ctx, a, range_bits); + self.is_less_than(ctx, a, Constant(F::from(b)), range_bits) + } + + /// Performs a range check that `a` has at most `ceil(b.bits() / lookup_bits) * lookup_bits` bits and then returns whether `a` is in `[0,b)`. + /// + /// Returns 1 if `a` < `b`, otherwise 0. + /// + /// * a: [AssignedValue] value to check + /// * b: upper bound as [BigUint] value + /// + /// For the current implementation using `is_less_than`, we require `ceil(b.bits() / lookup_bits) + 1 < F::NUM_BITS / lookup_bits` + fn is_big_less_than_safe( + &self, + ctx: &mut Context, + a: AssignedValue, + b: BigUint, + ) -> AssignedValue + where + F: BigPrimeField, + { + let range_bits = (b.bits() as usize).div_ceil(self.lookup_bits()) * self.lookup_bits(); + + self.range_check(ctx, a, range_bits); + self.is_less_than(ctx, a, Constant(biguint_to_fe(&b)), range_bits) + } + + /// Constrains and returns `(c, r)` such that `a = b * c + r`. + /// + /// * a: [QuantumCell] value to divide + /// * b: [BigUint] value to divide by + /// * a_num_bits: number of bits needed to represent the value of `a` + /// + /// ## Assumptions + /// * `b != 0` and that `a` has <= `a_num_bits` bits. + /// * `a_num_bits <= F::CAPACITY = F::NUM_BITS - 1` + /// * Unsafe behavior if `a_num_bits >= F::NUM_BITS` + fn div_mod( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into, + a_num_bits: usize, + ) -> (AssignedValue, AssignedValue) + where + F: BigPrimeField, + { + let a = a.into(); + let b = b.into(); + let a_val = fe_to_biguint(a.value()); + let (div, rem) = a_val.div_mod_floor(&b); + let [div, rem] = [div, rem].map(|v| biguint_to_fe(&v)); + ctx.assign_region([Witness(rem), Constant(biguint_to_fe(&b)), Witness(div), a], [0]); + let rem = ctx.get(-4); + let div = ctx.get(-2); + // Constrain that a_num_bits fulfills `div < 2 ** a_num_bits / b`. + self.check_big_less_than_safe( + ctx, + div, + BigUint::one().shl(a_num_bits as u32) / &b + BigUint::one(), + ); + // Constrain that remainder is less than divisor (i.e. `r < b`). + self.check_big_less_than_safe(ctx, rem, b); + (div, rem) + } + + /// Constrains and returns `(c, r)` such that `a = b * c + r`. + /// + /// Assumes: + /// that `b != 0`. + /// that `a` has <= `a_num_bits` bits. + /// that `b` has <= `b_num_bits` bits. + /// + /// Note: + /// Let `X = 2 ** b_num_bits` + /// Write `a = a1 * X + a0` and `c = c1 * X + c0` + /// If we write `b * c0 + r = d1 * X + d0` then + /// `b * c + r = (b * c1 + d1) * X + d0` + /// * a: [QuantumCell] value to divide + /// * b: [QuantumCell] value to divide by + /// * a_num_bits: number of bits needed to represent the value of `a` + /// * b_num_bits: number of bits needed to represent the value of `b` + /// + /// ## Assumptions + /// * `a_num_bits <= F::CAPACITY = F::NUM_BITS - 1` + /// * `b_num_bits <= F::CAPACITY = F::NUM_BITS - 1` + /// * Unsafe behavior if `a_num_bits >= F::NUM_BITS` or `b_num_bits >= F::NUM_BITS` + fn div_mod_var( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + a_num_bits: usize, + b_num_bits: usize, + ) -> (AssignedValue, AssignedValue) + where + F: BigPrimeField, + { + let a = a.into(); + let b = b.into(); + let a_val = fe_to_biguint(a.value()); + let b_val = fe_to_biguint(b.value()); + let (div, rem) = a_val.div_mod_floor(&b_val); + let x = BigUint::one().shl(b_num_bits as u32); + let (div_hi, div_lo) = div.div_mod_floor(&x); + + let x_fe = self.gate().pow_of_two()[b_num_bits]; + let [div, div_hi, div_lo, rem] = [div, div_hi, div_lo, rem].map(|v| biguint_to_fe(&v)); + ctx.assign_region( + [Witness(div_lo), Witness(div_hi), Constant(x_fe), Witness(div), Witness(rem)], + [0], + ); + let [div_lo, div_hi, div, rem] = [-5, -4, -2, -1].map(|i| ctx.get(i)); + self.range_check(ctx, div_lo, b_num_bits); + if a_num_bits <= b_num_bits { + self.gate().assert_is_const(ctx, &div_hi, &F::ZERO); + } else { + self.range_check(ctx, div_hi, a_num_bits - b_num_bits); + } + + let (bcr0_hi, bcr0_lo) = { + let bcr0 = self.gate().mul_add(ctx, b, Existing(div_lo), Existing(rem)); + self.div_mod(ctx, Existing(bcr0), x.clone(), a_num_bits) + }; + let bcr_hi = self.gate().mul_add(ctx, b, Existing(div_hi), Existing(bcr0_hi)); + + let (a_hi, a_lo) = self.div_mod(ctx, a, x, a_num_bits); + ctx.constrain_equal(&bcr_hi, &a_hi); + ctx.constrain_equal(&bcr0_lo, &a_lo); + + self.range_check(ctx, rem, b_num_bits); + self.check_less_than(ctx, Existing(rem), b, b_num_bits); + (div, rem) + } + + /// Constrains and returns the last bit of the value of `a`. + /// + /// Assume `a` has been range checked already to `limb_bits` bits. + /// * a: [AssignedValue] value to get the last bit of + /// * limb_bits: number of bits in a limb + fn get_last_bit( + &self, + ctx: &mut Context, + a: AssignedValue, + limb_bits: usize, + ) -> AssignedValue { + let a_big = fe_to_biguint(a.value()); + let bit_v = F::from(a_big.bit(0)); + let two = F::from(2u64); + let h_v = F::from_bytes_le(&(a_big >> 1usize).to_bytes_le()); + + ctx.assign_region([Witness(bit_v), Witness(h_v), Constant(two), Existing(a)], [0]); + let half = ctx.get(-3); + let bit = ctx.get(-4); + + self.range_check(ctx, half, limb_bits - 1); + self.gate().assert_bit(ctx, bit); + bit + } +} + +/// # RangeChip +/// This chip provides methods that rely on "range checking" that a field element `x` is within a range of bits. +/// Range checks are done using a lookup table with the numbers [0, 2lookup_bits). +#[derive(Clone, Debug, Getters)] +pub struct RangeChip { + /// Underlying [GateChip] for this chip. + pub gate: GateChip, + /// Lookup manager for each phase, lazily initiated using the [`SharedCopyConstraintManager`](crate::virtual_region::copy_constraints::SharedCopyConstraintManager) from the [Context] + /// that first calls it. + /// + /// The lookup manager is used to store the cells that need to be looked up in the range check lookup table. + #[getset(get = "pub")] + lookup_manager: [LookupAnyManager; MAX_PHASE], + /// Defines the number of bits represented in the lookup table [0,2lookup_bits). + lookup_bits: usize, + /// [Vec] of powers of `2 ** lookup_bits` represented as [QuantumCell::Constant]. + /// These are precomputed and cached as a performance optimization for later limb decompositions. We precompute up to the higher power that fits in `F`, which is `2 ** ((F::CAPACITY / lookup_bits) * lookup_bits)`. + pub limb_bases: Vec>, +} + +impl RangeChip { + /// Creates a new [RangeChip] with the given strategy and lookup_bits. + /// * `lookup_bits`: number of bits represented in the lookup table [0,2lookup_bits) + /// * `lookup_manager`: a [LookupAnyManager] for each phase. + /// + /// **IMPORTANT:** It is **critical** that all `LookupAnyManager`s use the same [`SharedCopyConstraintManager`](crate::virtual_region::copy_constraints::SharedCopyConstraintManager) + /// as in your primary circuit builder. + /// + /// It is not advised to call this function directly. Instead you should call `BaseCircuitBuilder::range_chip`. + pub fn new(lookup_bits: usize, lookup_manager: [LookupAnyManager; MAX_PHASE]) -> Self { + let limb_base = F::from(1u64 << lookup_bits); + let mut running_base = limb_base; + let num_bases = F::CAPACITY as usize / lookup_bits; + let mut limb_bases = Vec::with_capacity(num_bases + 1); + limb_bases.extend([Constant(F::ONE), Constant(running_base)]); + for _ in 2..=num_bases { + running_base *= &limb_base; + limb_bases.push(Constant(running_base)); + } + let gate = GateChip::new(); + + Self { gate, lookup_bits, lookup_manager, limb_bases } + } + + fn add_cell_to_lookup(&self, ctx: &Context, a: AssignedValue) { + let phase = ctx.phase(); + let manager = &self.lookup_manager[phase]; + manager.add_lookup(ctx.tag(), [a]); + } + + /// Checks and constrains that `a` lies in the range [0, 2range_bits). + /// + /// This is done by decomposing `a` into `num_limbs` limbs, where `num_limbs = ceil(range_bits / lookup_bits)`. + /// Each limb is constrained to be within the range [0, 2lookup_bits). + /// The limbs are then combined to form `a` again with the last limb having `rem_bits` number of bits. + /// + /// Returns the last (highest) limb. + /// + /// Inputs: + /// * `a`: [AssignedValue] value to be range checked + /// * `range_bits`: number of bits in the range + /// * `lookup_bits`: number of bits in the lookup table + /// + /// # Assumptions + /// * `ceil(range_bits / lookup_bits) * lookup_bits <= F::CAPACITY` + fn _range_check( + &self, + ctx: &mut Context, + a: AssignedValue, + range_bits: usize, + ) -> AssignedValue { + if range_bits == 0 { + self.gate.assert_is_const(ctx, &a, &F::ZERO); + return a; + } + // the number of limbs + let num_limbs = range_bits.div_ceil(self.lookup_bits); + // println!("range check {} bits {} len", range_bits, k); + let rem_bits = range_bits % self.lookup_bits; + + debug_assert!(self.limb_bases.len() >= num_limbs); + + let last_limb = if num_limbs == 1 { + self.add_cell_to_lookup(ctx, a); + a + } else { + let limbs = decompose_fe_to_u64_limbs(a.value(), num_limbs, self.lookup_bits) + .into_iter() + .map(|x| Witness(F::from(x))); + let row_offset = ctx.advice.len() as isize; + let acc = self.gate.inner_product(ctx, limbs, self.limb_bases[..num_limbs].to_vec()); + // the inner product above must equal `a` + ctx.constrain_equal(&a, &acc); + // we fetch the cells to lookup by getting the indices where `limbs` were assigned in `inner_product`. Because `limb_bases[0]` is 1, the progression of indices is 0,1,4,...,4+3*i + self.add_cell_to_lookup(ctx, ctx.get(row_offset)); + for i in 0..num_limbs - 1 { + self.add_cell_to_lookup(ctx, ctx.get(row_offset + 1 + 3 * i as isize)); + } + ctx.get(row_offset + 1 + 3 * (num_limbs - 2) as isize) + }; + + // additional constraints for the last limb if rem_bits != 0 + match rem_bits.cmp(&1) { + // we want to check x := limbs[num_limbs-1] is boolean + // we constrain x*(x-1) = 0 + x * x - x == 0 + // | 0 | x | x | x | + Ordering::Equal => { + self.gate.assert_bit(ctx, last_limb); + } + Ordering::Greater => { + let mult_val = self.gate.pow_of_two[self.lookup_bits - rem_bits]; + let check = self.gate.mul(ctx, last_limb, Constant(mult_val)); + self.add_cell_to_lookup(ctx, check); + } + _ => {} + } + last_limb + } +} + +impl RangeInstructions for RangeChip { + type Gate = GateChip; + + /// The type of Gate used in this chip. + fn gate(&self) -> &Self::Gate { + &self.gate + } + + /// Returns the number of bits represented in the lookup table [0,2lookup_bits). + fn lookup_bits(&self) -> usize { + self.lookup_bits + } + + /// Checks and constrains that `a` lies in the range [0, 2range_bits). + /// + /// This is done by decomposing `a` into `num_limbs` limbs, where `num_limbs = ceil(range_bits / lookup_bits)`. + /// Each limb is constrained to be within the range [0, 2lookup_bits). + /// The limbs are then combined to form `a` again with the last limb having `rem_bits` number of bits. + /// + /// Inputs: + /// * `a`: [AssignedValue] value to be range checked + /// * `range_bits`: number of bits in the range + /// + /// # Assumptions + /// * `ceil(range_bits / lookup_bits) * lookup_bits <= F::CAPACITY` + fn range_check(&self, ctx: &mut Context, a: AssignedValue, range_bits: usize) { + self._range_check(ctx, a, range_bits); + } + + /// Constrains that 'a' is less than 'b'. + /// + /// Assumes that`a` and `b` are known to have <= num_bits bits. + /// + /// Note: This may fail silently if a or b have more than num_bits + /// * a: [QuantumCell] value to check + /// * b: upper bound expressed as a [QuantumCell] + /// * num_bits: number of bits to represent the values + fn check_less_than( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + num_bits: usize, + ) { + let a = a.into(); + let b = b.into(); + let pow_of_two = self.gate.pow_of_two[num_bits]; + let check_cell = { + let shift_a_val = pow_of_two + a.value(); + // | a + 2^(num_bits) - b | b | 1 | a + 2^(num_bits) | - 2^(num_bits) | 1 | a | + let cells = [ + Witness(shift_a_val - b.value()), + b, + Constant(F::ONE), + Witness(shift_a_val), + Constant(-pow_of_two), + Constant(F::ONE), + a, + ]; + ctx.assign_region(cells, [0, 3]); + ctx.get(-7) + }; + + self.range_check(ctx, check_cell, num_bits); + } + + /// Constrains whether `a` is in `[0, b)`, and returns 1 if `a` < `b`, otherwise 0. + /// + /// * a: first [QuantumCell] to compare + /// * b: second [QuantumCell] to compare + /// * num_bits: number of bits to represent the values + /// + /// # Assumptions + /// * `a` and `b` are known to have `<= num_bits` bits. + /// * (`ceil(num_bits / lookup_bits) + 1) * lookup_bits <= F::CAPACITY` + fn is_less_than( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + num_bits: usize, + ) -> AssignedValue { + let a = a.into(); + let b = b.into(); + + let k = num_bits.div_ceil(self.lookup_bits); + let padded_bits = k * self.lookup_bits; + debug_assert!( + padded_bits + self.lookup_bits <= F::CAPACITY as usize, + "num_bits is too large for this is_less_than implementation" + ); + let pow_padded = self.gate.pow_of_two[padded_bits]; + + let shift_a_val = pow_padded + a.value(); + let shifted_val = shift_a_val - b.value(); + let shifted_cell = { + ctx.assign_region( + [ + Witness(shifted_val), + b, + Constant(F::ONE), + Witness(shift_a_val), + Constant(-pow_padded), + Constant(F::ONE), + a, + ], + [0, 3], + ); + ctx.get(-7) + }; + + // check whether a - b + 2^padded_bits < 2^padded_bits ? + // since assuming a, b < 2^padded_bits we are guaranteed a - b + 2^padded_bits < 2^{padded_bits + 1} + let last_limb = self._range_check(ctx, shifted_cell, padded_bits + self.lookup_bits); + // last_limb will have the (k + 1)-th limb of `a - b + 2^{k * limb_bits}`, which is zero iff `a < b` + self.gate.is_zero(ctx, last_limb) + } +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/tests/flex_gate.rs b/vendor/halo2-lib/halo2-base/src/gates/tests/flex_gate.rs new file mode 100644 index 00000000..49243dd5 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/tests/flex_gate.rs @@ -0,0 +1,225 @@ +#![allow(clippy::type_complexity)] +use super::*; +use crate::utils::biguint_to_fe; +use crate::utils::testing::base_test; +use crate::QuantumCell::{Constant, Witness}; +use crate::{gates::flex_gate::GateInstructions, QuantumCell}; +use itertools::Itertools; +use num_bigint::BigUint; +use test_case::test_case; + +#[test_case(&[10, 12].map(Fr::from).map(Witness)=> Fr::from(22); "add(): 10 + 12 == 22")] +#[test_case(&[1, 1].map(Fr::from).map(Witness)=> Fr::from(2); "add(): 1 + 1 == 2")] +pub fn test_add(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.add(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(Witness(Fr::from(10))=> Fr::from(11); "inc(): 10 -> 11")] +#[test_case(Witness(Fr::from(1))=> Fr::from(2); "inc(): 1 -> 2")] +pub fn test_inc(input: QuantumCell) -> Fr { + base_test().run_gate(|ctx, chip| *chip.inc(ctx, input).value()) +} + +#[test_case(&[10, 12].map(Fr::from).map(Witness)=> -Fr::from(2) ; "sub(): 10 - 12 == -2")] +#[test_case(&[1, 1].map(Fr::from).map(Witness)=> Fr::from(0) ; "sub(): 1 - 1 == 0")] +pub fn test_sub(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.sub(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(Witness(Fr::from(10))=> Fr::from(9); "dec(): 10 -> 9")] +#[test_case(Witness(Fr::from(1))=> Fr::from(0); "dec(): 1 -> 0")] +pub fn test_dec(input: QuantumCell) -> Fr { + base_test().run_gate(|ctx, chip| *chip.dec(ctx, input).value()) +} + +#[test_case(&[1, 1, 1].map(Fr::from).map(Witness) => Fr::from(0) ; "sub_mul(): 1 - 1 * 1 == 0")] +pub fn test_sub_mul(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.sub_mul(ctx, inputs[0], inputs[1], inputs[2]).value()) +} + +#[test_case(Witness(Fr::from(1)) => -Fr::from(1) ; "neg(): 1 -> -1")] +pub fn test_neg(a: QuantumCell) -> Fr { + base_test().run_gate(|ctx, chip| *chip.neg(ctx, a).value()) +} + +#[test_case(&[10, 12].map(Fr::from).map(Witness) => Fr::from(120) ; "mul(): 10 * 12 == 120")] +#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::from(1) ; "mul(): 1 * 1 == 1")] +pub fn test_mul(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.mul(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(&[1, 1, 1].map(Fr::from).map(Witness) => Fr::from(2) ; "mul_add(): 1 * 1 + 1 == 2")] +pub fn test_mul_add(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.mul_add(ctx, inputs[0], inputs[1], inputs[2]).value()) +} + +#[test_case(&[0, 10].map(Fr::from).map(Witness) => Fr::from(10); "mul_not(): (1 - 0) * 10 == 10")] +#[test_case(&[1, 10].map(Fr::from).map(Witness) => Fr::from(0); "mul_not(): (1 - 1) * 10 == 0")] +pub fn test_mul_not(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.mul_not(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(Fr::from(0), true; "assert_bit(0)")] +#[test_case(Fr::from(1), true; "assert_bit(1)")] +#[test_case(Fr::from(2), false; "assert_bit(2)")] +pub fn test_assert_bit(input: Fr, is_bit: bool) { + base_test().expect_satisfied(is_bit).run_gate(|ctx, chip| { + let a = ctx.load_witness(input); + chip.assert_bit(ctx, a); + }); +} + +#[test_case(&[6, 2].map(Fr::from).map(Witness)=> Fr::from(3) ; "div_unsafe(): 6 / 2 == 3")] +#[test_case(&[1, 1].map(Fr::from).map(Witness)=> Fr::from(1) ; "div_unsafe(): 1 / 1 == 1")] +pub fn test_div_unsafe(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.div_unsafe(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(&[1, 1].map(Fr::from); "assert_is_const(1,1)")] +#[test_case(&[0, 1].map(Fr::from); "assert_is_const(0,1)")] +pub fn test_assert_is_const(inputs: &[Fr]) { + base_test().expect_satisfied(inputs[0] == inputs[1]).run_gate(|ctx, chip| { + let a = ctx.load_witness(inputs[0]); + chip.assert_is_const(ctx, &a, &inputs[1]); + }); +} + +#[test_case((vec![Witness(Fr::one()); 5], vec![Witness(Fr::one()); 5]) => Fr::from(5) ; "inner_product(): 1 * 1 + ... + 1 * 1 == 5")] +pub fn test_inner_product(input: (Vec>, Vec>)) -> Fr { + base_test().run_gate(|ctx, chip| *chip.inner_product(ctx, input.0, input.1).value()) +} + +#[test_case((vec![Witness(Fr::one()); 5], vec![Witness(Fr::one()); 5]) => (Fr::from(5), Fr::from(1)); "inner_product_left_last(): 1 * 1 + ... + 1 * 1 == (5, 1)")] +pub fn test_inner_product_left_last( + input: (Vec>, Vec>), +) -> (Fr, Fr) { + base_test().run_gate(|ctx, chip| { + let a = chip.inner_product_left_last(ctx, input.0, input.1); + (*a.0.value(), *a.1.value()) + }) +} + +#[test_case([4,5,6].map(Fr::from).to_vec(), [1,2,3].map(|x| Constant(Fr::from(x))).to_vec() => (Fr::from(32), [4,5,6].map(Fr::from).to_vec()); +"inner_product_left(): <[4,5,6],[1,2,3]> Constant b starts with 1")] +#[test_case([1,2,3].map(Fr::from).to_vec(), [4,5,6].map(|x| Witness(Fr::from(x))).to_vec() => (Fr::from(32), [1,2,3].map(Fr::from).to_vec()); +"inner_product_left(): <[1,2,3],[4,5,6]> Witness")] +pub fn test_inner_product_left(a: Vec, b: Vec>) -> (Fr, Vec) { + base_test().run_gate(|ctx, chip| { + let (prod, a) = chip.inner_product_left(ctx, a.into_iter().map(Witness), b); + (*prod.value(), a.iter().map(|v| *v.value()).collect()) + }) +} + +#[test_case((vec![Witness(Fr::one()); 5], vec![Witness(Fr::one()); 5]) => (1..=5).map(Fr::from).collect::>(); "inner_product_with_sums(): 1 * 1 + ... + 1 * 1 == [1, 2, 3, 4, 5]")] +pub fn test_inner_product_with_sums( + input: (Vec>, Vec>), +) -> Vec { + base_test().run_gate(|ctx, chip| { + chip.inner_product_with_sums(ctx, input.0, input.1).map(|a| *a.value()).collect() + }) +} + +#[test_case((vec![(Fr::from(1), Witness(Fr::from(1)), Witness(Fr::from(1)))], Witness(Fr::from(1))) => Fr::from(2) ; "sum_product_with_coeff_and_var(): 1 * 1 + 1 == 2")] +pub fn test_sum_products_with_coeff_and_var( + input: (Vec<(Fr, QuantumCell, QuantumCell)>, QuantumCell), +) -> Fr { + base_test() + .run_gate(|ctx, chip| *chip.sum_products_with_coeff_and_var(ctx, input.0, input.1).value()) +} + +#[test_case(&[1, 0].map(Fr::from).map(Witness) => Fr::from(0) ; "and(): 1 && 0 == 0")] +#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::from(1) ; "and(): 1 && 1 == 1")] +pub fn test_and(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.and(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(Witness(Fr::from(1)) => Fr::zero(); "not(): !1 == 0")] +#[test_case(Witness(Fr::from(0)) => Fr::one(); "not(): !0 == 1")] +pub fn test_not(a: QuantumCell) -> Fr { + base_test().run_gate(|ctx, chip| *chip.not(ctx, a).value()) +} + +#[test_case(&[2, 3, 1].map(Fr::from).map(Witness) => Fr::from(2); "select(): 2 ? 3 : 1 == 2")] +pub fn test_select(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.select(ctx, inputs[0], inputs[1], inputs[2]).value()) +} + +#[test_case(&[0, 1, 0].map(Fr::from).map(Witness) => Fr::from(0); "or_and(): 0 || (1 && 0) == 0")] +#[test_case(&[1, 0, 1].map(Fr::from).map(Witness) => Fr::from(1); "or_and(): 1 || (0 && 1) == 1")] +#[test_case(&[1, 1, 1].map(Fr::from).map(Witness) => Fr::from(1); "or_and(): 1 || (1 && 1) == 1")] +pub fn test_or_and(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.or_and(ctx, inputs[0], inputs[1], inputs[2]).value()) +} + +#[test_case(&[0,1] => [0,0,1,0].map(Fr::from).to_vec(); "bits_to_indicator(): bin\"10 -> [0, 0, 1, 0]")] +#[test_case(&[0] => [1,0].map(Fr::from).to_vec(); "bits_to_indicator(): 0 -> [1, 0]")] +pub fn test_bits_to_indicator(bits: &[u8]) -> Vec { + base_test().run_gate(|ctx, chip| { + let a = ctx.assign_witnesses(bits.iter().map(|x| Fr::from(*x as u64))); + chip.bits_to_indicator(ctx, &a).iter().map(|a| *a.value()).collect() + }) +} + +#[test_case(Witness(Fr::from(0)),3 => [1,0,0].map(Fr::from).to_vec(); "idx_to_indicator(): 0 -> [1, 0, 0]")] +pub fn test_idx_to_indicator(idx: QuantumCell, len: usize) -> Vec { + base_test().run_gate(|ctx, chip| { + chip.idx_to_indicator(ctx, idx, len).iter().map(|a| *a.value()).collect() + }) +} + +#[test_case((0..3).map(Fr::from).map(Witness).collect(), Witness(Fr::one()) => Fr::from(1); "select_by_indicator(1): [0, 1, 2] -> 1")] +pub fn test_select_by_indicator(array: Vec>, idx: QuantumCell) -> Fr { + base_test().run_gate(|ctx, chip| { + let a = chip.idx_to_indicator(ctx, idx, array.len()); + *chip.select_by_indicator(ctx, array, a).value() + }) +} + +#[test_case((0..3).map(Fr::from).map(Witness).collect(), Witness(Fr::from(1)) => Fr::from(1); "select_from_idx(): [0, 1, 2] -> 1")] +pub fn test_select_from_idx(array: Vec>, idx: QuantumCell) -> Fr { + base_test().run_gate(|ctx, chip| *chip.select_from_idx(ctx, array, idx).value()) +} + +#[test_case(vec![vec![1,2,3], vec![4,5,6], vec![7,8,9]].into_iter().map(|a| a.into_iter().map(Fr::from).collect_vec()).collect_vec(), +Fr::from(1) => +[4,5,6].map(Fr::from).to_vec(); +"select_array_by_indicator(1): [[1,2,3], [4,5,6], [7,8,9]] -> [4,5,6]")] +pub fn test_select_array_by_indicator(array2d: Vec>, idx: Fr) -> Vec { + base_test().run_gate(|ctx, chip| { + let array2d = array2d.into_iter().map(|a| ctx.assign_witnesses(a)).collect_vec(); + let idx = ctx.load_witness(idx); + let ind = chip.idx_to_indicator(ctx, idx, array2d.len()); + chip.select_array_by_indicator(ctx, &array2d, &ind).iter().map(|a| *a.value()).collect() + }) +} + +#[test_case(Fr::zero() => Fr::from(1); "is_zero(): 0 -> 1")] +pub fn test_is_zero(input: Fr) -> Fr { + base_test().run_gate(|ctx, chip| { + let input = ctx.load_witness(input); + *chip.is_zero(ctx, input).value() + }) +} + +#[test_case(&[1, 1].map(Fr::from).map(Witness) => Fr::one(); "is_equal(): 1 == 1")] +pub fn test_is_equal(inputs: &[QuantumCell]) -> Fr { + base_test().run_gate(|ctx, chip| *chip.is_equal(ctx, inputs[0], inputs[1]).value()) +} + +#[test_case(6, 3 => [0,1,1].map(Fr::from).to_vec(); "num_to_bits(): 6")] +pub fn test_num_to_bits(num: usize, bits: usize) -> Vec { + base_test().run_gate(|ctx, chip| { + let num = ctx.load_witness(Fr::from(num as u64)); + chip.num_to_bits(ctx, num, bits).iter().map(|a| *a.value()).collect() + }) +} + +#[test_case(Fr::from(3), BigUint::from(3u32), 4 => Fr::from(27); "pow_var(): 3^3 = 27")] +pub fn test_pow_var(a: Fr, exp: BigUint, max_bits: usize) -> Fr { + assert!(exp.bits() <= max_bits as u64); + base_test().run_gate(|ctx, chip| { + let a = ctx.load_witness(a); + let exp = ctx.load_witness(biguint_to_fe(&exp)); + *chip.pow_var(ctx, a, exp, max_bits).value() + }) +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/tests/general.rs b/vendor/halo2-lib/halo2-base/src/gates/tests/general.rs new file mode 100644 index 00000000..55e5ee1b --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/tests/general.rs @@ -0,0 +1,125 @@ +use crate::ff::Field; +use crate::gates::flex_gate::threads::parallelize_core; +use crate::halo2_proofs::halo2curves::bn256::Fr; +use crate::utils::{BigPrimeField, ScalarField}; +use crate::{ + gates::{ + flex_gate::{GateChip, GateInstructions}, + range::{RangeChip, RangeInstructions}, + }, + utils::testing::base_test, +}; +use crate::{Context, QuantumCell::Constant}; +use rand::rngs::StdRng; +use rand::SeedableRng; +use test_log::test; + +fn gate_tests(ctx: &mut Context, inputs: [F; 3]) { + let [a, b, c]: [_; 3] = ctx.assign_witnesses(inputs).try_into().unwrap(); + let chip = GateChip::default(); + + // test add + chip.add(ctx, a, b); + + // test sub + chip.sub(ctx, a, b); + + // test multiply + chip.mul(ctx, c, b); + + // test idx_to_indicator + chip.idx_to_indicator(ctx, Constant(F::from(3u64)), 4); + + let bits = ctx.assign_witnesses([F::ZERO, F::ONE]); + chip.bits_to_indicator(ctx, &bits); + + chip.is_equal(ctx, b, a); + + chip.is_zero(ctx, a); +} + +#[test] +fn test_multithread_gates() { + let mut rng = StdRng::seed_from_u64(0); + base_test().k(6).bench_builder( + vec![[Fr::ZERO; 3]; 4], + (0..4usize).map(|_| [(); 3].map(|_| Fr::random(&mut rng))).collect(), + |pool, _, inputs| { + parallelize_core(pool, inputs, |ctx, input| { + gate_tests(ctx, input); + }); + }, + ); +} + +#[cfg(feature = "dev-graph")] +#[test] +fn plot_gates() { + let k = 5; + use plotters::prelude::*; + + use crate::gates::circuit::builder::BaseCircuitBuilder; + + let root = BitMapBackend::new("layout.png", (1024, 1024)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root.titled("Gates Layout", ("sans-serif", 60)).unwrap(); + + let inputs = [Fr::zero(); 3]; + let mut builder = BaseCircuitBuilder::new(false).use_k(k); + gate_tests(builder.main(0), inputs); + + // auto-tune circuit + builder.calculate_params(Some(9)); + halo2_proofs::dev::CircuitLayout::default().render(k as u32, &builder, &root).unwrap(); +} + +fn range_tests( + ctx: &mut Context, + chip: &RangeChip, + inputs: [F; 2], + range_bits: usize, + lt_bits: usize, +) { + let [a, b]: [_; 2] = ctx.assign_witnesses(inputs).try_into().unwrap(); + chip.range_check(ctx, a, range_bits); + + chip.check_less_than(ctx, a, b, lt_bits); + + chip.is_less_than(ctx, a, b, lt_bits); + + chip.is_less_than(ctx, b, a, lt_bits); + + chip.div_mod(ctx, a, 7u64, lt_bits); +} + +#[test] +fn test_range_single() { + base_test().k(11).lookup_bits(3).bench_builder( + [Fr::ZERO; 2], + [100, 101].map(Fr::from), + |pool, range, inputs| { + range_tests(pool.main(), range, inputs, 8, 8); + }, + ); +} + +#[test] +fn test_range_multicolumn() { + let inputs = [100, 101].map(Fr::from); + base_test().k(5).lookup_bits(3).run(|ctx, range| { + range_tests(ctx, range, inputs, 8, 8); + }) +} + +#[test] +fn test_multithread_range() { + base_test().k(6).lookup_bits(3).unusable_rows(20).bench_builder( + vec![[Fr::ZERO; 2]; 3], + vec![[0, 1].map(Fr::from), [100, 101].map(Fr::from), [254, 255].map(Fr::from)], + |pool, range, inputs| { + parallelize_core(pool, inputs, |ctx, input| { + range_tests(ctx, range, input, 8, 8); + }); + }, + ); +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/tests/idx_to_indicator.rs b/vendor/halo2-lib/halo2-base/src/gates/tests/idx_to_indicator.rs new file mode 100644 index 00000000..6d709b48 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/tests/idx_to_indicator.rs @@ -0,0 +1,118 @@ +use crate::ff::Field; +use crate::gates::circuit::{builder::RangeCircuitBuilder, CircuitBuilderStage}; +use crate::{ + gates::{GateChip, GateInstructions}, + halo2_proofs::{ + halo2curves::bn256::Fr, + plonk::keygen_pk, + plonk::{keygen_vk, Assigned}, + poly::kzg::commitment::ParamsKZG, + }, + utils::testing::{check_proof, gen_proof}, + QuantumCell::Witness, +}; +use itertools::Itertools; +use rand::{rngs::OsRng, thread_rng, Rng}; +use test_log::test; + +// soundness checks for `idx_to_indicator` function +fn test_idx_to_indicator_gen(k: u32, len: usize) { + // first create proving and verifying key + let mut builder = + RangeCircuitBuilder::from_stage(CircuitBuilderStage::Keygen).use_k(k as usize); + let gate = GateChip::default(); + let dummy_idx = Witness(Fr::zero()); + let indicator = gate.idx_to_indicator(builder.main(0), dummy_idx, len); + // get the offsets of the indicator cells for later 'pranking' + let ind_offsets = indicator.iter().map(|ind| ind.cell.unwrap().offset).collect::>(); + let config_params = builder.calculate_params(Some(9)); + + let params = ParamsKZG::setup(k, OsRng); + // generate proving key + let vk = keygen_vk(¶ms, &builder).unwrap(); + let pk = keygen_pk(¶ms, vk, &builder).unwrap(); + let vk = pk.get_vk(); // pk consumed vk + let break_points = builder.break_points(); + drop(builder); + + // now create different proofs to test the soundness of the circuit + + let gen_pf = |idx: usize, ind_witnesses: &[Fr]| { + let mut builder = RangeCircuitBuilder::prover(config_params.clone(), break_points.clone()); + let gate = GateChip::default(); + let idx = Witness(Fr::from(idx as u64)); + let ctx = builder.main(0); + gate.idx_to_indicator(ctx, idx, len); + // prank the indicator cells + for (offset, witness) in ind_offsets.iter().zip_eq(ind_witnesses) { + ctx.advice[*offset] = Assigned::Trivial(*witness); + } + gen_proof(¶ms, &pk, builder) + }; + + // expected answer + for idx in 0..len { + let mut ind_witnesses = vec![Fr::zero(); len]; + ind_witnesses[idx] = Fr::one(); + let pf = gen_pf(idx, &ind_witnesses); + check_proof(¶ms, vk, &pf, true); + } + + let mut rng = thread_rng(); + // bad cases + for idx in 0..len { + let mut ind_witnesses = vec![Fr::zero(); len]; + // all zeros is bad! + let pf = gen_pf(idx, &ind_witnesses); + check_proof(¶ms, vk, &pf, false); + + // ind[idx] != 1 is bad! + for _ in 0..100usize { + ind_witnesses.fill(Fr::zero()); + ind_witnesses[idx] = Fr::random(OsRng); + if ind_witnesses[idx] == Fr::one() { + continue; + } + let pf = gen_pf(idx, &ind_witnesses); + check_proof(¶ms, vk, &pf, false); + } + + if len < 2 { + continue; + } + // nonzeros where there should be zeros is bad! + for _ in 0..100usize { + ind_witnesses.fill(Fr::zero()); + ind_witnesses[idx] = Fr::one(); + let num_nonzeros = rng.gen_range(1..len); + let mut count = 0usize; + for _ in 0..num_nonzeros { + let index = rng.gen_range(0..len); + if index == idx { + continue; + } + ind_witnesses[index] = Fr::random(&mut rng); + count += 1; + } + if count == 0usize { + continue; + } + let pf = gen_pf(idx, &ind_witnesses); + check_proof(¶ms, vk, &pf, false); + } + } +} + +#[test] +fn test_idx_to_indicator() { + test_idx_to_indicator_gen(8, 1); + test_idx_to_indicator_gen(8, 4); + test_idx_to_indicator_gen(8, 10); + test_idx_to_indicator_gen(8, 20); +} + +#[test] +#[ignore = "takes too long"] +fn test_idx_to_indicator_large() { + test_idx_to_indicator_gen(11, 100); +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/tests/mod.rs b/vendor/halo2-lib/halo2-base/src/gates/tests/mod.rs new file mode 100644 index 00000000..8e35b53e --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/tests/mod.rs @@ -0,0 +1,9 @@ +use crate::halo2_proofs::halo2curves::bn256::Fr; + +mod flex_gate; +mod general; +mod idx_to_indicator; +mod neg_prop; +mod pos_prop; +mod range; +mod utils; diff --git a/vendor/halo2-lib/halo2-base/src/gates/tests/neg_prop.rs b/vendor/halo2-lib/halo2-base/src/gates/tests/neg_prop.rs new file mode 100644 index 00000000..b3339af6 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/tests/neg_prop.rs @@ -0,0 +1,266 @@ +use crate::{ + ff::Field, + gates::{ + range::RangeInstructions, + tests::{pos_prop::rand_fr, utils}, + GateInstructions, + }, + halo2_proofs::halo2curves::bn256::Fr, + utils::{biguint_to_fe, bit_length, fe_to_biguint, testing::base_test, ScalarField}, + QuantumCell::Witness, +}; + +use num_bigint::BigUint; +use proptest::{collection::vec, prelude::*}; +use rand::rngs::OsRng; + +// Strategies for generating random witnesses +prop_compose! { + // length == 1 is just selecting [0] which should be covered in unit test + fn idx_to_indicator_strat(k_bounds: (usize, usize), max_size: usize) + (k in k_bounds.0..=k_bounds.1, idx_val in prop::sample::select(vec![Fr::zero(), Fr::one(), Fr::random(OsRng)]), len in 2usize..=max_size) + (k in Just(k), idx in 0..len, idx_val in Just(idx_val), len in Just(len), mut witness_vals in arb_indicator::(len)) + -> (usize, usize, usize, Vec) { + witness_vals[idx] = idx_val; + (k, len, idx, witness_vals) + } +} + +prop_compose! { + fn select_strat(k_bounds: (usize, usize)) + (k in k_bounds.0..=k_bounds.1, a in rand_fr(), b in rand_fr(), sel in any::(), rand_output in rand_fr()) + -> (usize, Fr, Fr, bool, Fr) { + (k, a, b, sel, rand_output) + } +} + +prop_compose! { + fn select_by_indicator_strat(k_bounds: (usize, usize), max_size: usize) + (k in k_bounds.0..=k_bounds.1, len in 2usize..=max_size) + (k in Just(k), a in vec(rand_fr(), len), idx in 0..len, rand_output in rand_fr()) + -> (usize, Vec, usize, Fr) { + (k, a, idx, rand_output) + } +} + +prop_compose! { + fn select_from_idx_strat(k_bounds: (usize, usize), max_size: usize) + (k in k_bounds.0..=k_bounds.1, len in 2usize..=max_size) + (k in Just(k), cells in vec(rand_fr(), len), idx in 0..len, rand_output in rand_fr()) + -> (usize, Vec, usize, Fr) { + (k, cells, idx, rand_output) + } +} + +prop_compose! { + fn inner_product_strat(k_bounds: (usize, usize), max_size: usize) + (k in k_bounds.0..=k_bounds.1, len in 2usize..=max_size) + (k in Just(k), a in vec(rand_fr(), len), b in vec(rand_fr(), len), rand_output in rand_fr()) + -> (usize, Vec, Vec, Fr) { + (k, a, b, rand_output) + } +} + +prop_compose! { + fn inner_product_left_last_strat(k_bounds: (usize, usize), max_size: usize) + (k in k_bounds.0..=k_bounds.1, len in 2usize..=max_size) + (k in Just(k), a in vec(rand_fr(), len), b in vec(rand_fr(), len), rand_output in (rand_fr(), rand_fr())) + -> (usize, Vec, Vec, (Fr, Fr)) { + (k, a, b, rand_output) + } +} + +prop_compose! { + pub fn range_check_strat(k_bounds: (usize, usize), max_range_bits: usize) + (k in k_bounds.0..=k_bounds.1, range_bits in 1usize..=max_range_bits) // lookup_bits must be less than k + (k in Just(k), range_bits in Just(range_bits), lookup_bits in 8..k, + rand_a in prop::sample::select(vec![ + biguint_to_fe(&(BigUint::from(2u64).pow(range_bits as u32) - 1usize)), + biguint_to_fe(&BigUint::from(2u64).pow(range_bits as u32)), + biguint_to_fe(&(BigUint::from(2u64).pow(range_bits as u32) + 1usize)), + Fr::random(OsRng) + ])) + -> (usize, usize, usize, Fr) { + (k, range_bits, lookup_bits, rand_a) + } +} + +prop_compose! { + fn is_less_than_safe_strat(k_bounds: (usize, usize)) + // compose strat to generate random rand fr in range + (b in any::().prop_filter("not zero", |&i| i != 0), k in k_bounds.0..=k_bounds.1) + (k in Just(k), b in Just(b), lookup_bits in k_bounds.0 - 1..k, rand_a in rand_fr(), out in any::()) + -> (usize, u64, usize, Fr, bool) { + (k, b, lookup_bits, rand_a, out) + } +} + +fn arb_indicator(max_size: usize) -> impl Strategy> { + vec(Just(0), max_size).prop_map(|val| val.iter().map(|&x| F::from(x)).collect::>()) +} + +fn check_idx_to_indicator(idx: Fr, len: usize, ind_witnesses: &[Fr]) -> bool { + // check that: + // the length of the witnes array is correct + // the sum of the witnesses is 1, indicting that there is only one index that is 1 + if ind_witnesses.len() != len + || ind_witnesses.iter().fold(Fr::zero(), |acc, val| acc + *val) != Fr::one() + { + return false; + } + + let idx_val = idx.get_lower_64() as usize; + + // Check that all indexes are zero except for the one at idx + for (i, v) in ind_witnesses.iter().enumerate() { + if i != idx_val && *v != Fr::zero() { + return false; + } + } + true +} + +// verify rand_output == a if sel == 1, rand_output == b if sel == 0 +fn check_select(a: Fr, b: Fr, sel: bool, rand_output: Fr) -> bool { + if (!sel && rand_output != b) || (sel && rand_output != a) { + return false; + } + true +} + +fn neg_test_idx_to_indicator(k: usize, len: usize, idx: usize, ind_witnesses: &[Fr]) { + // Check soundness of witness values + let is_valid_witness = check_idx_to_indicator(Fr::from(idx as u64), len, ind_witnesses); + base_test().k(k as u32).expect_satisfied(is_valid_witness).run_gate(|ctx, gate| { + // assign value to advice column before by assigning `idx` via ctx.load() -> use same method as ind_offsets to get offset + let dummy_idx = Witness(Fr::from(idx as u64)); + let mut indicator = gate.idx_to_indicator(ctx, dummy_idx, len); + for (advice, prank_val) in indicator.iter_mut().zip(ind_witnesses) { + advice.debug_prank(ctx, *prank_val); + } + }); +} + +fn neg_test_select(k: usize, a: Fr, b: Fr, sel: bool, prank_output: Fr) { + // Check soundness of output + let is_valid_instance = check_select(a, b, sel, prank_output); + base_test().k(k as u32).expect_satisfied(is_valid_instance).run_gate(|ctx, gate| { + let [a, b, sel] = [a, b, Fr::from(sel)].map(|x| ctx.load_witness(x)); + let select = gate.select(ctx, a, b, sel); + select.debug_prank(ctx, prank_output); + }) +} + +fn neg_test_select_by_indicator(k: usize, a: Vec, idx: usize, prank_output: Fr) { + // retrieve the value of a[idx] and check that it is equal to rand_output + let is_valid_witness = prank_output == a[idx]; + base_test().k(k as u32).expect_satisfied(is_valid_witness).run_gate(|ctx, gate| { + let indicator = gate.idx_to_indicator(ctx, Witness(Fr::from(idx as u64)), a.len()); + let a = ctx.assign_witnesses(a); + let a_idx = gate.select_by_indicator(ctx, a, indicator); + a_idx.debug_prank(ctx, prank_output); + }); +} + +fn neg_test_select_from_idx(k: usize, cells: Vec, idx: usize, prank_output: Fr) { + // Check soundness of witness values + let is_valid_witness = prank_output == cells[idx]; + base_test().k(k as u32).expect_satisfied(is_valid_witness).run_gate(|ctx, gate| { + let cells = ctx.assign_witnesses(cells); + let idx_val = gate.select_from_idx(ctx, cells, Witness(Fr::from(idx as u64))); + idx_val.debug_prank(ctx, prank_output); + }); +} + +fn neg_test_inner_product(k: usize, a: Vec, b: Vec, prank_output: Fr) { + let is_valid_witness = prank_output == utils::inner_product_ground_truth(&a, &b); + base_test().k(k as u32).expect_satisfied(is_valid_witness).run_gate(|ctx, gate| { + let a = ctx.assign_witnesses(a); + let inner_product = gate.inner_product(ctx, a, b.into_iter().map(Witness)); + inner_product.debug_prank(ctx, prank_output); + }); +} + +fn neg_test_inner_product_left_last( + k: usize, + a: Vec, + b: Vec, + (prank_output, prank_a_last): (Fr, Fr), +) { + let is_valid_witness = prank_output == utils::inner_product_ground_truth(&a, &b) + && prank_a_last == *a.last().unwrap(); + base_test().k(k as u32).expect_satisfied(is_valid_witness).run_gate(|ctx, gate| { + let a = ctx.assign_witnesses(a); + let (inner_product, a_last) = + gate.inner_product_left_last(ctx, a, b.into_iter().map(Witness)); + inner_product.debug_prank(ctx, prank_output); + a_last.debug_prank(ctx, prank_a_last); + }); +} + +// Range Check + +fn neg_test_range_check(k: usize, range_bits: usize, lookup_bits: usize, rand_a: Fr) { + let correct = fe_to_biguint(&rand_a).bits() <= range_bits as u64; + base_test().k(k as u32).lookup_bits(lookup_bits).expect_satisfied(correct).run(|ctx, range| { + let a_witness = ctx.load_witness(rand_a); + range.range_check(ctx, a_witness, range_bits); + }) +} + +// TODO: expand to prank output of is_less_than_safe() +fn neg_test_is_less_than_safe(k: usize, b: u64, lookup_bits: usize, rand_a: Fr, prank_out: bool) { + let a_big = fe_to_biguint(&rand_a); + let is_lt = a_big < BigUint::from(b); + let correct = (is_lt == prank_out) + && (a_big.bits() as usize <= bit_length(b).div_ceil(lookup_bits) * lookup_bits); // circuit should always fail if `a` doesn't pass range check + + base_test().k(k as u32).lookup_bits(lookup_bits).expect_satisfied(correct).run(|ctx, range| { + let a_witness = ctx.load_witness(rand_a); + let out = range.is_less_than_safe(ctx, a_witness, b); + out.debug_prank(ctx, Fr::from(prank_out)); + }); +} + +proptest! { + // Note setting the minimum value of k to 8 is intentional as it is the smallest value that will not cause an `out of columns` error. Should be noted that filtering by len * (number cells per iteration) < 2^k leads to the filtering of to many cases and the failure of the tests w/o any runs. + #[test] + fn prop_test_neg_idx_to_indicator((k, len, idx, witness_vals) in idx_to_indicator_strat((10,20),100)) { + neg_test_idx_to_indicator(k, len, idx, witness_vals.as_slice()); + } + + #[test] + fn prop_test_neg_select((k, a, b, sel, rand_output) in select_strat((10,20))) { + neg_test_select(k, a, b, sel, rand_output); + } + + #[test] + fn prop_test_neg_select_by_indicator((k, a, idx, rand_output) in select_by_indicator_strat((12,20),100)) { + neg_test_select_by_indicator(k, a, idx, rand_output); + } + + #[test] + fn prop_test_neg_select_from_idx((k, cells, idx, rand_output) in select_from_idx_strat((10,20),100)) { + neg_test_select_from_idx(k, cells, idx, rand_output); + } + + #[test] + fn prop_test_neg_inner_product((k, a, b, rand_output) in inner_product_strat((10,20),100)) { + neg_test_inner_product(k, a, b, rand_output); + } + + #[test] + fn prop_test_neg_inner_product_left_last((k, a, b, rand_output) in inner_product_left_last_strat((10,20),100)) { + neg_test_inner_product_left_last(k, a, b, rand_output); + } + + #[test] + fn prop_test_neg_range_check((k, range_bits, lookup_bits, rand_a) in range_check_strat((10,23),90)) { + neg_test_range_check(k, range_bits, lookup_bits, rand_a); + } + + #[test] + fn prop_test_neg_is_less_than_safe((k, b, lookup_bits, rand_a, out) in is_less_than_safe_strat((10,20))) { + neg_test_is_less_than_safe(k, b, lookup_bits, rand_a, out); + } +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/tests/pos_prop.rs b/vendor/halo2-lib/halo2-base/src/gates/tests/pos_prop.rs new file mode 100644 index 00000000..9b8e96b2 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/tests/pos_prop.rs @@ -0,0 +1,380 @@ +use std::cmp::max; + +use crate::ff::{Field, PrimeField}; +use crate::gates::tests::{flex_gate, range, utils::*, Fr}; +use crate::utils::{biguint_to_fe, bit_length, fe_to_biguint}; +use crate::{QuantumCell, QuantumCell::Witness}; + +use num_bigint::{BigUint, RandBigInt, RandomBits}; +use proptest::{collection::vec, prelude::*}; +use rand::rngs::StdRng; +use rand::SeedableRng; + +prop_compose! { + pub fn rand_fr()(seed in any::()) -> Fr { + let rng = StdRng::seed_from_u64(seed); + Fr::random(rng) + } +} + +prop_compose! { + pub fn rand_witness()(seed in any::()) -> QuantumCell { + let rng = StdRng::seed_from_u64(seed); + Witness(Fr::random(rng)) + } +} + +prop_compose! { + pub fn sum_products_with_coeff_and_var_strat(max_length: usize)(val in vec((rand_fr(), rand_witness(), rand_witness()), 1..=max_length), witness in rand_witness()) -> (Vec<(Fr, QuantumCell, QuantumCell)>, QuantumCell) { + (val, witness) + } +} + +prop_compose! { + pub fn rand_bin_witness()(val in prop::sample::select(vec![Fr::zero(), Fr::one()])) -> QuantumCell { + Witness(val) + } +} + +prop_compose! { + pub fn rand_fr_range(bits: u64)(seed in any::()) -> Fr { + let mut rng = StdRng::seed_from_u64(seed); + let n = rng.sample(RandomBits::new(bits)); + biguint_to_fe(&n) + } +} + +prop_compose! { + pub fn rand_witness_range(bits: u64)(x in rand_fr_range(bits)) -> QuantumCell { + Witness(x) + } +} + +prop_compose! { + fn lookup_strat((k_lo, k_hi): (usize, usize), min_lookup_bits: usize) + (k in k_lo..=k_hi) + (k in Just(k), lookup_bits in min_lookup_bits..k) + -> (usize, usize) { + (k, lookup_bits) + } +} +// k is in [k_lo, k_hi] +// lookup_bits is in [min_lookup_bits, k-1] +prop_compose! { + fn range_check_strat((k_lo, k_hi): (usize, usize), min_lookup_bits: usize, max_range_bits: u64) + ((k, lookup_bits) in lookup_strat((k_lo,k_hi), min_lookup_bits), range_bits in 2..=max_range_bits) + (k in Just(k), lookup_bits in Just(lookup_bits), a in rand_fr_range(range_bits), range_bits in Just(range_bits)) + -> (usize, usize, Fr, usize) { + (k, lookup_bits, a, range_bits as usize) + } +} + +prop_compose! { + fn check_less_than_strat((k_lo, k_hi): (usize, usize), min_lookup_bits: usize, max_num_bits: usize) + (num_bits in 2..max_num_bits, k in k_lo..=k_hi) + (k in Just(k), num_bits in Just(num_bits), lookup_bits in min_lookup_bits..k, seed in any::()) + -> (usize, usize, Fr, Fr, usize) { + let mut rng = StdRng::seed_from_u64(seed); + let mut b = rng.sample(RandomBits::new(num_bits as u64)); + if b == BigUint::from(0u32) { + b = BigUint::from(1u32) + } + let a = rng.gen_biguint_below(&b); + let [a,b] = [a,b].map(|x| biguint_to_fe(&x)); + (k, lookup_bits, a, b, num_bits) + } +} + +prop_compose! { + fn check_less_than_safe_strat((k_lo, k_hi): (usize, usize), min_lookup_bits: usize) + (k in k_lo..=k_hi, b in any::()) + (lookup_bits in min_lookup_bits..k, k in Just(k), a in 0..b, b in Just(b)) + -> (usize, usize, u64, u64) { + (k, lookup_bits, a, b) + } +} + +proptest! { + // Flex Gate Positive Tests + #[test] + fn prop_test_add(input in vec(rand_witness(), 2)) { + let ground_truth = add_ground_truth(input.as_slice()); + let result = flex_gate::test_add(input.as_slice()); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_sub(input in vec(rand_witness(), 2)) { + let ground_truth = sub_ground_truth(input.as_slice()); + let result = flex_gate::test_sub(input.as_slice()); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_sub_mul(input in vec(rand_witness(), 3)) { + let ground_truth = sub_mul_ground_truth(input.as_slice()); + let result = flex_gate::test_sub_mul(input.as_slice()); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_neg(input in rand_witness()) { + let ground_truth = neg_ground_truth(input); + let result = flex_gate::test_neg(input); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_mul(inputs in vec(rand_witness(), 2)) { + let ground_truth = mul_ground_truth(inputs.as_slice()); + let result = flex_gate::test_mul(inputs.as_slice()); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_mul_add(inputs in vec(rand_witness(), 3)) { + let ground_truth = mul_add_ground_truth(inputs.as_slice()); + let result = flex_gate::test_mul_add(inputs.as_slice()); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_mul_not(inputs in vec(rand_witness(), 2)) { + let ground_truth = mul_not_ground_truth(inputs.as_slice()); + let result = flex_gate::test_mul_not(inputs.as_slice()); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_assert_bit(input in rand_fr()) { + let ground_truth = input == Fr::one() || input == Fr::zero(); + flex_gate::test_assert_bit(input, ground_truth); + } + + // Note: due to unwrap after inversion this test will fail if the denominator is zero so we want to test for that. Therefore we do not filter for zero values. + #[test] + fn prop_test_div_unsafe(inputs in vec(rand_witness().prop_filter("Input cannot be 0",|x| *x.value() != Fr::zero()), 2)) { + let ground_truth = div_unsafe_ground_truth(inputs.as_slice()); + let result = flex_gate::test_div_unsafe(inputs.as_slice()); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_assert_is_const(input in rand_fr()) { + flex_gate::test_assert_is_const(&[input; 2]); + } + + #[test] + fn prop_test_inner_product(inputs in (vec(rand_witness(), 0..=100), vec(rand_witness(), 0..=100)).prop_filter("Input vectors must have equal length", |(a, b)| a.len() == b.len())) { + let a = inputs.0.iter().map(|x| *x.value()).collect::>(); + let b = inputs.1.iter().map(|x| *x.value()).collect::>(); + let ground_truth = inner_product_ground_truth(&a, &b); + let result = flex_gate::test_inner_product(inputs); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_inner_product_left_last(inputs in (vec(rand_witness(), 1..=100), vec(rand_witness(), 1..=100)).prop_filter("Input vectors must have equal length", |(a, b)| a.len() == b.len())) { + let a = inputs.0.iter().map(|x| *x.value()).collect::>(); + let b = inputs.1.iter().map(|x| *x.value()).collect::>(); + let ground_truth = inner_product_left_last_ground_truth(&a, &b); + let result = flex_gate::test_inner_product_left_last(inputs); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_inner_product_with_sums(inputs in (vec(rand_witness(), 0..=10), vec(rand_witness(), 1..=100)).prop_filter("Input vectors must have equal length", |(a, b)| a.len() == b.len())) { + let ground_truth = inner_product_with_sums_ground_truth(&inputs); + let result = flex_gate::test_inner_product_with_sums(inputs); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_sum_products_with_coeff_and_var(input in sum_products_with_coeff_and_var_strat(100)) { + let expected = sum_products_with_coeff_and_var_ground_truth(&input); + let output = flex_gate::test_sum_products_with_coeff_and_var(input); + prop_assert_eq!(expected, output); + } + + #[test] + fn prop_test_and(inputs in vec(rand_witness(), 2)) { + let ground_truth = and_ground_truth(inputs.as_slice()); + let result = flex_gate::test_and(inputs.as_slice()); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_not(input in rand_witness()) { + let ground_truth = not_ground_truth(&input); + let result = flex_gate::test_not(input); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_select(vals in vec(rand_witness(), 2), sel in rand_bin_witness()) { + let inputs = vec![vals[0], vals[1], sel]; + let ground_truth = select_ground_truth(inputs.as_slice()); + let result = flex_gate::test_select(inputs.as_slice()); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_or_and(inputs in vec(rand_witness(), 3)) { + let ground_truth = or_and_ground_truth(inputs.as_slice()); + let result = flex_gate::test_or_and(inputs.as_slice()); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_idx_to_indicator(input in (rand_witness(), 1..=16_usize)) { + let ground_truth = idx_to_indicator_ground_truth(input); + let result = flex_gate::test_idx_to_indicator(input.0, input.1); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_select_by_indicator(inputs in (vec(rand_witness(), 1..=10), rand_witness())) { + let ground_truth = select_by_indicator_ground_truth(&inputs); + let result = flex_gate::test_select_by_indicator(inputs.0, inputs.1); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_select_from_idx(inputs in (vec(rand_witness(), 1..=10), rand_witness())) { + let ground_truth = select_from_idx_ground_truth(&inputs); + let result = flex_gate::test_select_from_idx(inputs.0, inputs.1); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_is_zero(x in rand_fr()) { + let ground_truth = is_zero_ground_truth(x); + let result = flex_gate::test_is_zero(x); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_is_equal(inputs in vec(rand_witness(), 2)) { + let ground_truth = is_equal_ground_truth(inputs.as_slice()); + let result = flex_gate::test_is_equal(inputs.as_slice()); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_num_to_bits(num in any::()) { + let mut tmp = num; + let mut bits = vec![]; + if num == 0 { + bits.push(0); + } + while tmp > 0 { + bits.push(tmp & 1); + tmp /= 2; + } + let result = flex_gate::test_num_to_bits(num as usize, bits.len()); + prop_assert_eq!(bits.into_iter().map(Fr::from).collect::>(), result); + } + + #[test] + fn prop_test_pow_var(a in rand_fr(), num in any::()) { + let native_res = a.pow_vartime([num]); + let result = flex_gate::test_pow_var(a, BigUint::from(num), Fr::CAPACITY as usize); + prop_assert_eq!(result, native_res); + } + + /* + #[test] + fn prop_test_lagrange_eval(inputs in vec(rand_fr(), 3)) { + } + */ + + // Range Check Property Tests + + #[test] + fn prop_test_is_less_than( + (k, lookup_bits)in lookup_strat((10,18),4), + bits in 1..Fr::CAPACITY as usize, + seed in any::() + ) { + // current is_less_than requires bits to not be too large + prop_assume!((bits.div_ceil(lookup_bits) + 1) * lookup_bits <= Fr::CAPACITY as usize); + let mut rng = StdRng::seed_from_u64(seed); + let a = biguint_to_fe(&rng.sample(RandomBits::new(bits as u64))); + let b = biguint_to_fe(&rng.sample(RandomBits::new(bits as u64))); + let ground_truth = is_less_than_ground_truth((a, b)); + let result = range::test_is_less_than(k, lookup_bits, [Witness(a), Witness(b)], bits); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_is_less_than_safe( + (k, lookup_bits) in lookup_strat((10,18),4), + a in any::(), + b in any::(), + ) { + prop_assume!(bit_length(a) <= bit_length(b)); + let a = Fr::from(a); + let ground_truth = is_less_than_ground_truth((a, Fr::from(b))); + let result = range::test_is_less_than_safe(k, lookup_bits, a, b); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_div_mod( + a in rand_witness(), + b in any::().prop_filter("Non-zero divisor", |x| *x != 0u64) + ) { + let ground_truth = div_mod_ground_truth((*a.value(), b)); + let num_bits = max(fe_to_biguint(a.value()).bits() as usize, bit_length(b)); + prop_assume!(num_bits <= Fr::CAPACITY as usize); + let result = range::test_div_mod(a, b, num_bits); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_get_last_bit(bits in 1..Fr::CAPACITY as usize, pad_bits in 0..10usize, seed in any::()) { + prop_assume!(bits + pad_bits <= Fr::CAPACITY as usize); + let mut rng = StdRng::seed_from_u64(seed); + let a = rng.sample(RandomBits::new(bits as u64)); + let a = biguint_to_fe(&a); + let ground_truth = get_last_bit_ground_truth(a); + let bits = bits + pad_bits; + let result = range::test_get_last_bit(a, bits); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_div_mod_var(a in rand_fr(), b in any::()) { + let ground_truth = div_mod_ground_truth((a, b)); + let a_num_bits = fe_to_biguint(&a).bits() as usize; + let lookup_bits = 9; + prop_assume!(a_num_bits.div_ceil(lookup_bits) * lookup_bits <= Fr::CAPACITY as usize); + let b_num_bits= bit_length(b); + let result = range::test_div_mod_var(Witness(a), Witness(Fr::from(b)), a_num_bits, b_num_bits); + prop_assert_eq!(result, ground_truth); + } + + #[test] + fn prop_test_range_check((k, lookup_bits, a, range_bits) in range_check_strat((14,22),3,253)) { + // current range check only works when range_bits isn't too big: + prop_assume!(range_bits.div_ceil(lookup_bits) * lookup_bits <= Fr::CAPACITY as usize); + range::test_range_check(k, lookup_bits, a, range_bits); + } + + #[test] + fn prop_test_check_less_than((k, lookup_bits, a, b, num_bits) in check_less_than_strat((10,18),8,253)) { + prop_assume!(num_bits.div_ceil(lookup_bits) * lookup_bits <= Fr::CAPACITY as usize); + range::test_check_less_than(k, lookup_bits, Witness(a), Witness(b), num_bits); + } + + #[test] + fn prop_test_check_less_than_safe((k, lookup_bits, a, b) in check_less_than_safe_strat((10,18),3)) { + range::test_check_less_than_safe(k, lookup_bits, Fr::from(a), b); + } + + #[test] + fn prop_test_check_big_less_than_safe((k, lookup_bits, a, b, num_bits) in check_less_than_strat((18,22),8,253)) { + prop_assume!(num_bits.div_ceil(lookup_bits) * lookup_bits <= Fr::CAPACITY as usize); + range::test_check_big_less_than_safe(k, lookup_bits, a, fe_to_biguint(&b)); + } +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/tests/range.rs b/vendor/halo2-lib/halo2-base/src/gates/tests/range.rs new file mode 100644 index 00000000..d477d3f2 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/tests/range.rs @@ -0,0 +1,108 @@ +use super::*; +use crate::utils::biguint_to_fe; +use crate::utils::testing::base_test; +use crate::QuantumCell::Witness; +use crate::{gates::range::RangeInstructions, QuantumCell}; +use num_bigint::BigUint; +use test_case::test_case; + +#[test_case(16, 10, Fr::zero(), 0; "range_check() 0 bits")] +#[test_case(16, 10, Fr::from(100), 8; "range_check() pos")] +pub fn test_range_check(k: usize, lookup_bits: usize, a_val: Fr, range_bits: usize) { + base_test().k(k as u32).lookup_bits(lookup_bits).run(|ctx, chip| { + let a = ctx.load_witness(a_val); + chip.range_check(ctx, a, range_bits); + }) +} + +#[test_case(12, 10, Witness(Fr::zero()), Witness(Fr::one()), 64; "check_less_than() pos")] +pub fn test_check_less_than( + k: usize, + lookup_bits: usize, + a: QuantumCell, + b: QuantumCell, + num_bits: usize, +) { + base_test().k(k as u32).lookup_bits(lookup_bits).run(|ctx, chip| { + chip.check_less_than(ctx, a, b, num_bits); + }) +} + +#[test_case(10, 8, Fr::zero(), 1; "check_less_than_safe() pos")] +pub fn test_check_less_than_safe(k: usize, lookup_bits: usize, a: Fr, b: u64) { + base_test().k(k as u32).lookup_bits(lookup_bits).run(|ctx, chip| { + let a = ctx.load_witness(a); + chip.check_less_than_safe(ctx, a, b); + }) +} + +#[test_case(10, 8, biguint_to_fe(&BigUint::from(2u64).pow(239)), BigUint::from(2u64).pow(240) - 1usize; "check_big_less_than_safe() pos")] +pub fn test_check_big_less_than_safe(k: usize, lookup_bits: usize, a: Fr, b: BigUint) { + base_test().k(k as u32).lookup_bits(lookup_bits).run(|ctx, chip| { + let a = ctx.load_witness(a); + chip.check_big_less_than_safe(ctx, a, b) + }) +} + +#[test_case(10, 8, [6, 7].map(Fr::from).map(Witness), 3 => Fr::from(1); "is_less_than() pos")] +pub fn test_is_less_than( + k: usize, + lookup_bits: usize, + inputs: [QuantumCell; 2], + bits: usize, +) -> Fr { + base_test() + .k(k as u32) + .lookup_bits(lookup_bits) + .run(|ctx, chip| *chip.is_less_than(ctx, inputs[0], inputs[1], bits).value()) +} + +#[test_case(10, 8, Fr::from(2), 3 => Fr::from(1); "is_less_than_safe() pos")] +pub fn test_is_less_than_safe(k: usize, lookup_bits: usize, a: Fr, b: u64) -> Fr { + base_test().k(k as u32).lookup_bits(lookup_bits).run(|ctx, chip| { + let a = ctx.load_witness(a); + let lt = chip.is_less_than_safe(ctx, a, b); + *lt.value() + }) +} + +#[test_case(10, 8, biguint_to_fe(&BigUint::from(2u64).pow(239)), BigUint::from(2u64).pow(240) - 1usize => Fr::from(1); "is_big_less_than_safe() pos")] +pub fn test_is_big_less_than_safe(k: usize, lookup_bits: usize, a: Fr, b: BigUint) -> Fr { + base_test().k(k as u32).lookup_bits(lookup_bits).run(|ctx, chip| { + let a = ctx.load_witness(a); + *chip.is_big_less_than_safe(ctx, a, b).value() + }) +} + +#[test_case(Witness(Fr::from(3)), 2, 2 => (Fr::from(1), Fr::from(1)) ; "div_mod(3, 2)")] +pub fn test_div_mod(a: QuantumCell, b: u64, num_bits: usize) -> (Fr, Fr) { + base_test().run(|ctx, chip| { + let a = chip.div_mod(ctx, a, b, num_bits); + (*a.0.value(), *a.1.value()) + }) +} + +#[test_case(Fr::from(3), 8 => Fr::one() ; "get_last_bit(): 3, 8 bits")] +#[test_case(Fr::from(3), 2 => Fr::one() ; "get_last_bit(): 3, 2 bits")] +#[test_case(Fr::from(0), 2 => Fr::zero() ; "get_last_bit(): 0")] +#[test_case(Fr::from(1), 2 => Fr::one() ; "get_last_bit(): 1")] +#[test_case(Fr::from(2), 2 => Fr::zero() ; "get_last_bit(): 2")] +pub fn test_get_last_bit(a: Fr, bits: usize) -> Fr { + base_test().run(|ctx, chip| { + let a = ctx.load_witness(a); + *chip.get_last_bit(ctx, a, bits).value() + }) +} + +#[test_case(Witness(Fr::from(3)), Witness(Fr::from(2)), 3, 3 => (Fr::one(), Fr::one()); "div_mod_var(3 ,2)")] +pub fn test_div_mod_var( + a: QuantumCell, + b: QuantumCell, + a_num_bits: usize, + b_num_bits: usize, +) -> (Fr, Fr) { + base_test().run(|ctx, chip| { + let a = chip.div_mod_var(ctx, a, b, a_num_bits, b_num_bits); + (*a.0.value(), *a.1.value()) + }) +} diff --git a/vendor/halo2-lib/halo2-base/src/gates/tests/utils.rs b/vendor/halo2-lib/halo2-base/src/gates/tests/utils.rs new file mode 100644 index 00000000..2b8eb10a --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/gates/tests/utils.rs @@ -0,0 +1,184 @@ +#![allow(clippy::type_complexity)] +use num_integer::Integer; + +use crate::utils::biguint_to_fe; +use crate::utils::fe_to_biguint; +use crate::utils::BigPrimeField; +use crate::utils::ScalarField; +use crate::QuantumCell; + +// Ground truth functions + +// Flex Gate Ground Truths + +pub fn add_ground_truth(inputs: &[QuantumCell]) -> F { + *inputs[0].value() + *inputs[1].value() +} + +pub fn sub_ground_truth(inputs: &[QuantumCell]) -> F { + *inputs[0].value() - *inputs[1].value() +} + +pub fn sub_mul_ground_truth(inputs: &[QuantumCell]) -> F { + *inputs[0].value() - *inputs[1].value() * *inputs[2].value() +} + +pub fn neg_ground_truth(input: QuantumCell) -> F { + -(*input.value()) +} + +pub fn mul_ground_truth(inputs: &[QuantumCell]) -> F { + *inputs[0].value() * *inputs[1].value() +} + +pub fn mul_add_ground_truth(inputs: &[QuantumCell]) -> F { + *inputs[0].value() * *inputs[1].value() + *inputs[2].value() +} + +pub fn mul_not_ground_truth(inputs: &[QuantumCell]) -> F { + (F::ONE - *inputs[0].value()) * *inputs[1].value() +} + +pub fn div_unsafe_ground_truth(inputs: &[QuantumCell]) -> F { + inputs[1].value().invert().unwrap() * *inputs[0].value() +} + +pub fn inner_product_ground_truth(a: &[F], b: &[F]) -> F { + a.iter().zip(b.iter()).fold(F::ZERO, |acc, (&a, &b)| acc + a * b) +} + +pub fn inner_product_left_last_ground_truth(a: &[F], b: &[F]) -> (F, F) { + let product = inner_product_ground_truth(a, b); + let last = *a.last().unwrap(); + (product, last) +} + +pub fn inner_product_with_sums_ground_truth( + input: &(Vec>, Vec>), +) -> Vec { + let (a, b) = &input; + let mut result = Vec::new(); + let mut sum = F::ZERO; + // TODO: convert to fold + for (ai, bi) in a.iter().zip(b) { + let product = *ai.value() * *bi.value(); + sum += product; + result.push(sum); + } + result +} + +pub fn sum_products_with_coeff_and_var_ground_truth( + input: &(Vec<(F, QuantumCell, QuantumCell)>, QuantumCell), +) -> F { + let expected = + input.0.iter().fold(F::ZERO, |acc, (coeff, cell1, cell2)| { + acc + *coeff * *cell1.value() * *cell2.value() + }) + *input.1.value(); + expected +} + +pub fn and_ground_truth(inputs: &[QuantumCell]) -> F { + *inputs[0].value() * *inputs[1].value() +} + +pub fn not_ground_truth(a: &QuantumCell) -> F { + F::ONE - *a.value() +} + +pub fn select_ground_truth(inputs: &[QuantumCell]) -> F { + (*inputs[0].value() - inputs[1].value()) * *inputs[2].value() + *inputs[1].value() +} + +pub fn or_and_ground_truth(inputs: &[QuantumCell]) -> F { + let bc_val = *inputs[1].value() * inputs[2].value(); + bc_val + inputs[0].value() - bc_val * inputs[0].value() +} + +pub fn idx_to_indicator_ground_truth(inputs: (QuantumCell, usize)) -> Vec { + let (idx, size) = inputs; + let mut indicator = vec![F::ZERO; size]; + let mut idx_value = size + 1; + for i in 0..size as u64 { + if F::from(i) == *idx.value() { + idx_value = i as usize; + break; + } + } + if idx_value < size { + indicator[idx_value] = F::ONE; + } + indicator +} + +pub fn select_by_indicator_ground_truth( + inputs: &(Vec>, QuantumCell), +) -> F { + let mut idx_value = inputs.0.len() + 1; + let mut indicator = vec![F::ZERO; inputs.0.len()]; + for i in 0..inputs.0.len() as u64 { + if F::from(i) == *inputs.1.value() { + idx_value = i as usize; + break; + } + } + if idx_value < inputs.0.len() { + indicator[idx_value] = F::ONE; + } + // take cross product of indicator and inputs.0 + inputs.0.iter().zip(indicator.iter()).fold(F::ZERO, |acc, (a, b)| acc + (*a.value() * *b)) +} + +pub fn select_from_idx_ground_truth( + inputs: &(Vec>, QuantumCell), +) -> F { + let idx = inputs.1.value(); + // Since F does not implement From, we have to iterate and find the matching index + for i in 0..inputs.0.len() as u64 { + if F::from(i) == *idx { + return *inputs.0[i as usize].value(); + } + } + F::ZERO +} + +pub fn is_zero_ground_truth(x: F) -> F { + if x.is_zero().into() { + F::ONE + } else { + F::ZERO + } +} + +pub fn is_equal_ground_truth(inputs: &[QuantumCell]) -> F { + if inputs[0].value() == inputs[1].value() { + F::ONE + } else { + F::ZERO + } +} + +/* +pub fn lagrange_eval_ground_truth(inputs: &[F]) -> (F, F) { +} +*/ + +// Range Chip Ground Truths + +pub fn is_less_than_ground_truth(inputs: (F, F)) -> F { + if inputs.0 < inputs.1 { + F::ONE + } else { + F::ZERO + } +} + +pub fn div_mod_ground_truth(inputs: (F, u64)) -> (F, F) { + let a = fe_to_biguint(&inputs.0); + let (div, rem) = a.div_mod_floor(&inputs.1.into()); + (biguint_to_fe(&div), biguint_to_fe(&rem)) +} + +pub fn get_last_bit_ground_truth(input: F) -> F { + F::from(input.get_lower_32() & 1 == 1) +} diff --git a/vendor/halo2-lib/halo2-base/src/lib.rs b/vendor/halo2-lib/halo2-base/src/lib.rs new file mode 100644 index 00000000..27c036f1 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/lib.rs @@ -0,0 +1,486 @@ +//! Base library to build Halo2 circuits. +#![allow(incomplete_features)] +#![deny(clippy::perf)] +#![allow(clippy::too_many_arguments)] +#![warn(clippy::default_numeric_fallback)] +#![warn(missing_docs)] + +use getset::CopyGetters; +use itertools::Itertools; +// Different memory allocator options: +#[cfg(feature = "jemallocator")] +use jemallocator::Jemalloc; +#[cfg(feature = "jemallocator")] +#[global_allocator] +static GLOBAL: Jemalloc = Jemalloc; + +// mimalloc is fastest on Mac M2 +#[cfg(feature = "mimalloc")] +use mimalloc::MiMalloc; +#[cfg(feature = "mimalloc")] +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + +#[cfg(all(feature = "halo2-pse", feature = "halo2-axiom"))] +compile_error!( + "Cannot have both \"halo2-pse\" and \"halo2-axiom\" features enabled at the same time!" +); +#[cfg(not(any(feature = "halo2-pse", feature = "halo2-axiom")))] +compile_error!("Must enable exactly one of \"halo2-pse\" or \"halo2-axiom\" features to choose which halo2_proofs crate to use."); + +// use gates::flex_gate::MAX_PHASE; +#[cfg(feature = "halo2-pse")] +pub use halo2_proofs; +#[cfg(feature = "halo2-axiom")] +pub use halo2_proofs_axiom as halo2_proofs; + +use halo2_proofs::halo2curves::ff; +use halo2_proofs::plonk::Assigned; +use utils::ScalarField; +use virtual_region::copy_constraints::SharedCopyConstraintManager; + +/// Module that contains the main API for creating and working with circuits. +/// `gates` is misleading because we currently only use one custom gate throughout. +pub mod gates; +/// Module for the Poseidon hash function. +pub mod poseidon; +/// Module for SafeType which enforce value range and realted functions. +pub mod safe_types; +/// Utility functions for converting between different types of field elements. +pub mod utils; +pub mod virtual_region; + +/// Constant representing whether the Layouter calls `synthesize` once just to get region shape. +#[cfg(feature = "halo2-axiom")] +pub const SKIP_FIRST_PASS: bool = false; +/// Constant representing whether the Layouter calls `synthesize` once just to get region shape. +#[cfg(feature = "halo2-pse")] +pub const SKIP_FIRST_PASS: bool = true; + +/// Convenience Enum which abstracts the scenarios under a value is added to an advice column. +#[derive(Clone, Copy, Debug)] +pub enum QuantumCell { + /// An [AssignedValue] already existing in the advice column (e.g., a witness value that was already assigned in a previous cell in the column). + /// * Assigns a new cell into the advice column with value equal to the value of a. + /// * Imposes an equality constraint between the new cell and the cell of a so the Verifier guarantees that these two cells are always equal. + Existing(AssignedValue), + // This is a guard for witness values assigned after pkey generation. We do not use `Value` api anymore. + /// A non-existing witness [ScalarField] value (e.g. private input) to add to an advice column. + Witness(F), + /// A non-existing witness [ScalarField] marked as a fraction for optimization in batch inversion later. + WitnessFraction(Assigned), + /// A known constant value added as a witness value to the advice column and added to the "Fixed" column during circuit creation time. + /// * Visible to both the Prover and the Verifier. + /// * Imposes an equality constraint between the two corresponding cells in the advice and fixed columns. + Constant(F), +} + +impl From> for QuantumCell { + /// Converts an [`AssignedValue`] into a [`QuantumCell`] of enum variant `Existing`. + fn from(a: AssignedValue) -> Self { + Self::Existing(a) + } +} + +impl QuantumCell { + /// Returns an immutable reference to the underlying [ScalarField] value of a [`QuantumCell`]. + /// + /// Panics if the [`QuantumCell`] is of type `WitnessFraction`. + pub fn value(&self) -> &F { + match self { + Self::Existing(a) => a.value(), + Self::Witness(a) => a, + Self::WitnessFraction(_) => { + panic!("Trying to get value of a fraction before batch inversion") + } + Self::Constant(a) => a, + } + } +} + +/// Unique tag for a context across all virtual regions. +/// In the form `(type_id, context_id)` where `type_id` should be a unique identifier +/// for the virtual region this context belongs to, and `context_id` is a counter local to that virtual region. +pub type ContextTag = (&'static str, usize); + +/// Pointer to the position of a cell at `offset` in an advice column within a [Context] of `context_id`. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct ContextCell { + /// The unique string identifier of the virtual region that this cell belongs to. + pub type_id: &'static str, + /// Identifier of the [Context] that this cell belongs to. + pub context_id: usize, + /// Relative offset of the cell within this [Context] advice column. + pub offset: usize, +} + +impl ContextCell { + /// Creates a new [ContextCell] with the given `type_id`, `context_id`, and `offset`. + /// + /// **Warning:** If you create your own `Context` in a new virtual region not provided by our libraries, you must ensure that the `type_id: &str` of the context is a globally unique identifier for the virtual region, distinct from the other `type_id` strings used to identify other virtual regions. We suggest that you either include your crate name as a prefix in the `type_id` or use [`module_path!`](https://doc.rust-lang.org/std/macro.module_path.html) to generate a prefix. + /// In the future we will introduce a macro to check this uniqueness at compile time. + pub fn new(type_id: &'static str, context_id: usize, offset: usize) -> Self { + Self { type_id, context_id, offset } + } +} + +/// Pointer containing cell value and location within [Context]. +/// +/// Note: Performs a copy of the value, should only be used when you are about to assign the value again elsewhere. +#[derive(Clone, Copy, Debug)] +pub struct AssignedValue { + /// Value of the cell. + pub value: Assigned, // we don't use reference to avoid issues with lifetimes (you can't safely borrow from vector and push to it at the same time). + // only needed during vkey, pkey gen to fetch the actual cell from the relevant context + /// [ContextCell] pointer to the cell the value is assigned to within an advice column of a [Context]. + pub cell: Option, +} + +impl AssignedValue { + /// Returns an immutable reference to the underlying value of an [`AssignedValue`]. + /// + /// Panics if the witness value is of type [Assigned::Rational] or [Assigned::Zero]. + pub fn value(&self) -> &F { + match &self.value { + Assigned::Trivial(a) => a, + _ => unreachable!(), // if trying to fetch an un-evaluated fraction, you will have to do something manual + } + } + + /// Debug helper function for writing negative tests. This will change the **witness** value in `ctx` corresponding to `self.offset`. + /// This assumes that `ctx` is the context that `self` lies in. + pub fn debug_prank(&self, ctx: &mut Context, prank_value: F) { + ctx.advice[self.cell.unwrap().offset] = Assigned::Trivial(prank_value); + } +} + +impl AsRef> for AssignedValue { + fn as_ref(&self) -> &AssignedValue { + self + } +} + +/// Represents a single thread of an execution trace. +/// * We keep the naming [Context] for historical reasons. +/// +/// [Context] is CPU thread-local. +#[derive(Clone, Debug, CopyGetters)] +pub struct Context { + /// Flag to determine whether only witness generation or proving and verification key generation is being performed. + /// * If witness gen is performed many operations can be skipped for optimization. + #[getset(get_copy = "pub")] + witness_gen_only: bool, + /// The challenge phase that this [Context] will map to. + #[getset(get_copy = "pub")] + phase: usize, + /// Identifier for what virtual region this context is in. + /// Warning: the circuit writer must ensure that distinct virtual regions have distinct names as strings to prevent possible errors. + /// We do not use [std::any::TypeId] because it is not stable across rust builds or dependencies. + #[getset(get_copy = "pub")] + type_id: &'static str, + /// Identifier to reference cells from this [Context]. + context_id: usize, + + /// Single column of advice cells. + pub advice: Vec>, + + /// Slight optimization: since zero is so commonly used, keep a reference to the zero cell. + zero_cell: Option>, + + // ======================================== + // General principle: we don't need to optimize anything specific to `witness_gen_only == false` because it is only done during keygen + // If `witness_gen_only == false`: + /// [Vec] representing the selector column of this [Context] accompanying each `advice` column + /// * Assumed to have the same length as `advice` + pub selector: Vec, + + /// Global shared thread-safe manager for all copy (equality) constraints between virtual advice, constants, and raw external Halo2 cells. + pub copy_manager: SharedCopyConstraintManager, +} + +impl Context { + /// Creates a new [Context] with the given `context_id` and witness generation enabled/disabled by the `witness_gen_only` flag. + /// * `witness_gen_only`: flag to determine whether public key generation or only witness generation is being performed. + /// * `context_id`: identifier to reference advice cells from this [Context] later. + /// + /// **Warning:** If you create your own `Context` in a new virtual region not provided by our libraries, you must ensure that the `type_id: &str` of the context is a globally unique identifier for the virtual region, distinct from the other `type_id` strings used to identify other virtual regions. We suggest that you either include your crate name as a prefix in the `type_id` or use [`module_path!`](https://doc.rust-lang.org/std/macro.module_path.html) to generate a prefix. + /// In the future we will introduce a macro to check this uniqueness at compile time. + pub fn new( + witness_gen_only: bool, + phase: usize, + type_id: &'static str, + context_id: usize, + copy_manager: SharedCopyConstraintManager, + ) -> Self { + Self { + witness_gen_only, + phase, + type_id, + context_id, + advice: Vec::new(), + selector: Vec::new(), + zero_cell: None, + copy_manager, + } + } + + /// The context id, this can be used as a tag when CPU multi-threading + pub fn id(&self) -> usize { + self.context_id + } + + /// A unique tag that should identify this context across all virtual regions and phases. + pub fn tag(&self) -> ContextTag { + (self.type_id, self.context_id) + } + + fn latest_cell(&self) -> ContextCell { + ContextCell::new(self.type_id, self.context_id, self.advice.len() - 1) + } + + /// Virtually assigns the `input` within the current [Context], with different handling depending on the [QuantumCell] variant. + pub fn assign_cell(&mut self, input: impl Into>) { + // Determine the type of the cell and push it to the relevant vector + match input.into() { + QuantumCell::Existing(acell) => { + self.advice.push(acell.value); + // If witness generation is not performed, enforce equality constraints between the existing cell and the new cell + if !self.witness_gen_only { + let new_cell = self.latest_cell(); + self.copy_manager + .lock() + .unwrap() + .advice_equalities + .push((new_cell, acell.cell.unwrap())); + } + } + QuantumCell::Witness(val) => { + self.advice.push(Assigned::Trivial(val)); + } + QuantumCell::WitnessFraction(val) => { + self.advice.push(val); + } + QuantumCell::Constant(c) => { + self.advice.push(Assigned::Trivial(c)); + // If witness generation is not performed, enforce equality constraints between the existing cell and the new cell + if !self.witness_gen_only { + let new_cell = self.latest_cell(); + self.copy_manager.lock().unwrap().constant_equalities.push((c, new_cell)); + } + } + } + } + + /// Returns the [AssignedValue] of the last cell in the `advice` column of [Context] or [None] if `advice` is empty + pub fn last(&self) -> Option> { + self.advice.last().map(|v| { + let cell = (!self.witness_gen_only).then_some(self.latest_cell()); + AssignedValue { value: *v, cell } + }) + } + + /// Returns the [AssignedValue] of the cell at the given `offset` in the `advice` column of [Context] + /// * `offset`: the offset of the cell to be fetched + /// * `offset` may be negative indexing from the end of the column (e.g., `-1` is the last cell) + /// * Assumes `offset` is a valid index in `advice`; + /// * `0` <= `offset` < `advice.len()` (or `advice.len() + offset >= 0` if `offset` is negative) + pub fn get(&self, offset: isize) -> AssignedValue { + let offset = if offset < 0 { + self.advice.len().wrapping_add_signed(offset) + } else { + offset as usize + }; + assert!(offset < self.advice.len()); + let cell = (!self.witness_gen_only).then_some(ContextCell::new( + self.type_id, + self.context_id, + offset, + )); + AssignedValue { value: self.advice[offset], cell } + } + + /// Creates an equality constraint between two `advice` cells. + /// * `a`: the first `advice` cell to be constrained equal + /// * `b`: the second `advice` cell to be constrained equal + /// * Assumes both cells are `advice` cells + pub fn constrain_equal(&mut self, a: &AssignedValue, b: &AssignedValue) { + if !self.witness_gen_only { + self.copy_manager + .lock() + .unwrap() + .advice_equalities + .push((a.cell.unwrap(), b.cell.unwrap())); + } + } + + /// Pushes multiple advice cells to the `advice` column of [Context] and enables them by enabling the corresponding selector specified in `gate_offset`. + /// + /// * `inputs`: Iterator that specifies the cells to be assigned + /// * `gate_offsets`: specifies relative offset from current position to enable selector for the gate (e.g., `0` is `inputs[0]`). + /// * `offset` may be negative indexing from the end of the column (e.g., `-1` is the last previously assigned cell) + pub fn assign_region( + &mut self, + inputs: impl IntoIterator, + gate_offsets: impl IntoIterator, + ) where + Q: Into>, + { + if self.witness_gen_only { + for input in inputs { + self.assign_cell(input); + } + } else { + let row_offset = self.advice.len(); + // note: row_offset may not equal self.selector.len() at this point if we previously used `load_constant` or `load_witness` + for input in inputs { + self.assign_cell(input); + } + self.selector.resize(self.advice.len(), false); + for offset in gate_offsets { + *self + .selector + .get_mut(row_offset.checked_add_signed(offset).expect("Invalid gate offset")) + .expect("Invalid selector offset") = true; + } + } + } + + /// Pushes multiple advice cells to the `advice` column of [Context] and enables them by enabling the corresponding selector specified in `gate_offset` and returns the last assigned cell. + /// + /// Assumes `gate_offsets` is the same length as `inputs` + /// + /// Returns the last assigned cell + /// * `inputs`: Iterator that specifies the cells to be assigned + /// * `gate_offsets`: specifies indices to enable selector for the gate; assume `gate_offsets` is sorted in increasing order + /// * `offset` may be negative indexing from the end of the column (e.g., `-1` is the last cell) + pub fn assign_region_last( + &mut self, + inputs: impl IntoIterator, + gate_offsets: impl IntoIterator, + ) -> AssignedValue + where + Q: Into>, + { + self.assign_region(inputs, gate_offsets); + self.last().unwrap() + } + + /// Pushes multiple advice cells to the `advice` column of [Context] and enables them by enabling the corresponding selector specified in `gate_offset`. + /// + /// Allows for the specification of equality constraints between cells at `equality_offsets` within the `advice` column and external advice cells specified in `external_equality` (e.g, Fixed column). + /// * `gate_offsets`: specifies indices to enable selector for the gate; + /// * `offset` may be negative indexing from the end of the column (e.g., `-1` is the last cell) + /// * `equality_offsets`: specifies pairs of indices to constrain equality + /// * `external_equality`: specifies an existing cell to constrain equality with the cell at a certain index + pub fn assign_region_smart( + &mut self, + inputs: impl IntoIterator, + gate_offsets: impl IntoIterator, + equality_offsets: impl IntoIterator, + external_equality: impl IntoIterator, isize)>, + ) where + Q: Into>, + { + let row_offset = self.advice.len(); + self.assign_region(inputs, gate_offsets); + + // note: row_offset may not equal self.selector.len() at this point if we previously used `load_constant` or `load_witness` + // If not in witness generation mode, add equality constraints. + if !self.witness_gen_only { + // Add equality constraints between cells in the advice column. + for (offset1, offset2) in equality_offsets { + self.copy_manager.lock().unwrap().advice_equalities.push(( + ContextCell::new( + self.type_id, + self.context_id, + row_offset.wrapping_add_signed(offset1), + ), + ContextCell::new( + self.type_id, + self.context_id, + row_offset.wrapping_add_signed(offset2), + ), + )); + } + // Add equality constraints between cells in the advice column and external cells (Fixed column). + for (cell, offset) in external_equality { + self.copy_manager.lock().unwrap().advice_equalities.push(( + cell.unwrap(), + ContextCell::new( + self.type_id, + self.context_id, + row_offset.wrapping_add_signed(offset), + ), + )); + } + } + } + + /// Assigns a region of witness cells in an iterator and returns a [Vec] of assigned cells. + /// * `witnesses`: Iterator that specifies the cells to be assigned + pub fn assign_witnesses( + &mut self, + witnesses: impl IntoIterator, + ) -> Vec> { + let row_offset = self.advice.len(); + self.assign_region(witnesses.into_iter().map(QuantumCell::Witness), []); + self.advice[row_offset..] + .iter() + .enumerate() + .map(|(i, v)| { + let cell = (!self.witness_gen_only).then_some(ContextCell::new( + self.type_id, + self.context_id, + row_offset + i, + )); + AssignedValue { value: *v, cell } + }) + .collect() + } + + /// Assigns a witness value and returns the corresponding assigned cell. + /// * `witness`: the witness value to be assigned + pub fn load_witness(&mut self, witness: F) -> AssignedValue { + self.assign_cell(QuantumCell::Witness(witness)); + if !self.witness_gen_only { + self.selector.resize(self.advice.len(), false); + } + self.last().unwrap() + } + + /// Assigns a constant value and returns the corresponding assigned cell. + /// * `c`: the constant value to be assigned + pub fn load_constant(&mut self, c: F) -> AssignedValue { + self.assign_cell(QuantumCell::Constant(c)); + if !self.witness_gen_only { + self.selector.resize(self.advice.len(), false); + } + self.last().unwrap() + } + + /// Assigns a list of constant values and returns the corresponding assigned cells. + /// * `c`: the list of constant values to be assigned + pub fn load_constants(&mut self, c: &[F]) -> Vec> { + c.iter().map(|v| self.load_constant(*v)).collect_vec() + } + + /// Assigns the 0 value to a new cell or returns a previously assigned zero cell from `zero_cell`. + pub fn load_zero(&mut self) -> AssignedValue { + if let Some(zcell) = &self.zero_cell { + return *zcell; + } + let zero_cell = self.load_constant(F::ZERO); + self.zero_cell = Some(zero_cell); + zero_cell + } + + /// Helper function for debugging using `MockProver`. This adds a constraint that always fails. + /// The `MockProver` will print out the row, column where it fails, so it serves as a debugging "break point" + /// so you can add to your code to search for where the actual constraint failure occurs. + pub fn debug_assert_false(&mut self) { + use rand_chacha::rand_core::OsRng; + let rand1 = self.load_witness(F::random(OsRng)); + let rand2 = self.load_witness(F::random(OsRng)); + self.constrain_equal(&rand1, &rand2); + } +} diff --git a/vendor/halo2-lib/halo2-base/src/poseidon/hasher/mds.rs b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/mds.rs new file mode 100644 index 00000000..91b7d262 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/mds.rs @@ -0,0 +1,172 @@ +#![allow(clippy::needless_range_loop)] +use getset::Getters; + +use crate::ff::PrimeField; + +/// The type used to hold the MDS matrix +pub(crate) type Mds = [[F; T]; T]; + +/// `MDSMatrices` holds the MDS matrix as well as transition matrix which is +/// also called `pre_sparse_mds` and sparse matrices that enables us to reduce +/// number of multiplications in apply MDS step +#[derive(Debug, Clone, Getters)] +pub struct MDSMatrices { + /// MDS matrix + #[getset(get = "pub")] + pub(crate) mds: MDSMatrix, + /// Transition matrix + #[getset(get = "pub")] + pub(crate) pre_sparse_mds: MDSMatrix, + /// Sparse matrices + #[getset(get = "pub")] + pub(crate) sparse_matrices: Vec>, +} + +/// `SparseMDSMatrix` are in `[row], [hat | identity]` form and used in linear +/// layer of partial rounds instead of the original MDS +#[derive(Debug, Clone, Getters)] +pub struct SparseMDSMatrix { + /// row + #[getset(get = "pub")] + pub(crate) row: [F; T], + /// column transpose + #[getset(get = "pub")] + pub(crate) col_hat: [F; RATE], +} + +/// `MDSMatrix` is applied to `State` to achive linear layer of Poseidon +#[derive(Clone, Debug)] +pub struct MDSMatrix(pub(crate) Mds); + +impl AsRef> for MDSMatrix { + fn as_ref(&self) -> &Mds { + &self.0 + } +} + +impl MDSMatrix { + pub(crate) fn mul_vector(&self, v: &[F; T]) -> [F; T] { + let mut res = [F::ZERO; T]; + for i in 0..T { + for j in 0..T { + res[i] += self.0[i][j] * v[j]; + } + } + res + } + + pub(crate) fn identity() -> Mds { + let mut mds = [[F::ZERO; T]; T]; + for i in 0..T { + mds[i][i] = F::ONE; + } + mds + } + + /// Multiplies two MDS matrices. Used in sparse matrix calculations + pub(crate) fn mul(&self, other: &Self) -> Self { + let mut res = [[F::ZERO; T]; T]; + for i in 0..T { + for j in 0..T { + for k in 0..T { + res[i][j] += self.0[i][k] * other.0[k][j]; + } + } + } + Self(res) + } + + pub(crate) fn transpose(&self) -> Self { + let mut res = [[F::ZERO; T]; T]; + for i in 0..T { + for j in 0..T { + res[i][j] = self.0[j][i]; + } + } + Self(res) + } + + pub(crate) fn determinant(m: [[F; N]; N]) -> F { + let mut res = F::ONE; + let mut m = m; + for i in 0..N { + let mut pivot = i; + while m[pivot][i] == F::ZERO { + pivot += 1; + assert!(pivot < N, "matrix is not invertible"); + } + if pivot != i { + res = -res; + m.swap(pivot, i); + } + res *= m[i][i]; + let inv = m[i][i].invert().unwrap(); + for j in i + 1..N { + let factor = m[j][i] * inv; + for k in i + 1..N { + m[j][k] -= m[i][k] * factor; + } + } + } + res + } + + /// See Section B in Supplementary Material https://eprint.iacr.org/2019/458.pdf + /// Factorises an MDS matrix `M` into `M'` and `M''` where `M = M' * M''`. + /// Resulted `M''` matrices are the sparse ones while `M'` will contribute + /// to the accumulator of the process + pub(crate) fn factorise(&self) -> (Self, SparseMDSMatrix) { + assert_eq!(RATE + 1, T); + // Given `(t-1 * t-1)` MDS matrix called `hat` constructs the `t * t` matrix in + // form `[[1 | 0], [0 | m]]`, ie `hat` is the right bottom sub-matrix + let prime = |hat: Mds| -> Self { + let mut prime = Self::identity(); + for (prime_row, hat_row) in prime.iter_mut().skip(1).zip(hat.iter()) { + for (el_prime, el_hat) in prime_row.iter_mut().skip(1).zip(hat_row.iter()) { + *el_prime = *el_hat; + } + } + Self(prime) + }; + + // Given `(t-1)` sized `w_hat` vector constructs the matrix in form + // `[[m_0_0 | m_0_i], [w_hat | identity]]` + let prime_prime = |w_hat: [F; RATE]| -> Mds { + let mut prime_prime = Self::identity(); + prime_prime[0] = self.0[0]; + for (row, w) in prime_prime.iter_mut().skip(1).zip(w_hat.iter()) { + row[0] = *w + } + prime_prime + }; + + let w = self.0.iter().skip(1).map(|row| row[0]).collect::>(); + // m_hat is the `(t-1 * t-1)` right bottom sub-matrix of m := self.0 + let mut m_hat = [[F::ZERO; RATE]; RATE]; + for i in 0..RATE { + for j in 0..RATE { + m_hat[i][j] = self.0[i + 1][j + 1]; + } + } + // w_hat = m_hat^{-1} * w, where m_hat^{-1} is matrix inverse and * is matrix mult + // we avoid computing m_hat^{-1} explicitly by using Cramer's rule: https://en.wikipedia.org/wiki/Cramer%27s_rule + let mut w_hat = [F::ZERO; RATE]; + let det = Self::determinant(m_hat); + let det_inv = Option::::from(det.invert()).expect("matrix is not invertible"); + for j in 0..RATE { + let mut m_hat_j = m_hat; + for i in 0..RATE { + m_hat_j[i][j] = w[i]; + } + w_hat[j] = Self::determinant(m_hat_j) * det_inv; + } + let m_prime = prime(m_hat); + let m_prime_prime = prime_prime(w_hat); + // row = first row of m_prime_prime.transpose() = first column of m_prime_prime + let row: [F; T] = + m_prime_prime.iter().map(|row| row[0]).collect::>().try_into().unwrap(); + // col_hat = first column of m_prime_prime.transpose() without first element = first row of m_prime_prime without first element + let col_hat: [F; RATE] = m_prime_prime[0][1..].try_into().unwrap(); + (m_prime, SparseMDSMatrix { row, col_hat }) + } +} diff --git a/vendor/halo2-lib/halo2-base/src/poseidon/hasher/mod.rs b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/mod.rs new file mode 100644 index 00000000..9cd70040 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/mod.rs @@ -0,0 +1,361 @@ +use crate::{ + gates::{GateInstructions, RangeInstructions}, + poseidon::hasher::{spec::OptimizedPoseidonSpec, state::PoseidonState}, + safe_types::{SafeBool, SafeTypeChip}, + utils::BigPrimeField, + AssignedValue, Context, + QuantumCell::Constant, + ScalarField, +}; + +use getset::{CopyGetters, Getters}; +use num_bigint::BigUint; +use std::{cell::OnceCell, mem}; + +#[cfg(test)] +mod tests; + +/// Module for maximum distance separable matrix operations. +pub mod mds; +/// Module for poseidon specification. +pub mod spec; +/// Module for poseidon states. +pub mod state; + +/// Stateless Poseidon hasher. +#[derive(Clone, Debug, Getters)] +pub struct PoseidonHasher { + /// Spec, contains round constants and optimized matrices. + #[getset(get = "pub")] + spec: OptimizedPoseidonSpec, + consts: OnceCell>, +} +#[derive(Clone, Debug, Getters)] +struct PoseidonHasherConsts { + #[getset(get = "pub")] + init_state: PoseidonState, + // hash of an empty input(""). + #[getset(get = "pub")] + empty_hash: AssignedValue, +} + +impl PoseidonHasherConsts { + pub fn new( + ctx: &mut Context, + gate: &impl GateInstructions, + spec: &OptimizedPoseidonSpec, + ) -> Self { + let init_state = PoseidonState::default(ctx); + let mut state = init_state.clone(); + let empty_hash = fix_len_array_squeeze(ctx, gate, &[], &mut state, spec); + Self { init_state, empty_hash } + } +} + +/// 1 logical row of compact input for Poseidon hasher. +#[derive(Copy, Clone, Debug, Getters, CopyGetters)] +pub struct PoseidonCompactInput { + /// Right padded inputs. No constrains on paddings. + #[getset(get = "pub")] + inputs: [AssignedValue; RATE], + /// is_final = 1 triggers squeeze. + #[getset(get_copy = "pub")] + is_final: SafeBool, + /// Length of `inputs`. + #[getset(get_copy = "pub")] + len: AssignedValue, +} + +impl PoseidonCompactInput { + /// Create a new PoseidonCompactInput. + pub fn new( + inputs: [AssignedValue; RATE], + is_final: SafeBool, + len: AssignedValue, + ) -> Self { + Self { inputs, is_final, len } + } + + /// Add data validation constraints. + pub fn add_validation_constraints( + &self, + ctx: &mut Context, + range: &impl RangeInstructions, + ) { + range.is_less_than_safe(ctx, self.len, (RATE + 1) as u64); + // Invalid case: (!is_final && len != RATE) ==> !(is_final || len == RATE) + let is_full: AssignedValue = + range.gate().is_equal(ctx, self.len, Constant(F::from(RATE as u64))); + let invalid_cond = range.gate().or(ctx, *self.is_final.as_ref(), is_full); + range.gate().assert_is_const(ctx, &invalid_cond, &F::ZERO); + } +} + +/// A compact chunk input for Poseidon hasher. The end of a logical input could only be at the boundary of a chunk. +#[derive(Clone, Debug, Getters, CopyGetters)] +pub struct PoseidonCompactChunkInput { + /// Inputs of a chunk. All witnesses will be absorbed. + #[getset(get = "pub")] + inputs: Vec<[AssignedValue; RATE]>, + /// is_final = 1 triggers squeeze. + #[getset(get_copy = "pub")] + is_final: SafeBool, +} + +impl PoseidonCompactChunkInput { + /// Create a new PoseidonCompactInput. + pub fn new(inputs: Vec<[AssignedValue; RATE]>, is_final: SafeBool) -> Self { + Self { inputs, is_final } + } +} + +/// 1 logical row of compact output for Poseidon hasher. +#[derive(Copy, Clone, Debug, CopyGetters)] +pub struct PoseidonCompactOutput { + /// hash of 1 logical input. + #[getset(get_copy = "pub")] + hash: AssignedValue, + /// is_final = 1 ==> this is the end of a logical input. + #[getset(get_copy = "pub")] + is_final: SafeBool, +} + +impl PoseidonHasher { + /// Create a poseidon hasher from an existing spec. + pub fn new(spec: OptimizedPoseidonSpec) -> Self { + Self { spec, consts: OnceCell::new() } + } + /// Initialize necessary consts of hasher. Must be called before any computation. + pub fn initialize_consts(&mut self, ctx: &mut Context, gate: &impl GateInstructions) { + self.consts.get_or_init(|| PoseidonHasherConsts::::new(ctx, gate, &self.spec)); + } + + /// Clear all consts. + pub fn clear(&mut self) { + self.consts.take(); + } + + fn empty_hash(&self) -> &AssignedValue { + self.consts.get().unwrap().empty_hash() + } + fn init_state(&self) -> &PoseidonState { + self.consts.get().unwrap().init_state() + } + + /// Constrains and returns hash of a witness array with a variable length. + /// + /// Assumes `len` is within [usize] and `len <= inputs.len()`. + /// * inputs: An right-padded array of [AssignedValue]. Constraints on paddings are not required. + /// * len: Length of `inputs`. + /// + /// Return hash of `inputs`. + pub fn hash_var_len_array( + &self, + ctx: &mut Context, + range: &impl RangeInstructions, + inputs: &[AssignedValue], + len: AssignedValue, + ) -> AssignedValue + where + F: BigPrimeField, + { + // TODO: rewrite this using hash_compact_input. + let max_len = inputs.len(); + if max_len == 0 { + return *self.empty_hash(); + }; + + // len <= max_len --> num_of_bits(len) <= num_of_bits(max_len) + let num_bits = (usize::BITS - max_len.leading_zeros()) as usize; + // num_perm = len // RATE + 1, len_last_chunk = len % RATE + let (mut num_perm, len_last_chunk) = range.div_mod(ctx, len, BigUint::from(RATE), num_bits); + num_perm = range.gate().inc(ctx, num_perm); + + let mut state = self.init_state().clone(); + let mut result_state = state.clone(); + for (i, chunk) in inputs.chunks(RATE).enumerate() { + let is_last_perm = + range.gate().is_equal(ctx, num_perm, Constant(F::from((i + 1) as u64))); + let len_chunk = range.gate().select( + ctx, + len_last_chunk, + Constant(F::from(RATE as u64)), + is_last_perm, + ); + + state.permutation(ctx, range.gate(), chunk, Some(len_chunk), &self.spec); + result_state.select( + ctx, + range.gate(), + SafeTypeChip::::unsafe_to_bool(is_last_perm), + &state, + ); + } + if max_len % RATE == 0 { + let is_last_perm = range.gate().is_equal( + ctx, + num_perm, + Constant(F::from((max_len / RATE + 1) as u64)), + ); + let len_chunk = ctx.load_zero(); + state.permutation(ctx, range.gate(), &[], Some(len_chunk), &self.spec); + result_state.select( + ctx, + range.gate(), + SafeTypeChip::::unsafe_to_bool(is_last_perm), + &state, + ); + } + result_state.s[1] + } + + /// Constrains and returns hash of a witness array. + /// + /// * inputs: An array of [AssignedValue]. + /// + /// Return hash of `inputs`. + pub fn hash_fix_len_array( + &self, + ctx: &mut Context, + gate: &impl GateInstructions, + inputs: &[AssignedValue], + ) -> AssignedValue + where + F: BigPrimeField, + { + let mut state = self.init_state().clone(); + fix_len_array_squeeze(ctx, gate, inputs, &mut state, &self.spec) + } + + /// Constrains and returns hashes of inputs in a compact format. Length of `compact_inputs` should be determined at compile time. + pub fn hash_compact_input( + &self, + ctx: &mut Context, + gate: &impl GateInstructions, + compact_inputs: &[PoseidonCompactInput], + ) -> Vec> + where + F: BigPrimeField, + { + let mut outputs = Vec::with_capacity(compact_inputs.len()); + let mut state = self.init_state().clone(); + for input in compact_inputs { + // Assume this is the last row of a logical input: + // Depending on if len == RATE. + let is_full = gate.is_equal(ctx, input.len, Constant(F::from(RATE as u64))); + // Case 1: if len != RATE. + state.permutation(ctx, gate, &input.inputs, Some(input.len), &self.spec); + // Case 2: if len == RATE, an extra permuation is needed for squeeze. + let mut state_2 = state.clone(); + state_2.permutation(ctx, gate, &[], None, &self.spec); + // Select the result of case 1/2 depending on if len == RATE. + let hash = gate.select(ctx, state_2.s[1], state.s[1], is_full); + outputs.push(PoseidonCompactOutput { hash, is_final: input.is_final }); + // Reset state to init_state if this is the end of a logical input. + // TODO: skip this if this is the last row. + state.select(ctx, gate, input.is_final, self.init_state()); + } + outputs + } + + /// Constrains and returns hashes of chunk inputs in a compact format. Length of `chunk_inputs` should be determined at compile time. + pub fn hash_compact_chunk_inputs( + &self, + ctx: &mut Context, + gate: &impl GateInstructions, + chunk_inputs: &[PoseidonCompactChunkInput], + ) -> Vec> + where + F: BigPrimeField, + { + let zero_witness = ctx.load_zero(); + let mut outputs = Vec::with_capacity(chunk_inputs.len()); + let mut state = self.init_state().clone(); + for chunk_input in chunk_inputs { + let is_final = chunk_input.is_final; + for absorb in &chunk_input.inputs { + state.permutation(ctx, gate, absorb, None, &self.spec); + } + // Because the length of each absorb is always RATE. An extra permutation is needed for squeeze. + let mut output_state = state.clone(); + output_state.permutation(ctx, gate, &[], None, &self.spec); + let hash = gate.select(ctx, output_state.s[1], zero_witness, *is_final.as_ref()); + outputs.push(PoseidonCompactOutput { hash, is_final }); + // Reset state to init_state if this is the end of a logical input. + state.select(ctx, gate, is_final, self.init_state()); + } + outputs + } +} + +/// Poseidon sponge. This is stateful. +pub struct PoseidonSponge { + init_state: PoseidonState, + state: PoseidonState, + spec: OptimizedPoseidonSpec, + absorbing: Vec>, +} + +impl PoseidonSponge { + /// Create new Poseidon hasher. + pub fn new( + ctx: &mut Context, + ) -> Self { + let init_state = PoseidonState::default(ctx); + let state = init_state.clone(); + Self { + init_state, + state, + spec: OptimizedPoseidonSpec::new::(), + absorbing: Vec::new(), + } + } + + /// Initialize a poseidon hasher from an existing spec. + pub fn from_spec(ctx: &mut Context, spec: OptimizedPoseidonSpec) -> Self { + let init_state = PoseidonState::default(ctx); + Self { spec, state: init_state.clone(), init_state, absorbing: Vec::new() } + } + + /// Reset state to default and clear the buffer. + pub fn clear(&mut self) { + self.state = self.init_state.clone(); + self.absorbing.clear(); + } + + /// Store given `elements` into buffer. + pub fn update(&mut self, elements: &[AssignedValue]) { + self.absorbing.extend_from_slice(elements); + } + + /// Consume buffer and perform permutation, then output second element of + /// state. + pub fn squeeze( + &mut self, + ctx: &mut Context, + gate: &impl GateInstructions, + ) -> AssignedValue { + let input_elements = mem::take(&mut self.absorbing); + fix_len_array_squeeze(ctx, gate, &input_elements, &mut self.state, &self.spec) + } +} + +/// ATTETION: input_elements.len() needs to be fixed at compile time. +fn fix_len_array_squeeze( + ctx: &mut Context, + gate: &impl GateInstructions, + input_elements: &[AssignedValue], + state: &mut PoseidonState, + spec: &OptimizedPoseidonSpec, +) -> AssignedValue { + let exact = input_elements.len() % RATE == 0; + + for chunk in input_elements.chunks(RATE) { + state.permutation(ctx, gate, chunk, None, spec); + } + if exact { + state.permutation(ctx, gate, &[], None, spec); + } + + state.s[1] +} diff --git a/vendor/halo2-lib/halo2-base/src/poseidon/hasher/spec.rs b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/spec.rs new file mode 100644 index 00000000..e0a0d2c9 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/spec.rs @@ -0,0 +1,176 @@ +use crate::{ + ff::{FromUniformBytes, PrimeField}, + poseidon::hasher::mds::*, +}; + +use getset::{CopyGetters, Getters}; +use poseidon_rs::poseidon::primitives::Spec as PoseidonSpec; // trait +use std::marker::PhantomData; + +// struct so we can use PoseidonSpec trait to generate round constants and MDS matrix +#[derive(Debug)] +pub(crate) struct Poseidon128Pow5Gen< + F: PrimeField, + const T: usize, + const RATE: usize, + const R_F: usize, + const R_P: usize, + const SECURE_MDS: usize, +> { + _marker: PhantomData, +} + +impl< + F: PrimeField, + const T: usize, + const RATE: usize, + const R_F: usize, + const R_P: usize, + const SECURE_MDS: usize, + > PoseidonSpec for Poseidon128Pow5Gen +{ + fn full_rounds() -> usize { + R_F + } + + fn partial_rounds() -> usize { + R_P + } + + fn sbox(val: F) -> F { + val.pow_vartime([5]) + } + + // see "Avoiding insecure matrices" in Section 2.3 of https://eprint.iacr.org/2019/458.pdf + // most Specs used in practice have SECURE_MDS = 0 + fn secure_mds() -> usize { + SECURE_MDS + } +} + +// We use the optimized Poseidon implementation described in Supplementary Material Section B of https://eprint.iacr.org/2019/458.pdf +// This involves some further computation of optimized constants and sparse MDS matrices beyond what the Scroll PoseidonSpec generates +// The implementation below is adapted from https://github.com/privacy-scaling-explorations/poseidon + +/// `OptimizedPoseidonSpec` holds construction parameters as well as constants that are used in +/// permutation step. +#[derive(Debug, Clone, Getters, CopyGetters)] +pub struct OptimizedPoseidonSpec { + /// Number of full rounds + #[getset(get_copy = "pub")] + pub(crate) r_f: usize, + /// MDS matrices + #[getset(get = "pub")] + pub(crate) mds_matrices: MDSMatrices, + /// Round constants + #[getset(get = "pub")] + pub(crate) constants: OptimizedConstants, +} + +/// `OptimizedConstants` has round constants that are added each round. While +/// full rounds has T sized constants there is a single constant for each +/// partial round +#[derive(Debug, Clone, Getters)] +pub struct OptimizedConstants { + /// start + #[getset(get = "pub")] + pub(crate) start: Vec<[F; T]>, + /// partial + #[getset(get = "pub")] + pub(crate) partial: Vec, + /// end + #[getset(get = "pub")] + pub(crate) end: Vec<[F; T]>, +} + +impl OptimizedPoseidonSpec { + /// Generate new spec with specific number of full and partial rounds. `SECURE_MDS` is usually 0, but may need to be specified because insecure matrices may sometimes be generated + pub fn new() -> Self + where + F: FromUniformBytes<64> + Ord, + { + let (round_constants, mds, mds_inv) = + Poseidon128Pow5Gen::::constants(); + let mds = MDSMatrix(mds); + let inverse_mds = MDSMatrix(mds_inv); + + let constants = + Self::calculate_optimized_constants(R_F, R_P, round_constants, &inverse_mds); + let (sparse_matrices, pre_sparse_mds) = Self::calculate_sparse_matrices(R_P, &mds); + + Self { + r_f: R_F, + constants, + mds_matrices: MDSMatrices { mds, sparse_matrices, pre_sparse_mds }, + } + } + + fn calculate_optimized_constants( + r_f: usize, + r_p: usize, + constants: Vec<[F; T]>, + inverse_mds: &MDSMatrix, + ) -> OptimizedConstants { + let (number_of_rounds, r_f_half) = (r_f + r_p, r_f / 2); + assert_eq!(constants.len(), number_of_rounds); + + // Calculate optimized constants for first half of the full rounds + let mut constants_start: Vec<[F; T]> = vec![[F::ZERO; T]; r_f_half]; + constants_start[0] = constants[0]; + for (optimized, constants) in + constants_start.iter_mut().skip(1).zip(constants.iter().skip(1)) + { + *optimized = inverse_mds.mul_vector(constants); + } + + // Calculate constants for partial rounds + let mut acc = constants[r_f_half + r_p]; + let mut constants_partial = vec![F::ZERO; r_p]; + for (optimized, constants) in constants_partial + .iter_mut() + .rev() + .zip(constants.iter().skip(r_f_half).rev().skip(r_f_half)) + { + let mut tmp = inverse_mds.mul_vector(&acc); + *optimized = tmp[0]; + + tmp[0] = F::ZERO; + for ((acc, tmp), constant) in acc.iter_mut().zip(tmp).zip(constants.iter()) { + *acc = tmp + constant + } + } + constants_start.push(inverse_mds.mul_vector(&acc)); + + // Calculate optimized constants for ending half of the full rounds + let mut constants_end: Vec<[F; T]> = vec![[F::ZERO; T]; r_f_half - 1]; + for (optimized, constants) in + constants_end.iter_mut().zip(constants.iter().skip(r_f_half + r_p + 1)) + { + *optimized = inverse_mds.mul_vector(constants); + } + + OptimizedConstants { + start: constants_start, + partial: constants_partial, + end: constants_end, + } + } + + fn calculate_sparse_matrices( + r_p: usize, + mds: &MDSMatrix, + ) -> (Vec>, MDSMatrix) { + let mds = mds.transpose(); + let mut acc = mds.clone(); + let mut sparse_matrices = (0..r_p) + .map(|_| { + let (m_prime, m_prime_prime) = acc.factorise(); + acc = mds.mul(&m_prime); + m_prime_prime + }) + .collect::>>(); + + sparse_matrices.reverse(); + (sparse_matrices, acc.transpose()) + } +} diff --git a/vendor/halo2-lib/halo2-base/src/poseidon/hasher/state.rs b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/state.rs new file mode 100644 index 00000000..5b8fd308 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/state.rs @@ -0,0 +1,251 @@ +use std::iter; + +use itertools::Itertools; + +use crate::{ + gates::GateInstructions, + poseidon::hasher::{mds::SparseMDSMatrix, spec::OptimizedPoseidonSpec}, + safe_types::SafeBool, + utils::ScalarField, + AssignedValue, Context, + QuantumCell::{Constant, Existing}, +}; + +#[derive(Clone, Debug)] +pub(crate) struct PoseidonState { + pub(crate) s: [AssignedValue; T], +} + +impl PoseidonState { + pub fn default(ctx: &mut Context) -> Self { + let mut default_state = [F::ZERO; T]; + // from Section 4.2 of https://eprint.iacr.org/2019/458.pdf + // • Variable-Input-Length Hashing. The capacity value is 2^64 + (o−1) where o the output length. + // for our transcript use cases, o = 1 + default_state[0] = F::from_u128(1u128 << 64); + Self { s: default_state.map(|f| ctx.load_constant(f)) } + } + + /// Perform permutation on this state. + /// + /// ATTETION: inputs.len() needs to be fixed at compile time. + /// Assume len <= inputs.len(). + /// `inputs` is right padded. + /// If `len` is `None`, treat `inputs` as a fixed length array. + pub fn permutation( + &mut self, + ctx: &mut Context, + gate: &impl GateInstructions, + inputs: &[AssignedValue], + len: Option>, + spec: &OptimizedPoseidonSpec, + ) { + let r_f = spec.r_f / 2; + let mds = &spec.mds_matrices.mds.0; + let pre_sparse_mds = &spec.mds_matrices.pre_sparse_mds.0; + let sparse_matrices = &spec.mds_matrices.sparse_matrices; + + // First half of the full round + let constants = &spec.constants.start; + if let Some(len) = len { + // Note: this doesn't mean `padded_inputs` is 0 padded because there is no constraints on `inputs[len..]` + let padded_inputs: [AssignedValue; RATE] = + core::array::from_fn( + |i| if i < inputs.len() { inputs[i] } else { ctx.load_zero() }, + ); + self.absorb_var_len_with_pre_constants(ctx, gate, padded_inputs, len, &constants[0]); + } else { + self.absorb_with_pre_constants(ctx, gate, inputs, &constants[0]); + } + for constants in constants.iter().skip(1).take(r_f - 1) { + self.sbox_full(ctx, gate, constants); + self.apply_mds(ctx, gate, mds); + } + self.sbox_full(ctx, gate, constants.last().unwrap()); + self.apply_mds(ctx, gate, pre_sparse_mds); + + // Partial rounds + let constants = &spec.constants.partial; + for (constant, sparse_mds) in constants.iter().zip(sparse_matrices.iter()) { + self.sbox_part(ctx, gate, constant); + self.apply_sparse_mds(ctx, gate, sparse_mds); + } + + // Second half of the full rounds + let constants = &spec.constants.end; + for constants in constants.iter() { + self.sbox_full(ctx, gate, constants); + self.apply_mds(ctx, gate, mds); + } + self.sbox_full(ctx, gate, &[F::ZERO; T]); + self.apply_mds(ctx, gate, mds); + } + + /// Constrains and set self to a specific state if `selector` is true. + pub fn select( + &mut self, + ctx: &mut Context, + gate: &impl GateInstructions, + selector: SafeBool, + set_to: &Self, + ) { + for i in 0..T { + self.s[i] = gate.select(ctx, set_to.s[i], self.s[i], *selector.as_ref()); + } + } + + fn x_power5_with_constant( + ctx: &mut Context, + gate: &impl GateInstructions, + x: AssignedValue, + constant: &F, + ) -> AssignedValue { + let x2 = gate.mul(ctx, x, x); + let x4 = gate.mul(ctx, x2, x2); + gate.mul_add(ctx, x, x4, Constant(*constant)) + } + + fn sbox_full( + &mut self, + ctx: &mut Context, + gate: &impl GateInstructions, + constants: &[F; T], + ) { + for (x, constant) in self.s.iter_mut().zip(constants.iter()) { + *x = Self::x_power5_with_constant(ctx, gate, *x, constant); + } + } + + fn sbox_part(&mut self, ctx: &mut Context, gate: &impl GateInstructions, constant: &F) { + let x = &mut self.s[0]; + *x = Self::x_power5_with_constant(ctx, gate, *x, constant); + } + + fn absorb_with_pre_constants( + &mut self, + ctx: &mut Context, + gate: &impl GateInstructions, + inputs: &[AssignedValue], + pre_constants: &[F; T], + ) { + assert!(inputs.len() < T); + + // Explanation of what's going on: before each round of the poseidon permutation, + // two things have to be added to the state: inputs (the absorbed elements) and + // preconstants. Imagine the state as a list of T elements, the first of which is + // the capacity: |--cap--|--el1--|--el2--|--elR--| + // - A preconstant is added to each of all T elements (which is different for each) + // - The inputs are added to all elements starting from el1 (so, not to the capacity), + // to as many elements as inputs are available. + // - To the first element for which no input is left (if any), an extra 1 is added. + + // adding preconstant to the distinguished capacity element (only one) + self.s[0] = gate.add(ctx, self.s[0], Constant(pre_constants[0])); + + // adding pre-constants and inputs to the elements for which both are available + for ((x, constant), input) in + self.s.iter_mut().zip(pre_constants.iter()).skip(1).zip(inputs.iter()) + { + *x = gate.sum(ctx, [Existing(*x), Existing(*input), Constant(*constant)]); + } + + let offset = inputs.len() + 1; + // adding only pre-constants when no input is left + for (i, (x, constant)) in + self.s.iter_mut().zip(pre_constants.iter()).skip(offset).enumerate() + { + *x = gate.add(ctx, *x, Constant(if i == 0 { F::ONE + constant } else { *constant })); + // the if idx == 0 { F::one() } else { F::zero() } is to pad the input with a single 1 and then 0s + // this is the padding suggested in pg 31 of https://eprint.iacr.org/2019/458.pdf and in Section 4.2 (Variable-Input-Length Hashing. The padding consists of one field element being 1, and the remaining elements being 0.) + } + } + + /// Absorb inputs with a variable length. + /// + /// `inputs` is right padded. + fn absorb_var_len_with_pre_constants( + &mut self, + ctx: &mut Context, + gate: &impl GateInstructions, + inputs: [AssignedValue; RATE], + len: AssignedValue, + pre_constants: &[F; T], + ) { + // Explanation of what's going on: before each round of the poseidon permutation, + // two things have to be added to the state: inputs (the absorbed elements) and + // preconstants. Imagine the state as a list of T elements, the first of which is + // the capacity: |--cap--|--el1--|--el2--|--elR--| + // - A preconstant is added to each of all T elements (which is different for each) + // - The inputs are added to all elements starting from el1 (so, not to the capacity), + // to as many elements as inputs are available. + // - To the first element for which no input is left (if any), an extra 1 is added. + + // Adding preconstants to the current state. + for (i, pre_const) in pre_constants.iter().enumerate() { + self.s[i] = gate.add(ctx, self.s[i], Constant(*pre_const)); + } + + // Generate a mask array where a[i] = i < len for i = 0..RATE. + let idx = gate.dec(ctx, len); + let len_indicator = gate.idx_to_indicator(ctx, idx, RATE); + // inputs_mask[i] = sum(len_indicator[i..]) + let mut inputs_mask = + gate.partial_sums(ctx, len_indicator.clone().into_iter().rev()).collect_vec(); + inputs_mask.reverse(); + + let padded_inputs = inputs + .iter() + .zip(inputs_mask.iter()) + .map(|(input, mask)| gate.mul(ctx, *input, *mask)) + .collect_vec(); + for i in 0..RATE { + // Add all inputs. + self.s[i + 1] = gate.add(ctx, self.s[i + 1], padded_inputs[i]); + // Add the extra 1 after inputs. + if i + 2 < T { + self.s[i + 2] = gate.add(ctx, self.s[i + 2], len_indicator[i]); + } + } + // If len == 0, inputs_mask is all 0. Then the extra 1 should be added into s[1]. + let empty_extra_one = gate.not(ctx, inputs_mask[0]); + self.s[1] = gate.add(ctx, self.s[1], empty_extra_one); + } + + fn apply_mds( + &mut self, + ctx: &mut Context, + gate: &impl GateInstructions, + mds: &[[F; T]; T], + ) { + let res = mds + .iter() + .map(|row| { + gate.inner_product(ctx, self.s.iter().copied(), row.iter().map(|c| Constant(*c))) + }) + .collect::>(); + + self.s = res.try_into().unwrap(); + } + + fn apply_sparse_mds( + &mut self, + ctx: &mut Context, + gate: &impl GateInstructions, + mds: &SparseMDSMatrix, + ) { + self.s = iter::once(gate.inner_product( + ctx, + self.s.iter().copied(), + mds.row.iter().map(|c| Constant(*c)), + )) + .chain( + mds.col_hat + .iter() + .zip(self.s.iter().skip(1)) + .map(|(coeff, state)| gate.mul_add(ctx, self.s[0], Constant(*coeff), *state)), + ) + .collect::>() + .try_into() + .unwrap(); + } +} diff --git a/vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/compatibility.rs b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/compatibility.rs new file mode 100644 index 00000000..74e40531 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/compatibility.rs @@ -0,0 +1,117 @@ +use std::{cmp::max, iter::zip}; + +use crate::{ + gates::{flex_gate::threads::SinglePhaseCoreManager, GateChip}, + halo2_proofs::halo2curves::bn256::Fr, + poseidon::hasher::PoseidonSponge, + utils::ScalarField, +}; +use pse_poseidon::Poseidon; +use rand::Rng; + +// make interleaved calls to absorb and squeeze elements and +// check that the result is the same in-circuit and natively +fn sponge_compatiblity_verification< + F: ScalarField, + const T: usize, + const RATE: usize, + const R_F: usize, + const R_P: usize, +>( + // elements of F to absorb; one sublist = one absorption + mut absorptions: Vec>, + // list of amounts of elements of F that should be squeezed every time + mut squeezings: Vec, +) { + let mut pool = SinglePhaseCoreManager::new(true, Default::default()); + let gate = GateChip::default(); + + let ctx = pool.main(); + + // constructing native and in-circuit Poseidon sponges + let mut native_sponge = Poseidon::::new(R_F, R_P); + // assuming SECURE_MDS = 0 + let mut circuit_sponge = PoseidonSponge::::new::(ctx); + + // preparing to interleave absorptions and squeezings + let n_iterations = max(absorptions.len(), squeezings.len()); + absorptions.resize(n_iterations, Vec::new()); + squeezings.resize(n_iterations, 0); + + for (absorption, squeezing) in zip(absorptions, squeezings) { + // absorb (if any elements were provided) + native_sponge.update(&absorption); + circuit_sponge.update(&ctx.assign_witnesses(absorption)); + + // squeeze (if any elements were requested) + for _ in 0..squeezing { + let native_squeezed = native_sponge.squeeze(); + let circuit_squeezed = circuit_sponge.squeeze(ctx, &gate); + + assert_eq!(native_squeezed, *circuit_squeezed.value()); + } + } + + // even if no squeezings were requested, we squeeze to verify the + // states are the same after all absorptions + let native_squeezed = native_sponge.squeeze(); + let circuit_squeezed = circuit_sponge.squeeze(ctx, &gate); + + assert_eq!(native_squeezed, *circuit_squeezed.value()); +} + +fn random_nested_list_f(len: usize, max_sub_len: usize) -> Vec> { + let mut rng = rand::thread_rng(); + let mut list = Vec::new(); + for _ in 0..len { + let len = rng.gen_range(0..=max_sub_len); + let mut sublist = Vec::new(); + + for _ in 0..len { + sublist.push(F::random(&mut rng)); + } + list.push(sublist); + } + list +} + +fn random_list_usize(len: usize, max: usize) -> Vec { + let mut rng = rand::thread_rng(); + let mut list = Vec::new(); + for _ in 0..len { + list.push(rng.gen_range(0..=max)); + } + list +} + +#[test] +fn test_sponge_compatibility_squeezing_only() { + let absorptions = Vec::new(); + let squeezings = random_list_usize(10, 7); + + sponge_compatiblity_verification::(absorptions, squeezings); +} + +#[test] +fn test_sponge_compatibility_absorbing_only() { + let absorptions = random_nested_list_f(8, 5); + let squeezings = Vec::new(); + + sponge_compatiblity_verification::(absorptions, squeezings); +} + +#[test] +fn test_sponge_compatibility_interleaved() { + let absorptions = random_nested_list_f(10, 5); + let squeezings = random_list_usize(7, 10); + + sponge_compatiblity_verification::(absorptions, squeezings); +} + +#[test] +fn test_sponge_compatibility_other_params() { + let absorptions = random_nested_list_f(10, 10); + let squeezings = random_list_usize(10, 10); + + sponge_compatiblity_verification::(absorptions, squeezings); +} diff --git a/vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/hasher.rs b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/hasher.rs new file mode 100644 index 00000000..7b55c3c4 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/hasher.rs @@ -0,0 +1,357 @@ +use crate::{ + gates::{range::RangeInstructions, RangeChip}, + halo2_proofs::{arithmetic::Field, halo2curves::bn256::Fr}, + poseidon::hasher::{ + spec::OptimizedPoseidonSpec, PoseidonCompactChunkInput, PoseidonCompactInput, + PoseidonHasher, + }, + safe_types::SafeTypeChip, + utils::{testing::base_test, ScalarField}, + Context, +}; +use itertools::Itertools; +use pse_poseidon::Poseidon; +use rand::Rng; + +#[derive(Clone)] +struct Payload { + // Represent value of a right-padded witness array with a variable length + pub values: Vec, + // Length of `values`. + pub len: usize, +} + +// check if the results from hasher and native sponge are same for hash_var_len_array. +fn hasher_compatiblity_verification< + const T: usize, + const RATE: usize, + const R_F: usize, + const R_P: usize, +>( + payloads: Vec>, +) { + base_test().k(12).run(|ctx, range| { + // Construct in-circuit Poseidon hasher. Assuming SECURE_MDS = 0. + let spec = OptimizedPoseidonSpec::::new::(); + let mut hasher = PoseidonHasher::::new(spec); + hasher.initialize_consts(ctx, range.gate()); + + for payload in payloads { + // Construct native Poseidon sponge. + let mut native_sponge = Poseidon::::new(R_F, R_P); + native_sponge.update(&payload.values[..payload.len]); + let native_result = native_sponge.squeeze(); + let inputs = ctx.assign_witnesses(payload.values); + let len = ctx.load_witness(Fr::from(payload.len as u64)); + let hasher_result = hasher.hash_var_len_array(ctx, range, &inputs, len); + assert_eq!(native_result, *hasher_result.value()); + } + }); +} + +// check if the results from hasher and native sponge are same for hash_compact_input. +fn hasher_compact_inputs_compatiblity_verification< + const T: usize, + const RATE: usize, + const R_F: usize, + const R_P: usize, +>( + payloads: Vec>, + ctx: &mut Context, + range: &RangeChip, +) { + // Construct in-circuit Poseidon hasher. Assuming SECURE_MDS = 0. + let spec = OptimizedPoseidonSpec::::new::(); + let mut hasher = PoseidonHasher::::new(spec); + hasher.initialize_consts(ctx, range.gate()); + + let mut native_results = Vec::with_capacity(payloads.len()); + let mut compact_inputs = Vec::>::new(); + let rate_witness = ctx.load_constant(Fr::from(RATE as u64)); + let true_witness = ctx.load_constant(Fr::ONE); + let false_witness = ctx.load_zero(); + for payload in payloads { + assert!(payload.values.len() % RATE == 0); + assert!(payload.values.len() >= payload.len); + assert!(payload.values.len() == RATE || payload.values.len() - payload.len < RATE); + let num_chunk = payload.values.len() / RATE; + let last_chunk_len = RATE - (payload.values.len() - payload.len); + let inputs = ctx.assign_witnesses(payload.values.clone()); + for (chunk_idx, input_chunk) in inputs.chunks(RATE).enumerate() { + let len_witness = if chunk_idx + 1 == num_chunk { + ctx.load_witness(Fr::from(last_chunk_len as u64)) + } else { + rate_witness + }; + let is_final_witness = SafeTypeChip::unsafe_to_bool(if chunk_idx + 1 == num_chunk { + true_witness + } else { + false_witness + }); + compact_inputs.push(PoseidonCompactInput { + inputs: input_chunk.try_into().unwrap(), + len: len_witness, + is_final: is_final_witness, + }); + } + // Construct native Poseidon sponge. + let mut native_sponge = Poseidon::::new(R_F, R_P); + native_sponge.update(&payload.values[..payload.len]); + let native_result = native_sponge.squeeze(); + native_results.push(native_result); + } + let compact_outputs = hasher.hash_compact_input(ctx, range.gate(), &compact_inputs); + let mut output_offset = 0; + for (compact_output, compact_input) in compact_outputs.iter().zip(compact_inputs) { + // into() doesn't work if ! is in the beginning in the bool expression... + let is_not_final_input: bool = compact_input.is_final.as_ref().value().is_zero().into(); + let is_not_final_output: bool = compact_output.is_final().as_ref().value().is_zero().into(); + assert_eq!(is_not_final_input, is_not_final_output); + if !is_not_final_output { + assert_eq!(native_results[output_offset], *compact_output.hash().value()); + output_offset += 1; + } + } +} + +// check if the results from hasher and native sponge are same for hash_compact_input. +fn hasher_compact_chunk_inputs_compatiblity_verification< + const T: usize, + const RATE: usize, + const R_F: usize, + const R_P: usize, +>( + payloads: Vec<(Payload, bool)>, + ctx: &mut Context, + range: &RangeChip, +) { + // Construct in-circuit Poseidon hasher. Assuming SECURE_MDS = 0. + let spec = OptimizedPoseidonSpec::::new::(); + let mut hasher = PoseidonHasher::::new(spec); + hasher.initialize_consts(ctx, range.gate()); + + let mut native_results = Vec::with_capacity(payloads.len()); + let mut chunk_inputs = Vec::>::new(); + let true_witness = SafeTypeChip::unsafe_to_bool(ctx.load_constant(Fr::ONE)); + let false_witness = SafeTypeChip::unsafe_to_bool(ctx.load_zero()); + + // Construct native Poseidon sponge. + let mut native_sponge = Poseidon::::new(R_F, R_P); + for (payload, is_final) in payloads { + assert!(payload.values.len() == payload.len); + assert!(payload.values.len() % RATE == 0); + let inputs = ctx.assign_witnesses(payload.values.clone()); + + let is_final_witness = if is_final { true_witness } else { false_witness }; + chunk_inputs.push(PoseidonCompactChunkInput { + inputs: inputs.chunks(RATE).map(|c| c.try_into().unwrap()).collect_vec(), + is_final: is_final_witness, + }); + native_sponge.update(&payload.values); + if is_final { + let native_result = native_sponge.squeeze(); + native_results.push(native_result); + native_sponge = Poseidon::::new(R_F, R_P); + } + } + let compact_outputs = hasher.hash_compact_chunk_inputs(ctx, range.gate(), &chunk_inputs); + assert_eq!(chunk_inputs.len(), compact_outputs.len()); + let mut output_offset = 0; + for (compact_output, chunk_input) in compact_outputs.iter().zip(chunk_inputs) { + // into() doesn't work if ! is in the beginning in the bool expression... + let is_final_input = chunk_input.is_final.as_ref().value(); + let is_final_output = compact_output.is_final.as_ref().value(); + assert_eq!(is_final_input, is_final_output); + if is_final_output == &Fr::ONE { + assert_eq!(native_results[output_offset], *compact_output.hash().value()); + output_offset += 1; + } + } +} + +fn random_payload(max_len: usize, len: usize, max_value: usize) -> Payload { + assert!(len <= max_len); + let mut rng = rand::thread_rng(); + let mut values = Vec::new(); + for _ in 0..max_len { + values.push(F::from(rng.gen_range(0..=max_value) as u64)); + } + Payload { values, len } +} + +fn random_payload_without_len(max_len: usize, max_value: usize) -> Payload { + let mut rng = rand::thread_rng(); + let mut values = Vec::new(); + for _ in 0..max_len { + values.push(F::from(rng.gen_range(0..=max_value) as u64)); + } + Payload { values, len: rng.gen_range(0..=max_len) } +} + +#[test] +fn test_poseidon_hasher_compatiblity() { + { + const T: usize = 3; + const RATE: usize = 2; + let payloads = vec![ + // max_len = 0 + random_payload(0, 0, usize::MAX), + // max_len % RATE == 0 && len = 0 + random_payload(RATE * 2, 0, usize::MAX), + // max_len % RATE == 0 && 0 < len < max_len && len % RATE == 0 + random_payload(RATE * 2, RATE, usize::MAX), + // max_len % RATE == 0 && 0 < len < max_len && len % RATE != 0 + random_payload(RATE * 5, RATE * 2 + 1, usize::MAX), + // max_len % RATE == 0 && len == max_len + random_payload(RATE * 2, RATE * 2, usize::MAX), + random_payload(RATE * 5, RATE * 5, usize::MAX), + // len % RATE != 0 && len = 0 + random_payload(RATE * 2 + 1, 0, usize::MAX), + random_payload(RATE * 5 + 1, 0, usize::MAX), + // len % RATE != 0 && 0 < len < max_len && len % RATE == 0 + random_payload(RATE * 2 + 1, RATE, usize::MAX), + // len % RATE != 0 && 0 < len < max_len && len % RATE != 0 + random_payload(RATE * 5 + 1, RATE * 2 + 1, usize::MAX), + // len % RATE != 0 && len = max_len + random_payload(RATE * 2 + 1, RATE * 2 + 1, usize::MAX), + random_payload(RATE * 5 + 1, RATE * 5 + 1, usize::MAX), + ]; + hasher_compatiblity_verification::(payloads); + } +} + +#[test] +fn test_poseidon_hasher_with_prover() { + { + const T: usize = 3; + const RATE: usize = 2; + const R_F: usize = 8; + const R_P: usize = 57; + + let max_lens = vec![0, RATE * 2, RATE * 5, RATE * 2 + 1, RATE * 5 + 1]; + for max_len in max_lens { + let init_input = random_payload_without_len(max_len, usize::MAX); + let logic_input = random_payload_without_len(max_len, usize::MAX); + base_test().k(12).bench_builder(init_input, logic_input, |pool, range, payload| { + let ctx = pool.main(); + // Construct in-circuit Poseidon hasher. Assuming SECURE_MDS = 0. + let spec = OptimizedPoseidonSpec::::new::(); + let mut hasher = PoseidonHasher::::new(spec); + hasher.initialize_consts(ctx, range.gate()); + let inputs = ctx.assign_witnesses(payload.values); + let len = ctx.load_witness(Fr::from(payload.len as u64)); + hasher.hash_var_len_array(ctx, range, &inputs, len); + }); + } + } +} + +#[test] +fn test_poseidon_hasher_compact_inputs() { + { + const T: usize = 3; + const RATE: usize = 2; + let payloads = vec![ + // len == 0 + random_payload(RATE, 0, usize::MAX), + // 0 < len < max_len + random_payload(RATE * 2, RATE + 1, usize::MAX), + random_payload(RATE * 5, RATE * 4 + 1, usize::MAX), + // len == max_len + random_payload(RATE * 2, RATE * 2, usize::MAX), + random_payload(RATE * 5, RATE * 5, usize::MAX), + ]; + base_test().k(12).run(|ctx, range| { + hasher_compact_inputs_compatiblity_verification::(payloads, ctx, range); + }); + } +} + +#[test] +fn test_poseidon_hasher_compact_inputs_with_prover() { + { + const T: usize = 3; + const RATE: usize = 2; + let params = [ + (RATE, 0), + (RATE * 2, RATE + 1), + (RATE * 5, RATE * 4 + 1), + (RATE * 2, RATE * 2), + (RATE * 5, RATE * 5), + ]; + let init_payloads = params + .iter() + .map(|(max_len, len)| random_payload(*max_len, *len, usize::MAX)) + .collect::>(); + let logic_payloads = params + .iter() + .map(|(max_len, len)| random_payload(*max_len, *len, usize::MAX)) + .collect::>(); + base_test().k(12).bench_builder(init_payloads, logic_payloads, |pool, range, input| { + let ctx = pool.main(); + hasher_compact_inputs_compatiblity_verification::(input, ctx, range); + }); + } +} + +#[test] +fn test_poseidon_hasher_compact_chunk_inputs() { + { + const T: usize = 3; + const RATE: usize = 2; + let payloads = vec![ + (random_payload(RATE * 5, RATE * 5, usize::MAX), true), + (random_payload(RATE, RATE, usize::MAX), false), + (random_payload(RATE * 2, RATE * 2, usize::MAX), true), + (random_payload(RATE * 3, RATE * 3, usize::MAX), true), + ]; + base_test().k(12).run(|ctx, range| { + hasher_compact_chunk_inputs_compatiblity_verification::( + payloads, ctx, range, + ); + }); + } + { + const T: usize = 3; + const RATE: usize = 2; + let payloads = vec![ + (random_payload(0, 0, usize::MAX), true), + (random_payload(0, 0, usize::MAX), false), + (random_payload(0, 0, usize::MAX), false), + ]; + base_test().k(12).run(|ctx, range| { + hasher_compact_chunk_inputs_compatiblity_verification::( + payloads, ctx, range, + ); + }); + } +} + +#[test] +fn test_poseidon_hasher_compact_chunk_inputs_with_prover() { + { + const T: usize = 3; + const RATE: usize = 2; + let params = [ + (RATE, false), + (RATE * 2, false), + (RATE * 5, false), + (RATE * 2, true), + (RATE * 5, true), + ]; + let init_payloads = params + .iter() + .map(|(len, is_final)| (random_payload(*len, *len, usize::MAX), *is_final)) + .collect::>(); + let logic_payloads = params + .iter() + .map(|(len, is_final)| (random_payload(*len, *len, usize::MAX), *is_final)) + .collect::>(); + base_test().k(12).bench_builder(init_payloads, logic_payloads, |pool, range, input| { + let ctx = pool.main(); + hasher_compact_chunk_inputs_compatiblity_verification::( + input, ctx, range, + ); + }); + } +} diff --git a/vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/mod.rs b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/mod.rs new file mode 100644 index 00000000..76087be2 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/mod.rs @@ -0,0 +1,39 @@ +use super::*; +use crate::halo2_proofs::halo2curves::{bn256::Fr, ff::PrimeField}; + +use itertools::Itertools; + +mod compatibility; +mod hasher; +mod state; + +#[test] +fn test_mds() { + let spec = OptimizedPoseidonSpec::::new::<8, 57, 0>(); + + let mds = [ + [ + "7511745149465107256748700652201246547602992235352608707588321460060273774987", + "10370080108974718697676803824769673834027675643658433702224577712625900127200", + "19705173408229649878903981084052839426532978878058043055305024233888854471533", + ], + [ + "18732019378264290557468133440468564866454307626475683536618613112504878618481", + "20870176810702568768751421378473869562658540583882454726129544628203806653987", + "7266061498423634438633389053804536045105766754026813321943009179476902321146", + ], + [ + "9131299761947733513298312097611845208338517739621853568979632113419485819303", + "10595341252162738537912664445405114076324478519622938027420701542910180337937", + "11597556804922396090267472882856054602429588299176362916247939723151043581408", + ], + ]; + for (row1, row2) in mds.iter().zip_eq(spec.mds_matrices.mds.0.iter()) { + for (e1, e2) in row1.iter().zip_eq(row2.iter()) { + assert_eq!(Fr::from_str_vartime(e1).unwrap(), *e2); + } + } +} + +// TODO: test clear()/squeeze(). +// TODO: test constraints actually work. diff --git a/vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/state.rs b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/state.rs new file mode 100644 index 00000000..f09fb76e --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/poseidon/hasher/tests/state.rs @@ -0,0 +1,129 @@ +use super::*; +use crate::{ + gates::{flex_gate::threads::SinglePhaseCoreManager, GateChip}, + halo2_proofs::halo2curves::{bn256::Fr, ff::PrimeField}, +}; + +#[test] +fn test_fix_permutation_against_test_vectors() { + let mut pool = SinglePhaseCoreManager::new(true, Default::default()); + let gate = GateChip::::default(); + let ctx = pool.main(); + + // https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/test_vectors.txt + // poseidonperm_x5_254_3 + { + const R_F: usize = 8; + const R_P: usize = 57; + const T: usize = 3; + const RATE: usize = 2; + + let spec = OptimizedPoseidonSpec::::new::(); + + let mut state = PoseidonState:: { + s: [0u64, 1, 2].map(|v| ctx.load_constant(Fr::from(v))), + }; + let inputs = [Fr::zero(); RATE].iter().map(|f| ctx.load_constant(*f)).collect_vec(); + state.permutation(ctx, &gate, &inputs, None, &spec); // avoid padding + let state_0 = state.s; + let expected = [ + "7853200120776062878684798364095072458815029376092732009249414926327459813530", + "7142104613055408817911962100316808866448378443474503659992478482890339429929", + "6549537674122432311777789598043107870002137484850126429160507761192163713804", + ]; + for (word, expected) in state_0.into_iter().zip(expected.iter()) { + assert_eq!(word.value(), &Fr::from_str_vartime(expected).unwrap()); + } + } + + // https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/test_vectors.txt + // poseidonperm_x5_254_5 + { + const R_F: usize = 8; + const R_P: usize = 60; + const T: usize = 5; + const RATE: usize = 4; + + let spec = OptimizedPoseidonSpec::::new::(); + + let mut state = PoseidonState:: { + s: [0u64, 1, 2, 3, 4].map(|v| ctx.load_constant(Fr::from(v))), + }; + let inputs = [Fr::zero(); RATE].iter().map(|f| ctx.load_constant(*f)).collect_vec(); + state.permutation(ctx, &gate, &inputs, None, &spec); + let state_0 = state.s; + let expected: [&str; 5] = [ + "18821383157269793795438455681495246036402687001665670618754263018637548127333", + "7817711165059374331357136443537800893307845083525445872661165200086166013245", + "16733335996448830230979566039396561240864200624113062088822991822580465420551", + "6644334865470350789317807668685953492649391266180911382577082600917830417726", + "3372108894677221197912083238087960099443657816445944159266857514496320565191", + ]; + for (word, expected) in state_0.into_iter().zip(expected.iter()) { + assert_eq!(word.value(), &Fr::from_str_vartime(expected).unwrap()); + } + } +} + +#[test] +fn test_var_permutation_against_test_vectors() { + let mut pool = SinglePhaseCoreManager::new(true, Default::default()); + let gate = GateChip::::default(); + let ctx = pool.main(); + + // https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/test_vectors.txt + // poseidonperm_x5_254_3 + { + const R_F: usize = 8; + const R_P: usize = 57; + const T: usize = 3; + const RATE: usize = 2; + + let spec = OptimizedPoseidonSpec::::new::(); + + let mut state = PoseidonState:: { + s: [0u64, 1, 2].map(|v| ctx.load_constant(Fr::from(v))), + }; + let inputs = [Fr::zero(); RATE].iter().map(|f| ctx.load_constant(*f)).collect_vec(); + let len = ctx.load_constant(Fr::from(RATE as u64)); + state.permutation(ctx, &gate, &inputs, Some(len), &spec); // avoid padding + let state_0 = state.s; + let expected = [ + "7853200120776062878684798364095072458815029376092732009249414926327459813530", + "7142104613055408817911962100316808866448378443474503659992478482890339429929", + "6549537674122432311777789598043107870002137484850126429160507761192163713804", + ]; + for (word, expected) in state_0.into_iter().zip(expected.iter()) { + assert_eq!(word.value(), &Fr::from_str_vartime(expected).unwrap()); + } + } + + // https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/test_vectors.txt + // poseidonperm_x5_254_5 + { + const R_F: usize = 8; + const R_P: usize = 60; + const T: usize = 5; + const RATE: usize = 4; + + let spec = OptimizedPoseidonSpec::::new::(); + + let mut state = PoseidonState:: { + s: [0u64, 1, 2, 3, 4].map(|v| ctx.load_constant(Fr::from(v))), + }; + let inputs = [Fr::zero(); RATE].iter().map(|f| ctx.load_constant(*f)).collect_vec(); + let len = ctx.load_constant(Fr::from(RATE as u64)); + state.permutation(ctx, &gate, &inputs, Some(len), &spec); + let state_0 = state.s; + let expected: [&str; 5] = [ + "18821383157269793795438455681495246036402687001665670618754263018637548127333", + "7817711165059374331357136443537800893307845083525445872661165200086166013245", + "16733335996448830230979566039396561240864200624113062088822991822580465420551", + "6644334865470350789317807668685953492649391266180911382577082600917830417726", + "3372108894677221197912083238087960099443657816445944159266857514496320565191", + ]; + for (word, expected) in state_0.into_iter().zip(expected.iter()) { + assert_eq!(word.value(), &Fr::from_str_vartime(expected).unwrap()); + } + } +} diff --git a/vendor/halo2-lib/halo2-base/src/poseidon/mod.rs b/vendor/halo2-lib/halo2-base/src/poseidon/mod.rs new file mode 100644 index 00000000..8e87cabe --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/poseidon/mod.rs @@ -0,0 +1,114 @@ +use crate::{ + gates::{RangeChip, RangeInstructions}, + poseidon::hasher::{spec::OptimizedPoseidonSpec, PoseidonHasher}, + safe_types::{FixLenBytes, VarLenBytes, VarLenBytesVec}, + utils::{BigPrimeField, ScalarField}, + AssignedValue, Context, +}; + +use itertools::Itertools; + +/// Module for Poseidon hasher +pub mod hasher; + +/// Chip for Poseidon hash. +pub struct PoseidonChip<'a, F: ScalarField, const T: usize, const RATE: usize> { + range_chip: &'a RangeChip, + hasher: PoseidonHasher, +} + +impl<'a, F: ScalarField, const T: usize, const RATE: usize> PoseidonChip<'a, F, T, RATE> { + /// Create a new PoseidonChip. + pub fn new( + ctx: &mut Context, + spec: OptimizedPoseidonSpec, + range_chip: &'a RangeChip, + ) -> Self { + let mut hasher = PoseidonHasher::new(spec); + hasher.initialize_consts(ctx, range_chip.gate()); + Self { range_chip, hasher } + } +} + +/// Trait for Poseidon instructions +pub trait PoseidonInstructions { + /// Return hash of a [VarLenBytes] + fn hash_var_len_bytes( + &self, + ctx: &mut Context, + inputs: &VarLenBytes, + ) -> AssignedValue + where + F: BigPrimeField; + + /// Return hash of a [VarLenBytesVec] + fn hash_var_len_bytes_vec( + &self, + ctx: &mut Context, + inputs: &VarLenBytesVec, + ) -> AssignedValue + where + F: BigPrimeField; + + /// Return hash of a [FixLenBytes] + fn hash_fix_len_bytes( + &self, + ctx: &mut Context, + inputs: &FixLenBytes, + ) -> AssignedValue + where + F: BigPrimeField; +} + +impl PoseidonInstructions + for PoseidonChip<'_, F, T, RATE> +{ + fn hash_var_len_bytes( + &self, + ctx: &mut Context, + inputs: &VarLenBytes, + ) -> AssignedValue + where + F: BigPrimeField, + { + let inputs_len = inputs.len(); + self.hasher.hash_var_len_array( + ctx, + self.range_chip, + inputs.bytes().map(|sb| *sb.as_ref()).as_ref(), + *inputs_len, + ) + } + + fn hash_var_len_bytes_vec( + &self, + ctx: &mut Context, + inputs: &VarLenBytesVec, + ) -> AssignedValue + where + F: BigPrimeField, + { + let inputs_len = inputs.len(); + self.hasher.hash_var_len_array( + ctx, + self.range_chip, + &inputs.bytes().iter().map(|sb| *sb.as_ref()).collect_vec(), + *inputs_len, + ) + } + + fn hash_fix_len_bytes( + &self, + ctx: &mut Context, + inputs: &FixLenBytes, + ) -> AssignedValue + where + F: BigPrimeField, + { + self.hasher.hash_fix_len_array( + ctx, + self.range_chip.gate(), + inputs.bytes().map(|sb| *sb.as_ref()).as_ref(), + ) + } +} diff --git a/vendor/halo2-lib/halo2-base/src/safe_types/bytes.rs b/vendor/halo2-lib/halo2-base/src/safe_types/bytes.rs new file mode 100644 index 00000000..2badf24c --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/safe_types/bytes.rs @@ -0,0 +1,227 @@ +#![allow(clippy::len_without_is_empty)] +use crate::{ + gates::GateInstructions, + utils::bit_length, + AssignedValue, Context, + QuantumCell::{Constant, Existing}, +}; + +use super::{SafeByte, ScalarField}; + +use getset::Getters; +use itertools::Itertools; + +/// Represents a variable length byte array in circuit. +/// +/// Each element is guaranteed to be a byte, given by type [`SafeByte`]. +/// To represent a variable length array, we must know the maximum possible length `MAX_LEN` the array could be -- this is some additional context the user must provide. +/// Then we right pad the array with 0s to the maximum length (we do **not** constrain that these paddings must be 0s). +#[derive(Debug, Clone, Getters)] +pub struct VarLenBytes { + /// The byte array, right padded + #[getset(get = "pub")] + bytes: [SafeByte; MAX_LEN], + /// Witness representing the actual length of the byte array. Upon construction, this is range checked to be at most `MAX_LEN` + #[getset(get = "pub")] + len: AssignedValue, +} + +impl VarLenBytes { + /// Slightly unsafe constructor: it is not constrained that `len <= MAX_LEN`. + pub fn new(bytes: [SafeByte; MAX_LEN], len: AssignedValue) -> Self { + assert!( + len.value().le(&F::from(MAX_LEN as u64)), + "Invalid length which exceeds MAX_LEN {MAX_LEN}", + ); + Self { bytes, len } + } + + /// Returns the maximum length of the byte array. + pub fn max_len(&self) -> usize { + MAX_LEN + } + + /// Left pads the variable length byte array with 0s to the `MAX_LEN`. + /// Takes a fixed length array `self.bytes` and returns a length `MAX_LEN` array equal to + /// `[[0; MAX_LEN - len], self.bytes[..len]].concat()`, i.e., we take `self.bytes[..len]` and + /// zero pad it on the left, where `len = self.len` + /// + /// Assumes `0 < self.len <= MAX_LEN`. + /// + /// ## Panics + /// If `self.len` is not in the range `(0, MAX_LEN]`. + pub fn left_pad_to_fixed( + &self, + ctx: &mut Context, + gate: &impl GateInstructions, + ) -> FixLenBytes { + let padded = left_pad_var_array_to_fixed(ctx, gate, &self.bytes, self.len, MAX_LEN); + FixLenBytes::new( + padded.into_iter().map(|b| SafeByte(b)).collect::>().try_into().unwrap(), + ) + } + + /// Return a copy of the byte array with 0 padding ensured. + pub fn ensure_0_padding(&self, ctx: &mut Context, gate: &impl GateInstructions) -> Self { + let bytes = ensure_0_padding(ctx, gate, &self.bytes, self.len); + Self::new(bytes.try_into().unwrap(), self.len) + } +} + +/// Represents a variable length byte array in circuit. Not encouraged to use because `MAX_LEN` cannot be verified at compile time. +/// +/// Each element is guaranteed to be a byte, given by type [`SafeByte`]. +/// To represent a variable length array, we must know the maximum possible length `MAX_LEN` the array could be -- this is provided when constructing and `bytes.len()` == `MAX_LEN` is enforced. +/// Then we right pad the array with 0s to the maximum length (we do **not** constrain that these paddings must be 0s). +#[derive(Debug, Clone, Getters)] +pub struct VarLenBytesVec { + /// The byte array, right padded + #[getset(get = "pub")] + bytes: Vec>, + /// Witness representing the actual length of the byte array. Upon construction, this is range checked to be at most `MAX_LEN` + #[getset(get = "pub")] + len: AssignedValue, +} + +impl VarLenBytesVec { + /// Slightly unsafe constructor: it is not constrained that `len <= max_len`. + pub fn new(bytes: Vec>, len: AssignedValue, max_len: usize) -> Self { + assert!( + len.value().le(&F::from(max_len as u64)), + "Invalid length which exceeds MAX_LEN {}", + max_len + ); + assert_eq!(bytes.len(), max_len, "bytes is not padded correctly"); + Self { bytes, len } + } + + /// Returns the maximum length of the byte array. + pub fn max_len(&self) -> usize { + self.bytes.len() + } + + /// Left pads the variable length byte array with 0s to the MAX_LEN + pub fn left_pad_to_fixed( + &self, + ctx: &mut Context, + gate: &impl GateInstructions, + ) -> FixLenBytesVec { + let padded = left_pad_var_array_to_fixed(ctx, gate, &self.bytes, self.len, self.max_len()); + FixLenBytesVec::new(padded.into_iter().map(|b| SafeByte(b)).collect_vec(), self.max_len()) + } + + /// Return a copy of the byte array with 0 padding ensured. + pub fn ensure_0_padding(&self, ctx: &mut Context, gate: &impl GateInstructions) -> Self { + let bytes = ensure_0_padding(ctx, gate, &self.bytes, self.len); + Self::new(bytes, self.len, self.max_len()) + } +} + +/// Represents a fixed length byte array in circuit. +#[derive(Debug, Clone, Getters)] +pub struct FixLenBytes { + /// The byte array + #[getset(get = "pub")] + bytes: [SafeByte; LEN], +} + +impl FixLenBytes { + /// Constructor + pub fn new(bytes: [SafeByte; LEN]) -> Self { + Self { bytes } + } + + /// Returns the length of the byte array. + pub fn len(&self) -> usize { + LEN + } + + /// Returns inner array of [SafeByte]s. + pub fn into_bytes(self) -> [SafeByte; LEN] { + self.bytes + } +} + +/// Represents a fixed length byte array in circuit. Not encouraged to use because `MAX_LEN` cannot be verified at compile time. +#[derive(Debug, Clone, Getters)] +pub struct FixLenBytesVec { + /// The byte array + #[getset(get = "pub")] + bytes: Vec>, +} + +impl FixLenBytesVec { + /// Constructor + pub fn new(bytes: Vec>, len: usize) -> Self { + assert_eq!(bytes.len(), len, "bytes length doesn't match"); + Self { bytes } + } + + /// Returns the length of the byte array. + pub fn len(&self) -> usize { + self.bytes.len() + } + + /// Returns inner array of [SafeByte]s. + pub fn into_bytes(self) -> Vec> { + self.bytes + } +} + +// Represents a fixed length byte array in circuit as a vector, where length must be fixed. +// Not encouraged to use because `LEN` cannot be verified at compile time. +// pub type FixLenBytesVec = Vec>; + +/// Takes a fixed length array `arr` and returns a length `out_len` array equal to +/// `[[0; out_len - len], arr[..len]].concat()`, i.e., we take `arr[..len]` and +/// zero pad it on the left. +/// +/// Assumes `0 < len <= max_len <= out_len`. +pub fn left_pad_var_array_to_fixed( + ctx: &mut Context, + gate: &impl GateInstructions, + arr: &[impl AsRef>], + len: AssignedValue, + out_len: usize, +) -> Vec> { + debug_assert!(arr.len() <= out_len); + debug_assert!(bit_length(out_len as u64) < F::CAPACITY as usize); + + let mut padded = arr.iter().map(|b| *b.as_ref()).collect_vec(); + padded.resize(out_len, padded[0]); + // We use a barrel shifter to shift `arr` to the right by `out_len - len` bits. + let shift = gate.sub(ctx, Constant(F::from(out_len as u64)), len); + let shift_bits = gate.num_to_bits(ctx, shift, bit_length(out_len as u64)); + for (i, shift_bit) in shift_bits.into_iter().enumerate() { + let shifted = (0..out_len) + .map(|j| if j >= (1 << i) { Existing(padded[j - (1 << i)]) } else { Constant(F::ZERO) }) + .collect_vec(); + padded = padded + .into_iter() + .zip(shifted) + .map(|(noshift, shift)| gate.select(ctx, shift, noshift, shift_bit)) + .collect_vec(); + } + padded +} + +fn ensure_0_padding( + ctx: &mut Context, + gate: &impl GateInstructions, + bytes: &[SafeByte], + len: AssignedValue, +) -> Vec> { + let max_len = bytes.len(); + // Generate a mask array where a[i] = i < len for i = 0..max_len. + let idx = gate.dec(ctx, len); + let len_indicator = gate.idx_to_indicator(ctx, idx, max_len); + // inputs_mask[i] = sum(len_indicator[i..]) + let mut mask = gate.partial_sums(ctx, len_indicator.clone().into_iter().rev()).collect_vec(); + mask.reverse(); + + bytes + .iter() + .zip(mask.iter()) + .map(|(byte, mask)| SafeByte(gate.mul(ctx, byte.0, *mask))) + .collect_vec() +} diff --git a/vendor/halo2-lib/halo2-base/src/safe_types/mod.rs b/vendor/halo2-lib/halo2-base/src/safe_types/mod.rs new file mode 100644 index 00000000..c328c27a --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/safe_types/mod.rs @@ -0,0 +1,324 @@ +use std::{ + borrow::Borrow, + cmp::{max, min}, +}; + +use crate::{ + gates::{ + flex_gate::GateInstructions, + range::{RangeChip, RangeInstructions}, + }, + utils::ScalarField, + AssignedValue, Context, + QuantumCell::Witness, +}; + +use itertools::Itertools; + +mod bytes; +mod primitives; + +pub use bytes::*; +pub use primitives::*; + +/// Unit Tests +#[cfg(test)] +pub mod tests; + +type RawAssignedValues = Vec>; + +const BITS_PER_BYTE: usize = 8; + +/// [`SafeType`]'s goal is to avoid out-of-range undefined behavior. +/// When building circuits, it's common to use multiple [`AssignedValue`]s to represent +/// a logical variable. For example, we might want to represent a hash with 32 [`AssignedValue`] +/// where each [`AssignedValue`] represents 1 byte. However, the range of [`AssignedValue`] is much +/// larger than 1 byte(0~255). If a circuit takes 32 [`AssignedValue`] as inputs and some of them +/// are actually greater than 255, there could be some undefined behaviors. +/// [`SafeType`] gurantees the value range of its owned [`AssignedValue`]. So circuits don't need to +/// do any extra value checking if they take SafeType as inputs. +/// - `TOTAL_BITS` is the number of total bits of this type. +/// - `BYTES_PER_ELE` is the number of bytes of each element. +#[derive(Clone, Debug)] +pub struct SafeType { + // value is stored in little-endian. + value: RawAssignedValues, +} + +impl + SafeType +{ + /// Number of bytes of each element. + pub const BYTES_PER_ELE: usize = BYTES_PER_ELE; + /// Total bits of this type. + pub const TOTAL_BITS: usize = TOTAL_BITS; + /// Number of elements of this type. + pub const VALUE_LENGTH: usize = TOTAL_BITS.div_ceil(BYTES_PER_ELE * BITS_PER_BYTE); + + /// Number of bits of each element. + pub fn bits_per_ele() -> usize { + min(TOTAL_BITS, BYTES_PER_ELE * BITS_PER_BYTE) + } + + // new is private so Safetype can only be constructed by this crate. + fn new(raw_values: RawAssignedValues) -> Self { + assert!(raw_values.len() == Self::VALUE_LENGTH, "Invalid raw values length"); + Self { value: raw_values } + } + + /// Return values in little-endian. + pub fn value(&self) -> &[AssignedValue] { + &self.value + } +} + +impl AsRef<[AssignedValue]> + for SafeType +{ + fn as_ref(&self) -> &[AssignedValue] { + self.value() + } +} + +impl TryFrom>> + for SafeType +{ + type Error = String; + + fn try_from(value: Vec>) -> Result { + if value.len() * 8 != TOTAL_BITS { + return Err("Invalid length".to_owned()); + } + Ok(Self::new(value.into_iter().map(|b| b.0).collect::>())) + } +} + +/// SafeType for Address. +pub type SafeAddress = SafeType; +/// SafeType for bytes32. +pub type SafeBytes32 = SafeType; + +/// Chip for SafeType +pub struct SafeTypeChip<'a, F: ScalarField> { + range_chip: &'a RangeChip, +} + +impl<'a, F: ScalarField> SafeTypeChip<'a, F> { + /// Construct a SafeTypeChip. + pub fn new(range_chip: &'a RangeChip) -> Self { + Self { range_chip } + } + + /// Convert a vector of AssignedValue (treated as little-endian) to a SafeType. + /// The number of bytes of inputs must equal to the number of bytes of outputs. + /// This function also add contraints that a AssignedValue in inputs must be in the range of a byte. + pub fn raw_bytes_to( + &self, + ctx: &mut Context, + inputs: RawAssignedValues, + ) -> SafeType { + let element_bits = SafeType::::bits_per_ele(); + let bits = TOTAL_BITS; + assert!( + inputs.len() * BITS_PER_BYTE == max(bits, BITS_PER_BYTE), + "number of bits doesn't match" + ); + self.add_bytes_constraints(ctx, &inputs, bits); + // inputs is a bool or uint8. + if bits == 1 || element_bits == BITS_PER_BYTE { + return SafeType::::new(inputs); + }; + + let byte_base = (0..BYTES_PER_ELE) + .map(|i| Witness(self.range_chip.gate.pow_of_two[i * BITS_PER_BYTE])) + .collect::>(); + let value = inputs + .chunks(BYTES_PER_ELE) + .map(|chunk| { + self.range_chip.gate.inner_product( + ctx, + chunk.to_vec(), + byte_base[..chunk.len()].to_vec(), + ) + }) + .collect::>(); + SafeType::::new(value) + } + + /// Unsafe method that directly converts `input` to [`SafeType`] **without any checks**. + /// This should **only** be used if an external library needs to convert their types to [`SafeType`]. + pub fn unsafe_to_safe_type( + inputs: RawAssignedValues, + ) -> SafeType { + assert_eq!(inputs.len(), SafeType::::VALUE_LENGTH); + SafeType::::new(inputs) + } + + /// Constrains that the `input` is a boolean value (either 0 or 1) and wraps it in [`SafeBool`]. + pub fn assert_bool(&self, ctx: &mut Context, input: AssignedValue) -> SafeBool { + self.range_chip.gate().assert_bit(ctx, input); + SafeBool(input) + } + + /// Load a boolean value as witness and constrain it is either 0 or 1. + pub fn load_bool(&self, ctx: &mut Context, input: bool) -> SafeBool { + let input = ctx.load_witness(F::from(input)); + self.assert_bool(ctx, input) + } + + /// Unsafe method that directly converts `input` to [`SafeBool`] **without any checks**. + /// This should **only** be used if an external library needs to convert their types to [`SafeBool`]. + pub fn unsafe_to_bool(input: AssignedValue) -> SafeBool { + SafeBool(input) + } + + /// Constrains that the `input` is a byte value and wraps it in [`SafeByte`]. + pub fn assert_byte(&self, ctx: &mut Context, input: AssignedValue) -> SafeByte { + self.range_chip.range_check(ctx, input, BITS_PER_BYTE); + SafeByte(input) + } + + /// Load a boolean value as witness and constrain it is either 0 or 1. + pub fn load_byte(&self, ctx: &mut Context, input: u8) -> SafeByte { + let input = ctx.load_witness(F::from(input as u64)); + self.assert_byte(ctx, input) + } + + /// Unsafe method that directly converts `input` to [`SafeByte`] **without any checks**. + /// This should **only** be used if an external library needs to convert their types to [`SafeByte`]. + pub fn unsafe_to_byte(input: AssignedValue) -> SafeByte { + SafeByte(input) + } + + /// Unsafe method that directly converts `inputs` to [`VarLenBytes`] **without any checks**. + /// This should **only** be used if an external library needs to convert their types to [`SafeByte`]. + pub fn unsafe_to_var_len_bytes( + inputs: [AssignedValue; MAX_LEN], + len: AssignedValue, + ) -> VarLenBytes { + VarLenBytes::::new(inputs.map(|input| Self::unsafe_to_byte(input)), len) + } + + /// Unsafe method that directly converts `inputs` to [`VarLenBytesVec`] **without any checks**. + /// This should **only** be used if an external library needs to convert their types to [`SafeByte`]. + pub fn unsafe_to_var_len_bytes_vec( + inputs: RawAssignedValues, + len: AssignedValue, + max_len: usize, + ) -> VarLenBytesVec { + VarLenBytesVec::::new( + inputs.iter().map(|input| Self::unsafe_to_byte(*input)).collect_vec(), + len, + max_len, + ) + } + + /// Unsafe method that directly converts `inputs` to [`FixLenBytes`] **without any checks**. + /// This should **only** be used if an external library needs to convert their types to [`SafeByte`]. + pub fn unsafe_to_fix_len_bytes( + inputs: [AssignedValue; MAX_LEN], + ) -> FixLenBytes { + FixLenBytes::::new(inputs.map(|input| Self::unsafe_to_byte(input))) + } + + /// Unsafe method that directly converts `inputs` to [`FixLenBytesVec`] **without any checks**. + /// This should **only** be used if an external library needs to convert their types to [`SafeByte`]. + pub fn unsafe_to_fix_len_bytes_vec( + inputs: RawAssignedValues, + len: usize, + ) -> FixLenBytesVec { + FixLenBytesVec::::new( + inputs.into_iter().map(|input| Self::unsafe_to_byte(input)).collect_vec(), + len, + ) + } + + /// Converts a slice of AssignedValue(treated as little-endian) to VarLenBytes. + /// + /// * inputs: Slice representing the byte array. + /// * len: [`AssignedValue`] witness representing the variable length of the byte array. Constrained to be `<= MAX_LEN`. + /// * MAX_LEN: [usize] representing the maximum length of the byte array and the number of elements it must contain. + /// + /// ## Assumptions + /// * `MAX_LEN < u64::MAX` to prevent overflow (but you should never make an array this large) + /// * `ceil((MAX_LEN + 1).bits() / lookup_bits) * lookup_bits <= F::CAPACITY` where `lookup_bits = self.range_chip.lookup_bits` + pub fn raw_to_var_len_bytes( + &self, + ctx: &mut Context, + inputs: [AssignedValue; MAX_LEN], + len: AssignedValue, + ) -> VarLenBytes { + self.range_chip.check_less_than_safe(ctx, len, MAX_LEN as u64 + 1); + VarLenBytes::::new(inputs.map(|input| self.assert_byte(ctx, input)), len) + } + + /// Converts a vector of AssignedValue to [VarLenBytesVec]. Not encouraged to use because `MAX_LEN` cannot be verified at compile time. + /// + /// * inputs: Vector representing the byte array, right padded to `max_len`. See [VarLenBytesVec] for details about padding. + /// * len: [`AssignedValue`] witness representing the variable length of the byte array. Constrained to be `<= max_len`. + /// * max_len: [usize] representing the maximum length of the byte array and the number of elements it must contain. We enforce this to be provided explictly to make sure length of `inputs` is determinstic. + /// + /// ## Assumptions + /// * `max_len < u64::MAX` to prevent overflow (but you should never make an array this large) + /// * `ceil((max_len + 1).bits() / lookup_bits) * lookup_bits <= F::CAPACITY` where `lookup_bits = self.range_chip.lookup_bits` + pub fn raw_to_var_len_bytes_vec( + &self, + ctx: &mut Context, + inputs: RawAssignedValues, + len: AssignedValue, + max_len: usize, + ) -> VarLenBytesVec { + self.range_chip.check_less_than_safe(ctx, len, max_len as u64 + 1); + VarLenBytesVec::::new( + inputs.iter().map(|input| self.assert_byte(ctx, *input)).collect_vec(), + len, + max_len, + ) + } + + /// Converts a slice of AssignedValue(treated as little-endian) to FixLenBytes. + /// + /// * inputs: Slice representing the byte array. + /// * LEN: length of the byte array. + pub fn raw_to_fix_len_bytes( + &self, + ctx: &mut Context, + inputs: [AssignedValue; LEN], + ) -> FixLenBytes { + FixLenBytes::::new(inputs.map(|input| self.assert_byte(ctx, input))) + } + + /// Converts a slice of AssignedValue(treated as little-endian) to FixLenBytesVec. + /// + /// * inputs: Slice representing the byte array. + /// * len: length of the byte array. We enforce this to be provided explictly to make sure length of `inputs` is determinstic. + pub fn raw_to_fix_len_bytes_vec( + &self, + ctx: &mut Context, + inputs: RawAssignedValues, + len: usize, + ) -> FixLenBytesVec { + FixLenBytesVec::::new( + inputs.into_iter().map(|input| self.assert_byte(ctx, input)).collect_vec(), + len, + ) + } + + /// Assumes that `bits <= inputs.len() * 8`. + fn add_bytes_constraints( + &self, + ctx: &mut Context, + inputs: &RawAssignedValues, + bits: usize, + ) { + let mut bits_left = bits; + for input in inputs { + let num_bit = min(bits_left, BITS_PER_BYTE); + self.range_chip.range_check(ctx, *input, num_bit); + bits_left -= num_bit; + } + } + + // TODO: Add comparison. e.g. is_less_than(SafeUint8, SafeUint8) -> SafeBool + // TODO: Add type castings. e.g. uint256 -> bytes32/uint32 -> uint64 +} diff --git a/vendor/halo2-lib/halo2-base/src/safe_types/primitives.rs b/vendor/halo2-lib/halo2-base/src/safe_types/primitives.rs new file mode 100644 index 00000000..92e00f2d --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/safe_types/primitives.rs @@ -0,0 +1,59 @@ +use std::ops::Deref; + +use crate::QuantumCell; + +use super::*; +/// SafeType for bool (1 bit). +/// +/// This is a separate struct from `CompactSafeType` with the same behavior. Because +/// we know only one [`AssignedValue`] is needed to hold the boolean value, we avoid +/// using `CompactSafeType` to avoid the additional heap allocation from a length 1 vector. +#[derive(Clone, Copy, Debug)] +pub struct SafeBool(pub(super) AssignedValue); + +/// SafeType for byte (8 bits). +/// +/// This is a separate struct from `CompactSafeType` with the same behavior. Because +/// we know only one [`AssignedValue`] is needed to hold the boolean value, we avoid +/// using `CompactSafeType` to avoid the additional heap allocation from a length 1 vector. +#[derive(Clone, Copy, Debug)] +pub struct SafeByte(pub(super) AssignedValue); + +macro_rules! safe_primitive_impls { + ($SafePrimitive:ty) => { + impl AsRef> for $SafePrimitive { + fn as_ref(&self) -> &AssignedValue { + &self.0 + } + } + + impl Borrow> for $SafePrimitive { + fn borrow(&self) -> &AssignedValue { + &self.0 + } + } + + impl Deref for $SafePrimitive { + type Target = AssignedValue; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl From<$SafePrimitive> for AssignedValue { + fn from(safe_primitive: $SafePrimitive) -> Self { + safe_primitive.0 + } + } + + impl From<$SafePrimitive> for QuantumCell { + fn from(safe_primitive: $SafePrimitive) -> Self { + QuantumCell::Existing(safe_primitive.0) + } + } + }; +} + +safe_primitive_impls!(SafeBool); +safe_primitive_impls!(SafeByte); diff --git a/vendor/halo2-lib/halo2-base/src/safe_types/tests/bytes.rs b/vendor/halo2-lib/halo2-base/src/safe_types/tests/bytes.rs new file mode 100644 index 00000000..9c24444f --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/safe_types/tests/bytes.rs @@ -0,0 +1,235 @@ +use crate::{ + gates::{circuit::builder::RangeCircuitBuilder, RangeInstructions}, + halo2_proofs::{ + halo2curves::bn256::{Bn256, Fr}, + plonk::{keygen_pk, keygen_vk}, + poly::kzg::commitment::ParamsKZG, + }, + safe_types::SafeTypeChip, + utils::{ + testing::{base_test, check_proof, gen_proof}, + ScalarField, + }, + Context, +}; +use rand::rngs::OsRng; +use std::vec; +use test_case::test_case; + +// =========== Utilies =============== +fn mock_circuit_test, SafeTypeChip<'_, Fr>)>(mut f: FM) { + base_test().k(10).lookup_bits(8).run(|ctx, range| { + let safe = SafeTypeChip::new(range); + f(ctx, safe); + }); +} + +// =========== Mock Prover =========== + +// Circuit Satisfied for valid inputs +#[test] +fn pos_var_len_bytes() { + base_test().k(10).lookup_bits(8).run(|ctx, range| { + let safe = SafeTypeChip::new(range); + let bytes = ctx.assign_witnesses( + vec![255u64, 255u64, 255u64, 255u64].into_iter().map(Fr::from).collect::>(), + ); + let len = ctx.load_witness(Fr::from(3u64)); + safe.raw_to_var_len_bytes::<4>(ctx, bytes.clone().try_into().unwrap(), len); + + // check edge case len == MAX_LEN + let len = ctx.load_witness(Fr::from(4u64)); + safe.raw_to_var_len_bytes::<4>(ctx, bytes.try_into().unwrap(), len); + }); +} + +#[test_case(vec![1,2,3], 4 => vec![0,1,2,3]; "pos left pad 3 to 4")] +#[test_case(vec![1,2,3], 5 => vec![0,0,1,2,3]; "pos left pad 3 to 5")] +#[test_case(vec![1,2,3], 6 => vec![0,0,0,1,2,3]; "pos left pad 3 to 6")] +fn left_pad_var_len_bytes(mut bytes: Vec, max_len: usize) -> Vec { + base_test().k(10).lookup_bits(8).run(|ctx, range| { + let safe = SafeTypeChip::new(range); + let len = bytes.len(); + bytes.resize(max_len, 0); + let bytes = ctx.assign_witnesses(bytes.into_iter().map(|b| Fr::from(b as u64))); + let len = ctx.load_witness(Fr::from(len as u64)); + let bytes = safe.raw_to_var_len_bytes_vec(ctx, bytes, len, max_len); + let padded = bytes.left_pad_to_fixed(ctx, range.gate()); + padded.bytes().iter().map(|b| b.as_ref().value().get_lower_64() as u8).collect() + }) +} + +// Checks circuit is unsatisfied for AssignedValue's are not in range 0..256 +#[test] +#[should_panic(expected = "circuit was not satisfied")] +fn neg_var_len_bytes_witness_values_not_bytes() { + mock_circuit_test(|ctx: &mut Context, safe: SafeTypeChip<'_, Fr>| { + let len = ctx.load_witness(Fr::from(3u64)); + let fake_bytes = ctx.assign_witnesses( + vec![500u64, 500u64, 500u64, 500u64].into_iter().map(Fr::from).collect::>(), + ); + safe.raw_to_var_len_bytes::<4>(ctx, fake_bytes.try_into().unwrap(), len); + }); +} + +// Checks assertion len <= max_len +#[test] +#[should_panic] +fn neg_var_len_bytes_len_less_than_max_len() { + mock_circuit_test(|ctx: &mut Context, safe: SafeTypeChip<'_, Fr>| { + let len = ctx.load_witness(Fr::from(5u64)); + let fake_bytes = ctx.assign_witnesses( + vec![500u64, 500u64, 500u64, 500u64].into_iter().map(Fr::from).collect::>(), + ); + safe.raw_to_var_len_bytes::<4>(ctx, fake_bytes.try_into().unwrap(), len); + }); +} + +// Circuit Satisfied for valid inputs +#[test] +fn pos_var_len_bytes_vec() { + base_test().k(10).lookup_bits(8).run(|ctx, range| { + let safe = SafeTypeChip::new(range); + let bytes = ctx.assign_witnesses( + vec![255u64, 255u64, 255u64, 255u64].into_iter().map(Fr::from).collect::>(), + ); + let len = ctx.load_witness(Fr::from(3u64)); + safe.raw_to_var_len_bytes_vec(ctx, bytes.clone(), len, 4); + + // check edge case len == MAX_LEN + let len = ctx.load_witness(Fr::from(4u64)); + safe.raw_to_var_len_bytes_vec(ctx, bytes, len, 4); + }); +} + +// Checks circuit is unsatisfied for AssignedValue's are not in range 0..256 +#[test] +#[should_panic(expected = "circuit was not satisfied")] +fn neg_var_len_bytes_vec_witness_values_not_bytes() { + mock_circuit_test(|ctx: &mut Context, safe: SafeTypeChip<'_, Fr>| { + let len = ctx.load_witness(Fr::from(3u64)); + let fake_bytes = ctx.assign_witnesses( + vec![500u64, 500u64, 500u64, 500u64].into_iter().map(Fr::from).collect::>(), + ); + let max_len = fake_bytes.len(); + safe.raw_to_var_len_bytes_vec(ctx, fake_bytes, len, max_len); + }); +} + +// Checks assertion len <= max_len +#[test] +#[should_panic] +fn neg_var_len_bytes_vec_len_less_than_max_len() { + mock_circuit_test(|ctx: &mut Context, safe: SafeTypeChip<'_, Fr>| { + let len = ctx.load_witness(Fr::from(5u64)); + let fake_bytes = ctx.assign_witnesses( + vec![500u64, 500u64, 500u64, 500u64].into_iter().map(Fr::from).collect::>(), + ); + let max_len = 4; + safe.raw_to_var_len_bytes_vec(ctx, fake_bytes, len, max_len); + }); +} + +// Circuit Satisfied for valid inputs +#[test] +fn pos_fix_len_bytes() { + base_test().k(10).lookup_bits(8).run(|ctx, range| { + let safe = SafeTypeChip::new(range); + let fake_bytes = ctx.assign_witnesses( + vec![255u64, 255u64, 255u64, 255u64].into_iter().map(Fr::from).collect::>(), + ); + safe.raw_to_fix_len_bytes::<4>(ctx, fake_bytes.try_into().unwrap()); + }); +} + +// Assert inputs.len() == len +#[test] +#[should_panic] +fn neg_fix_len_bytes_vec() { + base_test().k(10).lookup_bits(8).run(|ctx, range| { + let safe = SafeTypeChip::new(range); + let fake_bytes = ctx.assign_witnesses( + vec![255u64, 255u64, 255u64, 255u64].into_iter().map(Fr::from).collect::>(), + ); + safe.raw_to_fix_len_bytes_vec(ctx, fake_bytes, 5); + }); +} + +// Circuit Satisfied for valid inputs +#[test] +fn pos_fix_len_bytes_vec() { + base_test().k(10).lookup_bits(8).run(|ctx, range| { + let safe = SafeTypeChip::new(range); + let fake_bytes = ctx.assign_witnesses( + vec![255u64, 255u64, 255u64, 255u64].into_iter().map(Fr::from).collect::>(), + ); + safe.raw_to_fix_len_bytes_vec(ctx, fake_bytes, 4); + }); +} + +// =========== Prover =========== +#[test] +fn pos_prover_satisfied() { + const KEYGEN_MAX_LEN: usize = 4; + const PROVER_MAX_LEN: usize = 4; + let keygen_inputs = (vec![1u64, 2u64, 3u64, 4u64], 3); + let proof_inputs = (vec![1u64, 2u64, 3u64, 4u64], 3); + prover_satisfied::(keygen_inputs, proof_inputs); +} + +#[test] +fn pos_diff_len_same_max_len() { + const KEYGEN_MAX_LEN: usize = 4; + const PROVER_MAX_LEN: usize = 4; + let keygen_inputs = (vec![1u64, 2u64, 3u64, 4u64], 3); + let proof_inputs = (vec![1u64, 2u64, 3u64, 4u64], 2); + prover_satisfied::(keygen_inputs, proof_inputs); +} + +#[test] +#[should_panic] +fn neg_different_proof_max_len() { + const KEYGEN_MAX_LEN: usize = 4; + const PROVER_MAX_LEN: usize = 3; + let keygen_inputs = (vec![1u64, 2u64, 3u64, 4u64], 4); + let proof_inputs = (vec![1u64, 2u64, 3u64], 3); + prover_satisfied::(keygen_inputs, proof_inputs); +} + +// test circuit +fn var_byte_array_circuit( + k: usize, + witness_gen_only: bool, + (bytes, len): (Vec, usize), +) -> RangeCircuitBuilder { + let lookup_bits = 3; + let mut builder = + RangeCircuitBuilder::new(witness_gen_only).use_k(k).use_lookup_bits(lookup_bits); + let range = builder.range_chip(); + let safe = SafeTypeChip::new(&range); + let ctx = builder.main(0); + let len = ctx.load_witness(Fr::from(len as u64)); + let fake_bytes = ctx.assign_witnesses(bytes.into_iter().map(Fr::from).collect::>()); + safe.raw_to_var_len_bytes::(ctx, fake_bytes.try_into().unwrap(), len); + builder.calculate_params(Some(9)); + builder +} + +// Prover test +fn prover_satisfied( + keygen_inputs: (Vec, usize), + proof_inputs: (Vec, usize), +) { + let k = 11; + let rng = OsRng; + let params = ParamsKZG::::setup(k as u32, rng); + let keygen_circuit = var_byte_array_circuit::(k, false, keygen_inputs); + let vk = keygen_vk(¶ms, &keygen_circuit).unwrap(); + let pk = keygen_pk(¶ms, vk.clone(), &keygen_circuit).unwrap(); + let break_points = keygen_circuit.break_points(); + + let mut proof_circuit = var_byte_array_circuit::(k, true, proof_inputs); + proof_circuit.set_break_points(break_points); + let proof = gen_proof(¶ms, &pk, proof_circuit); + check_proof(¶ms, &vk, &proof[..], true); +} diff --git a/vendor/halo2-lib/halo2-base/src/safe_types/tests/mod.rs b/vendor/halo2-lib/halo2-base/src/safe_types/tests/mod.rs new file mode 100644 index 00000000..ee37540f --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/safe_types/tests/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod bytes; +pub(crate) mod safe_type; diff --git a/vendor/halo2-lib/halo2-base/src/safe_types/tests/safe_type.rs b/vendor/halo2-lib/halo2-base/src/safe_types/tests/safe_type.rs new file mode 100644 index 00000000..069c71c1 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/safe_types/tests/safe_type.rs @@ -0,0 +1,257 @@ +use crate::{ + gates::circuit::{builder::RangeCircuitBuilder, CircuitBuilderStage}, + halo2_proofs::plonk::{keygen_pk, keygen_vk, Assigned}, + halo2_proofs::{halo2curves::bn256::Fr, poly::kzg::commitment::ParamsKZG}, + safe_types::*, + utils::testing::{check_proof, gen_proof}, +}; +use itertools::Itertools; +use rand::rngs::OsRng; + +/// Represent TOTAL_BITS with the least number of AssignedValue. +/// (2^(F::NUM_BITS) - 1) might not be a valid value for F. e.g. max value of F is a prime in [2^(F::NUM_BITS-1), 2^(F::NUM_BITS) - 1] +// @dev Cannot compute BYTES_PER_ELEMENT = F::CAPACITY / 8 generically due to Rust const generic limitations in stable Rust. +// Fr::CAPACITY = 253 so 253 / 8 = 31. +type CompactSafeType = SafeType; + +/// SafeType for uint64. +pub type SafeUint64 = CompactSafeType<64>; +/// SafeType for uint256. +pub type SafeUint256 = CompactSafeType<256>; + +// soundness checks for `raw_bytes_to` function +fn test_raw_bytes_to_gen( + k: u32, + raw_bytes: &[Fr], + outputs: &[Fr], + expect_satisfied: bool, +) { + // first create proving and verifying key + let lookup_bits = 3; + let mut builder = RangeCircuitBuilder::from_stage(CircuitBuilderStage::Keygen) + .use_k(k as usize) + .use_lookup_bits(lookup_bits); + let range_chip = builder.range_chip(); + let safe_type_chip = SafeTypeChip::new(&range_chip); + + let dummy_raw_bytes = builder + .main(0) + .assign_witnesses((0..raw_bytes.len()).map(|_| Fr::zero()).collect::>()); + + let safe_value = + safe_type_chip.raw_bytes_to::(builder.main(0), dummy_raw_bytes); + // get the offsets of the safe value cells for later 'pranking' + let safe_value_offsets = + safe_value.value().iter().map(|v| v.cell.unwrap().offset).collect::>(); + + let config_params = builder.calculate_params(Some(9)); + let params = ParamsKZG::setup(k, OsRng); + // generate proving key + let vk = keygen_vk(¶ms, &builder).unwrap(); + let pk = keygen_pk(¶ms, vk, &builder).unwrap(); + let vk = pk.get_vk(); // pk consumed vk + let break_points = builder.break_points(); + drop(builder); + + // now create different proofs to test the soundness of the circuit + let gen_pf = |inputs: &[Fr], outputs: &[Fr]| { + let mut builder = RangeCircuitBuilder::prover(config_params.clone(), break_points.clone()); + let range_chip = builder.range_chip(); + let safe_type_chip = SafeTypeChip::new(&range_chip); + + let assigned_raw_bytes = builder.main(0).assign_witnesses(inputs.to_vec()); + safe_type_chip + .raw_bytes_to::(builder.main(0), assigned_raw_bytes); + // prank the safe value cells + for (offset, witness) in safe_value_offsets.iter().zip_eq(outputs) { + builder.main(0).advice[*offset] = Assigned::::Trivial(*witness); + } + gen_proof(¶ms, &pk, builder) + }; + let pf = gen_pf(raw_bytes, outputs); + check_proof(¶ms, vk, &pf, expect_satisfied); +} + +#[test] +fn test_raw_bytes_to_bool() { + let k = 8; + test_raw_bytes_to_gen::<1, 1>(k, &[Fr::from(0)], &[Fr::from(0)], true); + test_raw_bytes_to_gen::<1, 1>(k, &[Fr::from(1)], &[Fr::from(1)], true); + test_raw_bytes_to_gen::<1, 1>(k, &[Fr::from(1)], &[Fr::from(0)], false); + test_raw_bytes_to_gen::<1, 1>(k, &[Fr::from(0)], &[Fr::from(1)], false); + test_raw_bytes_to_gen::<1, 1>(k, &[Fr::from(3)], &[Fr::from(0)], false); + test_raw_bytes_to_gen::<1, 1>(k, &[Fr::from(3)], &[Fr::from(1)], false); +} + +#[test] +fn test_raw_bytes_to_uint256() { + const BYTES_PER_ELE: usize = SafeUint256::BYTES_PER_ELE; + const TOTAL_BITS: usize = SafeUint256::TOTAL_BITS; + let k = 11; + // [0x0; 32] -> [0x0, 0x0] + test_raw_bytes_to_gen::( + k, + &[Fr::from(0); 32], + &[Fr::from(0), Fr::from(0)], + true, + ); + test_raw_bytes_to_gen::( + k, + &[[Fr::from(1)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), + &[Fr::from(1), Fr::from(0)], + true, + ); + // [0x1, 0x2] + [0x0; 30] -> [0x201, 0x0] + test_raw_bytes_to_gen::( + k, + &[[Fr::from(1), Fr::from(2)].as_slice(), [Fr::from(0); 30].as_slice()].concat(), + &[Fr::from(0x201), Fr::from(0)], + true, + ); + // [[0xff; 32] -> [2^248 - 1, 0xff] + test_raw_bytes_to_gen::( + k, + &[Fr::from(0xff); 32], + &[ + Fr::from_raw([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffff, + ]), + Fr::from(0xff), + ], + true, + ); + + // invalid raw_bytes, last bytes > 0xff + test_raw_bytes_to_gen::( + k, + &[[Fr::from(0); 31].as_slice(), [Fr::from(0x1ff)].as_slice()].concat(), + &[Fr::from(0), Fr::from(0xff)], + false, + ); + // 0xff != 0xff00000000000000000000000000000000000000000000000000000000000000 + test_raw_bytes_to_gen::( + k, + &[[Fr::from(0xff)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), + &[Fr::from(0), Fr::from(0xff)], + false, + ); + // outputs overflow + test_raw_bytes_to_gen::( + k, + &[Fr::from(0xff); 32], + &[ + Fr::from_raw([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffff, + ]), + Fr::from(0x1ff), + ], + false, + ); +} + +#[test] +fn test_raw_bytes_to_uint64() { + const BYTES_PER_ELE: usize = SafeUint64::BYTES_PER_ELE; + const TOTAL_BITS: usize = SafeUint64::TOTAL_BITS; + let k = 10; + // [0x0; 8] -> [0x0] + test_raw_bytes_to_gen::(k, &[Fr::from(0); 8], &[Fr::from(0)], true); + // [0x1, 0x2] + [0x0; 6] -> [0x201] + test_raw_bytes_to_gen::( + k, + &[[Fr::from(1), Fr::from(2)].as_slice(), [Fr::from(0); 6].as_slice()].concat(), + &[Fr::from(0x201)], + true, + ); + // [[0xff; 8] -> [2^64-1] + test_raw_bytes_to_gen::( + k, + &[Fr::from(0xff); 8], + &[Fr::from(0xffffffffffffffff)], + true, + ); + + // invalid raw_bytes, last bytes > 0xff + test_raw_bytes_to_gen::( + k, + &[[Fr::from(0); 7].as_slice(), [Fr::from(0x1ff)].as_slice()].concat(), + &[Fr::from(0xff00000000000000)], + false, + ); + // 0xff != 0xff00000000000000000000000000000000000000000000000000000000000000 + test_raw_bytes_to_gen::( + k, + &[[Fr::from(0xff)].as_slice(), [Fr::from(0); 7].as_slice()].concat(), + &[Fr::from(0xff00000000000000)], + false, + ); + // outputs overflow + test_raw_bytes_to_gen::( + k, + &[Fr::from(0xff); 8], + &[Fr::from_raw([0xffffffffffffffff, 0x1, 0x0, 0x0])], + false, + ); +} + +#[test] +fn test_raw_bytes_to_bytes32() { + const BYTES_PER_ELE: usize = SafeBytes32::::BYTES_PER_ELE; + const TOTAL_BITS: usize = SafeBytes32::::TOTAL_BITS; + let k = 10; + // [0x0; 32] -> [0x0; 32] + test_raw_bytes_to_gen::( + k, + &[Fr::from(0); 32], + &[Fr::from(0); 32], + true, + ); + test_raw_bytes_to_gen::( + k, + &[[Fr::from(1)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), + &[[Fr::from(1)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), + true, + ); + // [0x1, 0x2] + [0x0; 30] -> [0x201, 0x0] + test_raw_bytes_to_gen::( + k, + &[[Fr::from(1), Fr::from(2)].as_slice(), [Fr::from(0); 30].as_slice()].concat(), + &[[Fr::from(1), Fr::from(2)].as_slice(), [Fr::from(0); 30].as_slice()].concat(), + true, + ); + // [[0xff; 32] -> [2^248 - 1, 0xff] + test_raw_bytes_to_gen::( + k, + &[Fr::from(0xff); 32], + &[Fr::from(0xff); 32], + true, + ); + + // invalid raw_bytes, last bytes > 0xff + test_raw_bytes_to_gen::( + k, + &[[Fr::from(0); 31].as_slice(), [Fr::from(0x1ff)].as_slice()].concat(), + &[[Fr::from(0); 31].as_slice(), [Fr::from(0x1ff)].as_slice()].concat(), + false, + ); + // 0xff != 0xff00000000000000000000000000000000000000000000000000000000000000 + test_raw_bytes_to_gen::( + k, + &[[Fr::from(0xff)].as_slice(), [Fr::from(0); 31].as_slice()].concat(), + &[[Fr::from(0); 31].as_slice(), [Fr::from(0xff)].as_slice()].concat(), + false, + ); + // outputs overflow + test_raw_bytes_to_gen::( + k, + &[Fr::from(0xff); 32], + &[Fr::from(0x1ff); 32], + false, + ); +} diff --git a/vendor/halo2-lib/halo2-base/src/utils/halo2.rs b/vendor/halo2-lib/halo2-base/src/utils/halo2.rs new file mode 100644 index 00000000..750787f1 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/utils/halo2.rs @@ -0,0 +1,183 @@ +use std::collections::hash_map::Entry; + +use crate::ff::Field; +use crate::halo2_proofs::{ + circuit::{AssignedCell, Cell, Region, Value}, + halo2curves::bn256::Bn256, + plonk::{Advice, Assigned, Circuit, Column, Fixed}, + poly::kzg::commitment::ParamsKZG, +}; +use crate::virtual_region::copy_constraints::{CopyConstraintManager, EXTERNAL_CELL_TYPE_ID}; +use crate::AssignedValue; + +pub use keygen::ProvingKeyGenerator; + +/// Raw (physical) assigned cell in Plonkish arithmetization. +#[cfg(feature = "halo2-axiom")] +pub type Halo2AssignedCell<'v, F> = AssignedCell<&'v Assigned, F>; +/// Raw (physical) assigned cell in Plonkish arithmetization. +#[cfg(not(feature = "halo2-axiom"))] +pub type Halo2AssignedCell<'v, F> = AssignedCell, F>; + +/// Assign advice to physical region. +#[inline(always)] +pub fn raw_assign_advice<'v, F: Field>( + region: &mut Region, + column: Column, + offset: usize, + value: Value>>, +) -> Halo2AssignedCell<'v, F> { + #[cfg(feature = "halo2-axiom")] + { + region.assign_advice(column, offset, value) + } + #[cfg(feature = "halo2-pse")] + { + let value = value.map(|a| Into::>::into(a)); + region + .assign_advice( + || format!("assign advice {column:?} offset {offset}"), + column, + offset, + || value, + ) + .unwrap() + } +} + +/// Assign fixed to physical region. +#[inline(always)] +pub fn raw_assign_fixed( + region: &mut Region, + column: Column, + offset: usize, + value: F, +) -> Cell { + #[cfg(feature = "halo2-axiom")] + { + region.assign_fixed(column, offset, value) + } + #[cfg(feature = "halo2-pse")] + { + region + .assign_fixed( + || format!("assign fixed {column:?} offset {offset}"), + column, + offset, + || Value::known(value), + ) + .unwrap() + .cell() + } +} + +/// Constrain two physical cells to be equal. +#[inline(always)] +pub fn raw_constrain_equal(region: &mut Region, left: Cell, right: Cell) { + #[cfg(feature = "halo2-axiom")] + region.constrain_equal(left, right); + #[cfg(not(feature = "halo2-axiom"))] + region.constrain_equal(left, right).unwrap(); +} + +/// Constrains that `virtual_cell` is equal to `external_cell`. The `virtual_cell` must have +/// already been raw assigned with the raw assigned cell stored in `copy_manager` +/// **unless** it is marked an external-only cell with type id [EXTERNAL_CELL_TYPE_ID]. +/// * When the virtual cell has already been assigned, the assigned cell is constrained to be equal to the external cell. +/// * When the virtual cell has not been assigned **and** it is marked as an external cell, it is assigned to `external_cell` and the mapping is stored in `copy_manager`. +/// +/// This should only be called when `witness_gen_only` is false, otherwise it will panic. +/// +/// ## Panics +/// If witness generation only mode is true. +pub fn constrain_virtual_equals_external( + region: &mut Region, + virtual_cell: AssignedValue, + external_cell: Cell, + copy_manager: &mut CopyConstraintManager, +) { + let ctx_cell = virtual_cell.cell.unwrap(); + match copy_manager.assigned_advices.entry(ctx_cell) { + Entry::Occupied(acell) => { + // The virtual cell has already been assigned, so we can constrain it to equal the external cell. + region.constrain_equal(*acell.get(), external_cell); + } + Entry::Vacant(assigned) => { + // The virtual cell **must** be an external cell + assert_eq!(ctx_cell.type_id, EXTERNAL_CELL_TYPE_ID); + // We map the virtual cell to point to the raw external cell in `copy_manager` + assigned.insert(external_cell); + } + } +} + +/// This trait should be implemented on the minimal circuit configuration data necessary to +/// completely determine a circuit (independent of circuit inputs). +/// This is used to generate a _dummy_ instantiation of a concrete `Circuit` type for the purposes of key generation. +/// This dummy instantiation just needs to have the correct arithmetization format, but the witnesses do not need to +/// satisfy constraints. +pub trait KeygenCircuitIntent { + /// Concrete circuit type + type ConcreteCircuit: Circuit; + /// Additional data that "pins" down the circuit. These can always to deterministically rederived from `Self`, but + /// storing the `Pinning` saves recomputations in future proof generations. + type Pinning; + + /// The intent must include the log_2 domain size of the circuit. + /// This is used to get the correct trusted setup file. + fn get_k(&self) -> u32; + + /// Builds a _dummy_ instantiation of `Self::ConcreteCircuit` for the purposes of key generation. + /// This dummy instantiation just needs to have the correct arithmetization format, but the witnesses do not need to + /// satisfy constraints. + fn build_keygen_circuit(self) -> Self::ConcreteCircuit; + + /// Pinning is only fully computed after `synthesize` has been run during keygen + fn get_pinning_after_keygen( + self, + kzg_params: &ParamsKZG, + circuit: &Self::ConcreteCircuit, + ) -> Self::Pinning; +} + +mod keygen { + use crate::halo2_proofs::{ + halo2curves::bn256::{Bn256, Fr, G1Affine}, + plonk::{self, ProvingKey}, + poly::{commitment::Params, kzg::commitment::ParamsKZG}, + }; + + use super::KeygenCircuitIntent; + + /// Trait for creating a proving key and a pinning for a circuit from minimal circuit configuration data. + pub trait ProvingKeyGenerator { + /// Create proving key and pinning. + fn create_pk_and_pinning( + self, + kzg_params: &ParamsKZG, + ) -> (ProvingKey, serde_json::Value); + } + + impl ProvingKeyGenerator for CI + where + CI: KeygenCircuitIntent + Clone, + CI::Pinning: serde::Serialize, + { + fn create_pk_and_pinning( + self, + kzg_params: &ParamsKZG, + ) -> (ProvingKey, serde_json::Value) { + assert_eq!(kzg_params.k(), self.get_k()); + let circuit = self.clone().build_keygen_circuit(); + #[cfg(feature = "halo2-axiom")] + let pk = plonk::keygen_pk2(kzg_params, &circuit, false).unwrap(); + #[cfg(not(feature = "halo2-axiom"))] + let pk = { + let vk = plonk::keygen_vk_custom(kzg_params, &circuit, false).unwrap(); + plonk::keygen_pk(kzg_params, vk, &circuit).unwrap() + }; + let pinning = self.get_pinning_after_keygen(kzg_params, &circuit); + (pk, serde_json::to_value(pinning).unwrap()) + } + } +} diff --git a/vendor/halo2-lib/halo2-base/src/utils/mod.rs b/vendor/halo2-lib/halo2-base/src/utils/mod.rs new file mode 100644 index 00000000..a362be45 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/utils/mod.rs @@ -0,0 +1,623 @@ +use core::hash::Hash; + +use crate::ff::{FromUniformBytes, PrimeField}; +#[cfg(not(feature = "halo2-axiom"))] +use crate::halo2_proofs::arithmetic::CurveAffine; +use crate::halo2_proofs::circuit::Value; +#[cfg(feature = "halo2-axiom")] +pub use crate::halo2_proofs::halo2curves::CurveAffineExt; + +use num_bigint::BigInt; +use num_bigint::BigUint; +use num_bigint::Sign; +use num_traits::Signed; +use num_traits::{One, Zero}; + +/// Helper functions for raw halo2 operations to unify slight differences in API for halo2-axiom and halo2-pse +pub mod halo2; +#[cfg(any(test, feature = "test-utils"))] +pub mod testing; + +/// Helper trait to convert to and from a [BigPrimeField] by converting a list of [u64] digits +#[cfg(feature = "halo2-axiom")] +pub trait BigPrimeField: ScalarField { + /// Converts a slice of [u64] to [BigPrimeField] + /// * `val`: the slice of u64 + /// + /// # Assumptions + /// * `val` has the correct length for the implementation + /// * The integer value of `val` is already less than the modulus of `Self` + fn from_u64_digits(val: &[u64]) -> Self; +} +#[cfg(feature = "halo2-axiom")] +impl BigPrimeField for F +where + F: ScalarField + From<[u64; 4]>, // Assume [u64; 4] is little-endian. We only implement ScalarField when this is true. +{ + #[inline(always)] + fn from_u64_digits(val: &[u64]) -> Self { + debug_assert!(val.len() <= 4); + let mut raw = [0u64; 4]; + raw[..val.len()].copy_from_slice(val); + Self::from(raw) + } +} + +/// Helper trait to represent a field element that can be converted into [u64] limbs. +/// +/// Note: Since the number of bits necessary to represent a field element is larger than the number of bits in a u64, we decompose the integer representation of the field element into multiple [u64] values e.g. `limbs`. +pub trait ScalarField: PrimeField + FromUniformBytes<64> + From + Hash + Ord { + /// Returns the base `2bit_len` little endian representation of the [ScalarField] element up to `num_limbs` number of limbs (truncates any extra limbs). + /// + /// Assumes `bit_len < 64`. + /// * `num_limbs`: number of limbs to return + /// * `bit_len`: number of bits in each limb + fn to_u64_limbs(self, num_limbs: usize, bit_len: usize) -> Vec; + + /// Returns the little endian byte representation of the element. + fn to_bytes_le(&self) -> Vec; + + /// Creates a field element from a little endian byte representation. + /// + /// The default implementation assumes that `PrimeField::from_repr` is implemented for little-endian. + /// It should be overriden if this is not the case. + fn from_bytes_le(bytes: &[u8]) -> Self { + let mut repr = Self::Repr::default(); + repr.as_mut()[..bytes.len()].copy_from_slice(bytes); + Self::from_repr(repr).unwrap() + } + + /// Gets the least significant 32 bits of the field element. + fn get_lower_32(&self) -> u32 { + let bytes = self.to_bytes_le(); + let mut lower_32 = 0u32; + for (i, byte) in bytes.into_iter().enumerate().take(4) { + lower_32 |= (byte as u32) << (i * 8); + } + lower_32 + } + + /// Gets the least significant 64 bits of the field element. + fn get_lower_64(&self) -> u64 { + let bytes = self.to_bytes_le(); + let mut lower_64 = 0u64; + for (i, byte) in bytes.into_iter().enumerate().take(8) { + lower_64 |= (byte as u64) << (i * 8); + } + lower_64 + } +} +// See below for implementations + +// Later: will need to separate BigPrimeField from ScalarField when Goldilocks is introduced + +/// [ScalarField] that is ~256 bits long +#[cfg(feature = "halo2-pse")] +pub trait BigPrimeField: PrimeField + ScalarField {} +#[cfg(feature = "halo2-pse")] +impl + ScalarField> BigPrimeField for F {} + +/// Converts an [Iterator] of u64 digits into `number_of_limbs` limbs of `bit_len` bits returned as a [Vec]. +/// +/// Assumes: `bit_len < 64`. +/// * `e`: Iterator of [u64] digits +/// * `number_of_limbs`: number of limbs to return +/// * `bit_len`: number of bits in each limb +#[inline(always)] +pub(crate) fn decompose_u64_digits_to_limbs( + e: impl IntoIterator, + number_of_limbs: usize, + bit_len: usize, +) -> Vec { + debug_assert!(bit_len < 64); + + let mut e = e.into_iter(); + // Mask to extract the bits from each digit + let mask: u64 = (1u64 << bit_len) - 1u64; + let mut u64_digit = e.next().unwrap_or(0); + let mut rem = 64; + + // For each digit, we extract its individual limbs by repeatedly masking and shifting the digit based on how many bits we have left to extract. + (0..number_of_limbs) + .map(|_| match rem.cmp(&bit_len) { + // If `rem` > `bit_len`, we mask the bits from the `u64_digit` to return the first limb. + // We shift the digit to the right by `bit_len` bits and subtract `bit_len` from `rem` + core::cmp::Ordering::Greater => { + let limb = u64_digit & mask; + u64_digit >>= bit_len; + rem -= bit_len; + limb + } + // If `rem` == `bit_len`, then we mask the bits from the `u64_digit` to return the first limb + // We retrieve the next digit and reset `rem` to 64 + core::cmp::Ordering::Equal => { + let limb = u64_digit & mask; + u64_digit = e.next().unwrap_or(0); + rem = 64; + limb + } + // If `rem` < `bit_len`, we retrieve the next digit, mask it, and shift left `rem` bits from the `u64_digit` to return the first limb. + // we shift the digit to the right by `bit_len` - `rem` bits to retrieve the start of the next limb and add 64 - bit_len to `rem` to get the remainder. + core::cmp::Ordering::Less => { + let mut limb = u64_digit; + u64_digit = e.next().unwrap_or(0); + limb |= (u64_digit & ((1u64 << (bit_len - rem)) - 1u64)) << rem; + u64_digit >>= bit_len - rem; + rem += 64 - bit_len; + limb + } + }) + .collect() +} + +/// Returns the number of bits needed to represent the value of `x`. +pub const fn bit_length(x: u64) -> usize { + (u64::BITS - x.leading_zeros()) as usize +} + +/// Returns the ceiling of the base 2 logarithm of `x`. +/// +/// `log2_ceil(0)` returns 0. +pub fn log2_ceil(x: u64) -> usize { + (u64::BITS - x.leading_zeros()) as usize - usize::from(x.is_power_of_two()) +} + +/// Returns the modulus of [BigPrimeField]. +pub fn modulus() -> BigUint { + fe_to_biguint(&-F::ONE) + 1u64 +} + +/// Returns the [BigPrimeField] element of 2n. +/// * `n`: the desired power of 2. +pub fn power_of_two(n: usize) -> F { + biguint_to_fe(&(BigUint::one() << n)) +} + +/// Converts an immutable reference to [BigUint] to a [BigPrimeField]. +/// * `e`: immutable reference to [BigUint] +/// +/// # Assumptions: +/// * `e` is less than the modulus of `F` +pub fn biguint_to_fe(e: &BigUint) -> F { + #[cfg(feature = "halo2-axiom")] + { + F::from_u64_digits(&e.to_u64_digits()) + } + + #[cfg(feature = "halo2-pse")] + { + let bytes = e.to_bytes_le(); + F::from_bytes_le(&bytes) + } +} + +/// Converts an immutable reference to [BigInt] to a [BigPrimeField]. +/// * `e`: immutable reference to [BigInt] +/// +/// # Assumptions: +/// * The absolute value of `e` is less than the modulus of `F` +pub fn bigint_to_fe(e: &BigInt) -> F { + #[cfg(feature = "halo2-axiom")] + { + let (sign, digits) = e.to_u64_digits(); + if sign == Sign::Minus { + -F::from_u64_digits(&digits) + } else { + F::from_u64_digits(&digits) + } + } + #[cfg(feature = "halo2-pse")] + { + let (sign, bytes) = e.to_bytes_le(); + let f_abs = F::from_bytes_le(&bytes); + if sign == Sign::Minus { + -f_abs + } else { + f_abs + } + } +} + +/// Converts an immutable reference to an PrimeField element into a [BigUint] element. +/// * `fe`: immutable reference to PrimeField element to convert +pub fn fe_to_biguint(fe: &F) -> BigUint { + BigUint::from_bytes_le(fe.to_bytes_le().as_ref()) +} + +/// Converts a [BigPrimeField] element into a [BigInt] element by sending `fe` in `[0, F::modulus())` to +/// ```ignore +/// fe, if fe < F::modulus() / 2 +/// fe - F::modulus(), otherwise +/// ``` +pub fn fe_to_bigint(fe: &F) -> BigInt { + // TODO: `F` should just have modulus as lazy_static or something + let modulus = modulus::(); + let e = fe_to_biguint(fe); + if e <= &modulus / 2u32 { + BigInt::from_biguint(Sign::Plus, e) + } else { + BigInt::from_biguint(Sign::Minus, modulus - e) + } +} + +/// Decomposes an immutable reference to a [BigPrimeField] element into `number_of_limbs` limbs of `bit_len` bits each and returns a [Vec] of [BigPrimeField] represented by those limbs. +/// +/// Assumes `bit_len < 128`. +/// * `e`: immutable reference to [BigPrimeField] element to decompose +/// * `number_of_limbs`: number of limbs to decompose `e` into +/// * `bit_len`: number of bits in each limb +pub fn decompose(e: &F, number_of_limbs: usize, bit_len: usize) -> Vec { + if bit_len > 64 { + decompose_biguint(&fe_to_biguint(e), number_of_limbs, bit_len) + } else { + decompose_fe_to_u64_limbs(e, number_of_limbs, bit_len).into_iter().map(F::from).collect() + } +} + +/// Decomposes an immutable reference to a [ScalarField] element into `number_of_limbs` limbs of `bit_len` bits each and returns a [Vec] of [u64] represented by those limbs. +/// +/// Assumes `bit_len` < 64 +/// * `e`: immutable reference to [ScalarField] element to decompose +/// * `number_of_limbs`: number of limbs to decompose `e` into +/// * `bit_len`: number of bits in each limb +pub fn decompose_fe_to_u64_limbs( + e: &F, + number_of_limbs: usize, + bit_len: usize, +) -> Vec { + #[cfg(feature = "halo2-axiom")] + { + e.to_u64_limbs(number_of_limbs, bit_len) + } + + #[cfg(feature = "halo2-pse")] + { + decompose_u64_digits_to_limbs(fe_to_biguint(e).iter_u64_digits(), number_of_limbs, bit_len) + } +} + +/// Decomposes an immutable reference to a [BigUint] into `num_limbs` limbs of `bit_len` bits each and returns a [Vec] of [BigPrimeField] represented by those limbs. +/// +/// Assumes 64 <= `bit_len` < 128. +/// * `e`: immutable reference to [BigInt] to decompose +/// * `num_limbs`: number of limbs to decompose `e` into +/// * `bit_len`: number of bits in each limb +/// +/// Truncates to `num_limbs` limbs if `e` is too large. +pub fn decompose_biguint( + e: &BigUint, + num_limbs: usize, + bit_len: usize, +) -> Vec { + // bit_len must be between 64` and 128 + debug_assert!((64..128).contains(&bit_len)); + let mut e = e.iter_u64_digits(); + + // Grab first 128-bit limb from iterator + let mut limb0 = e.next().unwrap_or(0) as u128; + let mut rem = bit_len - 64; + let mut u64_digit = e.next().unwrap_or(0); + // Extract second limb (bit length 64) from e + limb0 |= ((u64_digit & ((1u64 << rem) - 1u64)) as u128) << 64u32; + u64_digit >>= rem; + rem = 64 - rem; + + // Convert `limb0` into field element `F` and create an iterator by chaining `limb0` with the computing the remaining limbs + core::iter::once(F::from_u128(limb0)) + .chain((1..num_limbs).map(|_| { + let mut limb = u64_digit as u128; + let mut bits = rem; + u64_digit = e.next().unwrap_or(0); + if bit_len >= 64 + bits { + limb |= (u64_digit as u128) << bits; + u64_digit = e.next().unwrap_or(0); + bits += 64; + } + rem = bit_len - bits; + limb |= ((u64_digit & ((1u64 << rem) - 1u64)) as u128) << bits; + u64_digit >>= rem; + rem = 64 - rem; + F::from_u128(limb) + })) + .collect() +} + +/// Decomposes an immutable reference to a [BigInt] into `num_limbs` limbs of `bit_len` bits each and returns a [Vec] of [BigPrimeField] represented by those limbs. +/// +/// Assumes `bit_len < 128`. +/// * `e`: immutable reference to `BigInt` to decompose +/// * `num_limbs`: number of limbs to decompose `e` into +/// * `bit_len`: number of bits in each limb +pub fn decompose_bigint(e: &BigInt, num_limbs: usize, bit_len: usize) -> Vec { + if e.is_negative() { + decompose_biguint::(e.magnitude(), num_limbs, bit_len).into_iter().map(|x| -x).collect() + } else { + decompose_biguint(e.magnitude(), num_limbs, bit_len) + } +} + +/// Decomposes an immutable reference to a [BigInt] into `num_limbs` limbs of `bit_len` bits each and returns a [Vec] of [BigPrimeField] represented by those limbs wrapped in [Value]. +/// +/// Assumes `bit_len` < 128. +/// * `e`: immutable reference to `BigInt` to decompose +/// * `num_limbs`: number of limbs to decompose `e` into +/// * `bit_len`: number of bits in each limb +pub fn decompose_bigint_option( + value: Value<&BigInt>, + number_of_limbs: usize, + bit_len: usize, +) -> Vec> { + value.map(|e| decompose_bigint(e, number_of_limbs, bit_len)).transpose_vec(number_of_limbs) +} + +/// Wraps the internal value of `value` in an [Option]. +/// If the value is [None], then the function returns [None]. +/// * `value`: Value to convert. +pub fn value_to_option(value: Value) -> Option { + let mut v = None; + value.map(|val| { + v = Some(val); + }); + v +} + +/// Computes the value of an integer by passing as `input` a [Vec] of its limb values and the `bit_len` (bit length) used. +/// +/// Returns the sum of all limbs scaled by 2(bit_len * i) where i is the index of the limb. +/// * `input`: Limb values of the integer. +/// * `bit_len`: Length of limb in bits +pub fn compose(input: Vec, bit_len: usize) -> BigUint { + input.iter().rev().fold(BigUint::zero(), |acc, val| (acc << bit_len) + val) +} + +/// Helper trait +#[cfg(feature = "halo2-pse")] +pub trait CurveAffineExt: CurveAffine { + /// Returns the raw affine (X, Y) coordinantes + fn into_coordinates(self) -> (Self::Base, Self::Base) { + let coordinates = self.coordinates().unwrap(); + (*coordinates.x(), *coordinates.y()) + } +} +#[cfg(feature = "halo2-pse")] +impl CurveAffineExt for C {} + +mod scalar_field_impls { + use super::{decompose_u64_digits_to_limbs, ScalarField}; + #[cfg(feature = "halo2-pse")] + use crate::ff::PrimeField; + use crate::halo2_proofs::halo2curves::{ + bn256::{Fq as bn254Fq, Fr as bn254Fr}, + secp256k1::{Fp as secpFp, Fq as secpFq}, + }; + + /// To ensure `ScalarField` is only implemented for `ff:Field` where `Repr` is little endian, we use the following macro + /// to implement the trait for each field. + #[cfg(feature = "halo2-axiom")] + #[macro_export] + macro_rules! impl_scalar_field { + ($field:ident) => { + impl ScalarField for $field { + #[inline(always)] + fn to_u64_limbs(self, num_limbs: usize, bit_len: usize) -> Vec { + // Basically same as `to_repr` but does not go further into bytes + let tmp: [u64; 4] = self.into(); + decompose_u64_digits_to_limbs(tmp, num_limbs, bit_len) + } + + #[inline(always)] + fn to_bytes_le(&self) -> Vec { + let tmp: [u64; 4] = (*self).into(); + tmp.iter().flat_map(|x| x.to_le_bytes()).collect() + } + + #[inline(always)] + fn get_lower_32(&self) -> u32 { + let tmp: [u64; 4] = (*self).into(); + tmp[0] as u32 + } + + #[inline(always)] + fn get_lower_64(&self) -> u64 { + let tmp: [u64; 4] = (*self).into(); + tmp[0] + } + } + }; + } + + /// To ensure `ScalarField` is only implemented for `ff:Field` where `Repr` is little endian, we use the following macro + /// to implement the trait for each field. + #[cfg(feature = "halo2-pse")] + #[macro_export] + macro_rules! impl_scalar_field { + ($field:ident) => { + impl ScalarField for $field { + #[inline(always)] + fn to_u64_limbs(self, num_limbs: usize, bit_len: usize) -> Vec { + let bytes = self.to_repr(); + let digits = (0..4) + .map(|i| u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap())); + decompose_u64_digits_to_limbs(digits, num_limbs, bit_len) + } + + #[inline(always)] + fn to_bytes_le(&self) -> Vec { + self.to_repr().to_vec() + } + } + }; + } + + impl_scalar_field!(bn254Fr); + impl_scalar_field!(bn254Fq); + impl_scalar_field!(secpFp); + impl_scalar_field!(secpFq); +} + +/// Module for reading parameters for Halo2 proving system from the file system. +pub mod fs { + use std::{ + env::var, + fs::{self, File}, + io::{BufReader, BufWriter}, + }; + + use crate::halo2_proofs::{ + halo2curves::{ + bn256::{Bn256, G1Affine}, + CurveAffine, + }, + poly::{ + commitment::{Params, ParamsProver}, + kzg::commitment::ParamsKZG, + }, + }; + use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; + + /// Reads the srs from a file found in `./params/kzg_bn254_{k}.srs` or `{dir}/kzg_bn254_{k}.srs` if `PARAMS_DIR` env var is specified. + /// * `k`: degree that expresses the size of circuit (i.e., 2^k is the number of rows in the circuit) + pub fn read_params(k: u32) -> ParamsKZG { + let dir = var("PARAMS_DIR").unwrap_or_else(|_| "./params".to_string()); + ParamsKZG::::read(&mut BufReader::new( + File::open(format!("{dir}/kzg_bn254_{k}.srs").as_str()) + .expect("Params file does not exist"), + )) + .unwrap() + } + + /// Attempts to read the srs from a file found in `./params/kzg_bn254_{k}.srs` or `{dir}/kzg_bn254_{k}.srs` if `PARAMS_DIR` env var is specified, creates a file it if it does not exist. + /// * `k`: degree that expresses the size of circuit (i.e., 2^k is the number of rows in the circuit) + /// * `setup`: a function that creates the srs + pub fn read_or_create_srs<'a, C: CurveAffine, P: ParamsProver<'a, C>>( + k: u32, + setup: impl Fn(u32) -> P, + ) -> P { + let dir = var("PARAMS_DIR").unwrap_or_else(|_| "./params".to_string()); + let path = format!("{dir}/kzg_bn254_{k}.srs"); + match File::open(path.as_str()) { + Ok(f) => { + #[cfg(feature = "display")] + println!("read params from {path}"); + let mut reader = BufReader::new(f); + P::read(&mut reader).unwrap() + } + Err(_) => { + #[cfg(feature = "display")] + println!("creating params for {k}"); + fs::create_dir_all(dir).unwrap(); + let params = setup(k); + params.write(&mut BufWriter::new(File::create(path).unwrap())).unwrap(); + params + } + } + } + + /// Generates the SRS for the KZG scheme and writes it to a file found in "./params/kzg_bn2_{k}.srs` or `{dir}/kzg_bn254_{k}.srs` if `PARAMS_DIR` env var is specified, creates a file it if it does not exist" + /// * `k`: degree that expresses the size of circuit (i.e., 2^k is the number of rows in the circuit) + pub fn gen_srs(k: u32) -> ParamsKZG { + read_or_create_srs::(k, |k| { + ParamsKZG::::setup(k, ChaCha20Rng::from_seed(Default::default())) + }) + } +} + +#[cfg(test)] +mod tests { + use crate::halo2_proofs::halo2curves::bn256::Fr; + use num_bigint::RandomBits; + use rand::{ + rngs::{OsRng, StdRng}, + Rng, SeedableRng, + }; + use std::ops::Shl; + + use super::*; + + #[test] + fn test_signed_roundtrip() { + use crate::halo2_proofs::halo2curves::bn256::Fr; + assert_eq!(fe_to_bigint(&bigint_to_fe::(&-BigInt::one())), -BigInt::one()); + } + + #[test] + fn test_decompose_biguint() { + let mut rng = OsRng; + const MAX_LIMBS: u64 = 5; + for bit_len in 64..128usize { + for num_limbs in 1..=MAX_LIMBS { + for _ in 0..10_000usize { + let mut e: BigUint = rng.sample(RandomBits::new(num_limbs * bit_len as u64)); + let limbs = decompose_biguint::(&e, num_limbs as usize, bit_len); + + let limbs2 = { + let mut limbs = vec![]; + let mask = BigUint::one().shl(bit_len) - 1usize; + for _ in 0..num_limbs { + let limb = &e & &mask; + let mut bytes_le = limb.to_bytes_le(); + bytes_le.resize(32, 0u8); + limbs.push(Fr::from_bytes(&bytes_le.try_into().unwrap()).unwrap()); + e >>= bit_len; + } + limbs + }; + assert_eq!(limbs, limbs2); + } + } + } + } + + #[test] + fn test_decompose_u64_digits_to_limbs() { + let mut rng = OsRng; + const MAX_LIMBS: u64 = 5; + for bit_len in 0..64usize { + for num_limbs in 1..=MAX_LIMBS { + for _ in 0..10_000usize { + let mut e: BigUint = rng.sample(RandomBits::new(num_limbs * bit_len as u64)); + let limbs = decompose_u64_digits_to_limbs( + e.to_u64_digits(), + num_limbs as usize, + bit_len, + ); + let limbs2 = { + let mut limbs = vec![]; + let mask = BigUint::one().shl(bit_len) - 1usize; + for _ in 0..num_limbs { + let limb = &e & &mask; + limbs.push(u64::try_from(limb).unwrap()); + e >>= bit_len; + } + limbs + }; + assert_eq!(limbs, limbs2); + } + } + } + } + + #[test] + fn test_log2_ceil_zero() { + assert_eq!(log2_ceil(0), 0); + } + + #[test] + fn test_get_lower_32() { + let mut rng = StdRng::seed_from_u64(0); + for _ in 0..10_000usize { + let e: u32 = rng.gen_range(0..u32::MAX); + assert_eq!(Fr::from(e as u64).get_lower_32(), e); + } + assert_eq!(Fr::from((1u64 << 32_i32) + 1).get_lower_32(), 1); + } + + #[test] + fn test_get_lower_64() { + let mut rng = StdRng::seed_from_u64(0); + for _ in 0..10_000usize { + let e: u64 = rng.gen_range(0..u64::MAX); + assert_eq!(Fr::from(e).get_lower_64(), e); + } + } +} diff --git a/vendor/halo2-lib/halo2-base/src/utils/testing.rs b/vendor/halo2-lib/halo2-base/src/utils/testing.rs new file mode 100644 index 00000000..a4608df1 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/utils/testing.rs @@ -0,0 +1,264 @@ +//! Utilities for testing +use crate::{ + gates::{ + circuit::{builder::RangeCircuitBuilder, BaseCircuitParams, CircuitBuilderStage}, + flex_gate::threads::SinglePhaseCoreManager, + GateChip, RangeChip, + }, + halo2_proofs::{ + dev::MockProver, + halo2curves::bn256::{Bn256, Fr, G1Affine}, + plonk::{ + create_proof, keygen_pk, keygen_vk, verify_proof, Circuit, ProvingKey, VerifyingKey, + }, + poly::commitment::ParamsProver, + poly::kzg::{ + commitment::KZGCommitmentScheme, commitment::ParamsKZG, multiopen::ProverSHPLONK, + multiopen::VerifierSHPLONK, strategy::SingleStrategy, + }, + transcript::{ + Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, + }, + }, + Context, +}; +use ark_std::{end_timer, perf_trace::TimerInfo, start_timer}; +use rand::{rngs::StdRng, SeedableRng}; + +use super::fs::gen_srs; + +/// Helper function to generate a proof with real prover using SHPLONK KZG multi-open polynomical commitment scheme +/// and Blake2b as the hash function for Fiat-Shamir. +pub fn gen_proof_with_instances( + params: &ParamsKZG, + pk: &ProvingKey, + circuit: impl Circuit, + instances: &[&[Fr]], +) -> Vec { + let rng = StdRng::seed_from_u64(0); + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof::< + KZGCommitmentScheme, + ProverSHPLONK<'_, Bn256>, + Challenge255<_>, + _, + Blake2bWrite, G1Affine, _>, + _, + >(params, pk, &[circuit], &[instances], rng, &mut transcript) + .expect("prover should not fail"); + transcript.finalize() +} + +/// For testing use only: Helper function to generate a proof **without public instances** with real prover using SHPLONK KZG multi-open polynomical commitment scheme +/// and Blake2b as the hash function for Fiat-Shamir. +pub fn gen_proof( + params: &ParamsKZG, + pk: &ProvingKey, + circuit: impl Circuit, +) -> Vec { + gen_proof_with_instances(params, pk, circuit, &[]) +} + +/// Helper function to verify a proof (generated using [`gen_proof_with_instances`]) using SHPLONK KZG multi-open polynomical commitment scheme +/// and Blake2b as the hash function for Fiat-Shamir. +pub fn check_proof_with_instances( + params: &ParamsKZG, + vk: &VerifyingKey, + proof: &[u8], + instances: &[&[Fr]], + expect_satisfied: bool, +) { + let verifier_params = params.verifier_params(); + let strategy = SingleStrategy::new(params); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(proof); + let res = verify_proof::< + KZGCommitmentScheme, + VerifierSHPLONK<'_, Bn256>, + Challenge255, + Blake2bRead<&[u8], G1Affine, Challenge255>, + SingleStrategy<'_, Bn256>, + >(verifier_params, vk, strategy, &[instances], &mut transcript); + // Just FYI, because strategy is `SingleStrategy`, the output `res` is `Result<(), Error>`, so there is no need to call `res.finalize()`. + + if expect_satisfied { + res.unwrap(); + } else { + assert!(res.is_err()); + } +} + +/// For testing only: Helper function to verify a proof (generated using [`gen_proof`]) without public instances using SHPLONK KZG multi-open polynomical commitment scheme +/// and Blake2b as the hash function for Fiat-Shamir. +pub fn check_proof( + params: &ParamsKZG, + vk: &VerifyingKey, + proof: &[u8], + expect_satisfied: bool, +) { + check_proof_with_instances(params, vk, proof, &[], expect_satisfied); +} + +/// Helper to facilitate easier writing of tests using `RangeChip` and `RangeCircuitBuilder`. +/// By default, the [`MockProver`] is used. +/// +/// Currently this tester uses all private inputs. +pub struct BaseTester { + k: u32, + lookup_bits: Option, + expect_satisfied: bool, + unusable_rows: usize, +} + +impl Default for BaseTester { + fn default() -> Self { + Self { k: 10, lookup_bits: Some(9), expect_satisfied: true, unusable_rows: 9 } + } +} + +/// Creates a [`BaseTester`] +pub fn base_test() -> BaseTester { + BaseTester::default() +} + +impl BaseTester { + /// Changes the number of rows in the circuit to 2k. + /// By default it will also set lookup bits as large as possible, to `k - 1`. + pub fn k(mut self, k: u32) -> Self { + self.k = k; + self.lookup_bits = Some(k as usize - 1); + self + } + + /// Sets the size of the lookup table used for range checks to [0, 2lookup_bits) + pub fn lookup_bits(mut self, lookup_bits: usize) -> Self { + assert!(lookup_bits < self.k as usize, "lookup_bits must be less than k"); + self.lookup_bits = Some(lookup_bits); + self + } + + /// Specify whether you expect this test to pass or fail. Default: pass + pub fn expect_satisfied(mut self, expect_satisfied: bool) -> Self { + self.expect_satisfied = expect_satisfied; + self + } + + /// Set the number of blinding (poisoned) rows + pub fn unusable_rows(mut self, unusable_rows: usize) -> Self { + self.unusable_rows = unusable_rows; + self + } + + /// Run a mock test by providing a closure that uses a `ctx` and `RangeChip`. + /// - `expect_satisfied`: flag for whether you expect the test to pass or fail. Failure means a constraint system failure -- the tester does not catch system panics. + pub fn run(&self, f: impl FnOnce(&mut Context, &RangeChip) -> R) -> R { + self.run_builder(|builder, range| f(builder.main(), range)) + } + + /// Run a mock test by providing a closure that uses a `ctx` and `GateChip`. + /// - `expect_satisfied`: flag for whether you expect the test to pass or fail. Failure means a constraint system failure -- the tester does not catch system panics. + pub fn run_gate(&self, f: impl FnOnce(&mut Context, &GateChip) -> R) -> R { + self.run(|ctx, range| f(ctx, &range.gate)) + } + + /// Run a mock test by providing a closure that uses a `builder` and `RangeChip`. + pub fn run_builder( + &self, + f: impl FnOnce(&mut SinglePhaseCoreManager, &RangeChip) -> R, + ) -> R { + let mut builder = RangeCircuitBuilder::default().use_k(self.k as usize); + if let Some(lb) = self.lookup_bits { + builder.set_lookup_bits(lb) + } + let range = RangeChip::new(self.lookup_bits.unwrap_or(0), builder.lookup_manager().clone()); + // run the function, mutating `builder` + let res = f(builder.pool(0), &range); + + // helper check: if your function didn't use lookups, turn lookup table "off" + let t_cells_lookup = + builder.lookup_manager().iter().map(|lm| lm.total_rows()).sum::(); + let lookup_bits = if t_cells_lookup == 0 { None } else { self.lookup_bits }; + builder.config_params.lookup_bits = lookup_bits; + + // configure the circuit shape, 9 blinding rows seems enough + builder.calculate_params(Some(self.unusable_rows)); + if self.expect_satisfied { + MockProver::run(self.k, &builder, vec![]).unwrap().assert_satisfied(); + } else { + assert!(MockProver::run(self.k, &builder, vec![]).unwrap().verify().is_err()); + } + res + } + + /// Runs keygen, real prover, and verifier by providing a closure that uses a `builder` and `RangeChip`. + /// + /// Must provide `init_input` for use during key generation, which is preferably not equal to `logic_input`. + /// These are the inputs to the closure, not necessary public inputs to the circuit. + /// + /// Currently for testing, no public instances. + pub fn bench_builder( + &self, + init_input: I, + logic_input: I, + f: impl Fn(&mut SinglePhaseCoreManager, &RangeChip, I), + ) -> BenchStats { + let mut builder = + RangeCircuitBuilder::from_stage(CircuitBuilderStage::Keygen).use_k(self.k as usize); + if let Some(lb) = self.lookup_bits { + builder.set_lookup_bits(lb) + } + let range = RangeChip::new(self.lookup_bits.unwrap_or(0), builder.lookup_manager().clone()); + // run the function, mutating `builder` + f(builder.pool(0), &range, init_input); + + // helper check: if your function didn't use lookups, turn lookup table "off" + let t_cells_lookup = + builder.lookup_manager().iter().map(|lm| lm.total_rows()).sum::(); + let lookup_bits = if t_cells_lookup == 0 { None } else { self.lookup_bits }; + builder.config_params.lookup_bits = lookup_bits; + + // configure the circuit shape, 9 blinding rows seems enough + let config_params = builder.calculate_params(Some(self.unusable_rows)); + + let params = gen_srs(self.k); + let vk_time = start_timer!(|| "Generating vkey"); + let vk = keygen_vk(¶ms, &builder).unwrap(); + end_timer!(vk_time); + let pk_time = start_timer!(|| "Generating pkey"); + let pk = keygen_pk(¶ms, vk, &builder).unwrap(); + end_timer!(pk_time); + + let break_points = builder.break_points(); + drop(builder); + // create real proof + let proof_time = start_timer!(|| "Proving time"); + let mut builder = RangeCircuitBuilder::prover(config_params.clone(), break_points); + let range = RangeChip::new(self.lookup_bits.unwrap_or(0), builder.lookup_manager().clone()); + f(builder.pool(0), &range, logic_input); + let proof = gen_proof(¶ms, &pk, builder); + end_timer!(proof_time); + + let proof_size = proof.len(); + + let verify_time = start_timer!(|| "Verify time"); + check_proof(¶ms, pk.get_vk(), &proof, self.expect_satisfied); + end_timer!(verify_time); + + BenchStats { config_params, vk_time, pk_time, proof_time, proof_size, verify_time } + } +} + +/// Bench stats +pub struct BenchStats { + /// Config params + pub config_params: BaseCircuitParams, + /// Vkey gen time + pub vk_time: TimerInfo, + /// Pkey gen time + pub pk_time: TimerInfo, + /// Proving time + pub proof_time: TimerInfo, + /// Proof size in bytes + pub proof_size: usize, + /// Verify time + pub verify_time: TimerInfo, +} diff --git a/vendor/halo2-lib/halo2-base/src/virtual_region/copy_constraints.rs b/vendor/halo2-lib/halo2-base/src/virtual_region/copy_constraints.rs new file mode 100644 index 00000000..3c873ed1 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/virtual_region/copy_constraints.rs @@ -0,0 +1,181 @@ +use std::collections::{BTreeMap, HashMap}; +use std::ops::DerefMut; +use std::sync::{Arc, Mutex, OnceLock}; + +use itertools::Itertools; +use rayon::slice::ParallelSliceMut; + +use crate::halo2_proofs::{ + circuit::{Cell, Region}, + plonk::{Assigned, Column, Fixed}, +}; +use crate::utils::halo2::{raw_assign_fixed, raw_constrain_equal, Halo2AssignedCell}; +use crate::AssignedValue; +use crate::{ff::Field, ContextCell}; + +use super::manager::VirtualRegionManager; + +/// Type ID to distinguish external raw Halo2 cells. **This Type ID must be unique.** +pub const EXTERNAL_CELL_TYPE_ID: &str = "halo2-base:External Raw Halo2 Cell"; + +/// Thread-safe shared global manager for all copy constraints. +pub type SharedCopyConstraintManager = Arc>>; + +/// Global manager for all copy constraints. Thread-safe. +/// +/// This will only be accessed during key generation, not proof generation, so it does not need to be optimized. +/// +/// Implements [VirtualRegionManager], which should be assigned only after all cells have been assigned +/// by other managers. +#[derive(Clone, Default, Debug)] +pub struct CopyConstraintManager { + /// A [Vec] tracking equality constraints between pairs of virtual advice cells, tagged by [ContextCell]. + /// These can be across different virtual regions. + pub advice_equalities: Vec<(ContextCell, ContextCell)>, + + /// A [Vec] tracking equality constraints between virtual advice cell and fixed values. + /// Fixed values will only be added once globally. + pub constant_equalities: Vec<(F, ContextCell)>, + + external_cell_count: usize, + + // In circuit assignments + /// Advice assignments, mapping from virtual [ContextCell] to assigned physical [Cell] + pub assigned_advices: HashMap, + /// Constant assignments, (key = constant, value = [Cell]) + pub assigned_constants: BTreeMap, + /// Flag for whether `assign_raw` has been called, for safety only. + assigned: OnceLock<()>, +} + +impl CopyConstraintManager { + /// Returns the number of distinct constants used. + pub fn num_distinct_constants(&self) -> usize { + self.constant_equalities.iter().map(|(x, _)| x).sorted().dedup().count() + } + + /// Adds external raw [Halo2AssignedCell] to `self.assigned_advices` and returns a new virtual [AssignedValue] + /// that can be used in any virtual region. No copy constraint is imposed, as the virtual cell "points" to the + /// raw assigned cell. The returned [ContextCell] will have `type_id` the `TypeId::of::()`. + pub fn load_external_assigned( + &mut self, + assigned_cell: Halo2AssignedCell, + ) -> AssignedValue { + let context_cell = self.load_external_cell(assigned_cell.cell()); + let mut value = Assigned::Trivial(F::ZERO); + assigned_cell.value().map(|v| { + #[cfg(feature = "halo2-axiom")] + { + value = **v; + } + #[cfg(not(feature = "halo2-axiom"))] + { + value = *v; + } + }); + AssignedValue { value, cell: Some(context_cell) } + } + + /// Adds external raw Halo2 cell to `self.assigned_advices` and returns a new virtual cell that can be + /// used as a tag (but will not be re-assigned). The returned [ContextCell] will have `type_id` the `TypeId::of::()`. + pub fn load_external_cell(&mut self, cell: Cell) -> ContextCell { + self.load_external_cell_impl(Some(cell)) + } + + /// Mock to load an external cell for base circuit simulation. If any mock external cell is loaded, calling `assign_raw` will panic. + pub fn mock_external_assigned(&mut self, v: F) -> AssignedValue { + let context_cell = self.load_external_cell_impl(None); + AssignedValue { value: Assigned::Trivial(v), cell: Some(context_cell) } + } + + fn load_external_cell_impl(&mut self, cell: Option) -> ContextCell { + let context_cell = ContextCell::new(EXTERNAL_CELL_TYPE_ID, 0, self.external_cell_count); + self.external_cell_count += 1; + if let Some(cell) = cell { + if let Some(old_cell) = self.assigned_advices.insert(context_cell, cell) { + assert!( + old_cell.row_offset == cell.row_offset && old_cell.column == cell.column, + "External cell already assigned" + ) + } + } + context_cell + } + + /// Clears state + pub fn clear(&mut self) { + self.advice_equalities.clear(); + self.constant_equalities.clear(); + self.assigned_advices.clear(); + self.assigned_constants.clear(); + self.external_cell_count = 0; + self.assigned.take(); + } +} + +impl Drop for CopyConstraintManager { + fn drop(&mut self) { + if self.assigned.get().is_some() { + return; + } + if !self.advice_equalities.is_empty() { + log::warn!("WARNING: advice_equalities not empty"); + } + if !self.constant_equalities.is_empty() { + log::warn!("WARNING: constant_equalities not empty"); + } + } +} + +impl VirtualRegionManager for SharedCopyConstraintManager { + // The fixed columns + type Config = Vec>; + type Assignment = (); + + /// This should be the last manager to be assigned, after all other managers have assigned cells. + fn assign_raw(&self, config: &Self::Config, region: &mut Region) -> Self::Assignment { + let mut guard = self.lock().unwrap(); + let manager = guard.deref_mut(); + // sort by constant so constant assignment order is deterministic + // this is necessary because constants can be assigned by multiple CPU threads + // We further sort by ContextCell because the backend implementation of `raw_constrain_equal` (permutation argument) seems to depend on the order you specify copy constraints... + manager + .constant_equalities + .par_sort_unstable_by(|(c1, cell1), (c2, cell2)| c1.cmp(c2).then(cell1.cmp(cell2))); + // Assign fixed cells, we go left to right, then top to bottom, to avoid needing to know number of rows here + let mut fixed_col = 0; + let mut fixed_offset = 0; + for (c, _) in manager.constant_equalities.iter() { + if !manager.assigned_constants.contains_key(c) { + // this will panic if you run out of rows + let cell = raw_assign_fixed(region, config[fixed_col], fixed_offset, *c); + manager.assigned_constants.insert(*c, cell); + fixed_col += 1; + if fixed_col >= config.len() { + fixed_col = 0; + fixed_offset += 1; + } + } + } + + // Just in case: we sort by ContextCell because the backend implementation of `raw_constrain_equal` (permutation argument) seems to depend on the order you specify copy constraints... + manager.advice_equalities.par_sort_unstable(); + // Impose equality constraints between assigned advice cells + // At this point we assume all cells have been assigned by other VirtualRegionManagers + for (left, right) in &manager.advice_equalities { + let left = manager.assigned_advices.get(left).expect("virtual cell not assigned"); + let right = manager.assigned_advices.get(right).expect("virtual cell not assigned"); + raw_constrain_equal(region, *left, *right); + } + for (left, right) in &manager.constant_equalities { + let left = manager.assigned_constants[left]; + let right = manager.assigned_advices.get(right).expect("virtual cell not assigned"); + raw_constrain_equal(region, left, *right); + } + // We can't clear advice_equalities and constant_equalities because keygen_vk and keygen_pk will call this function twice + let _ = manager.assigned.set(()); + // When keygen_vk and keygen_pk are both run, you need to clear assigned constants + // so the second run still assigns constants in the pk + manager.assigned_constants.clear(); + } +} diff --git a/vendor/halo2-lib/halo2-base/src/virtual_region/lookups.rs b/vendor/halo2-lib/halo2-base/src/virtual_region/lookups.rs new file mode 100644 index 00000000..895dfb3a --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/virtual_region/lookups.rs @@ -0,0 +1,156 @@ +use std::collections::BTreeMap; +use std::sync::{Arc, Mutex, OnceLock}; + +use getset::{CopyGetters, Getters, Setters}; + +use crate::ff::Field; +use crate::halo2_proofs::{ + circuit::{Region, Value}, + plonk::{Advice, Column}, +}; +use crate::utils::halo2::{constrain_virtual_equals_external, raw_assign_advice}; +use crate::{AssignedValue, ContextTag}; + +use super::copy_constraints::SharedCopyConstraintManager; +use super::manager::VirtualRegionManager; + +/// Basic dynamic lookup table gadget. +pub mod basic; + +/// A manager that can be used for any lookup argument. This manager automates +/// the process of copying cells to designed advice columns with lookup enabled. +/// It also manages how many such advice columns are necessary. +/// +/// ## Detailed explanation +/// If we have a lookup argument that uses `ADVICE_COLS` advice columns and `TABLE_COLS` table columns, where +/// the table is either fixed or dynamic (advice), then we want to dynamically allocate chunks of `ADVICE_COLS` columns +/// that have the lookup into the table **always on** so that: +/// - every time we want to lookup [_; ADVICE_COLS] values, we copy them over to a row in the special +/// +/// lookup-enabled advice columns. +/// - note that just for assignment, we don't need to know anything about the table itself. +/// +/// Note: the manager does not need to know the value of `TABLE_COLS`. +/// +/// We want this manager to be CPU thread safe, while ensuring that the resulting circuit is +/// deterministic -- the order in which the cells to lookup are added matters. +/// The current solution is to tag the cells to lookup with the context id from the [`Context`](crate::Context) in which +/// it was called, and add virtual cells sequentially to buckets labelled by id. +/// The virtual cells will be assigned to physical cells sequentially by id. +/// We use a `BTreeMap` for the buckets instead of sorting to cells, to ensure that the order of the cells +/// within a bucket is deterministic. +/// The assumption is that the [`Context`](crate::Context) is thread-local. +/// +/// Cheap to clone across threads because everything is in [Arc]. +#[derive(Clone, Debug, Getters, CopyGetters, Setters)] +pub struct LookupAnyManager { + /// Shared cells to lookup, tagged by (type id, context id). + #[allow(clippy::type_complexity)] + pub cells_to_lookup: Arc; ADVICE_COLS]>>>>, + /// Global shared copy manager + #[getset(get = "pub", set = "pub")] + copy_manager: SharedCopyConstraintManager, + /// Specify whether constraints should be imposed for additional safety. + #[getset(get_copy = "pub")] + witness_gen_only: bool, + /// Flag for whether `assign_raw` has been called, for safety only. + pub(crate) assigned: Arc>, +} + +impl LookupAnyManager { + /// Creates a new [LookupAnyManager] with a given copy manager. + pub fn new(witness_gen_only: bool, copy_manager: SharedCopyConstraintManager) -> Self { + Self { + witness_gen_only, + cells_to_lookup: Default::default(), + copy_manager, + assigned: Default::default(), + } + } + + /// Add a lookup argument to the manager. + pub fn add_lookup(&self, tag: ContextTag, cells: [AssignedValue; ADVICE_COLS]) { + self.cells_to_lookup + .lock() + .unwrap() + .entry(tag) + .and_modify(|thread| thread.push(cells)) + .or_insert(vec![cells]); + } + + /// The total number of virtual rows needed to special lookups + pub fn total_rows(&self) -> usize { + self.cells_to_lookup.lock().unwrap().iter().flat_map(|(_, advices)| advices).count() + } + + /// The optimal number of `ADVICE_COLS` chunks of advice columns with lookup enabled for this + /// particular lookup argument that we should allocate. + pub fn num_advice_chunks(&self, usable_rows: usize) -> usize { + let total = self.total_rows(); + total.div_ceil(usable_rows) + } + + /// Clears state + pub fn clear(&mut self) { + self.cells_to_lookup.lock().unwrap().clear(); + self.copy_manager.lock().unwrap().clear(); + self.assigned = Arc::new(OnceLock::new()); + } + + /// Deep clone with the specified copy manager. Unsets `assigned`. + pub fn deep_clone(&self, copy_manager: SharedCopyConstraintManager) -> Self { + Self { + witness_gen_only: self.witness_gen_only, + cells_to_lookup: Arc::new(Mutex::new(self.cells_to_lookup.lock().unwrap().clone())), + copy_manager, + assigned: Default::default(), + } + } +} + +impl Drop for LookupAnyManager { + /// Sanity checks whether the manager has assigned cells to lookup, + /// to prevent user error. + fn drop(&mut self) { + if Arc::strong_count(&self.cells_to_lookup) > 1 { + return; + } + if self.total_rows() > 0 && self.assigned.get().is_none() { + log::warn!("WARNING: LookupAnyManager was not assigned!"); + } + } +} + +impl VirtualRegionManager + for LookupAnyManager +{ + type Config = Vec<[Column; ADVICE_COLS]>; + type Assignment = (); + + fn assign_raw(&self, config: &Self::Config, region: &mut Region) { + let mut copy_manager = + (!self.witness_gen_only).then(|| self.copy_manager().lock().unwrap()); + let cells_to_lookup = self.cells_to_lookup.lock().unwrap(); + // Copy the cells to the config columns, going left to right, then top to bottom. + // Will panic if out of rows + let mut lookup_offset = 0; + let mut lookup_col = 0; + for advices in cells_to_lookup.iter().flat_map(|(_, advices)| advices) { + if lookup_col >= config.len() { + lookup_col = 0; + lookup_offset += 1; + } + for (advice, &column) in advices.iter().zip(config[lookup_col].iter()) { + let bcell = + raw_assign_advice(region, column, lookup_offset, Value::known(advice.value)); + if let Some(copy_manager) = copy_manager.as_mut() { + constrain_virtual_equals_external(region, *advice, bcell.cell(), copy_manager); + } + } + + lookup_col += 1; + } + // We cannot clear `cells_to_lookup` because keygen_vk and keygen_pk both call this function + let _ = self.assigned.set(()); + } +} diff --git a/vendor/halo2-lib/halo2-base/src/virtual_region/lookups/basic.rs b/vendor/halo2-lib/halo2-base/src/virtual_region/lookups/basic.rs new file mode 100644 index 00000000..f5299c38 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/virtual_region/lookups/basic.rs @@ -0,0 +1,209 @@ +use std::iter::zip; + +use crate::{ + halo2_proofs::{ + circuit::{Layouter, Region, Value}, + halo2curves::ff::Field, + plonk::{Advice, Column, ConstraintSystem, Fixed, Phase}, + poly::Rotation, + }, + utils::{ + halo2::{constrain_virtual_equals_external, raw_assign_advice, raw_assign_fixed}, + ScalarField, + }, + virtual_region::copy_constraints::SharedCopyConstraintManager, + AssignedValue, +}; + +/// A simple dynamic lookup table for when you want to verify some length `KEY_COL` key +/// is in a provided (dynamic) table of the same format. +/// +/// Note that you can also use this to look up (key, out) pairs, where you consider the whole +/// pair as the new key. +/// +/// We can have multiple sets of dedicated columns to be looked up: these can be specified +/// when calling `new`, but typically we just need 1 set. +/// +/// The `table` consists of advice columns. Since this table may have poisoned rows (blinding factors), +/// we use a fixed column `table_selector` which is default 0 and only 1 on enabled rows of the table. +/// The dynamic lookup will check that for `(key, key_is_enabled)` in `to_lookup` we have `key` matches one of +/// the rows in `table` where `table_selector == key_is_enabled`. +/// Reminder: the Halo2 lookup argument will ignore the poisoned rows in `to_lookup` +/// (see [https://zcash.github.io/halo2/design/proving-system/lookup.html#zero-knowledge-adjustment]), but it will +/// not ignore the poisoned rows in `table`. +/// +/// Part of this design consideration is to allow a key of `[F::ZERO; KEY_COL]` to still be used as a valid key +/// in the lookup argument. By default, unfilled rows in `to_lookup` will be all zeros; we require +/// at least one row in `table` where `table_is_enabled = 0` and the rest of the row in `table` are also 0s. +#[derive(Clone, Debug)] +pub struct BasicDynLookupConfig { + /// Columns for cells to be looked up. Consists of `(key, key_is_enabled)`. + pub to_lookup: Vec<([Column; KEY_COL], Column)>, + /// Table to look up against. + pub table: [Column; KEY_COL], + /// Selector to enable a row in `table` to actually be part of the lookup table. This is to prevent + /// blinding factors in `table` advice columns from being used in the lookup. + pub table_is_enabled: Column, +} + +impl BasicDynLookupConfig { + /// Assumes all columns are in the same phase `P` to make life easier. + /// We enable equality on all columns because we envision both the columns to lookup + /// and the table will need to talk to halo2-lib. + pub fn new( + meta: &mut ConstraintSystem, + phase: impl Fn() -> P, + num_lu_sets: usize, + ) -> Self { + let mut make_columns = || { + let advices = [(); KEY_COL].map(|_| { + let advice = meta.advice_column_in(phase()); + meta.enable_equality(advice); + advice + }); + let is_enabled = meta.fixed_column(); + (advices, is_enabled) + }; + let (table, table_is_enabled) = make_columns(); + let to_lookup: Vec<_> = (0..num_lu_sets).map(|_| make_columns()).collect(); + + for (key, key_is_enabled) in &to_lookup { + meta.lookup_any("dynamic lookup table", |meta| { + let table = table.map(|c| meta.query_advice(c, Rotation::cur())); + let table_is_enabled = meta.query_fixed(table_is_enabled, Rotation::cur()); + let key = key.map(|c| meta.query_advice(c, Rotation::cur())); + let key_is_enabled = meta.query_fixed(*key_is_enabled, Rotation::cur()); + zip(key, table).chain([(key_is_enabled, table_is_enabled)]).collect() + }); + } + + Self { table_is_enabled, table, to_lookup } + } + + /// Assign managed lookups. The `keys` must have already been raw assigned beforehand. + /// + /// `copy_manager` **must** be provided unless you are only doing witness generation + /// without constraints. + pub fn assign_virtual_to_lookup_to_raw( + &self, + mut layouter: impl Layouter, + keys: impl IntoIterator; KEY_COL]>, + copy_manager: Option<&SharedCopyConstraintManager>, + ) { + #[cfg(not(feature = "halo2-axiom"))] + let keys = keys.into_iter().collect::>(); + layouter + .assign_region( + || "[BasicDynLookupConfig] Advice cells to lookup", + |mut region| { + self.assign_virtual_to_lookup_to_raw_from_offset( + &mut region, + #[cfg(feature = "halo2-axiom")] + keys, + #[cfg(not(feature = "halo2-axiom"))] + keys.clone(), + 0, + copy_manager, + ); + Ok(()) + }, + ) + .unwrap(); + } + + /// Assign managed lookups. The `keys` must have already been raw assigned beforehand. + /// + /// `copy_manager` **must** be provided unless you are only doing witness generation + /// without constraints. + pub fn assign_virtual_to_lookup_to_raw_from_offset( + &self, + region: &mut Region, + keys: impl IntoIterator; KEY_COL]>, + mut offset: usize, + copy_manager: Option<&SharedCopyConstraintManager>, + ) { + let mut copy_manager = copy_manager.map(|c| c.lock().unwrap()); + // Copied from `LookupAnyManager::assign_raw` but modified to set `key_is_enabled` to 1. + // Copy the cells to the config columns, going left to right, then top to bottom. + // Will panic if out of rows + let mut lookup_col = 0; + for key in keys { + if lookup_col >= self.to_lookup.len() { + lookup_col = 0; + offset += 1; + } + let (key_col, key_is_enabled_col) = self.to_lookup[lookup_col]; + // set key_is_enabled to 1 + raw_assign_fixed(region, key_is_enabled_col, offset, F::ONE); + for (advice, column) in zip(key, key_col) { + let bcell = raw_assign_advice(region, column, offset, Value::known(advice.value)); + if let Some(copy_manager) = copy_manager.as_mut() { + constrain_virtual_equals_external(region, advice, bcell.cell(), copy_manager); + } + } + + lookup_col += 1; + } + } + + /// Assign virtual table to raw. The `rows` must have already been raw assigned beforehand. + /// + /// `copy_manager` **must** be provided unless you are only doing witness generation + /// without constraints. + pub fn assign_virtual_table_to_raw( + &self, + mut layouter: impl Layouter, + rows: impl IntoIterator; KEY_COL]>, + copy_manager: Option<&SharedCopyConstraintManager>, + ) { + #[cfg(not(feature = "halo2-axiom"))] + let rows = rows.into_iter().collect::>(); + layouter + .assign_region( + || "[BasicDynLookupConfig] Dynamic Lookup Table", + |mut region| { + self.assign_virtual_table_to_raw_from_offset( + &mut region, + #[cfg(feature = "halo2-axiom")] + rows, + #[cfg(not(feature = "halo2-axiom"))] + rows.clone(), + 0, + copy_manager, + ); + Ok(()) + }, + ) + .unwrap(); + } + + /// Assign virtual table to raw. The `rows` must have already been raw assigned beforehand. + /// + /// `copy_manager` **must** be provided unless you are only doing witness generation + /// without constraints. + pub fn assign_virtual_table_to_raw_from_offset( + &self, + region: &mut Region, + rows: impl IntoIterator; KEY_COL]>, + mut offset: usize, + copy_manager: Option<&SharedCopyConstraintManager>, + ) { + let mut copy_manager = copy_manager.map(|c| c.lock().unwrap()); + for row in rows { + // Enable this row in the table + raw_assign_fixed(region, self.table_is_enabled, offset, F::ONE); + for (advice, column) in zip(row, self.table) { + let bcell = raw_assign_advice(region, column, offset, Value::known(advice.value)); + if let Some(copy_manager) = copy_manager.as_mut() { + constrain_virtual_equals_external(region, advice, bcell.cell(), copy_manager); + } + } + offset += 1; + } + // always assign one disabled row with all 0s, so disabled to_lookup works for sure + raw_assign_fixed(region, self.table_is_enabled, offset, F::ZERO); + for col in self.table { + raw_assign_advice(region, col, offset, Value::known(F::ZERO)); + } + } +} diff --git a/vendor/halo2-lib/halo2-base/src/virtual_region/manager.rs b/vendor/halo2-lib/halo2-base/src/virtual_region/manager.rs new file mode 100644 index 00000000..a790f759 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/virtual_region/manager.rs @@ -0,0 +1,16 @@ +use crate::ff::Field; +use crate::halo2_proofs::circuit::Region; + +/// A virtual region manager is responsible for managing a virtual region and assigning the +/// virtual region to a physical Halo2 region. +/// +pub trait VirtualRegionManager { + /// The Halo2 config with associated columns and gates describing the physical Halo2 region + /// that this virtual region manager is responsible for. + type Config: Clone; + /// Return type of the `assign_raw` method. Default is `()`. + type Assignment; + + /// Assign virtual region this is in charge of to the raw region described by `config`. + fn assign_raw(&self, config: &Self::Config, region: &mut Region) -> Self::Assignment; +} diff --git a/vendor/halo2-lib/halo2-base/src/virtual_region/mod.rs b/vendor/halo2-lib/halo2-base/src/virtual_region/mod.rs new file mode 100644 index 00000000..47d4bbf4 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/virtual_region/mod.rs @@ -0,0 +1,15 @@ +//! Trait describing the shared properties for a struct that is in charge of managing a virtual region of a circuit +//! _and_ assigning that virtual region to a "raw" Halo2 region in the "physical" circuit. +//! +//! Currently a raw region refers to a subset of columns of the circuit, and spans all rows (so it is a vertical region), +//! but this is not a requirement of the trait. + +/// Shared copy constraints across different virtual regions +pub mod copy_constraints; +/// Virtual region manager for lookup tables +pub mod lookups; +/// Virtual region manager +pub mod manager; + +#[cfg(test)] +mod tests; diff --git a/vendor/halo2-lib/halo2-base/src/virtual_region/tests/lookups/memory.rs b/vendor/halo2-lib/halo2-base/src/virtual_region/tests/lookups/memory.rs new file mode 100644 index 00000000..16c3481b --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/virtual_region/tests/lookups/memory.rs @@ -0,0 +1,254 @@ +use crate::{ + halo2_proofs::{ + arithmetic::Field, + circuit::{Layouter, SimpleFloorPlanner}, + dev::MockProver, + halo2curves::bn256::Fr, + plonk::{keygen_pk, keygen_vk, Assigned, Circuit, ConstraintSystem, Error}, + }, + virtual_region::{ + copy_constraints::EXTERNAL_CELL_TYPE_ID, lookups::basic::BasicDynLookupConfig, + }, + AssignedValue, ContextCell, +}; +use halo2_proofs_axiom::plonk::FirstPhase; +use rand::{rngs::StdRng, Rng, SeedableRng}; +use test_log::test; + +use crate::{ + gates::{ + flex_gate::{threads::SinglePhaseCoreManager, FlexGateConfig, FlexGateConfigParams}, + GateChip, GateInstructions, + }, + utils::{ + fs::gen_srs, + testing::{check_proof, gen_proof}, + ScalarField, + }, + virtual_region::manager::VirtualRegionManager, +}; + +#[derive(Clone, Debug)] +struct RAMConfig { + cpu: FlexGateConfig, + memory: BasicDynLookupConfig<2>, +} + +#[derive(Clone, Default)] +struct RAMConfigParams { + cpu: FlexGateConfigParams, + num_lu_sets: usize, +} + +struct RAMCircuit { + // private memory input + memory: Vec, + // memory accesses + ptrs: [usize; CYCLES], + + cpu: SinglePhaseCoreManager, + mem_access: Vec<[AssignedValue; 2]>, + + params: RAMConfigParams, +} + +impl RAMCircuit { + fn new( + memory: Vec, + ptrs: [usize; CYCLES], + params: RAMConfigParams, + witness_gen_only: bool, + ) -> Self { + let cpu = SinglePhaseCoreManager::new(witness_gen_only, Default::default()); + let mem_access = vec![]; + Self { memory, ptrs, cpu, mem_access, params } + } + + fn compute(&mut self) { + let gate = GateChip::default(); + let ctx = self.cpu.main(); + let mut sum = ctx.load_constant(F::ZERO); + for &ptr in &self.ptrs { + let value = self.memory[ptr]; + let ptr = ctx.load_witness(F::from(ptr as u64)); + let value = ctx.load_witness(value); + self.mem_access.push([ptr, value]); + sum = gate.add(ctx, sum, value); + } + } +} + +impl Circuit for RAMCircuit { + type Config = RAMConfig; + type FloorPlanner = SimpleFloorPlanner; + type Params = RAMConfigParams; + + fn params(&self) -> Self::Params { + self.params.clone() + } + + fn without_witnesses(&self) -> Self { + unimplemented!() + } + + fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config { + let memory = BasicDynLookupConfig::new(meta, || FirstPhase, params.num_lu_sets); + let cpu = FlexGateConfig::configure(meta, params.cpu); + + log::debug!("Poisoned rows: {}", meta.minimum_rows()); + + RAMConfig { cpu, memory } + } + + fn configure(_: &mut ConstraintSystem) -> Self::Config { + unreachable!() + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "cpu", + |mut region| { + self.cpu.assign_raw( + &(config.cpu.basic_gates[0].clone(), config.cpu.max_rows), + &mut region, + ); + Ok(()) + }, + )?; + + let copy_manager = (!self.cpu.witness_gen_only()).then_some(&self.cpu.copy_manager); + + // Make purely virtual cells so we can raw assign them + let memory = self.memory.iter().enumerate().map(|(i, value)| { + let idx = Assigned::Trivial(F::from(i as u64)); + let idx = AssignedValue { + value: idx, + cell: Some(ContextCell::new(EXTERNAL_CELL_TYPE_ID, 0, i)), + }; + let value = Assigned::Trivial(*value); + let value = + AssignedValue { value, cell: Some(ContextCell::new(EXTERNAL_CELL_TYPE_ID, 1, i)) }; + [idx, value] + }); + + config.memory.assign_virtual_table_to_raw( + layouter.namespace(|| "memory"), + memory, + copy_manager, + ); + + config.memory.assign_virtual_to_lookup_to_raw( + layouter.namespace(|| "memory accesses"), + self.mem_access.clone(), + copy_manager, + ); + // copy constraints at the very end for safety: + layouter.assign_region( + || "copy constraints", + |mut region| { + self.cpu.copy_manager.assign_raw(&config.cpu.constants, &mut region); + Ok(()) + }, + ) + } +} + +#[test] +fn test_ram_mock() { + let k = 5u32; + const CYCLES: usize = 50; + let mut rng = StdRng::seed_from_u64(0); + let mem_len = 16usize; + let memory: Vec<_> = (0..mem_len).map(|_| Fr::random(&mut rng)).collect(); + let ptrs = [(); CYCLES].map(|_| rng.gen_range(0..memory.len())); + let usable_rows = 2usize.pow(k) - 11; // guess + let params = RAMConfigParams::default(); + let mut circuit = RAMCircuit::new(memory, ptrs, params, false); + circuit.compute(); + // auto-configuration stuff + let num_advice = circuit.cpu.total_advice() / usable_rows + 1; + circuit.params.cpu = FlexGateConfigParams { + k: k as usize, + num_advice_per_phase: vec![num_advice], + num_fixed: 1, + }; + circuit.params.num_lu_sets = CYCLES / usable_rows + 1; + MockProver::run(k, &circuit, vec![]).unwrap().assert_satisfied(); +} + +#[test] +#[should_panic = "called `Result::unwrap()` on an `Err` value: [Lookup dynamic lookup table(index: 2) is not satisfied in Region 2 ('[BasicDynLookupConfig] Advice cells to lookup') at offset 16]"] +fn test_ram_mock_failed_access() { + let k = 5u32; + const CYCLES: usize = 50; + let mut rng = StdRng::seed_from_u64(0); + let mem_len = 16usize; + let memory: Vec<_> = (0..mem_len).map(|_| Fr::random(&mut rng)).collect(); + let ptrs = [(); CYCLES].map(|_| rng.gen_range(0..memory.len())); + let usable_rows = 2usize.pow(k) - 11; // guess + let params = RAMConfigParams::default(); + let mut circuit = RAMCircuit::new(memory, ptrs, params, false); + circuit.compute(); + + // === PRANK === + // Try to claim memory[0] = 0 + let ctx = circuit.cpu.main(); + let ptr = ctx.load_witness(Fr::ZERO); + let value = ctx.load_witness(Fr::ZERO); + circuit.mem_access.push([ptr, value]); + // === end prank === + + // auto-configuration stuff + let num_advice = circuit.cpu.total_advice() / usable_rows + 1; + circuit.params.cpu = FlexGateConfigParams { + k: k as usize, + num_advice_per_phase: vec![num_advice], + num_fixed: 1, + }; + circuit.params.num_lu_sets = CYCLES / usable_rows + 1; + MockProver::run(k, &circuit, vec![]).unwrap().verify().unwrap(); +} + +#[test] +fn test_ram_prover() { + let k = 10u32; + const CYCLES: usize = 2000; + + let mut rng = StdRng::seed_from_u64(0); + let mem_len = 500; + + let memory = vec![Fr::ZERO; mem_len]; + let ptrs = [0; CYCLES]; + + let usable_rows = 2usize.pow(k) - 11; // guess + let params = RAMConfigParams::default(); + let mut circuit = RAMCircuit::new(memory, ptrs, params, false); + circuit.compute(); + let num_advice = circuit.cpu.total_advice() / usable_rows + 1; + circuit.params.cpu = FlexGateConfigParams { + k: k as usize, + num_advice_per_phase: vec![num_advice], + num_fixed: 1, + }; + circuit.params.num_lu_sets = CYCLES / usable_rows + 1; + + let params = gen_srs(k); + let vk = keygen_vk(¶ms, &circuit).unwrap(); + let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); + let circuit_params = circuit.params(); + let break_points = circuit.cpu.break_points.borrow().clone().unwrap(); + drop(circuit); + + let memory: Vec<_> = (0..mem_len).map(|_| Fr::random(&mut rng)).collect(); + let ptrs = [(); CYCLES].map(|_| rng.gen_range(0..memory.len())); + let mut circuit = RAMCircuit::new(memory, ptrs, circuit_params, true); + *circuit.cpu.break_points.borrow_mut() = Some(break_points); + circuit.compute(); + + let proof = gen_proof(¶ms, &pk, circuit); + check_proof(¶ms, pk.get_vk(), &proof, true); +} diff --git a/vendor/halo2-lib/halo2-base/src/virtual_region/tests/lookups/mod.rs b/vendor/halo2-lib/halo2-base/src/virtual_region/tests/lookups/mod.rs new file mode 100644 index 00000000..23635403 --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/virtual_region/tests/lookups/mod.rs @@ -0,0 +1 @@ +mod memory; diff --git a/vendor/halo2-lib/halo2-base/src/virtual_region/tests/mod.rs b/vendor/halo2-lib/halo2-base/src/virtual_region/tests/mod.rs new file mode 100644 index 00000000..5b0a9bcb --- /dev/null +++ b/vendor/halo2-lib/halo2-base/src/virtual_region/tests/mod.rs @@ -0,0 +1 @@ +mod lookups; diff --git a/vendor/halo2-lib/halo2-ecc/Cargo.toml b/vendor/halo2-lib/halo2-ecc/Cargo.toml new file mode 100644 index 00000000..08d17209 --- /dev/null +++ b/vendor/halo2-lib/halo2-ecc/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "halo2-ecc" +version = "0.5.1" +authors = ["Intrinsic Technologies"] +license = "MIT" +edition = "2021" +repository = "https://github.com/axiom-crypto/halo2-lib" +readme = "../README.md" +description = "In-circuit elliptic curve library for halo2." +rust-version = "1.73.0" + +[dependencies] +itertools = "0.11" +num-bigint = { version = "0.4", features = ["rand"] } +num-integer = "0.1" +num-traits = "0.2" +rand_core = { version = "0.6", default-features = false, features = [ + "getrandom", +] } +rand = "0.8" +rand_chacha = "0.3.1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +rayon = "1.8" +test-case = "3.1.0" + +halo2-base = { version = "=0.5.1", path = "../halo2-base", default-features = false } + +# plotting circuit layout +plotters = { version = "0.3.0", optional = true } + +[dev-dependencies] +ark-std = { version = "0.3.0", features = ["print-trace"] } +pprof = { version = "0.13", features = ["criterion", "flamegraph"] } +criterion = "0.5.1" +criterion-macro = "0.4" +halo2-base = { version = "=0.5.1", path = "../halo2-base", default-features = false, features = [ + "test-utils", +] } +test-log = "0.2.12" +env_logger = "0.10.0" + +[features] +default = ["jemallocator", "halo2-axiom", "display"] +dev-graph = ["halo2-base/dev-graph", "plotters"] +display = ["halo2-base/display"] +asm = ["halo2-base/asm"] +halo2-pse = ["halo2-base/halo2-pse"] +halo2-axiom = ["halo2-base/halo2-axiom"] +jemallocator = ["halo2-base/jemallocator"] +mimalloc = ["halo2-base/mimalloc"] + +[[bench]] +name = "fp_mul" +harness = false + +[[bench]] +name = "msm" +harness = false + +[[bench]] +name = "fixed_base_msm" +harness = false diff --git a/vendor/halo2-lib/halo2-ecc/benches/README.md b/vendor/halo2-lib/halo2-ecc/benches/README.md new file mode 100644 index 00000000..0d3905ca --- /dev/null +++ b/vendor/halo2-lib/halo2-ecc/benches/README.md @@ -0,0 +1,45 @@ +To benchmark with flamegraph: + +``` +cargo bench --bench --profile=flamegraph --no-default-features --features "halo2-axiom, jemallocator" -- --profile-time