From c8960f5302c75d00f802c17e840f4e7c7d9cf8b4 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 23 Mar 2026 15:56:59 +0100 Subject: [PATCH 1/8] refactor: check final vk_hash in crisp fold circuit --- Cargo.lock | 1 + crates/zk-helpers/Cargo.toml | 7 +- crates/zk-helpers/src/bin/compute_vk_hash.rs | 57 +++++++++++ crates/zk-helpers/src/circuits/commitments.rs | 31 ++++++ examples/CRISP/Cargo.lock | 1 + examples/CRISP/Cargo.toml | 2 +- examples/CRISP/circuits/bin/fold/src/main.nr | 16 +-- examples/CRISP/package.json | 1 + .../contracts/CRISPProgram.sol | 14 +-- .../contracts/CRISPVerifier.sol | 98 +++++++++---------- examples/CRISP/packages/crisp-sdk/src/vote.ts | 6 +- examples/CRISP/scripts/compile_circuits.sh | 24 +++++ examples/CRISP/scripts/compute_vk_hash.sh | 17 ++++ 13 files changed, 204 insertions(+), 71 deletions(-) create mode 100644 crates/zk-helpers/src/bin/compute_vk_hash.rs create mode 100755 examples/CRISP/scripts/compute_vk_hash.sh diff --git a/Cargo.lock b/Cargo.lock index 2f4746ac98..5dafd131ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3863,6 +3863,7 @@ dependencies = [ "fhe", "fhe-math", "fhe-traits", + "hex", "itertools 0.14.0", "ndarray", "num-bigint", diff --git a/crates/zk-helpers/Cargo.toml b/crates/zk-helpers/Cargo.toml index 6c8a75feed..518bc9d2fc 100644 --- a/crates/zk-helpers/Cargo.toml +++ b/crates/zk-helpers/Cargo.toml @@ -17,6 +17,7 @@ e3-fhe-params = { workspace = true } fhe = { workspace = true } fhe-math = { workspace = true } fhe-traits = { workspace = true } +hex = { workspace = true } num-bigint = { workspace = true } num-integer = { workspace = true } num-traits = { workspace = true } @@ -35,4 +36,8 @@ tempfile = { workspace = true } [[bin]] name = "zk_cli" -path = "src/bin/zk_cli.rs" \ No newline at end of file +path = "src/bin/zk_cli.rs" + +[[bin]] +name = "compute-vk-hash" +path = "src/bin/compute_vk_hash.rs" \ No newline at end of file diff --git a/crates/zk-helpers/src/bin/compute_vk_hash.rs b/crates/zk-helpers/src/bin/compute_vk_hash.rs new file mode 100644 index 0000000000..44eb7466e1 --- /dev/null +++ b/crates/zk-helpers/src/bin/compute_vk_hash.rs @@ -0,0 +1,57 @@ +// 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. + +//! Combine any number of Barretenberg `vk_hash` blobs (32 bytes each, big-endian field) with the +//! same SAFE sponge as Noir `lib::math::commitments::compute_vk_hash` (`DS_VK_HASH`). +//! +//! Input order is preserved — e.g. CRISP fold uses: +//! `user_data_encryption`, `crisp`, `ct0`, `ct1`. + +use anyhow::{bail, Context, Result}; +use ark_bn254::Fr; +use ark_ff::{BigInteger, PrimeField}; +use clap::Parser; +use e3_zk_helpers::compute_vk_hash; +use std::fs; +use std::path::PathBuf; + +#[derive(Parser)] +#[command(name = "compute-vk-hash")] +#[command(about = "Hash N vk_hash files with compute_vk_hash (SAFE / DS_VK_HASH), order preserved")] +struct Args { + /// Paths to 32-byte `vk_hash` files from `bb write_vk ... -o ` (use one dir per circuit). + #[arg(required = true)] + vk_hash_files: Vec, +} + +fn field_from_vk_hash_file(path: &std::path::Path) -> Result { + let bytes = fs::read(path).with_context(|| format!("read {}", path.display()))?; + if bytes.len() != 32 { + bail!("{}: expected 32 bytes, got {}", path.display(), bytes.len()); + } + let mut arr = [0u8; 32]; + arr.copy_from_slice(&bytes); + Ok(Fr::from_be_bytes_mod_order(&arr)) +} + +fn field_to_padded_be_hex(fr: Fr) -> String { + let repr = fr.into_bigint().to_bytes_be(); + let mut out = [0u8; 32]; + let start = 32usize.saturating_sub(repr.len()); + out[start..].copy_from_slice(&repr); + format!("0x{}", hex::encode(out)) +} + +fn main() -> Result<()> { + let args = Args::parse(); + let mut fields = Vec::with_capacity(args.vk_hash_files.len()); + for path in &args.vk_hash_files { + fields.push(field_from_vk_hash_file(path).with_context(|| path.display().to_string())?); + } + let combined = compute_vk_hash(fields); + println!("{}", field_to_padded_be_hex(combined)); + Ok(()) +} diff --git a/crates/zk-helpers/src/circuits/commitments.rs b/crates/zk-helpers/src/circuits/commitments.rs index b145b1808d..15c8b34098 100644 --- a/crates/zk-helpers/src/circuits/commitments.rs +++ b/crates/zk-helpers/src/circuits/commitments.rs @@ -80,6 +80,14 @@ const DS_AGGREGATED_SHARES: [u8; 64] = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; +/// String: "VK_HASH" +const DS_VK_HASH: [u8; 64] = [ + 0x56, 0x4b, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + /// String: "RECURSIVE_AGGREGATION" const DS_RECURSIVE_AGGREGATION: [u8; 64] = [ 0x52, 0x45, 0x43, 0x55, 0x52, 0x53, 0x49, 0x56, 0x45, 0x5f, 0x41, 0x47, 0x47, 0x52, 0x45, 0x47, @@ -144,6 +152,15 @@ pub fn compute_commitments( compute_safe(domain_separator, payload, io_pattern) } +/// Combine verification-key hashes with the `VK_HASH` domain separator (SAFE sponge). +/// +/// Matches Noir `lib::math::commitments::compute_vk_hash`. +pub fn compute_vk_hash(vk_hashes: Vec) -> Field { + let input_size = vk_hashes.len() as u32; + let io_pattern = [0x80000000 | input_size, 1]; + compute_commitments(vk_hashes, DS_VK_HASH, io_pattern)[0] +} + // ============================================================================ // COMMITMENTS // ============================================================================ @@ -643,4 +660,18 @@ mod tests { let actual = compute_threshold_share_decryption_challenge(payload); assert_eq!(actual, expected); } + + #[test] + fn compute_vk_hash_matches_manual_commitment() { + let vk_hashes = vec![ + Field::from(7u64), + Field::from(8u64), + Field::from(9u64), + Field::from(10u64), + ]; + let input_size = vk_hashes.len() as u32; + let io_pattern = [0x80000000 | input_size, 1]; + let expected = compute_commitments(vk_hashes.clone(), super::DS_VK_HASH, io_pattern)[0]; + assert_eq!(compute_vk_hash(vk_hashes), expected); + } } diff --git a/examples/CRISP/Cargo.lock b/examples/CRISP/Cargo.lock index ad4276264f..12df3eb34f 100644 --- a/examples/CRISP/Cargo.lock +++ b/examples/CRISP/Cargo.lock @@ -2544,6 +2544,7 @@ dependencies = [ "fhe", "fhe-math", "fhe-traits", + "hex", "itertools 0.14.0", "ndarray", "num-bigint", diff --git a/examples/CRISP/Cargo.toml b/examples/CRISP/Cargo.toml index 59ad063930..ba7b78b1c9 100644 --- a/examples/CRISP/Cargo.toml +++ b/examples/CRISP/Cargo.toml @@ -6,7 +6,7 @@ members = [ "crates/zk-inputs", "crates/zk-inputs-wasm", "crates/evm_helpers", - "crates/crisp-utils" + "crates/crisp-utils", ] resolver = "3" diff --git a/examples/CRISP/circuits/bin/fold/src/main.nr b/examples/CRISP/circuits/bin/fold/src/main.nr index 1ffb1dc60f..dffe7f56ac 100644 --- a/examples/CRISP/circuits/bin/fold/src/main.nr +++ b/examples/CRISP/circuits/bin/fold/src/main.nr @@ -7,6 +7,11 @@ use bb_proof_verification::{UltraHonkProof, UltraHonkVerificationKey, verify_honk_proof_non_zk}; use enclave_lib::math::commitments::compute_vk_hash; +/// Binds the four inner `vk_hash` witnesses; +// update when ct0 / ct1 / user_data_encryption / crisp change (`pnpm compute:vk-hash`). +pub global CRISP_FOLD_EXPECTED_KEY_HASH: Field = + 0x2e53e3285d47eebd65eb932cf6a57ecaa5e43f90b1673165715c92b78e493a9c; + fn main( // User Data Encryption Section. user_data_encryption_verification_key: UltraHonkVerificationKey, @@ -25,7 +30,7 @@ fn main( final_ct_commitment: pub Field, ct_commitment: Field, k1_commitment: Field, -) -> pub (Field, Field) { +) -> pub Field { verify_honk_proof_non_zk( user_data_encryption_verification_key, user_data_encryption_proof, @@ -54,15 +59,14 @@ fn main( // Verify that the k1_commitment from the crisp proof matches the one computed from user data encryption. assert(k1_commitment == user_data_encryption_public_inputs[4]); - // Hash the full VK chain: key hashes from both proofs (user_data_encryption, crisp, ct0, ct1) so the verifier can - // check the entire proof genealogy. let mut vk_hashes = Vec::new(); vk_hashes.push(user_data_encryption_key_hash); vk_hashes.push(crisp_key_hash); vk_hashes.push(user_data_encryption_public_inputs[0]); // ct0_key_hash vk_hashes.push(user_data_encryption_public_inputs[1]); // ct1_key_hash - let key_hash = compute_vk_hash(vk_hashes); - // Return pk_commitment from user data encryption proof and the combined VK hash (verified in crisp contract). - (user_data_encryption_public_inputs[2], key_hash) + // Verify that the computed key hash matches the expected key hash. + assert(compute_vk_hash(vk_hashes) == CRISP_FOLD_EXPECTED_KEY_HASH); + + user_data_encryption_public_inputs[2] } diff --git a/examples/CRISP/package.json b/examples/CRISP/package.json index ab3c936bcc..309e70ee51 100644 --- a/examples/CRISP/package.json +++ b/examples/CRISP/package.json @@ -23,6 +23,7 @@ "test": "pnpm test:e2e", "test:circuits": "cd circuits/lib && nargo test", "compile:circuits": "bash ./scripts/compile_circuits.sh", + "compute:vk-hash": "pnpm compile:circuits && bash ./scripts/compute_vk_hash.sh", "test:sdk": "pnpm -C packages/crisp-sdk test", "build:sdk": "pnpm -C packages/crisp-sdk build", "report": "playwright show-report", diff --git a/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol b/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol index 87df50b1d2..5ac189c530 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol @@ -41,9 +41,6 @@ contract CRISPProgram is IE3Program, Ownable { uint8 public constant TREE_DEPTH = 20; /// @notice Maximum number of bits allocated for vote counts in the plaintext output per option. uint256 constant MAX_VOTE_BITS = 50; - /// @notice The zero-knowledge verification key hash for the CRISP program. - bytes32 public constant ZK_VK_HASH = 0x2e53e3285d47eebd65eb932cf6a57ecaa5e43f90b1673165715c92b78e493a9c; - // State variables IEnclave public enclave; IRiscZeroVerifier public risc0Verifier; @@ -173,15 +170,15 @@ contract CRISPProgram is IE3Program, Ownable { if (data.length == 0) revert EmptyInputData(); - (bytes memory noirProof, address slotAddress, bytes32 encryptedVoteCommitment, bytes32 zkKeyHash, bytes memory encryptedVote) = abi - .decode(data, (bytes, address, bytes32, bytes32, bytes)); - - if (zkKeyHash != ZK_VK_HASH) revert InvalidNoirProof(); + (bytes memory noirProof, address slotAddress, bytes32 encryptedVoteCommitment, bytes memory encryptedVote) = abi.decode( + data, + (bytes, address, bytes32, bytes) + ); (uint40 voteIndex, bytes32 previousEncryptedVoteCommitment) = _processVote(e3Id, slotAddress, encryptedVoteCommitment); // Set the public inputs for the proof. Order must match Noir circuit. - bytes32[] memory noirPublicInputs = new bytes32[](8); + bytes32[] memory noirPublicInputs = new bytes32[](7); noirPublicInputs[0] = previousEncryptedVoteCommitment; noirPublicInputs[1] = bytes32(e3Data[e3Id].merkleRoot); noirPublicInputs[2] = bytes32(uint256(uint160(slotAddress))); @@ -189,7 +186,6 @@ contract CRISPProgram is IE3Program, Ownable { noirPublicInputs[4] = bytes32(e3Data[e3Id].numOptions); noirPublicInputs[5] = encryptedVoteCommitment; noirPublicInputs[6] = e3.committeePublicKey; - noirPublicInputs[7] = zkKeyHash; // Check if the ciphertext was encrypted correctly if (!honkVerifier.verify(noirProof, noirPublicInputs)) { diff --git a/examples/CRISP/packages/crisp-contracts/contracts/CRISPVerifier.sol b/examples/CRISP/packages/crisp-contracts/contracts/CRISPVerifier.sol index f8de9409a6..a96f3f6ddb 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/CRISPVerifier.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/CRISPVerifier.sol @@ -7,85 +7,85 @@ pragma solidity >=0.8.21; uint256 constant N = 2097152; uint256 constant LOG_N = 21; -uint256 constant NUMBER_OF_PUBLIC_INPUTS = 24; -uint256 constant VK_HASH = 0x0359288034fa67d6452fb3ad06256357a05a13751a860cb9aefa1e6537bf741c; +uint256 constant NUMBER_OF_PUBLIC_INPUTS = 23; +uint256 constant VK_HASH = 0x0a2a97cbcf70be68e297357fc7b940d5c4a69e571fda313ec2749d9b9edb5eac; library HonkVerificationKey { function loadVerificationKey() internal pure returns (Honk.VerificationKey memory) { Honk.VerificationKey memory vk = Honk.VerificationKey({ circuitSize: uint256(2097152), logCircuitSize: uint256(21), - publicInputsSize: uint256(24), + publicInputsSize: uint256(23), ql: Honk.G1Point({ - x: uint256(0x234faa9054de61d5d3d8fcd4ef8da9eea97a0ce55e726d3b661477953c4a2251), - y: uint256(0x2af662ee06d2565688e865a012e0eb31e8ef519568fd5f36d793db868c06014c) + x: uint256(0x242d54b68c66568368d0404f9cd1b34bc0a494cedfcc53c14bd6945f504fdb48), + y: uint256(0x2157a81ed36f526beb4f73ddd0ba9eefff28f4e2681efea259d897a7ccc2b7c5) }), qr: Honk.G1Point({ - x: uint256(0x1d26b28d1460c0aded9fc8d8e60240ffc1307399032de105684237ca0b3d8b2c), - y: uint256(0x007836bcebafb4f2826a9568bdfc09ab40cc1e45fd0033cc52c437bcdfcdc106) + x: uint256(0x220f22ce02c5f86433f0b0c092eb10d7a6f8665a8ed85ad10ecdf1263597233b), + y: uint256(0x0bd316345847358ce7158797f94bde087c32ab752c4a7719a11b4bdbbc84f974) }), qo: Honk.G1Point({ - x: uint256(0x270fc953cc356ce24afe388466b9face4f44e8bf34aece7d400f866a60760ca7), - y: uint256(0x120ceb2eb686a23b375971abdcb803f3c76ac2970092a8122e6f549c7e2cbe27) + x: uint256(0x008acfd1ebe1d9a02e4f382defde95296beee0d436243203888814709d9010f6), + y: uint256(0x1baf2528d464b5eb2d89e2c945de6b316ab2276dc4bb2f54018e1f30d7a74e5b) }), q4: Honk.G1Point({ - x: uint256(0x09c27b010f18d47a875b4130a71e9414eff97027829cae3cc482d4beb1875c7b), - y: uint256(0x1875cc2dc5e537161fe454acbd1ce32e1240ada2d2618ce80b8481c206266ab0) + x: uint256(0x28049e993f2d92edee7f7e0e88db6a2b3cd685e315c008b6756f0c57da94f2a8), + y: uint256(0x1ab99eec5fc4626da9bdd107f385e5c7789645af76f89ad39eebfd688e42d551) }), qm: Honk.G1Point({ - x: uint256(0x1c5987d7d41b2436961f70bcb06cc20ff10d59d6f9b2c7b28ea5180b4e15da1b), - y: uint256(0x18816198ba37696350121c3cfa66abefa8d6e95b867be08e85cc64d29f9d3469) + x: uint256(0x1e10931f45270e5b77bd6527dac65c9686699918ae2b72c79c3f2fd8f5b4eaf7), + y: uint256(0x10ca3b75073c6976ed27c79f4c447b858cf1c2f6df9bd7c2c29ef3e6394539df) }), qc: Honk.G1Point({ - x: uint256(0x25f9f44ede97ba190b3dd793e16d74e063a47f323c6e983da6f9ab5db6beb63e), - y: uint256(0x1929f7a9d4a4a25ae32077714b299dccd62bac12f77da179704e3e96b422413b) + x: uint256(0x1411083d6937bc5991da7850d0b6d1cd35a91c9a9005b9916ce1fe624913dd2e), + y: uint256(0x07863eeb3af1ada7c6ddcfe010829a22ee57628b375e23d25813c137b5d85cc0) }), qLookup: Honk.G1Point({ - x: uint256(0x131342f1f7ba8c1218b01824d28190937da03bf8dea49d4d699c9a268871ea0b), - y: uint256(0x24bcefad8c9004741c53b103f782698c451cd5b7221b3817b3529cf1188278f3) + x: uint256(0x1958b13f38f58756e23989a418de3c113d47a2256ba6f60a9e2ec0523c52a991), + y: uint256(0x286c1d4c53af6654de284fbb620f90a9e00aa9470a9e3c58a56cc81e212df923) }), qArith: Honk.G1Point({ - x: uint256(0x2def7e188d2e52391dafd0925a06bc62de391a4b9bde443396bf561abf9c7ed4), - y: uint256(0x13a5a4401e3fa95d12bb1511f3836ba02cd42ca963d2a65227eb054fee5b56f2) + x: uint256(0x03130a68b3e95125f16a488caab56bd8696b7916b06b7ec38b82ec8eeef502fb), + y: uint256(0x2d3817390db7d4de3a9992c85d10ead9152303f1b0b1fc090c5212c8465e8849) }), qDeltaRange: Honk.G1Point({ - x: uint256(0x1f6899527408847834b3fffc57906066244d532194aca24616ac5bc799fefc4d), - y: uint256(0x101c4d2ec7504ddca07f96bb8c622883452ff4fcfc3f0bf285d7b634ba14ec31) + x: uint256(0x03e8f687eff189f129b0fa24fb1596c0e45521032cf95b37a8010f90289cee28), + y: uint256(0x0687c67cf56e0e6cbbd7f9d240c482ce7c29529c2c2645a13d458200691ceabd) }), qElliptic: Honk.G1Point({ - x: uint256(0x304c3aa07720dd601b93139c0c73c92ff98822bef32c92a1a3180d398aec5350), - y: uint256(0x0e17e55c50c8864b38040cddcc6b66f2127150658559a413d0e18704a4ca9ecd) + x: uint256(0x0af96472dc301f8ea15d24c89932462eff60385960a9a8e367c5afeab2f29089), + y: uint256(0x073f733bd4638696274b6853acd979b76c4011b2a92df241c794cf983cad7e56) }), qMemory: Honk.G1Point({ - x: uint256(0x0808e64714fd19ba05ec0a47bbbee6619e6b39885fa6f6ff4d3e2866f8445423), - y: uint256(0x00eccd76054d02841b7474edc58580b9b48848fe1c81ac201d480a9814996dbf) + x: uint256(0x28ae521fef3cd4137334cd47109c2a61b2ef60c1037ada38394cbc283ec9fb14), + y: uint256(0x1db8a90ad93ca5a434d436afc55ae0fb65ba655ef04e88bc83882458399b3401) }), qNnf: Honk.G1Point({ - x: uint256(0x0482a321762ae984d3abf9fb79cb9ae9a88f642c28ab2e6c8340938549a915b4), - y: uint256(0x154f88d62e30be611aa186b60c7ef4a24bfb6ec54fdbe10872ebc962c79266c0) + x: uint256(0x1efcf0bf90e4730db87f1c39c5c4d7fe5912707b8e03fb6a228cf573cbb7a847), + y: uint256(0x0789f3f4a2d3e9694fb444693d3637bbe77a80c15cbc9b698aa9a2f170cb96aa) }), qPoseidon2External: Honk.G1Point({ - x: uint256(0x190c7aa9c5916f96733c4e60c392fe92d977b896e1c087a27db64664b1923367), - y: uint256(0x2f98f87ac92ed3a4f0055098cad62aab7b9a2c4bd4383b37022aa323c6ea0d7a) + x: uint256(0x0e07a8da8d6e7d7412b2bc8932c876021c9911baf5fe244426423df1f8bd8dfe), + y: uint256(0x155d9bd466fc43a12bef1ee43dcddf75a65f54c6465706583bf917157902a730) }), qPoseidon2Internal: Honk.G1Point({ - x: uint256(0x2e9374229e687460b00565dbd78637a12cdbd026211e59662f3ccbaba095cace), - y: uint256(0x2fd8240ed5842471405822c80b9a3c82e3f80fa07359f364264ac256e0756599) + x: uint256(0x2a7d697eac85644ebd7ed8a6183be661a5f0af7ebc00f75b881fa6b0a9df67c9), + y: uint256(0x217e586cf7f60ef4fb5448cd1983908bf66e5779b486c4b070cfaa0fafaa94ec) }), s1: Honk.G1Point({ - x: uint256(0x01da6b0ff7e4247d584adfa0ee1c8a8f0f44ad0010d39f4a520d836192202856), - y: uint256(0x2eaf9744557a3b300d73fed4080e74b894296d179bcb75dd32fea66e63a02e5a) + x: uint256(0x1c10347f93203cbe2177cb7d68ae09288587ece9b38576d512ee4ab299a90dba), + y: uint256(0x0b0b14a9a9e530df6379cda78a6fcbb6e5d316cbbc989fd2151845db71200002) }), s2: Honk.G1Point({ - x: uint256(0x2322e09796c58d9acfa91dcc0c4491663a901c6ce98e4855dd59f273d6b9abd2), - y: uint256(0x0f6d5cef8233cffdf200f1acf0b6db5526a6517eee8946f2c813b64a1627edaa) + x: uint256(0x3043bbd2cf31841736e85916d52b6472646dfefd77bda3985fb34f2c6d29a5cf), + y: uint256(0x27bd57795ffc6d6860c14d8eea99e937e5dd7a8a8e6ce65989e230ac3f147dc3) }), s3: Honk.G1Point({ - x: uint256(0x227f46a481e3ee5cae6e9a935678ecaf85e497d8e9e3001b0d896869b4b618ae), - y: uint256(0x11108d549d670d953c712e9db06d38b49be83be4aace3bf51a20bbdb33f8bee9) + x: uint256(0x190115ef9a99f80fdc2c2e62980b84d5c760c884b0a6ceb6f4e6ed7dbe2a858c), + y: uint256(0x1fae5b7348acd64c333c14310e2165bb19bb11faa671e8736f738f6f3c7bd8bc) }), s4: Honk.G1Point({ - x: uint256(0x24859c2a41c9988e6b5699ea8fc920349ffe506399867de7731da1dd9054c30d), - y: uint256(0x1be6e6049fb1228ab9b3e1a87bcd102e1b758ba59f39b4bb354b1d44a32dc89f) + x: uint256(0x0420023320a950d9aefa400feb726fd04430fc5ee076dd019d474d5942c1c3f8), + y: uint256(0x1e932442ae6b7199b750e766a706e7144a090e46e87558380556f21189568ac6) }), t1: Honk.G1Point({ x: uint256(0x099e3bd5a0a00ab7fe18040105b9b395b5d8b7b4a63b05df652b0d10ef146d26), @@ -104,28 +104,28 @@ library HonkVerificationKey { y: uint256(0x261522c4089330646aff96736194949330952ae74c573d1686d9cb4a00733854) }), id1: Honk.G1Point({ - x: uint256(0x1609f3a69b11fceba44d010b8bcd36034b5264f4665c3b9d7d964693ae3f4c2e), - y: uint256(0x08aefcb1e6c05704d7f19526175dee32bf6db7f3780ca8e9f460727bacede468) + x: uint256(0x0366dcbbe46bdc20ba8c32bf1f728bb3545810f0a5d1f0767e92a19b100a3ea8), + y: uint256(0x22dcb03e03478e67e8430dbb8c76fc2e4f7c9c4ad95e7992681418a052349c3c) }), id2: Honk.G1Point({ - x: uint256(0x004e940ec86e291f968f5e1e703e8a61b39fb6d60bbbed3e72a58f230ac6c38b), - y: uint256(0x0ebbcea20f7ab33512246d748c0c0f8ac6b3084205cf358ab62fa4b1c83bc9d6) + x: uint256(0x2292c4a1ae537168604b25cbd971e5d34273f59112f1f11e0bd3f6a7b5a60e3a), + y: uint256(0x1463b8da6c876d73a1ef8b1d3f81d0a30fbe505ad512f7695c76e69e888cc61f) }), id3: Honk.G1Point({ - x: uint256(0x216055263eda3960804d6fc3d8fb45cfb282d92f44fa31e0e42bad8656eb7131), - y: uint256(0x059053bf915da4d1b013b070779e1cc3de3baca4f984bfc901b23ad26d72af03) + x: uint256(0x17e0f76be7ca15add8e69985d01b0e3926da6207cc5294805f8ac77944cf4827), + y: uint256(0x0b8fc29d8ec26f1a511ff4c1e8764a754eb894c936a36c68a54a8a359d6ce313) }), id4: Honk.G1Point({ - x: uint256(0x2c4cc79d180f4b4ecf1afc7dc189f5e753df807f80c976640e2ab7b52c26b74e), - y: uint256(0x2e7ce37a25ad2465d6443f27b3092a5b7f1b35286959a78256ae0f1f3cea8783) + x: uint256(0x07689f951bf952cf71fbadd10a9d9045989b715f48ae086d76087d7bbb11defe), + y: uint256(0x2f8f2787108298eb7a85b1279018462eaf254c5a8e367c24b2837517123009f6) }), lagrangeFirst: Honk.G1Point({ x: uint256(0x0000000000000000000000000000000000000000000000000000000000000001), y: uint256(0x0000000000000000000000000000000000000000000000000000000000000002) }), lagrangeLast: Honk.G1Point({ - x: uint256(0x2fcf4fb13521bea1bbe36d383297ec557a79c6fc506433f08590bb3ca6107c19), - y: uint256(0x0e638803c967dd3ac316ddddcfa548c998e833cba8219f3e6a11a4352ef5aa47) + x: uint256(0x03f905e0bdb4b0224867351c8f171cc3a7e876816264f9f214ab5d1b904e337e), + y: uint256(0x1e089d2e9200be93bf1d960df59d5bef8f619c1daee69fa3c2ec6b95d16f4fca) }) }); return vk; diff --git a/examples/CRISP/packages/crisp-sdk/src/vote.ts b/examples/CRISP/packages/crisp-sdk/src/vote.ts index 0ffcd55d9e..b2ec296976 100644 --- a/examples/CRISP/packages/crisp-sdk/src/vote.ts +++ b/examples/CRISP/packages/crisp-sdk/src/vote.ts @@ -339,14 +339,10 @@ export const encodeSolidityProof = ({ publicInputs, proof, encryptedVote }: Proo const slotAddress = getAddress(numberToHex(BigInt(publicInputs[2]), { size: 20 })) const encryptedVoteCommitment = publicInputs[5] as `0x${string}` - // Verification key hash (Poseidon) at index 7. Must match the value stored on-chain. - const keyHash = numberToHex(BigInt(publicInputs[7]), { size: 32 }) as `0x${string}` - - return encodeAbiParameters(parseAbiParameters('bytes, address, bytes32, bytes32, bytes'), [ + return encodeAbiParameters(parseAbiParameters('bytes, address, bytes32, bytes'), [ bytesToHex(proof), slotAddress, encryptedVoteCommitment, - keyHash, bytesToHex(encryptedVote), ]) } diff --git a/examples/CRISP/scripts/compile_circuits.sh b/examples/CRISP/scripts/compile_circuits.sh index a164fa10af..edf54554ea 100755 --- a/examples/CRISP/scripts/compile_circuits.sh +++ b/examples/CRISP/scripts/compile_circuits.sh @@ -32,6 +32,30 @@ if ! (cd "$CRISP_CIRCUITS/bin/crisp" && nargo compile); then exit 1 fi +# Inner recursive proofs use noir-recursive-no-zk; fold's compute_vk_hash chain reads these vk_hash blobs. +THRESHOLD_TARGET="${ENCLAVE_CIRCUITS}/bin/threshold/target" +RECURSIVE_VK_BASE="${THRESHOLD_TARGET}/recursive_vk" +echo "Writing noir-recursive-no-zk VKs (user_data_encryption + crisp stack)..." +for name in user_data_encryption_ct0 user_data_encryption_ct1 user_data_encryption; do + if ! mkdir -p "${RECURSIVE_VK_BASE}/${name}"; then + echo "Error: failed to create ${RECURSIVE_VK_BASE}/${name}" + exit 1 + fi + if ! bb write_vk -b "${THRESHOLD_TARGET}/${name}.json" -o "${RECURSIVE_VK_BASE}/${name}" -t noir-recursive-no-zk; then + echo "Error: bb write_vk (noir-recursive-no-zk) failed for ${name}" + exit 1 + fi +done +CRISP_RECURSIVE_VK="${CRISP_CIRCUITS}/bin/crisp/target/recursive_vk/crisp" +if ! mkdir -p "${CRISP_RECURSIVE_VK}"; then + echo "Error: failed to create ${CRISP_RECURSIVE_VK}" + exit 1 +fi +if ! bb write_vk -b "${CRISP_CIRCUITS}/bin/crisp/target/crisp.json" -o "${CRISP_RECURSIVE_VK}" -t noir-recursive-no-zk; then + echo "Error: bb write_vk (noir-recursive-no-zk) failed for crisp" + exit 1 +fi + echo "Compiling fold circuit (verifies user_data_encryption + crisp)..." if ! (cd "$CRISP_CIRCUITS/bin/fold" && nargo compile); then echo "Error: Fold circuit compilation failed" diff --git a/examples/CRISP/scripts/compute_vk_hash.sh b/examples/CRISP/scripts/compute_vk_hash.sh new file mode 100755 index 0000000000..d35e3980df --- /dev/null +++ b/examples/CRISP/scripts/compute_vk_hash.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# CRISP fold public key_hash = compute_vk_hash(ude, crisp, ct0, ct1). Needs pnpm compile:circuits. +set -euo pipefail + +CRISP="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +REPO="$(cd "$CRISP/../.." && pwd)" +R="$REPO/circuits/bin/threshold/target/recursive_vk" +VK=( + "$R/user_data_encryption/vk_hash" + "$CRISP/circuits/bin/crisp/target/recursive_vk/crisp/vk_hash" + "$R/user_data_encryption_ct0/vk_hash" + "$R/user_data_encryption_ct1/vk_hash" +) +for f in "${VK[@]}"; do + [[ -f "$f" ]] || { echo "missing $f (run pnpm compile:circuits in examples/CRISP)" >&2; exit 1; } +done +(cd "$REPO" && cargo run -q -p e3-zk-helpers --bin compute-vk-hash -- "${VK[@]}") From 296ee1957cf2a5aeb823bd87a5396e55e8d90589 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 23 Mar 2026 21:18:16 +0100 Subject: [PATCH 2/8] refactor: check final vk_hash in share-computation --- .../bin/dkg/share_computation/src/main.nr | 9 +- circuits/lib/src/configs/insecure/dkg.nr | 12 +++ circuits/lib/src/configs/secure/dkg.nr | 15 ++++ crates/zk-helpers/src/bin/zk_cli.rs | 8 ++ .../circuits/dkg/share_computation/codegen.rs | 89 +++++++++++++++++-- .../circuits/dkg/share_computation/utils.rs | 69 +++++++++++++- 6 files changed, 195 insertions(+), 7 deletions(-) diff --git a/circuits/bin/dkg/share_computation/src/main.nr b/circuits/bin/dkg/share_computation/src/main.nr index 57038aebb9..8a1cc20ef1 100644 --- a/circuits/bin/dkg/share_computation/src/main.nr +++ b/circuits/bin/dkg/share_computation/src/main.nr @@ -6,7 +6,10 @@ // Level 2: final_wrapper use bb_proof_verification::{UltraHonkProof, UltraHonkVerificationKey, verify_honk_proof_non_zk}; -use lib::configs::default::dkg::SHARE_COMPUTATION_N_BATCHES as N_BATCHES; +use lib::configs::default::dkg::{ + SHARE_COMPUTATION_EXPECTED_VK_HASH_ESM, SHARE_COMPUTATION_EXPECTED_VK_HASH_SK, + SHARE_COMPUTATION_N_BATCHES as N_BATCHES, +}; use lib::math::commitments::{compute_recursive_aggregation_commitment, compute_vk_hash}; // Public inputs of each batch wrapper proof (as exposed by `share_computation_chunk_batch`). @@ -61,6 +64,10 @@ fn main( vk_hashes.push(batch_public_inputs[0][1]); // chunk_key_hash (same across all batches) vk_hashes.push(batch_key_hash); // VK hash of the batch circuit that produced these proofs let key_hash = compute_vk_hash(vk_hashes); + assert( + (key_hash == SHARE_COMPUTATION_EXPECTED_VK_HASH_SK) + | (key_hash == SHARE_COMPUTATION_EXPECTED_VK_HASH_ESM), + ); (key_hash, final_commitment) } diff --git a/circuits/lib/src/configs/insecure/dkg.nr b/circuits/lib/src/configs/insecure/dkg.nr index 101d10a886..977dacb3fe 100644 --- a/circuits/lib/src/configs/insecure/dkg.nr +++ b/circuits/lib/src/configs/insecure/dkg.nr @@ -64,6 +64,18 @@ pub global SHARE_COMPUTATION_N_BATCHES: u32 = pub global SHARE_COMPUTATION_CHUNK_CONFIGS: ShareComputationChunkConfigs = ShareComputationChunkConfigs::new(QIS_THRESHOLD); +/************************************ +------------------------------------- +share_computation final (C2) - combined VK hash (noir-recursive-no-zk) +Regenerate: zk_cli share_computation codegen with ENCLAVE_CIRCUITS_ROOT + recursive_vk artifacts. +------------------------------------- +************************************/ + +pub global SHARE_COMPUTATION_EXPECTED_VK_HASH_SK: Field = + 0x166f69fac410d14aef0357cc8544aaa9e4466d50e2629c14c3e666b3a08fb25f; +pub global SHARE_COMPUTATION_EXPECTED_VK_HASH_ESM: Field = + 0x12d9d8102a32f8750061596b14144ac6209e039421e684e595082c92e31ceb71; + /************************************ ------------------------------------- share_encryption_sk (CIRCUIT 3a) diff --git a/circuits/lib/src/configs/secure/dkg.nr b/circuits/lib/src/configs/secure/dkg.nr index bf58ebfbcb..d03ab127c8 100644 --- a/circuits/lib/src/configs/secure/dkg.nr +++ b/circuits/lib/src/configs/secure/dkg.nr @@ -67,6 +67,21 @@ pub global SHARE_COMPUTATION_N_BATCHES: u32 = pub global SHARE_COMPUTATION_CHUNK_CONFIGS: ShareComputationChunkConfigs = ShareComputationChunkConfigs::new(QIS_THRESHOLD); +/************************************ +------------------------------------- +share_computation final (C2) - combined VK hash (noir-recursive-no-zk) +Regenerate for secure preset after compiling lib with configs::secure::dkg as default. +------------------------------------- +************************************/ + +// Placeholder: set lib default to secure::dkg, run nargo compile in circuits/bin/dkg, bb write_vk +// into target/recursive_vk/* (noir-recursive-no-zk), then compute-vk-hash (base,chunk,batch) per SK/ESM. +// TODO: update with actual hashes once we use secure parameters. +pub global SHARE_COMPUTATION_EXPECTED_VK_HASH_SK: Field = + 0x0000000000000000000000000000000000000000000000000000000000000001; +pub global SHARE_COMPUTATION_EXPECTED_VK_HASH_ESM: Field = + 0x0000000000000000000000000000000000000000000000000000000000000001; + /************************************ ------------------------------------- share_encryption_sk (CIRCUIT 3a) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 2dbeab5f68..a459472ca1 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -9,6 +9,14 @@ //! This binary lists available circuits and generates Prover.toml and configs.nr //! for use with the Noir prover. Use `--list_circuits` to see circuits and //! `--circuit --preset insecure|secure|2|80` to generate artifacts. +//! +//! **Share-computation (C2) configs.nr:** set `ENCLAVE_CIRCUITS_ROOT` to the repo `circuits` +//! directory (or run from the Enclave repo so it is auto-discovered). After `nargo compile` +//! in `circuits/bin/dkg`, run `bb write_vk -t noir-recursive-no-zk` into +//! `circuits/bin/dkg/target/recursive_vk/{sk_share_computation_base,e_sm_share_computation_base,share_computation_chunk,share_computation_chunk_batch}/` +//! (see `scripts/dkg_recursive_vk.sh`). If `ENCLAVE_CIRCUITS_ROOT` is set and those `vk_hash` +//! files are missing, codegen fails; if unset and artifacts are absent, the C2 literals are omitted +//! from the generated fragment. use anyhow::{anyhow, Context, Result}; use clap::{arg, command, Parser}; diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs index 5605e4589d..7ee26ba928 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -8,14 +8,19 @@ use crate::circuits::computation::{CircuitComputation, Computation}; use crate::circuits::dkg::share_computation::{ - utils::parity_matrix_constant_string, Bits, Bounds, ChunkInputs, Configs, Inputs, - ShareComputationBaseCircuit, ShareComputationChunkCircuit, ShareComputationChunkCircuitData, - ShareComputationCircuit, ShareComputationCircuitData, ShareComputationOutput, + utils::{ + parity_matrix_constant_string, resolve_enclave_circuits_root, + share_computation_expected_vk_hash_hex_literals, + }, + Bits, Bounds, ChunkInputs, Configs, Inputs, ShareComputationBaseCircuit, + ShareComputationChunkCircuit, ShareComputationChunkCircuitData, ShareComputationCircuit, + ShareComputationCircuitData, ShareComputationOutput, }; use crate::circuits::{Artifacts, CircuitCodegen, CircuitsErrors, CodegenToml}; use crate::codegen::CodegenConfigs; use crate::registry::Circuit; use e3_fhe_params::{build_pair_for_preset, BfvPreset}; +use std::env; /// Implementation of [`CircuitCodegen`] for the shared share-computation input builder. impl CircuitCodegen for ShareComputationCircuit { @@ -103,6 +108,19 @@ pub fn generate_chunk_toml(witness: &ChunkInputs) -> Result/ +for sk_share_computation_base, e_sm_share_computation_base, share_computation_chunk, +share_computation_chunk_batch (after nargo compile in bin/dkg), then re-run zk_cli codegen. +------------------------------------- +************************************/ +"#; + /// Builds the configs.nr string used by the split base/chunk share-computation circuits. pub fn generate_configs( preset: BfvPreset, @@ -119,6 +137,44 @@ pub fn generate_configs( let parity_matrix_str = parity_matrix_constant_string(&threshold_params, n_parties, threshold)?; let prefix = ::PREFIX; + let explicit_circuits_root = env::var("ENCLAVE_CIRCUITS_ROOT").is_ok(); + let (vk_sk, vk_esm) = match resolve_enclave_circuits_root() { + Some(root) => { + let rv = root.join("bin/dkg/target/recursive_vk"); + match share_computation_expected_vk_hash_hex_literals(&rv) { + Ok(pair) => pair, + Err(e) if explicit_circuits_root => { + return Err(CircuitsErrors::Sample(format!( + "C2 share_computation VK literals: {e}" + ))); + } + Err(_) => (String::new(), String::new()), + } + } + None if explicit_circuits_root => { + return Err(CircuitsErrors::Sample( + "ENCLAVE_CIRCUITS_ROOT is set but does not point to a tree with bin/dkg/target" + .into(), + )); + } + None => (String::new(), String::new()), + }; + + let vk_block = if vk_sk.is_empty() { + String::new() + } else { + let mut s = String::with_capacity( + SHARE_COMPUTATION_VK_HASH_NR_HEADER.len() + vk_sk.len() + vk_esm.len() + 96, + ); + s.push_str(SHARE_COMPUTATION_VK_HASH_NR_HEADER); + s.push_str("pub global SHARE_COMPUTATION_EXPECTED_VK_HASH_SK: Field = "); + s.push_str(&vk_sk); + s.push_str(";\npub global SHARE_COMPUTATION_EXPECTED_VK_HASH_ESM: Field = "); + s.push_str(&vk_esm); + s.push_str(";\n"); + s + }; + Ok(format!( r#"use crate::core::dkg::share_computation::chunk::Configs as ShareComputationChunkConfigs; pub use crate::configs::{config_name}::threshold::{{L as L_THRESHOLD, QIS as QIS_THRESHOLD}}; @@ -158,8 +214,7 @@ pub global {prefix}_N_BATCHES: u32 = {prefix}_N_CHUNKS / {prefix}_CHUNKS_PER_BATCH; pub global {prefix}_CHUNK_CONFIGS: ShareComputationChunkConfigs = - ShareComputationChunkConfigs::new(QIS_THRESHOLD); -"#, + ShareComputationChunkConfigs::new(QIS_THRESHOLD);{vk_block}"#, config_name = config_name, degree = preset.metadata().degree, parity_matrix = parity_matrix_str, @@ -169,6 +224,7 @@ pub global {prefix}_CHUNK_CONFIGS: ShareComputationChunkConfigs = bit_e_sm_secret = bits.bit_e_sm_secret, chunk_size = chunk_size, chunks_per_batch = chunks_per_batch, + vk_block = vk_block, )) } @@ -182,8 +238,31 @@ mod tests { use crate::computation::DkgInputType; use crate::Circuit; use e3_fhe_params::BfvPreset; + use std::fs; use tempfile::TempDir; + #[test] + fn share_computation_expected_vk_hash_hex_literals_differ_for_sk_and_esm() { + let tmp = TempDir::new().unwrap(); + let rv = tmp.path().join("recursive_vk"); + for (name, tag) in [ + ("sk_share_computation_base", 1u8), + ("e_sm_share_computation_base", 2u8), + ("share_computation_chunk", 3u8), + ("share_computation_chunk_batch", 4u8), + ] { + let d = rv.join(name); + fs::create_dir_all(&d).unwrap(); + let mut b = [0u8; 32]; + b[31] = tag; + fs::write(d.join("vk_hash"), b).unwrap(); + } + let (sk, esm) = share_computation_expected_vk_hash_hex_literals(&rv).unwrap(); + assert!(sk.starts_with("0x") && sk.len() == 66); + assert!(esm.starts_with("0x") && esm.len() == 66); + assert_ne!(sk, esm); + } + #[test] fn test_toml_generation_and_structure() { let committee = CiphernodesCommitteeSize::Small.values(); diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs index a9f280fab8..1abb7fbd9b 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs @@ -4,13 +4,20 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Shared utilities for the share-computation circuit (e.g. parity matrix). +//! Shared utilities for the share-computation circuit: parity matrix helpers and build-time +//! helpers that combine recursive `vk_hash` blobs like the final Noir `share_computation` circuit. +use crate::compute_vk_hash; use crate::utils::bigint_to_field; use crate::CircuitsErrors; +use ark_bn254::Fr; +use ark_ff::{BigInteger, PrimeField}; use e3_parity_matrix::build_generator_matrix; use e3_parity_matrix::{null_space, ParityMatrix, ParityMatrixConfig}; use num_bigint::{BigInt, BigUint}; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; /// Computes the parity check matrix (null space of the Reed–Solomon generator) per modulus. /// @@ -71,3 +78,63 @@ pub fn parity_matrix_constant_string( parity_matrix_strings.join(",\n ") )) } + +/// Root directory that contains `bin/dkg/` (i.e. the `circuits` folder in the Enclave repo). +pub fn resolve_enclave_circuits_root() -> Option { + if let Ok(root) = env::var("ENCLAVE_CIRCUITS_ROOT") { + let p = PathBuf::from(root); + if p.join("bin/dkg/target").is_dir() { + return Some(p); + } + } + let mut dir = env::current_dir().ok()?; + for _ in 0..10 { + let cand = dir.join("circuits").join("bin").join("dkg").join("target"); + if cand.is_dir() { + return Some(dir.join("circuits")); + } + dir = dir.parent()?.to_path_buf(); + } + None +} + +fn fr_from_vk_hash_file(path: &Path) -> Result { + let bytes = fs::read(path).map_err(|e| format!("{}: {e}", path.display()))?; + if bytes.len() != 32 { + return Err(format!( + "{}: expected 32-byte vk_hash, got {}", + path.display(), + bytes.len() + )); + } + let mut arr = [0u8; 32]; + arr.copy_from_slice(&bytes); + Ok(Fr::from_be_bytes_mod_order(&arr)) +} + +fn field_to_noir_hex(fr: Fr) -> String { + let repr = fr.into_bigint().to_bytes_be(); + let mut out = [0u8; 32]; + let start = 32usize.saturating_sub(repr.len()); + out[start..].copy_from_slice(&repr); + format!("0x{}", hex::encode(out)) +} + +/// `recursive_vk_root` is `.../circuits/bin/dkg/target/recursive_vk`. +pub fn share_computation_expected_vk_hash_hex_literals( + recursive_vk_root: &Path, +) -> Result<(String, String), String> { + let sk_base = fr_from_vk_hash_file( + &recursive_vk_root.join("sk_share_computation_base/vk_hash"), + )?; + let esm_base = fr_from_vk_hash_file( + &recursive_vk_root.join("e_sm_share_computation_base/vk_hash"), + )?; + let chunk = fr_from_vk_hash_file(&recursive_vk_root.join("share_computation_chunk/vk_hash"))?; + let batch = fr_from_vk_hash_file( + &recursive_vk_root.join("share_computation_chunk_batch/vk_hash"), + )?; + let sk_chain = compute_vk_hash(vec![sk_base, chunk, batch]); + let esm_chain = compute_vk_hash(vec![esm_base, chunk, batch]); + Ok((field_to_noir_hex(sk_chain), field_to_noir_hex(esm_chain))) +} From 50867fc84da9fd8c31ba7f54422c6b002e6fa47c Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 23 Mar 2026 21:18:54 +0100 Subject: [PATCH 3/8] style: format code with rustfmt --- .../src/circuits/dkg/share_computation/utils.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs index 1abb7fbd9b..e5aecc781c 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs @@ -124,16 +124,13 @@ fn field_to_noir_hex(fr: Fr) -> String { pub fn share_computation_expected_vk_hash_hex_literals( recursive_vk_root: &Path, ) -> Result<(String, String), String> { - let sk_base = fr_from_vk_hash_file( - &recursive_vk_root.join("sk_share_computation_base/vk_hash"), - )?; - let esm_base = fr_from_vk_hash_file( - &recursive_vk_root.join("e_sm_share_computation_base/vk_hash"), - )?; + let sk_base = + fr_from_vk_hash_file(&recursive_vk_root.join("sk_share_computation_base/vk_hash"))?; + let esm_base = + fr_from_vk_hash_file(&recursive_vk_root.join("e_sm_share_computation_base/vk_hash"))?; let chunk = fr_from_vk_hash_file(&recursive_vk_root.join("share_computation_chunk/vk_hash"))?; - let batch = fr_from_vk_hash_file( - &recursive_vk_root.join("share_computation_chunk_batch/vk_hash"), - )?; + let batch = + fr_from_vk_hash_file(&recursive_vk_root.join("share_computation_chunk_batch/vk_hash"))?; let sk_chain = compute_vk_hash(vec![sk_base, chunk, batch]); let esm_chain = compute_vk_hash(vec![esm_base, chunk, batch]); Ok((field_to_noir_hex(sk_chain), field_to_noir_hex(esm_chain))) From c0e1142c64f8750d178db768421603ca4ee7f91b Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 25 Mar 2026 12:40:49 +0100 Subject: [PATCH 4/8] chore: update paths in configs and zk-helpers --- circuits/lib/src/configs/insecure/dkg.nr | 2 +- circuits/lib/src/configs/secure/dkg.nr | 4 +-- crates/zk-helpers/src/bin/compute_vk_hash.rs | 17 +++++++++-- crates/zk-helpers/src/bin/zk_cli.rs | 12 ++++---- .../circuits/dkg/share_computation/codegen.rs | 29 +++++++++---------- .../circuits/dkg/share_computation/utils.rs | 23 +++++++++------ 6 files changed, 51 insertions(+), 36 deletions(-) diff --git a/circuits/lib/src/configs/insecure/dkg.nr b/circuits/lib/src/configs/insecure/dkg.nr index 977dacb3fe..dea183b776 100644 --- a/circuits/lib/src/configs/insecure/dkg.nr +++ b/circuits/lib/src/configs/insecure/dkg.nr @@ -67,7 +67,7 @@ pub global SHARE_COMPUTATION_CHUNK_CONFIGS: ShareComputationChunkConfigs Result { if bytes.len() != 32 { bail!("{}: expected 32 bytes, got {}", path.display(), bytes.len()); } - let mut arr = [0u8; 32]; - arr.copy_from_slice(&bytes); - Ok(Fr::from_be_bytes_mod_order(&arr)) + let n = BigUint::from_bytes_be(&bytes); + let bigint = ::BigInt::try_from(n).map_err(|_| { + anyhow::anyhow!( + "{}: vk_hash integer does not fit the field's BigInt representation", + path.display() + ) + })?; + Fr::from_bigint(bigint).ok_or_else(|| { + anyhow::anyhow!( + "{}: vk_hash is not in the canonical range [0, p)", + path.display() + ) + }) } fn field_to_padded_be_hex(fr: Fr) -> String { diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index a459472ca1..292990b276 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -11,12 +11,12 @@ //! `--circuit --preset insecure|secure|2|80` to generate artifacts. //! //! **Share-computation (C2) configs.nr:** set `ENCLAVE_CIRCUITS_ROOT` to the repo `circuits` -//! directory (or run from the Enclave repo so it is auto-discovered). After `nargo compile` -//! in `circuits/bin/dkg`, run `bb write_vk -t noir-recursive-no-zk` into -//! `circuits/bin/dkg/target/recursive_vk/{sk_share_computation_base,e_sm_share_computation_base,share_computation_chunk,share_computation_chunk_batch}/` -//! (see `scripts/dkg_recursive_vk.sh`). If `ENCLAVE_CIRCUITS_ROOT` is set and those `vk_hash` -//! files are missing, codegen fails; if unset and artifacts are absent, the C2 literals are omitted -//! from the generated fragment. +//! directory (or run from the Enclave repo so it is auto-discovered). After `pnpm build:circuits`, +//! `circuits/bin/dkg/target/` contains `sk_share_computation_base.vk_recursive_hash`, +//! `e_sm_share_computation_base.vk_recursive_hash`, `share_computation_chunk.vk_recursive_hash`, +//! and `share_computation_chunk_batch.vk_recursive_hash` (from `scripts/build-circuits.ts`). If +//! `ENCLAVE_CIRCUITS_ROOT` is set and those files are missing, codegen fails; if unset and artifacts +//! are absent, the C2 literals are omitted from the generated fragment. use anyhow::{anyhow, Context, Result}; use clap::{arg, command, Parser}; diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs index 7ee26ba928..789b0a6c0b 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -114,9 +114,9 @@ const SHARE_COMPUTATION_VK_HASH_NR_HEADER: &str = r#" ------------------------------------- share_computation final (C2) - combined VK hash (noir-recursive-no-zk) Matches circuits/bin/dkg/share_computation: compute_vk_hash(base, chunk, batch). -Regenerate: bb write_vk -t noir-recursive-no-zk into bin/dkg/target/recursive_vk// -for sk_share_computation_base, e_sm_share_computation_base, share_computation_chunk, -share_computation_chunk_batch (after nargo compile in bin/dkg), then re-run zk_cli codegen. +Regenerate: pnpm build:circuits (writes bin/dkg/target/*.vk_recursive_hash), or run bb write_vk +-t noir-recursive-no-zk per crate into bin/dkg/target and rename to {package}.vk_recursive_hash; +then re-run zk_cli codegen. ------------------------------------- ************************************/ "#; @@ -140,8 +140,8 @@ pub fn generate_configs( let explicit_circuits_root = env::var("ENCLAVE_CIRCUITS_ROOT").is_ok(); let (vk_sk, vk_esm) = match resolve_enclave_circuits_root() { Some(root) => { - let rv = root.join("bin/dkg/target/recursive_vk"); - match share_computation_expected_vk_hash_hex_literals(&rv) { + let dkg_target = root.join("bin/dkg/target"); + match share_computation_expected_vk_hash_hex_literals(&dkg_target) { Ok(pair) => pair, Err(e) if explicit_circuits_root => { return Err(CircuitsErrors::Sample(format!( @@ -244,20 +244,19 @@ mod tests { #[test] fn share_computation_expected_vk_hash_hex_literals_differ_for_sk_and_esm() { let tmp = TempDir::new().unwrap(); - let rv = tmp.path().join("recursive_vk"); - for (name, tag) in [ - ("sk_share_computation_base", 1u8), - ("e_sm_share_computation_base", 2u8), - ("share_computation_chunk", 3u8), - ("share_computation_chunk_batch", 4u8), + let dkg_target = tmp.path().join("target"); + fs::create_dir_all(&dkg_target).unwrap(); + for (filename, tag) in [ + ("sk_share_computation_base.vk_recursive_hash", 1u8), + ("e_sm_share_computation_base.vk_recursive_hash", 2u8), + ("share_computation_chunk.vk_recursive_hash", 3u8), + ("share_computation_chunk_batch.vk_recursive_hash", 4u8), ] { - let d = rv.join(name); - fs::create_dir_all(&d).unwrap(); let mut b = [0u8; 32]; b[31] = tag; - fs::write(d.join("vk_hash"), b).unwrap(); + fs::write(dkg_target.join(filename), b).unwrap(); } - let (sk, esm) = share_computation_expected_vk_hash_hex_literals(&rv).unwrap(); + let (sk, esm) = share_computation_expected_vk_hash_hex_literals(&dkg_target).unwrap(); assert!(sk.starts_with("0x") && sk.len() == 66); assert!(esm.starts_with("0x") && esm.len() == 66); assert_ne!(sk, esm); diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs index e5aecc781c..895afa560e 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs @@ -120,17 +120,22 @@ fn field_to_noir_hex(fr: Fr) -> String { format!("0x{}", hex::encode(out)) } -/// `recursive_vk_root` is `.../circuits/bin/dkg/target/recursive_vk`. +/// `dkg_target` is `.../circuits/bin/dkg/target` (Nargo target dir). After `pnpm build:circuits` / +/// `build-circuits.ts`, noir-recursive-no-zk hashes are named `{package}.vk_recursive_hash`. pub fn share_computation_expected_vk_hash_hex_literals( - recursive_vk_root: &Path, + dkg_target: &Path, ) -> Result<(String, String), String> { - let sk_base = - fr_from_vk_hash_file(&recursive_vk_root.join("sk_share_computation_base/vk_hash"))?; - let esm_base = - fr_from_vk_hash_file(&recursive_vk_root.join("e_sm_share_computation_base/vk_hash"))?; - let chunk = fr_from_vk_hash_file(&recursive_vk_root.join("share_computation_chunk/vk_hash"))?; - let batch = - fr_from_vk_hash_file(&recursive_vk_root.join("share_computation_chunk_batch/vk_hash"))?; + let sk_base = fr_from_vk_hash_file( + &dkg_target.join("sk_share_computation_base.vk_recursive_hash"), + )?; + let esm_base = fr_from_vk_hash_file( + &dkg_target.join("e_sm_share_computation_base.vk_recursive_hash"), + )?; + let chunk = + fr_from_vk_hash_file(&dkg_target.join("share_computation_chunk.vk_recursive_hash"))?; + let batch = fr_from_vk_hash_file( + &dkg_target.join("share_computation_chunk_batch.vk_recursive_hash"), + )?; let sk_chain = compute_vk_hash(vec![sk_base, chunk, batch]); let esm_chain = compute_vk_hash(vec![esm_base, chunk, batch]); Ok((field_to_noir_hex(sk_chain), field_to_noir_hex(esm_chain))) From c4aebe0e0b79ea2cb7e4e8308be2022c1885e45f Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 25 Mar 2026 12:48:04 +0100 Subject: [PATCH 5/8] chore: update paths in CRISP scripts --- examples/CRISP/scripts/compile_circuits.sh | 22 +++++++++------------- examples/CRISP/scripts/compute_vk_hash.sh | 10 +++++----- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/examples/CRISP/scripts/compile_circuits.sh b/examples/CRISP/scripts/compile_circuits.sh index edf54554ea..10a020fa6e 100755 --- a/examples/CRISP/scripts/compile_circuits.sh +++ b/examples/CRISP/scripts/compile_circuits.sh @@ -32,29 +32,25 @@ if ! (cd "$CRISP_CIRCUITS/bin/crisp" && nargo compile); then exit 1 fi -# Inner recursive proofs use noir-recursive-no-zk; fold's compute_vk_hash chain reads these vk_hash blobs. +# Inner recursive proofs use noir-recursive-no-zk; fold's compute_vk_hash chain reads +# `{name}.vk_recursive_hash` in each package target/ (same layout as enclave `scripts/build-circuits.ts`). THRESHOLD_TARGET="${ENCLAVE_CIRCUITS}/bin/threshold/target" -RECURSIVE_VK_BASE="${THRESHOLD_TARGET}/recursive_vk" +CRISP_TARGET="${CRISP_CIRCUITS}/bin/crisp/target" echo "Writing noir-recursive-no-zk VKs (user_data_encryption + crisp stack)..." for name in user_data_encryption_ct0 user_data_encryption_ct1 user_data_encryption; do - if ! mkdir -p "${RECURSIVE_VK_BASE}/${name}"; then - echo "Error: failed to create ${RECURSIVE_VK_BASE}/${name}" - exit 1 - fi - if ! bb write_vk -b "${THRESHOLD_TARGET}/${name}.json" -o "${RECURSIVE_VK_BASE}/${name}" -t noir-recursive-no-zk; then + if ! bb write_vk -b "${THRESHOLD_TARGET}/${name}.json" -o "${THRESHOLD_TARGET}" -t noir-recursive-no-zk; then echo "Error: bb write_vk (noir-recursive-no-zk) failed for ${name}" exit 1 fi + mv "${THRESHOLD_TARGET}/vk" "${THRESHOLD_TARGET}/${name}.vk_recursive" + mv "${THRESHOLD_TARGET}/vk_hash" "${THRESHOLD_TARGET}/${name}.vk_recursive_hash" done -CRISP_RECURSIVE_VK="${CRISP_CIRCUITS}/bin/crisp/target/recursive_vk/crisp" -if ! mkdir -p "${CRISP_RECURSIVE_VK}"; then - echo "Error: failed to create ${CRISP_RECURSIVE_VK}" - exit 1 -fi -if ! bb write_vk -b "${CRISP_CIRCUITS}/bin/crisp/target/crisp.json" -o "${CRISP_RECURSIVE_VK}" -t noir-recursive-no-zk; then +if ! bb write_vk -b "${CRISP_TARGET}/crisp.json" -o "${CRISP_TARGET}" -t noir-recursive-no-zk; then echo "Error: bb write_vk (noir-recursive-no-zk) failed for crisp" exit 1 fi +mv "${CRISP_TARGET}/vk" "${CRISP_TARGET}/crisp.vk_recursive" +mv "${CRISP_TARGET}/vk_hash" "${CRISP_TARGET}/crisp.vk_recursive_hash" echo "Compiling fold circuit (verifies user_data_encryption + crisp)..." if ! (cd "$CRISP_CIRCUITS/bin/fold" && nargo compile); then diff --git a/examples/CRISP/scripts/compute_vk_hash.sh b/examples/CRISP/scripts/compute_vk_hash.sh index d35e3980df..aceef96d0e 100755 --- a/examples/CRISP/scripts/compute_vk_hash.sh +++ b/examples/CRISP/scripts/compute_vk_hash.sh @@ -4,12 +4,12 @@ set -euo pipefail CRISP="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" REPO="$(cd "$CRISP/../.." && pwd)" -R="$REPO/circuits/bin/threshold/target/recursive_vk" +T="$REPO/circuits/bin/threshold/target" VK=( - "$R/user_data_encryption/vk_hash" - "$CRISP/circuits/bin/crisp/target/recursive_vk/crisp/vk_hash" - "$R/user_data_encryption_ct0/vk_hash" - "$R/user_data_encryption_ct1/vk_hash" + "$T/user_data_encryption.vk_recursive_hash" + "$CRISP/circuits/bin/crisp/target/crisp.vk_recursive_hash" + "$T/user_data_encryption_ct0.vk_recursive_hash" + "$T/user_data_encryption_ct1.vk_recursive_hash" ) for f in "${VK[@]}"; do [[ -f "$f" ]] || { echo "missing $f (run pnpm compile:circuits in examples/CRISP)" >&2; exit 1; } From 41379bf29defd24f780faaca9c561222753d0c03 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 25 Mar 2026 12:48:37 +0100 Subject: [PATCH 6/8] style: format code with rustfmt --- .../src/circuits/dkg/share_computation/utils.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs index 895afa560e..d7258f0ddd 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs @@ -125,17 +125,14 @@ fn field_to_noir_hex(fr: Fr) -> String { pub fn share_computation_expected_vk_hash_hex_literals( dkg_target: &Path, ) -> Result<(String, String), String> { - let sk_base = fr_from_vk_hash_file( - &dkg_target.join("sk_share_computation_base.vk_recursive_hash"), - )?; - let esm_base = fr_from_vk_hash_file( - &dkg_target.join("e_sm_share_computation_base.vk_recursive_hash"), - )?; + let sk_base = + fr_from_vk_hash_file(&dkg_target.join("sk_share_computation_base.vk_recursive_hash"))?; + let esm_base = + fr_from_vk_hash_file(&dkg_target.join("e_sm_share_computation_base.vk_recursive_hash"))?; let chunk = fr_from_vk_hash_file(&dkg_target.join("share_computation_chunk.vk_recursive_hash"))?; - let batch = fr_from_vk_hash_file( - &dkg_target.join("share_computation_chunk_batch.vk_recursive_hash"), - )?; + let batch = + fr_from_vk_hash_file(&dkg_target.join("share_computation_chunk_batch.vk_recursive_hash"))?; let sk_chain = compute_vk_hash(vec![sk_base, chunk, batch]); let esm_chain = compute_vk_hash(vec![esm_base, chunk, batch]); Ok((field_to_noir_hex(sk_chain), field_to_noir_hex(esm_chain))) From 31a17c05abbc52df9dc74aca3825d9e372b3ad1e Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 25 Mar 2026 12:54:34 +0100 Subject: [PATCH 7/8] chore: update template Cargo.lock --- templates/default/Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/default/Cargo.lock b/templates/default/Cargo.lock index d626e7997e..5e1c9f9883 100644 --- a/templates/default/Cargo.lock +++ b/templates/default/Cargo.lock @@ -1356,6 +1356,7 @@ dependencies = [ "fhe", "fhe-math", "fhe-traits", + "hex", "itertools 0.14.0", "ndarray", "num-bigint", From bede6b6a0617f68fd776ba0a4454119f6883c7ce Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 25 Mar 2026 13:21:42 +0100 Subject: [PATCH 8/8] ci: update Dockerfile --- crates/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/Dockerfile b/crates/Dockerfile index 54f81f8187..5c9f19b8b1 100644 --- a/crates/Dockerfile +++ b/crates/Dockerfile @@ -100,7 +100,9 @@ RUN for d in ./*/ ; do \ done # Crates with [[bin]] in Cargo.toml need stub binaries so the first build succeeds -RUN mkdir -p ./zk-helpers/src/bin && echo 'fn main() {}' > ./zk-helpers/src/bin/zk_cli.rs +RUN mkdir -p ./zk-helpers/src/bin && \ + echo 'fn main() {}' > ./zk-helpers/src/bin/zk_cli.rs && \ + echo 'fn main() {}' > ./zk-helpers/src/bin/compute_vk_hash.rs RUN mkdir -p ./fhe-params/src/bin && echo 'fn main() {}' > ./fhe-params/src/bin/search_params.rs RUN cargo build --locked --release