Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion circuits/bin/dkg/share_computation/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down Expand Up @@ -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)
}
12 changes: 12 additions & 0 deletions circuits/lib/src/configs/insecure/dkg.nr
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ pub global SHARE_COMPUTATION_N_BATCHES: u32 =
pub global SHARE_COMPUTATION_CHUNK_CONFIGS: ShareComputationChunkConfigs<L_THRESHOLD> =
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 + bin/dkg/target/\*.vk_recursive_hash.
-------------------------------------
************************************/

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)
Expand Down
15 changes: 15 additions & 0 deletions circuits/lib/src/configs/secure/dkg.nr
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ pub global SHARE_COMPUTATION_N_BATCHES: u32 =
pub global SHARE_COMPUTATION_CHUNK_CONFIGS: ShareComputationChunkConfigs<L_THRESHOLD> =
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 pnpm build:circuits (or bb write_vk per crate into
// circuits/bin/dkg/target as *.vk_recursive_hash), 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)
Expand Down
4 changes: 3 additions & 1 deletion crates/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion crates/zk-helpers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -35,4 +36,8 @@ tempfile = { workspace = true }

[[bin]]
name = "zk_cli"
path = "src/bin/zk_cli.rs"
path = "src/bin/zk_cli.rs"

[[bin]]
name = "compute-vk-hash"
path = "src/bin/compute_vk_hash.rs"
68 changes: 68 additions & 0 deletions crates/zk-helpers/src/bin/compute_vk_hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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 num_bigint::BigUint;
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 <dir>` (use one dir per circuit).
#[arg(required = true)]
vk_hash_files: Vec<PathBuf>,
}

fn field_from_vk_hash_file(path: &std::path::Path) -> Result<Fr> {
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 n = BigUint::from_bytes_be(&bytes);
let bigint = <Fr as PrimeField>::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 {
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(())
}
8 changes: 8 additions & 0 deletions crates/zk-helpers/src/bin/zk_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name> --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 `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};
Expand Down
31 changes: 31 additions & 0 deletions crates/zk-helpers/src/circuits/commitments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>) -> 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
// ============================================================================
Expand Down Expand Up @@ -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);
}
}
88 changes: 83 additions & 5 deletions crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -103,6 +108,19 @@ pub fn generate_chunk_toml(witness: &ChunkInputs) -> Result<CodegenToml, Circuit
Ok(toml::to_string(&json)?)
}

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: 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.
-------------------------------------
************************************/
"#;

/// Builds the configs.nr string used by the split base/chunk share-computation circuits.
pub fn generate_configs(
preset: BfvPreset,
Expand All @@ -119,6 +137,44 @@ pub fn generate_configs(
let parity_matrix_str = parity_matrix_constant_string(&threshold_params, n_parties, threshold)?;
let prefix = <ShareComputationCircuit as Circuit>::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 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!(
"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}};
Expand Down Expand Up @@ -158,8 +214,7 @@ pub global {prefix}_N_BATCHES: u32 =
{prefix}_N_CHUNKS / {prefix}_CHUNKS_PER_BATCH;

pub global {prefix}_CHUNK_CONFIGS: ShareComputationChunkConfigs<L_THRESHOLD> =
ShareComputationChunkConfigs::new(QIS_THRESHOLD);
"#,
ShareComputationChunkConfigs::new(QIS_THRESHOLD);{vk_block}"#,
config_name = config_name,
degree = preset.metadata().degree,
parity_matrix = parity_matrix_str,
Expand All @@ -169,6 +224,7 @@ pub global {prefix}_CHUNK_CONFIGS: ShareComputationChunkConfigs<L_THRESHOLD> =
bit_e_sm_secret = bits.bit_e_sm_secret,
chunk_size = chunk_size,
chunks_per_batch = chunks_per_batch,
vk_block = vk_block,
))
}

Expand All @@ -182,8 +238,30 @@ 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 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 mut b = [0u8; 32];
b[31] = tag;
fs::write(dkg_target.join(filename), b).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);
}

#[test]
fn test_toml_generation_and_structure() {
let committee = CiphernodesCommitteeSize::Small.values();
Expand Down
Loading
Loading