diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed3fdb9f92..3dbe36e654 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -118,6 +118,35 @@ jobs: - name: Run Integration Tests run: 'cargo test --test integration -- --nocapture' + zk_prover_integration: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Cache Rust dependencies + uses: ./.github/actions/cache-dependencies + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + + - name: pnpm-setup + uses: pnpm/action-setup@v4 + + - name: 'Setup node' + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + cache-dependency-path: pnpm-lock.yaml + + - name: 'Install the dependencies' + run: 'pnpm install --frozen-lockfile' + + - name: Run ZK Prover Integration Tests (with network downloads) + run: 'cargo test -p e3-zk-prover --features integration-tests --test integration_tests -- --nocapture' + build_e3_support_risc0: runs-on: ubuntu-latest steps: @@ -360,6 +389,9 @@ jobs: chmod +x target/debug/fake_encrypt chmod +x target/debug/pack_e3_params chmod +x ~/.cargo/bin/enclave + - name: 'Setup ZK prover' + run: | + enclave noir setup - name: 'Run ${{ matrix.test-suite }} tests' run: 'pnpm test:integration ${{ matrix.test-suite }} --no-prebuild' - name: 'Add test summary' @@ -560,6 +592,10 @@ jobs: run: | chmod +x ~/.cargo/bin/enclave + - name: Setup ZK prover + run: | + enclave noir setup + - name: Run Playwright tests working-directory: ./examples/CRISP env: @@ -763,6 +799,9 @@ jobs: run: | chmod +x ~/.cargo/bin/enclave chmod +x templates/default/target/debug/e3-support-scripts-dev + - name: Setup ZK prover + run: | + enclave noir setup - name: Verify downloaded artifacts run: | echo "Checking downloaded artifacts:" diff --git a/Cargo.lock b/Cargo.lock index 9674924606..4288fe69d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,47 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "acir" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acir_field", + "base64", + "bincode 2.0.1", + "brillig", + "color-eyre", + "flate2", + "noir_protobuf", + "noirc_span", + "num-bigint", + "num-traits", + "num_enum", + "prost 0.13.5", + "prost-build 0.13.5", + "protoc-bin-vendored", + "rmp-serde", + "serde", + "serde-big-array", + "strum 0.24.1", + "strum_macros 0.24.3", + "thiserror 1.0.69", +] + +[[package]] +name = "acir_field" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "ark-std 0.5.0", + "cfg-if", + "hex", + "num-bigint", + "serde", +] + [[package]] name = "actix" version = "0.13.5" @@ -221,6 +262,38 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "acvm" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acir", + "acvm_blackbox_solver", + "brillig_vm", + "indexmap 2.13.0", + "rustc-hash", + "serde", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "acvm_blackbox_solver" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acir", + "blake2", + "blake3", + "k256", + "keccak", + "libaes", + "log", + "p256", + "sha2", + "thiserror 1.0.69", +] + [[package]] name = "addr2line" version = "0.25.1" @@ -348,7 +421,7 @@ checksum = "90f374d3c6d729268bbe2d0e0ff992bb97898b2df756691a62ee1d5f0506bc39" dependencies = [ "alloy-primitives", "num_enum", - "strum", + "strum 0.27.2", ] [[package]] @@ -440,7 +513,7 @@ dependencies = [ "itoa", "serde", "serde_json", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -826,7 +899,7 @@ dependencies = [ "derive_more", "rand 0.8.5", "serde", - "strum", + "strum 0.27.2", ] [[package]] @@ -941,7 +1014,7 @@ dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", - "heck", + "heck 0.5.0", "indexmap 2.13.0", "proc-macro-error2", "proc-macro2", @@ -960,7 +1033,7 @@ dependencies = [ "alloy-json-abi", "const-hex", "dunce", - "heck", + "heck 0.5.0", "macro-string", "proc-macro2", "quote", @@ -976,7 +1049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94b91b13181d3bcd23680fd29d7bc861d1f33fbe90fdd0af67162434aeba902d" dependencies = [ "serde", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -1070,9 +1143,9 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428aa0f0e0658ff091f8f667c406e034b431cb10abd39de4f507520968acc499" +checksum = "4d7fd448ab0a017de542de1dcca7a58e7019fe0e7a34ed3f9543ebddf6aceffa" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -1081,6 +1154,7 @@ dependencies = [ "nybbles", "serde", "smallvec", + "thiserror 2.0.18", "tracing", ] @@ -1371,6 +1445,18 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "ark-grumpkin" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef677b59f5aff4123207c4dceb1c0ec8fdde2d4af7886f48be42ad864bfa0352" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-std 0.5.0", +] + [[package]] name = "ark-poly" version = "0.4.2" @@ -1830,6 +1916,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +[[package]] +name = "binary-merge" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597bb81c80a54b6a4381b23faba8d7774b144c94cbd1d6fe3f1329bd776554ab" + [[package]] name = "bincode" version = "1.3.3" @@ -1839,6 +1931,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -1888,6 +2000,15 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "bitvec" version = "1.0.1" @@ -1909,6 +2030,20 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -1952,6 +2087,52 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bn254_blackbox_solver" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acir", + "acvm_blackbox_solver", + "ark-bn254 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-grumpkin", + "hex", + "lazy_static", + "num-bigint", +] + +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "cfg_aliases", +] + +[[package]] +name = "brillig" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acir_field", + "serde", +] + +[[package]] +name = "brillig_vm" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acir", + "acvm_blackbox_solver", + "num-bigint", + "num-traits", + "thiserror 1.0.69", +] + [[package]] name = "brotli" version = "8.0.2" @@ -1982,6 +2163,16 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "build-data" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e6d5ca7a4989b90a9fafea85ce3c8bc9f0e0a76edcdcb330fe0c4fda92251f" +dependencies = [ + "chrono", + "safe-regex", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -2008,9 +2199,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -2158,7 +2349,7 @@ version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.114", @@ -2170,6 +2361,54 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +[[package]] +name = "codespan" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3362992a0d9f1dd7c3d0e89e0ab2bb540b7a95fea8cd798090e758fda2899b5e" +dependencies = [ + "codespan-reporting", + "serde", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "serde", + "termcolor", + "unicode-width 0.1.14", +] + +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -2222,7 +2461,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width", + "unicode-width 0.2.2", "windows-sys 0.59.0", ] @@ -2235,7 +2474,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width", + "unicode-width 0.2.2", "windows-sys 0.61.2", ] @@ -2283,6 +2522,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.10.0" @@ -2588,6 +2833,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -2683,6 +2929,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys 0.4.1", +] + [[package]] name = "directories" version = "6.0.0" @@ -2794,7 +3049,7 @@ dependencies = [ "actix", "anyhow", "async-trait", - "bincode", + "bincode 1.3.3", "e3-bfv-client", "e3-config", "e3-data", @@ -2831,7 +3086,7 @@ dependencies = [ "actix", "alloy", "anyhow", - "bincode", + "bincode 1.3.3", "derivative", "e3-aggregator", "e3-config", @@ -2840,6 +3095,7 @@ dependencies = [ "e3-events", "e3-evm", "e3-fhe", + "e3-fhe-params", "e3-keyshare", "e3-multithread", "e3-net", @@ -2848,6 +3104,7 @@ dependencies = [ "e3-sync", "e3-trbfv", "e3-utils", + "e3-zk-prover", "once_cell", "rayon", "tempfile", @@ -2872,6 +3129,7 @@ dependencies = [ "e3-evm", "e3-init", "e3-support-scripts", + "e3-zk-prover", "hex", "opentelemetry", "opentelemetry-otlp", @@ -2881,6 +3139,7 @@ dependencies = [ "rand 0.8.5", "serde", "tokio", + "toml 0.8.23", "tracing", "tracing-opentelemetry", "tracing-subscriber", @@ -2936,7 +3195,7 @@ dependencies = [ "anyhow", "argon2", "async-trait", - "bincode", + "bincode 1.3.3", "e3-utils", "rand 0.8.5", "serde", @@ -2953,7 +3212,7 @@ dependencies = [ "actix", "anyhow", "async-trait", - "bincode", + "bincode 1.3.3", "commitlog", "e3-events", "e3-utils", @@ -2973,7 +3232,7 @@ dependencies = [ "alloy", "alloy-primitives", "anyhow", - "bincode", + "bincode 1.3.3", "clap", "dirs 5.0.1", "e3-aggregator", @@ -2990,6 +3249,7 @@ dependencies = [ "e3-request", "e3-sortition", "e3-test-helpers", + "e3-zk-prover", "hex", "libp2p", "phf", @@ -3012,7 +3272,7 @@ dependencies = [ "alloy-primitives", "alloy-sol-types", "anyhow", - "bincode", + "bincode 1.3.3", "bloom", "bs58", "chrono", @@ -3021,6 +3281,7 @@ dependencies = [ "e3-crypto", "e3-data", "e3-events", + "e3-fhe-params", "e3-trbfv", "e3-utils", "futures-util", @@ -3030,7 +3291,7 @@ dependencies = [ "rand 0.8.5", "serde", "sha2", - "strum", + "strum 0.27.2", "thiserror 1.0.69", "tokio", "tracing", @@ -3091,7 +3352,7 @@ dependencies = [ "actix", "anyhow", "async-trait", - "bincode", + "bincode 1.3.3", "e3-bfv-client", "e3-config", "e3-data", @@ -3120,6 +3381,7 @@ dependencies = [ "fhe", "num-bigint", "num-traits", + "serde", "thiserror 1.0.69", ] @@ -3142,7 +3404,7 @@ version = "0.1.11" dependencies = [ "alloy", "async-trait", - "bincode", + "bincode 1.3.3", "e3-bfv-client", "e3-evm-helpers", "e3-fhe-params", @@ -3181,12 +3443,13 @@ dependencies = [ "actix", "anyhow", "async-trait", - "bincode", + "bincode 1.3.3", "e3-config", "e3-crypto", "e3-data", "e3-events", "e3-fhe", + "e3-fhe-params", "e3-multithread", "e3-request", "e3-trbfv", @@ -3220,8 +3483,13 @@ dependencies = [ "e3-crypto", "e3-data", "e3-events", + "e3-fhe-params", "e3-trbfv", "e3-utils", + "e3-zk-helpers", + "e3-zk-prover", + "fhe", + "fhe-traits", "rand 0.8.5", "rayon", "thiserror 1.0.69", @@ -3238,7 +3506,7 @@ dependencies = [ "anyhow", "async-std", "async-trait", - "bincode", + "bincode 1.3.3", "chrono", "e3-ciphernode-builder", "e3-config", @@ -3273,7 +3541,7 @@ dependencies = [ name = "e3-polynomial" version = "0.1.11" dependencies = [ - "bincode", + "bincode 1.3.3", "criterion", "fhe-math", "ndarray", @@ -3305,7 +3573,7 @@ dependencies = [ "actix", "anyhow", "async-trait", - "bincode", + "bincode 1.3.3", "e3-config", "e3-data", "e3-events", @@ -3386,7 +3654,7 @@ dependencies = [ "actix", "alloy", "anyhow", - "bincode", + "bincode 1.3.3", "clap", "e3-aggregator", "e3-bfv-client", @@ -3423,7 +3691,7 @@ dependencies = [ "anyhow", "async-std", "base64", - "bincode", + "bincode 1.3.3", "clap", "e3-aggregator", "e3-bfv-client", @@ -3462,7 +3730,7 @@ name = "e3-trbfv" version = "0.1.11" dependencies = [ "anyhow", - "bincode", + "bincode 1.3.3", "derivative", "e3-bfv-client", "e3-crypto", @@ -3538,7 +3806,49 @@ dependencies = [ "serde_json", "tempfile", "thiserror 1.0.69", - "toml", + "toml 0.8.23", +] + +[[package]] +name = "e3-zk-prover" +version = "0.1.11" +dependencies = [ + "acir", + "actix", + "acvm", + "anyhow", + "async-trait", + "base64", + "bincode 1.3.3", + "bn254_blackbox_solver", + "chrono", + "directories 5.0.1", + "e3-data", + "e3-events", + "e3-fhe-params", + "e3-polynomial", + "e3-request", + "e3-utils", + "e3-zk-helpers", + "fhe", + "flate2", + "futures-util", + "hex", + "indicatif 0.17.11", + "nargo", + "noirc_abi", + "num-bigint", + "reqwest", + "serde", + "serde_json", + "sha2", + "tar", + "tempfile", + "thiserror 1.0.69", + "tokio", + "toml 0.8.23", + "tracing", + "walkdir", ] [[package]] @@ -3613,6 +3923,7 @@ dependencies = [ "ff", "generic-array", "group", + "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", @@ -3627,7 +3938,7 @@ version = "0.1.11" dependencies = [ "anyhow", "clap", - "directories", + "directories 6.0.0", "flate2", "futures-util", "indicatif 0.17.11", @@ -3659,7 +3970,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.114", @@ -3787,7 +4098,7 @@ name = "fhe" version = "0.1.0-beta.7" source = "git+https://github.com/gnosisguild/fhe.rs#3824c52cb457c55551ffcdaeeaef9f3c53145a93" dependencies = [ - "bincode", + "bincode 1.3.3", "doc-comment", "fhe-math", "fhe-traits", @@ -3797,7 +4108,7 @@ dependencies = [ "num-bigint", "num-traits", "prost 0.12.6", - "prost-build", + "prost-build 0.12.6", "rand 0.8.5", "rand_chacha 0.3.1", "rand_distr", @@ -3822,7 +4133,7 @@ dependencies = [ "num-bigint-dig", "num-traits", "prost 0.12.6", - "prost-build", + "prost-build 0.12.6", "rand 0.8.5", "rand_chacha 0.3.1", "serde", @@ -3909,16 +4220,32 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", ] +[[package]] +name = "fm" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "codespan-reporting", + "iter-extended", + "serde", +] + [[package]] name = "fnv" version = "1.0.7" @@ -4313,6 +4640,12 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -4537,7 +4870,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", ] [[package]] @@ -4571,14 +4904,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http 1.4.0", "http-body 1.0.1", @@ -4588,7 +4920,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.2", - "system-configuration", + "system-configuration 0.7.0", "tokio", "tower-service", "tracing", @@ -4755,7 +5087,7 @@ dependencies = [ "netlink-proto", "netlink-sys", "rtnetlink", - "system-configuration", + "system-configuration 0.6.1", "tokio", "windows", ] @@ -4779,6 +5111,21 @@ dependencies = [ "xmltree", ] +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -4843,7 +5190,7 @@ dependencies = [ "console 0.15.11", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.2.2", "web-time", ] @@ -4855,7 +5202,7 @@ checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" dependencies = [ "console 0.16.2", "portable-atomic", - "unicode-width", + "unicode-width 0.2.2", "unit-prefix", "web-time", ] @@ -4869,6 +5216,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inplace-vec-builder" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf64c2edc8226891a71f127587a2861b132d2b942310843814d5001d99a1d307" +dependencies = [ + "smallvec", +] + [[package]] name = "instant" version = "0.1.13" @@ -4880,9 +5236,9 @@ dependencies = [ [[package]] name = "interprocess" -version = "2.2.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d" +checksum = "7b00d05442c2106c75b7410f820b152f61ec0edc7befcb9b381b673a20314753" dependencies = [ "doctest-file", "futures-core", @@ -4949,6 +5305,11 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "iter-extended" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" + [[package]] name = "itertools" version = "0.10.5" @@ -5012,17 +5373,58 @@ dependencies = [ ] [[package]] -name = "k256" -version = "0.13.4" +name = "jsonrpsee" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +checksum = "1fba77a59c4c644fd48732367624d1bcf6f409f9c9a286fbc71d2f1fc0b2ea16" dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", + "jsonrpsee-core", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693c93cbb7db25f4108ed121304b671a36002c2db67dff2ee4391a688c738547" +dependencies = [ + "async-trait", + "futures-util", + "http 1.4.0", + "jsonrpsee-types", + "pin-project", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tower 0.5.3", + "tracing", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66df7256371c45621b3b7d2fb23aea923d577616b9c0e9c0b950a6ea5c2be0ca" +dependencies = [ + "http 1.4.0", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", "serdect", "sha2", + "signature", ] [[package]] @@ -5077,6 +5479,12 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "libaes" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82903360c009b816f5ab72a9b68158c27c301ee2c3f20655b55c5e589e7d3bb7" + [[package]] name = "libc" version = "0.2.180" @@ -5439,7 +5847,7 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206e0aa0ebe004d778d79fb0966aa0de996c19894e2c0605ba2f8524dd4443d8" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.114", @@ -5783,6 +6191,31 @@ dependencies = [ "unsigned-varint 0.7.2", ] +[[package]] +name = "nargo" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acvm", + "brillig", + "fm", + "iter-extended", + "jsonrpsee", + "noir_greybox_fuzzer", + "noirc_abi", + "noirc_driver", + "noirc_errors", + "noirc_frontend", + "noirc_printable_type", + "rayon", + "serde", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "tracing", + "walkdir", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -5889,6 +6322,190 @@ dependencies = [ "libc", ] +[[package]] +name = "noir_greybox_fuzzer" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acvm", + "build-data", + "fm", + "noirc_abi", + "noirc_artifacts", + "num-traits", + "proptest", + "rand 0.8.5", + "rand_xorshift 0.3.0", + "rayon", + "sha256", + "termcolor", + "walkdir", +] + +[[package]] +name = "noir_protobuf" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "color-eyre", + "prost 0.13.5", +] + +[[package]] +name = "noirc_abi" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acvm", + "iter-extended", + "noirc_printable_type", + "num-bigint", + "num-traits", + "serde", + "serde_json", + "thiserror 1.0.69", + "toml 0.7.8", +] + +[[package]] +name = "noirc_arena" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" + +[[package]] +name = "noirc_artifacts" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acvm", + "codespan-reporting", + "fm", + "noirc_abi", + "noirc_driver", + "noirc_errors", + "noirc_printable_type", + "serde", +] + +[[package]] +name = "noirc_driver" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acvm", + "build-data", + "clap", + "fm", + "iter-extended", + "noirc_abi", + "noirc_errors", + "noirc_evaluator", + "noirc_frontend", + "rust-embed", + "rustc-hash", + "serde", + "tracing", +] + +[[package]] +name = "noirc_errors" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acvm", + "base64", + "codespan-reporting", + "flate2", + "fm", + "noirc_printable_type", + "noirc_span", + "rustc-hash", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "noirc_evaluator" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acvm", + "bn254_blackbox_solver", + "cfg-if", + "chrono", + "fm", + "im", + "indexmap 2.13.0", + "iter-extended", + "noirc_errors", + "noirc_frontend", + "noirc_printable_type", + "num-bigint", + "num-integer", + "num-traits", + "petgraph 0.8.3", + "rayon", + "rustc-hash", + "rustc-stable-hash", + "serde", + "serde_json", + "serde_with", + "smallvec", + "thiserror 1.0.69", + "tracing", + "vec-collections", +] + +[[package]] +name = "noirc_frontend" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acvm", + "bn254_blackbox_solver", + "cfg-if", + "fm", + "im", + "iter-extended", + "noirc_arena", + "noirc_errors", + "noirc_printable_type", + "num-bigint", + "num-traits", + "petgraph 0.8.3", + "rangemap", + "rustc-hash", + "serde", + "serde_json", + "small-ord-set", + "smol_str", + "strum 0.24.1", + "strum_macros 0.24.3", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "noirc_printable_type" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "acvm", + "iter-extended", + "serde", + "serde_json", +] + +[[package]] +name = "noirc_span" +version = "1.0.0-beta.15" +source = "git+https://github.com/noir-lang/noir?tag=v1.0.0-beta.15#83245db91dcf63420ef4bcbbd85b98f397fee663" +dependencies = [ + "codespan", + "serde", +] + [[package]] name = "nom" version = "7.1.3" @@ -6033,6 +6650,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.114", @@ -6256,6 +6874,24 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "page_size" version = "0.4.2" @@ -6381,6 +7017,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -6403,10 +7048,32 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset", + "fixedbitset 0.4.2", + "indexmap 2.13.0", +] + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset 0.5.7", "indexmap 2.13.0", ] +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset 0.5.7", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "serde", +] + [[package]] name = "petname" version = "2.0.2" @@ -6637,6 +7304,15 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -6723,8 +7399,8 @@ dependencies = [ "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", - "rand_xorshift", - "regex-syntax 0.8.8", + "rand_xorshift 0.4.0", + "regex-syntax 0.8.9", "rusty-fork", "tempfile", "unarray", @@ -6757,15 +7433,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", - "heck", + "heck 0.5.0", "itertools 0.12.1", "log", "multimap", "once_cell", - "petgraph", + "petgraph 0.6.5", "prettyplease", "prost 0.12.6", - "prost-types", + "prost-types 0.12.6", + "regex", + "syn 2.0.114", + "tempfile", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck 0.5.0", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph 0.7.1", + "prettyplease", + "prost 0.13.5", + "prost-types 0.13.5", "regex", "syn 2.0.114", "tempfile", @@ -6806,6 +7502,79 @@ dependencies = [ "prost 0.12.6", ] +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost 0.13.5", +] + +[[package]] +name = "protoc-bin-vendored" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c381df33c98266b5f08186583660090a4ffa0889e76c7e9a5e175f645a67fa" +dependencies = [ + "protoc-bin-vendored-linux-aarch_64", + "protoc-bin-vendored-linux-ppcle_64", + "protoc-bin-vendored-linux-s390_64", + "protoc-bin-vendored-linux-x86_32", + "protoc-bin-vendored-linux-x86_64", + "protoc-bin-vendored-macos-aarch_64", + "protoc-bin-vendored-macos-x86_64", + "protoc-bin-vendored-win32", +] + +[[package]] +name = "protoc-bin-vendored-linux-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c350df4d49b5b9e3ca79f7e646fde2377b199e13cfa87320308397e1f37e1a4c" + +[[package]] +name = "protoc-bin-vendored-linux-ppcle_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55a63e6c7244f19b5c6393f025017eb5d793fd5467823a099740a7a4222440c" + +[[package]] +name = "protoc-bin-vendored-linux-s390_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dba5565db4288e935d5330a07c264a4ee8e4a5b4a4e6f4e83fad824cc32f3b0" + +[[package]] +name = "protoc-bin-vendored-linux-x86_32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8854774b24ee28b7868cd71dccaae8e02a2365e67a4a87a6cd11ee6cdbdf9cf5" + +[[package]] +name = "protoc-bin-vendored-linux-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38b07546580df720fa464ce124c4b03630a6fb83e05c336fea2a241df7e5d78" + +[[package]] +name = "protoc-bin-vendored-macos-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89278a9926ce312e51f1d999fee8825d324d603213344a9a706daa009f1d8092" + +[[package]] +name = "protoc-bin-vendored-macos-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81745feda7ccfb9471d7a4de888f0652e806d5795b61480605d4943176299756" + +[[package]] +name = "protoc-bin-vendored-win32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" + [[package]] name = "quick-error" version = "1.2.3" @@ -6983,6 +7752,15 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -6992,6 +7770,21 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + [[package]] name = "rawpointer" version = "0.2.1" @@ -7113,8 +7906,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.13", - "regex-syntax 0.8.8", + "regex-automata 0.4.14", + "regex-syntax 0.8.9", ] [[package]] @@ -7128,20 +7921,20 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.8", + "regex-syntax 0.8.9", ] [[package]] name = "regex-lite" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" @@ -7151,9 +7944,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "reqwest" @@ -7191,14 +7984,16 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", + "tokio-util", "tower 0.5.3", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", ] [[package]] @@ -7256,6 +8051,25 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + [[package]] name = "rtnetlink" version = "0.13.1" @@ -7308,6 +8122,40 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rust-embed" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.114", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.27" @@ -7326,6 +8174,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc-stable-hash" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "781442f29170c5c93b7185ad559492601acdc71d5bb0706f5868094f45cfcd08" + [[package]] name = "rustc_version" version = "0.3.3" @@ -7446,6 +8300,53 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +[[package]] +name = "safe-proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "492d1a72624b0bd5b7f0193ea5834a1905534a517573a117e949e895f342906c" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "safe-quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcaa9a650f2f98ba4da0190623210c85945cb78b262709f606c57655eda173e1" +dependencies = [ + "safe-proc-macro2", +] + +[[package]] +name = "safe-regex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5194fafa3cb9da89e0cab6dffa1f3fdded586bd6396d12be11b4cae0c7ee45c2" +dependencies = [ + "safe-regex-macro", +] + +[[package]] +name = "safe-regex-compiler" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e822ae1e61251bcfd698317c237cf83f7c57161a5dc24ee609a85697f1ed15b3" +dependencies = [ + "safe-proc-macro2", + "safe-quote", +] + +[[package]] +name = "safe-regex-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2768de7e6ef19f59c5fd3c3ac207ef12b68a49f95e3172d67e4a04cfd992ca06" +dependencies = [ + "safe-proc-macro2", + "safe-regex-compiler", +] + [[package]] name = "same-file" version = "1.0.6" @@ -7608,6 +8509,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde-wasm-bindgen" version = "0.6.5" @@ -7774,6 +8684,18 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha256" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", +] + [[package]] name = "sha3" version = "0.10.8" @@ -7894,6 +8816,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "slab" version = "0.4.12" @@ -7916,6 +8848,15 @@ dependencies = [ "parking_lot 0.11.2", ] +[[package]] +name = "small-ord-set" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf7035a2b2268a5be8c1395738565b06beda836097e12021cdefc06b127a0e7e" +dependencies = [ + "smallvec", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -7925,6 +8866,16 @@ dependencies = [ "serde", ] +[[package]] +name = "smol_str" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d" +dependencies = [ + "borsh", + "serde", +] + [[package]] name = "socket2" version = "0.5.10" @@ -7945,6 +8896,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "sorted-iter" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bceb57dc07c92cdae60f5b27b3fa92ecaaa42fe36c55e22dbfb0b44893e0b1f7" + [[package]] name = "spin" version = "0.5.2" @@ -7985,13 +8942,32 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", ] [[package]] @@ -8000,7 +8976,7 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.114", @@ -8077,6 +9053,17 @@ dependencies = [ "system-configuration-sys", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "system-configuration-sys", +] + [[package]] name = "system-configuration-sys" version = "0.6.0" @@ -8130,6 +9117,15 @@ dependencies = [ "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 = "thiserror" version = "1.0.69" @@ -8355,6 +9351,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.19.15", +] + [[package]] name = "toml" version = "0.8.23" @@ -8385,6 +9393,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.13.0", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -8396,7 +9417,7 @@ dependencies = [ "serde_spanned", "toml_datetime 0.6.11", "toml_write", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -8408,7 +9429,7 @@ dependencies = [ "indexmap 2.13.0", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -8417,7 +9438,7 @@ version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ - "winnow", + "winnow 0.7.14", ] [[package]] @@ -8550,6 +9571,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -8695,6 +9726,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-width" version = "0.2.2" @@ -8753,6 +9790,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + [[package]] name = "url" version = "2.5.4" @@ -8800,6 +9843,21 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vec-collections" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9965c8f2ffed1dbcd16cafe18a009642f540fa22661c6cfd6309ddb02e4982" +dependencies = [ + "binary-merge", + "inplace-vec-builder", + "lazy_static", + "num-traits", + "serde", + "smallvec", + "sorted-iter", +] + [[package]] name = "version_check" version = "0.9.5" @@ -8819,6 +9877,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "void" version = "1.0.2" @@ -8927,6 +9991,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmtimer" version = "0.4.3" @@ -8967,14 +10044,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", ] [[package]] name = "webpki-roots" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -9352,6 +10429,15 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "0.7.14" @@ -9487,18 +10573,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.37" +version = "0.8.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.37" +version = "0.8.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 88d91a9481..a7854cec23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "crates/logger", "crates/multithread", "crates/net", + "crates/zk-prover", "crates/program-server", "crates/request", "crates/safe", @@ -103,6 +104,7 @@ e3-tests = { version = "0.1.11", path = "./crates/tests" } e3-trbfv = { version = "0.1.11", path = "./crates/trbfv" } e3-utils = { version = "0.1.11", path = "./crates/utils" } e3-safe = { version = "0.1.11", path = "./crates/safe" } +e3-zk-prover = { version = "0.1.11", path = "./crates/zk-prover" } e3-zk-helpers = { version = "0.1.11", path = "./crates/zk-helpers" } e3-parity-matrix = { version = "0.1.11", path = "./crates/parity-matrix" } @@ -178,6 +180,7 @@ strum = { version = "=0.27.2", features = ["derive"] } tempfile = "=3.20.0" thiserror = { version = "=1.0.69" } tokio = { version = "=1.46.1", features = ["full"] } +toml = "=0.8.23" tracing = "=0.1.41" tracing-opentelemetry = "=0.30.0" tracing-subscriber = { version = "=0.3.19", features = ["env-filter", "time"] } diff --git a/crates/Dockerfile b/crates/Dockerfile index b8aedf9838..c2cc6246a0 100644 --- a/crates/Dockerfile +++ b/crates/Dockerfile @@ -65,6 +65,7 @@ COPY crates/logger/Cargo.toml ./logger/Cargo.toml COPY crates/multithread/Cargo.toml ./multithread/Cargo.toml COPY crates/net/Cargo.toml ./net/Cargo.toml COPY crates/parity-matrix/Cargo.toml ./parity-matrix/Cargo.toml +COPY crates/zk-prover/Cargo.toml ./zk-prover/Cargo.toml COPY crates/polynomial/Cargo.toml ./polynomial/Cargo.toml COPY crates/polynomial/benches ./polynomial/benches COPY crates/program-server/Cargo.toml ./program-server/Cargo.toml diff --git a/crates/aggregator/src/threshold_plaintext_aggregator.rs b/crates/aggregator/src/threshold_plaintext_aggregator.rs index 2345773ea9..1c0fb0d5c2 100644 --- a/crates/aggregator/src/threshold_plaintext_aggregator.rs +++ b/crates/aggregator/src/threshold_plaintext_aggregator.rs @@ -10,9 +10,9 @@ use actix::prelude::*; use anyhow::{anyhow, bail, ensure, Result}; use e3_data::Persistable; use e3_events::{ - prelude::*, trap, BusHandle, ComputeRequest, ComputeResponse, CorrelationId, - DecryptionshareCreated, Die, E3id, EType, EnclaveEvent, EnclaveEventData, PlaintextAggregated, - Seed, + prelude::*, trap, BusHandle, ComputeRequest, ComputeResponse, ComputeResponseKind, + CorrelationId, DecryptionshareCreated, Die, E3id, EType, EnclaveEvent, EnclaveEventData, + PlaintextAggregated, Seed, }; use e3_sortition::{GetNodesForE3, Sortition}; use e3_trbfv::{ @@ -209,7 +209,7 @@ impl ThresholdPlaintextAggregator { let trbfv_config = TrBFVConfig::new(state.params.clone(), state.threshold_n, state.threshold_m); - let event = ComputeRequest::new( + let event = ComputeRequest::trbfv( TrBFVRequest::CalculateThresholdDecryption( CalculateThresholdDecryptionRequest { ciphertexts: msg.ciphertext_output, @@ -231,7 +231,9 @@ impl ThresholdPlaintextAggregator { "PlaintextAggregator should never receive incorrect e3_id msgs" ); - let TrBFVResponse::CalculateThresholdDecryption(response) = msg.response else { + let ComputeResponseKind::TrBFV(TrBFVResponse::CalculateThresholdDecryption(response)) = + msg.response + else { // Must be another compute response so ignoring return Ok(()); }; diff --git a/crates/ciphernode-builder/Cargo.toml b/crates/ciphernode-builder/Cargo.toml index 495eda85f4..e30c2dba3e 100644 --- a/crates/ciphernode-builder/Cargo.toml +++ b/crates/ciphernode-builder/Cargo.toml @@ -19,9 +19,11 @@ e3-data.workspace = true e3-events.workspace = true e3-evm.workspace = true e3-fhe.workspace = true +e3-fhe-params.workspace = true e3-keyshare.workspace = true e3-multithread.workspace = true e3-net.workspace = true +e3-zk-prover.workspace = true e3-request.workspace = true e3-sortition.workspace = true e3-sync.workspace = true diff --git a/crates/ciphernode-builder/src/ciphernode_builder.rs b/crates/ciphernode-builder/src/ciphernode_builder.rs index 775666a877..3503a07446 100644 --- a/crates/ciphernode-builder/src/ciphernode_builder.rs +++ b/crates/ciphernode-builder/src/ciphernode_builder.rs @@ -18,6 +18,7 @@ use e3_events::{AggregateId, BusHandle, EnclaveEvent, EventBus, EventBusConfig, use e3_evm::{BondingRegistrySolReader, CiphernodeRegistrySolReader, EnclaveSolWriter}; use e3_evm::{CiphernodeRegistrySol, EnclaveSolReader}; use e3_fhe::ext::FheExtension; +use e3_fhe_params::BfvPreset; use e3_keyshare::ext::ThresholdKeyshareExtension; use e3_multithread::{Multithread, MultithreadReport, TaskPool}; use e3_net::{NetEventTranslator, NetRepositoryFactory}; @@ -28,6 +29,7 @@ use e3_sortition::{ }; use e3_sync::Synchronizer; use e3_utils::{rand_eth_addr, SharedRng}; +use e3_zk_prover::{setup_zk_actors, ZkBackend}; use std::{collections::HashMap, path::PathBuf, sync::Arc}; use tracing::{error, info}; @@ -66,6 +68,7 @@ pub struct CiphernodeBuilder { task_pool: Option, threads: Option, threshold_plaintext_agg: bool, + zk_backend: Option, net_config: Option, } @@ -131,6 +134,7 @@ impl CiphernodeBuilder { threads: None, threshold_plaintext_agg: false, net_config: None, + zk_backend: None, } } @@ -251,6 +255,12 @@ impl CiphernodeBuilder { self } + /// Enable ZK proof generation with the given backend. + pub fn with_zkproof(mut self, backend: ZkBackend) -> Self { + self.zk_backend = Some(backend); + self + } + /// Use score-based sortition (recommended) pub fn with_sortition_score(mut self) -> Self { self.sortition_backend = SortitionBackend::score(); @@ -412,14 +422,20 @@ impl CiphernodeBuilder { if let Some(KeyshareKind::Threshold) = self.keyshare { let _ = self.ensure_multithread(&bus); - let share_encryption_params = e3_trbfv::helpers::get_share_encryption_params(); + // TODO: Make BfvPreset configurable via builder method (e.g., with_share_enc_preset()) + // Currently hardcoded to InsecureDkg512 for DKG operations. + // Production deployments should use BfvPreset::SecureDkg8192. + let share_enc_preset = BfvPreset::InsecureDkg512; info!("Setting up ThresholdKeyshareExtension"); e3_builder = e3_builder.with(ThresholdKeyshareExtension::create( &bus, &self.cipher, &addr, - share_encryption_params, - )) + share_enc_preset, + )); + + info!("Setting up ZK actors"); + setup_zk_actors(&bus, self.zk_backend.as_ref()); } if self.pubkey_agg { @@ -491,14 +507,26 @@ impl CiphernodeBuilder { ) }); - // Create it - let addr = Multithread::attach( - bus, - self.rng.clone(), - self.cipher.clone(), - task_pool, - self.multithread_report.clone(), - ); + // Create it with or without ZK prover + let addr = if let Some(ref backend) = self.zk_backend { + info!("Multithread actor with ZK prover"); + Multithread::attach_with_zk( + bus, + self.rng.clone(), + self.cipher.clone(), + task_pool, + self.multithread_report.clone(), + backend, + ) + } else { + Multithread::attach( + bus, + self.rng.clone(), + self.cipher.clone(), + task_pool, + self.multithread_report.clone(), + ) + }; // Set the cache self.multithread_cache = Some(addr.clone()); diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 920732a697..1d1a05355e 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -24,6 +24,7 @@ e3-entrypoint = { workspace = true } e3-evm = { workspace = true } e3-events = { workspace = true } e3-init = { workspace = true } +e3-zk-prover = { workspace = true } e3-support-scripts = { workspace = true } hex = { workspace = true } opentelemetry = { workspace = true } @@ -34,6 +35,7 @@ petname = { workspace = true } rand = { workspace = true } serde = { workspace = true } tokio = { workspace = true } +toml = { workspace = true } tracing = { workspace = true } tracing-opentelemetry = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index 398e80ec5e..e9d25c0dee 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -10,10 +10,11 @@ use crate::ciphernode::{self, CiphernodeCommands}; use crate::helpers::telemetry::{setup_simple_tracing, setup_tracing}; use crate::net::NetCommands; use crate::nodes::{self, NodeCommands}; +use crate::noir::NoirCommands; use crate::password::PasswordCommands; use crate::program::{self, ProgramCommands}; use crate::wallet::WalletCommands; -use crate::{config_set, init, net, password, purge_all, rev, wallet}; +use crate::{config_set, init, net, noir, password, purge_all, rev, wallet}; use crate::{print_env, start}; use anyhow::{bail, Result}; use clap::{command, ArgAction, Parser, Subcommand}; @@ -129,6 +130,10 @@ impl Cli { ) .await?; }, + Commands::Noir { command } => { + setup_simple_tracing(log_level); + noir::execute_without_config(command).await? + }, _ => bail!( "Configuration file not found. Run `enclave config-set` to create a configuration." ), @@ -184,6 +189,7 @@ impl Cli { Commands::Wallet { command } => wallet::execute(command, config).await?, Commands::Ciphernode { command } => ciphernode::execute(command, &config).await?, Commands::Net { command } => net::execute(command, &config).await?, + Commands::Noir { command } => noir::execute(command, &config).await?, Commands::Rev => rev::execute().await?, } @@ -283,6 +289,12 @@ pub enum Commands { command: NetCommands, }, + /// Noir prover management and proof generation + Noir { + #[command(subcommand)] + command: NoirCommands, + }, + /// On-chain ciphernode lifecycle management Ciphernode { #[command(subcommand)] diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index d6db9cd79f..e5a09ba751 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -27,6 +27,7 @@ mod nodes_start; mod nodes_status; mod nodes_stop; mod nodes_up; +pub mod noir; mod password; mod password_delete; mod password_set; diff --git a/crates/cli/src/noir.rs b/crates/cli/src/noir.rs new file mode 100644 index 0000000000..2cde9b43e4 --- /dev/null +++ b/crates/cli/src/noir.rs @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use anyhow::*; +use clap::Subcommand; +use e3_config::AppConfig; +use e3_zk_prover::{SetupStatus, ZkBackend}; + +#[derive(Subcommand, Debug)] +pub enum NoirCommands { + Status, + Setup { + #[arg(long, short)] + force: bool, + }, +} + +pub async fn execute(command: NoirCommands, config: &AppConfig) -> Result<()> { + let zk_config = e3_zk_prover::ZkConfig::fetch_or_default().await; + let backend = ZkBackend::new( + config.bb_binary(), + config.circuits_dir(), + config.work_dir(), + zk_config, + ); + + match command { + NoirCommands::Status => { + execute_status(&backend).await?; + } + NoirCommands::Setup { force } => { + execute_setup(&backend, force).await?; + } + } + + Ok(()) +} + +pub async fn execute_without_config(command: NoirCommands) -> Result<()> { + let backend = ZkBackend::with_default_dir("default") + .await + .map_err(|e| anyhow!("Failed to initialize ZK backend: {}", e))?; + + match command { + NoirCommands::Status => { + execute_status(&backend).await?; + } + NoirCommands::Setup { force } => { + execute_setup(&backend, force).await?; + } + } + + Ok(()) +} + +async fn execute_status(backend: &ZkBackend) -> Result<()> { + let status = backend.check_status().await; + let version_info = backend.load_version_info().await; + + println!("=== ZK Prover Status ===\n"); + + println!("Barretenberg (bb):"); + println!(" Path: {}", backend.bb_binary.display()); + if let Some(ref v) = version_info.bb_version { + println!(" Version: {}", v); + } + if backend.bb_binary.exists() { + println!(" Installed"); + } else { + println!(" Not installed"); + } + + println!(); + + println!("Circuits:"); + println!(" Path: {}", backend.circuits_dir.display()); + if let Some(ref v) = version_info.circuits_version { + println!(" Version: {}", v); + } + if backend.circuits_dir.exists() { + println!(" Installed"); + } else { + println!(" Not installed"); + } + + println!(); + + match status { + SetupStatus::Ready => { + println!("Status: Ready"); + } + SetupStatus::BbNeedsUpdate { + installed, + required, + } => { + println!("Status: Barretenberg needs update"); + println!( + " Installed: {}", + installed.as_deref().unwrap_or("not installed") + ); + println!(" Required: {}", required); + println!("\nRun `enclave noir setup` to update"); + } + SetupStatus::CircuitsNeedUpdate { + installed, + required, + } => { + println!("Status: Circuits need update"); + println!( + " Installed: {}", + installed.as_deref().unwrap_or("not installed") + ); + println!(" Required: {}", required); + println!("\nRun `enclave noir setup` to update"); + } + SetupStatus::FullSetupNeeded => { + println!("Status: Setup required"); + println!("\nRun `enclave noir setup` to install"); + } + } + + Ok(()) +} + +async fn execute_setup(backend: &ZkBackend, force: bool) -> Result<()> { + if force { + println!("Force reinstalling ZK prover components...\n"); + println!("Setting up ZK prover...\n"); + + // Force reinstall by directly downloading components + backend + .download_bb() + .await + .map_err(|e| anyhow!("Failed to download bb: {}", e))?; + backend + .download_circuits() + .await + .map_err(|e| anyhow!("Failed to download circuits: {}", e))?; + } else { + let status = backend.check_status().await; + if matches!(status, SetupStatus::Ready) { + println!("ZK prover is already set up and up to date."); + println!(" Use --force to reinstall."); + return Ok(()); + } + + println!("Setting up ZK prover...\n"); + + backend + .ensure_installed() + .await + .map_err(|e| anyhow!("Setup failed: {}", e))?; + } + + println!("\nZK prover setup complete!"); + println!(" bb binary: {}", backend.bb_binary.display()); + println!(" circuits: {}", backend.circuits_dir.display()); + + Ok(()) +} diff --git a/crates/config/src/app_config.rs b/crates/config/src/app_config.rs index 5074b23086..5da8971b0c 100644 --- a/crates/config/src/app_config.rs +++ b/crates/config/src/app_config.rs @@ -256,6 +256,21 @@ impl AppConfig { self.paths.log_file() } + /// Get the bb binary path + pub fn bb_binary(&self) -> PathBuf { + self.paths.bb_binary() + } + + /// Get the circuits directory + pub fn circuits_dir(&self) -> PathBuf { + self.paths.circuits_dir() + } + + /// Get the work directory for this node + pub fn work_dir(&self) -> PathBuf { + self.paths.work_dir(&self.name) + } + fn node_def(&self) -> &NodeDefinition { // NOTE: on creation an invariant we have is that our node name is an extant key in our // nodes datastructure so expect here is ok and we dont have to clone the NodeDefinition diff --git a/crates/config/src/chain_config.rs b/crates/config/src/chain_config.rs index 63e241eee4..69a203932b 100644 --- a/crates/config/src/chain_config.rs +++ b/crates/config/src/chain_config.rs @@ -11,7 +11,7 @@ use crate::{ rpc::{RpcAuth, RPC}, }; use anyhow::*; -use e3_events::{EvmEventConfig, EvmEventConfigChain}; +use e3_events::EvmEventConfigChain; use serde::{Deserialize, Serialize}; use tracing::error; diff --git a/crates/config/src/paths_engine.rs b/crates/config/src/paths_engine.rs index 891fceb0f3..c5391a907c 100644 --- a/crates/config/src/paths_engine.rs +++ b/crates/config/src/paths_engine.rs @@ -168,6 +168,34 @@ impl PathsEngine { } None } + + fn get_noir_base(&self) -> PathBuf { + if let Some(root_dir) = self.get_root_dir() { + return root_dir; + } + // Fallback to .enclave relative to default config dir (e.g., ~/.config/enclave/.enclave) + self.default_config_dir.join(".enclave") + } + + /// Get the noir base directory for ZK circuits and prover + pub fn noir_dir(&self) -> PathBuf { + clean(self.get_noir_base().join("noir")) + } + + /// Get the bb binary path + pub fn bb_binary(&self) -> PathBuf { + clean(self.noir_dir().join("bin").join("bb")) + } + + /// Get the circuits directory (shared across nodes) + pub fn circuits_dir(&self) -> PathBuf { + clean(self.noir_dir().join("circuits")) + } + + /// Get the work directory for a specific node + pub fn work_dir(&self, node_name: &str) -> PathBuf { + clean(self.noir_dir().join("work").join(node_name)) + } } #[cfg(test)] diff --git a/crates/entrypoint/Cargo.toml b/crates/entrypoint/Cargo.toml index 85a665dd1f..9770304087 100644 --- a/crates/entrypoint/Cargo.toml +++ b/crates/entrypoint/Cargo.toml @@ -36,6 +36,7 @@ serde = { workspace = true } serde_json = { workspace = true } e3-sortition = { workspace = true } e3-test-helpers = { workspace = true } +e3-zk-prover = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } zeroize = { workspace = true } diff --git a/crates/entrypoint/src/start/start.rs b/crates/entrypoint/src/start/start.rs index 9dae6086f7..b541a38d16 100644 --- a/crates/entrypoint/src/start/start.rs +++ b/crates/entrypoint/src/start/start.rs @@ -9,19 +9,25 @@ use anyhow::Result; use e3_ciphernode_builder::{CiphernodeBuilder, CiphernodeHandle}; use e3_config::AppConfig; use e3_crypto::Cipher; -use e3_data::RepositoriesFactory; -use e3_events::BusHandle; -use e3_net::{NetEventTranslator, NetRepositoryFactory}; +use e3_zk_prover::ZkBackend; use rand::SeedableRng; use rand_chacha::rand_core::OsRng; use std::sync::{Arc, Mutex}; -use tokio::task::JoinHandle; use tracing::instrument; #[instrument(name = "app", skip_all)] pub async fn execute(config: &AppConfig, address: Address) -> Result { let rng = Arc::new(Mutex::new(rand_chacha::ChaCha20Rng::from_rng(OsRng)?)); let cipher = Arc::new(Cipher::from_file(&config.key_file()).await?); + let zk_config = e3_zk_prover::ZkConfig::fetch_or_default().await; + let backend = ZkBackend::new( + config.bb_binary(), + config.circuits_dir(), + config.work_dir(), + zk_config, + ); + backend.ensure_installed().await?; + let node = CiphernodeBuilder::new(&config.name(), rng.clone(), cipher.clone()) .with_address(&address.to_string()) .with_persistence(&config.log_file(), &config.db_file()) @@ -32,6 +38,7 @@ pub async fn execute(config: &AppConfig, address: Address) -> Result Self { + pub fn new(request: ComputeRequestKind, correlation_id: CorrelationId, e3_id: E3id) -> Self { Self { request, correlation_id, e3_id, } } + + pub fn trbfv( + request: e3_trbfv::TrBFVRequest, + correlation_id: CorrelationId, + e3_id: E3id, + ) -> Self { + Self::new(ComputeRequestKind::TrBFV(request), correlation_id, e3_id) + } + + pub fn zk(request: ZkRequest, correlation_id: CorrelationId, e3_id: E3id) -> Self { + Self::new(ComputeRequestKind::Zk(request), correlation_id, e3_id) + } } + impl ToString for ComputeRequest { fn to_string(&self) -> String { - match self.request { - e3_trbfv::TrBFVRequest::GenEsiSss(_) => "GenEsiSss", - e3_trbfv::TrBFVRequest::GenPkShareAndSkSss(_) => "GenPkShareAndSkSss", - e3_trbfv::TrBFVRequest::CalculateDecryptionKey(_) => "CalculateDecryptionKey", - e3_trbfv::TrBFVRequest::CalculateDecryptionShare(_) => "CalculateDecryptionShare", - e3_trbfv::TrBFVRequest::CalculateThresholdDecryption(_) => { - "CalculateThresholdDecryption" - } + match &self.request { + ComputeRequestKind::TrBFV(req) => match req { + e3_trbfv::TrBFVRequest::GenEsiSss(_) => "GenEsiSss", + e3_trbfv::TrBFVRequest::GenPkShareAndSkSss(_) => "GenPkShareAndSkSss", + e3_trbfv::TrBFVRequest::CalculateDecryptionKey(_) => "CalculateDecryptionKey", + e3_trbfv::TrBFVRequest::CalculateDecryptionShare(_) => "CalculateDecryptionShare", + e3_trbfv::TrBFVRequest::CalculateThresholdDecryption(_) => { + "CalculateThresholdDecryption" + } + }, + ComputeRequestKind::Zk(req) => match req { + ZkRequest::PkBfv(_) => "ZkPkBfv", + }, } .to_string() } } -/// The compute result from a threadpool computation -/// This enum provides protocol disambiguation +/// The compute result from a threadpool computation. #[derive(Message, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[rtype(result = "()")] pub struct ComputeResponse { - pub response: e3_trbfv::TrBFVResponse, + pub response: ComputeResponseKind, pub correlation_id: CorrelationId, pub e3_id: E3id, } impl ComputeResponse { - pub fn new( - response: e3_trbfv::TrBFVResponse, - correlation_id: CorrelationId, - e3_id: E3id, - ) -> ComputeResponse { - ComputeResponse { + pub fn new(response: ComputeResponseKind, correlation_id: CorrelationId, e3_id: E3id) -> Self { + Self { response, correlation_id, e3_id, } } + + pub fn trbfv( + response: e3_trbfv::TrBFVResponse, + correlation_id: CorrelationId, + e3_id: E3id, + ) -> Self { + Self::new(ComputeResponseKind::TrBFV(response), correlation_id, e3_id) + } + + pub fn zk(response: ZkResponse, correlation_id: CorrelationId, e3_id: E3id) -> Self { + Self::new(ComputeResponseKind::Zk(response), correlation_id, e3_id) + } + + pub fn try_into_zk(self) -> anyhow::Result { + match self.response { + ComputeResponseKind::Zk(zk) => Ok(zk), + _ => bail!("Expected ZkResponse but got TrBFV"), + } + } + + pub fn try_into_trbfv(self) -> anyhow::Result { + match self.response { + ComputeResponseKind::TrBFV(trbfv) => Ok(trbfv), + _ => bail!("Expected TrBFVResponse but got Zk"), + } + } } -/// An error from a threadpool computation -/// This enum provides protocol disambiguation +/// An error from a threadpool computation. #[derive(Message, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[rtype(result = "()")] pub struct ComputeRequestError { @@ -96,23 +145,31 @@ impl ComputeRequestError { pub fn new(kind: ComputeRequestErrorKind, request: ComputeRequest) -> Self { Self { kind, request } } + + pub fn get_err(&self) -> &ComputeRequestErrorKind { + &self.kind + } + + pub fn correlation_id(&self) -> &CorrelationId { + &self.request.correlation_id + } + + pub fn request(&self) -> &ComputeRequest { + &self.request + } } #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum ComputeRequestErrorKind { TrBFV(e3_trbfv::TrBFVError), -} - -impl ComputeRequestError { - pub fn get_err(&self) -> &ComputeRequestErrorKind { - &self.kind - } + Zk(ZkError), } impl std::error::Error for ComputeRequestError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self.get_err() { ComputeRequestErrorKind::TrBFV(err) => Some(err), + ComputeRequestErrorKind::Zk(err) => Some(err), } } } @@ -121,7 +178,10 @@ impl fmt::Display for ComputeRequestError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.get_err() { ComputeRequestErrorKind::TrBFV(err) => { - write!(f, "We had an error number crunching: {:?}", err) + write!(f, "TrBFV computation error: {:?}", err) + } + ComputeRequestErrorKind::Zk(err) => { + write!(f, "ZK proof error: {}", err) } } } @@ -131,7 +191,7 @@ impl TryFrom for CalculateDecryptionShareResponse { type Error = anyhow::Error; fn try_from(value: ComputeResponse) -> Result { match value.response { - TrBFVResponse::CalculateDecryptionShare(data) => Ok(data), + ComputeResponseKind::TrBFV(TrBFVResponse::CalculateDecryptionShare(data)) => Ok(data), _ => { bail!("Expected CalculateDecryptionShareResponse in response but it was not found") } @@ -143,7 +203,7 @@ impl TryFrom for CalculateDecryptionKeyResponse { type Error = anyhow::Error; fn try_from(value: ComputeResponse) -> Result { match value.response { - TrBFVResponse::CalculateDecryptionKey(data) => Ok(data), + ComputeResponseKind::TrBFV(TrBFVResponse::CalculateDecryptionKey(data)) => Ok(data), _ => { bail!("Expected CalculateDecryptionKeyResponse in response but it was not found") } @@ -155,7 +215,7 @@ impl TryFrom for GenPkShareAndSkSssResponse { type Error = anyhow::Error; fn try_from(value: ComputeResponse) -> Result { match value.response { - TrBFVResponse::GenPkShareAndSkSss(data) => Ok(data), + ComputeResponseKind::TrBFV(TrBFVResponse::GenPkShareAndSkSss(data)) => Ok(data), _ => { bail!("Expected GenPkShareAndSkSssResponse in response but it was not found") } @@ -167,7 +227,7 @@ impl TryFrom for GenEsiSssResponse { type Error = anyhow::Error; fn try_from(value: ComputeResponse) -> Result { match value.response { - TrBFVResponse::GenEsiSss(data) => Ok(data), + ComputeResponseKind::TrBFV(TrBFVResponse::GenEsiSss(data)) => Ok(data), _ => { bail!("Expected GenEsiSssResponse in response but it was not found") } @@ -179,9 +239,25 @@ impl TryFrom for CalculateThresholdDecryptionResponse { type Error = anyhow::Error; fn try_from(value: ComputeResponse) -> Result { match value.response { - TrBFVResponse::CalculateThresholdDecryption(data) => Ok(data), + ComputeResponseKind::TrBFV(TrBFVResponse::CalculateThresholdDecryption(data)) => { + Ok(data) + } + _ => { + bail!( + "Expected CalculateThresholdDecryptionResponse in response but it was not found" + ) + } + } + } +} + +impl TryFrom for PkBfvProofResponse { + type Error = anyhow::Error; + fn try_from(value: ComputeResponse) -> Result { + match value.response { + ComputeResponseKind::Zk(ZkResponse::PkBfv(data)) => Ok(data), _ => { - bail!("Expected CalculateThresholdDecryptionResponse in response but it was not found") + bail!("Expected PkBfvProofResponse in response but it was not found") } } } diff --git a/crates/events/src/enclave_event/compute_request/zk.rs b/crates/events/src/enclave_event/compute_request/zk.rs new file mode 100644 index 0000000000..ec3fa1cd26 --- /dev/null +++ b/crates/events/src/enclave_event/compute_request/zk.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::Proof; +use derivative::Derivative; +use e3_fhe_params::BfvPreset; +use e3_utils::utility_types::ArcBytes; +use serde::{Deserialize, Serialize}; + +/// ZK proof generation request variants. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ZkRequest { + /// Generate proof for BFV public key (T0). + PkBfv(PkBfvProofRequest), +} + +/// Request to generate a proof for BFV public key generation (T0). +#[derive(Derivative, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derivative(Debug)] +pub struct PkBfvProofRequest { + /// The BFV public key bytes. + #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] + pub pk_bfv: ArcBytes, + pub params_preset: BfvPreset, +} + +impl PkBfvProofRequest { + pub fn new(pk_bfv: impl Into, params_preset: BfvPreset) -> Self { + Self { + pk_bfv: pk_bfv.into(), + params_preset, + } + } +} + +/// ZK proof generation response variants. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ZkResponse { + /// Proof for BFV public key (T0). + PkBfv(PkBfvProofResponse), +} + +/// Response containing a generated BFV public key proof. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct PkBfvProofResponse { + pub proof: Proof, +} + +impl PkBfvProofResponse { + pub fn new(proof: Proof) -> Self { + Self { proof } + } +} + +/// ZK-specific error variants. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ZkError { + /// Proof generation failed. + ProofGenerationFailed(String), + /// Witness generation failed. + WitnessGenerationFailed(String), + /// Invalid parameters. + InvalidParams(String), +} + +impl std::fmt::Display for ZkError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ZkError::ProofGenerationFailed(msg) => write!(f, "Proof generation failed: {}", msg), + ZkError::WitnessGenerationFailed(msg) => { + write!(f, "Witness generation failed: {}", msg) + } + ZkError::InvalidParams(msg) => write!(f, "Invalid parameters: {}", msg), + } + } +} + +impl std::error::Error for ZkError {} diff --git a/crates/events/src/enclave_event/encryption_key_created.rs b/crates/events/src/enclave_event/encryption_key_created.rs index e3deb25af7..60a99e2916 100644 --- a/crates/events/src/enclave_event/encryption_key_created.rs +++ b/crates/events/src/enclave_event/encryption_key_created.rs @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use crate::E3id; +use crate::{E3id, Proof}; use actix::Message; use derivative::Derivative; use e3_utils::utility_types::ArcBytes; @@ -14,12 +14,30 @@ use std::{ sync::Arc, }; +/// BFV encryption key with optional proof of correct generation. #[derive(Derivative, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derivative(Debug)] pub struct EncryptionKey { pub party_id: u64, #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] pub pk_bfv: ArcBytes, + /// Proof of correct BFV public key generation (T0 proof). + pub proof: Option, +} + +impl EncryptionKey { + pub fn new(party_id: u64, pk_bfv: impl Into) -> Self { + Self { + party_id, + pk_bfv: pk_bfv.into(), + proof: None, + } + } + + pub fn with_proof(mut self, proof: Proof) -> Self { + self.proof = Some(proof); + self + } } #[derive(Message, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/crates/events/src/enclave_event/encryption_key_pending.rs b/crates/events/src/enclave_event/encryption_key_pending.rs new file mode 100644 index 0000000000..fd7e2e646f --- /dev/null +++ b/crates/events/src/enclave_event/encryption_key_pending.rs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::{E3id, EncryptionKey}; +use actix::Message; +use e3_fhe_params::BfvPreset; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; +use std::sync::Arc; + +/// Encryption key pending proof generation and verification. +/// +/// This event is emitted by local key generation and consumed by ZkActor. +#[derive(Message, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[rtype(result = "()")] +pub struct EncryptionKeyPending { + pub e3_id: E3id, + pub key: Arc, + pub params_preset: BfvPreset, +} + +impl Display for EncryptionKeyPending { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/crates/events/src/enclave_event/encryption_key_received.rs b/crates/events/src/enclave_event/encryption_key_received.rs new file mode 100644 index 0000000000..a779e9a1c9 --- /dev/null +++ b/crates/events/src/enclave_event/encryption_key_received.rs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::{E3id, EncryptionKey}; +use actix::Message; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; +use std::sync::Arc; + +/// Encryption key received from external sources (network). +/// +/// This event is consumed by ZkActor for verification before publishing +/// `EncryptionKeyCreated`. +#[derive(Message, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[rtype(result = "()")] +pub struct EncryptionKeyReceived { + pub e3_id: E3id, + pub key: Arc, +} + +impl Display for EncryptionKeyReceived { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/crates/events/src/enclave_event/mod.rs b/crates/events/src/enclave_event/mod.rs index 514ec33337..1d4f7f8f48 100644 --- a/crates/events/src/enclave_event/mod.rs +++ b/crates/events/src/enclave_event/mod.rs @@ -21,6 +21,8 @@ mod e3_requested; mod enclave_error; mod encryption_key_collection_failed; mod encryption_key_created; +mod encryption_key_pending; +mod encryption_key_received; mod evm_sync_events_received; mod keyshare_created; mod net_sync_events_received; @@ -28,6 +30,7 @@ mod operator_activation_changed; mod outgoing_sync_requested; mod plaintext_aggregated; mod plaintext_output_published; +mod proof; mod publickey_aggregated; mod publish_document; mod shutdown; @@ -60,6 +63,8 @@ use e3_utils::{colorize, Color}; pub use enclave_error::*; pub use encryption_key_collection_failed::*; pub use encryption_key_created::*; +pub use encryption_key_pending::*; +pub use encryption_key_received::*; pub use evm_sync_events_received::*; pub use keyshare_created::*; pub use net_sync_events_received::*; @@ -67,6 +72,7 @@ pub use operator_activation_changed::*; pub use outgoing_sync_requested::*; pub use plaintext_aggregated::*; pub use plaintext_output_published::*; +pub use proof::*; pub use publickey_aggregated::*; pub use publish_document::*; pub use shutdown::*; @@ -203,6 +209,8 @@ pub enum EnclaveEventData { Shutdown(Shutdown), DocumentReceived(DocumentReceived), ThresholdShareCreated(ThresholdShareCreated), + EncryptionKeyPending(EncryptionKeyPending), + EncryptionKeyReceived(EncryptionKeyReceived), EncryptionKeyCreated(EncryptionKeyCreated), EncryptionKeyCollectionFailed(EncryptionKeyCollectionFailed), ThresholdShareCollectionFailed(ThresholdShareCollectionFailed), @@ -413,6 +421,8 @@ impl EnclaveEventData { EnclaveEventData::PlaintextAggregated(ref data) => Some(data.e3_id.clone()), EnclaveEventData::CiphernodeSelected(ref data) => Some(data.e3_id.clone()), EnclaveEventData::ThresholdShareCreated(ref data) => Some(data.e3_id.clone()), + EnclaveEventData::EncryptionKeyPending(ref data) => Some(data.e3_id.clone()), + EnclaveEventData::EncryptionKeyReceived(ref data) => Some(data.e3_id.clone()), EnclaveEventData::CommitteePublished(ref data) => Some(data.e3_id.clone()), EnclaveEventData::CommitteeRequested(ref data) => Some(data.e3_id.clone()), EnclaveEventData::CommitteeFinalizeRequested(ref data) => Some(data.e3_id.clone()), @@ -477,6 +487,8 @@ impl_event_types!( TestEvent, DocumentReceived, ThresholdShareCreated, + EncryptionKeyPending, + EncryptionKeyReceived, EncryptionKeyCreated, EncryptionKeyCollectionFailed, ThresholdShareCollectionFailed, diff --git a/crates/events/src/enclave_event/proof.rs b/crates/events/src/enclave_event/proof.rs new file mode 100644 index 0000000000..ae74a9aa64 --- /dev/null +++ b/crates/events/src/enclave_event/proof.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: LGPL-3.0-only + +use derivative::Derivative; +use e3_utils::utility_types::ArcBytes; +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// A zero-knowledge proof with all data needed for verification. +#[derive(Derivative, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derivative(Debug)] +pub struct Proof { + /// Circuit that generated this proof. + pub circuit: CircuitName, + /// The proof bytes. + #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] + pub data: ArcBytes, + /// Public signals from the circuit (inputs and outputs). + #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] + pub public_signals: ArcBytes, +} + +impl Proof { + pub fn new( + circuit: CircuitName, + data: impl Into, + public_signals: impl Into, + ) -> Self { + Self { + circuit, + data: data.into(), + public_signals: public_signals.into(), + } + } +} + +/// Circuit identifiers for ZK proofs. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum CircuitName { + /// BFV public key proof (T0). + PkBfv, + /// TrBFV public key share proof (T1). + PkTrbfv, + /// Encrypted shares proof (T2/T3). + EncShares, + /// Decryption share proof (T4/T5). + DecShares, + /// Public key aggregation proof (T6). + PkAgg, +} + +impl CircuitName { + pub fn as_str(&self) -> &'static str { + match self { + CircuitName::PkBfv => "pk", + CircuitName::PkTrbfv => "pk_trbfv", + CircuitName::EncShares => "enc_shares", + CircuitName::DecShares => "dec_shares", + CircuitName::PkAgg => "pk_agg", + } + } + + pub fn group(&self) -> &'static str { + match self { + CircuitName::PkBfv => "dkg", + CircuitName::PkTrbfv => "threshold", + CircuitName::EncShares => "threshold", + CircuitName::DecShares => "threshold", + CircuitName::PkAgg => "threshold", + } + } + + pub fn dir_path(&self) -> String { + format!("{}/{}", self.group(), self.as_str()) + } +} + +impl fmt::Display for CircuitName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} diff --git a/crates/evm/src/evm_chain_gateway.rs b/crates/evm/src/evm_chain_gateway.rs index d730c1585f..fa8eadf72c 100644 --- a/crates/evm/src/evm_chain_gateway.rs +++ b/crates/evm/src/evm_chain_gateway.rs @@ -198,7 +198,7 @@ impl Handler for EvmChainGateway { impl Handler for EvmChainGateway { type Result = (); - fn handle(&mut self, msg: EnclaveEvmEvent, ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: EnclaveEvmEvent, _ctx: &mut Self::Context) -> Self::Result { trap(EType::Evm, &self.bus.clone(), || self.handle_evm_event(msg)) } } diff --git a/crates/evm/src/evm_hub.rs b/crates/evm/src/evm_hub.rs index 3c22437424..301b383f59 100644 --- a/crates/evm/src/evm_hub.rs +++ b/crates/evm/src/evm_hub.rs @@ -29,7 +29,7 @@ impl Actor for EvmHub { impl Handler for EvmHub { type Result = (); - fn handle(&mut self, msg: EnclaveEvmEvent, ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: EnclaveEvmEvent, _ctx: &mut Self::Context) -> Self::Result { let EnclaveEvmEvent::Log { .. } = msg.clone() else { return; }; diff --git a/crates/evm/src/evm_read_interface.rs b/crates/evm/src/evm_read_interface.rs index ece45967f2..fd37247026 100644 --- a/crates/evm/src/evm_read_interface.rs +++ b/crates/evm/src/evm_read_interface.rs @@ -19,7 +19,6 @@ use e3_events::{BusHandle, CorrelationId, ErrorDispatcher, Event, EventSubscribe use e3_events::{EType, EnclaveEvent, EnclaveEventData, EventId}; use futures_util::stream::StreamExt; use std::collections::{HashMap, HashSet}; -use std::time::{SystemTime, UNIX_EPOCH}; use tokio::select; use tokio::sync::oneshot; use tracing::{error, info, instrument, warn}; diff --git a/crates/evm/src/evm_router.rs b/crates/evm/src/evm_router.rs index 790cb78487..01c963de5a 100644 --- a/crates/evm/src/evm_router.rs +++ b/crates/evm/src/evm_router.rs @@ -5,7 +5,7 @@ // or FITNESS FOR A PARTICULAR PURPOSE. use crate::events::{EnclaveEvmEvent, EvmEventProcessor, EvmLog}; -use actix::{Actor, Addr, Handler}; +use actix::{Actor, Handler}; use alloy_primitives::Address; use std::collections::HashMap; use tracing::{debug, error, info}; @@ -46,7 +46,7 @@ impl Actor for EvmRouter { impl Handler for EvmRouter { type Result = (); - fn handle(&mut self, msg: EnclaveEvmEvent, ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: EnclaveEvmEvent, _ctx: &mut Self::Context) -> Self::Result { match msg.clone() { // Take all log events and route them EnclaveEvmEvent::Log(EvmLog { log, .. }) => { diff --git a/crates/evm/src/sync_start_extractor.rs b/crates/evm/src/sync_start_extractor.rs index 5cd2a52ee3..7aa25b20bd 100644 --- a/crates/evm/src/sync_start_extractor.rs +++ b/crates/evm/src/sync_start_extractor.rs @@ -26,7 +26,7 @@ impl Actor for SyncStartExtractor { impl Handler for SyncStartExtractor { type Result = (); - fn handle(&mut self, msg: EnclaveEvent, ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: EnclaveEvent, _ctx: &mut Self::Context) -> Self::Result { if let EnclaveEventData::SyncStart(evt) = msg.into_data() { self.dest.do_send(evt) } diff --git a/crates/fhe-params/Cargo.toml b/crates/fhe-params/Cargo.toml index b5fe6bf513..b4dd190400 100644 --- a/crates/fhe-params/Cargo.toml +++ b/crates/fhe-params/Cargo.toml @@ -15,6 +15,7 @@ clap = { workspace = true } anyhow = { workspace = true } alloy-dyn-abi = { workspace = true, optional = true } alloy-primitives = { workspace = true, optional = true } +serde = { workspace = true } [[bin]] name = "search_params" diff --git a/crates/fhe-params/src/presets.rs b/crates/fhe-params/src/presets.rs index 1d6c9cb16c..54f2e7b7d6 100644 --- a/crates/fhe-params/src/presets.rs +++ b/crates/fhe-params/src/presets.rs @@ -13,6 +13,7 @@ use crate::constants::{ search_defaults::{B, B_CHI, SEARCH_K, SEARCH_N, SEARCH_Z}, secure_8192, }; +use serde::{Deserialize, Serialize}; use std::sync::Arc; use thiserror::Error as ThisError; @@ -30,7 +31,7 @@ use fhe::bfv::BfvParameters; /// generates a standard (non-threshold) BFV key-pair using these parameters. These keys are /// used exclusively for encrypting secret shares during DKG, since the threshold public key /// doesn't exist yet. After DKG completes, these keys are no longer needed. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub enum BfvPreset { /// Insecure threshold BFV parameters (degree 512) - DO NOT USE IN PRODUCTION /// @@ -310,6 +311,19 @@ impl BfvPreset { } } + /// Returns the threshold preset that pairs with this DKG preset. + /// + /// Used when you have a DKG preset (e.g. for share encryption during key generation) and need + /// the corresponding threshold parameters (e.g. for encryption/decryption). + /// Returns `None` when called on a threshold preset. + pub fn threshold_counterpart(self) -> Option { + match self { + BfvPreset::InsecureDkg512 => Some(BfvPreset::InsecureThreshold512), + BfvPreset::SecureDkg8192 => Some(BfvPreset::SecureThreshold8192), + BfvPreset::InsecureThreshold512 | BfvPreset::SecureThreshold8192 => None, + } + } + pub fn metadata(&self) -> PresetMetadata { match self { BfvPreset::InsecureThreshold512 => PresetMetadata { diff --git a/crates/indexer/tests/integration.rs b/crates/indexer/tests/integration.rs index bd6d1be158..4b26ec4efe 100644 --- a/crates/indexer/tests/integration.rs +++ b/crates/indexer/tests/integration.rs @@ -11,8 +11,8 @@ use alloy::{ }; use e3_bfv_client::compute_pk_commitment; use e3_evm_helpers::contracts::ReadOnly; +use e3_fhe_params::build_bfv_params_from_set_arc; use e3_fhe_params::DEFAULT_BFV_PRESET; -use e3_fhe_params::{build_bfv_params_from_set_arc, BfvPreset}; use e3_indexer::{DataStore, EnclaveIndexer, InMemoryStore}; use eyre::Result; use fhe::bfv::{PublicKey, SecretKey}; diff --git a/crates/keyshare/Cargo.toml b/crates/keyshare/Cargo.toml index 46da25753f..fcd908ced0 100644 --- a/crates/keyshare/Cargo.toml +++ b/crates/keyshare/Cargo.toml @@ -16,6 +16,7 @@ e3-data = { workspace = true } e3-crypto = { workspace = true } e3-events = { workspace = true } e3-fhe = { workspace = true } +e3-fhe-params = { workspace = true } e3-multithread = { workspace = true } e3-request = { workspace = true } e3-trbfv = { workspace = true } diff --git a/crates/keyshare/src/ext.rs b/crates/keyshare/src/ext.rs index 11ff88e46d..e3cb47105e 100644 --- a/crates/keyshare/src/ext.rs +++ b/crates/keyshare/src/ext.rs @@ -14,16 +14,16 @@ use async_trait::async_trait; use e3_crypto::Cipher; use e3_data::{AutoPersist, RepositoriesFactory}; use e3_events::{prelude::*, BusHandle, EType, EnclaveEvent, EnclaveEventData}; +use e3_fhe_params::BfvPreset; use e3_request::{E3Context, E3ContextSnapshot, E3Extension, META_KEY}; -use std::sync::Arc; use crate::KeyshareState; - +use std::sync::Arc; pub struct ThresholdKeyshareExtension { bus: BusHandle, cipher: Arc, address: String, - share_encryption_params: Arc, + share_enc_preset: BfvPreset, } impl ThresholdKeyshareExtension { @@ -31,13 +31,13 @@ impl ThresholdKeyshareExtension { bus: &BusHandle, cipher: &Arc, address: &str, - share_encryption_params: Arc, + share_enc_preset: BfvPreset, ) -> Box { Box::new(Self { bus: bus.clone(), cipher: cipher.to_owned(), address: address.to_owned(), - share_encryption_params, + share_enc_preset, }) } } @@ -79,7 +79,7 @@ impl E3Extension for ThresholdKeyshareExtension { bus: self.bus.clone(), cipher: self.cipher.clone(), state: container, - share_encryption_params: self.share_encryption_params.clone(), + share_enc_preset: self.share_enc_preset, }) .start() .into(), @@ -109,7 +109,7 @@ impl E3Extension for ThresholdKeyshareExtension { bus: self.bus.clone(), cipher: self.cipher.clone(), state, - share_encryption_params: self.share_encryption_params.clone(), + share_enc_preset: self.share_enc_preset, }) .start() .into(); diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index 92e4ba1e8d..84b29045b0 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -5,31 +5,31 @@ // or FITNESS FOR A PARTICULAR PURPOSE. use actix::prelude::*; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use e3_crypto::{Cipher, SensitiveBytes}; use e3_data::Persistable; use e3_events::{ prelude::*, trap, BusHandle, CiphernodeSelected, CiphertextOutputPublished, ComputeRequest, - ComputeResponse, CorrelationId, DecryptionshareCreated, Die, E3RequestComplete, E3id, EType, - EnclaveEvent, EnclaveEventData, EncryptionKey, EncryptionKeyCollectionFailed, - EncryptionKeyCreated, KeyshareCreated, PartyId, ThresholdShare, ThresholdShareCollectionFailed, - ThresholdShareCreated, TypedEvent, + ComputeResponse, ComputeResponseKind, CorrelationId, DecryptionshareCreated, Die, + E3RequestComplete, E3id, EType, EnclaveEvent, EnclaveEventData, EncryptionKey, + EncryptionKeyCollectionFailed, EncryptionKeyCreated, EncryptionKeyPending, KeyshareCreated, + PartyId, ThresholdShare, ThresholdShareCollectionFailed, ThresholdShareCreated, TypedEvent, }; use e3_fhe::create_crp; +use e3_fhe_params::{BfvParamSet, BfvPreset}; use e3_trbfv::{ - calculate_decryption_key::CalculateDecryptionKeyRequest, + calculate_decryption_key::{CalculateDecryptionKeyRequest, CalculateDecryptionKeyResponse}, calculate_decryption_share::{ CalculateDecryptionShareRequest, CalculateDecryptionShareResponse, }, gen_esi_sss::{GenEsiSssRequest, GenEsiSssResponse}, - gen_pk_share_and_sk_sss::GenPkShareAndSkSssRequest, + gen_pk_share_and_sk_sss::{GenPkShareAndSkSssRequest, GenPkShareAndSkSssResponse}, helpers::{deserialize_secret_key, serialize_secret_key}, shares::{BfvEncryptedShares, EncryptableVec, Encrypted, ShamirShare, SharedSecret}, TrBFVConfig, TrBFVRequest, TrBFVResponse, }; use e3_utils::NotifySync; use e3_utils::{to_ordered_vec, utility_types::ArcBytes}; -use fhe::bfv::BfvParameters; use fhe::bfv::{PublicKey, SecretKey}; use fhe_traits::{DeserializeParametrized, Serialize}; use rand::{rngs::OsRng, SeedableRng}; @@ -44,10 +44,6 @@ use tracing::{error, info, warn}; use crate::encryption_key_collector::{AllEncryptionKeysCollected, EncryptionKeyCollector}; use crate::threshold_share_collector::ThresholdShareCollector; -#[derive(Message, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] -#[rtype(result = "Result<()>")] -struct StartThresholdShareGeneration(CiphernodeSelected); - #[derive(Message, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] #[rtype(result = "()")] pub struct GenPkShareAndSkSss(CiphernodeSelected); @@ -56,10 +52,6 @@ pub struct GenPkShareAndSkSss(CiphernodeSelected); #[rtype(result = "()")] pub struct GenEsiSss(CiphernodeSelected); -#[derive(Message, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] -#[rtype(result = "Result<()>")] -struct SharesGenerated; - #[derive(Message)] #[rtype(result = "()")] pub struct AllThresholdSharesCollected { @@ -254,7 +246,7 @@ impl TryInto for ThresholdKeyshareState { fn try_into(self) -> std::result::Result { match self.state { KeyshareState::CollectingEncryptionKeys(s) => Ok(s), - _ => Err(anyhow!("Invalid state")), + _ => Err(anyhow!("Invalid state: expected CollectingEncryptionKeys")), } } } @@ -303,7 +295,7 @@ pub struct ThresholdKeyshareParams { pub bus: BusHandle, pub cipher: Arc, pub state: Persistable, - pub share_encryption_params: Arc, + pub share_enc_preset: BfvPreset, } pub struct ThresholdKeyshare { @@ -312,7 +304,7 @@ pub struct ThresholdKeyshare { decryption_key_collector: Option>, encryption_key_collector: Option>, state: Persistable, - share_encryption_params: Arc, + share_enc_preset: BfvPreset, } impl ThresholdKeyshare { @@ -323,7 +315,7 @@ impl ThresholdKeyshare { decryption_key_collector: None, encryption_key_collector: None, state: params.state, - share_encryption_params: params.share_encryption_params, + share_enc_preset: params.share_enc_preset, } } } @@ -409,32 +401,35 @@ impl ThresholdKeyshare { pub fn handle_compute_response(&mut self, msg: TypedEvent) -> Result<()> { match &msg.response { - TrBFVResponse::GenEsiSss(_) => self.handle_gen_esi_sss_response(msg), - TrBFVResponse::GenPkShareAndSkSss(_) => { - self.handle_gen_pk_share_and_sk_sss_response(msg) - } - TrBFVResponse::CalculateDecryptionKey(_) => { - self.handle_calculate_decryption_key_response(msg) - } - TrBFVResponse::CalculateDecryptionShare(_) => { - self.handle_calculate_decryption_share_response(msg) - } - _ => Ok(()), + ComputeResponseKind::TrBFV(trbfv) => match trbfv { + TrBFVResponse::GenEsiSss(_) => self.handle_gen_esi_sss_response(msg), + TrBFVResponse::GenPkShareAndSkSss(_) => { + self.handle_gen_pk_share_and_sk_sss_response(msg) + } + TrBFVResponse::CalculateDecryptionKey(_) => { + self.handle_calculate_decryption_key_response(msg) + } + TrBFVResponse::CalculateDecryptionShare(_) => { + self.handle_calculate_decryption_share_response(msg) + } + _ => Ok(()), + }, + ComputeResponseKind::Zk(_) => Ok(()), } } - /// 1. CiphernodeSelected - Generate BFV keys and start collecting + /// 1. CiphernodeSelected - Generate BFV keys and publish EncryptionKeyPending pub fn handle_ciphernode_selected( &mut self, msg: TypedEvent, address: Addr, ) -> Result<()> { info!("CiphernodeSelected received."); - // Ensure the collector is created + // Ensure the collectors are created let _ = self.ensure_collector(address.clone()); let _ = self.ensure_encryption_key_collector(address.clone()); - let params = self.share_encryption_params.clone(); + let params = BfvParamSet::from(self.share_enc_preset.clone()).build_arc(); let mut rng = OsRng; let sk_bfv = SecretKey::random(¶ms, &mut rng); let pk_bfv = PublicKey::new(&sk_bfv, &mut rng); @@ -443,6 +438,9 @@ impl ThresholdKeyshare { let sk_bfv_encrypted = SensitiveBytes::new(sk_bytes, &self.cipher)?; let pk_bfv_bytes = ArcBytes::from_bytes(&pk_bfv.to_bytes()); + let state = self.state.try_get()?; + let e3_id = state.e3_id.clone(); + self.state.try_mutate(|s| { s.new_state(KeyshareState::CollectingEncryptionKeys( CollectingEncryptionKeysData { @@ -453,14 +451,10 @@ impl ThresholdKeyshare { )) })?; - let state = self.state.try_get()?; - self.bus.publish(EncryptionKeyCreated { - e3_id: state.e3_id.clone(), - key: Arc::new(EncryptionKey { - party_id: state.party_id, - pk_bfv: pk_bfv_bytes, - }), - external: false, + self.bus.publish(EncryptionKeyPending { + e3_id, + key: Arc::new(EncryptionKey::new(state.party_id, pk_bfv_bytes)), + params_preset: self.share_enc_preset, })?; Ok(()) @@ -519,7 +513,7 @@ impl ThresholdKeyshare { let trbfv_config = state.get_trbfv_config(); - let event = ComputeRequest::new( + let event = ComputeRequest::trbfv( TrBFVRequest::GenEsiSss( GenEsiSssRequest { trbfv_config, @@ -603,7 +597,7 @@ impl ThresholdKeyshare { ) .to_bytes(), ); - let event = ComputeRequest::new( + let event = ComputeRequest::trbfv( TrBFVRequest::GenPkShareAndSkSss( GenPkShareAndSkSssRequest { trbfv_config, crp }.into(), ), @@ -620,9 +614,10 @@ impl ThresholdKeyshare { &mut self, res: TypedEvent, ) -> Result<()> { - let TrBFVResponse::GenPkShareAndSkSss(output) = res.into_inner().response else { - bail!("Error extracting data from compute process") - }; + let output: GenPkShareAndSkSssResponse = res + .into_inner() + .try_into() + .context("Error extracting data from compute process")?; let (pk_share, sk_sss) = (output.pk_share, output.sk_sss); @@ -686,7 +681,7 @@ impl ThresholdKeyshare { let encryption_keys = &collected_encryption_keys; // Convert to BFV public keys - let params = self.share_encryption_params.clone(); + let params = BfvParamSet::from(self.share_enc_preset.clone()).build_arc(); let recipient_pks: Vec = encryption_keys .iter() .map(|k| { @@ -762,7 +757,7 @@ impl ThresholdKeyshare { // Get our BFV secret key from state let current: AggregatingDecryptionKey = state.clone().try_into()?; let sk_bytes = current.sk_bfv.access(&cipher)?; - let params = self.share_encryption_params.clone(); + let params = BfvParamSet::from(self.share_enc_preset.clone()).build_arc(); let sk_bfv = deserialize_secret_key(&sk_bytes, ¶ms)?; let degree = params.degree(); @@ -808,7 +803,7 @@ impl ThresholdKeyshare { sk_sss_collected: sk_sss_collected.encrypt(&cipher)?, }; - let event = ComputeRequest::new( + let event = ComputeRequest::trbfv( TrBFVRequest::CalculateDecryptionKey(request), CorrelationId::new(), e3_id.clone(), @@ -823,9 +818,10 @@ impl ThresholdKeyshare { &mut self, res: TypedEvent, ) -> Result<()> { - let TrBFVResponse::CalculateDecryptionKey(output) = res.into_inner().response else { - bail!("Error extracting data from compute process") - }; + let output: CalculateDecryptionKeyResponse = res + .into_inner() + .try_into() + .context("Error extracting data from compute process")?; let (sk_poly_sum, es_poly_sum) = (output.sk_poly_sum, output.es_poly_sum); @@ -885,7 +881,7 @@ impl ThresholdKeyshare { let e3_id = state.get_e3_id(); let decrypting: Decrypting = state.clone().try_into()?; let trbfv_config = state.get_trbfv_config(); - let event = ComputeRequest::new( + let event = ComputeRequest::trbfv( TrBFVRequest::CalculateDecryptionShare( CalculateDecryptionShareRequest { name: format!("party_id({})", state.party_id), diff --git a/crates/multithread/Cargo.toml b/crates/multithread/Cargo.toml index ddc497ad7e..b231789b93 100644 --- a/crates/multithread/Cargo.toml +++ b/crates/multithread/Cargo.toml @@ -10,10 +10,15 @@ repository.workspace = true actix = { workspace = true } anyhow = { workspace = true } e3-data = { workspace = true } +e3-fhe-params = { workspace = true } e3-trbfv = { workspace = true } e3-crypto = { workspace = true } e3-events = { workspace = true } +e3-zk-helpers = { workspace = true } e3-utils = { workspace = true } +e3-zk-prover = { workspace = true } +fhe = { workspace = true } +fhe-traits = { workspace = true } rand = { workspace = true } rayon = { workspace = true } tokio = { workspace = true } diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 09bf9e8f29..45094b4e1a 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -17,24 +17,24 @@ use actix::prelude::*; use actix::{Actor, Handler}; use anyhow::Result; use e3_crypto::Cipher; -use e3_events::BusHandle; -use e3_events::ComputeRequestErrorKind; -use e3_events::EType; -use e3_events::EnclaveEvent; -use e3_events::EnclaveEventData; -use e3_events::ErrorDispatcher; -use e3_events::Event; -use e3_events::EventPublisher; -use e3_events::EventSubscriber; -use e3_events::{ComputeRequest, ComputeRequestError, ComputeResponse, EventType}; +use e3_events::{ + BusHandle, ComputeRequest, ComputeRequestError, ComputeRequestErrorKind, ComputeRequestKind, + ComputeResponse, EType, EnclaveEvent, EnclaveEventData, ErrorDispatcher, Event, EventPublisher, + EventSubscriber, EventType, PkBfvProofRequest, PkBfvProofResponse, ZkError as ZkEventError, + ZkRequest, ZkResponse, +}; +use e3_fhe_params::{BfvParamSet, BfvPreset}; use e3_trbfv::calculate_decryption_key::calculate_decryption_key; use e3_trbfv::calculate_decryption_share::calculate_decryption_share; use e3_trbfv::calculate_threshold_decryption::calculate_threshold_decryption; use e3_trbfv::gen_esi_sss::gen_esi_sss; use e3_trbfv::gen_pk_share_and_sk_sss::gen_pk_share_and_sk_sss; use e3_trbfv::{TrBFVError, TrBFVRequest, TrBFVResponse}; -use e3_utils::NotifySync; use e3_utils::SharedRng; +use e3_zk_helpers::circuits::dkg::pk::circuit::PkCircuit; +use e3_zk_prover::{Provable, ZkBackend, ZkProver}; +use fhe::bfv::PublicKey; +use fhe_traits::DeserializeParametrized; use rand::Rng; use tracing::error; use tracing::info; @@ -46,6 +46,7 @@ pub struct Multithread { cipher: Arc, task_pool: TaskPool, report: Option>, + zk_prover: Option>, } impl Multithread { @@ -62,9 +63,16 @@ impl Multithread { cipher, task_pool, report, + zk_prover: None, } } + /// Set the ZK prover for handling proof requests. + pub fn with_zk_prover(mut self, prover: Arc) -> Self { + self.zk_prover = Some(prover); + self + } + /// Subtract the given amount from the total number of available threads and return the result pub fn get_max_threads_minus(amount: usize) -> usize { let total_threads = thread::available_parallelism() @@ -86,6 +94,22 @@ impl Multithread { addr } + pub fn attach_with_zk( + bus: &BusHandle, + rng: SharedRng, + cipher: Arc, + task_pool: TaskPool, + report: Option>, + zk_backend: &ZkBackend, + ) -> Addr { + let zk_prover = Arc::new(ZkProver::new(zk_backend)); + let actor = Self::new(bus.clone(), rng.clone(), cipher.clone(), task_pool, report) + .with_zk_prover(zk_prover); + let addr = actor.start(); + bus.subscribe(EventType::ComputeRequest, addr.clone().recipient()); + addr + } + pub fn create_taskpool(threads: usize, max_tasks: usize) -> TaskPool { TaskPool::new(threads, max_tasks) } @@ -114,9 +138,11 @@ impl Handler for Multithread { let bus = self.bus.clone(); let pool = self.task_pool.clone(); let report = self.report.clone(); - // TODO: replace with trap_fut + let zk_prover = self.zk_prover.clone(); + Box::pin(async move { - match handle_compute_request_event(msg, bus, cipher, rng, pool, report).await { + match handle_compute_request_event(msg, bus, cipher, rng, pool, report, zk_prover).await + { Ok(_) => (), Err(e) => error!("{e}"), } @@ -131,20 +157,17 @@ async fn handle_compute_request_event( rng: SharedRng, pool: TaskPool, report: Option>, + zk_prover: Option>, ) -> anyhow::Result<()> { let msg_string = msg.to_string(); let job_name = msg_string.clone(); - // We spawn a thread on rayon moving to "sync"-land let (result, duration) = pool .spawn(job_name, TaskTimeouts::default(), move || { - // Do the actual work this is gonna take a while... - handle_compute_request(rng, cipher, msg) + handle_compute_request(rng, cipher, zk_prover, msg) }) .await?; - // we are back in async io land... - // incase we are collecting events for a report if let Some(report) = report { report.do_send(TrackDuration::new(msg_string, duration)) }; @@ -164,29 +187,45 @@ fn timefunc( where F: FnOnce() -> Result, { - info!("\nSTARTING MULTITHREAD `{}({})`\n", name, id); + info!("STARTING MULTITHREAD `{}({})`", name, id); let start = Instant::now(); let out = func(); let dur = start.elapsed(); - info!("\nFINISHED MULTITHREAD `{}`({}) in {:?}\n", name, id, dur); - (out, dur) // return output as well as timing info + info!("FINISHED MULTITHREAD `{}`({}) in {:?}", name, id, dur); + (out, dur) } -/// Handle our compute request. This function is run on a rayon threadpool. +/// Handle compute request. This function is run on a rayon threadpool. fn handle_compute_request( rng: SharedRng, cipher: Arc, + zk_prover: Option>, request: ComputeRequest, ) -> (Result, Duration) { let id: u8 = rand::thread_rng().gen(); match request.request.clone() { + ComputeRequestKind::TrBFV(trbfv_req) => { + handle_trbfv_request(rng, cipher, trbfv_req, request, id) + } + ComputeRequestKind::Zk(zk_req) => handle_zk_request(zk_prover, zk_req, request, id), + } +} + +fn handle_trbfv_request( + rng: SharedRng, + cipher: Arc, + trbfv_req: TrBFVRequest, + request: ComputeRequest, + id: u8, +) -> (Result, Duration) { + match trbfv_req { TrBFVRequest::GenPkShareAndSkSss(req) => { timefunc( "gen_pk_share_and_sk_sss", id, || match gen_pk_share_and_sk_sss(&rng, &cipher, req) { - Ok(o) => Ok(ComputeResponse::new( + Ok(o) => Ok(ComputeResponse::trbfv( TrBFVResponse::GenPkShareAndSkSss(o), request.correlation_id, request.e3_id, @@ -202,7 +241,7 @@ fn handle_compute_request( } TrBFVRequest::GenEsiSss(req) => timefunc("gen_esi_sss", id, || { match gen_esi_sss(&rng, &cipher, req) { - Ok(o) => Ok(ComputeResponse::new( + Ok(o) => Ok(ComputeResponse::trbfv( TrBFVResponse::GenEsiSss(o), request.correlation_id, request.e3_id, @@ -217,7 +256,7 @@ fn handle_compute_request( "calculate_decryption_key", id, || match calculate_decryption_key(&cipher, req) { - Ok(o) => Ok(ComputeResponse::new( + Ok(o) => Ok(ComputeResponse::trbfv( TrBFVResponse::CalculateDecryptionKey(o), request.correlation_id, request.e3_id, @@ -237,7 +276,7 @@ fn handle_compute_request( "calculate_decryption_share", id, || match calculate_decryption_share(&cipher, req) { - Ok(o) => Ok(ComputeResponse::new( + Ok(o) => Ok(ComputeResponse::trbfv( TrBFVResponse::CalculateDecryptionShare(o), request.correlation_id, request.e3_id, @@ -254,7 +293,7 @@ fn handle_compute_request( "calculate_threshold_decryption", id, || match calculate_threshold_decryption(req) { - Ok(o) => Ok(ComputeResponse::new( + Ok(o) => Ok(ComputeResponse::trbfv( TrBFVResponse::CalculateThresholdDecryption(o), request.correlation_id, request.e3_id, @@ -269,3 +308,70 @@ fn handle_compute_request( ), } } + +fn handle_zk_request( + zk_prover: Option>, + zk_req: ZkRequest, + request: ComputeRequest, + id: u8, +) -> (Result, Duration) { + let Some(prover) = zk_prover else { + return ( + Err(ComputeRequestError::new( + ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed( + "ZK prover not configured".to_string(), + )), + request, + )), + Duration::ZERO, + ); + }; + + match zk_req { + ZkRequest::PkBfv(req) => timefunc("zk_pk_bfv", id, || { + handle_pk_bfv_proof(&prover, req, request.clone()) + }), + } +} + +fn handle_pk_bfv_proof( + prover: &ZkProver, + req: PkBfvProofRequest, + request: ComputeRequest, +) -> Result { + // NOTE: req.params_preset is expected to contain a DKG preset (e.g., InsecureDkg512) + // because the proof is for the DKG circuit. This preset is converted to BFV parameters. + let params = BfvParamSet::from(req.params_preset.clone()).build_arc(); + let pk_bfv = PublicKey::from_bytes(&req.pk_bfv, ¶ms).map_err(|e| { + ComputeRequestError::new( + ComputeRequestErrorKind::Zk(ZkEventError::InvalidParams(format!( + "Failed to deserialize pk_bfv: {:?}", + e + ))), + request.clone(), + ) + })?; + + let circuit = PkCircuit; + let e3_id_str = request.e3_id.to_string(); + let preset_counterpart = req + .params_preset + .threshold_counterpart() + .unwrap_or_else(|| BfvPreset::InsecureThreshold512); + // But here we have to pass the InsecureThreshold512 preset because the underlaying witness generator + // builds both params, but will only use the DKG one + let proof = circuit + .prove(prover, &preset_counterpart, &pk_bfv, &e3_id_str) + .map_err(|e| { + ComputeRequestError::new( + ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), + request.clone(), + ) + })?; + + Ok(ComputeResponse::zk( + ZkResponse::PkBfv(PkBfvProofResponse::new(proof)), + request.correlation_id, + request.e3_id, + )) +} diff --git a/crates/net/src/document_publisher.rs b/crates/net/src/document_publisher.rs index 7db3f39c29..44e29556cf 100644 --- a/crates/net/src/document_publisher.rs +++ b/crates/net/src/document_publisher.rs @@ -16,8 +16,8 @@ use chrono::{DateTime, Utc}; use e3_events::{ prelude::*, BusHandle, CiphernodeSelected, CorrelationId, DocumentKind, DocumentMeta, DocumentReceived, E3RequestComplete, E3id, EType, EnclaveEvent, EnclaveEventData, - EncryptionKeyCreated, Event, EventType, Filter, PartyId, PublishDocumentRequested, - ThresholdShareCreated, + EncryptionKeyCreated, EncryptionKeyReceived, Event, EventType, Filter, PartyId, + PublishDocumentRequested, ThresholdShareCreated, }; use e3_utils::retry::{retry_with_backoff, to_retry}; use e3_utils::ArcBytes; @@ -490,8 +490,7 @@ impl EventConverter { "Received EncryptionKeyCreated from party {}", evt.key.party_id ); - self.bus.publish(EncryptionKeyCreated { - external: true, + self.bus.publish(EncryptionKeyReceived { e3_id: evt.e3_id, key: evt.key, })?; diff --git a/crates/sync/Cargo.toml b/crates/sync/Cargo.toml index 97ba4b7fea..147d026610 100644 --- a/crates/sync/Cargo.toml +++ b/crates/sync/Cargo.toml @@ -14,4 +14,4 @@ tokio.workspace = true tracing.workspace = true [dev-dependencies] -e3-ciphernode-builder.workspace = true +e3-ciphernode-builder.workspace = true \ No newline at end of file diff --git a/crates/sync/src/sync.rs b/crates/sync/src/sync.rs index 27fc8efff0..dd2736ae6d 100644 --- a/crates/sync/src/sync.rs +++ b/crates/sync/src/sync.rs @@ -81,7 +81,7 @@ impl Actor for Synchronizer { impl Handler for Synchronizer { type Result = (); - fn handle(&mut self, msg: SyncEvmEvent, ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: SyncEvmEvent, _ctx: &mut Self::Context) -> Self::Result { trap(EType::Sync, &self.bus.clone(), || { match msg { // Buffer events as the sync actor receives them @@ -118,11 +118,11 @@ pub struct Bootstrap; mod tests { use super::*; use e3_ciphernode_builder::EventSystem; + use e3_events::EnclaveEvent; use e3_events::{ CorrelationId, EnclaveEventData, Event, EvmEventConfig, EvmEventConfigChain, GetEvents, TestEvent, }; - use e3_events::{EnclaveEvent, EventContextAccessors}; use std::time::Duration; use tokio::time::sleep; diff --git a/crates/test-helpers/src/usecase_helpers.rs b/crates/test-helpers/src/usecase_helpers.rs index e1d1506df0..6f1460b384 100644 --- a/crates/test-helpers/src/usecase_helpers.rs +++ b/crates/test-helpers/src/usecase_helpers.rs @@ -8,6 +8,7 @@ use std::{collections::HashMap, sync::Arc}; use anyhow::{Context, Result}; use e3_crypto::{Cipher, SensitiveBytes}; use e3_events::ThresholdShare; +use e3_fhe_params::{BfvParamSet, BfvPreset}; use e3_trbfv::{ calculate_decryption_key::{ calculate_decryption_key, CalculateDecryptionKeyRequest, CalculateDecryptionKeyResponse, @@ -16,7 +17,6 @@ use e3_trbfv::{ gen_pk_share_and_sk_sss::{ gen_pk_share_and_sk_sss, GenPkShareAndSkSssRequest, GenPkShareAndSkSssResponse, }, - helpers::get_share_encryption_params, shares::{BfvEncryptedShares, EncryptableVec, ShamirShare, SharedSecret}, TrBFVConfig, }; @@ -48,7 +48,7 @@ pub fn generate_shares_hash_map( let threshold_n = trbfv_config.num_parties() as usize; // First, generate BFV encryption keys for all parties - let bfv_params = get_share_encryption_params(); + let bfv_params = BfvParamSet::from(BfvPreset::InsecureDkg512).build_arc(); let mut bfv_rng = OsRng; let mut bfv_secret_keys = Vec::with_capacity(threshold_n); let mut bfv_public_keys = Vec::with_capacity(threshold_n); @@ -144,7 +144,7 @@ pub fn get_decryption_keys( trbfv_config: &TrBFVConfig, ) -> Result, SensitiveBytes)>> { let threshold_n = trbfv_config.num_parties() as usize; - let bfv_params = get_share_encryption_params(); + let bfv_params = BfvParamSet::from(BfvPreset::InsecureDkg512).build_arc(); let degree = bfv_params.degree(); // Individualize based on node - each party decrypts their share from each sender diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index 25ed475c3f..7813a55428 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -707,15 +707,13 @@ async fn test_stopped_keyshares_retain_state() -> Result<()> { use e3_events::{EventBus, EventBusConfig, GetEvents, Shutdown, TakeEvents}; use e3_test_helpers::{create_random_eth_addrs, get_common_setup, simulate_libp2p_net}; use fhe::{ - bfv::{PublicKey, SecretKey}, + bfv::PublicKey, mbfv::{AggregateIter, PublicKeyShare}, }; use fhe_traits::Serialize; use std::time::Duration; use tokio::time::sleep; - type PkSkShareTuple = (PublicKeyShare, SecretKey, String); - async fn setup_local_ciphernode( bus: &BusHandle, rng: &e3_utils::SharedRng, diff --git a/crates/trbfv/src/helpers.rs b/crates/trbfv/src/helpers.rs index 1a78fdd09d..a36ef9c1cf 100644 --- a/crates/trbfv/src/helpers.rs +++ b/crates/trbfv/src/helpers.rs @@ -7,7 +7,6 @@ use crate::shares::ShamirShare; use anyhow::Result; use e3_crypto::{Cipher, SensitiveBytes}; -use e3_fhe_params::{BfvParamSet, DEFAULT_BFV_PRESET}; use fhe::mbfv::PublicKeyShare; use fhe::{ bfv::{self, BfvParameters, SecretKey}, @@ -43,17 +42,6 @@ pub fn deserialize_secret_key(bytes: &[u8], params: &Arc) -> Resu Ok(SecretKey::new(data.coeffs.to_vec(), params)) } -/// TODO: Make this modular -/// Returns DKG BFV parameters (used for share encryption during key generation), -/// matching the security level of the default threshold preset. -pub fn get_share_encryption_params() -> Arc { - let dkg_preset = DEFAULT_BFV_PRESET - .dkg_counterpart() - .expect("default threshold preset has DKG counterpart"); - let param_set: BfvParamSet = dkg_preset.into(); - param_set.build_arc() -} - pub fn try_poly_from_bytes(bytes: &[u8], params: &BfvParameters) -> Result { Ok(Poly::from_bytes(bytes, params.ctx_at_level(0)?)?) } diff --git a/crates/trbfv/src/shares/bfv_encrypted.rs b/crates/trbfv/src/shares/bfv_encrypted.rs index 53cc78b65d..3eed984313 100644 --- a/crates/trbfv/src/shares/bfv_encrypted.rs +++ b/crates/trbfv/src/shares/bfv_encrypted.rs @@ -21,9 +21,7 @@ use std::sync::Arc; use super::{ShamirShare, SharedSecret}; // Re-export helper functions from helpers module -pub use crate::helpers::{ - deserialize_secret_key, get_share_encryption_params, serialize_secret_key, -}; +pub use crate::helpers::{deserialize_secret_key, serialize_secret_key}; /// A BFV-encrypted Shamir share for secure transmission. /// @@ -212,11 +210,12 @@ impl Default for BfvEncryptedShares { #[cfg(test)] mod tests { use super::*; + use e3_fhe_params::{BfvParamSet, BfvPreset}; use rand::rngs::OsRng; #[test] fn test_encrypt_decrypt_share() { - let params = get_share_encryption_params(); + let params = BfvParamSet::from(BfvPreset::InsecureDkg512).build_arc(); let mut rng = OsRng; // Generate key pair @@ -247,7 +246,7 @@ mod tests { #[test] fn test_secret_key_serialization() { - let params = get_share_encryption_params(); + let params = BfvParamSet::from(BfvPreset::InsecureDkg512).build_arc(); let mut rng = OsRng; // Generate a secret key diff --git a/crates/trbfv/tests/integration.rs b/crates/trbfv/tests/integration.rs index 45d73261f8..72fb33d71f 100644 --- a/crates/trbfv/tests/integration.rs +++ b/crates/trbfv/tests/integration.rs @@ -13,7 +13,7 @@ use e3_bfv_client::decode_bytes_to_vec_u64; use e3_crypto::Cipher; use e3_fhe::create_crp; use e3_fhe_params::DEFAULT_BFV_PRESET; -use e3_fhe_params::{encode_bfv_params, BfvParamSet, BfvPreset}; +use e3_fhe_params::{encode_bfv_params, BfvParamSet}; use e3_test_helpers::{create_seed_from_u64, create_shared_rng_from_u64, usecase_helpers}; use e3_trbfv::{ calculate_decryption_share::{ diff --git a/crates/zk-helpers/Cargo.toml b/crates/zk-helpers/Cargo.toml index 6624dc343c..741732d526 100644 --- a/crates/zk-helpers/Cargo.toml +++ b/crates/zk-helpers/Cargo.toml @@ -24,7 +24,7 @@ rand = { workspace = true } rayon = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -toml = "0.8.23" +toml = { workspace = true } itertools = "0.14.0" ndarray = { workspace = true } e3-parity-matrix = { workspace = true } diff --git a/crates/zk-prover/Cargo.toml b/crates/zk-prover/Cargo.toml new file mode 100644 index 0000000000..31f2967f31 --- /dev/null +++ b/crates/zk-prover/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "e3-zk-prover" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "ZK proof generation for Enclave ciphernodes" +repository.workspace = true + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +actix.workspace = true +tokio = { workspace = true, features = ["process", "fs"] } +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +reqwest = { workspace = true, features = ["json", "stream"] } +tempfile.workspace = true +tracing.workspace = true +directories = "5" +flate2 = "1" +tar = "0.4" +toml.workspace = true +sha2.workspace = true +hex.workspace = true +futures-util.workspace = true +indicatif = "0.17" +thiserror.workspace = true +walkdir = "2.5" +chrono = { workspace = true } +# Noir ACVM crates for native witness generation +acir = { git = "https://github.com/noir-lang/noir", tag = "v1.0.0-beta.15" } +acvm = { git = "https://github.com/noir-lang/noir", tag = "v1.0.0-beta.15" } +bn254_blackbox_solver = { git = "https://github.com/noir-lang/noir", tag = "v1.0.0-beta.15" } +noirc_abi = { git = "https://github.com/noir-lang/noir", tag = "v1.0.0-beta.15" } +nargo = { git = "https://github.com/noir-lang/noir", tag = "v1.0.0-beta.15" } +# Base64 decoding +base64 = "0.22" +bincode = "1.3.3" +fhe.workspace = true +e3-fhe-params.workspace = true +e3-polynomial.workspace = true +num-bigint.workspace = true +e3-zk-helpers.workspace = true +e3-request.workspace = true +e3-events.workspace = true +e3-data.workspace = true +e3-utils.workspace = true + +[dev-dependencies] +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } + +[features] +default = [] +integration-tests = [] diff --git a/crates/zk-prover/src/actors/mod.rs b/crates/zk-prover/src/actors/mod.rs new file mode 100644 index 0000000000..e3d8ba210e --- /dev/null +++ b/crates/zk-prover/src/actors/mod.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Actor-based components for ZK proof generation and verification. +//! +//! ## Architecture +//! +//! This module follows a clean separation between core business logic and IO operations: +//! +//! ### Core Actors (Business Logic - No IO) +//! - [`ProofRequestActor`]: Converts `EncryptionKeyPending` → `ComputeRequest` and handles responses +//! - [`ProofVerificationActor`]: Verifies `EncryptionKeyReceived` and converts to `EncryptionKeyCreated` +//! +//! ### IO Actors (File System Operations) +//! - [`ZkActor`]: Performs actual proof generation/verification using disk-based circuits and bb binary +//! +//! ## Usage +//! +//! ```rust,ignore +//! use e3_zk_prover::{ZkBackend, setup_zk_actors}; +//! use e3_events::BusHandle; +//! +//! let bus = BusHandle::default(); +//! let backend = ZkBackend::with_default_dir().await?; +//! +//! // Setup all actors with proper separation of concerns +//! setup_zk_actors(&bus, Some(&backend)); +//! ``` + +pub mod proof_request; +pub mod proof_verification; +pub mod zk_actor; + +pub use proof_request::ProofRequestActor; +pub use proof_verification::{ + ProofVerificationActor, ZkVerificationRequest, ZkVerificationResponse, +}; +pub use zk_actor::ZkActor; + +use actix::{Actor, Addr}; +use e3_events::BusHandle; + +use crate::ZkBackend; + +/// Setup all ZK-related actors with proper separation of concerns. +/// +/// When `backend` is provided: +/// - Creates IO actor (ZkActor) for proof generation/verification +/// - Creates core actors that delegate to IO actor +/// +/// When `backend` is None: +/// - Creates core actors without verification capabilities +/// - Proofs are disabled, keys are accepted without verification +pub fn setup_zk_actors(bus: &BusHandle, backend: Option<&ZkBackend>) -> ZkActors { + let (zk_actor, verifier) = if let Some(backend) = backend { + let zk_actor = ZkActor::new(backend).start(); + let verifier = Some(zk_actor.clone().recipient()); + (Some(zk_actor), verifier) + } else { + (None, None) + }; + + let proof_request = ProofRequestActor::setup(bus, backend.is_some()); + let proof_verification = ProofVerificationActor::setup(bus, verifier); + + ZkActors { + zk_actor, + proof_request, + proof_verification, + } +} + +/// Container for all ZK-related actor addresses. +pub struct ZkActors { + pub zk_actor: Option>, + pub proof_request: Addr, + pub proof_verification: Addr, +} diff --git a/crates/zk-prover/src/actors/proof_request.rs b/crates/zk-prover/src/actors/proof_request.rs new file mode 100644 index 0000000000..643dfc0162 --- /dev/null +++ b/crates/zk-prover/src/actors/proof_request.rs @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use std::collections::HashMap; +use std::sync::Arc; + +use actix::{Actor, Addr, Context, Handler}; +use e3_events::{ + BusHandle, ComputeRequest, ComputeRequestError, ComputeRequestErrorKind, ComputeResponse, + ComputeResponseKind, CorrelationId, E3id, EnclaveEvent, EnclaveEventData, EncryptionKey, + EncryptionKeyCreated, EncryptionKeyPending, Event, EventPublisher, EventSubscriber, EventType, + PkBfvProofRequest, ZkRequest, ZkResponse, +}; +use e3_utils::NotifySync; +use tracing::{error, info, warn}; + +#[derive(Clone, Debug)] +struct PendingProofRequest { + e3_id: E3id, + key: Arc, +} + +/// Core actor that handles encryption key proof requests. +pub struct ProofRequestActor { + bus: BusHandle, + proofs_enabled: bool, + pending: HashMap, +} + +impl ProofRequestActor { + pub fn new(bus: &BusHandle, proofs_enabled: bool) -> Self { + Self { + bus: bus.clone(), + proofs_enabled, + pending: HashMap::new(), + } + } + + pub fn setup(bus: &BusHandle, proofs_enabled: bool) -> Addr { + let addr = Self::new(bus, proofs_enabled).start(); + bus.subscribe(EventType::EncryptionKeyPending, addr.clone().into()); + bus.subscribe(EventType::ComputeResponse, addr.clone().into()); + bus.subscribe(EventType::ComputeRequestError, addr.clone().into()); + addr + } + + fn handle_encryption_key_pending(&mut self, msg: EncryptionKeyPending) { + if !self.proofs_enabled { + info!( + "ZK proofs disabled; publishing EncryptionKeyCreated without proof for party {}", + msg.key.party_id + ); + if let Err(err) = self.bus.publish(EncryptionKeyCreated { + e3_id: msg.e3_id, + key: msg.key, + external: false, + }) { + error!("Failed to publish EncryptionKeyCreated: {err}"); + } + return; + } + + let correlation_id = CorrelationId::new(); + self.pending.insert( + correlation_id, + PendingProofRequest { + e3_id: msg.e3_id.clone(), + key: msg.key.clone(), + }, + ); + + let request = ComputeRequest::zk( + ZkRequest::PkBfv(PkBfvProofRequest::new( + msg.key.pk_bfv.clone(), + msg.params_preset, + )), + correlation_id, + msg.e3_id, + ); + + info!("Requesting T0 proof generation"); + if let Err(err) = self.bus.publish(request) { + error!("Failed to publish ZK proof request: {err}"); + self.pending.remove(&correlation_id); + } + } + + fn handle_compute_response(&mut self, msg: ComputeResponse) { + let ComputeResponseKind::Zk(ZkResponse::PkBfv(resp)) = msg.response else { + return; + }; + + let Some(pending) = self.pending.remove(&msg.correlation_id) else { + return; + }; + + let mut key = (*pending.key).clone(); + key.proof = Some(resp.proof); + + if let Err(err) = self.bus.publish(EncryptionKeyCreated { + e3_id: pending.e3_id, + key: Arc::new(key), + external: false, + }) { + error!("Failed to publish EncryptionKeyCreated: {err}"); + } + } + + fn handle_compute_request_error(&mut self, msg: ComputeRequestError) { + let ComputeRequestErrorKind::Zk(err) = msg.get_err() else { + return; + }; + + if let Some(pending) = self.pending.remove(msg.correlation_id()) { + warn!("ZK proof request failed for E3 {}: {err}", pending.e3_id); + + // Publish EncryptionKeyCreated without proof to allow the system to continue + // Applications can check the has_proof field to determine if validation is required + if let Err(err) = self.bus.publish(EncryptionKeyCreated { + e3_id: pending.e3_id, + key: pending.key, + external: false, + }) { + error!("Failed to publish EncryptionKeyCreated after ZK proof failure: {err}"); + } + } + } +} + +impl Actor for ProofRequestActor { + type Context = Context; +} + +impl Handler for ProofRequestActor { + type Result = (); + + fn handle(&mut self, msg: EnclaveEvent, ctx: &mut Self::Context) -> Self::Result { + match msg.into_data() { + EnclaveEventData::EncryptionKeyPending(data) => self.notify_sync(ctx, data), + EnclaveEventData::ComputeResponse(data) => self.notify_sync(ctx, data), + EnclaveEventData::ComputeRequestError(data) => self.notify_sync(ctx, data), + _ => (), + } + } +} + +impl Handler for ProofRequestActor { + type Result = (); + + fn handle(&mut self, msg: EncryptionKeyPending, _ctx: &mut Self::Context) -> Self::Result { + self.handle_encryption_key_pending(msg) + } +} + +impl Handler for ProofRequestActor { + type Result = (); + + fn handle(&mut self, msg: ComputeResponse, _ctx: &mut Self::Context) -> Self::Result { + self.handle_compute_response(msg) + } +} + +impl Handler for ProofRequestActor { + type Result = (); + + fn handle(&mut self, msg: ComputeRequestError, _ctx: &mut Self::Context) -> Self::Result { + self.handle_compute_request_error(msg) + } +} diff --git a/crates/zk-prover/src/actors/proof_verification.rs b/crates/zk-prover/src/actors/proof_verification.rs new file mode 100644 index 0000000000..b6fe432b70 --- /dev/null +++ b/crates/zk-prover/src/actors/proof_verification.rs @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Core business logic actor for verifying received encryption keys. +//! This actor verifies EncryptionKeyReceived events and converts them +//! to EncryptionKeyCreated events after validation. +//! +//! This is a CORE actor - it delegates IO operations (verification) to ZkActor. + +use std::sync::Arc; + +use actix::{Actor, Addr, AsyncContext, Context, Handler, Message, Recipient}; +use e3_events::{ + BusHandle, E3id, EnclaveEvent, EnclaveEventData, EncryptionKey, EncryptionKeyCreated, + EncryptionKeyReceived, Event, EventPublisher, EventSubscriber, EventType, Proof, +}; +use e3_utils::NotifySync; +use tracing::{error, info, warn}; + +/// Request to verify a ZK proof. +#[derive(Debug, Message)] +#[rtype(result = "()")] +pub struct ZkVerificationRequest { + pub proof: Proof, + pub e3_id: E3id, + pub key: Arc, + pub sender: Recipient, +} + +/// Response from ZK proof verification with context. +#[derive(Debug, Clone, Message)] +#[rtype(result = "()")] +pub struct ZkVerificationResponse { + pub verified: bool, + pub error: Option, + pub e3_id: E3id, + pub key: Arc, +} + +/// Core actor that handles encryption key verification. +pub struct ProofVerificationActor { + bus: BusHandle, + verifier: Option>, +} + +impl ProofVerificationActor { + pub fn new(bus: &BusHandle, verifier: Option>) -> Self { + Self { + bus: bus.clone(), + verifier, + } + } + + pub fn setup( + bus: &BusHandle, + verifier: Option>, + ) -> Addr { + let addr = Self::new(bus, verifier).start(); + bus.subscribe(EventType::EncryptionKeyReceived, addr.clone().into()); + addr + } + + fn handle_encryption_key_received(&mut self, msg: EncryptionKeyReceived, ctx: &Context) { + let Some(ref verifier) = self.verifier else { + warn!( + "ZK verifier not available - accepting key from party {} without verification", + msg.key.party_id + ); + self.publish_key_created(msg.e3_id, msg.key); + return; + }; + + let Some(ref proof) = msg.key.proof else { + warn!( + "External key from party {} is missing T0 proof - rejecting", + msg.key.party_id + ); + return; + }; + + let request = ZkVerificationRequest { + proof: proof.clone(), + e3_id: msg.e3_id, + key: msg.key, + sender: ctx.address().recipient(), + }; + + verifier.do_send(request); + } + + fn publish_key_created(&self, e3_id: E3id, key: Arc) { + if let Err(err) = self.bus.publish(EncryptionKeyCreated { + e3_id, + key, + external: true, + }) { + error!("Failed to publish EncryptionKeyCreated: {err}"); + } + } +} + +impl Actor for ProofVerificationActor { + type Context = Context; +} + +impl Handler for ProofVerificationActor { + type Result = (); + + fn handle(&mut self, msg: EnclaveEvent, ctx: &mut Self::Context) -> Self::Result { + match msg.into_data() { + EnclaveEventData::EncryptionKeyReceived(data) => self.notify_sync(ctx, data), + _ => (), + } + } +} + +impl Handler for ProofVerificationActor { + type Result = (); + + fn handle(&mut self, msg: EncryptionKeyReceived, ctx: &mut Self::Context) -> Self::Result { + self.handle_encryption_key_received(msg, ctx) + } +} + +impl Handler for ProofVerificationActor { + type Result = (); + + fn handle(&mut self, msg: ZkVerificationResponse, _ctx: &mut Self::Context) -> Self::Result { + if msg.verified { + info!( + "T0 proof verified for party {} - accepting key", + msg.key.party_id + ); + self.publish_key_created(msg.e3_id, msg.key); + } else { + error!( + "T0 proof verification FAILED for party {} - rejecting key: {}", + msg.key.party_id, + msg.error.unwrap_or_else(|| "unknown error".to_string()) + ); + } + } +} diff --git a/crates/zk-prover/src/actors/zk_actor.rs b/crates/zk-prover/src/actors/zk_actor.rs new file mode 100644 index 0000000000..a3c3f02c3e --- /dev/null +++ b/crates/zk-prover/src/actors/zk_actor.rs @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! IO actor for ZK proof generation and verification. +//! This actor handles actual disk-based operations for proving and verifying. +//! +//! This is an IO actor - it performs file system operations. + +use actix::{Actor, Context, Handler}; +use tracing::{debug, error}; + +use crate::{ZkBackend, ZkProver}; + +use super::proof_verification::{ZkVerificationRequest, ZkVerificationResponse}; + +/// IO actor that handles ZK proof generation and verification. +pub struct ZkActor { + prover: ZkProver, +} + +impl ZkActor { + pub fn new(backend: &ZkBackend) -> Self { + Self { + prover: ZkProver::new(backend), + } + } +} + +impl Actor for ZkActor { + type Context = Context; +} + +impl Handler for ZkActor { + type Result = (); + + fn handle(&mut self, msg: ZkVerificationRequest, _ctx: &mut Self::Context) -> Self::Result { + debug!( + "Verifying proof for circuit: {} (party {})", + msg.proof.circuit, msg.key.party_id + ); + + let e3_id_str = msg.e3_id.to_string(); + let result = self.prover.verify_proof( + msg.proof.circuit, + &msg.proof.data, + &msg.proof.public_signals, + &e3_id_str, + msg.key.party_id, + ); + + let response = match result { + Ok(true) => { + debug!("Proof verification successful"); + ZkVerificationResponse { + verified: true, + error: None, + e3_id: msg.e3_id, + key: msg.key, + } + } + Ok(false) => { + error!("Proof verification failed"); + ZkVerificationResponse { + verified: false, + error: Some("Verification returned false".to_string()), + e3_id: msg.e3_id, + key: msg.key, + } + } + Err(e) => { + error!("Proof verification error: {}", e); + ZkVerificationResponse { + verified: false, + error: Some(e.to_string()), + e3_id: msg.e3_id, + key: msg.key, + } + } + }; + + // Send response back to the sender + msg.sender.do_send(response); + } +} diff --git a/crates/zk-prover/src/backend/download.rs b/crates/zk-prover/src/backend/download.rs new file mode 100644 index 0000000000..51269776cf --- /dev/null +++ b/crates/zk-prover/src/backend/download.rs @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::config::{verify_checksum, BbTarget}; +use crate::error::ZkError; +use flate2::read::GzDecoder; +use futures_util::StreamExt; +use indicatif::{ProgressBar, ProgressStyle}; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use tar::Archive; +use tokio::fs; +use tracing::{info, warn}; +use walkdir::WalkDir; + +use super::ZkBackend; + +impl ZkBackend { + pub async fn download_bb(&self) -> Result<(), ZkError> { + let target = BbTarget::current().ok_or_else(|| ZkError::UnsupportedPlatform { + os: std::env::consts::OS.to_string(), + arch: std::env::consts::ARCH.to_string(), + })?; + + let (arch, os) = target.url_parts(); + let version = &self.config.required_bb_version; + + let url = self + .config + .bb_download_url + .replace("{version}", version) + .replace("{os}", &os) + .replace("{arch}", &arch); + + info!("downloading Barretenberg from: {}", url); + + let bytes = download_with_progress(&url, "Downloading bb").await?; + let expected_checksum = self.config.bb_checksum_for(target); + verify_checksum(&format!("bb-{}", target), &bytes, expected_checksum)?; + + let decoder = GzDecoder::new(&bytes[..]); + let mut archive = Archive::new(decoder); + + let bin_dir = self.base_dir.join("bin"); + fs::create_dir_all(&bin_dir).await?; + + let temp_dir = tempfile::tempdir()?; + archive.unpack(temp_dir.path())?; + + let bb_source = find_bb_in_dir(temp_dir.path())?; + + fs::copy(&bb_source, &self.bb_binary).await?; + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = fs::metadata(&self.bb_binary).await?.permissions(); + perms.set_mode(0o755); + fs::set_permissions(&self.bb_binary, perms).await?; + } + + let mut version_info = self.load_version_info().await; + version_info.bb_version = Some(version.clone()); + version_info.bb_checksum = expected_checksum.map(|s| s.to_string()); + version_info.last_updated = Some(chrono::Utc::now().to_rfc3339()); + version_info.save(&self.version_file()).await?; + + info!("installed Barretenberg v{}", version); + Ok(()) + } + + pub async fn download_circuits(&self) -> Result<(), ZkError> { + let version = &self.config.required_circuits_version; + let url = self + .config + .circuits_download_url + .replace("{version}", version); + + info!("downloading circuits from: {}", url); + + let result = download_with_progress(&url, "Downloading circuits").await; + + let mut version_info = self.load_version_info().await; + + match result { + Ok(bytes) => { + if self.circuits_dir.exists() { + fs::remove_dir_all(&self.circuits_dir).await?; + } + + let decoder = GzDecoder::new(&bytes[..]); + let mut archive = Archive::new(decoder); + archive.unpack(&self.base_dir)?; + + version_info.circuits_version = Some(version.clone()); + version_info.last_updated = Some(chrono::Utc::now().to_rfc3339()); + version_info.save(&self.version_file()).await?; + + info!("installed circuits v{}", version); + } + Err(e) => { + warn!( + "could not download circuits ({}), creating placeholder for testing", + e + ); + create_placeholder_circuits(&self.circuits_dir).await?; + + version_info.circuits_version = Some("0.0.0-placeholder".to_string()); + version_info.last_updated = Some(chrono::Utc::now().to_rfc3339()); + version_info.save(&self.version_file()).await?; + + info!("created placeholder circuits (will retry download on next setup)"); + } + } + + Ok(()) + } + + pub async fn verify_bb(&self) -> Result { + if !self.bb_binary.exists() { + return Err(ZkError::BbNotInstalled); + } + + let output = tokio::process::Command::new(&self.bb_binary) + .arg("--version") + .output() + .await?; + + if !output.status.success() { + return Err(ZkError::ProveFailed( + String::from_utf8_lossy(&output.stderr).to_string(), + )); + } + + let version = String::from_utf8_lossy(&output.stdout).trim().to_string(); + Ok(version) + } +} + +fn find_bb_in_dir(dir: &Path) -> Result { + for candidate in ["bb", "bin/bb", "barretenberg/bin/bb"] { + let path = dir.join(candidate); + if path.exists() && path.is_file() { + return Ok(path); + } + } + + WalkDir::new(dir) + .into_iter() + .filter_map(|e| e.ok()) + .find(|e| e.file_name().to_string_lossy() == "bb" && e.file_type().is_file()) + .map(|e| e.path().to_path_buf()) + .ok_or_else(|| { + ZkError::IoError(std::io::Error::new( + std::io::ErrorKind::NotFound, + "bb binary not found in archive", + )) + }) +} + +async fn download_with_progress(url: &str, message: &str) -> Result, ZkError> { + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(300)) + .build() + .map_err(|e| ZkError::DownloadFailed(url.to_string(), e.to_string()))?; + + let response = client + .get(url) + .send() + .await + .map_err(|e| ZkError::DownloadFailed(url.to_string(), e.to_string()))?; + + if !response.status().is_success() { + return Err(ZkError::DownloadFailed( + url.to_string(), + format!("HTTP {}", response.status()), + )); + } + + let total_size = response.content_length().unwrap_or(0); + + let pb = ProgressBar::new(total_size); + pb.set_style( + ProgressStyle::default_bar() + .template( + "{msg} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})", + ) + .unwrap() + .progress_chars("#>-"), + ); + pb.set_message(message.to_string()); + + let mut bytes = Vec::new(); + let mut stream = response.bytes_stream(); + + while let Some(chunk) = stream.next().await { + let chunk = chunk.map_err(|e| ZkError::DownloadFailed(url.to_string(), e.to_string()))?; + bytes.extend_from_slice(&chunk); + pb.set_position(bytes.len() as u64); + } + + pb.finish_with_message("download complete"); + Ok(bytes) +} + +async fn create_placeholder_circuits(circuits_dir: &Path) -> Result<(), ZkError> { + fs::create_dir_all(circuits_dir).await?; + + let placeholder = serde_json::json!({ + "noir_version":"1.0.0-beta.15+83245db91dcf63420ef4bcbbd85b98f397fee663", + "hash":"15412581843239610929", + "abi":{ + "parameters":[ + {"name":"x","type":{"kind":"field"},"visibility":"private"}, + {"name":"y","type":{"kind":"field"},"visibility":"private"}, + {"name":"_sum","type":{"kind":"field"},"visibility":"public"} + ], + "return_type":null, + "error_types":{} + }, + "bytecode":"H4sIAAAAAAAA/5WOMQ5AMBRA/y8HMbIRRxCJSYwWg8RiIGIz9gjiAk4hHKeb0WLX0KHRDu1bXvL/y89H+HCFu7rtCTeCiiPsgRFo06LUhk0+smgN9iLdKC0rPz6z6RjmhN3LxffE/O7byg+hZv7nAb2HRPkUAQAA", + "debug_symbols":"jZDRCoMwDEX/Jc996MbG1F8ZQ2qNUghtie1giP++KLrpw2BPaXJ7bsgdocUm97XzXRiguo/QsCNyfU3BmuSCl+k4KdjaOjGijGCnCxUNo09Q+Uyk4GkoL5+GaPxSk2FRtQL0rVQx7Bzh/JrUl9a/0Vu5ssXlA1//psvbSp90ccAf0hnr+HAuaKjO0+zGzjSEawRd9naXSHrFTdkyixwstplxtls0WfAG", + "file_map":{ + "50":{"source":"pub fn main(\n x: Field,\n y: Field,\n _sum: pub Field\n) {\n let sum = x + y;\n assert(sum == _sum);\n}\n", + "path":"./enclave/circuits/bin/dummy/src/main.nr"} + },"expression_width":{"Bounded":{"width":4}} + }); + + let circuit_path = circuits_dir.join("pk.json"); + fs::write(&circuit_path, serde_json::to_string_pretty(&placeholder)?).await?; + + fs::create_dir_all(circuits_dir.join("vk")).await?; + + // Create a minimal placeholder VK file so proof generation doesn't fail + // This is just for testing when actual circuits can't be downloaded + let vk_path = circuits_dir.join("vk").join("pk.vk"); + fs::write(&vk_path, b"placeholder_vk").await?; + + Ok(()) +} diff --git a/crates/zk-prover/src/backend/mod.rs b/crates/zk-prover/src/backend/mod.rs new file mode 100644 index 0000000000..b4f4c9dcc1 --- /dev/null +++ b/crates/zk-prover/src/backend/mod.rs @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +mod download; +mod setup; + +#[cfg(test)] +mod tests; + +use crate::config::ZkConfig; +use crate::error::ZkError; +use std::path::PathBuf; + +#[derive(Debug, Clone)] +pub enum SetupStatus { + Ready, + BbNeedsUpdate { + installed: Option, + required: String, + }, + CircuitsNeedUpdate { + installed: Option, + required: String, + }, + FullSetupNeeded, +} + +#[derive(Debug, Clone)] +pub struct ZkBackend { + pub base_dir: PathBuf, + pub bb_binary: PathBuf, + pub circuits_dir: PathBuf, + pub work_dir: PathBuf, + pub config: ZkConfig, +} + +impl ZkBackend { + pub fn new( + bb_binary: PathBuf, + circuits_dir: PathBuf, + work_dir: PathBuf, + config: ZkConfig, + ) -> Self { + let base_dir = circuits_dir + .parent() + .expect("circuits_dir should have a parent") + .to_path_buf(); + + Self { + bb_binary, + circuits_dir, + work_dir, + base_dir, + config, + } + } + + pub async fn with_default_dir(node_name: &str) -> Result { + let base_dirs = directories::BaseDirs::new().ok_or_else(|| { + ZkError::IoError(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Could not determine home directory", + )) + })?; + + let home_dir = base_dirs.home_dir(); + let noir_dir = home_dir.join(".enclave").join("noir"); + let bb_binary = noir_dir.join("bin").join("bb"); + let circuits_dir = noir_dir.join("circuits"); + let work_dir = noir_dir.join("work").join(node_name); + + let config = ZkConfig::fetch_or_default().await; + Ok(Self::new(bb_binary, circuits_dir, work_dir, config)) + } + + fn sanitize_e3_id(e3_id: &str) -> Result<&str, ZkError> { + // Sanitize e3_id to prevent path traversal + if e3_id.is_empty() + || e3_id.contains('\0') + || e3_id.contains("..") + || e3_id.contains('/') + || e3_id.contains('\\') + { + return Err(ZkError::IoError(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "e3_id contains invalid characters", + ))); + } + + Ok(e3_id) + } + + pub fn work_dir_for(&self, e3_id: &str) -> Result { + let sanitized = Self::sanitize_e3_id(e3_id)?; + Ok(self.work_dir.join(sanitized)) + } + + pub async fn cleanup_work_dir(&self, e3_id: &str) -> Result<(), ZkError> { + let work_dir = self.work_dir_for(e3_id)?; + if work_dir.exists() { + tokio::fs::remove_dir_all(&work_dir).await?; + } + Ok(()) + } +} diff --git a/crates/zk-prover/src/backend/setup.rs b/crates/zk-prover/src/backend/setup.rs new file mode 100644 index 0000000000..0d8d7918c9 --- /dev/null +++ b/crates/zk-prover/src/backend/setup.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::config::VersionInfo; +use crate::error::ZkError; +use std::path::PathBuf; +use tokio::fs; +use tracing::{debug, info}; + +use super::{SetupStatus, ZkBackend}; + +impl ZkBackend { + pub fn version_file(&self) -> PathBuf { + self.base_dir.join("version.json") + } + + pub async fn load_version_info(&self) -> VersionInfo { + match VersionInfo::load(&self.version_file()).await { + Ok(info) => info, + Err(_) => VersionInfo::default(), + } + } + + pub async fn check_status(&self) -> SetupStatus { + let version_info = self.load_version_info().await; + + let bb_ok = + version_info.bb_matches(&self.config.required_bb_version) && self.bb_binary.exists(); + let circuits_ok = version_info.circuits_match(&self.config.required_circuits_version) + && self.circuits_dir.exists(); + + match (bb_ok, circuits_ok) { + (true, true) => SetupStatus::Ready, + (false, true) => SetupStatus::BbNeedsUpdate { + installed: version_info.bb_version, + required: self.config.required_bb_version.clone(), + }, + (true, false) => SetupStatus::CircuitsNeedUpdate { + installed: version_info.circuits_version, + required: self.config.required_circuits_version.clone(), + }, + (false, false) => SetupStatus::FullSetupNeeded, + } + } + + pub async fn ensure_installed(&self) -> Result<(), ZkError> { + fs::create_dir_all(&self.base_dir).await?; + fs::create_dir_all(self.base_dir.join("bin")).await?; + fs::create_dir_all(&self.circuits_dir).await?; + fs::create_dir_all(&self.work_dir).await?; + + let status = self.check_status().await; + + match status { + SetupStatus::Ready => { + debug!("ZK backend is ready"); + Ok(()) + } + SetupStatus::BbNeedsUpdate { + installed, + required, + } => { + info!( + "updating Barretenberg: {} -> {}", + installed.as_deref().unwrap_or("not installed"), + required + ); + self.download_bb().await + } + SetupStatus::CircuitsNeedUpdate { + installed, + required, + } => { + info!( + "updating circuits: {} -> {}", + installed.as_deref().unwrap_or("not installed"), + required + ); + self.download_circuits().await + } + SetupStatus::FullSetupNeeded => { + info!("setting up ZK proving infrastructure..."); + self.download_bb().await?; + self.download_circuits().await + } + } + } +} diff --git a/crates/zk-prover/src/backend/tests.rs b/crates/zk-prover/src/backend/tests.rs new file mode 100644 index 0000000000..dbf1af8239 --- /dev/null +++ b/crates/zk-prover/src/backend/tests.rs @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use super::*; +use crate::config::VersionInfo; +use tempfile::tempdir; +use tokio::fs; + +fn test_backend(temp_path: &std::path::Path, config: ZkConfig) -> ZkBackend { + let noir_dir = temp_path.join("noir"); + let bb_binary = noir_dir.join("bin").join("bb"); + let circuits_dir = noir_dir.join("circuits"); + let work_dir = noir_dir.join("work").join("test_node"); + ZkBackend::new(bb_binary, circuits_dir, work_dir, config) +} + +#[tokio::test] +async fn test_backend_creates_directories() { + let temp = tempdir().unwrap(); + let backend = test_backend(temp.path(), ZkConfig::default()); + + fs::create_dir_all(&backend.base_dir).await.unwrap(); + fs::create_dir_all(&backend.circuits_dir).await.unwrap(); + fs::create_dir_all(&backend.work_dir).await.unwrap(); + + assert!(backend.base_dir.exists()); + assert!(backend.circuits_dir.exists()); + assert!(backend.work_dir.exists()); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} + +#[tokio::test] +async fn test_version_info_roundtrip() { + let temp = tempdir().unwrap(); + let path = temp.path().join("version.json"); + + let info = VersionInfo { + bb_version: Some("0.87.0".to_string()), + circuits_version: Some("0.1.0".to_string()), + ..Default::default() + }; + + info.save(&path).await.unwrap(); + let loaded = VersionInfo::load(&path).await.unwrap(); + + assert_eq!(loaded.bb_version, info.bb_version); + assert_eq!(loaded.circuits_version, info.circuits_version); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} + +#[tokio::test] +async fn test_check_status_full_setup_needed() { + let temp = tempdir().unwrap(); + let backend = test_backend(temp.path(), ZkConfig::default()); + + let status = backend.check_status().await; + assert!(matches!(status, SetupStatus::FullSetupNeeded)); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} + +#[tokio::test] +async fn test_check_status_ready_when_installed() { + let temp = tempdir().unwrap(); + let config = ZkConfig::default(); + let backend = test_backend(temp.path(), config.clone()); + + fs::create_dir_all(&backend.base_dir.join("bin")) + .await + .unwrap(); + fs::create_dir_all(&backend.circuits_dir).await.unwrap(); + fs::write(&backend.bb_binary, b"fake bb binary") + .await + .unwrap(); + + let info = VersionInfo { + bb_version: Some(config.required_bb_version.clone()), + circuits_version: Some(config.required_circuits_version.clone()), + ..Default::default() + }; + info.save(&backend.version_file()).await.unwrap(); + + let status = backend.check_status().await; + assert!(matches!(status, SetupStatus::Ready)); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} + +#[tokio::test] +async fn test_check_status_bb_needs_update() { + let temp = tempdir().unwrap(); + let config = ZkConfig::default(); + let backend = test_backend(temp.path(), config.clone()); + + fs::create_dir_all(&backend.base_dir.join("bin")) + .await + .unwrap(); + fs::create_dir_all(&backend.circuits_dir).await.unwrap(); + fs::write(&backend.bb_binary, b"fake bb binary") + .await + .unwrap(); + + let info = VersionInfo { + bb_version: Some("0.0.1".to_string()), + circuits_version: Some(config.required_circuits_version.clone()), + ..Default::default() + }; + info.save(&backend.version_file()).await.unwrap(); + + let status = backend.check_status().await; + assert!(matches!(status, SetupStatus::BbNeedsUpdate { .. })); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} + +#[tokio::test] +async fn test_work_dir_cleanup() { + let temp = tempdir().unwrap(); + let backend = test_backend(temp.path(), ZkConfig::default()); + + fs::create_dir_all(&backend.work_dir).await.unwrap(); + + let e3_id = "test-e3-123"; + let work_dir = backend.work_dir_for(e3_id).unwrap(); + + fs::create_dir_all(&work_dir).await.unwrap(); + fs::write(work_dir.join("proof.bin"), b"fake proof") + .await + .unwrap(); + fs::write(work_dir.join("witness.bin"), b"fake witness") + .await + .unwrap(); + assert!(work_dir.exists()); + + backend.cleanup_work_dir(e3_id).await.unwrap(); + assert!(!work_dir.exists()); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} diff --git a/crates/zk-prover/src/circuits/mod.rs b/crates/zk-prover/src/circuits/mod.rs new file mode 100644 index 0000000000..f71a54ccda --- /dev/null +++ b/crates/zk-prover/src/circuits/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +mod pkbfv; diff --git a/crates/zk-prover/src/circuits/pkbfv.rs b/crates/zk-prover/src/circuits/pkbfv.rs new file mode 100644 index 0000000000..9f370ba512 --- /dev/null +++ b/crates/zk-prover/src/circuits/pkbfv.rs @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE + +use crate::error::ZkError; +use crate::traits::Provable; +use acir::FieldElement; +use e3_events::CircuitName; +use e3_fhe_params::BfvPreset; +use e3_polynomial::CrtPolynomial; +use e3_zk_helpers::circuits::dkg::pk::circuit::{PkCircuit, PkCircuitInput}; +use e3_zk_helpers::circuits::dkg::pk::computation::Witness; +use e3_zk_helpers::Computation; +use fhe::bfv::PublicKey; +use noirc_abi::{input_parser::InputValue, InputMap}; +use std::collections::BTreeMap; + +impl Provable for PkCircuit { + type Params = BfvPreset; + type Input = PublicKey; + + fn circuit(&self) -> CircuitName { + CircuitName::PkBfv + } + + fn build_witness( + &self, + preset: &Self::Params, + input: &Self::Input, + ) -> Result { + let circuit_input = PkCircuitInput { + public_key: input.clone(), + }; + + let witness = Witness::compute(preset.clone(), &circuit_input) + .map_err(|e| ZkError::WitnessGenerationFailed(e.to_string()))?; + + let mut inputs = InputMap::new(); + inputs.insert( + "pk0is".to_string(), + crt_polynomial_to_array(&witness.pk0is)?, + ); + inputs.insert( + "pk1is".to_string(), + crt_polynomial_to_array(&witness.pk1is)?, + ); + + Ok(inputs) + } +} + +fn crt_polynomial_to_array(crt_poly: &CrtPolynomial) -> Result { + let mut polynomials = Vec::with_capacity(crt_poly.limbs.len()); + + for limb in &crt_poly.limbs { + let coeffs = limb.coefficients(); + let mut field_coeffs = Vec::with_capacity(coeffs.len()); + + for b in coeffs { + let s = b.to_string(); + let field = FieldElement::try_from_str(&s).ok_or_else(|| { + ZkError::SerializationError(format!("invalid field element: {}", s)) + })?; + field_coeffs.push(InputValue::Field(field)); + } + + let mut fields = BTreeMap::new(); + fields.insert("coefficients".to_string(), InputValue::Vec(field_coeffs)); + polynomials.push(InputValue::Struct(fields)); + } + + Ok(InputValue::Vec(polynomials)) +} diff --git a/crates/zk-prover/src/config.rs b/crates/zk-prover/src/config.rs new file mode 100644 index 0000000000..c385cbad25 --- /dev/null +++ b/crates/zk-prover/src/config.rs @@ -0,0 +1,535 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::error::ZkError; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::path::Path; +use std::time::Duration; +use tokio::fs; +use tracing::{debug, warn}; + +// TODO: change to main when feat/noir-prover is merged +const VERSIONS_MANIFEST_URL: &str = + "https://raw.githubusercontent.com/gnosisguild/enclave/feat/noir-prover/crates/zk-prover/versions.json"; + +const BB_VERSION: &str = "3.0.0-nightly.20251104"; +const CIRCUITS_VERSION: &str = "0.1.11"; + +/// Supported bb binary targets +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum BbTarget { + Amd64Linux, + Amd64Darwin, + Arm64Linux, + Arm64Darwin, +} + +impl BbTarget { + /// Detect the current system's target + pub fn current() -> Option { + match (std::env::consts::ARCH, std::env::consts::OS) { + ("x86_64", "linux") => Some(Self::Amd64Linux), + ("x86_64", "macos") => Some(Self::Amd64Darwin), + ("aarch64", "linux") => Some(Self::Arm64Linux), + ("aarch64", "macos") => Some(Self::Arm64Darwin), + _ => None, + } + } + + pub fn as_str(&self) -> &'static str { + match self { + Self::Amd64Linux => "amd64-linux", + Self::Amd64Darwin => "amd64-darwin", + Self::Arm64Linux => "arm64-linux", + Self::Arm64Darwin => "arm64-darwin", + } + } + + /// Returns (arch, os) for URL templating + pub fn url_parts(&self) -> (&'static str, &'static str) { + match self { + Self::Amd64Linux => ("amd64", "linux"), + Self::Amd64Darwin => ("amd64", "darwin"), + Self::Arm64Linux => ("arm64", "linux"), + Self::Arm64Darwin => ("arm64", "darwin"), + } + } +} + +impl std::fmt::Display for BbTarget { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ZkConfig { + pub bb_download_url: String, + #[serde(default)] + pub bb_checksums: HashMap, + pub circuits_download_url: String, + #[serde(default)] + pub circuits_checksums: HashMap, + pub required_bb_version: String, + pub required_circuits_version: String, +} + +impl Default for ZkConfig { + fn default() -> Self { + Self { + bb_download_url: "https://github.com/AztecProtocol/aztec-packages/releases/download/v{version}/barretenberg-{arch}-{os}.tar.gz".to_string(), + circuits_download_url: "https://github.com/gnosisguild/enclave/releases/download/v{version}/circuits-{version}.tar.gz".to_string(), + bb_checksums: HashMap::new(), + circuits_checksums: HashMap::new(), + required_bb_version: BB_VERSION.to_string(), + required_circuits_version: CIRCUITS_VERSION.to_string(), + } + } +} + +impl ZkConfig { + pub async fn fetch_latest() -> Result { + let client = reqwest::Client::new(); + let response = client + .get(VERSIONS_MANIFEST_URL) + .timeout(Duration::from_secs(10)) + .send() + .await + .map_err(|e| { + ZkError::DownloadFailed(VERSIONS_MANIFEST_URL.to_string(), e.to_string()) + })?; + + if !response.status().is_success() { + return Err(ZkError::DownloadFailed( + VERSIONS_MANIFEST_URL.to_string(), + format!("HTTP {}", response.status()), + )); + } + + let config: ZkConfig = response.json().await.map_err(|e| { + ZkError::DownloadFailed(VERSIONS_MANIFEST_URL.to_string(), e.to_string()) + })?; + + Ok(config) + } + + pub async fn fetch_or_default() -> Self { + match Self::fetch_latest().await { + Ok(config) => { + debug!( + "fetched versions manifest: bb={}, circuits={}", + config.required_bb_version, config.required_circuits_version + ); + config + } + Err(e) => { + warn!("could not fetch versions manifest ({}), using defaults", e); + Self::default() + } + } + } + + pub async fn load(path: &Path) -> std::io::Result { + let contents = fs::read_to_string(path).await?; + serde_json::from_str(&contents) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)) + } + + /// Get checksum for a specific target from the remote manifest + pub fn bb_checksum_for(&self, target: BbTarget) -> Option<&str> { + self.bb_checksums.get(target.as_str()).map(|s| s.as_str()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct VersionInfo { + #[serde(default)] + pub bb_version: Option, + #[serde(default)] + pub bb_checksum: Option, + #[serde(default)] + pub circuits_version: Option, + #[serde(default)] + pub circuits: HashMap, + #[serde(default)] + pub last_updated: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CircuitInfo { + pub file: String, + pub checksum: String, +} + +impl VersionInfo { + pub async fn load(path: &Path) -> std::io::Result { + let contents = fs::read_to_string(path).await?; + serde_json::from_str(&contents) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)) + } + + pub async fn save(&self, path: &Path) -> std::io::Result<()> { + let contents = serde_json::to_string_pretty(self)?; + fs::write(path, contents).await + } + + pub fn bb_matches(&self, required: &str) -> bool { + self.bb_version.as_deref() == Some(required) + } + + pub fn circuits_match(&self, required: &str) -> bool { + self.circuits_version.as_deref() == Some(required) + } + + /// Verify downloaded bb binary against stored checksum + pub fn verify_bb_checksum(&self, data: &[u8]) -> Result<(), ZkError> { + verify_checksum("bb", data, self.bb_checksum.as_deref()) + } + + pub fn verify_circuit_checksum(&self, circuit_name: &str, data: &[u8]) -> Result<(), ZkError> { + let expected = self.circuits.get(circuit_name).map(|c| c.checksum.as_str()); + verify_checksum(circuit_name, data, expected) + } +} + +pub fn verify_checksum(file: &str, data: &[u8], expected: Option<&str>) -> Result<(), ZkError> { + let Some(expected) = expected else { + debug!("no checksum provided for {}, skipping verification", file); + return Ok(()); + }; + + let mut hasher = Sha256::new(); + hasher.update(data); + let actual = hex::encode(hasher.finalize()); + + if actual != expected { + return Err(ZkError::ChecksumMismatch { + file: file.to_string(), + expected: expected.to_string(), + actual, + }); + } + + debug!("checksum verified for {}", file); + Ok(()) +} + +#[cfg(test)] +mod tests { + use tempfile::tempdir; + + use super::*; + + // BbTarget tests + #[test] + fn test_bb_target_as_str() { + assert_eq!(BbTarget::Amd64Linux.as_str(), "amd64-linux"); + assert_eq!(BbTarget::Amd64Darwin.as_str(), "amd64-darwin"); + assert_eq!(BbTarget::Arm64Linux.as_str(), "arm64-linux"); + assert_eq!(BbTarget::Arm64Darwin.as_str(), "arm64-darwin"); + } + + #[test] + fn test_bb_target_url_parts() { + assert_eq!(BbTarget::Amd64Linux.url_parts(), ("amd64", "linux")); + assert_eq!(BbTarget::Amd64Darwin.url_parts(), ("amd64", "darwin")); + assert_eq!(BbTarget::Arm64Linux.url_parts(), ("arm64", "linux")); + assert_eq!(BbTarget::Arm64Darwin.url_parts(), ("arm64", "darwin")); + } + + #[test] + fn test_bb_target_current_returns_some_on_supported_platform() { + let target = BbTarget::current(); + if let Some(t) = target { + assert!(!t.as_str().is_empty()); + } + } + + #[test] + fn test_verify_checksum_success() { + let data = b"hello world"; + let expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"; + + let result = verify_checksum("test-file", data, Some(expected)); + assert!(result.is_ok()); + } + + #[test] + fn test_verify_checksum_mismatch() { + let data = b"hello world"; + let wrong = "0000000000000000000000000000000000000000000000000000000000000000"; + + let result = verify_checksum("test-file", data, Some(wrong)); + + let Err(ZkError::ChecksumMismatch { + file, + expected, + actual, + }) = result + else { + panic!("expected ChecksumMismatch error"); + }; + assert_eq!(file, "test-file"); + assert_eq!(expected, wrong); + assert_eq!( + actual, + "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" + ); + } + + #[test] + fn test_verify_checksum_skipped_when_none() { + let result = verify_checksum("test-file", b"any data", None); + assert!(result.is_ok()); + } + + // VersionInfo tests (local installed state) + + #[test] + fn test_version_info_verify_bb_checksum() { + let info = VersionInfo { + bb_version: Some("0.86.0".to_string()), + bb_checksum: Some( + "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9".to_string(), + ), + ..Default::default() + }; + + assert!(info.verify_bb_checksum(b"hello world").is_ok()); + } + + #[test] + fn test_version_info_verify_bb_checksum_mismatch() { + let info = VersionInfo { + bb_checksum: Some( + "0000000000000000000000000000000000000000000000000000000000000000".to_string(), + ), + ..Default::default() + }; + + let result = info.verify_bb_checksum(b"hello world"); + assert!(matches!(result, Err(ZkError::ChecksumMismatch { .. }))); + } + + #[test] + fn test_version_info_verify_bb_checksum_skipped_when_none() { + let info = VersionInfo::default(); + assert!(info.verify_bb_checksum(b"any data").is_ok()); + } + + #[test] + fn test_version_info_verify_circuit_checksum() { + let mut circuits = HashMap::new(); + circuits.insert( + "my-circuit".to_string(), + CircuitInfo { + file: "my-circuit.bin".to_string(), + checksum: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" + .to_string(), + }, + ); + + let info = VersionInfo { + circuits, + ..Default::default() + }; + + assert!(info + .verify_circuit_checksum("my-circuit", b"hello world") + .is_ok()); + assert!(info + .verify_circuit_checksum("unknown-circuit", b"hello world") + .is_ok()); // skipped + } + + #[test] + fn test_version_info_serialization_roundtrip() { + let info = VersionInfo { + bb_version: Some("0.86.0".to_string()), + bb_checksum: Some("abc123".to_string()), + circuits_version: Some("0.1.0".to_string()), + circuits: HashMap::new(), + last_updated: Some("2026-01-27T10:00:00Z".to_string()), + }; + + let json = serde_json::to_string(&info).unwrap(); + let parsed: VersionInfo = serde_json::from_str(&json).unwrap(); + + assert_eq!(parsed.bb_version, info.bb_version); + assert_eq!(parsed.bb_checksum, info.bb_checksum); + assert_eq!(parsed.circuits_version, info.circuits_version); + } + + // ZkConfig tests (remote manifest with all targets) + + #[test] + fn test_zk_config_bb_checksum_for_target() { + let mut bb_checksums = HashMap::new(); + bb_checksums.insert("amd64-linux".to_string(), "checksum-amd64".to_string()); + bb_checksums.insert("arm64-darwin".to_string(), "checksum-arm64".to_string()); + + let config = ZkConfig { + bb_checksums, + ..Default::default() + }; + + assert_eq!( + config.bb_checksum_for(BbTarget::Amd64Linux), + Some("checksum-amd64") + ); + assert_eq!( + config.bb_checksum_for(BbTarget::Arm64Darwin), + Some("checksum-arm64") + ); + assert_eq!(config.bb_checksum_for(BbTarget::Arm64Linux), None); + } + + #[test] + fn test_zk_config_default() { + let config = ZkConfig::default(); + + assert!(config.bb_download_url.contains("{version}")); + assert!(config.bb_checksums.is_empty()); + assert_eq!(config.required_bb_version, BB_VERSION); + } + + /// Integration test that downloads a real bb binary and verifies checksum. + #[tokio::test] + async fn test_download_and_verify_bb() { + let Some(target) = BbTarget::current() else { + println!("skipping test: unsupported platform"); + return; + }; + + // Known good checksums for bb v0.82.2 + // Update these when bumping BB_VERSION + let checksums: HashMap<&str, &str> = [ + ( + "amd64-linux", + "9740013d1aa0eb1b0bb2d71484c8b3debc5050a409bd5f12f8454fbfc7cb5419", + ), + ( + "amd64-darwin", + "7874494dd1238655993a44b85d94e9dcc3589d29980eff8b03a7f167a45c32e4", + ), + ( + "arm64-linux", + "ae6bf8518998523b4e135cd638f305a802f52e8dfa5ea9b1c210de7d04c55343", + ), + ( + "arm64-darwin", + "6d353c05dbecc573d1b0ca992c8b222db8e873853b7910b792915629347f6789", + ), + ] + .into_iter() + .collect(); + + let version = BB_VERSION; + let (arch, os) = target.url_parts(); + let url = format!( + "https://github.com/AztecProtocol/aztec-packages/releases/download/v{version}/barretenberg-{arch}-{os}.tar.gz" + ); + + println!("downloading {} from {}", target, url); + + let client = reqwest::Client::new(); + let response = client + .get(&url) + .timeout(Duration::from_secs(120)) + .send() + .await + .expect("failed to send request"); + + assert!( + response.status().is_success(), + "download failed: {}", + response.status() + ); + + let bytes = response + .bytes() + .await + .expect("failed to read response body"); + println!("downloaded {} bytes", bytes.len()); + + // Verify checksum + let expected = checksums + .get(target.as_str()) + .expect("no checksum for target"); + let result = verify_checksum(&format!("bb-{}", target), &bytes, Some(expected)); + + assert!(result.is_ok(), "checksum verification failed: {:?}", result); + println!("checksum verified for {}", target); + + // Test saving and loading through VersionInfo + let temp = tempdir().expect("failed to create temp dir"); + let tarball_path = temp.path().join("bb.tar.gz"); + + fs::write(&tarball_path, &bytes) + .await + .expect("failed to write tarball"); + assert!(tarball_path.exists()); + + // Verify VersionInfo checksum method works + let info = VersionInfo { + bb_version: Some(version.to_string()), + bb_checksum: Some(expected.to_string()), + ..Default::default() + }; + assert!(info.verify_bb_checksum(&bytes).is_ok()); + + // Cleanup happens automatically when temp goes out of scope + println!("test passed, temp dir cleaned up"); + } + + /// Test that checksum verification fails for corrupted data + #[tokio::test] + async fn test_download_checksum_mismatch_on_corruption() { + let Some(target) = BbTarget::current() else { + println!("skipping test: unsupported platform"); + return; + }; + + let version = BB_VERSION; + let (arch, os) = target.url_parts(); + let url = format!( + "https://github.com/AztecProtocol/aztec-packages/releases/download/v{version}/barretenberg-{arch}-{os}.tar.gz" + ); + + let client = reqwest::Client::new(); + let response = client + .get(&url) + .timeout(Duration::from_secs(120)) + .send() + .await + .expect("failed to send request"); + + let mut bytes = response + .bytes() + .await + .expect("failed to read body") + .to_vec(); + + // Corrupt the data + if !bytes.is_empty() { + bytes[0] ^= 0xFF; + } + + // Use a valid checksum that won't match corrupted data + let info = VersionInfo { + bb_checksum: Some( + "a56257d8edc226180f5a7093393e4adc99447368a65099bb34292bd261408b99".to_string(), + ), + ..Default::default() + }; + + let result = info.verify_bb_checksum(&bytes); + assert!(matches!(result, Err(ZkError::ChecksumMismatch { .. }))); + println!("correctly detected corrupted download"); + } +} diff --git a/crates/zk-prover/src/error.rs b/crates/zk-prover/src/error.rs new file mode 100644 index 0000000000..eefdde3e3b --- /dev/null +++ b/crates/zk-prover/src/error.rs @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ZkError { + #[error("Barretenberg binary not found. Run 'enclave noir setup' first.")] + BbNotInstalled, + + #[error("Circuit '{0}' not found. Run 'enclave noir setup' first.")] + CircuitNotFound(String), + + #[error("Version mismatch: installed {installed}, required {required}")] + VersionMismatch { installed: String, required: String }, + + #[error("Failed to download {0}: {1}")] + DownloadFailed(String, String), + + #[error("Checksum mismatch for {file}: expected {expected}, got {actual}")] + ChecksumMismatch { + file: String, + expected: String, + actual: String, + }, + + #[error("Proof generation failed: {0}")] + ProveFailed(String), + + #[error("Proof verification failed: {0}")] + VerifyFailed(String), + + #[error("Serialization error: {0}")] + SerializationError(String), + + #[error("Failed to read proof output: {0}")] + OutputReadError(String), + + #[error("IO error: {0}")] + IoError(#[from] std::io::Error), + + #[error("JSON error: {0}")] + JsonError(#[from] serde_json::Error), + + #[error("HTTP error: {0}")] + HttpError(#[from] reqwest::Error), + + #[error("TOML error: {0}")] + TomlError(#[from] toml::ser::Error), + + #[error("Backend not initialized")] + NotInitialized, + + #[error("Unsupported platform: {os}-{arch}")] + UnsupportedPlatform { os: String, arch: String }, + + #[error("Witness generation failed: {0}")] + WitnessGenerationFailed(String), + + #[error("checksum missing for {0}")] + ChecksumMissing(String), +} diff --git a/crates/zk-prover/src/lib.rs b/crates/zk-prover/src/lib.rs new file mode 100644 index 0000000000..f3ee939522 --- /dev/null +++ b/crates/zk-prover/src/lib.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +mod actors; +mod backend; +mod circuits; +mod config; +mod error; +mod prover; +mod traits; +mod witness; + +pub use actors::{ + setup_zk_actors, ProofRequestActor, ProofVerificationActor, ZkActors, ZkVerificationRequest, + ZkVerificationResponse, +}; + +pub use backend::{SetupStatus, ZkBackend}; +pub use config::{verify_checksum, BbTarget, CircuitInfo, VersionInfo, ZkConfig}; +pub use e3_zk_helpers::circuits::dkg::pk::circuit::PkCircuit; +pub use error::ZkError; +pub use prover::ZkProver; +pub use traits::Provable; +pub use witness::{input_map, CompiledCircuit, WitnessGenerator}; diff --git a/crates/zk-prover/src/prover.rs b/crates/zk-prover/src/prover.rs new file mode 100644 index 0000000000..80c44593a1 --- /dev/null +++ b/crates/zk-prover/src/prover.rs @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::backend::ZkBackend; +use crate::error::ZkError; +use e3_events::{CircuitName, Proof}; +use e3_utils::utility_types::ArcBytes; +use std::fs; +use std::path::PathBuf; +use std::process::Command as StdCommand; +use tracing::{debug, info, warn}; + +pub struct ZkProver { + bb_binary: PathBuf, + circuits_dir: PathBuf, + work_dir: PathBuf, +} + +impl ZkProver { + pub fn new(backend: &ZkBackend) -> Self { + Self { + bb_binary: backend.bb_binary.clone(), + circuits_dir: backend.circuits_dir.clone(), + work_dir: backend.work_dir.clone(), + } + } + + pub fn circuits_dir(&self) -> &PathBuf { + &self.circuits_dir + } + + pub fn work_dir(&self) -> &PathBuf { + &self.work_dir + } + + pub fn generate_proof( + &self, + circuit: CircuitName, + witness_data: &[u8], + e3_id: &str, + ) -> Result { + if !self.bb_binary.exists() { + return Err(ZkError::BbNotInstalled); + } + + // Circuits are organized as: circuits/{group}/{name}/{name}.json + let circuit_dir = self.circuits_dir.join(circuit.dir_path()); + let circuit_path = circuit_dir.join(format!("{}.json", circuit.as_str())); + let vk_path = circuit_dir.join(format!("{}.vk", circuit.as_str())); + + if !circuit_path.exists() { + return Err(ZkError::CircuitNotFound(format!( + "Circuit not found: {} (expected at {})", + circuit.as_str(), + circuit_path.display() + ))); + } + if !vk_path.exists() { + return Err(ZkError::CircuitNotFound(format!( + "VK not found: {}", + vk_path.display() + ))); + } + + let job_dir = self.work_dir.join(e3_id); + fs::create_dir_all(&job_dir)?; + + let witness_path = job_dir.join("witness.gz"); + let output_dir = job_dir.join("out"); + + fs::write(&witness_path, witness_data)?; + + debug!( + "generating proof for circuit {} using circuit: {}, vk: {}", + circuit.as_str(), + circuit_path.display(), + vk_path.display() + ); + + let output = StdCommand::new(&self.bb_binary) + .args([ + "prove", + "--scheme", + "ultra_honk", + "-b", + &circuit_path.to_string_lossy(), + "-w", + &witness_path.to_string_lossy(), + "-k", + &vk_path.to_string_lossy(), + "-o", + &output_dir.to_string_lossy(), + ]) + .output()?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + return Err(ZkError::ProveFailed(format!( + "bb prove failed:\nstderr: {}\nstdout: {}", + stderr, stdout + ))); + } + + let proof_data = fs::read(output_dir.join("proof"))?; + let public_signals = fs::read(output_dir.join("public_inputs"))?; + + info!( + "generated proof ({} bytes) for {} / {}", + proof_data.len(), + circuit.as_str(), + e3_id + ); + + Ok(Proof::new( + circuit, + ArcBytes::from_bytes(&proof_data), + ArcBytes::from_bytes(&public_signals), + )) + } + + pub fn verify(&self, proof: &Proof, e3_id: &str, party_id: u64) -> Result { + self.verify_proof( + proof.circuit, + &proof.data, + &proof.public_signals, + e3_id, + party_id, + ) + } + + pub fn verify_proof( + &self, + circuit: CircuitName, + proof_data: &[u8], + public_signals: &[u8], + e3_id: &str, + party_id: u64, + ) -> Result { + if !self.bb_binary.exists() { + return Err(ZkError::BbNotInstalled); + } + + let vk_path = self + .circuits_dir + .join(circuit.dir_path()) + .join(format!("{}.vk", circuit.as_str())); + if !vk_path.exists() { + return Err(ZkError::CircuitNotFound(format!( + "VK not found: {}", + vk_path.display() + ))); + } + + let verification_subdir = format!("verify_party_{}", party_id); + + debug!( + "verifying proof for circuit {} (party {}) using VK: {}", + circuit.as_str(), + party_id, + vk_path.display() + ); + + let job_dir = self.work_dir.join(e3_id).join(&verification_subdir); + let out_dir = job_dir.join("out"); + fs::create_dir_all(&out_dir)?; + + let proof_path = job_dir.join("proof"); + let public_inputs_path = out_dir.join("public_inputs"); + + fs::write(&proof_path, proof_data)?; + fs::write(&public_inputs_path, public_signals)?; + + let output = StdCommand::new(&self.bb_binary) + .args([ + "verify", + "--scheme", + "ultra_honk", + "-i", + &public_inputs_path.to_string_lossy(), + "-p", + &proof_path.to_string_lossy(), + "-k", + &vk_path.to_string_lossy(), + ]) + .output()?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + warn!( + "bb verification failed for {}:\nVK: {}\nstderr: {}\nstdout: {}", + circuit.as_str(), + vk_path.display(), + stderr, + stdout + ); + } + + Ok(output.status.success()) + } + + pub fn cleanup(&self, e3_id: &str) -> Result<(), ZkError> { + let job_dir = self.work_dir.join(e3_id); + if job_dir.exists() { + fs::remove_dir_all(&job_dir)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::ZkConfig; + use tempfile::tempdir; + + #[test] + fn test_prover_requires_bb() { + let temp = tempdir().unwrap(); + let temp_path = temp.path(); + let noir_dir = temp_path.join("noir"); + let bb_binary = noir_dir.join("bin").join("bb"); + let circuits_dir = noir_dir.join("circuits"); + let work_dir = noir_dir.join("work").join("test_node"); + let backend = ZkBackend::new(bb_binary, circuits_dir, work_dir, ZkConfig::default()); + let prover = ZkProver::new(&backend); + + let result = prover.generate_proof(CircuitName::PkBfv, b"witness", "e3-1"); + assert!(matches!(result, Err(ZkError::BbNotInstalled))); + } +} diff --git a/crates/zk-prover/src/traits.rs b/crates/zk-prover/src/traits.rs new file mode 100644 index 0000000000..a41c2f156c --- /dev/null +++ b/crates/zk-prover/src/traits.rs @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::error::ZkError; +use crate::prover::ZkProver; +use crate::witness::{CompiledCircuit, WitnessGenerator}; +use e3_events::{CircuitName, Proof}; +use noirc_abi::InputMap; + +/// Trait for types that can generate ZK proofs. +/// +/// Implementors define how to build witness data from their inputs. +/// The prove/verify methods are provided with default implementations. +pub trait Provable: Send + Sync { + type Params: Send + Sync; + type Input: Send + Sync; + + fn circuit(&self) -> CircuitName; + + fn build_witness( + &self, + params: &Self::Params, + input: &Self::Input, + ) -> Result; + + fn prove( + &self, + prover: &ZkProver, + params: &Self::Params, + input: &Self::Input, + e3_id: &str, + ) -> Result { + let inputs = self.build_witness(params, input)?; + + let circuit_name = self.circuit().as_str(); + let circuit_path = prover + .circuits_dir() + .join(self.circuit().dir_path()) + .join(format!("{}.json", circuit_name)); + let circuit = CompiledCircuit::from_file(&circuit_path)?; + + let witness_gen = WitnessGenerator::new(); + let witness = witness_gen.generate_witness(&circuit, inputs)?; + + prover.generate_proof(self.circuit(), &witness, e3_id) + } + + fn verify( + &self, + prover: &ZkProver, + proof: &Proof, + e3_id: &str, + party_id: u64, + ) -> Result { + if proof.circuit != self.circuit() { + return Err(ZkError::VerifyFailed(format!( + "circuit mismatch: expected {}, got {}", + self.circuit(), + proof.circuit + ))); + } + prover.verify(proof, e3_id, party_id) + } +} diff --git a/crates/zk-prover/src/witness.rs b/crates/zk-prover/src/witness.rs new file mode 100644 index 0000000000..d5cca4783f --- /dev/null +++ b/crates/zk-prover/src/witness.rs @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::error::ZkError; +use acir::{ + circuit::Program, + native_types::{WitnessMap, WitnessStack}, + FieldElement, +}; +use base64::engine::{general_purpose, Engine}; +use bn254_blackbox_solver::Bn254BlackBoxSolver; +use flate2::write::GzEncoder; +use flate2::Compression; +use nargo::foreign_calls::default::DefaultForeignCallBuilder; +use nargo::ops::execute_program; +use noirc_abi::{input_parser::InputValue, Abi, InputMap}; +use serde::{Deserialize, Serialize}; +use std::io::Write; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CompiledCircuit { + pub bytecode: String, + pub abi: Abi, +} + +impl CompiledCircuit { + pub fn from_json(json: &str) -> Result { + serde_json::from_str(json).map_err(ZkError::JsonError) + } + + pub fn from_file(path: &std::path::Path) -> Result { + let contents = std::fs::read_to_string(path)?; + Self::from_json(&contents) + } +} + +fn get_acir_buffer(bytecode: &str) -> Result, ZkError> { + general_purpose::STANDARD + .decode(bytecode) + .map_err(|e| ZkError::SerializationError(format!("base64 decode: {}", e))) +} + +fn get_program(bytecode: &str) -> Result, ZkError> { + let acir_buffer = get_acir_buffer(bytecode)?; + Program::deserialize_program(&acir_buffer) + .map_err(|e| ZkError::SerializationError(format!("ACIR decode: {:?}", e))) +} + +fn execute( + bytecode: &str, + initial_witness: WitnessMap, +) -> Result, ZkError> { + let program = get_program(bytecode)?; + let blackbox_solver = Bn254BlackBoxSolver::default(); + let mut foreign_call_executor = DefaultForeignCallBuilder::default().build(); + + execute_program( + &program, + initial_witness, + &blackbox_solver, + &mut foreign_call_executor, + ) + .map_err(|e| ZkError::WitnessGenerationFailed(e.to_string())) +} + +fn serialize_witness(witness_stack: &WitnessStack) -> Result, ZkError> { + let buf = bincode::serialize(witness_stack) + .map_err(|e| ZkError::SerializationError(format!("bincode: {}", e)))?; + + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder + .write_all(&buf) + .map_err(|e| ZkError::SerializationError(format!("gzip: {}", e)))?; + encoder + .finish() + .map_err(|e| ZkError::SerializationError(format!("gzip finish: {}", e))) +} + +pub struct WitnessGenerator; + +impl WitnessGenerator { + pub fn new() -> Self { + Self + } + + pub fn generate_witness( + &self, + circuit: &CompiledCircuit, + inputs: InputMap, + ) -> Result, ZkError> { + let initial_witness = circuit + .abi + .encode(&inputs, None) + .map_err(|e| ZkError::WitnessGenerationFailed(format!("ABI encode: {:?}", e)))?; + + let witness_stack = execute(&circuit.bytecode, initial_witness)?; + serialize_witness(&witness_stack) + } +} + +impl Default for WitnessGenerator { + fn default() -> Self { + Self::new() + } +} + +pub fn input_map(iter: I) -> Result +where + I: IntoIterator, + K: Into, + V: AsRef, +{ + iter.into_iter() + .map(|(k, v)| { + let key = k.into(); + let field = FieldElement::try_from_str(v.as_ref()).ok_or_else(|| { + ZkError::SerializationError(format!( + "invalid field element for key '{}': {}", + key, + v.as_ref() + )) + })?; + Ok((key, InputValue::Field(field))) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + const DUMMY_CIRCUIT: &str = r#"{"noir_version":"1.0.0-beta.15+83245db91dcf63420ef4bcbbd85b98f397fee663","hash":"15412581843239610929","abi":{"parameters":[{"name":"x","type":{"kind":"field"},"visibility":"private"},{"name":"y","type":{"kind":"field"},"visibility":"private"},{"name":"_sum","type":{"kind":"field"},"visibility":"public"}],"return_type":null,"error_types":{}},"bytecode":"H4sIAAAAAAAA/5WOMQ5AMBRA/y8HMbIRRxCJSYwWg8RiIGIz9gjiAk4hHKeb0WLX0KHRDu1bXvL/y89H+HCFu7rtCTeCiiPsgRFo06LUhk0+smgN9iLdKC0rPz6z6RjmhN3LxffE/O7byg+hZv7nAb2HRPkUAQAA","debug_symbols":"jZDRCoMwDEX/Jc996MbG1F8ZQ2qNUghtie1giP++KLrpw2BPaXJ7bsgdocUm97XzXRiguo/QsCNyfU3BmuSCl+k4KdjaOjGijGCnCxUNo09Q+Uyk4GkoL5+GaPxSk2FRtQL0rVQx7Bzh/JrUl9a/0Vu5ssXlA1//psvbSp90ccAf0hnr+HAuaKjO0+zGzjSEawRd9naXSHrFTdkyixwstplxtls0WfAG","file_map":{"50":{"source":"pub fn main(\n x: Field,\n y: Field,\n _sum: pub Field\n) {\n let sum = x + y;\n assert(sum == _sum);\n}\n","path":"/Users/ctrlc03/Documents/zk/enclave/circuits/bin/dummy/src/main.nr"}},"expression_width":{"Bounded":{"width":4}}}"#; + + #[test] + fn test_load_circuit() { + let circuit = CompiledCircuit::from_json(DUMMY_CIRCUIT).unwrap(); + assert_eq!(circuit.abi.parameters.len(), 3); + } + + #[test] + fn test_generate_witness() { + let circuit = CompiledCircuit::from_json(DUMMY_CIRCUIT).unwrap(); + let generator = WitnessGenerator::new(); + let inputs = input_map([("x", "5"), ("y", "3"), ("_sum", "8")]).unwrap(); + + let witness = generator.generate_witness(&circuit, inputs).unwrap(); + + assert!(witness.len() > 2); + assert_eq!(witness[0], 0x1f); + assert_eq!(witness[1], 0x8b); + } + + #[test] + fn test_wrong_sum_fails() { + let circuit = CompiledCircuit::from_json(DUMMY_CIRCUIT).unwrap(); + let generator = WitnessGenerator::new(); + let inputs = input_map([("x", "5"), ("y", "3"), ("_sum", "10")]).unwrap(); + + let result = generator.generate_witness(&circuit, inputs); + assert!(result.is_err()); + } + + #[test] + fn test_invalid_field_element() { + let result = input_map([("x", "not_a_number"), ("y", "3")]); + assert!(result.is_err()); + + let err = result.unwrap_err(); + assert!(matches!(err, ZkError::SerializationError(_))); + assert!(err.to_string().contains("invalid field element")); + assert!(err.to_string().contains("'x'")); + } +} diff --git a/crates/zk-prover/tests/backend_tests.rs b/crates/zk-prover/tests/backend_tests.rs new file mode 100644 index 0000000000..9243fab059 --- /dev/null +++ b/crates/zk-prover/tests/backend_tests.rs @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use e3_zk_prover::{ZkBackend, ZkConfig, ZkProver}; +use tempfile::tempdir; +use tokio::fs; + +fn test_backend(temp_path: &std::path::Path, config: ZkConfig) -> ZkBackend { + let noir_dir = temp_path.join("noir"); + let bb_binary = noir_dir.join("bin").join("bb"); + let circuits_dir = noir_dir.join("circuits"); + let work_dir = noir_dir.join("work").join("test_node"); + ZkBackend::new(bb_binary, circuits_dir, work_dir, config) +} + +#[tokio::test] +async fn test_backend_creates_directories() { + let temp = tempdir().unwrap(); + let backend = test_backend(temp.path(), ZkConfig::default()); + + fs::create_dir_all(&backend.base_dir).await.unwrap(); + fs::create_dir_all(&backend.circuits_dir).await.unwrap(); + fs::create_dir_all(&backend.work_dir).await.unwrap(); + + assert!(backend.base_dir.exists()); + assert!(backend.circuits_dir.exists()); + assert!(backend.work_dir.exists()); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} + +#[tokio::test] +async fn test_work_dir_cleanup() { + let temp = tempdir().unwrap(); + let backend = test_backend(temp.path(), ZkConfig::default()); + + fs::create_dir_all(&backend.work_dir).await.unwrap(); + + let e3_id = "test-e3-123"; + let work_dir = backend.work_dir_for(e3_id).unwrap(); + + fs::create_dir_all(&work_dir).await.unwrap(); + fs::write(work_dir.join("proof.bin"), b"fake proof") + .await + .unwrap(); + fs::write(work_dir.join("witness.bin"), b"fake witness") + .await + .unwrap(); + assert!(work_dir.exists()); + + backend.cleanup_work_dir(e3_id).await.unwrap(); + assert!(!work_dir.exists()); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} + +#[tokio::test] +async fn test_work_dir_path_traversal_protection() { + let temp = tempdir().unwrap(); + let backend = test_backend(temp.path(), ZkConfig::default()); + + // Test path traversal attempts + let invalid_ids = vec!["../etc/passwd", "test/../../../etc", "test/../../secret"]; + + for invalid_id in invalid_ids { + let result = backend.cleanup_work_dir(invalid_id).await; + assert!( + result.is_err(), + "Should reject path traversal: {}", + invalid_id + ); + } + + let result = backend.cleanup_work_dir("").await; + assert!(result.is_err(), "Should reject empty e3_id"); + let result = backend.cleanup_work_dir("test\0bad").await; + assert!(result.is_err(), "Should reject null byte in e3_id"); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} + +#[test] +fn test_prover_requires_bb() { + let temp = tempdir().unwrap(); + let backend = test_backend(temp.path(), ZkConfig::default()); + let prover = ZkProver::new(&backend); + + let result = prover.generate_proof(e3_events::CircuitName::PkBfv, b"witness", "e3-1"); + assert!(matches!(result, Err(e3_zk_prover::ZkError::BbNotInstalled))); +} diff --git a/crates/zk-prover/tests/common/mod.rs b/crates/zk-prover/tests/common/mod.rs new file mode 100644 index 0000000000..0592b197b6 --- /dev/null +++ b/crates/zk-prover/tests/common/mod.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use std::path::PathBuf; + +pub fn fixtures_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("fixtures") +} diff --git a/crates/zk-prover/tests/fixtures/dummy.json b/crates/zk-prover/tests/fixtures/dummy.json new file mode 100644 index 0000000000..9a4c224d19 --- /dev/null +++ b/crates/zk-prover/tests/fixtures/dummy.json @@ -0,0 +1,22 @@ +{ + "noir_version": "1.0.0-beta.15+83245db91dcf63420ef4bcbbd85b98f397fee663", + "hash": "13330399022024109035", + "abi": { + "parameters": [ + { "name": "x", "type": { "kind": "field" }, "visibility": "private" }, + { "name": "y", "type": { "kind": "field" }, "visibility": "private" }, + { "name": "_sum", "type": { "kind": "field" }, "visibility": "private" } + ], + "return_type": null, + "error_types": {} + }, + "bytecode": "H4sIAAAAAAAA/5WOMQ5AQBBFZ4aDKOmII4hEJUqNQqJRENEp9wjiAk4hHGc7pUZvE0skmtnXvGL+/HyEG1u7KeuWlBH+WNoOsECTLH6yfpX2Mpi9NYsXIfLCDfdk2Loxkud0qDvxe9/NzyBi/Fx0+gP3FAEAAA==", + "debug_symbols": "jZDdCoMwDIXfJde9kA1/8FXGkFqjFEJbYjsY4rsvim56MfAqTU6/E3Im6LBNQ2Nd70eoHxO0bIns0JA3OlrvZDrNCva2iYwoIzjoQgXN6CLULhEpeGlK66cxaLfWqFnUTAG6TqoY9pZwec3qR2f/0bza2OL+hfPLdFlsdFWe6Kd02lg+XQsZ1Ld5MWOrW8ItgT45cwgkvsOu7JEF9ga7xLjYrZos+AA=", + "file_map": { + "50": { + "source": "fn main (x: Field, y: Field, _sum: Field) {\n let sum = x + y;\n assert(sum == _sum);\n}", + "path": "/Users/ctrlc03/Documents/zk/enclave/circuits/bin/dummy/src/main.nr" + } + }, + "expression_width": { "Bounded": { "width": 4 } } +} diff --git a/crates/zk-prover/tests/fixtures/dummy.vk b/crates/zk-prover/tests/fixtures/dummy.vk new file mode 100644 index 0000000000..ccf7644ebc Binary files /dev/null and b/crates/zk-prover/tests/fixtures/dummy.vk differ diff --git a/crates/zk-prover/tests/fixtures/pk.json b/crates/zk-prover/tests/fixtures/pk.json new file mode 100644 index 0000000000..d5834b1b29 --- /dev/null +++ b/crates/zk-prover/tests/fixtures/pk.json @@ -0,0 +1,65 @@ +{ + "noir_version": "1.0.0-beta.15+83245db91dcf63420ef4bcbbd85b98f397fee663", + "hash": "2071989265523136017", + "abi": { + "parameters": [ + { + "name": "pk0is", + "type": { + "kind": "array", + "length": 1, + "type": { + "kind": "struct", + "path": "lib::math::polynomial::Polynomial", + "fields": [{ "name": "coefficients", "type": { "kind": "array", "length": 512, "type": { "kind": "field" } } }] + } + }, + "visibility": "private" + }, + { + "name": "pk1is", + "type": { + "kind": "array", + "length": 1, + "type": { + "kind": "struct", + "path": "lib::math::polynomial::Polynomial", + "fields": [{ "name": "coefficients", "type": { "kind": "array", "length": 512, "type": { "kind": "field" } } }] + } + }, + "visibility": "private" + } + ], + "return_type": { "abi_type": { "kind": "field" }, "visibility": "public" }, + "error_types": {} + }, + "bytecode": "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", + "debug_symbols": "pdjRattIFIfxd/F1Lub8z2hG01dZluKmbjEYJ7jJwlL67mvX5ztOFiTSpDdK4upDkc5PM+Tn5uvuy/P3z/vjt4cfm09//dx8Oe0Ph/33z4eH++3T/uF4/unPX3cbvv38dNrtzj/avPj8fNbj9rQ7Pm0+HZ8Ph7vNP9vD8+//9ONxe/x9fNqezp+Wu83u+PV8PAe/7Q+7y1e/7m5nl+VTa5/j5DrPefr0+nxbPn+UGuePNuX5Ta/O1/L5bVQuoJdyu4JeXxV8pdCtR6H1aSwV6nLBy8Q1eHnxW/Tp7YXqWehjqdCWC9P5XxSmaS7vKTTLQpO/q1BKFsw+WlBdKqzMQy+qOQ+zLT1Nsw8PhOnDE7GeeNNIWP3wTKwm3jYU64k3TcWbE+8cizlfEza1xWc6VsbCL59ex8J7WUqorF5FyeEci3dTa8Npl6G5XoVNt6s4j/pbE9M8GndzvHhl/j+x8s4cVrkXw0ZdTKw801qbsXLUcbsKG39wL1o+EZX+nnvxKiG963aOeruddfkq1ubCZDmdvvi60Mp09ub8Ir1NtnQVXj78UN0+/lDXfpHZuZ197tOrq/j7/N32fn96tQnalHPw/JKy60HXg18P9XqYrod2PfTrYb4eRpxOJjoWIYuSRcqiZRGzqFnkLHqKnriu6Cl6ip6ip+gpeoqeoufR8+g5v2j0PHoePY+eR8+j59Gr0avRq9Gr3Lno1ejV6NXo1ejV6E3Rm6I3RW+K3sSjiN4UvSl6U/Sm6LXotei16LXoteg1nm30WvRa9Fr0evR69Hr0evR69Hr0OsMSvR69Hr05enP05ujN0ZujN0dvjt7M9EVvjt6I3ojeiN6I3ojeiN6I3ojeYJxznhnowkQXRrow04WhLkx1YawLc10Y7EL5RoVyYkktySW9JJgUk2QwY6AxpULKuDHgGHIMOoYdA4+hx+Bj+DFP4JQhZBgyEBmKDEaGIwOSIcmgZDXfHZTRZHAyPBmgDFEGKcOUgcpQZVO+ligDy5Bl0DJsGbgMXQYvw5cBzFq+8ShjzEBmKDOYGc4MaIY0g5phzXq+TCnDzfBmgDPEGeQMcwY6Q53BzuZ8T1NGnkHPsGfgM/QZ/Ax/BkBDoI1cAnINYBHAoDAoDAqDwqAwKAwKg8KgLJcXyhgUBoVBYVAYFAaFQeW6lQvXbeWinGtXLl65euXyletXLmAYFAaFQXkuipQxKAwKg8KgMCgMCoPCoDComustZQwKg8KgMCgMCoPCoDAoDGrKpZwyBoVBYVAYFAaFQWFQGBQG1XKXQBmDwqAwKAwKg8KgMCgMCoPquQGhjEFhUBgUBoVBYVAYFAaFQc25t6GMQWFQGBQGhUFhUBgUBoVBjdw25b6JjRMGHYOOQcegY9Ax6Bh0DDoG3XJLRhmDjkHHoGPQMegYdAw6Bh2DrtztUcagY9Ax6Bh0DDoGPXeRuY3MfeRtI0k5t5K5l8zNZO4mczuJQcegXwy6Ll/o8sWvy17/tN9+Oezib5bfno/3L/6E+fTvI5/wR87H08P97uvzaXfZ6f/+7Lz3/w8=", + "file_map": { + "19": { + "source": "// Exposed only for usage in `std::meta`\npub(crate) mod poseidon2;\n\nuse crate::default::Default;\nuse crate::embedded_curve_ops::{\n EmbeddedCurvePoint, EmbeddedCurveScalar, multi_scalar_mul, multi_scalar_mul_array_return,\n};\nuse crate::meta::derive_via;\n\n#[foreign(sha256_compression)]\n// docs:start:sha256_compression\npub fn sha256_compression(input: [u32; 16], state: [u32; 8]) -> [u32; 8] {}\n// docs:end:sha256_compression\n\n#[foreign(keccakf1600)]\n// docs:start:keccakf1600\npub fn keccakf1600(input: [u64; 25]) -> [u64; 25] {}\n// docs:end:keccakf1600\n\npub mod keccak {\n #[deprecated(\"This function has been moved to std::hash::keccakf1600\")]\n pub fn keccakf1600(input: [u64; 25]) -> [u64; 25] {\n super::keccakf1600(input)\n }\n}\n\n#[foreign(blake2s)]\n// docs:start:blake2s\npub fn blake2s(input: [u8; N]) -> [u8; 32]\n// docs:end:blake2s\n{}\n\n// docs:start:blake3\npub fn blake3(input: [u8; N]) -> [u8; 32]\n// docs:end:blake3\n{\n if crate::runtime::is_unconstrained() {\n // Temporary measure while Barretenberg is main proving system.\n // Please open an issue if you're working on another proving system and running into problems due to this.\n crate::static_assert(\n N <= 1024,\n \"Barretenberg cannot prove blake3 hashes with inputs larger than 1024 bytes\",\n );\n }\n __blake3(input)\n}\n\n#[foreign(blake3)]\nfn __blake3(input: [u8; N]) -> [u8; 32] {}\n\n// docs:start:pedersen_commitment\npub fn pedersen_commitment(input: [Field; N]) -> EmbeddedCurvePoint {\n // docs:end:pedersen_commitment\n pedersen_commitment_with_separator(input, 0)\n}\n\n#[inline_always]\npub fn pedersen_commitment_with_separator(\n input: [Field; N],\n separator: u32,\n) -> EmbeddedCurvePoint {\n let mut points = [EmbeddedCurveScalar { lo: 0, hi: 0 }; N];\n for i in 0..N {\n // we use the unsafe version because the multi_scalar_mul will constrain the scalars.\n points[i] = from_field_unsafe(input[i]);\n }\n let generators = derive_generators(\"DEFAULT_DOMAIN_SEPARATOR\".as_bytes(), separator);\n multi_scalar_mul(generators, points)\n}\n\n// docs:start:pedersen_hash\npub fn pedersen_hash(input: [Field; N]) -> Field\n// docs:end:pedersen_hash\n{\n pedersen_hash_with_separator(input, 0)\n}\n\n#[no_predicates]\npub fn pedersen_hash_with_separator(input: [Field; N], separator: u32) -> Field {\n let mut scalars: [EmbeddedCurveScalar; N + 1] = [EmbeddedCurveScalar { lo: 0, hi: 0 }; N + 1];\n let mut generators: [EmbeddedCurvePoint; N + 1] =\n [EmbeddedCurvePoint::point_at_infinity(); N + 1];\n let domain_generators: [EmbeddedCurvePoint; N] =\n derive_generators(\"DEFAULT_DOMAIN_SEPARATOR\".as_bytes(), separator);\n\n for i in 0..N {\n scalars[i] = from_field_unsafe(input[i]);\n generators[i] = domain_generators[i];\n }\n scalars[N] = EmbeddedCurveScalar { lo: N as Field, hi: 0 as Field };\n\n let length_generator: [EmbeddedCurvePoint; 1] =\n derive_generators(\"pedersen_hash_length\".as_bytes(), 0);\n generators[N] = length_generator[0];\n multi_scalar_mul_array_return(generators, scalars, true)[0].x\n}\n\n#[field(bn254)]\n#[inline_always]\npub fn derive_generators(\n domain_separator_bytes: [u8; M],\n starting_index: u32,\n) -> [EmbeddedCurvePoint; N] {\n crate::assert_constant(domain_separator_bytes);\n // TODO(https://github.com/noir-lang/noir/issues/5672): Add back assert_constant on starting_index\n __derive_generators(domain_separator_bytes, starting_index)\n}\n\n#[builtin(derive_pedersen_generators)]\n#[field(bn254)]\nfn __derive_generators(\n domain_separator_bytes: [u8; M],\n starting_index: u32,\n) -> [EmbeddedCurvePoint; N] {}\n\n#[field(bn254)]\n// Same as from_field but:\n// does not assert the limbs are 128 bits\n// does not assert the decomposition does not overflow the EmbeddedCurveScalar\nfn from_field_unsafe(scalar: Field) -> EmbeddedCurveScalar {\n // Safety: xlo and xhi decomposition is checked below\n let (xlo, xhi) = unsafe { crate::field::bn254::decompose_hint(scalar) };\n // Check that the decomposition is correct\n assert_eq(scalar, xlo + crate::field::bn254::TWO_POW_128 * xhi);\n EmbeddedCurveScalar { lo: xlo, hi: xhi }\n}\n\npub fn poseidon2_permutation(input: [Field; N], state_len: u32) -> [Field; N] {\n assert_eq(input.len(), state_len);\n poseidon2_permutation_internal(input)\n}\n\n#[foreign(poseidon2_permutation)]\nfn poseidon2_permutation_internal(input: [Field; N]) -> [Field; N] {}\n\n// Generic hashing support.\n// Partially ported and impacted by rust.\n\n// Hash trait shall be implemented per type.\n#[derive_via(derive_hash)]\npub trait Hash {\n fn hash(self, state: &mut H)\n where\n H: Hasher;\n}\n\n// docs:start:derive_hash\ncomptime fn derive_hash(s: TypeDefinition) -> Quoted {\n let name = quote { $crate::hash::Hash };\n let signature = quote { fn hash(_self: Self, _state: &mut H) where H: $crate::hash::Hasher };\n let for_each_field = |name| quote { _self.$name.hash(_state); };\n crate::meta::make_trait_impl(\n s,\n name,\n signature,\n for_each_field,\n quote {},\n |fields| fields,\n )\n}\n// docs:end:derive_hash\n\n// Hasher trait shall be implemented by algorithms to provide hash-agnostic means.\n// TODO: consider making the types generic here ([u8], [Field], etc.)\npub trait Hasher {\n fn finish(self) -> Field;\n\n fn write(&mut self, input: Field);\n}\n\n// BuildHasher is a factory trait, responsible for production of specific Hasher.\npub trait BuildHasher {\n type H: Hasher;\n\n fn build_hasher(self) -> H;\n}\n\npub struct BuildHasherDefault;\n\nimpl BuildHasher for BuildHasherDefault\nwhere\n H: Hasher + Default,\n{\n type H = H;\n\n fn build_hasher(_self: Self) -> H {\n H::default()\n }\n}\n\nimpl Default for BuildHasherDefault\nwhere\n H: Hasher + Default,\n{\n fn default() -> Self {\n BuildHasherDefault {}\n }\n}\n\nimpl Hash for Field {\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n H::write(state, self);\n }\n}\n\nimpl Hash for u1 {\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n H::write(state, self as Field);\n }\n}\n\nimpl Hash for u8 {\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n H::write(state, self as Field);\n }\n}\n\nimpl Hash for u16 {\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n H::write(state, self as Field);\n }\n}\n\nimpl Hash for u32 {\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n H::write(state, self as Field);\n }\n}\n\nimpl Hash for u64 {\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n H::write(state, self as Field);\n }\n}\n\nimpl Hash for u128 {\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n H::write(state, self as Field);\n }\n}\n\nimpl Hash for i8 {\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n H::write(state, self as u8 as Field);\n }\n}\n\nimpl Hash for i16 {\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n H::write(state, self as u16 as Field);\n }\n}\n\nimpl Hash for i32 {\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n H::write(state, self as u32 as Field);\n }\n}\n\nimpl Hash for i64 {\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n H::write(state, self as u64 as Field);\n }\n}\n\nimpl Hash for bool {\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n H::write(state, self as Field);\n }\n}\n\nimpl Hash for () {\n fn hash(_self: Self, _state: &mut H)\n where\n H: Hasher,\n {}\n}\n\nimpl Hash for [T; N]\nwhere\n T: Hash,\n{\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n for elem in self {\n elem.hash(state);\n }\n }\n}\n\nimpl Hash for [T]\nwhere\n T: Hash,\n{\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n self.len().hash(state);\n for elem in self {\n elem.hash(state);\n }\n }\n}\n\nimpl Hash for (A, B)\nwhere\n A: Hash,\n B: Hash,\n{\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n self.0.hash(state);\n self.1.hash(state);\n }\n}\n\nimpl Hash for (A, B, C)\nwhere\n A: Hash,\n B: Hash,\n C: Hash,\n{\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n self.0.hash(state);\n self.1.hash(state);\n self.2.hash(state);\n }\n}\n\nimpl Hash for (A, B, C, D)\nwhere\n A: Hash,\n B: Hash,\n C: Hash,\n D: Hash,\n{\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n self.0.hash(state);\n self.1.hash(state);\n self.2.hash(state);\n self.3.hash(state);\n }\n}\n\nimpl Hash for (A, B, C, D, E)\nwhere\n A: Hash,\n B: Hash,\n C: Hash,\n D: Hash,\n E: Hash,\n{\n fn hash(self, state: &mut H)\n where\n H: Hasher,\n {\n self.0.hash(state);\n self.1.hash(state);\n self.2.hash(state);\n self.3.hash(state);\n self.4.hash(state);\n }\n}\n\n// Some test vectors for Pedersen hash and Pedersen Commitment.\n// They have been generated using the same functions so the tests are for now useless\n// but they will be useful when we switch to Noir implementation.\n#[test]\nfn assert_pedersen() {\n assert_eq(\n pedersen_hash_with_separator([1], 1),\n 0x1b3f4b1a83092a13d8d1a59f7acb62aba15e7002f4440f2275edb99ebbc2305f,\n );\n assert_eq(\n pedersen_commitment_with_separator([1], 1),\n EmbeddedCurvePoint {\n x: 0x054aa86a73cb8a34525e5bbed6e43ba1198e860f5f3950268f71df4591bde402,\n y: 0x209dcfbf2cfb57f9f6046f44d71ac6faf87254afc7407c04eb621a6287cac126,\n is_infinite: false,\n },\n );\n\n assert_eq(\n pedersen_hash_with_separator([1, 2], 2),\n 0x26691c129448e9ace0c66d11f0a16d9014a9e8498ee78f4d69f0083168188255,\n );\n assert_eq(\n pedersen_commitment_with_separator([1, 2], 2),\n EmbeddedCurvePoint {\n x: 0x2e2b3b191e49541fe468ec6877721d445dcaffe41728df0a0eafeb15e87b0753,\n y: 0x2ff4482400ad3a6228be17a2af33e2bcdf41be04795f9782bd96efe7e24f8778,\n is_infinite: false,\n },\n );\n assert_eq(\n pedersen_hash_with_separator([1, 2, 3], 3),\n 0x0bc694b7a1f8d10d2d8987d07433f26bd616a2d351bc79a3c540d85b6206dbe4,\n );\n assert_eq(\n pedersen_commitment_with_separator([1, 2, 3], 3),\n EmbeddedCurvePoint {\n x: 0x1fee4e8cf8d2f527caa2684236b07c4b1bad7342c01b0f75e9a877a71827dc85,\n y: 0x2f9fedb9a090697ab69bf04c8bc15f7385b3e4b68c849c1536e5ae15ff138fd1,\n is_infinite: false,\n },\n );\n assert_eq(\n pedersen_hash_with_separator([1, 2, 3, 4], 4),\n 0xdae10fb32a8408521803905981a2b300d6a35e40e798743e9322b223a5eddc,\n );\n assert_eq(\n pedersen_commitment_with_separator([1, 2, 3, 4], 4),\n EmbeddedCurvePoint {\n x: 0x07ae3e202811e1fca39c2d81eabe6f79183978e6f12be0d3b8eda095b79bdbc9,\n y: 0x0afc6f892593db6fbba60f2da558517e279e0ae04f95758587760ba193145014,\n is_infinite: false,\n },\n );\n assert_eq(\n pedersen_hash_with_separator([1, 2, 3, 4, 5], 5),\n 0xfc375b062c4f4f0150f7100dfb8d9b72a6d28582dd9512390b0497cdad9c22,\n );\n assert_eq(\n pedersen_commitment_with_separator([1, 2, 3, 4, 5], 5),\n EmbeddedCurvePoint {\n x: 0x1754b12bd475a6984a1094b5109eeca9838f4f81ac89c5f0a41dbce53189bb29,\n y: 0x2da030e3cfcdc7ddad80eaf2599df6692cae0717d4e9f7bfbee8d073d5d278f7,\n is_infinite: false,\n },\n );\n assert_eq(\n pedersen_hash_with_separator([1, 2, 3, 4, 5, 6], 6),\n 0x1696ed13dc2730062a98ac9d8f9de0661bb98829c7582f699d0273b18c86a572,\n );\n assert_eq(\n pedersen_commitment_with_separator([1, 2, 3, 4, 5, 6], 6),\n EmbeddedCurvePoint {\n x: 0x190f6c0e97ad83e1e28da22a98aae156da083c5a4100e929b77e750d3106a697,\n y: 0x1f4b60f34ef91221a0b49756fa0705da93311a61af73d37a0c458877706616fb,\n is_infinite: false,\n },\n );\n assert_eq(\n pedersen_hash_with_separator([1, 2, 3, 4, 5, 6, 7], 7),\n 0x128c0ff144fc66b6cb60eeac8a38e23da52992fc427b92397a7dffd71c45ede3,\n );\n assert_eq(\n pedersen_commitment_with_separator([1, 2, 3, 4, 5, 6, 7], 7),\n EmbeddedCurvePoint {\n x: 0x015441e9d29491b06563fac16fc76abf7a9534c715421d0de85d20dbe2965939,\n y: 0x1d2575b0276f4e9087e6e07c2cb75aa1baafad127af4be5918ef8a2ef2fea8fc,\n is_infinite: false,\n },\n );\n assert_eq(\n pedersen_hash_with_separator([1, 2, 3, 4, 5, 6, 7, 8], 8),\n 0x2f960e117482044dfc99d12fece2ef6862fba9242be4846c7c9a3e854325a55c,\n );\n assert_eq(\n pedersen_commitment_with_separator([1, 2, 3, 4, 5, 6, 7, 8], 8),\n EmbeddedCurvePoint {\n x: 0x1657737676968887fceb6dd516382ea13b3a2c557f509811cd86d5d1199bc443,\n y: 0x1f39f0cb569040105fa1e2f156521e8b8e08261e635a2b210bdc94e8d6d65f77,\n is_infinite: false,\n },\n );\n assert_eq(\n pedersen_hash_with_separator([1, 2, 3, 4, 5, 6, 7, 8, 9], 9),\n 0x0c96db0790602dcb166cc4699e2d306c479a76926b81c2cb2aaa92d249ec7be7,\n );\n assert_eq(\n pedersen_commitment_with_separator([1, 2, 3, 4, 5, 6, 7, 8, 9], 9),\n EmbeddedCurvePoint {\n x: 0x0a3ceae42d14914a432aa60ec7fded4af7dad7dd4acdbf2908452675ec67e06d,\n y: 0xfc19761eaaf621ad4aec9a8b2e84a4eceffdba78f60f8b9391b0bd9345a2f2,\n is_infinite: false,\n },\n );\n assert_eq(\n pedersen_hash_with_separator([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10),\n 0x2cd37505871bc460a62ea1e63c7fe51149df5d0801302cf1cbc48beb8dff7e94,\n );\n assert_eq(\n pedersen_commitment_with_separator([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10),\n EmbeddedCurvePoint {\n x: 0x2fb3f8b3d41ddde007c8c3c62550f9a9380ee546fcc639ffbb3fd30c8d8de30c,\n y: 0x300783be23c446b11a4c0fabf6c91af148937cea15fcf5fb054abf7f752ee245,\n is_infinite: false,\n },\n );\n}\n", + "path": "std/hash/mod.nr" + }, + "50": { + "source": "// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\nuse lib::configs::default::dkg::{L, N};\nuse lib::configs::default::dkg::PK_BIT_PK;\nuse lib::core::dkg::pk::Pk;\nuse lib::math::polynomial::Polynomial;\n\nfn main(pk0is: [Polynomial; L], pk1is: [Polynomial; L]) -> pub Field {\n let pk: Pk = Pk::new(pk0is, pk1is);\n pk.execute()\n}\n", + "path": "enclave/circuits/bin/dkg/pk/src/main.nr" + }, + "62": { + "source": "// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\nuse crate::math::commitments::compute_dkg_pk_commitment;\nuse crate::math::polynomial::Polynomial;\n\n/// Correct DKG Public Key Circuit (Circuit 0).\npub struct Pk {\n /// Correct DKG public key components\n /// pk0[i] is the first component for modulus i\n pk0: [Polynomial; L],\n /// pk1[i] is the second component for modulus i\n pk1: [Polynomial; L],\n}\n\nimpl Pk {\n pub fn new(pk0: [Polynomial; L], pk1: [Polynomial; L]) -> Self {\n Pk { pk0, pk1 }\n }\n\n /// Main verification function\n /// Returns commitment to correct DKG public key\n pub fn execute(self) -> Field {\n compute_dkg_pk_commitment::(self.pk0, self.pk1)\n }\n}\n", + "path": "enclave/circuits/lib/src/core/dkg/pk.nr" + }, + "74": { + "source": "// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\nuse crate::math::helpers::{compute_safe, flatten};\nuse crate::math::polynomial::Polynomial;\n\n/// DOMAIN SEPARATORS\n\n// Domain separator - \"PK\"\npub global DS_PK: [u8; 64] = [\n 0x50, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n];\n// Domain separator - \"PK_GENERATION\"\npub global DS_PK_GENERATION: [u8; 64] = [\n 0x50, 0x4b, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n];\n// Domain separator - \"SHARE_COMPUTATION\"\npub global DS_SHARE_COMPUTATION: [u8; 64] = [\n 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x55, 0x54, 0x41, 0x54, 0x49, 0x4f,\n 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n];\n// Domain separator - \"SHARE_ENCRYPTION\"\npub global DS_SHARE_ENCRYPTION: [u8; 64] = [\n 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x45, 0x4e, 0x43, 0x52, 0x59, 0x50, 0x54, 0x49, 0x4f, 0x4e,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n];\n// Domain separator - \"PK_AGGREGATION\"\npub global DS_PK_AGGREGATION: [u8; 64] = [\n 0x50, 0x4b, 0x5f, 0x41, 0x47, 0x47, 0x52, 0x45, 0x47, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n];\n// Domain separator - \"CIPHERTEXT\"\npub global DS_CIPHERTEXT: [u8; 64] = [\n 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n];\n// Domain separator - \"AGGREGATED_SHARES\"\npub global DS_AGGREGATED_SHARES: [u8; 64] = [\n 0x41, 0x47, 0x47, 0x52, 0x45, 0x47, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x53, 0x48, 0x41, 0x52, 0x45,\n 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n];\n// Domain separator - \"RECURSIVE_AGGREGATION\"\npub global DS_RECURSIVE_AGGREGATION: [u8; 64] = [\n 0x52, 0x45, 0x43, 0x55, 0x52, 0x53, 0x49, 0x56, 0x45, 0x5f, 0x41, 0x47, 0x47, 0x52, 0x45, 0x47,\n 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n];\n// Domain separator - \"CLG_PK_GENERATION\"\npub global DS_CLG_PK_GENERATION: [u8; 64] = [\n 0x43, 0x4c, 0x47, 0x5f, 0x50, 0x4b, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f,\n 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n];\n// Domain separator - \"CLG_SHARE_ENCRYPTION\"\npub global DS_CLG_SHARE_ENCRYPTION: [u8; 64] = [\n 0x43, 0x4c, 0x47, 0x5f, 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x45, 0x4e, 0x43, 0x52, 0x59, 0x50,\n 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n];\n// Domain separator - \"CLG_USER_DATA_ENCRYPTION\"\npub global DS_CLG_USER_DATA_ENCRYPTION: [u8; 64] = [\n 0x43, 0x4c, 0x47, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x45, 0x4e,\n 0x43, 0x52, 0x59, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n];\n// Domain separator - \"CLG_SHARE_DECRYPTION\"\npub global DS_CLG_SHARE_DECRYPTION: [u8; 64] = [\n 0x43, 0x4c, 0x47, 0x5f, 0x53, 0x48, 0x41, 0x52, 0x45, 0x5f, 0x44, 0x45, 0x43, 0x52, 0x59, 0x50,\n 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n];\n\n/// WRAPPERS\n\npub fn compute_commitments(\n payload: Vec,\n domain_separator: [u8; 64],\n io_pattern: [u32; 2],\n) -> Vec {\n compute_safe(domain_separator, payload, io_pattern)\n}\n\npub fn single_polynomial_payload(\n payload: Vec,\n input: Polynomial,\n) -> Vec {\n flatten::<_, _, BIT_POLY>(payload, [input])\n}\n\npub fn multiple_polynomial_payload(\n payload: Vec,\n inputs: [Polynomial; L],\n) -> Vec {\n flatten::<_, _, BIT_POLY>(payload, inputs)\n}\n\n/// COMMITMENTS\n\npub fn compute_dkg_pk_commitment(\n pk0: [Polynomial; L],\n pk1: [Polynomial; L],\n) -> Field {\n let mut payload = multiple_polynomial_payload::(Vec::new(), pk0);\n payload = multiple_polynomial_payload::(payload, pk1);\n\n compute_commitments(payload, DS_PK, [0x80000000 | payload.len(), 1]).get(0)\n}\n\npub fn compute_threshold_pk_commitment(\n pk0: [Polynomial; L],\n pk1: [Polynomial; L],\n) -> Field {\n let mut payload = multiple_polynomial_payload::(Vec::new(), pk0);\n payload = multiple_polynomial_payload::(payload, pk1);\n\n compute_commitments(payload, DS_PK_GENERATION, [0x80000000 | payload.len(), 1]).get(0)\n}\n\npub fn compute_share_computation_sk_commitment(\n sk: Polynomial,\n) -> Field {\n let mut payload = single_polynomial_payload::(Vec::new(), sk);\n compute_commitments(\n payload,\n DS_SHARE_COMPUTATION,\n [0x80000000 | payload.len(), 1],\n )\n .get(0)\n}\n\npub fn compute_share_computation_e_sm_commitment(\n e_sm: [Polynomial; L],\n) -> Field {\n let mut payload = multiple_polynomial_payload::(Vec::new(), e_sm);\n compute_commitments(\n payload,\n DS_SHARE_COMPUTATION,\n [0x80000000 | payload.len(), 1],\n )\n .get(0)\n}\n\npub fn compute_share_encryption_commitment_from_message(\n message: Polynomial,\n) -> Field {\n let mut payload = single_polynomial_payload::(Vec::new(), message);\n compute_commitments(\n payload,\n DS_SHARE_ENCRYPTION,\n [0x80000000 | payload.len(), 1],\n )\n .get(0)\n}\n\npub fn compute_share_encryption_commitment_from_shares(\n y: [[[Field; N_PARTIES + 1]; L]; N],\n party_idx: u32,\n mod_idx: u32,\n) -> Field {\n let mut payload = Vec::new();\n\n for coeff_idx in 0..N {\n payload.push(y[coeff_idx][mod_idx][party_idx + 1]);\n }\n\n // Include party_idx and mod_idx in the hash\n payload.push(party_idx as Field);\n payload.push(mod_idx as Field);\n\n compute_commitments(\n payload,\n DS_SHARE_ENCRYPTION,\n [0x80000000 | payload.len(), 1],\n )\n .get(0)\n}\n\npub fn compute_aggregated_shares_commitment(\n agg_shares: [Polynomial; L],\n) -> Field {\n let mut payload = multiple_polynomial_payload::(Vec::new(), agg_shares);\n compute_commitments(\n payload,\n DS_AGGREGATED_SHARES,\n [0x80000000 | payload.len(), 1],\n )\n .get(0)\n}\n\npub fn compute_pk_aggregation_commitment(\n pk0: [Polynomial; L],\n pk1: [Polynomial; L],\n) -> Field {\n let mut payload = multiple_polynomial_payload::(Vec::new(), pk0);\n payload = multiple_polynomial_payload::(payload, pk1);\n\n compute_commitments(payload, DS_PK_AGGREGATION, [0x80000000 | payload.len(), 1]).get(0)\n}\n\npub fn compute_recursive_aggregation_commitment(payload: Vec) -> Field {\n compute_safe(\n DS_RECURSIVE_AGGREGATION,\n payload,\n [0x80000000 | payload.len(), 1],\n )\n .get(0)\n}\n\npub fn compute_ciphertext_commitment(\n ct0: [Polynomial; L],\n ct1: [Polynomial; L],\n) -> Field {\n let mut payload = multiple_polynomial_payload::(Vec::new(), ct0);\n payload = multiple_polynomial_payload::(payload, ct1);\n\n compute_commitments(payload, DS_CIPHERTEXT, [0x80000000 | payload.len(), 1]).get(0)\n}\n\n/// COMMITMENTS FOR CHALLENGES\n\npub fn compute_threshold_pk_challenge(payload: Vec) -> Vec {\n compute_commitments(\n payload,\n DS_CLG_PK_GENERATION,\n [0x80000000 | payload.len(), 2 * L],\n )\n}\n\npub fn compute_share_encryption_challenge(payload: Vec) -> Vec {\n compute_commitments(\n payload,\n DS_CLG_SHARE_ENCRYPTION,\n [0x80000000 | payload.len(), 2 * L],\n )\n}\n\npub fn compute_user_data_encryption_challenge_commitment(\n pk0is: [Polynomial; L],\n pk1is: [Polynomial; L],\n gammas_payload: Vec,\n pk_commitment: Field,\n) -> Vec {\n assert(compute_pk_aggregation_commitment::(pk0is, pk1is) == pk_commitment);\n\n compute_commitments(\n gammas_payload,\n DS_CLG_USER_DATA_ENCRYPTION,\n [0x80000000 | gammas_payload.len(), 2 * L],\n )\n}\n\npub fn compute_threshold_share_decryption_challenge(payload: Vec) -> Field {\n compute_commitments(\n payload,\n DS_CLG_SHARE_DECRYPTION,\n [0x80000000 | payload.len(), 1],\n )\n .get(0)\n}\n", + "path": "enclave/circuits/lib/src/math/commitments.nr" + }, + "75": { + "source": "// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\n//! Helper functions for circuit construction and cryptographic operations.\nuse crate::math::polynomial::Polynomial;\nuse crate::math::safe::SafeSponge;\n\n/// Compute hex-aligned packing parameters for a given `BIT`.\n///\n/// # Purpose\n/// Returns `(nibble_bits, group)` for use by pack/flatten so layout stays consistent.\n/// - `nibble_bits`: ceil (`BIT`) to the next multiple of 4 (nibble alignment).\n/// - Examples: `BIT = 7 -> 8`, `BIT = 8 -> 8`, `BIT = 9 -> 12`, `BIT = 10 -> 12`, `BIT = 11 -> 12`,\n/// `BIT=16 -> 16`, `BIT = 17 -> 20`.\n/// - `group`: max number of encoded limbs that fit in one BN254 field element,\n/// when each limb uses an extra 4 bits (see below).\n///\n/// # Rationale\n/// - We align to nibbles so powers of two are hex-friendly and deterministic.\n/// - We reserve one extra nibble (4 bits) per stored value to lift signed\n/// coefficients into the non-negative range (e.g., store `v + 2^nibble_bits`),\n/// which implies a radix of `2^(nibble_bits + 4)`.\n///\n/// # Safety\n/// - Asserts `nibble_bits + 4 <= 254` to avoid mod-p wrap on BN254.\n/// - Ensures at least one limb fits: `group >= 1`.\nfn packing_layout() -> (u32, u32) {\n // Ceil BIT up to the next multiple of 4 (nibble alignment).\n let nibble_bits = ((BIT + 3) / 4) * 4;\n\n // Each stored limb uses an extra nibble because negative coefficients\n // will be shifted to positive, so radix = 2^(nibble_bits+4).\n assert(nibble_bits + 4 <= 254);\n\n // Maximum limbs that fit in one BN254 element without wrap.\n let group = 254 / (nibble_bits + 4);\n assert(group >= 1);\n (nibble_bits, group)\n}\n\n/// Flatten `L` polynomials into a single linear stream of packed `Field` carriers.\n///\n/// ## What this does\n/// - For each CRT limb `j` in `0..L`, it packs the coefficients of `poly[j]`\n/// with `pack::` and appends all resulting carriers to `inputs`.\n/// - The packing layout (nibble-aligned width and `group` size) is taken from\n/// `packing_layout::()` and must match what `pack` uses.\n///\n/// ## Determinism & order\n/// - Preserves a stable order: iterate `j = 0..L`, then for each `j` append\n/// carriers in ascending chunk index `i = 0..num_chunks`.\n/// - This ensures transcripts remain deterministic across runs.\n///\n/// ## Generics\n/// - `A`: polynomial degree (number of coefficients per polynomial).\n/// - `L`: number of CRT bases (polynomials).\n/// - `BIT`: per-coefficient bit bound used by the packing layout (compile-time).\n///\n/// ## Returns\n/// - The same `inputs` vector, extended with all carriers in deterministic order.\npub fn flatten(\n mut inputs: Vec,\n poly: [Polynomial; L],\n) -> Vec {\n for j in 0..L {\n // Pack its A coefficients into `num_chunks` carriers using the same BIT layout.\n let packed = pack::(poly[j].coefficients);\n\n // Append carriers in-order to `inputs` to keep a stable transcript layout.\n for i in 0..packed.len() {\n inputs.push(packed.get(i));\n }\n }\n\n // Return the extended input stream.\n inputs\n}\n\n/// Pack `A` values into a `Vec` of carriers using the shared hex-aligned layout.\n///\n/// ## What this does\n/// - Computes `(nibble_bits, group)` via `packing_layout::()`.\n/// - Encodes each value as a limb `digit = v + 2^nibble_bits` and concatenates\n/// limbs in base `radix = 2^(nibble_bits + 4)` (one extra nibble of headroom).\n/// - Packs up to `group` limbs per carrier (fits within BN254 254-bit capacity).\n/// - Pads the last, partial carrier with `digit = 2^nibble_bits` to keep a stable layout.\n///\n/// ## Determinism & order\n/// - Processes values in increasing index order and emits carriers in chunk order\n/// (`chunk = 0..num_chunks`). Padding is deterministic.\n///\n/// ## Generics\n/// - `A`: number of input values.\n/// - `BIT`: per-value bit bound; rounded up to `nibble_bits` by `packing_layout`.\n///\n/// ## Preconditions / Notes\n/// - Call with the raw coefficients whose magnitudes already satisfy the BIT bound\n/// (as enforced by the upstream range checks); `pack` performs the signed -> unsigned\n/// shift internally via `v + base`.\n/// - `group >= 1` is enforced by `packing_layout::()`.\n/// - Padding with `digit = 2^nibble_bits` encodes `zero limb` consistently.\n///\n/// ## Returns\n/// - A `Vec` where each element is a concatenation of up to `group` limbs,\n/// suitable for hashing or transcript I/O.\npub fn pack(values: [Field; A]) -> Vec {\n // Layout parameters: nibble-aligned width and limbs-per-carrier group size.\n let (nibble_bits, group) = packing_layout::();\n\n let base = 2.pow_32(nibble_bits as Field); // 2^nibble_bits\n let radix = 2.pow_32((nibble_bits + 4) as Field); // 2^(nibble_bits + 4)\n\n // Number of chunks to emit: ceil(A / group).\n let num_chunks = (A + group - 1) / group;\n let mut out = Vec::new();\n\n // Process in fixed-size chunks of `group` limbs.\n for chunk in 0..num_chunks {\n // How many real values go into this chunk.\n let remain = A - (chunk * group);\n let take = if remain < group { remain } else { group };\n\n // Build field element accumulator (big-endian concatenation in `radix`).\n let mut acc = 0;\n for i in 0..take {\n let v = values[chunk * group + i];\n acc = acc * radix + (v + base);\n }\n\n // Pad remaining limb slots with the canonical zero-limb `digit = base`.\n for _ in 0..(group - take) {\n acc = acc * radix + base;\n }\n\n out.push(acc);\n }\n out\n}\n\n/// Computes a cryptographic hash using the SAFE (Sponge API for Field Elements) protocol.\n///\n/// This is a convenience wrapper around the SAFE sponge API that handles the full\n/// lifecycle: initialization, absorption, squeezing, and finalization. It's designed\n/// for use in Fiat-Shamir challenge generation and commitment schemes within zero-knowledge circuits.\n///\n/// # Arguments\n/// * `domain_separator` - A 64-byte domain separator used to differentiate between\n/// different protocol instances and prevent cross-protocol attacks.\n/// * `inputs` - Vector of field elements to be absorbed into the sponge.\n/// * `io_pattern` - A 2-element array encoding the I/O pattern:\n/// - `io_pattern[0]`: Encoded ABSORB operation (MSB=1, lower 31 bits = length)\n/// - `io_pattern[1]`: Encoded SQUEEZE operation (MSB=0, lower 31 bits = length)\n///\n/// # Returns\n/// A vector of field elements squeezed from the sponge, with length determined by\n/// the SQUEEZE operation in the IO pattern.\npub fn compute_safe(\n domain_separator: [u8; 64],\n inputs: Vec,\n io_pattern: [u32; 2],\n) -> Vec {\n let mut sponge = SafeSponge::start(io_pattern, domain_separator);\n sponge.absorb(inputs);\n let digests = sponge.squeeze();\n sponge.finish();\n\n digests\n}\n\n#[test]\nfn test_flatten() {\n // Create test polynomials\n let poly1 = Polynomial::new([1, 2, 3]); // degree 2\n let poly2 = Polynomial::new([4, -16, 6]); // degree 2\n let poly3 = Polynomial::new([-7, 8, 9]); // degree 2\n\n let polynomials = [poly1, poly2, poly3];\n\n // Initialize target array with zeros\n let mut inputs = Vec::new();\n\n // Flatten the polynomials\n let result = flatten::<_, _, 4>(inputs, polynomials);\n\n // Verify the flattened coefficients are in the correct positions\n // Every value shifted 1 nibble incase of negative integers\n assert(result.get(0) == 0x11121310101010101010101010101010101010101010101010101010101010);\n assert(result.get(1) == 0x14001610101010101010101010101010101010101010101010101010101010); // -16 became 00 at 0x 14 00 16,\n assert(result.get(2) == 0x09181910101010101010101010101010101010101010101010101010101010); // -7 became 09 at 0x 09 18 19(16 - 7 = 9)\n}\n\n#[test]\nfn test_flatten_big() {\n // Create test polynomials\n let poly1 = Polynomial::new([\n 1791218451968394,\n 21888242871839275222246405745257275088548364400416034343698198265248580087864,\n 21888242871839275222246405745257275088548364400416034343698200542108324633466,\n 5430119342984413,\n 704811298945172,\n 8901715723925099,\n 21888242871839275222246405745257275088548364400416034343698203098124042812559,\n 21888242871839275222246405745257275088548364400416034343698200215091693880034,\n ]);\n let poly2 = Polynomial::new([\n 21888242871839275222246405745257275088548364400416034343698200314078269634250,\n 21888242871839275222246405745257275088548364400416034343698200967285641915872,\n 2909990636858607,\n 7896103832076587,\n 2078397209533893,\n 21888242871839275222246405745257275088548364400416034343698199792421452734531,\n 614400389245817,\n 8290314119277588,\n ]);\n let poly3 = Polynomial::new([\n 21888242871839275222246405745257275088548364400416034343698201373175279892906,\n 21888242871839275222246405745257275088548364400416034343698201087241869723721,\n 6768789983786188,\n 635797784303388,\n 7610153424227556,\n 4633893206538324,\n 2016269760615332,\n 21888242871839275222246405745257275088548364400416034343698201007080554428142,\n ]);\n\n let polynomials = [poly1, poly2, poly3];\n\n // Initialize target array with zeros\n let mut inputs = Vec::new();\n\n // Flatten the polynomials\n let result = flatten::<_, _, 54>(inputs, polynomials);\n\n // Verify the flattened coefficients are in the correct positions\n // Every value shifted 1 nibble incase of negative integers\n\n // For the first index of result operation goes like this,\n\n // First four index of poly1\n // 1791218451968394,\n // 21888242871839275222246405745257275088548364400416034343698198265248580087864,\n // 21888242871839275222246405745257275088548364400416034343698200542108324633466,\n // 5430119342984413,\n\n // base + 1791218451968394 = 0x1065d1a8b8b718a\n // base - 5921327228407753 = 0xeaf69591f3b037 (negative coefficient shifted)\n // base - 3644467483862151 = 0xf30d604a3a9b79 (negative coefficient shifted)\n // base + 5430119342984413 = 0x1134aaa2e86ccdd\n assert(result.get(0) == 0x1065d1a8b8b718a0eaf69591f3b0370f30d604a3a9b791134aaa2e86ccdd);\n assert(result.get(1) == 0x1028105ab1b789411fa010339db66b0fc220f1326bc8e0f1e3f4cc1e02e1);\n assert(result.get(2) == 0x0f23dfbe7cd76c90f4901299312ddf10a569efe35acef11c0d76f005412b);\n assert(result.get(3) == 0x107624a8f605dc50f0638a368960421022ecb3cf36b7911d73ff2c27ec14);\n assert(result.get(4) == 0x0f6013a24e1b9a90f4fd2c158a08481180c2dba8af4cc10242413515171c);\n assert(result.get(5) == 0x11b0964eb898ce411076805680b85410729c962da53a40f4b44412d0f6ed);\n}\n\n#[test]\nfn test_flatten_small() {\n // Create test polynomials\n let poly1 = Polynomial::new([712345, 104857, 999999, 500001, 123, 654321, 77]);\n let poly2 = Polynomial::new([1, 524287, 888888, 23456, 34567, 765432, 0]);\n let poly3 = Polynomial::new([444444, 333333, 222222, 111111, 987654, 246810, 13579]);\n\n let polynomials = [poly1, poly2, poly3];\n\n // Initialize target array with zeros\n let mut inputs = Vec::new();\n\n // Flatten the polynomials\n let result = flatten::<_, _, 20>(inputs, polynomials);\n\n assert(result.get(0) == 0x1ade991199991f423f17a12110007b19fbf110004d100000100000100000);\n assert(result.get(1) == 0x10000117ffff1d9038105ba01087071badf8100000100000100000100000);\n assert(result.get(2) == 0x16c81c15161513640e11b2071f120613c41a10350b100000100000100000);\n}\n\n#[test]\nfn test_safe_hashing_with_safe_helper() {\n // Verifies basic hash functionality with a simple ABSORB(3) + SQUEEZE(1) pattern.\n let domain_separator = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n let elements = Vec::from_slice(&[1, 2, 3]);\n\n // Pattern: ABSORB(3), SQUEEZE(1)\n let io_pattern = [0x80000003, 0x00000001];\n let digests1 = compute_safe(domain_separator, elements, io_pattern);\n\n assert(digests1.len() == 1);\n assert(digests1.get(0) != 0);\n\n // Test determinism\n let digests2 = compute_safe(domain_separator, elements, io_pattern);\n\n assert(digests2.len() == 1);\n assert(digests2.get(0) != 0);\n assert(digests2.get(0) == digests1.get(0));\n}\n\n#[test]\nfn test_pack() {\n // Test pack function directly with small values\n let values = [1, 2, 3, 4];\n let packed = pack::<4, 4>(values);\n\n // With BIT=4, nibble_bits=4, group should be floor(254/(4+4)) = 31\n // So all 4 values should fit in one carrier\n assert(packed.len() >= 1);\n\n // Test with negative values\n let values_neg = [-1, 2, -3, 4];\n let packed_neg = pack::<4, 4>(values_neg);\n assert(packed_neg.len() >= 1);\n}\n\n#[test]\nfn test_pack_single_value() {\n // Test packing a single value\n let values = [42];\n let packed = pack::<1, 8>(values);\n assert(packed.len() == 1);\n assert(packed.get(0) != 0);\n}\n\n#[test]\nfn test_pack_determinism() {\n // Test that packing is deterministic\n let values = [10, 20, 30];\n let packed1 = pack::<3, 8>(values);\n let packed2 = pack::<3, 8>(values);\n\n assert(packed1.len() == packed2.len());\n for i in 0..packed1.len() {\n assert(packed1.get(i) == packed2.get(i));\n }\n}\n", + "path": "enclave/circuits/lib/src/math/helpers.nr" + }, + "81": { + "source": "// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\nuse keccak256::keccak256;\nuse poseidon::poseidon2_permutation;\n\n/// SAFE (Sponge API for Field Elements)\n///\n/// This module provides a complete implementation of the SAFE API in Noir as defined in:\n/// \"SAFE (Sponge API for Field Elements) - A Toolbox for ZK Hash Applications\"\n/// see https://hackmd.io/bHgsH6mMStCVibM_wYvb2w#22-Sponge-state for more details.\n///\n/// SAFE provides a unified interface for cryptographic sponge functions that can be\n/// instantiated with various permutations to create hash functions, MACs, authenticated\n/// encryption schemes, and other cryptographic primitives for ZK proof systems.\n///\n/// This implementation follows the SAFE specification exactly, providing:\n/// - Complete API: START, ABSORB, SQUEEZE, FINISH operations.\n/// - Full security: Domain separation, tag computation, IO pattern validation.\n/// - Poseidon2 integration: Field-friendly permutation for ZK systems.\n/// - Specification compliance: All operations follow SAFE spec 2.4 exactly.\n/// - Natural API design: Variable-length inputs, automatic length detection from IO patterns.\n///\n/// # API Design\n///\n/// The API is designed for natural usage while maintaining type safety:\n/// - `absorb(input: [Field])`: Accepts variable-length arrays, no padding required.\n/// - `squeeze()`: Returns a vector with field element(s).\n/// - IO patterns automatically determine operation lengths for validation.\n\n/// Rate parameter for the sponge construction (number of field elements that can be absorbed per permutation call).\nglobal RATE: u32 = 3;\n\n/// Capacity parameter for the sponge construction (security parameter, typically 1-2 field elements).\nglobal CAPACITY: u32 = 1;\n\n/// Total state size (rate + capacity) in field elements.\nglobal STATE_SIZE: u32 = RATE + CAPACITY;\n\n/// IO Pattern encoding constants (from SAFE spec 2.3).\n///\n/// These constants are used for encoding operation types in the 32-bit word format:\n/// - MSB set to 1 for ABSORB operations\n/// - MSB set to 0 for SQUEEZE operations\n\n/// Flag for ABSORB operations (MSB = 1)\nglobal ABSORB_FLAG: u32 = 0x80000000;\n\n/// Flag for SQUEEZE operations (MSB = 0)\nglobal SQUEEZE_FLAG: u32 = 0x00000000;\n\n/// SAFE Sponge State (following spec 2.2)\n///\n/// The sponge state consists of the permutation state, tag, position counters,\n/// and IO pattern tracking as defined in the SAFE specification.\n///\n/// # Generic Parameters\n/// - `L`: The length of the IO pattern array\n///\n/// # Fields\n/// - `state`: Permutation state V in F^n (rate + capacity elements)\n/// - `tag`: Parameter tag T used for instance differentiation\n/// - `absorb_pos`: Current absorb position (<= n-c)\n/// - `squeeze_pos`: Current squeeze position (<= n-c)\n/// - `io_pattern`: Expected IO pattern for validation (encoded 32-bit words)\n/// - `io_count`: Current operation count for pattern tracking\npub struct SafeSponge {\n /// Permutation state V in F^n (rate + capacity elements).\n state: [Field; STATE_SIZE],\n /// Parameter tag T used for instance differentiation.\n tag: Field,\n /// Current absorb position (<= n-c).\n absorb_pos: u32,\n /// Current squeeze position (<= n-c).\n squeeze_pos: u32,\n /// Expected IO pattern for validation.\n io_pattern: [u32; L],\n /// Current operation count for pattern tracking (spec 2.4: io_count).\n io_count: u32,\n}\n\nimpl SafeSponge {\n /// Initializes a new SAFE sponge instance with the given IO pattern and domain separator (following spec 2.4).\n ///\n /// # Arguments\n /// - `io_pattern`: Array of 32-bit encoded operations defining the expected sequence of ABSORB/SQUEEZE calls.\n /// Each word has MSB=1 for ABSORB operations, MSB=0 for SQUEEZE operations.\n /// - `domain_separator`: 64-byte domain separator for cross-protocol security.\n ///\n /// # Returns\n /// A new `SafeSponge` instance with initialized state\n pub fn start(io_pattern: [u32; L], domain_separator: [u8; 64]) -> SafeSponge {\n // Compute tag from IO pattern and domain separator (spec 2.3).\n let tag = compute_tag(io_pattern, domain_separator);\n\n let mut state = [0; STATE_SIZE];\n // Initialize capacity with tag (spec 2.4).\n // Add T to the first 128 bits of the state.\n state[0] = tag;\n\n SafeSponge { state, tag, absorb_pos: 0, squeeze_pos: 0, io_pattern, io_count: 0 }\n }\n\n /// Absorbs field elements into the sponge state, interleaving permutation calls as needed (following spec 2.4).\n ///\n /// The number of elements to absorb is automatically validated against the IO pattern.\n /// This method accepts variable-length arrays, making it natural to use without padding.\n ///\n /// # Arguments\n /// - `input`: Array of field elements to absorb (variable length, must match IO pattern)\n pub fn absorb(&mut self, input: Vec) {\n let length = input.len() as u32;\n\n // Validate against IO pattern.\n assert(self.io_count < L);\n\n // Parse expected operation from io_pattern (encoded word)\n let expected_encoded_word = self.io_pattern[self.io_count];\n let is_expected_absorb = (expected_encoded_word & ABSORB_FLAG) != 0;\n let expected_length = expected_encoded_word & 0x7FFFFFFF;\n\n // Validate operation type and length\n assert(is_expected_absorb, \"Expected ABSORB operation\");\n assert(expected_length == length, \"Length mismatch\");\n\n // Process each element naturally (no unnecessary iterations).\n for i in 0..length {\n // If absorb_pos == (n-c) then permute and reset (spec 2.4).\n if self.absorb_pos == RATE {\n // n-c = RATE.\n self.state = self.permute();\n self.absorb_pos = 0;\n }\n\n // Add X[i] to state at absorb_pos (spec 2.4).\n // Note: absorb_pos is the rate position, not capacity position.\n self.state[self.absorb_pos + CAPACITY] =\n self.state[self.absorb_pos + CAPACITY] + input.get(i);\n self.absorb_pos += 1;\n }\n\n // Verify that the encoded word matches the expected pattern.\n let encoded_word = ABSORB_FLAG | length;\n assert(encoded_word == expected_encoded_word);\n\n self.io_count += 1;\n\n // Force permute at start of next SQUEEZE (spec 2.4).\n self.squeeze_pos = RATE;\n }\n\n /// Extracts field elements from the sponge state, interleaving permutation calls as needed (following spec 2.4).\n ///\n /// The number of elements to squeeze is automatically determined from the IO pattern.\n pub fn squeeze(&mut self) -> Vec {\n // Validate against IO pattern.\n assert(self.io_count < L);\n\n // Parse expected operation from io_pattern (encoded word)\n let expected_encoded_word = self.io_pattern[self.io_count];\n let is_expected_squeeze = (expected_encoded_word & ABSORB_FLAG) == 0;\n let length = expected_encoded_word & 0x7FFFFFFF;\n\n // Validate operation type\n assert(is_expected_squeeze, \"Expected SQUEEZE operation\");\n\n let mut output = Vec::new();\n\n // SQUEEZE implementation following spec 2.4.\n // If length==0, loop won't execute (spec 2.4).\n for _ in 0..length {\n // If squeeze_pos==(n-c) then permute and reset (spec 2.4).\n if self.squeeze_pos == RATE {\n // n-c = RATE.\n self.state = self.permute();\n self.squeeze_pos = 0;\n self.absorb_pos = 0;\n }\n // Set Y[i] to state element at squeeze_pos (spec 2.4).\n output.push(self.state[self.squeeze_pos + CAPACITY]);\n self.squeeze_pos += 1;\n }\n\n // Verify that the encoded word matches the expected pattern.\n let encoded_word = SQUEEZE_FLAG | length;\n assert(encoded_word == expected_encoded_word);\n\n self.io_count += 1;\n output\n }\n\n /// Finalizes the sponge instance, verifying that all expected operations have been performed and clearing the internal state for security (following spec 2.4).\n ///\n /// This function is used to ensure that the sponge instance has been used correctly and to prevent information leakage.\n pub fn finish(&mut self) {\n // Check that io_count equals the length of the IO pattern expected (spec 2.4).\n assert(self.io_count == L, \"IO pattern not completed\");\n\n // Erase the state and its variables (spec 2.4).\n self.state = [0; STATE_SIZE];\n self.absorb_pos = 0;\n self.squeeze_pos = 0;\n self.io_count = 0;\n }\n\n /// Permute the state using Poseidon2 (following spec 2.4).\n ///\n /// Applies the Poseidon2 permutation to the current state.\n /// This is the core cryptographic primitive of the sponge construction.\n ///\n /// # Returns\n /// New state after permutation\n fn permute(self) -> [Field; STATE_SIZE] {\n poseidon2_permutation(self.state, STATE_SIZE)\n }\n}\n\n/// Computes a unique tag for a sponge instance based on its IO pattern and domain separator.\n/// The tag is used to ensure that distinct instances behave like distinct functions.\n///\n/// # Arguments\n/// - `io_pattern`: Array of 32-bit encoded operations defining the sponge's usage pattern.\n/// Each word has MSB=1 for ABSORB operations, MSB=0 for SQUEEZE operations.\n/// - `domain_separator`: 64-byte domain separator for cross-protocol security.\n///\n/// # Returns\n/// A field element representing the 128-bit tag.\npub fn compute_tag(io_pattern: [u32; L], domain_separator: [u8; 64]) -> Field {\n // Step 1: Parse and aggregate consecutive operations of the same type\n let mut encoded_words = [0; L]; // Support up to L operations.\n let mut word_count = 0;\n let mut current_absorb_sum = 0;\n let mut current_squeeze_sum = 0;\n let mut last_was_absorb = false;\n\n for i in 0..L {\n if io_pattern[i] > 0 {\n // Parse operation type from MSB and length from lower 31 bits\n let is_absorb = (io_pattern[i] & ABSORB_FLAG) != 0;\n let length = io_pattern[i] & 0x7FFFFFFF; // Clear MSB to get length\n\n if is_absorb {\n if last_was_absorb {\n // Aggregate consecutive ABSORB operations\n current_absorb_sum += length;\n } else {\n // Start new ABSORB sequence\n if current_squeeze_sum > 0 {\n // Flush previous SQUEEZE sequence\n encoded_words[word_count] = SQUEEZE_FLAG | current_squeeze_sum;\n word_count += 1;\n current_squeeze_sum = 0;\n }\n current_absorb_sum = length;\n }\n last_was_absorb = true;\n } else {\n if !last_was_absorb {\n // Aggregate consecutive SQUEEZE operations\n current_squeeze_sum += length;\n } else {\n // Start new SQUEEZE sequence\n if current_absorb_sum > 0 {\n // Flush previous ABSORB sequence\n encoded_words[word_count] = ABSORB_FLAG | current_absorb_sum;\n word_count += 1;\n current_absorb_sum = 0;\n }\n current_squeeze_sum = length;\n }\n last_was_absorb = false;\n }\n }\n }\n\n // Flush remaining operations\n if current_absorb_sum > 0 {\n encoded_words[word_count] = ABSORB_FLAG | current_absorb_sum;\n word_count += 1;\n }\n if current_squeeze_sum > 0 {\n encoded_words[word_count] = SQUEEZE_FLAG | current_squeeze_sum;\n word_count += 1;\n }\n\n // Step 2: Serialize to byte string and append domain separator (following SAFE spec 2.3).\n // Buffer is 256 bytes: max 192 bytes for IO pattern (48 words) + 64 bytes for domain separator.\n // Note: We must use a fixed-size array because Noir's keccak256 requires [u8; N], not Vec.\n let max_io_pattern_bytes: u32 = 192; // 256 - 64 (domain separator)\n let io_pattern_bytes = word_count * 4;\n assert(\n io_pattern_bytes <= max_io_pattern_bytes,\n \"IO pattern too large: max 48 aggregated words supported\",\n );\n\n let mut input_bytes = [0u8; 256];\n let mut byte_count: u32 = 0;\n\n // Serialize encoded words to bytes (big-endian as per SAFE spec).\n // Note: Noir requires compile-time loop bounds, so we iterate over L (the array size)\n // instead of word_count (runtime value). The condition `i < word_count` ensures we only\n // process valid encoded words. This is safe because word_count <= L always holds\n // (we can have at most L encoded words from L input operations).\n for i in 0..L {\n if i < word_count {\n let word = encoded_words[i];\n input_bytes[byte_count] = (word >> 24) as u8;\n input_bytes[byte_count + 1] = (word >> 16) as u8;\n input_bytes[byte_count + 2] = (word >> 8) as u8;\n input_bytes[byte_count + 3] = word as u8;\n byte_count += 4;\n }\n }\n\n // Append full 64-byte domain separator.\n for i in 0..64 {\n input_bytes[byte_count] = domain_separator[i];\n byte_count += 1;\n }\n\n // Step 3: Hash with Keccak-256 and truncate to 128 bits.\n // Note: The SAFE spec uses SHA3-256, but we use Keccak-256 for Noir compatibility.\n // Keccak-256 differs from SHA3-256 in padding, but both provide equivalent security.\n let hash_bytes = keccak256(input_bytes, byte_count);\n\n // Convert first 128 bits (16 bytes) to field element.\n let mut tag_value: Field = 0;\n for i in 0..16 {\n tag_value = tag_value * 256 + (hash_bytes[i] as Field);\n }\n\n tag_value\n}\n\n#[test]\nfn test_safe_hashing() {\n // Verifies basic hash functionality with a simple ABSORB(3) + SQUEEZE(1) pattern.\n let domain_separator = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n let elements = Vec::from_slice(&[1, 2, 3]);\n\n // Pattern: ABSORB(3), SQUEEZE(1)\n let io_pattern = [0x80000003, 0x00000001];\n let mut sponge = SafeSponge::start(io_pattern, domain_separator);\n sponge.absorb(elements);\n let output = sponge.squeeze();\n sponge.finish();\n\n assert(output.len() == 1);\n assert(output.get(0) != 0);\n\n // Test determinism\n let mut sponge2 = SafeSponge::start(io_pattern, domain_separator);\n sponge2.absorb(elements);\n let output2 = sponge2.squeeze();\n sponge2.finish();\n\n assert(output2.len() == 1);\n assert(output2.get(0) != 0);\n}\n\n#[test]\nfn test_merkle_node() {\n // Verifies SAFE can be used for Merkle tree node hashing with pattern ABSORB(1) + ABSORB(1) + SQUEEZE(1).\n // Tests the ability to absorb multiple inputs before squeezing output.\n let domain_separator = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n let left = Vec::from_slice([123]);\n let right = Vec::from_slice([456]);\n\n // Pattern: ABSORB(1), ABSORB(1), SQUEEZE(1)\n let io_pattern = [0x80000001, 0x80000001, 0x00000001];\n let mut sponge = SafeSponge::start(io_pattern, domain_separator);\n sponge.absorb(left);\n sponge.absorb(right);\n let output = sponge.squeeze();\n sponge.finish();\n\n assert(output.len() == 1);\n assert(output.get(0) != 0);\n\n // Test determinism\n let mut sponge2 = SafeSponge::start(io_pattern, domain_separator);\n sponge2.absorb(left);\n sponge2.absorb(right);\n let output2 = sponge2.squeeze();\n sponge2.finish();\n\n assert(output2.len() == 1);\n assert(output2.get(0) != 0);\n}\n\n#[test]\nfn test_commitment_scheme() {\n // Verifies SAFE can be used for commitment schemes with pattern ABSORB(3) + SQUEEZE(1).\n // Tests the ability to create deterministic commitments from multiple field elements.\n let domain_separator = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n let values = Vec::from_slice([10, 20, 30]);\n\n // Pattern: ABSORB(3), SQUEEZE(1)\n let io_pattern = [0x80000003, 0x00000001];\n let mut sponge = SafeSponge::start(io_pattern, domain_separator);\n sponge.absorb(values);\n let output = sponge.squeeze();\n sponge.finish();\n\n assert(output.len() == 1);\n assert(output.get(0) != 0);\n\n // Test determinism\n let mut sponge2 = SafeSponge::start(io_pattern, domain_separator);\n sponge2.absorb(values);\n let output2 = sponge2.squeeze();\n sponge2.finish();\n\n assert(output2.len() == 1);\n assert(output2.get(0) != 0);\n}\n\n#[test]\nfn test_domain_separation() {\n // Verifies that different domain separators produce different outputs for the same input.\n // This is crucial for cross-protocol security and preventing collisions between different applications.\n let elements = Vec::from_slice([1, 2, 3]);\n let domain1 = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n let domain2 = [\n 0x41, 0x42, 0x43, 0x45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n\n // Pattern: ABSORB(3), SQUEEZE(1)\n let io_pattern = [0x80000003, 0x00000001];\n\n let mut sponge1 = SafeSponge::start(io_pattern, domain1);\n sponge1.absorb(elements);\n let output1 = sponge1.squeeze();\n sponge1.finish();\n\n let mut sponge2 = SafeSponge::start(io_pattern, domain2);\n sponge2.absorb(elements);\n let output2 = sponge2.squeeze();\n sponge2.finish();\n\n assert(output1.len() == 1);\n assert(output2.len() == 1);\n assert(output1.get(0) != output2.get(0)); // Different domain separators should produce different outputs\n}\n\n#[test]\nfn test_multiple_squeeze() {\n // Verifies that multiple field elements can be squeezed in a single operation.\n // Tests pattern ABSORB(3) + SQUEEZE(2) to ensure proper state management.\n let domain_separator = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n let elements = Vec::from_slice([1, 2, 3]);\n\n // Pattern: ABSORB(3), SQUEEZE(2)\n let io_pattern = [0x80000003, 0x00000002];\n let mut sponge = SafeSponge::start(io_pattern, domain_separator);\n sponge.absorb(elements);\n let output = sponge.squeeze();\n sponge.finish();\n\n assert(output.len() == 2);\n assert(output.get(0) != 0);\n assert(output.get(1) != 0);\n assert(output.get(0) != output.get(1)); // Different squeeze outputs should be different\n}\n\n#[test]\nfn test_zero_length_operations() {\n // Verifies that zero-length ABSORB and SQUEEZE operations are handled correctly.\n // Tests pattern ABSORB(0) + SQUEEZE(1) to ensure proper state transitions.\n let domain_separator = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n\n // Pattern: ABSORB(0), SQUEEZE(1)\n let io_pattern = [0x80000000, 0x00000001];\n let mut sponge = SafeSponge::start(io_pattern, domain_separator);\n sponge.absorb(Vec::new());\n let output = sponge.squeeze();\n sponge.finish();\n\n assert(output.len() == 1);\n assert(output.get(0) != 0);\n}\n\n#[test]\nfn test_tag_computation() {\n // Verifies the tag computation algorithm using the example from the SAFE specification.\n // Pattern: ABSORB(3), ABSORB(3), SQUEEZE(3)\n // Should aggregate to: ABSORB(6), SQUEEZE(3)\n // Encoded as: [0x80000006, 0x00000003]\n // Tests determinism and pattern differentiation.\n\n let io_pattern = [0x80000003, 0x80000003, 0x00000003];\n let domain_separator = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n\n let tag = compute_tag(io_pattern, domain_separator);\n\n // Test determinism\n let tag2 = compute_tag(io_pattern, domain_separator);\n assert(tag == tag2);\n\n // Test that different patterns produce different tags\n let io_pattern2 = [0x80000003, 0x00000003]; // ABSORB(3), SQUEEZE(3) - different pattern\n let tag3 = compute_tag(io_pattern2, domain_separator);\n assert(tag != tag3);\n}\n\n#[test]\nfn test_tag_computation_debug() {\n println(\"=== SAFE Tag Computation Debug Test ===\");\n\n // Test your specific pattern [2, 2, 2] (ABSORB(2), SQUEEZE(2), ABSORB(2))\n let io_pattern = [0x80000002, 0x00000002, 0x80000002];\n let domain_separator = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n\n println(f\"Testing pattern: {io_pattern}\");\n println(\n f\"Expected to aggregate to: ABSORB(2), SQUEEZE(2), ABSORB(2)\",\n );\n println(\n f\"Expected encoded words: [0x80000002, 0x00000002, 0x80000002]\",\n );\n println(\"\");\n\n let tag = compute_tag(io_pattern, domain_separator);\n\n println(f\"=== Expected Rust Output ===\");\n println(\"Pattern [2, 2, 2] (ABSORB(2), SQUEEZE(2), ABSORB(2))\");\n println(\"Domain separator: 0x41424344...\");\n println(\"Tag: 0xce3bb9ee4b2d41c42e9cdda38afe8b6a\");\n println(\"\");\n\n println(f\"=== Noir Output ===\");\n println(f\"Tag: {tag}\");\n println(\"\");\n\n println(\"Compare the tag values above with Rust script!\");\n}\n\n#[test]\nfn test_consecutive_absorb_aggregation() {\n // Test that consecutive ABSORB operations are properly aggregated\n // Pattern: ABSORB(1), ABSORB(1), SQUEEZE(1) should aggregate to ABSORB(2), SQUEEZE(1)\n let domain_separator = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n\n // Test pattern: ABSORB(1), ABSORB(1), SQUEEZE(1)\n let io_pattern = [0x80000001, 0x80000001, 0x00000001];\n\n // This should aggregate to: ABSORB(2), SQUEEZE(1) = [0x80000002, 0x00000001]\n let tag = compute_tag(io_pattern, domain_separator);\n\n // Test that the aggregated pattern produces the same tag ABSORB(2), SQUEEZE(1)\n let aggregated_pattern = [0x80000002, 0x00000001];\n let aggregated_tag = compute_tag(aggregated_pattern, domain_separator);\n\n // The tags should be identical because the patterns are equivalent after aggregation\n assert(tag == aggregated_tag, \"Consecutive ABSORB operations should aggregate to the same tag\");\n\n // Test that a different pattern produces a different tag\n let different_pattern = [0x80000001, 0x00000001, 0x80000001]; // ABSORB(1), SQUEEZE(1), ABSORB(1)\n let different_tag = compute_tag(different_pattern, domain_separator);\n\n // This should be different because it doesn't have consecutive ABSORB operations\n assert(tag != different_tag, \"Different patterns should produce different tags\");\n\n println(\"=== Consecutive ABSORB Aggregation Test ===\");\n println(\n f\"Original pattern: [0x80000001, 0x80000001, 0x00000001] (ABSORB(1), ABSORB(1), SQUEEZE(1))\",\n );\n println(\n f\"Aggregated pattern: [0x80000002, 0x00000001] (ABSORB(2), SQUEEZE(1))\",\n );\n println(f\"Original tag: {tag}\");\n println(f\"Aggregated tag: {aggregated_tag}\");\n println(f\"Original tag: {tag}\");\n println(f\"Aggregated tag: {aggregated_tag}\");\n println(f\"Different pattern tag: {different_tag}\");\n}\n\n#[test]\nfn test_consecutive_squeeze_aggregation() {\n // Test that consecutive SQUEEZE operations are properly aggregated\n // Pattern: ABSORB(1), SQUEEZE(1), SQUEEZE(1) should aggregate to ABSORB(1), SQUEEZE(2)\n let domain_separator = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n\n // Test pattern: ABSORB(1), SQUEEZE(1), SQUEEZE(1)\n let io_pattern = [0x80000001, 0x00000001, 0x00000001];\n\n // This should aggregate to: ABSORB(1), SQUEEZE(2) = [0x80000001, 0x00000002]\n let tag = compute_tag(io_pattern, domain_separator);\n\n // Test that the aggregated pattern produces the same tag ABSORB(1), SQUEEZE(2)\n let aggregated_pattern = [0x80000001, 0x00000002];\n let aggregated_tag = compute_tag(aggregated_pattern, domain_separator);\n\n // The tags should be identical because the patterns are equivalent after aggregation\n assert(\n tag == aggregated_tag,\n \"Consecutive SQUEEZE operations should aggregate to the same tag\",\n );\n\n // Test that a different pattern produces a different tag\n let different_pattern = [0x80000001, 0x00000001, 0x80000001]; // ABSORB(1), SQUEEZE(1), ABSORB(1)\n let different_tag = compute_tag(different_pattern, domain_separator);\n\n // This should be different because it doesn't have consecutive SQUEEZE operations\n assert(tag != different_tag, \"Different patterns should produce different tags\");\n\n println(\"=== Consecutive SQUEEZE Aggregation Test ===\");\n println(\n f\"Original pattern: [0x80000001, 0x00000001, 0x00000001] (ABSORB(1), SQUEEZE(1), SQUEEZE(1))\",\n );\n println(\n f\"Aggregated pattern: [0x80000001, 0x00000002] (ABSORB(1), SQUEEZE(2))\",\n );\n println(f\"Original tag: {tag}\");\n println(f\"Aggregated tag: {aggregated_tag}\");\n println(f\"Different pattern tag: {different_tag}\");\n}\n\n#[test]\nfn test_mixed_consecutive_aggregation() {\n // Test that both consecutive ABSORB and SQUEEZE operations are properly aggregated\n // Pattern: ABSORB(1), ABSORB(1), SQUEEZE(1), SQUEEZE(1), ABSORB(1)\n // Should aggregate to: ABSORB(2), SQUEEZE(2), ABSORB(1)\n let domain_separator = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n\n // Test pattern: ABSORB(1), ABSORB(1), SQUEEZE(1), SQUEEZE(1), ABSORB(1)\n let io_pattern = [0x80000001, 0x80000001, 0x00000001, 0x00000001, 0x80000001];\n\n // This should aggregate to: ABSORB(2), SQUEEZE(2), ABSORB(1) = [0x80000002, 0x00000002, 0x80000001]\n let tag = compute_tag(io_pattern, domain_separator);\n\n // Test that the aggregated pattern produces the same tag\n let aggregated_pattern = [0x80000002, 0x00000002, 0x80000001]; // ABSORB(2), SQUEEZE(2), ABSORB(1)\n let aggregated_tag = compute_tag(aggregated_pattern, domain_separator);\n\n // The tags should be identical because the patterns are equivalent after aggregation\n assert(tag == aggregated_tag, \"Mixed consecutive operations should aggregate to the same tag\");\n\n println(\"=== Mixed Consecutive Aggregation Test ===\");\n println(\n f\"Original pattern: [0x80000001, 0x80000001, 0x00000001, 0x00000001, 0x80000001]\",\n );\n println(\n f\" (ABSORB(1), ABSORB(1), SQUEEZE(1), SQUEEZE(1), ABSORB(1))\",\n );\n println(f\"Aggregated pattern: [0x80000002, 0x00000002, 0x80000001]\");\n println(f\" (ABSORB(2), SQUEEZE(2), ABSORB(1))\");\n println(f\"Original tag: {tag}\");\n println(f\"Aggregated tag: {aggregated_tag}\");\n}\n\n#[test]\nfn test_large_io_pattern() {\n let domain_separator = [\n 0x41, 0x42, 0x43, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 0, 0, 0, 0,\n ];\n\n // Create pattern with 48 alternating ABSORB(1) and SQUEEZE(1) operations\n // This is the maximum supported (48 words * 4 bytes = 192 bytes, leaving 64 for domain separator)\n let mut io_pattern = [0u32; 48];\n for i in 0..48 {\n if i % 2 == 0 {\n io_pattern[i] = ABSORB_FLAG | 1; // ABSORB(1)\n } else {\n io_pattern[i] = SQUEEZE_FLAG | 1; // SQUEEZE(1)\n }\n }\n\n let tag = compute_tag(io_pattern, domain_separator);\n assert(tag != 0);\n}\n\n#[test]\nfn test_domain_separator_not_truncated() {\n // This test verifies that the domain separator is always included in the tag computation,\n // even for large IO patterns. If the domain separator were truncated, different domain\n // separators would produce the same tag for large patterns.\n\n let domain_separator_a = [\n 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,\n 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,\n 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,\n 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,\n 0x41, 0x41, 0x41, 0x41,\n ]; // All 'A's\n\n let domain_separator_b = [\n 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,\n 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,\n 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,\n 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,\n 0x42, 0x42, 0x42, 0x42,\n ]; // All 'B's\n\n // Create pattern with 48 alternating operations (max supported: 192 bytes of IO pattern)\n let mut io_pattern = [0u32; 48];\n for i in 0..48 {\n if i % 2 == 0 {\n io_pattern[i] = ABSORB_FLAG | 1;\n } else {\n io_pattern[i] = SQUEEZE_FLAG | 1;\n }\n }\n\n let tag_a = compute_tag(io_pattern, domain_separator_a);\n let tag_b = compute_tag(io_pattern, domain_separator_b);\n\n // Tags MUST be different because domain separators are different.\n // If they were the same, it would mean the domain separator was truncated/ignored.\n assert(tag_a != tag_b, \"Domain separator must affect tag even for large IO patterns\");\n}\n", + "path": "enclave/circuits/lib/src/math/safe.nr" + } + }, + "expression_width": { "Bounded": { "width": 4 } } +} diff --git a/crates/zk-prover/tests/fixtures/pk.vk b/crates/zk-prover/tests/fixtures/pk.vk new file mode 100644 index 0000000000..56540013f3 Binary files /dev/null and b/crates/zk-prover/tests/fixtures/pk.vk differ diff --git a/crates/zk-prover/tests/integration_tests.rs b/crates/zk-prover/tests/integration_tests.rs new file mode 100644 index 0000000000..25898f4c2b --- /dev/null +++ b/crates/zk-prover/tests/integration_tests.rs @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Integration tests that require network access to download binaries. +//! Run with: cargo test --features integration-tests + +#![cfg(feature = "integration-tests")] + +use e3_zk_prover::{BbTarget, SetupStatus, ZkBackend, ZkConfig}; +use std::path::PathBuf; +use tempfile::tempdir; + +fn test_backend(temp_path: &std::path::Path, config: ZkConfig) -> ZkBackend { + let noir_dir = temp_path.join("noir"); + let bb_binary = noir_dir.join("bin").join("bb"); + let circuits_dir = noir_dir.join("circuits"); + let work_dir = noir_dir.join("work").join("test_node"); + ZkBackend::new(bb_binary, circuits_dir, work_dir, config) +} + +fn versions_json_path() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("versions.json") +} + +#[tokio::test] +async fn test_download_bb_and_verify_structure() { + let config = ZkConfig::load(&versions_json_path()) + .await + .expect("versions.json should exist"); + + let temp = tempdir().unwrap(); + let backend = test_backend(temp.path(), config); + + let result = backend.download_bb().await; + assert!(result.is_ok(), "download failed: {:?}", result); + + assert!(backend.bb_binary.exists(), "bb binary not found"); + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let perms = std::fs::metadata(&backend.bb_binary).unwrap().permissions(); + assert_eq!(perms.mode() & 0o111, 0o111, "bb should be executable"); + } + + let metadata = std::fs::metadata(&backend.bb_binary).unwrap(); + assert!(metadata.len() > 0, "bb binary should not be empty"); + + let version_info = backend.load_version_info().await; + assert_eq!( + version_info.bb_version.as_deref(), + Some(backend.config.required_bb_version.as_str()) + ); + assert!(version_info.last_updated.is_some()); + + if backend + .config + .bb_checksum_for(BbTarget::current().unwrap()) + .is_some() + { + assert!( + version_info.bb_checksum.is_some(), + "checksum should be saved in version.json" + ); + } + + assert!(backend.base_dir.exists()); + assert!(backend.base_dir.join("bin").exists()); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} + +#[tokio::test] +async fn test_download_bb_rejects_wrong_checksum() { + let mut config = ZkConfig::load(&versions_json_path()) + .await + .expect("versions.json should exist"); + + for checksum in config.bb_checksums.values_mut() { + *checksum = "0".repeat(64); + } + + let temp = tempdir().unwrap(); + let backend = test_backend(temp.path(), config); + + let result = backend.download_bb().await; + assert!( + matches!(result, Err(e3_zk_prover::ZkError::ChecksumMismatch { .. })), + "expected ChecksumMismatch, got {:?}", + result + ); + + assert!(!backend.bb_binary.exists()); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} + +#[tokio::test] +async fn test_ensure_installed_full_flow() { + let config = ZkConfig::load(&versions_json_path()) + .await + .expect("versions.json should exist"); + + let temp = tempdir().unwrap(); + let backend = test_backend(temp.path(), config); + + assert!(matches!( + backend.check_status().await, + SetupStatus::FullSetupNeeded + )); + + let result = backend.ensure_installed().await; + assert!(result.is_ok(), "ensure_installed failed: {:?}", result); + + assert!(matches!(backend.check_status().await, SetupStatus::Ready)); + + let version = backend.verify_bb().await; + assert!(version.is_ok(), "bb --version failed: {:?}", version); + println!("bb version: {}", version.unwrap()); + + assert!(backend.bb_binary.exists()); + assert!(backend.circuits_dir.exists()); + assert!(backend.work_dir.exists()); + assert!(backend.base_dir.join("version.json").exists()); + + // Idempotent - running setup again should work + let result = backend.ensure_installed().await; + assert!(result.is_ok()); + assert!(matches!(backend.check_status().await, SetupStatus::Ready)); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} + +#[tokio::test] +async fn test_download_circuits() { + let config = ZkConfig::load(&versions_json_path()) + .await + .expect("versions.json should exist"); + + let temp = tempdir().unwrap(); + let backend = test_backend(temp.path(), config); + + tokio::fs::create_dir_all(&backend.circuits_dir) + .await + .unwrap(); + + // Download circuits (may fall back to placeholder on failure) + let result = backend.download_circuits().await; + assert!(result.is_ok(), "download_circuits failed: {:?}", result); + + // Should have at least the placeholder circuit + assert!(backend + .circuits_dir + .join("dkg") + .join("pk") + .join("pk.json") + .exists()); + assert!(backend + .circuits_dir + .join("dkg") + .join("pk") + .join("pk.vk") + .exists()); + + let temp_path = temp.path().to_path_buf(); + drop(temp); + assert!(!temp_path.exists()); +} diff --git a/crates/zk-prover/tests/local_e2e_tests.rs b/crates/zk-prover/tests/local_e2e_tests.rs new file mode 100644 index 0000000000..7dea5d0896 --- /dev/null +++ b/crates/zk-prover/tests/local_e2e_tests.rs @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Local end-to-end tests that require a local bb binary. +//! These tests will be skipped if bb is not found on the system. + +mod common; + +use common::fixtures_dir; +use e3_fhe_params::BfvPreset; +use e3_zk_helpers::ciphernodes_committee::CiphernodesCommitteeSize; +use e3_zk_helpers::circuits::dkg::pk::circuit::PkCircuitInput; +use e3_zk_helpers::circuits::dkg::pk::prepare_pk_sample_for_test; +use e3_zk_helpers::circuits::{commitments::compute_dkg_pk_commitment, CircuitComputation}; +use e3_zk_helpers::PkCircuit; +use e3_zk_prover::{Provable, ZkBackend, ZkConfig, ZkProver}; +use std::path::PathBuf; +use tempfile::tempdir; +use tokio::{fs, process::Command}; + +async fn find_bb() -> Option { + if let Ok(output) = Command::new("which").arg("bb").output().await { + if output.status.success() { + let path = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if !path.is_empty() { + return Some(PathBuf::from(path)); + } + } + } + if let Ok(home) = std::env::var("HOME") { + for path in [ + format!("{}/.bb/bb", home), + format!("{}/.nargo/bin/bb", home), + format!("{}/.enclave/noir/bin/bb", home), + ] { + if std::path::Path::new(&path).exists() { + return Some(PathBuf::from(path)); + } + } + } + None +} + +async fn setup_test_prover(bb: &PathBuf) -> (ZkBackend, tempfile::TempDir) { + let temp = tempdir().unwrap(); + let temp_path = temp.path(); + let noir_dir = temp_path.join("noir"); + let bb_binary = noir_dir.join("bin").join("bb"); + let circuits_dir = noir_dir.join("circuits"); + let work_dir = noir_dir.join("work").join("test_node"); + let backend = ZkBackend::new( + bb_binary.clone(), + circuits_dir.clone(), + work_dir.clone(), + ZkConfig::default(), + ); + + fs::create_dir_all(&backend.circuits_dir).await.unwrap(); + fs::create_dir_all(backend.circuits_dir.join("vk")) + .await + .unwrap(); + fs::create_dir_all(&backend.work_dir).await.unwrap(); + fs::create_dir_all(backend.base_dir.join("bin")) + .await + .unwrap(); + + #[cfg(unix)] + std::os::unix::fs::symlink(bb, &backend.bb_binary).unwrap(); + + (backend, temp) +} + +#[tokio::test] +async fn test_pk_bfv_proof_generation() { + let bb = match find_bb().await { + Some(p) => p, + None => { + println!("skipping: bb not found"); + return; + } + }; + + let (backend, _temp) = setup_test_prover(&bb).await; + let fixtures = fixtures_dir(); + + let circuit_dir = backend.circuits_dir.join("dkg").join("pk"); + fs::create_dir_all(&circuit_dir).await.unwrap(); + fs::copy(fixtures.join("pk.json"), circuit_dir.join("pk.json")) + .await + .unwrap(); + fs::copy(fixtures.join("pk.vk"), circuit_dir.join("pk.vk")) + .await + .unwrap(); + + let preset = BfvPreset::InsecureThreshold512; + let sample = prepare_pk_sample_for_test(preset, CiphernodesCommitteeSize::Small); + + let prover = ZkProver::new(&backend); + let circuit = PkCircuit; + let e3_id = "test-pk-bfv-001"; + + let proof = circuit + .prove(&prover, &preset, &sample.dkg_public_key, e3_id) + .expect("proof generation should succeed"); + + assert!(!proof.data.is_empty(), "proof data should not be empty"); + assert!( + !proof.public_signals.is_empty(), + "public signals should not be empty" + ); + + prover.cleanup(e3_id).unwrap(); +} + +#[tokio::test] +async fn test_pk_bfv_proof_verification() { + let bb = match find_bb().await { + Some(p) => p, + None => { + println!("skipping: bb not found"); + return; + } + }; + + let (backend, _temp) = setup_test_prover(&bb).await; + let fixtures = fixtures_dir(); + + let circuit_dir = backend.circuits_dir.join("dkg").join("pk"); + fs::create_dir_all(&circuit_dir).await.unwrap(); + fs::copy(fixtures.join("pk.json"), circuit_dir.join("pk.json")) + .await + .unwrap(); + fs::copy(fixtures.join("pk.vk"), circuit_dir.join("pk.vk")) + .await + .unwrap(); + + let preset = BfvPreset::InsecureThreshold512; + let sample = prepare_pk_sample_for_test(preset, CiphernodesCommitteeSize::Small); + + let prover = ZkProver::new(&backend); + let circuit = PkCircuit; + let e3_id = "test-verify-001"; + + let proof = circuit + .prove(&prover, &preset, &sample.dkg_public_key, e3_id) + .expect("proof generation should succeed"); + + let party_id = 1; + let verification_result = circuit.verify(&prover, &proof, e3_id, party_id); + assert!( + verification_result.as_ref().is_ok_and(|&v| v), + "Proof verification failed: {:?}", + verification_result + ); + + prover.cleanup(e3_id).unwrap(); +} + +#[tokio::test] +async fn test_pk_bfv_commitment_consistency() { + let bb = match find_bb().await { + Some(p) => p, + None => { + println!("skipping: bb not found"); + return; + } + }; + + let (backend, _temp) = setup_test_prover(&bb).await; + let fixtures = fixtures_dir(); + + let circuit_dir = backend.circuits_dir.join("dkg").join("pk"); + fs::create_dir_all(&circuit_dir).await.unwrap(); + fs::copy(fixtures.join("pk.json"), circuit_dir.join("pk.json")) + .await + .unwrap(); + fs::copy(fixtures.join("pk.vk"), circuit_dir.join("pk.vk")) + .await + .unwrap(); + + let preset = BfvPreset::InsecureThreshold512; + let sample = prepare_pk_sample_for_test(preset, CiphernodesCommitteeSize::Small); + + let prover = ZkProver::new(&backend); + let circuit = PkCircuit; + let e3_id = "test-commitment-001"; + + let proof = circuit + .prove(&prover, &preset, &sample.dkg_public_key, e3_id) + .expect("proof generation should succeed"); + + // Verify the commitment from the proof is a valid field element + let commitment_from_proof = + num_bigint::BigInt::from_bytes_be(num_bigint::Sign::Plus, &proof.public_signals); + assert!( + commitment_from_proof > num_bigint::BigInt::from(0), + "commitment should be positive" + ); + + // Compute the commitment independently to ensure consistency + let circuit_input = PkCircuitInput { + public_key: sample.dkg_public_key.clone(), + }; + let computation_output = + PkCircuit::compute(preset, &circuit_input).expect("computation should succeed"); + let commitment_calculated = compute_dkg_pk_commitment( + &computation_output.witness.pk0is, + &computation_output.witness.pk1is, + computation_output.bits.pk_bit, + ); + + println!("Commitment from proof: {}", commitment_from_proof); + println!("Commitment calculated: {}", commitment_calculated); + + assert_eq!( + commitment_from_proof, commitment_calculated, + "Commitment from proof must match independently calculated commitment" + ); + + prover.cleanup(e3_id).unwrap(); +} diff --git a/crates/zk-prover/tests/witness_tests.rs b/crates/zk-prover/tests/witness_tests.rs new file mode 100644 index 0000000000..471dd929dd --- /dev/null +++ b/crates/zk-prover/tests/witness_tests.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +mod common; + +use common::fixtures_dir; +use e3_zk_prover::{input_map, CompiledCircuit, WitnessGenerator}; + +#[test] +fn test_witness_generation_from_fixture() { + let fixtures = fixtures_dir(); + let circuit = CompiledCircuit::from_file(&fixtures.join("dummy.json")).unwrap(); + + let witness_gen = WitnessGenerator::new(); + let inputs = input_map([("x", "5"), ("y", "3"), ("_sum", "8")]).unwrap(); + let witness = witness_gen.generate_witness(&circuit, inputs).unwrap(); + + assert!(witness.len() > 2); + assert_eq!(witness[0], 0x1f); + assert_eq!(witness[1], 0x8b); +} + +#[test] +fn test_witness_generation_wrong_sum_fails() { + let fixtures = fixtures_dir(); + let circuit = CompiledCircuit::from_file(&fixtures.join("dummy.json")).unwrap(); + + let witness_gen = WitnessGenerator::new(); + let inputs = input_map([("x", "5"), ("y", "3"), ("_sum", "10")]).unwrap(); + let result = witness_gen.generate_witness(&circuit, inputs); + + assert!(result.is_err()); +} + +#[test] +fn test_compiled_circuit_from_fixture() { + let fixtures = fixtures_dir(); + let circuit = CompiledCircuit::from_file(&fixtures.join("pk.json")).unwrap(); + + assert!( + !circuit.abi.parameters.is_empty(), + "PkBfv circuit should have parameters" + ); + assert!(circuit.abi.parameters.len() > 0); +} diff --git a/crates/zk-prover/versions.json b/crates/zk-prover/versions.json new file mode 100644 index 0000000000..26957fb367 --- /dev/null +++ b/crates/zk-prover/versions.json @@ -0,0 +1,12 @@ +{ + "required_bb_version": "3.0.0-nightly.20251104", + "required_circuits_version": "0.1.11", + "bb_download_url": "https://github.com/AztecProtocol/aztec-packages/releases/download/v{version}/barretenberg-{arch}-{os}.tar.gz", + "bb_checksums": { + "amd64-linux": "9740013d1aa0eb1b0bb2d71484c8b3debc5050a409bd5f12f8454fbfc7cb5419", + "amd64-darwin": "7874494dd1238655993a44b85d94e9dcc3589d29980eff8b03a7f167a45c32e4", + "arm64-linux": "ae6bf8518998523b4e135cd638f305a802f52e8dfa5ea9b1c210de7d04c55343", + "arm64-darwin": "6d353c05dbecc573d1b0ca992c8b222db8e873853b7910b792915629347f6789" + }, + "circuits_download_url": "https://github.com/gnosisguild/enclave/releases/download/v{version}/circuits-{version}.tar.gz" +} diff --git a/examples/CRISP/.gitignore b/examples/CRISP/.gitignore index 384bd03867..d007b81dcc 100644 --- a/examples/CRISP/.gitignore +++ b/examples/CRISP/.gitignore @@ -48,4 +48,5 @@ playwright-report/ .enclave/config/ .enclave/caches/ .enclave/ready +.enclave/noir/ cache_hardhat/ diff --git a/examples/CRISP/Cargo.lock b/examples/CRISP/Cargo.lock index a391de8df8..368788038b 100644 --- a/examples/CRISP/Cargo.lock +++ b/examples/CRISP/Cargo.lock @@ -2403,6 +2403,7 @@ dependencies = [ "fhe", "num-bigint", "num-traits", + "serde", "thiserror 1.0.69", ] diff --git a/examples/CRISP/scripts/dev_cipher.sh b/examples/CRISP/scripts/dev_cipher.sh index 2df14bc3ff..7760f6e8b2 100755 --- a/examples/CRISP/scripts/dev_cipher.sh +++ b/examples/CRISP/scripts/dev_cipher.sh @@ -22,6 +22,9 @@ enclave wallet set --name cn3 --private-key "$PRIVATE_KEY_CN3" enclave wallet set --name cn4 --private-key "$PRIVATE_KEY_CN4" enclave wallet set --name cn5 --private-key "$PRIVATE_KEY_CN5" +echo "Setting up ZK prover..." +enclave noir setup + # using & instead of -d so that wait works below enclave nodes up -v & diff --git a/examples/CRISP/server/src/cli/commands.rs b/examples/CRISP/server/src/cli/commands.rs index de7d43f563..1211c2a26c 100644 --- a/examples/CRISP/server/src/cli/commands.rs +++ b/examples/CRISP/server/src/cli/commands.rs @@ -107,7 +107,16 @@ pub async fn initialize_crisp_round( let credits = U256::from(1); // Serialize the custom parameters to bytes. - let custom_params_bytes = Bytes::from((token_address, balance_threshold, num_options, credit_mode, credits).abi_encode()); + let custom_params_bytes = Bytes::from( + ( + token_address, + balance_threshold, + num_options, + credit_mode, + credits, + ) + .abi_encode(), + ); let threshold: [u32; 2] = [CONFIG.e3_threshold_min, CONFIG.e3_threshold_max]; let mut current_timestamp = get_current_timestamp().await?; diff --git a/examples/CRISP/server/src/server/models.rs b/examples/CRISP/server/src/server/models.rs index 9c7518f1d2..f80a6b4ff2 100644 --- a/examples/CRISP/server/src/server/models.rs +++ b/examples/CRISP/server/src/server/models.rs @@ -7,7 +7,7 @@ use anyhow::Result; use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; -use serde_repr::{Serialize_repr, Deserialize_repr}; +use serde_repr::{Deserialize_repr, Serialize_repr}; #[derive(Derivative, Deserialize, Serialize)] #[derivative(Debug)] diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json index 749edd8cf4..ef641a20af 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/IBondingRegistry.sol/IBondingRegistry.json @@ -877,5 +877,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/IBondingRegistry.sol", - "buildInfoId": "solc-0_8_28-15da934c0854938352efc3fea207d9956917d5c6" + "buildInfoId": "solc-0_8_28-e1018ade42270e3293a7c3b47ab6b91dfdeea5fe" } \ No newline at end of file diff --git a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json index abb231f6ab..2ce871f7a3 100644 --- a/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json +++ b/packages/enclave-contracts/artifacts/contracts/interfaces/ICiphernodeRegistry.sol/ICiphernodeRegistry.json @@ -540,5 +540,5 @@ "deployedLinkReferences": {}, "immutableReferences": {}, "inputSourceName": "project/contracts/interfaces/ICiphernodeRegistry.sol", - "buildInfoId": "solc-0_8_28-c50eb34c88f83554dbf0f8f704a3681d5dfc5322" + "buildInfoId": "solc-0_8_28-e1018ade42270e3293a7c3b47ab6b91dfdeea5fe" } \ No newline at end of file diff --git a/packages/enclave-contracts/deployed_contracts.json b/packages/enclave-contracts/deployed_contracts.json index bb782c5a49..9429c1f1b5 100644 --- a/packages/enclave-contracts/deployed_contracts.json +++ b/packages/enclave-contracts/deployed_contracts.json @@ -106,5 +106,113 @@ "blockNumber": 10043257, "address": "0xC39b101f2FB4ea677c1EA18f92C15CDD54Af40c2" } + }, + "localhost": { + "PoseidonT3": { + "blockNumber": 3, + "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" + }, + "MockUSDC": { + "constructorArgs": { + "initialSupply": "1000000" + }, + "blockNumber": 4, + "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" + }, + "EnclaveToken": { + "constructorArgs": { + "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + }, + "blockNumber": 5, + "address": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" + }, + "EnclaveTicketToken": { + "constructorArgs": { + "baseToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "registry": "0x0000000000000000000000000000000000000001", + "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + }, + "blockNumber": 7, + "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + }, + "SlashingManager": { + "constructorArgs": { + "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "bondingRegistry": "0x0000000000000000000000000000000000000001" + }, + "blockNumber": 8, + "address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" + }, + "BondingRegistry": { + "constructorArgs": { + "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "ticketToken": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", + "licenseToken": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", + "registry": "0x0000000000000000000000000000000000000001", + "slashedFundsTreasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "ticketPrice": "10000000", + "licenseRequiredBond": "100000000000000000000", + "minTicketBalance": "1", + "exitDelay": "604800" + }, + "proxyRecords": { + "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f875707000000000000000000000000cf7ed3acca5a467e9e704c703e8d87f634fb0fc90000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", + "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "proxyAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", + "proxyAdminAddress": "0x94099942864EA81cCF197E9D71ac53310b1468D8", + "implementationAddress": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" + }, + "blockNumber": 8, + "address": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" + }, + "CiphernodeRegistryOwnable": { + "constructorArgs": { + "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "enclaveAddress": "0x0000000000000000000000000000000000000001", + "submissionWindow": "10" + }, + "proxyRecords": { + "initData": "0x1794bb3c000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000a", + "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "proxyAddress": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + "proxyAdminAddress": "0x6F1216D1BFe15c98520CA1434FC1d9D57AC95321", + "implementationAddress": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" + }, + "blockNumber": 11, + "address": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + }, + "Enclave": { + "constructorArgs": { + "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "registry": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + "bondingRegistry": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", + "feeToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "maxDuration": "2592000", + "params": [ + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000ffffee0010000000000000000000000000000000000000000000000000000000ffffc400100000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000" + ] + }, + "proxyRecords": { + "initData": "0xefe0308b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000610178da211fef7d417bc0e6fed39f05609ad7880000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe60000000000000000000000009fe46736679d2d9a65f0992f2272de9f3c7fa6e00000000000000000000000000000000000000000000000000000000000278d0000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000ffffee0010000000000000000000000000000000000000000000000000000000ffffc400100000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000", + "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "proxyAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "proxyAdminAddress": "0x1F708C24a0D3A740cD47cC0444E9480899f3dA7D", + "implementationAddress": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" + }, + "blockNumber": 13, + "address": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + }, + "MockComputeProvider": { + "blockNumber": 23, + "address": "0x59b670e9fA9D0A427751Af201D676719a970857b" + }, + "MockDecryptionVerifier": { + "blockNumber": 24, + "address": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1" + }, + "MockE3Program": { + "blockNumber": 25, + "address": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" + } } } \ No newline at end of file diff --git a/templates/default/Cargo.lock b/templates/default/Cargo.lock index 6421ea5a9f..480bc9a824 100644 --- a/templates/default/Cargo.lock +++ b/templates/default/Cargo.lock @@ -1254,6 +1254,7 @@ dependencies = [ "fhe", "num-bigint", "num-traits", + "serde", "thiserror", ] diff --git a/templates/default/scripts/dev_ciphernodes.sh b/templates/default/scripts/dev_ciphernodes.sh index 6beb31455e..3f424bd477 100755 --- a/templates/default/scripts/dev_ciphernodes.sh +++ b/templates/default/scripts/dev_ciphernodes.sh @@ -38,6 +38,9 @@ enclave wallet set --name cn3 --private-key "$PRIVATE_KEY_CN3" enclave wallet set --name cn4 --private-key "$PRIVATE_KEY_CN4" enclave wallet set --name cn5 --private-key "$PRIVATE_KEY_CN5" +echo "Setting up ZK prover..." +enclave noir setup + # using & instead of -d so that wait works below enclave nodes up -v & diff --git a/tests/integration/base.sh b/tests/integration/base.sh index 9bd345b2c6..8899b5791e 100755 --- a/tests/integration/base.sh +++ b/tests/integration/base.sh @@ -27,6 +27,9 @@ enclave_wallet_set cn3 "$PRIVATE_KEY_CN3" enclave_wallet_set cn4 "$PRIVATE_KEY_CN4" enclave_wallet_set cn5 "$PRIVATE_KEY_CN5" +heading "Setup ZK prover" +$ENCLAVE_BIN noir setup + # start swarm enclave_nodes_up diff --git a/tests/integration/persist.sh b/tests/integration/persist.sh index 096050c966..26e813291d 100755 --- a/tests/integration/persist.sh +++ b/tests/integration/persist.sh @@ -27,6 +27,9 @@ enclave_wallet_set cn3 "$PRIVATE_KEY_CN3" enclave_wallet_set cn4 "$PRIVATE_KEY_CN4" enclave_wallet_set cn5 "$PRIVATE_KEY_CN5" +heading "Setup ZK prover" +$ENCLAVE_BIN noir setup + # start swarm enclave_nodes_up