diff --git a/crates/events/src/enclave_event/compute_request/zk.rs b/crates/events/src/enclave_event/compute_request/zk.rs index 724c823b56..17bb1234b8 100644 --- a/crates/events/src/enclave_event/compute_request/zk.rs +++ b/crates/events/src/enclave_event/compute_request/zk.rs @@ -389,7 +389,7 @@ pub struct PartyShareDecryptionProofsToVerify { /// Signed C4a proof (SecretKey decryption). pub signed_sk_decryption_proof: SignedProofPayload, /// Signed C4b proofs (SmudgingNoise decryption), one per smudging noise index. - pub signed_esm_decryption_proofs: Vec, + pub signed_e_sm_decryption_proofs: Vec, } /// Batch verification results for C4 proofs. diff --git a/crates/events/src/enclave_event/decryption_key_shared.rs b/crates/events/src/enclave_event/decryption_key_shared.rs index 27a173588d..af51165c5a 100644 --- a/crates/events/src/enclave_event/decryption_key_shared.rs +++ b/crates/events/src/enclave_event/decryption_key_shared.rs @@ -30,7 +30,7 @@ pub struct DecryptionKeyShared { /// ECDSA-signed C4a proof (SecretKey decryption) for verification and fault attribution. pub signed_sk_decryption_proof: SignedProofPayload, /// ECDSA-signed C4b proofs (SmudgingNoise decryption), one per smudging noise index. - pub signed_esm_decryption_proofs: Vec, + pub signed_e_sm_decryption_proofs: Vec, /// Whether this was received from the network. pub external: bool, } diff --git a/crates/events/src/enclave_event/signed_proof.rs b/crates/events/src/enclave_event/signed_proof.rs index 5a63f4178f..af8a4c70df 100644 --- a/crates/events/src/enclave_event/signed_proof.rs +++ b/crates/events/src/enclave_event/signed_proof.rs @@ -39,12 +39,12 @@ pub enum ProofType { C3aSkShareEncryption = 4, /// C3b — Smudging noise share encryption proof (Proof 3b). C3bESmShareEncryption = 5, - /// T2 — DKG share decryption proof (Proof 4). - T2DkgShareDecryption = 6, - /// T5 — Threshold share decryption proof (Proof 6). - T5ShareDecryption = 7, - /// T6 — Decrypted shares aggregation proof (Proof 7). - T6DecryptedSharesAggregation = 8, + /// C4 — DKG share decryption proof (Proof 4). + C4DkgShareDecryption = 6, + /// C6 — Threshold share decryption proof (Proof 6). + C6ThresholdShareDecryption = 7, + /// C7 — Decrypted shares aggregation proof (Proof 7). + C7DecryptedSharesAggregation = 8, /// C5 — Public key aggregation proof (Proof 5). C5PkAggregation = 9, } @@ -59,9 +59,9 @@ impl ProofType { ProofType::C2bESmShareComputation => vec![CircuitName::ESmShareComputation], ProofType::C3aSkShareEncryption => vec![CircuitName::ShareEncryption], ProofType::C3bESmShareEncryption => vec![CircuitName::ShareEncryption], - ProofType::T2DkgShareDecryption => vec![CircuitName::DkgShareDecryption], - ProofType::T5ShareDecryption => vec![CircuitName::ThresholdShareDecryption], - ProofType::T6DecryptedSharesAggregation => vec![ + ProofType::C4DkgShareDecryption => vec![CircuitName::DkgShareDecryption], + ProofType::C6ThresholdShareDecryption => vec![CircuitName::ThresholdShareDecryption], + ProofType::C7DecryptedSharesAggregation => vec![ CircuitName::DecryptedSharesAggregationBn, CircuitName::DecryptedSharesAggregationMod, ], @@ -78,9 +78,9 @@ impl ProofType { | ProofType::C2bESmShareComputation | ProofType::C3aSkShareEncryption | ProofType::C3bESmShareEncryption - | ProofType::T2DkgShareDecryption => "E3_BAD_DKG_PROOF", - ProofType::T5ShareDecryption => "E3_BAD_DECRYPTION_PROOF", - ProofType::T6DecryptedSharesAggregation => "E3_BAD_AGGREGATION_PROOF", + | ProofType::C4DkgShareDecryption => "E3_BAD_DKG_PROOF", + ProofType::C6ThresholdShareDecryption => "E3_BAD_DECRYPTION_PROOF", + ProofType::C7DecryptedSharesAggregation => "E3_BAD_AGGREGATION_PROOF", ProofType::C5PkAggregation => "E3_BAD_PK_AGGREGATION_PROOF", } } @@ -392,15 +392,15 @@ mod tests { vec![CircuitName::ShareEncryption] ); assert_eq!( - ProofType::T2DkgShareDecryption.circuit_names(), + ProofType::C4DkgShareDecryption.circuit_names(), vec![CircuitName::DkgShareDecryption] ); assert_eq!( - ProofType::T5ShareDecryption.circuit_names(), + ProofType::C6ThresholdShareDecryption.circuit_names(), vec![CircuitName::ThresholdShareDecryption] ); assert_eq!( - ProofType::T6DecryptedSharesAggregation.circuit_names(), + ProofType::C7DecryptedSharesAggregation.circuit_names(), vec![ CircuitName::DecryptedSharesAggregationBn, CircuitName::DecryptedSharesAggregationMod, diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index 97a8471f02..907dc09e92 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -1924,11 +1924,11 @@ impl ThresholdKeyshare { let party_proofs: Vec = collected_shares .iter() .filter_map(|(&party_id, share)| { - if share.signed_esm_decryption_proofs.len() != expected_esm { + if share.signed_e_sm_decryption_proofs.len() != expected_esm { warn!( "Party {} has wrong ESM proof count ({} vs expected {}) for E3 {} — treating as dishonest", party_id, - share.signed_esm_decryption_proofs.len(), + share.signed_e_sm_decryption_proofs.len(), expected_esm, e3_id ); @@ -1938,7 +1938,7 @@ impl ThresholdKeyshare { Some(PartyShareDecryptionProofsToVerify { sender_party_id: party_id, signed_sk_decryption_proof: share.signed_sk_decryption_proof.clone(), - signed_esm_decryption_proofs: share.signed_esm_decryption_proofs.clone(), + signed_e_sm_decryption_proofs: share.signed_e_sm_decryption_proofs.clone(), }) } }) diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 18c7d6c7ca..f9f145a430 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -1159,7 +1159,7 @@ fn handle_verify_share_decryption_proofs( // Guard: an empty esm_decryption_proofs vec would make this loop // vacuously true. Defence-in-depth: reject any party with zero ESM proofs. - if party.signed_esm_decryption_proofs.is_empty() { + if party.signed_e_sm_decryption_proofs.is_empty() { return PartyVerificationResult { sender_party_id: sender, all_verified: false, @@ -1171,7 +1171,7 @@ fn handle_verify_share_decryption_proofs( // Flatten all signed proofs (SK + ESMs) and verify uniformly. let all_signed: Vec<&e3_events::SignedProofPayload> = std::iter::once(&party.signed_sk_decryption_proof) - .chain(party.signed_esm_decryption_proofs.iter()) + .chain(party.signed_e_sm_decryption_proofs.iter()) .collect(); for signed_proof in &all_signed { diff --git a/crates/trbfv/src/gen_pk_share_and_sk_sss.rs b/crates/trbfv/src/gen_pk_share_and_sk_sss.rs index b6c596a89f..06144baab9 100644 --- a/crates/trbfv/src/gen_pk_share_and_sk_sss.rs +++ b/crates/trbfv/src/gen_pk_share_and_sk_sss.rs @@ -62,11 +62,11 @@ pub struct GenPkShareAndSkSssResponse { pub pk_share: ArcBytes, /// SecretKey Shamir Shares for other parties pub sk_sss: Encrypted, - /// Raw pk0 share polynomial (RNS form) for ZK proof generation (T1a). + /// Raw pk0 share polynomial (RNS form) for ZK proof generation (C1). pub pk0_share_raw: ArcBytes, - /// Raw secret key polynomial (RNS form) for ZK proof generation (T1a) — encrypted at rest. + /// Raw secret key polynomial (RNS form) for ZK proof generation (C1) — encrypted at rest. pub sk_raw: SensitiveBytes, - /// Raw error polynomial from key generation (RNS form) for ZK proof generation (T1a) — encrypted at rest. + /// Raw error polynomial from key generation (RNS form) for ZK proof generation (C1) — encrypted at rest. pub eek_raw: SensitiveBytes, /// Raw smudging noise polynomial (RNS form) for ZK proof generation (C1) — encrypted at rest. pub e_sm_raw: SensitiveBytes, diff --git a/crates/zk-prover/src/actors/proof_request.rs b/crates/zk-prover/src/actors/proof_request.rs index 64d71ca593..a1e229e078 100644 --- a/crates/zk-prover/src/actors/proof_request.rs +++ b/crates/zk-prover/src/actors/proof_request.rs @@ -697,7 +697,7 @@ impl ProofRequestActor { // Sign C4a (SK decryption proof) let Some(signed_sk) = self.sign_proof( e3_id, - ProofType::T2DkgShareDecryption, + ProofType::C4DkgShareDecryption, pending.sk_proof.expect("checked in is_complete"), ) else { error!("Failed to sign C4a SK proof — DecryptionKeyShared will not be published"); @@ -712,7 +712,7 @@ impl ProofRequestActor { .get(&idx) .expect("checked in is_complete") .clone(); - let Some(signed) = self.sign_proof(e3_id, ProofType::T2DkgShareDecryption, proof) + let Some(signed) = self.sign_proof(e3_id, ProofType::C4DkgShareDecryption, proof) else { error!( "Failed to sign C4b ESM proof [{}] — DecryptionKeyShared will not be published", @@ -738,7 +738,7 @@ impl ProofRequestActor { sk_poly_sum: pending.sk_poly_sum, es_poly_sum: pending.es_poly_sum, signed_sk_decryption_proof: signed_sk, - signed_esm_decryption_proofs: signed_esms, + signed_e_sm_decryption_proofs: signed_esms, external: false, }, pending.ec, @@ -826,7 +826,9 @@ impl ProofRequestActor { // Sign raw C6 proofs (for ShareVerification) let mut signed_proofs = Vec::with_capacity(proofs.len()); for proof in proofs { - let Some(signed) = self.sign_proof(&e3_id, ProofType::T5ShareDecryption, proof) else { + let Some(signed) = + self.sign_proof(&e3_id, ProofType::C6ThresholdShareDecryption, proof) + else { error!("Failed to sign C6 proof — DecryptionshareCreated will not be published"); return; }; @@ -1004,7 +1006,7 @@ impl ProofRequestActor { let mut signed_proofs = Vec::with_capacity(proofs.len()); for proof in proofs { let Some(signed) = - self.sign_proof(&e3_id, ProofType::T6DecryptedSharesAggregation, proof) + self.sign_proof(&e3_id, ProofType::C7DecryptedSharesAggregation, proof) else { error!("Failed to sign C7 proof — AggregationProofSigned will not be published"); return; diff --git a/crates/zk-prover/src/actors/share_verification.rs b/crates/zk-prover/src/actors/share_verification.rs index 75d3a89a30..50765d59c3 100644 --- a/crates/zk-prover/src/actors/share_verification.rs +++ b/crates/zk-prover/src/actors/share_verification.rs @@ -38,6 +38,32 @@ use e3_events::{ use e3_utils::NotifySync; use tracing::{error, info, warn}; +/// Trait for party types whose signed proofs can be ECDSA-validated and ZK-verified. +trait VerifiableParty: Clone { + fn party_id(&self) -> u64; + fn signed_proofs(&self) -> Vec; +} + +impl VerifiableParty for PartyProofsToVerify { + fn party_id(&self) -> u64 { + self.sender_party_id + } + fn signed_proofs(&self) -> Vec { + self.signed_proofs.clone() + } +} + +impl VerifiableParty for PartyShareDecryptionProofsToVerify { + fn party_id(&self) -> u64 { + self.sender_party_id + } + fn signed_proofs(&self) -> Vec { + std::iter::once(self.signed_sk_decryption_proof.clone()) + .chain(self.signed_e_sm_decryption_proofs.iter().cloned()) + .collect() + } +} + /// ECDSA validation result for a single party. struct EcdsaPartyResult { passed: bool, @@ -106,22 +132,57 @@ impl ShareVerificationActor { VerificationKind::ShareProofs | VerificationKind::ThresholdDecryptionProofs | VerificationKind::PkGenerationProofs => { - self.verify_share_proofs(e3_id, msg.kind, msg.share_proofs, msg.pre_dishonest, ec); + let kind = msg.kind.clone(); + self.verify_proofs( + e3_id, + kind.clone(), + msg.share_proofs, + msg.pre_dishonest, + ec, + |passed, corr_id, e3| { + ComputeRequest::zk( + ZkRequest::VerifyShareProofs(VerifyShareProofsRequest { + party_proofs: passed, + }), + corr_id, + e3, + ) + }, + ); } VerificationKind::DecryptionProofs => { - self.verify_decryption_proofs(e3_id, msg.decryption_proofs, msg.pre_dishonest, ec); + self.verify_proofs( + e3_id, + VerificationKind::DecryptionProofs, + msg.decryption_proofs, + msg.pre_dishonest, + ec, + |passed, corr_id, e3| { + ComputeRequest::zk( + ZkRequest::VerifyShareDecryptionProofs( + VerifyShareDecryptionProofsRequest { + party_proofs: passed, + }, + ), + corr_id, + e3, + ) + }, + ); } } } - /// C2/C3/C6/C1 verification: ECDSA check on each party, then dispatch ZK. - fn verify_share_proofs( + /// Generic ECDSA + ZK verification: validates signed proofs for each party, + /// then dispatches ZK verification for ECDSA-passed parties. + fn verify_proofs( &mut self, e3_id: E3id, kind: VerificationKind, - party_proofs: Vec, + party_proofs: Vec

, pre_dishonest: BTreeSet, ec: EventContext, + build_request: impl FnOnce(Vec

, CorrelationId, E3id) -> ComputeRequest, ) { let e3_id_str = e3_id.to_string(); let label = match &kind { @@ -135,28 +196,26 @@ impl ShareVerificationActor { let mut party_addresses: HashMap = HashMap::new(); for party in &party_proofs { - let result = self.ecdsa_validate_signed_proofs( - party.sender_party_id, - &party.signed_proofs, - &e3_id_str, - label, - ); + let proofs = party.signed_proofs(); + let result = + self.ecdsa_validate_signed_proofs(party.party_id(), &proofs, &e3_id_str, label); if result.passed { ecdsa_passed_parties.push(party.clone()); } else { - ecdsa_dishonest.insert(party.sender_party_id); + ecdsa_dishonest.insert(party.party_id()); if let Some((ref signed, addr)) = result.failed_payload { - self.emit_signed_proof_failed(&e3_id, signed, addr, party.sender_party_id, &ec); + self.emit_signed_proof_failed(&e3_id, signed, addr, party.party_id(), &ec); } } } // Store recovered addresses for passed parties for party in &party_proofs { - if !ecdsa_dishonest.contains(&party.sender_party_id) { - if let Some(first_signed) = party.signed_proofs.first() { + if !ecdsa_dishonest.contains(&party.party_id()) { + let proofs = party.signed_proofs(); + if let Some(first_signed) = proofs.first() { if let Ok(addr) = first_signed.recover_address() { - party_addresses.insert(party.sender_party_id, addr); + party_addresses.insert(party.party_id(), addr); } } } @@ -172,16 +231,14 @@ impl ShareVerificationActor { // Dispatch ZK-only verification to multithread let correlation_id = CorrelationId::new(); - let dispatched_party_ids: HashSet = ecdsa_passed_parties - .iter() - .map(|p| p.sender_party_id) - .collect(); + let dispatched_party_ids: HashSet = + ecdsa_passed_parties.iter().map(|p| p.party_id()).collect(); // Compute proof hashes for ECDSA-passed parties (for ProofVerificationPassed on success) let mut party_proof_hashes: HashMap> = HashMap::new(); for party in &ecdsa_passed_parties { let hashes: Vec<(ProofType, [u8; 32])> = party - .signed_proofs + .signed_proofs() .iter() .map(|signed| { let msg = ( @@ -192,7 +249,7 @@ impl ShareVerificationActor { (signed.payload.proof_type, keccak256(&msg).into()) }) .collect(); - party_proof_hashes.insert(party.sender_party_id, hashes); + party_proof_hashes.insert(party.party_id(), hashes); } self.pending.insert( @@ -209,13 +266,7 @@ impl ShareVerificationActor { }, ); - let request = ComputeRequest::zk( - ZkRequest::VerifyShareProofs(VerifyShareProofsRequest { - party_proofs: ecdsa_passed_parties, - }), - correlation_id, - e3_id.clone(), - ); + let request = build_request(ecdsa_passed_parties, correlation_id, e3_id.clone()); if let Err(err) = self.bus.publish(request, ec.clone()) { error!("Failed to dispatch {} ZK verification: {err}", label); @@ -229,122 +280,6 @@ impl ShareVerificationActor { } } - /// C4 verification: ECDSA check on each party, then dispatch ZK. - fn verify_decryption_proofs( - &mut self, - e3_id: E3id, - party_proofs: Vec, - pre_dishonest: BTreeSet, - ec: EventContext, - ) { - let e3_id_str = e3_id.to_string(); - let mut ecdsa_dishonest = HashSet::new(); - let mut ecdsa_passed_parties = Vec::new(); - let mut party_addresses: HashMap = HashMap::new(); - - for party in &party_proofs { - // Flatten all signed proofs (SK + ESMs) - let all_signed: Vec<&SignedProofPayload> = - std::iter::once(&party.signed_sk_decryption_proof) - .chain(party.signed_esm_decryption_proofs.iter()) - .collect(); - let all_signed_cloned: Vec = - all_signed.iter().map(|s| (*s).clone()).collect(); - - let result = self.ecdsa_validate_signed_proofs( - party.sender_party_id, - &all_signed_cloned, - &e3_id_str, - "C4", - ); - - if result.passed { - ecdsa_passed_parties.push(party.clone()); - } else { - ecdsa_dishonest.insert(party.sender_party_id); - if let Some((ref signed, addr)) = result.failed_payload { - self.emit_signed_proof_failed(&e3_id, signed, addr, party.sender_party_id, &ec); - } - } - } - - // Store recovered addresses for passed parties - for party in &party_proofs { - if !ecdsa_dishonest.contains(&party.sender_party_id) { - if let Ok(addr) = party.signed_sk_decryption_proof.recover_address() { - party_addresses.insert(party.sender_party_id, addr); - } - } - } - - if ecdsa_passed_parties.is_empty() { - let mut all_dishonest: BTreeSet = pre_dishonest; - all_dishonest.extend(ecdsa_dishonest); - self.publish_complete(e3_id, VerificationKind::DecryptionProofs, all_dishonest, ec); - return; - } - - let correlation_id = CorrelationId::new(); - let dispatched_party_ids: HashSet = ecdsa_passed_parties - .iter() - .map(|p| p.sender_party_id) - .collect(); - - // Compute proof hashes for ECDSA-passed parties (for ProofVerificationPassed on success) - let mut party_proof_hashes: HashMap> = HashMap::new(); - for party in &ecdsa_passed_parties { - let all_signed: Vec<&SignedProofPayload> = - std::iter::once(&party.signed_sk_decryption_proof) - .chain(party.signed_esm_decryption_proofs.iter()) - .collect(); - let hashes: Vec<(ProofType, [u8; 32])> = all_signed - .iter() - .map(|signed| { - let msg = ( - Bytes::copy_from_slice(&signed.payload.proof.data), - Bytes::copy_from_slice(&signed.payload.proof.public_signals), - ) - .abi_encode(); - (signed.payload.proof_type, keccak256(&msg).into()) - }) - .collect(); - party_proof_hashes.insert(party.sender_party_id, hashes); - } - - self.pending.insert( - correlation_id, - PendingVerification { - e3_id: e3_id.clone(), - kind: VerificationKind::DecryptionProofs, - ec: ec.clone(), - ecdsa_dishonest, - pre_dishonest, - dispatched_party_ids, - party_addresses, - party_proof_hashes, - }, - ); - - let request = ComputeRequest::zk( - ZkRequest::VerifyShareDecryptionProofs(VerifyShareDecryptionProofsRequest { - party_proofs: ecdsa_passed_parties, - }), - correlation_id, - e3_id.clone(), - ); - - if let Err(err) = self.bus.publish(request, ec.clone()) { - error!("Failed to dispatch C4 ZK verification: {err}"); - if let Some(pending) = self.pending.remove(&correlation_id) { - let mut all_dishonest: BTreeSet = pending.pre_dishonest; - all_dishonest.extend(pending.ecdsa_dishonest); - // Dispatched parties were never ZK-verified — treat as dishonest - all_dishonest.extend(pending.dispatched_party_ids); - self.publish_complete(e3_id, VerificationKind::DecryptionProofs, all_dishonest, ec); - } - } - } - /// Validate ECDSA properties for a set of signed proofs from one party: /// 1. e3_id match /// 2. Signature recovery (valid ECDSA) diff --git a/packages/enclave-contracts/test/Slashing/CommitteeExpulsion.spec.ts b/packages/enclave-contracts/test/Slashing/CommitteeExpulsion.spec.ts index b8476f5642..375e476f09 100644 --- a/packages/enclave-contracts/test/Slashing/CommitteeExpulsion.spec.ts +++ b/packages/enclave-contracts/test/Slashing/CommitteeExpulsion.spec.ts @@ -597,12 +597,12 @@ describe("Committee Expulsion & Fault Tolerance", function () { // Slash operator1 again for a different proof type to verify expulsion is idempotent. // Same (e3Id, operator, proofType) would revert DuplicateEvidence — that's correct. - // Using proofType=7 (T5ShareDecryption) with REASON_PT_7 instead. + // Using proofType=7 (C6ThresholdShareDecryption) with REASON_PT_7 instead. const proof2 = await signAndEncodeAttestation( [operator2, operator3], 0, await operator1.getAddress(), - 7, // T5ShareDecryption — different proofType + 7, // C6ThresholdShareDecryption — different proofType 31337, ethers.keccak256(ethers.toUtf8Bytes("second")), );