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
2 changes: 1 addition & 1 deletion crates/events/src/enclave_event/compute_request/zk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ pub struct ShareEncryptionProofRequest {
pub struct DkgShareDecryptionProofRequest {
/// BFV secret key used for decryption (witness — encrypted at rest).
pub sk_bfv: SensitiveBytes,
/// Serialized BFV Ciphertext bytes from H honest parties, flattened as [H * L].
/// BFV ciphertexts from H honest parties, flattened [H * L] in ascending party_id order.
/// Layout: party 0 mod 0, party 0 mod 1, ..., party 1 mod 0, ...
pub honest_ciphertexts_raw: Vec<ArcBytes>,
/// Number of honest parties (H).
Expand Down
15 changes: 13 additions & 2 deletions crates/keyshare/src/threshold_keyshare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub struct GenEsiSss {
#[derive(Message)]
#[rtype(result = "()")]
pub struct AllThresholdSharesCollected {
/// Threshold shares sorted by ascending `party_id`.
shares: Vec<Arc<ThresholdShare>>,
/// Proofs from each sender, ordered by party_id (parallel to shares).
share_proofs: Vec<ReceivedShareProofs>,
Expand Down Expand Up @@ -258,7 +259,9 @@ pub struct ThresholdKeyshareState {
/// Aggregated public key bytes, captured from PublicKeyAggregated event for C6 proof.
pub aggregated_pk: Option<ArcBytes>,
pub expelled_parties: HashSet<u64>,
pub honest_parties: Option<HashSet<u64>>,
/// Honest party IDs in deterministic ascending order (`BTreeSet` guarantees this).
/// Downstream proof circuits index parties by position in this sorted set.
pub honest_parties: Option<BTreeSet<u64>>,
#[serde(default = "default_proof_agg")]
pub proof_aggregation_enabled: bool,
}
Expand Down Expand Up @@ -1648,7 +1651,15 @@ impl ThresholdKeyshare {
}

// Store honest party IDs in state (after dimension exclusion)
let honest_party_ids: HashSet<u64> = honest_shares.iter().map(|s| s.party_id).collect();
let honest_party_ids: BTreeSet<u64> = honest_shares.iter().map(|s| s.party_id).collect();

// honest_shares inherits sorted order from AllThresholdSharesCollected.
debug_assert!(
honest_shares
.windows(2)
.all(|w| w[0].party_id < w[1].party_id),
"BUG: honest_shares must be in strictly ascending party_id order"
);

let num_honest = honest_shares.len();
info!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct ShareDecryptionCircuitData {
/// DKG secret key used to decrypt (private input).
pub secret_key: SecretKey,
/// Ciphertexts from H honest parties: [party_idx][mod_idx] (one ciphertext per party per TRBFV modulus).
/// party_idx follows ascending party_id among honest parties
pub honest_ciphertexts: Vec<Vec<Ciphertext>>,
/// Which input type (SecretKey or SmudgingNoise) to resolve circuit path.
pub dkg_input_type: DkgInputType,
Expand Down
37 changes: 37 additions & 0 deletions crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,4 +286,41 @@ mod tests {
sample.honest_ciphertexts.len()
);
}

/// Verify expected_commitments[i][j] matches direct commitment computation
/// for honest_ciphertexts[i][j], proving row ordering is consistent.
#[test]
fn test_commitment_ordering_consistency() {
let committee = CiphernodesCommitteeSize::Small.values();
let preset = BfvPreset::InsecureThreshold512;
let sample =
ShareDecryptionCircuitData::generate_sample(preset, committee, DkgInputType::SecretKey)
.unwrap();

let (threshold_params, dkg_params) = build_pair_for_preset(preset).unwrap();
let threshold_l = threshold_params.moduli().len();
let msg_bit = calculate_bit_width(BigInt::from(dkg_params.plaintext()));

let inputs = Inputs::compute(preset, &sample).unwrap();
assert_eq!(
inputs.expected_commitments.len(),
sample.honest_ciphertexts.len()
);

for (party_idx, party_cts) in sample.honest_ciphertexts.iter().enumerate() {
for mod_idx in 0..threshold_l {
let decrypted_pt = sample.secret_key.try_decrypt(&party_cts[mod_idx]).unwrap();
let share_coeffs = decrypted_pt.value.deref().to_vec();
let direct_commitment = compute_share_encryption_commitment_from_message(
&Polynomial::from_u64_vector(share_coeffs),
msg_bit,
);
assert_eq!(
inputs.expected_commitments[party_idx][mod_idx], direct_commitment,
"expected_commitments[{}][{}] doesn't match direct computation",
party_idx, mod_idx
);
}
}
}
}
Loading