From 8a52bebaeae5171962b7aa7e51240e44aff55b16 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Thu, 12 Feb 2026 08:23:22 +0000 Subject: [PATCH 01/23] feat: integrate share proofs in ciphernode --- crates/fhe/src/runtime.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/fhe/src/runtime.rs b/crates/fhe/src/runtime.rs index 64d6869b25..9ca229a386 100644 --- a/crates/fhe/src/runtime.rs +++ b/crates/fhe/src/runtime.rs @@ -185,3 +185,4 @@ impl SecretKeySerializer { }) } } + From 5b49830823fd7ab36ffe63627bae41e50b180edb Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:49:18 +0000 Subject: [PATCH 02/23] feat: add t1 proof gen --- crates/events/src/enclave_event/threshold_share_created.rs | 5 ++++- crates/fhe/src/runtime.rs | 1 - crates/keyshare/src/threshold_keyshare.rs | 2 ++ crates/test-helpers/src/usecase_helpers.rs | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/events/src/enclave_event/threshold_share_created.rs b/crates/events/src/enclave_event/threshold_share_created.rs index e9794a0397..69e84f76d7 100644 --- a/crates/events/src/enclave_event/threshold_share_created.rs +++ b/crates/events/src/enclave_event/threshold_share_created.rs @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use crate::E3id; +use crate::{E3id, SignedProofPayload}; use actix::Message; use derivative::Derivative; use e3_trbfv::shares::BfvEncryptedShares; @@ -32,6 +32,8 @@ pub struct ThresholdShare { pub sk_sss: BfvEncryptedShares, /// BFV-encrypted esi_sss - one per secret key (sk), each recipient can decrypt their share pub esi_sss: Vec, + /// The signed proof payload for fault attribution + pub signed_pk_generation_proof: Option, } impl ThresholdShare { @@ -49,6 +51,7 @@ impl ThresholdShare { pk_share: self.pk_share.clone(), sk_sss, esi_sss, + signed_pk_generation_proof: self.signed_pk_generation_proof.clone(), }) } diff --git a/crates/fhe/src/runtime.rs b/crates/fhe/src/runtime.rs index 9ca229a386..64d6869b25 100644 --- a/crates/fhe/src/runtime.rs +++ b/crates/fhe/src/runtime.rs @@ -185,4 +185,3 @@ impl SecretKeySerializer { }) } } - diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index 590f4599e2..23f5a82eaa 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -775,6 +775,7 @@ impl ThresholdKeyshare { )) })?; } + Ok(()) } @@ -850,6 +851,7 @@ impl ThresholdKeyshare { pk_share, sk_sss: encrypted_sk_sss, esi_sss: encrypted_esi_sss, + signed_pk_generation_proof: None, }; // Build the proof request diff --git a/crates/test-helpers/src/usecase_helpers.rs b/crates/test-helpers/src/usecase_helpers.rs index 3372e9ca60..72b7f62abf 100644 --- a/crates/test-helpers/src/usecase_helpers.rs +++ b/crates/test-helpers/src/usecase_helpers.rs @@ -114,6 +114,7 @@ pub fn generate_shares_hash_map( esi_sss: encrypted_esi_sss, sk_sss: encrypted_sk_sss, pk_share, + signed_pk_generation_proof: None, }, ); } From d643938b8366f9e57bf99720c6722af5b8506e22 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:41:48 +0000 Subject: [PATCH 03/23] chore: refactoring --- .../enclave_event/threshold_share_created.rs | 5 +- crates/keyshare/src/threshold_keyshare.rs | 9 +- crates/test-helpers/src/usecase_helpers.rs | 1 - crates/tests/tests/integration.rs | 116 +++++++++++------- 4 files changed, 82 insertions(+), 49 deletions(-) diff --git a/crates/events/src/enclave_event/threshold_share_created.rs b/crates/events/src/enclave_event/threshold_share_created.rs index 69e84f76d7..e9794a0397 100644 --- a/crates/events/src/enclave_event/threshold_share_created.rs +++ b/crates/events/src/enclave_event/threshold_share_created.rs @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use crate::{E3id, SignedProofPayload}; +use crate::E3id; use actix::Message; use derivative::Derivative; use e3_trbfv::shares::BfvEncryptedShares; @@ -32,8 +32,6 @@ pub struct ThresholdShare { pub sk_sss: BfvEncryptedShares, /// BFV-encrypted esi_sss - one per secret key (sk), each recipient can decrypt their share pub esi_sss: Vec, - /// The signed proof payload for fault attribution - pub signed_pk_generation_proof: Option, } impl ThresholdShare { @@ -51,7 +49,6 @@ impl ThresholdShare { pk_share: self.pk_share.clone(), sk_sss, esi_sss, - signed_pk_generation_proof: self.signed_pk_generation_proof.clone(), }) } diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index 23f5a82eaa..06a7a757a8 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -37,8 +37,16 @@ use e3_zk_helpers::computation::DkgInputType; use e3_zk_helpers::CiphernodesCommitteeSize; use fhe::bfv::{PublicKey, SecretKey}; use fhe_traits::{DeserializeParametrized, Serialize}; +<<<<<<< HEAD use rand::rngs::OsRng; use std::{collections::HashMap, mem, sync::Arc}; +======= +use rand::{rngs::OsRng, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use std::{ + collections::HashMap, mem, sync::{Arc, Mutex} +}; +>>>>>>> be8c56d3 (chore: refactoring) use tracing::{info, trace, warn}; use crate::encryption_key_collector::{AllEncryptionKeysCollected, EncryptionKeyCollector}; @@ -851,7 +859,6 @@ impl ThresholdKeyshare { pk_share, sk_sss: encrypted_sk_sss, esi_sss: encrypted_esi_sss, - signed_pk_generation_proof: None, }; // Build the proof request diff --git a/crates/test-helpers/src/usecase_helpers.rs b/crates/test-helpers/src/usecase_helpers.rs index 72b7f62abf..3372e9ca60 100644 --- a/crates/test-helpers/src/usecase_helpers.rs +++ b/crates/test-helpers/src/usecase_helpers.rs @@ -114,7 +114,6 @@ pub fn generate_shares_hash_map( esi_sss: encrypted_esi_sss, sk_sss: encrypted_sk_sss, pk_share, - signed_pk_generation_proof: None, }, ); } diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index fc2cf56de7..df7f6af648 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -525,44 +525,68 @@ async fn test_trbfv_actor() -> Result<()> { )); // First, wait for all EncryptionKeyCreated events (BFV key exchange) + // Each of the 5 parties: + // - EncryptionKeyPending = 5 + // - ComputeRequest (T0 ZK proof) = 5 + // - ComputeResponse (T0 ZK proof) = 5 + // - EncryptionKeyCreated = 5 + // Total: 20 events let encryption_keys_timer = Instant::now(); - let expected = vec![ - "EncryptionKeyCreated", - "EncryptionKeyCreated", - "EncryptionKeyCreated", - "EncryptionKeyCreated", - "EncryptionKeyCreated", - ]; - let _ = nodes - .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) + let expected_count = 5 + 5 + 5 + 5; // EncKeyPending + T0 ComputeReq + T0 ComputeResp + EncKeyCreated + println!( + "DEBUG: Waiting for {} encryption key events...", + expected_count + ); + let h = nodes + .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) .await?; + println!("DEBUG: EncryptionKey phase events: {:?}", h.event_types()); report.push(( "All EncryptionKeyCreated events", encryption_keys_timer.elapsed(), )); - // Then wait for all ThresholdShareCreated events - // With domain-level splitting, each of the 5 parties publishes 5 events (one per target party) - // Total: 5 parties × 5 targets = 25 events + // Then wait for share generation compute events + ThresholdShareCreated + PkGenerationProofSigned + // Each of the 5 parties: + // - ComputeRequest + ComputeResponse for GenPkShareAndSkSss = 10 + // - ComputeRequest + ComputeResponse for GenEsiSss = 10 + // - ThresholdSharePending = 5 + // - ComputeRequest + ComputeResponse for T1 ZK proof = 10 + // - 5 ThresholdShareCreated events (one per target party) = 25 total + // - 1 PkGenerationProofSigned event = 5 total + // Total: 10 + 10 + 5 + 10 + 25 + 5 = 65 events let shares_timer = Instant::now(); - let expected: Vec<&str> = (0..25).map(|_| "ThresholdShareCreated").collect(); - let _ = nodes - .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) + let expected_count = 10 + 10 + 5 + 10 + 25 + 5; // GenPk + GenEsi + TSPending + T1 ZK + TSCreated + PkGenProof + println!( + "DEBUG: Waiting for {} share generation events...", + expected_count + ); + let h = nodes + .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) .await?; - report.push(("All ThresholdShareCreated events", shares_timer.elapsed())); - + println!( + "DEBUG: Share generation phase events: {:?}", + h.event_types() + ); + report.push(("All share generation events", shares_timer.elapsed())); + + // Wait for CalculateDecryptionKey compute events + KeyshareCreated + PublicKeyAggregated + // Each of the 5 parties: + // - ComputeRequest for CalculateDecryptionKey + // - ComputeResponse for CalculateDecryptionKey + // - KeyshareCreated + // Plus 1 PublicKeyAggregated at the end + // Total: 5 + 5 + 5 + 1 = 16 events let shares_to_pubkey_agg_timer = Instant::now(); - let expected = vec![ - "KeyshareCreated", - "KeyshareCreated", - "KeyshareCreated", - "KeyshareCreated", - "KeyshareCreated", - "PublicKeyAggregated", - ]; + let expected_count = 5 + 5 + 5 + 1; // ComputeRequest + ComputeResponse + KeyshareCreated + PublicKeyAggregated + println!( + "DEBUG: Waiting for {} decryption key events...", + expected_count + ); let h = nodes - .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) + .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) .await?; + println!("DEBUG: Decryption key phase events: {:?}", h.event_types()); report.push(( "ThresholdShares -> PublicKeyAggregated", @@ -574,14 +598,16 @@ async fn test_trbfv_actor() -> Result<()> { e3_requested_timer.elapsed(), )); let app_gen_timer = Instant::now(); - assert_eq!(h.event_types(), expected); - // Aggregate decryption + // Verify we got the expected events (last should be PublicKeyAggregated) // First we get the public key println!("Getting public key"); let Some(EnclaveEventData::PublicKeyAggregated(pubkey_event)) = h.last().map(|e| e.get_data()) else { - panic!("Was expecting event to be PublicKeyAggregated"); + panic!( + "Was expecting last event to be PublicKeyAggregated, got: {:?}", + h.event_types() + ); }; let pubkey_bytes = pubkey_event.pubkey.clone(); @@ -629,23 +655,24 @@ async fn test_trbfv_actor() -> Result<()> { println!("CiphertextOutputPublished event has been dispatched!"); // Lets grab decryption share events - let expected = vec![ - "CiphertextOutputPublished", - "DecryptionshareCreated", - "DecryptionshareCreated", - "DecryptionshareCreated", - "ComputeRequest", - "DecryptionshareCreated", - "DecryptionshareCreated", - "ComputeResponse", - "PlaintextAggregated", - ]; + // Each of the 5 parties: + // - ComputeRequest for CalculateDecryptionShare + // - ComputeResponse for CalculateDecryptionShare + // - DecryptionshareCreated + // Plus aggregation: + // - 1 CiphertextOutputPublished + // - 1 ComputeRequest (PlaintextAggregation) + // - 1 ComputeResponse (PlaintextAggregation) + // - 1 PlaintextAggregated + // Total: 1 + 5*3 + 3 = 19 events + let expected_count = 1 + (5 * 3) + 3; + println!("DEBUG: Waiting for {} decryption events...", expected_count); let h = nodes - .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) + .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) .await?; + println!("DEBUG: Decryption phase events: {:?}", h.event_types()); - assert_eq!(h.event_types(), expected); report.push(( "Ciphertext published -> PlaintextAggregated", publishing_ct_timer.elapsed(), @@ -656,7 +683,10 @@ async fn test_trbfv_actor() -> Result<()> { .. })) = h.last().map(|e| e.get_data()) else { - bail!("bad event") + bail!( + "Expected last event to be PlaintextAggregated, got: {:?}", + h.event_types() + ) }; let results = plaintext From 0da98c3c2bec7fc91a36439afdba8a19cfe0a763 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Thu, 12 Feb 2026 21:30:29 +0000 Subject: [PATCH 04/23] chore: integration test --- crates/tests/tests/integration.rs | 116 +++++++++++------------------- 1 file changed, 43 insertions(+), 73 deletions(-) diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index df7f6af648..fc2cf56de7 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -525,68 +525,44 @@ async fn test_trbfv_actor() -> Result<()> { )); // First, wait for all EncryptionKeyCreated events (BFV key exchange) - // Each of the 5 parties: - // - EncryptionKeyPending = 5 - // - ComputeRequest (T0 ZK proof) = 5 - // - ComputeResponse (T0 ZK proof) = 5 - // - EncryptionKeyCreated = 5 - // Total: 20 events let encryption_keys_timer = Instant::now(); - let expected_count = 5 + 5 + 5 + 5; // EncKeyPending + T0 ComputeReq + T0 ComputeResp + EncKeyCreated - println!( - "DEBUG: Waiting for {} encryption key events...", - expected_count - ); - let h = nodes - .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) + let expected = vec![ + "EncryptionKeyCreated", + "EncryptionKeyCreated", + "EncryptionKeyCreated", + "EncryptionKeyCreated", + "EncryptionKeyCreated", + ]; + let _ = nodes + .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) .await?; - println!("DEBUG: EncryptionKey phase events: {:?}", h.event_types()); report.push(( "All EncryptionKeyCreated events", encryption_keys_timer.elapsed(), )); - // Then wait for share generation compute events + ThresholdShareCreated + PkGenerationProofSigned - // Each of the 5 parties: - // - ComputeRequest + ComputeResponse for GenPkShareAndSkSss = 10 - // - ComputeRequest + ComputeResponse for GenEsiSss = 10 - // - ThresholdSharePending = 5 - // - ComputeRequest + ComputeResponse for T1 ZK proof = 10 - // - 5 ThresholdShareCreated events (one per target party) = 25 total - // - 1 PkGenerationProofSigned event = 5 total - // Total: 10 + 10 + 5 + 10 + 25 + 5 = 65 events + // Then wait for all ThresholdShareCreated events + // With domain-level splitting, each of the 5 parties publishes 5 events (one per target party) + // Total: 5 parties × 5 targets = 25 events let shares_timer = Instant::now(); - let expected_count = 10 + 10 + 5 + 10 + 25 + 5; // GenPk + GenEsi + TSPending + T1 ZK + TSCreated + PkGenProof - println!( - "DEBUG: Waiting for {} share generation events...", - expected_count - ); - let h = nodes - .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) + let expected: Vec<&str> = (0..25).map(|_| "ThresholdShareCreated").collect(); + let _ = nodes + .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) .await?; - println!( - "DEBUG: Share generation phase events: {:?}", - h.event_types() - ); - report.push(("All share generation events", shares_timer.elapsed())); - - // Wait for CalculateDecryptionKey compute events + KeyshareCreated + PublicKeyAggregated - // Each of the 5 parties: - // - ComputeRequest for CalculateDecryptionKey - // - ComputeResponse for CalculateDecryptionKey - // - KeyshareCreated - // Plus 1 PublicKeyAggregated at the end - // Total: 5 + 5 + 5 + 1 = 16 events + report.push(("All ThresholdShareCreated events", shares_timer.elapsed())); + let shares_to_pubkey_agg_timer = Instant::now(); - let expected_count = 5 + 5 + 5 + 1; // ComputeRequest + ComputeResponse + KeyshareCreated + PublicKeyAggregated - println!( - "DEBUG: Waiting for {} decryption key events...", - expected_count - ); + let expected = vec![ + "KeyshareCreated", + "KeyshareCreated", + "KeyshareCreated", + "KeyshareCreated", + "KeyshareCreated", + "PublicKeyAggregated", + ]; let h = nodes - .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) + .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) .await?; - println!("DEBUG: Decryption key phase events: {:?}", h.event_types()); report.push(( "ThresholdShares -> PublicKeyAggregated", @@ -598,16 +574,14 @@ async fn test_trbfv_actor() -> Result<()> { e3_requested_timer.elapsed(), )); let app_gen_timer = Instant::now(); + assert_eq!(h.event_types(), expected); + // Aggregate decryption - // Verify we got the expected events (last should be PublicKeyAggregated) // First we get the public key println!("Getting public key"); let Some(EnclaveEventData::PublicKeyAggregated(pubkey_event)) = h.last().map(|e| e.get_data()) else { - panic!( - "Was expecting last event to be PublicKeyAggregated, got: {:?}", - h.event_types() - ); + panic!("Was expecting event to be PublicKeyAggregated"); }; let pubkey_bytes = pubkey_event.pubkey.clone(); @@ -655,24 +629,23 @@ async fn test_trbfv_actor() -> Result<()> { println!("CiphertextOutputPublished event has been dispatched!"); // Lets grab decryption share events - // Each of the 5 parties: - // - ComputeRequest for CalculateDecryptionShare - // - ComputeResponse for CalculateDecryptionShare - // - DecryptionshareCreated - // Plus aggregation: - // - 1 CiphertextOutputPublished - // - 1 ComputeRequest (PlaintextAggregation) - // - 1 ComputeResponse (PlaintextAggregation) - // - 1 PlaintextAggregated - // Total: 1 + 5*3 + 3 = 19 events - let expected_count = 1 + (5 * 3) + 3; - println!("DEBUG: Waiting for {} decryption events...", expected_count); + let expected = vec![ + "CiphertextOutputPublished", + "DecryptionshareCreated", + "DecryptionshareCreated", + "DecryptionshareCreated", + "ComputeRequest", + "DecryptionshareCreated", + "DecryptionshareCreated", + "ComputeResponse", + "PlaintextAggregated", + ]; let h = nodes - .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) + .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) .await?; - println!("DEBUG: Decryption phase events: {:?}", h.event_types()); + assert_eq!(h.event_types(), expected); report.push(( "Ciphertext published -> PlaintextAggregated", publishing_ct_timer.elapsed(), @@ -683,10 +656,7 @@ async fn test_trbfv_actor() -> Result<()> { .. })) = h.last().map(|e| e.get_data()) else { - bail!( - "Expected last event to be PlaintextAggregated, got: {:?}", - h.event_types() - ) + bail!("bad event") }; let results = plaintext From 6e37a6a937bb01ef262810c179c8657ae5f754a3 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Sat, 14 Feb 2026 10:26:25 +0000 Subject: [PATCH 05/23] chore: simplify flow --- crates/keyshare/src/threshold_keyshare.rs | 84 +++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index 06a7a757a8..2a7dec6e22 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -787,6 +787,90 @@ impl ThresholdKeyshare { Ok(()) } + /// 3. GenEsiSss + pub fn handle_gen_esi_sss_requested(&self, msg: TypedEvent) -> Result<()> { + let (msg, ec) = msg.into_components(); + info!("GenEsiSss on ThresholdKeyshare"); + + let evt = msg.ciphernode_selected; + let e_sm_raw = msg.e_sm_raw; + let CiphernodeSelected { + e3_id, + .. + } = evt.clone(); + + let state = self + .state + .get() + .ok_or(anyhow!("State not found on ThrehsoldKeyshare"))?; + + let trbfv_config = state.get_trbfv_config(); + + let event = ComputeRequest::trbfv( + TrBFVRequest::GenEsiSss( + GenEsiSssRequest { + trbfv_config, + e_sm_raw, + } + .into(), + ), + CorrelationId::new(), + e3_id, + ); + + self.bus.publish(event, ec)?; + Ok(()) + } + + /// 3a. GenEsiSss result + pub fn handle_gen_esi_sss_response(&mut self, res: TypedEvent) -> Result<()> { + let (res, ec) = res.into_components(); + let output: GenEsiSssResponse = res.try_into()?; + + let esi_sss = output.esi_sss; + + // First store esi_sss in GeneratingThresholdShareData + self.state.try_mutate(&ec, |s| { + info!("try_store_esi_sss"); + let current: GeneratingThresholdShareData = s.clone().try_into()?; + s.new_state(KeyshareState::GeneratingThresholdShare( + GeneratingThresholdShareData { + esi_sss: Some(esi_sss), + ..current + }, + )) + })?; + + info!("esi stored"); + + // Check if all data is ready, if so call handle_shares_generated BEFORE transitioning + let current: GeneratingThresholdShareData = self.state.try_get()?.try_into()?; + let ready = current.pk_share.is_some() + && current.sk_sss.is_some() + && current.esi_sss.is_some() + && current.e_sm_raw.is_some() + && current.proof_request_data.is_some(); + + if ready { + // Call handle_shares_generated while still in GeneratingThresholdShare state + self.handle_shares_generated(ec.clone())?; + + // Now transition to AggregatingDecryptionKey with minimal state + self.state.try_mutate(&ec, |s| { + let current: GeneratingThresholdShareData = s.clone().try_into()?; + s.new_state(KeyshareState::AggregatingDecryptionKey( + AggregatingDecryptionKey { + pk_share: current.pk_share.expect("pk_share checked above"), + sk_bfv: current.sk_bfv, + signed_pk_generation_proof: None, + }, + )) + })?; + } + Ok(()) + } + + /// 4. SharesGenerated - Encrypt shares with BFV and publish pub fn handle_shares_generated(&mut self, ec: EventContext) -> Result<()> { let Some(ThresholdKeyshareState { From 2c6ac0b376bc1cc2a27d9e6f9ba12c19206722b5 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:50:39 +0000 Subject: [PATCH 06/23] chore: conflicts --- crates/keyshare/src/threshold_keyshare.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index 2a7dec6e22..8cf6b216c2 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -44,7 +44,9 @@ use std::{collections::HashMap, mem, sync::Arc}; use rand::{rngs::OsRng, SeedableRng}; use rand_chacha::ChaCha20Rng; use std::{ - collections::HashMap, mem, sync::{Arc, Mutex} + collections::HashMap, + mem, + sync::{Arc, Mutex}, }; >>>>>>> be8c56d3 (chore: refactoring) use tracing::{info, trace, warn}; @@ -787,17 +789,14 @@ impl ThresholdKeyshare { Ok(()) } - /// 3. GenEsiSss - pub fn handle_gen_esi_sss_requested(&self, msg: TypedEvent) -> Result<()> { + /// 3. GenEsiSss + pub fn handle_gen_esi_sss_requested(&self, msg: TypedEvent) -> Result<()> { let (msg, ec) = msg.into_components(); info!("GenEsiSss on ThresholdKeyshare"); let evt = msg.ciphernode_selected; let e_sm_raw = msg.e_sm_raw; - let CiphernodeSelected { - e3_id, - .. - } = evt.clone(); + let CiphernodeSelected { e3_id, .. } = evt.clone(); let state = self .state @@ -870,7 +869,6 @@ impl ThresholdKeyshare { Ok(()) } - /// 4. SharesGenerated - Encrypt shares with BFV and publish pub fn handle_shares_generated(&mut self, ec: EventContext) -> Result<()> { let Some(ThresholdKeyshareState { From 0d2ea958331716966efc2d864551134cca6d2036 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:29:22 +0000 Subject: [PATCH 07/23] chore: use sensitive bytes --- crates/keyshare/src/threshold_keyshare.rs | 90 +---------------------- 1 file changed, 4 insertions(+), 86 deletions(-) diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index 8cf6b216c2..976b8e1a67 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -37,10 +37,6 @@ use e3_zk_helpers::computation::DkgInputType; use e3_zk_helpers::CiphernodesCommitteeSize; use fhe::bfv::{PublicKey, SecretKey}; use fhe_traits::{DeserializeParametrized, Serialize}; -<<<<<<< HEAD -use rand::rngs::OsRng; -use std::{collections::HashMap, mem, sync::Arc}; -======= use rand::{rngs::OsRng, SeedableRng}; use rand_chacha::ChaCha20Rng; use std::{ @@ -48,7 +44,6 @@ use std::{ mem, sync::{Arc, Mutex}, }; ->>>>>>> be8c56d3 (chore: refactoring) use tracing::{info, trace, warn}; use crate::encryption_key_collector::{AllEncryptionKeysCollected, EncryptionKeyCollector}; @@ -710,92 +705,13 @@ impl ThresholdKeyshare { Ok(()) } - /// 3. GenEsiSss - pub fn handle_gen_esi_sss_requested(&self, msg: TypedEvent) -> Result<()> { - let (msg, ec) = msg.into_components(); - info!("GenEsiSss on ThresholdKeyshare"); - - let e_sm_raw = msg.e_sm_raw; - let CiphernodeSelected { e3_id, .. } = msg.ciphernode_selected; - - let state = self - .state - .get() - .ok_or(anyhow!("State not found on ThrehsoldKeyshare"))?; - - let trbfv_config = state.get_trbfv_config(); - - let event = ComputeRequest::trbfv( - TrBFVRequest::GenEsiSss(GenEsiSssRequest { - trbfv_config, - e_sm_raw, - }), - CorrelationId::new(), - e3_id, - ); - - self.bus.publish(event, ec)?; - Ok(()) - } - - /// 3a. GenEsiSss result - pub fn handle_gen_esi_sss_response(&mut self, res: TypedEvent) -> Result<()> { - let (res, ec) = res.into_components(); - let output: GenEsiSssResponse = res.try_into()?; - - let esi_sss = output.esi_sss; - - // First store esi_sss in GeneratingThresholdShareData - self.state.try_mutate(&ec, |s| { - info!("try_store_esi_sss"); - let current: GeneratingThresholdShareData = s.clone().try_into()?; - s.new_state(KeyshareState::GeneratingThresholdShare( - GeneratingThresholdShareData { - esi_sss: Some(esi_sss), - ..current - }, - )) - })?; - - info!("esi stored"); - - // Check if all data is ready, if so call handle_shares_generated BEFORE transitioning - let current: GeneratingThresholdShareData = self.state.try_get()?.try_into()?; - let ready = current.pk_share.is_some() - && current.sk_sss.is_some() - && current.esi_sss.is_some() - && current.e_sm_raw.is_some() - && current.proof_request_data.is_some(); - - if ready { - // Call handle_shares_generated while still in GeneratingThresholdShare state - self.handle_shares_generated(ec.clone())?; - - // Now transition to AggregatingDecryptionKey with minimal state - self.state.try_mutate(&ec, |s| { - let current: GeneratingThresholdShareData = s.clone().try_into()?; - s.new_state(KeyshareState::AggregatingDecryptionKey( - AggregatingDecryptionKey { - pk_share: current.pk_share.expect("pk_share checked above"), - sk_bfv: current.sk_bfv, - signed_pk_generation_proof: None, - signed_sk_share_computation_proof: None, - signed_e_sm_share_computation_proof: None, - }, - )) - })?; - } - - Ok(()) - } - /// 3. GenEsiSss pub fn handle_gen_esi_sss_requested(&self, msg: TypedEvent) -> Result<()> { let (msg, ec) = msg.into_components(); info!("GenEsiSss on ThresholdKeyshare"); let evt = msg.ciphernode_selected; - let e_sm_raw = msg.e_sm_raw; + let e_sm_raw_decrypted = ArcBytes::from_bytes(&msg.e_sm_raw.access_raw(&self.cipher)?); let CiphernodeSelected { e3_id, .. } = evt.clone(); let state = self @@ -809,7 +725,7 @@ impl ThresholdKeyshare { TrBFVRequest::GenEsiSss( GenEsiSssRequest { trbfv_config, - e_sm_raw, + e_sm_raw: e_sm_raw_decrypted, } .into(), ), @@ -862,6 +778,8 @@ impl ThresholdKeyshare { pk_share: current.pk_share.expect("pk_share checked above"), sk_bfv: current.sk_bfv, signed_pk_generation_proof: None, + signed_sk_share_computation_proof: None, + signed_e_sm_share_computation_proof: None, }, )) })?; From 54c3e6573243df0fb85bcd26a08e826006455828 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Mon, 16 Feb 2026 10:15:48 +0000 Subject: [PATCH 08/23] feat: integrate sk_share/e_sm_share computation --- crates/events/src/enclave_event/compute_request/zk.rs | 6 +++--- crates/keyshare/src/threshold_keyshare.rs | 2 +- crates/multithread/src/multithread.rs | 8 ++++---- crates/zk-prover/src/actors/proof_request.rs | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/events/src/enclave_event/compute_request/zk.rs b/crates/events/src/enclave_event/compute_request/zk.rs index 9d5f88d570..70f560756a 100644 --- a/crates/events/src/enclave_event/compute_request/zk.rs +++ b/crates/events/src/enclave_event/compute_request/zk.rs @@ -17,13 +17,13 @@ use serde::{Deserialize, Serialize}; pub enum ZkRequest { /// Generate proof for BFV public key (C0). PkBfv(PkBfvProofRequest), - /// Generate proof for PK generation (C1). + /// Generate proof for PK generation (T1). PkGeneration(PkGenerationProofRequest), - /// Generate proof for share and esm computation (C2a and C2b). + /// Generate proof for share and esm computation (T2a and T2b). ShareComputation(ShareComputationProofRequest), } -/// Request to generate a proof for share computation (C2a or C2b). +/// Request to generate a proof for share computation (T2a or T2b). #[derive(Derivative, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derivative(Debug)] pub struct ShareComputationProofRequest { diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index 976b8e1a67..a5d8b7c432 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -458,7 +458,7 @@ impl ThresholdKeyshare { Ok(()) } - /// Handle ShareComputationProofSigned - stores the signed proof in state based on proof type (C1, C2a or C2b) + /// Handle ShareComputationProofSigned - stores the signed proof in state based on proof type (T1, T2a or T2b) pub fn handle_share_computation_proof_signed( &mut self, msg: TypedEvent, diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 319bbe2fb8..41ed5a427b 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -461,13 +461,13 @@ fn handle_share_computation_proof( .map(|arr| arr.mapv(|v| BigInt::from(v))) .collect(); - // 5. Compute parity matrix + // 4. Compute parity matrix let committee = req.committee_size.values(); let parity_matrix = compute_parity_matrix(threshold_params.moduli(), committee.n, committee.threshold) .map_err(|e| make_zk_error(&request, format!("compute_parity_matrix: {}", e)))?; - // 6. Build circuit data + // 5. Build circuit data let circuit_data = ShareComputationCircuitData { dkg_input_type: req.dkg_input_type, secret, @@ -477,7 +477,7 @@ fn handle_share_computation_proof( threshold: committee.threshold as u32, }; - // 7. Generate proof + // 6. Generate proof let circuit = ShareComputationCircuit; let e3_id_str = request.e3_id.to_string(); @@ -490,7 +490,7 @@ fn handle_share_computation_proof( ) })?; - // 8. Return response + // 7. Return response Ok(ComputeResponse::zk( ZkResponse::ShareComputation(ShareComputationProofResponse { proof, diff --git a/crates/zk-prover/src/actors/proof_request.rs b/crates/zk-prover/src/actors/proof_request.rs index c3c91fa453..a78ade263c 100644 --- a/crates/zk-prover/src/actors/proof_request.rs +++ b/crates/zk-prover/src/actors/proof_request.rs @@ -273,7 +273,7 @@ impl ProofRequestActor { ProofType::T1PkGeneration, pending.pk_generation_proof.expect("checked"), ) else { - error!("Failed to sign C1 proof — shares will not be published"); + error!("Failed to sign T1 proof — shares will not be published"); return; }; @@ -282,7 +282,7 @@ impl ProofRequestActor { ProofType::T1SkShareComputation, pending.sk_share_computation_proof.expect("checked"), ) else { - error!("Failed to sign C2a proof — shares will not be published"); + error!("Failed to sign T2a proof — shares will not be published"); return; }; @@ -291,7 +291,7 @@ impl ProofRequestActor { ProofType::T1ESmShareComputation, pending.e_sm_share_computation_proof.expect("checked"), ) else { - error!("Failed to sign C2b proof — shares will not be published"); + error!("Failed to sign T2b proof — shares will not be published"); return; }; From 22f629b83a527221c3332d9fa559ee72a99f9071 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:04:06 +0000 Subject: [PATCH 09/23] chore: pr comments --- crates/keyshare/src/threshold_keyshare.rs | 2 +- crates/multithread/src/multithread.rs | 8 ++++---- crates/zk-prover/src/actors/proof_request.rs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index a5d8b7c432..976b8e1a67 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -458,7 +458,7 @@ impl ThresholdKeyshare { Ok(()) } - /// Handle ShareComputationProofSigned - stores the signed proof in state based on proof type (T1, T2a or T2b) + /// Handle ShareComputationProofSigned - stores the signed proof in state based on proof type (C1, C2a or C2b) pub fn handle_share_computation_proof_signed( &mut self, msg: TypedEvent, diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 41ed5a427b..319bbe2fb8 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -461,13 +461,13 @@ fn handle_share_computation_proof( .map(|arr| arr.mapv(|v| BigInt::from(v))) .collect(); - // 4. Compute parity matrix + // 5. Compute parity matrix let committee = req.committee_size.values(); let parity_matrix = compute_parity_matrix(threshold_params.moduli(), committee.n, committee.threshold) .map_err(|e| make_zk_error(&request, format!("compute_parity_matrix: {}", e)))?; - // 5. Build circuit data + // 6. Build circuit data let circuit_data = ShareComputationCircuitData { dkg_input_type: req.dkg_input_type, secret, @@ -477,7 +477,7 @@ fn handle_share_computation_proof( threshold: committee.threshold as u32, }; - // 6. Generate proof + // 7. Generate proof let circuit = ShareComputationCircuit; let e3_id_str = request.e3_id.to_string(); @@ -490,7 +490,7 @@ fn handle_share_computation_proof( ) })?; - // 7. Return response + // 8. Return response Ok(ComputeResponse::zk( ZkResponse::ShareComputation(ShareComputationProofResponse { proof, diff --git a/crates/zk-prover/src/actors/proof_request.rs b/crates/zk-prover/src/actors/proof_request.rs index a78ade263c..c3c91fa453 100644 --- a/crates/zk-prover/src/actors/proof_request.rs +++ b/crates/zk-prover/src/actors/proof_request.rs @@ -273,7 +273,7 @@ impl ProofRequestActor { ProofType::T1PkGeneration, pending.pk_generation_proof.expect("checked"), ) else { - error!("Failed to sign T1 proof — shares will not be published"); + error!("Failed to sign C1 proof — shares will not be published"); return; }; @@ -282,7 +282,7 @@ impl ProofRequestActor { ProofType::T1SkShareComputation, pending.sk_share_computation_proof.expect("checked"), ) else { - error!("Failed to sign T2a proof — shares will not be published"); + error!("Failed to sign C2a proof — shares will not be published"); return; }; @@ -291,7 +291,7 @@ impl ProofRequestActor { ProofType::T1ESmShareComputation, pending.e_sm_share_computation_proof.expect("checked"), ) else { - error!("Failed to sign T2b proof — shares will not be published"); + error!("Failed to sign C2b proof — shares will not be published"); return; }; From 7c097f0c63518611f0cff5df7244aaa0c5d722d3 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:06:04 +0000 Subject: [PATCH 10/23] chore: pr comments --- crates/events/src/enclave_event/compute_request/zk.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/events/src/enclave_event/compute_request/zk.rs b/crates/events/src/enclave_event/compute_request/zk.rs index 70f560756a..9d5f88d570 100644 --- a/crates/events/src/enclave_event/compute_request/zk.rs +++ b/crates/events/src/enclave_event/compute_request/zk.rs @@ -17,13 +17,13 @@ use serde::{Deserialize, Serialize}; pub enum ZkRequest { /// Generate proof for BFV public key (C0). PkBfv(PkBfvProofRequest), - /// Generate proof for PK generation (T1). + /// Generate proof for PK generation (C1). PkGeneration(PkGenerationProofRequest), - /// Generate proof for share and esm computation (T2a and T2b). + /// Generate proof for share and esm computation (C2a and C2b). ShareComputation(ShareComputationProofRequest), } -/// Request to generate a proof for share computation (T2a or T2b). +/// Request to generate a proof for share computation (C2a or C2b). #[derive(Derivative, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derivative(Debug)] pub struct ShareComputationProofRequest { From 452c170a2a643a0b90b3a0767beb2f94985c1ef5 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:41:48 +0000 Subject: [PATCH 11/23] chore: refactoring --- crates/tests/tests/integration.rs | 116 +++++++++++++++++++----------- 1 file changed, 73 insertions(+), 43 deletions(-) diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index fc2cf56de7..df7f6af648 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -525,44 +525,68 @@ async fn test_trbfv_actor() -> Result<()> { )); // First, wait for all EncryptionKeyCreated events (BFV key exchange) + // Each of the 5 parties: + // - EncryptionKeyPending = 5 + // - ComputeRequest (T0 ZK proof) = 5 + // - ComputeResponse (T0 ZK proof) = 5 + // - EncryptionKeyCreated = 5 + // Total: 20 events let encryption_keys_timer = Instant::now(); - let expected = vec![ - "EncryptionKeyCreated", - "EncryptionKeyCreated", - "EncryptionKeyCreated", - "EncryptionKeyCreated", - "EncryptionKeyCreated", - ]; - let _ = nodes - .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) + let expected_count = 5 + 5 + 5 + 5; // EncKeyPending + T0 ComputeReq + T0 ComputeResp + EncKeyCreated + println!( + "DEBUG: Waiting for {} encryption key events...", + expected_count + ); + let h = nodes + .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) .await?; + println!("DEBUG: EncryptionKey phase events: {:?}", h.event_types()); report.push(( "All EncryptionKeyCreated events", encryption_keys_timer.elapsed(), )); - // Then wait for all ThresholdShareCreated events - // With domain-level splitting, each of the 5 parties publishes 5 events (one per target party) - // Total: 5 parties × 5 targets = 25 events + // Then wait for share generation compute events + ThresholdShareCreated + PkGenerationProofSigned + // Each of the 5 parties: + // - ComputeRequest + ComputeResponse for GenPkShareAndSkSss = 10 + // - ComputeRequest + ComputeResponse for GenEsiSss = 10 + // - ThresholdSharePending = 5 + // - ComputeRequest + ComputeResponse for T1 ZK proof = 10 + // - 5 ThresholdShareCreated events (one per target party) = 25 total + // - 1 PkGenerationProofSigned event = 5 total + // Total: 10 + 10 + 5 + 10 + 25 + 5 = 65 events let shares_timer = Instant::now(); - let expected: Vec<&str> = (0..25).map(|_| "ThresholdShareCreated").collect(); - let _ = nodes - .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) + let expected_count = 10 + 10 + 5 + 10 + 25 + 5; // GenPk + GenEsi + TSPending + T1 ZK + TSCreated + PkGenProof + println!( + "DEBUG: Waiting for {} share generation events...", + expected_count + ); + let h = nodes + .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) .await?; - report.push(("All ThresholdShareCreated events", shares_timer.elapsed())); - + println!( + "DEBUG: Share generation phase events: {:?}", + h.event_types() + ); + report.push(("All share generation events", shares_timer.elapsed())); + + // Wait for CalculateDecryptionKey compute events + KeyshareCreated + PublicKeyAggregated + // Each of the 5 parties: + // - ComputeRequest for CalculateDecryptionKey + // - ComputeResponse for CalculateDecryptionKey + // - KeyshareCreated + // Plus 1 PublicKeyAggregated at the end + // Total: 5 + 5 + 5 + 1 = 16 events let shares_to_pubkey_agg_timer = Instant::now(); - let expected = vec![ - "KeyshareCreated", - "KeyshareCreated", - "KeyshareCreated", - "KeyshareCreated", - "KeyshareCreated", - "PublicKeyAggregated", - ]; + let expected_count = 5 + 5 + 5 + 1; // ComputeRequest + ComputeResponse + KeyshareCreated + PublicKeyAggregated + println!( + "DEBUG: Waiting for {} decryption key events...", + expected_count + ); let h = nodes - .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) + .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) .await?; + println!("DEBUG: Decryption key phase events: {:?}", h.event_types()); report.push(( "ThresholdShares -> PublicKeyAggregated", @@ -574,14 +598,16 @@ async fn test_trbfv_actor() -> Result<()> { e3_requested_timer.elapsed(), )); let app_gen_timer = Instant::now(); - assert_eq!(h.event_types(), expected); - // Aggregate decryption + // Verify we got the expected events (last should be PublicKeyAggregated) // First we get the public key println!("Getting public key"); let Some(EnclaveEventData::PublicKeyAggregated(pubkey_event)) = h.last().map(|e| e.get_data()) else { - panic!("Was expecting event to be PublicKeyAggregated"); + panic!( + "Was expecting last event to be PublicKeyAggregated, got: {:?}", + h.event_types() + ); }; let pubkey_bytes = pubkey_event.pubkey.clone(); @@ -629,23 +655,24 @@ async fn test_trbfv_actor() -> Result<()> { println!("CiphertextOutputPublished event has been dispatched!"); // Lets grab decryption share events - let expected = vec![ - "CiphertextOutputPublished", - "DecryptionshareCreated", - "DecryptionshareCreated", - "DecryptionshareCreated", - "ComputeRequest", - "DecryptionshareCreated", - "DecryptionshareCreated", - "ComputeResponse", - "PlaintextAggregated", - ]; + // Each of the 5 parties: + // - ComputeRequest for CalculateDecryptionShare + // - ComputeResponse for CalculateDecryptionShare + // - DecryptionshareCreated + // Plus aggregation: + // - 1 CiphertextOutputPublished + // - 1 ComputeRequest (PlaintextAggregation) + // - 1 ComputeResponse (PlaintextAggregation) + // - 1 PlaintextAggregated + // Total: 1 + 5*3 + 3 = 19 events + let expected_count = 1 + (5 * 3) + 3; + println!("DEBUG: Waiting for {} decryption events...", expected_count); let h = nodes - .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) + .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) .await?; + println!("DEBUG: Decryption phase events: {:?}", h.event_types()); - assert_eq!(h.event_types(), expected); report.push(( "Ciphertext published -> PlaintextAggregated", publishing_ct_timer.elapsed(), @@ -656,7 +683,10 @@ async fn test_trbfv_actor() -> Result<()> { .. })) = h.last().map(|e| e.get_data()) else { - bail!("bad event") + bail!( + "Expected last event to be PlaintextAggregated, got: {:?}", + h.event_types() + ) }; let results = plaintext From 7c2530e961596b2915e72bd8d2ce24694d2623ba Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:29:22 +0000 Subject: [PATCH 12/23] chore: use sensitive bytes --- crates/keyshare/src/threshold_keyshare.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index 976b8e1a67..cc20ef7ed4 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -711,7 +711,7 @@ impl ThresholdKeyshare { info!("GenEsiSss on ThresholdKeyshare"); let evt = msg.ciphernode_selected; - let e_sm_raw_decrypted = ArcBytes::from_bytes(&msg.e_sm_raw.access_raw(&self.cipher)?); + let e_sm_raw = msg.e_sm_raw; let CiphernodeSelected { e3_id, .. } = evt.clone(); let state = self From 42f0b42f9820c5b2f58a3b395a6d0b78f3bdd7fa Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Mon, 16 Feb 2026 10:15:48 +0000 Subject: [PATCH 13/23] feat: integrate sk_share/e_sm_share computation --- .../src/enclave_event/compute_request/zk.rs | 6 +++--- crates/keyshare/src/threshold_keyshare.rs | 16 ++++++---------- crates/multithread/src/multithread.rs | 8 ++++---- crates/zk-prover/src/actors/proof_request.rs | 6 +++--- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/crates/events/src/enclave_event/compute_request/zk.rs b/crates/events/src/enclave_event/compute_request/zk.rs index 9d5f88d570..70f560756a 100644 --- a/crates/events/src/enclave_event/compute_request/zk.rs +++ b/crates/events/src/enclave_event/compute_request/zk.rs @@ -17,13 +17,13 @@ use serde::{Deserialize, Serialize}; pub enum ZkRequest { /// Generate proof for BFV public key (C0). PkBfv(PkBfvProofRequest), - /// Generate proof for PK generation (C1). + /// Generate proof for PK generation (T1). PkGeneration(PkGenerationProofRequest), - /// Generate proof for share and esm computation (C2a and C2b). + /// Generate proof for share and esm computation (T2a and T2b). ShareComputation(ShareComputationProofRequest), } -/// Request to generate a proof for share computation (C2a or C2b). +/// Request to generate a proof for share computation (T2a or T2b). #[derive(Derivative, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derivative(Debug)] pub struct ShareComputationProofRequest { diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index cc20ef7ed4..f9dea3b8d5 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -458,7 +458,7 @@ impl ThresholdKeyshare { Ok(()) } - /// Handle ShareComputationProofSigned - stores the signed proof in state based on proof type (C1, C2a or C2b) + /// Handle ShareComputationProofSigned - stores the signed proof in state based on proof type (T1, T2a or T2b) pub fn handle_share_computation_proof_signed( &mut self, msg: TypedEvent, @@ -710,9 +710,8 @@ impl ThresholdKeyshare { let (msg, ec) = msg.into_components(); info!("GenEsiSss on ThresholdKeyshare"); - let evt = msg.ciphernode_selected; let e_sm_raw = msg.e_sm_raw; - let CiphernodeSelected { e3_id, .. } = evt.clone(); + let CiphernodeSelected { e3_id, .. } = msg.ciphernode_selected; let state = self .state @@ -722,13 +721,10 @@ impl ThresholdKeyshare { let trbfv_config = state.get_trbfv_config(); let event = ComputeRequest::trbfv( - TrBFVRequest::GenEsiSss( - GenEsiSssRequest { - trbfv_config, - e_sm_raw: e_sm_raw_decrypted, - } - .into(), - ), + TrBFVRequest::GenEsiSss(GenEsiSssRequest { + trbfv_config, + e_sm_raw, + }), CorrelationId::new(), e3_id, ); diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 319bbe2fb8..41ed5a427b 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -461,13 +461,13 @@ fn handle_share_computation_proof( .map(|arr| arr.mapv(|v| BigInt::from(v))) .collect(); - // 5. Compute parity matrix + // 4. Compute parity matrix let committee = req.committee_size.values(); let parity_matrix = compute_parity_matrix(threshold_params.moduli(), committee.n, committee.threshold) .map_err(|e| make_zk_error(&request, format!("compute_parity_matrix: {}", e)))?; - // 6. Build circuit data + // 5. Build circuit data let circuit_data = ShareComputationCircuitData { dkg_input_type: req.dkg_input_type, secret, @@ -477,7 +477,7 @@ fn handle_share_computation_proof( threshold: committee.threshold as u32, }; - // 7. Generate proof + // 6. Generate proof let circuit = ShareComputationCircuit; let e3_id_str = request.e3_id.to_string(); @@ -490,7 +490,7 @@ fn handle_share_computation_proof( ) })?; - // 8. Return response + // 7. Return response Ok(ComputeResponse::zk( ZkResponse::ShareComputation(ShareComputationProofResponse { proof, diff --git a/crates/zk-prover/src/actors/proof_request.rs b/crates/zk-prover/src/actors/proof_request.rs index c3c91fa453..a78ade263c 100644 --- a/crates/zk-prover/src/actors/proof_request.rs +++ b/crates/zk-prover/src/actors/proof_request.rs @@ -273,7 +273,7 @@ impl ProofRequestActor { ProofType::T1PkGeneration, pending.pk_generation_proof.expect("checked"), ) else { - error!("Failed to sign C1 proof — shares will not be published"); + error!("Failed to sign T1 proof — shares will not be published"); return; }; @@ -282,7 +282,7 @@ impl ProofRequestActor { ProofType::T1SkShareComputation, pending.sk_share_computation_proof.expect("checked"), ) else { - error!("Failed to sign C2a proof — shares will not be published"); + error!("Failed to sign T2a proof — shares will not be published"); return; }; @@ -291,7 +291,7 @@ impl ProofRequestActor { ProofType::T1ESmShareComputation, pending.e_sm_share_computation_proof.expect("checked"), ) else { - error!("Failed to sign C2b proof — shares will not be published"); + error!("Failed to sign T2b proof — shares will not be published"); return; }; From 675500a67c9f88166539ae8b57be83ac84791333 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Mon, 16 Feb 2026 19:39:28 +0000 Subject: [PATCH 14/23] feat: integrate share encryption proofs --- .../src/enclave_event/compute_request/mod.rs | 1 + .../src/enclave_event/compute_request/zk.rs | 49 ++++- crates/events/src/enclave_event/mod.rs | 6 +- .../share_computation_proof_signed.rs | 4 +- .../events/src/enclave_event/signed_proof.rs | 24 ++- crates/events/src/enclave_event/test_event.rs | 2 +- .../enclave_event/threshold_share_pending.rs | 9 +- crates/keyshare/src/threshold_keyshare.rs | 151 ++++++++++--- .../keyshare/src/threshold_share_collector.rs | 2 +- crates/multithread/src/multithread.rs | 79 ++++++- crates/tests/tests/integration.rs | 21 +- crates/trbfv/src/shares/bfv_encrypted.rs | 84 ++++++++ crates/zk-prover/src/actors/proof_request.rs | 199 ++++++++++++++++-- examples/CRISP/server/.env.example | 2 +- examples/CRISP/test/crisp.spec.ts | 4 +- templates/default/tests/integration.spec.ts | 2 +- 16 files changed, 568 insertions(+), 71 deletions(-) diff --git a/crates/events/src/enclave_event/compute_request/mod.rs b/crates/events/src/enclave_event/compute_request/mod.rs index 00d31b8c9e..85fa352f7c 100644 --- a/crates/events/src/enclave_event/compute_request/mod.rs +++ b/crates/events/src/enclave_event/compute_request/mod.rs @@ -84,6 +84,7 @@ impl ToString for ComputeRequest { ZkRequest::PkBfv(_) => "ZkPkBfv", ZkRequest::PkGeneration(_) => "ZkPkGeneration", ZkRequest::ShareComputation(_) => "ZkShareComputation", + ZkRequest::ShareEncryption(_) => "ZkShareEncryption", }, } .to_string() diff --git a/crates/events/src/enclave_event/compute_request/zk.rs b/crates/events/src/enclave_event/compute_request/zk.rs index 70f560756a..695af699e1 100644 --- a/crates/events/src/enclave_event/compute_request/zk.rs +++ b/crates/events/src/enclave_event/compute_request/zk.rs @@ -21,6 +21,8 @@ pub enum ZkRequest { PkGeneration(PkGenerationProofRequest), /// Generate proof for share and esm computation (T2a and T2b). ShareComputation(ShareComputationProofRequest), + /// Generate proof for share encryption (C3a/C3b). + ShareEncryption(ShareEncryptionProofRequest), } /// Request to generate a proof for share computation (T2a or T2b). @@ -39,7 +41,41 @@ pub struct ShareComputationProofRequest { pub committee_size: CiphernodesCommitteeSize, } -/// Request to generate a proof for BFV public key generation (C0). +/// Request to generate a proof for share encryption (C3a or C3b). +#[derive(Derivative, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derivative(Debug)] +pub struct ShareEncryptionProofRequest { + /// Bincode-serialized Vec share row coefficients. + #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] + pub share_row_raw: ArcBytes, + /// Serialized BFV Ciphertext bytes (via fhe_traits::Serialize). + #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] + pub ciphertext_raw: ArcBytes, + /// Serialized recipient BFV PublicKey bytes. + #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] + pub recipient_pk_raw: ArcBytes, + /// Serialized u_rns Poly bytes. + #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] + pub u_rns_raw: ArcBytes, + /// Serialized e0_rns Poly bytes. + #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] + pub e0_rns_raw: ArcBytes, + /// Serialized e1_rns Poly bytes. + #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] + pub e1_rns_raw: ArcBytes, + /// SecretKey or SmudgingNoise. + pub dkg_input_type: DkgInputType, + /// Threshold BFV preset (handler derives DKG params via build_pair_for_preset). + pub params_preset: BfvPreset, + /// Committee size. + pub committee_size: CiphernodesCommitteeSize, + /// Recipient index (for correlation tracking). + pub recipient_party_id: usize, + /// Modulus row index (for correlation tracking). + pub row_index: usize, +} + +/// Request to generate a proof for BFV public key generation (T0). #[derive(Derivative, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derivative(Debug)] pub struct PkBfvProofRequest { @@ -106,6 +142,8 @@ pub enum ZkResponse { PkGeneration(PkGenerationProofResponse), /// Proof for share and esm computation (T2a and T2b). ShareComputation(ShareComputationProofResponse), + /// Proof for share encryption (C3a/C3b). + ShareEncryption(ShareEncryptionProofResponse), } /// Response containing a generated share computation proof. @@ -115,6 +153,15 @@ pub struct ShareComputationProofResponse { pub dkg_input_type: DkgInputType, } +/// Response containing a generated share encryption proof. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct ShareEncryptionProofResponse { + pub proof: Proof, + pub dkg_input_type: DkgInputType, + pub recipient_party_id: usize, + pub row_index: usize, +} + /// Response containing a generated BFV public key proof. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct PkBfvProofResponse { diff --git a/crates/events/src/enclave_event/mod.rs b/crates/events/src/enclave_event/mod.rs index 004bfd1924..30ecdde43f 100644 --- a/crates/events/src/enclave_event/mod.rs +++ b/crates/events/src/enclave_event/mod.rs @@ -217,7 +217,7 @@ pub enum EnclaveEventData { TicketSubmitted(TicketSubmitted), PlaintextOutputPublished(PlaintextOutputPublished), PkGenerationProofSigned(PkGenerationProofSigned), - ShareComputationProofSigned(ShareComputationProofSigned), + DkgProofSigned(DkgProofSigned), EnclaveError(EnclaveError), E3RequestComplete(E3RequestComplete), E3Failed(E3Failed), @@ -471,7 +471,7 @@ impl EnclaveEventData { EnclaveEventData::DecryptionshareCreated(ref data) => Some(data.e3_id.clone()), EnclaveEventData::PlaintextAggregated(ref data) => Some(data.e3_id.clone()), EnclaveEventData::PkGenerationProofSigned(ref data) => Some(data.e3_id.clone()), - EnclaveEventData::ShareComputationProofSigned(ref data) => Some(data.e3_id.clone()), + EnclaveEventData::DkgProofSigned(ref data) => Some(data.e3_id.clone()), EnclaveEventData::CiphernodeSelected(ref data) => Some(data.e3_id.clone()), EnclaveEventData::ThresholdShareCreated(ref data) => Some(data.e3_id.clone()), EnclaveEventData::ThresholdSharePending(ref data) => Some(data.e3_id.clone()), @@ -523,7 +523,7 @@ impl_event_types!( PlaintextAggregated, PublishDocumentRequested, PkGenerationProofSigned, - ShareComputationProofSigned, + DkgProofSigned, E3RequestComplete, E3Failed, E3StageChanged, diff --git a/crates/events/src/enclave_event/share_computation_proof_signed.rs b/crates/events/src/enclave_event/share_computation_proof_signed.rs index 0f417ec797..6fa2922211 100644 --- a/crates/events/src/enclave_event/share_computation_proof_signed.rs +++ b/crates/events/src/enclave_event/share_computation_proof_signed.rs @@ -11,13 +11,13 @@ use std::fmt::{self, Display}; #[derive(Message, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[rtype(result = "()")] -pub struct ShareComputationProofSigned { +pub struct DkgProofSigned { pub e3_id: E3id, pub party_id: u64, pub signed_proof: SignedProofPayload, } -impl Display for ShareComputationProofSigned { +impl Display for DkgProofSigned { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } diff --git a/crates/events/src/enclave_event/signed_proof.rs b/crates/events/src/enclave_event/signed_proof.rs index 4834144cb7..a667e9870c 100644 --- a/crates/events/src/enclave_event/signed_proof.rs +++ b/crates/events/src/enclave_event/signed_proof.rs @@ -37,14 +37,16 @@ pub enum ProofType { T1SkShareComputation = 2, /// T1 — Smudging noise share computation proof (Proof 2b). T1ESmShareComputation = 3, - /// T1 — Share encryption proof (Proof 3). - T1ShareEncryption = 4, + /// T1 — Share encryption proof (Proof 3a). + T1SkShareEncryption = 4, + /// T1 — Smudging noise Share encryption proof (Proof 3b). + T1ESmShareEncryption = 5, /// T2 — DKG share decryption proof (Proof 4). - T2DkgShareDecryption = 5, + T2DkgShareDecryption = 6, /// T5 — Threshold share decryption proof (Proof 6). - T5ShareDecryption = 6, + T5ShareDecryption = 7, /// T6 — Decrypted shares aggregation proof (Proof 7). - T6DecryptedSharesAggregation = 7, + T6DecryptedSharesAggregation = 8, } impl ProofType { @@ -55,7 +57,8 @@ impl ProofType { ProofType::T1PkGeneration => vec![CircuitName::PkGeneration], ProofType::T1SkShareComputation => vec![CircuitName::SkShareComputation], ProofType::T1ESmShareComputation => vec![CircuitName::ESmShareComputation], - ProofType::T1ShareEncryption => vec![CircuitName::ShareEncryption], + ProofType::T1SkShareEncryption => vec![CircuitName::ShareEncryption], + ProofType::T1ESmShareEncryption => vec![CircuitName::ShareEncryption], ProofType::T2DkgShareDecryption => vec![CircuitName::DkgShareDecryption], ProofType::T5ShareDecryption => vec![CircuitName::ThresholdShareDecryption], ProofType::T6DecryptedSharesAggregation => vec![ @@ -72,7 +75,8 @@ impl ProofType { | ProofType::T1PkGeneration | ProofType::T1SkShareComputation | ProofType::T1ESmShareComputation - | ProofType::T1ShareEncryption + | ProofType::T1SkShareEncryption + | ProofType::T1ESmShareEncryption | ProofType::T2DkgShareDecryption => "E3_BAD_DKG_PROOF", ProofType::T5ShareDecryption => "E3_BAD_DECRYPTION_PROOF", ProofType::T6DecryptedSharesAggregation => "E3_BAD_AGGREGATION_PROOF", @@ -302,7 +306,11 @@ mod tests { vec![CircuitName::PkGeneration] ); assert_eq!( - ProofType::T1ShareEncryption.circuit_names(), + ProofType::T1SkShareEncryption.circuit_names(), + vec![CircuitName::ShareEncryption] + ); + assert_eq!( + ProofType::T1ESmShareEncryption.circuit_names(), vec![CircuitName::ShareEncryption] ); assert_eq!( diff --git a/crates/events/src/enclave_event/test_event.rs b/crates/events/src/enclave_event/test_event.rs index e38603589d..b037db3b62 100644 --- a/crates/events/src/enclave_event/test_event.rs +++ b/crates/events/src/enclave_event/test_event.rs @@ -36,7 +36,7 @@ impl TestEvent { #[cfg(test)] use std::fmt::{self, Display}; -use crate::{AggregateId, E3id}; +use crate::E3id; #[cfg(test)] impl Display for TestEvent { diff --git a/crates/events/src/enclave_event/threshold_share_pending.rs b/crates/events/src/enclave_event/threshold_share_pending.rs index 0f39aee7e0..e4df46e541 100644 --- a/crates/events/src/enclave_event/threshold_share_pending.rs +++ b/crates/events/src/enclave_event/threshold_share_pending.rs @@ -4,7 +4,10 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use crate::{E3id, PkGenerationProofRequest, ShareComputationProofRequest, ThresholdShare}; +use crate::{ + E3id, PkGenerationProofRequest, ShareComputationProofRequest, ShareEncryptionProofRequest, + ThresholdShare, +}; use actix::Message; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display}; @@ -22,6 +25,10 @@ pub struct ThresholdSharePending { pub sk_share_computation_request: ShareComputationProofRequest, /// The proof request for C2b (ESmShareComputation) pub e_sm_share_computation_request: ShareComputationProofRequest, + /// C3a: SK share encryption proof requests (one per recipient per modulus row) + pub sk_share_encryption_requests: Vec, + /// C3b: E_SM share encryption proof requests (per ESI, per recipient, per modulus row) + pub e_sm_share_encryption_requests: Vec, } impl Display for ThresholdSharePending { diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index f9dea3b8d5..29ee208ae9 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -11,15 +11,15 @@ use e3_data::Persistable; use e3_events::{ prelude::*, trap, BusHandle, CiphernodeSelected, CiphertextOutputPublished, ComputeRequest, ComputeResponse, ComputeResponseKind, CorrelationId, DecryptionshareCreated, Die, - E3RequestComplete, E3id, EType, EnclaveEvent, EnclaveEventData, EncryptionKey, + DkgProofSigned, E3RequestComplete, E3id, EType, EnclaveEvent, EnclaveEventData, EncryptionKey, EncryptionKeyCollectionFailed, EncryptionKeyCreated, EncryptionKeyPending, EventContext, KeyshareCreated, PartyId, PkGenerationProofRequest, PkGenerationProofSigned, ProofType, - Sequenced, ShareComputationProofRequest, ShareComputationProofSigned, SignedProofPayload, + Sequenced, ShareComputationProofRequest, ShareEncryptionProofRequest, SignedProofPayload, ThresholdShare, ThresholdShareCollectionFailed, ThresholdShareCreated, ThresholdSharePending, TypedEvent, }; use e3_fhe_params::create_deterministic_crp_from_default_seed; -use e3_fhe_params::{BfvParamSet, BfvPreset}; +use e3_fhe_params::{build_pair_for_preset, BfvParamSet, BfvPreset}; use e3_trbfv::{ calculate_decryption_key::{CalculateDecryptionKeyRequest, CalculateDecryptionKeyResponse}, calculate_decryption_share::{ @@ -108,6 +108,8 @@ pub struct AggregatingDecryptionKey { signed_pk_generation_proof: Option, signed_sk_share_computation_proof: Option, signed_e_sm_share_computation_proof: Option, + signed_sk_share_encryption_proofs: Vec, + signed_e_sm_share_encryption_proofs: Vec, } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] @@ -118,6 +120,8 @@ pub struct ReadyForDecryption { signed_pk_generation_proof: Option, signed_sk_share_computation_proof: Option, signed_e_sm_share_computation_proof: Option, + signed_sk_share_encryption_proofs: Vec, + signed_e_sm_share_encryption_proofs: Vec, } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] @@ -128,6 +132,8 @@ pub struct Decrypting { signed_pk_generation_proof: Option, signed_sk_share_computation_proof: Option, signed_e_sm_share_computation_proof: Option, + signed_sk_share_encryption_proofs: Vec, + signed_e_sm_share_encryption_proofs: Vec, } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] @@ -458,10 +464,10 @@ impl ThresholdKeyshare { Ok(()) } - /// Handle ShareComputationProofSigned - stores the signed proof in state based on proof type (T1, T2a or T2b) + /// Handle DkgProofSigned - stores the signed proof in state based on proof type (C2a, C2b, C3a or C3b) pub fn handle_share_computation_proof_signed( &mut self, - msg: TypedEvent, + msg: TypedEvent, ) -> Result<()> { let (msg, ec) = msg.into_components(); let state = self.state.try_get()?; @@ -472,7 +478,7 @@ impl ThresholdKeyshare { let proof_type = msg.signed_proof.payload.proof_type; info!( - "Received ShareComputationProofSigned ({:?}) for party {} E3 {}", + "Received DkgProofSigned ({:?}) for party {} E3 {}", proof_type, msg.party_id, msg.e3_id ); @@ -487,11 +493,22 @@ impl ThresholdKeyshare { signed_e_sm_share_computation_proof: Some(msg.signed_proof), ..current }, + ProofType::T1SkShareEncryption => { + let mut updated = current; + updated + .signed_sk_share_encryption_proofs + .push(msg.signed_proof); + updated + } + ProofType::T1ESmShareEncryption => { + let mut updated = current; + updated + .signed_e_sm_share_encryption_proofs + .push(msg.signed_proof); + updated + } other => { - warn!( - "Unexpected proof type {:?} in ShareComputationProofSigned", - other - ); + warn!("Unexpected proof type {:?} in DkgProofSigned", other); current } }; @@ -776,6 +793,8 @@ impl ThresholdKeyshare { signed_pk_generation_proof: None, signed_sk_share_computation_proof: None, signed_e_sm_share_computation_proof: None, + signed_sk_share_encryption_proofs: Vec::new(), + signed_e_sm_share_encryption_proofs: Vec::new(), }, )) })?; @@ -807,8 +826,12 @@ impl ThresholdKeyshare { // Get collected BFV public keys from all parties (from persisted state) let encryption_keys = &collected_encryption_keys; - // Convert to BFV public keys - let params = BfvParamSet::from(self.share_enc_preset.clone()).build_arc(); + // Convert to BFV public keys using DKG params + let threshold_preset = self + .share_enc_preset + .threshold_counterpart() + .ok_or_else(|| anyhow!("No threshold counterpart for {:?}", self.share_enc_preset))?; + let (_, params) = build_pair_for_preset(threshold_preset)?; let recipient_pks: Vec = encryption_keys .iter() .map(|k| { @@ -839,15 +862,23 @@ impl ThresholdKeyshare { }) .collect::>()?; - // Encrypt shares for all recipients using BFV + // Encrypt shares for all recipients using BFV (extended to capture randomness for C3 proofs) let mut rng = OsRng; - let encrypted_sk_sss = - BfvEncryptedShares::encrypt_all(&decrypted_sk_sss, &recipient_pks, ¶ms, &mut rng)?; + let (encrypted_sk_sss, sk_witnesses) = BfvEncryptedShares::encrypt_all_extended( + &decrypted_sk_sss, + &recipient_pks, + ¶ms, + &mut rng, + )?; - let encrypted_esi_sss: Vec = decrypted_esi_sss + let (encrypted_esi_sss, esi_witnesses): (Vec<_>, Vec<_>) = decrypted_esi_sss .iter() - .map(|esi| BfvEncryptedShares::encrypt_all(esi, &recipient_pks, ¶ms, &mut rng)) - .collect::>()?; + .map(|esi| { + BfvEncryptedShares::encrypt_all_extended(esi, &recipient_pks, ¶ms, &mut rng) + }) + .collect::>>()? + .into_iter() + .unzip(); // Create the full share with all parties' encrypted data let full_share = ThresholdShare { @@ -857,13 +888,7 @@ impl ThresholdKeyshare { esi_sss: encrypted_esi_sss, }; - // Build the proof request - let threshold_preset = self - .share_enc_preset - .threshold_counterpart() - .ok_or_else(|| anyhow!("No threshold counterpart for {:?}", self.share_enc_preset))?; - - // Build T1 request (PkGenerationProof) + // Build C1 request (PkGenerationProof) let proof_request = PkGenerationProofRequest::new( proof_request_data.pk0_share_raw.clone(), proof_request_data.sk_raw.clone(), @@ -873,16 +898,16 @@ impl ThresholdKeyshare { CiphernodesCommitteeSize::Small, // TODO: derive from config ); - // Build T2a request (SkShareComputation) + // Build C2a request (SkShareComputation) let sk_share_computation_request = ShareComputationProofRequest { secret_raw: proof_request_data.sk_raw.clone(), secret_sss_raw: sk_sss_raw, dkg_input_type: DkgInputType::SecretKey, params_preset: threshold_preset, - committee_size: CiphernodesCommitteeSize::Small, + committee_size: CiphernodesCommitteeSize::Small, // TODO: derive from config }; - // Build T2b request (ESmShareComputation) + // Build C2b request (ESmShareComputation) let e_sm_share_computation_request = ShareComputationProofRequest { secret_raw: e_sm_raw.clone(), secret_sss_raw: esi_sss_raw @@ -891,12 +916,68 @@ impl ThresholdKeyshare { .ok_or_else(|| anyhow!("esi_sss_raw is empty — expected at least one entry"))?, dkg_input_type: DkgInputType::SmudgingNoise, params_preset: threshold_preset, - committee_size: CiphernodesCommitteeSize::Small, + committee_size: CiphernodesCommitteeSize::Small, // TODO: derive from config }; + // Build C3a proof requests (SK share encryption) from witnesses + let mut sk_share_encryption_requests = Vec::new(); + for (recipient_idx, recipient_witnesses) in sk_witnesses.iter().enumerate() { + for (row_idx, witness) in recipient_witnesses.iter().enumerate() { + sk_share_encryption_requests.push(ShareEncryptionProofRequest { + share_row_raw: ArcBytes::from_bytes( + &bincode::serialize(&witness.share_row) + .map_err(|e| anyhow!("Failed to serialize share_row: {}", e))?, + ), + ciphertext_raw: ArcBytes::from_bytes(&witness.ciphertext.to_bytes()), + recipient_pk_raw: ArcBytes::from_bytes( + &recipient_pks[recipient_idx].to_bytes(), + ), + u_rns_raw: ArcBytes::from_bytes(&witness.u_rns.to_bytes()), + e0_rns_raw: ArcBytes::from_bytes(&witness.e0_rns.to_bytes()), + e1_rns_raw: ArcBytes::from_bytes(&witness.e1_rns.to_bytes()), + dkg_input_type: DkgInputType::SecretKey, + params_preset: threshold_preset, + committee_size: CiphernodesCommitteeSize::Small, + recipient_party_id: recipient_idx, + row_index: row_idx, + }); + } + } + + // Build C3b proof requests (E_SM share encryption) from witnesses + let mut e_sm_share_encryption_requests = Vec::new(); + for esi_recipient_witnesses in esi_witnesses.iter() { + for (recipient_idx, recipient_witnesses) in esi_recipient_witnesses.iter().enumerate() { + for (row_idx, witness) in recipient_witnesses.iter().enumerate() { + e_sm_share_encryption_requests.push(ShareEncryptionProofRequest { + share_row_raw: ArcBytes::from_bytes( + &bincode::serialize(&witness.share_row) + .map_err(|e| anyhow!("Failed to serialize share_row: {}", e))?, + ), + ciphertext_raw: ArcBytes::from_bytes(&witness.ciphertext.to_bytes()), + recipient_pk_raw: ArcBytes::from_bytes( + &recipient_pks[recipient_idx].to_bytes(), + ), + u_rns_raw: ArcBytes::from_bytes(&witness.u_rns.to_bytes()), + e0_rns_raw: ArcBytes::from_bytes(&witness.e0_rns.to_bytes()), + e1_rns_raw: ArcBytes::from_bytes(&witness.e1_rns.to_bytes()), + dkg_input_type: DkgInputType::SmudgingNoise, + params_preset: threshold_preset, + committee_size: CiphernodesCommitteeSize::Small, + recipient_party_id: recipient_idx, + row_index: row_idx, + }); + } + } + } + + let total_proofs = + 3 + sk_share_encryption_requests.len() + e_sm_share_encryption_requests.len(); info!( - "Publishing ThresholdSharePending for E3 {} (3 proofs: T1, T2a, T2b)", - e3_id + "Publishing ThresholdSharePending for E3 {} ({} proofs: T1, T2a, T2b + {} C3a + {} C3b)", + e3_id, total_proofs, + sk_share_encryption_requests.len(), + e_sm_share_encryption_requests.len() ); // Publish ThresholdSharePending - ProofRequestActor will generate proof, sign, and publish ThresholdShareCreated @@ -907,6 +988,8 @@ impl ThresholdKeyshare { proof_request, sk_share_computation_request, e_sm_share_computation_request, + sk_share_encryption_requests, + e_sm_share_encryption_requests, }, ec.clone(), )?; @@ -1013,6 +1096,8 @@ impl ThresholdKeyshare { signed_pk_generation_proof: current.signed_pk_generation_proof, signed_sk_share_computation_proof: current.signed_sk_share_computation_proof, signed_e_sm_share_computation_proof: current.signed_e_sm_share_computation_proof, + signed_sk_share_encryption_proofs: current.signed_sk_share_encryption_proofs, + signed_e_sm_share_encryption_proofs: current.signed_e_sm_share_encryption_proofs, }); s.new_state(next) @@ -1055,6 +1140,8 @@ impl ThresholdKeyshare { signed_pk_generation_proof: current.signed_pk_generation_proof, signed_sk_share_computation_proof: current.signed_sk_share_computation_proof, signed_e_sm_share_computation_proof: current.signed_e_sm_share_computation_proof, + signed_sk_share_encryption_proofs: current.signed_sk_share_encryption_proofs, + signed_e_sm_share_encryption_proofs: current.signed_e_sm_share_encryption_proofs, }); s.new_state(next) @@ -1141,7 +1228,7 @@ impl Handler for ThresholdKeyshare { EnclaveEventData::PkGenerationProofSigned(data) => { let _ = self.handle_pk_generation_proof_signed(TypedEvent::new(data, ec)); } - EnclaveEventData::ShareComputationProofSigned(data) => { + EnclaveEventData::DkgProofSigned(data) => { let _ = self.handle_share_computation_proof_signed(TypedEvent::new(data, ec)); } EnclaveEventData::E3RequestComplete(data) => self.notify_sync(ctx, data), diff --git a/crates/keyshare/src/threshold_share_collector.rs b/crates/keyshare/src/threshold_share_collector.rs index ae1167352f..f83240bba1 100644 --- a/crates/keyshare/src/threshold_share_collector.rs +++ b/crates/keyshare/src/threshold_share_collector.rs @@ -20,7 +20,7 @@ use tracing::{info, warn}; use crate::{AllThresholdSharesCollected, ThresholdKeyshare}; -const DEFAULT_COLLECTION_TIMEOUT: Duration = Duration::from_secs(120); +const DEFAULT_COLLECTION_TIMEOUT: Duration = Duration::from_secs(600); pub(crate) enum CollectorState { Collecting, diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 41ed5a427b..0964a03e14 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -25,8 +25,8 @@ use e3_events::{ BusHandle, ComputeRequest, ComputeRequestError, ComputeRequestErrorKind, ComputeRequestKind, ComputeResponse, EnclaveEvent, EnclaveEventData, EventPublisher, EventSubscriber, EventType, PkBfvProofRequest, PkBfvProofResponse, PkGenerationProofRequest, PkGenerationProofResponse, - ShareComputationProofRequest, ShareComputationProofResponse, TypedEvent, - ZkError as ZkEventError, ZkRequest, ZkResponse, + ShareComputationProofRequest, ShareComputationProofResponse, ShareEncryptionProofRequest, + ShareEncryptionProofResponse, TypedEvent, ZkError as ZkEventError, ZkRequest, ZkResponse, }; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::{BfvParamSet, BfvPreset}; @@ -48,11 +48,13 @@ use e3_zk_helpers::circuits::threshold::pk_generation::circuit::{ }; use e3_zk_helpers::computation::DkgInputType; use e3_zk_helpers::dkg::share_computation::{ShareComputationCircuit, ShareComputationCircuitData}; +use e3_zk_helpers::dkg::share_encryption::{ShareEncryptionCircuit, ShareEncryptionCircuitData}; use e3_zk_prover::{Provable, ZkBackend, ZkProver}; -use fhe::bfv::PublicKey; -use fhe_traits::DeserializeParametrized; +use fhe::bfv::{Ciphertext, Encoding, Plaintext, PublicKey, SecretKey}; +use fhe_traits::{DeserializeParametrized, FheEncoder}; use ndarray::Array2; use num_bigint::BigInt; +use rand::rngs::OsRng; use rand::Rng; use tracing::{error, info}; @@ -409,6 +411,9 @@ fn handle_zk_request( ZkRequest::ShareComputation(req) => timefunc("zk_share_computation", id, || { handle_share_computation_proof(&prover, &cipher, req, request.clone()) }), + ZkRequest::ShareEncryption(req) => timefunc("zk_share_encryption", id, || { + handle_share_encryption_proof(&prover, req, request.clone()) + }), } } @@ -616,3 +621,69 @@ fn handle_pk_bfv_proof( request.e3_id, )) } + +fn handle_share_encryption_proof( + prover: &ZkProver, + req: ShareEncryptionProofRequest, + request: ComputeRequest, +) -> Result { + // 1. Build DKG params from threshold preset + let (_threshold_params, dkg_params) = build_pair_for_preset(req.params_preset) + .map_err(|e| make_zk_error(&request, format!("build_pair_for_preset: {}", e)))?; + + // 2. Deserialize share row and re-encode as Plaintext + let share_row: Vec = bincode::deserialize(&req.share_row_raw) + .map_err(|e| make_zk_error(&request, format!("share_row: {}", e)))?; + let plaintext = Plaintext::try_encode(&share_row, Encoding::poly(), &dkg_params) + .map_err(|e| make_zk_error(&request, format!("plaintext encode: {:?}", e)))?; + + // 3. Deserialize ciphertext, public key, polys using DKG params + let ciphertext = Ciphertext::from_bytes(&req.ciphertext_raw, &dkg_params) + .map_err(|e| make_zk_error(&request, format!("ciphertext: {:?}", e)))?; + let public_key = PublicKey::from_bytes(&req.recipient_pk_raw, &dkg_params) + .map_err(|e| make_zk_error(&request, format!("recipient_pk: {:?}", e)))?; + let u_rns = try_poly_from_bytes(&req.u_rns_raw, &dkg_params) + .map_err(|e| make_zk_error(&request, format!("u_rns: {}", e)))?; + let e0_rns = try_poly_from_bytes(&req.e0_rns_raw, &dkg_params) + .map_err(|e| make_zk_error(&request, format!("e0_rns: {}", e)))?; + let e1_rns = try_poly_from_bytes(&req.e1_rns_raw, &dkg_params) + .map_err(|e| make_zk_error(&request, format!("e1_rns: {}", e)))?; + + // 4. Dummy SecretKey (not used by Inputs::compute) + let dummy_sk = SecretKey::random(&dkg_params, &mut OsRng); + + // 5. Build circuit data + let circuit_data = ShareEncryptionCircuitData { + plaintext, + ciphertext, + public_key, + secret_key: dummy_sk, + u_rns, + e0_rns, + e1_rns, + dkg_input_type: req.dkg_input_type, + }; + + // 6. Generate proof (preset = threshold preset; Inputs::compute derives DKG internally) + let circuit = ShareEncryptionCircuit; + let e3_id_str = request.e3_id.to_string(); + let proof = circuit + .prove(prover, &req.params_preset, &circuit_data, &e3_id_str) + .map_err(|e| { + ComputeRequestError::new( + ComputeRequestErrorKind::Zk(ZkEventError::ProofGenerationFailed(e.to_string())), + request.clone(), + ) + })?; + + Ok(ComputeResponse::zk( + ZkResponse::ShareEncryption(ShareEncryptionProofResponse { + proof, + dkg_input_type: req.dkg_input_type, + recipient_party_id: req.recipient_party_id, + row_index: req.row_index, + }), + request.correlation_id, + request.e3_id, + )) +} diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index df7f6af648..6ce0d530b0 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -168,7 +168,26 @@ async fn setup_test_zk_backend() -> (ZkBackend, tempfile::TempDir) { .await .unwrap(); + // Copy C3 (share_encryption) circuit — single circuit used for both SK and E_SM + let share_enc_circuit_dir = circuits_dir.join("dkg").join("share_encryption"); + tokio::fs::create_dir_all(&share_enc_circuit_dir) + .await + .unwrap(); + tokio::fs::copy( + dkg_target.join("share_encryption.json"), + share_enc_circuit_dir.join("share_encryption.json"), + ) + .await + .unwrap(); + tokio::fs::copy( + dkg_target.join("share_encryption.vk"), + share_enc_circuit_dir.join("share_encryption.vk"), + ) + .await + .unwrap(); + let backend = ZkBackend::new(BBPath::Default(bb_binary), circuits_dir, work_dir); + (backend, temp) } else { println!("bb binary not found locally, downloading via ensure_installed()..."); @@ -357,7 +376,7 @@ async fn test_trbfv_actor() -> Result<()> { // round information let threshold_m = 2; let threshold_n = 5; - let esi_per_ct = 3; + let esi_per_ct = 1; // WARNING: INSECURE SECURITY PARAMETER LAMBDA. // This is just for INSECURE parameter set. diff --git a/crates/trbfv/src/shares/bfv_encrypted.rs b/crates/trbfv/src/shares/bfv_encrypted.rs index 3eed984313..932bf16a59 100644 --- a/crates/trbfv/src/shares/bfv_encrypted.rs +++ b/crates/trbfv/src/shares/bfv_encrypted.rs @@ -8,6 +8,7 @@ use anyhow::{Context, Result}; use derivative::Derivative; use e3_utils::utility_types::ArcBytes; use fhe::bfv::{BfvParameters, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey}; +use fhe_math::rq::Poly; use fhe_traits::{ DeserializeParametrized, FheDecoder, FheDecrypter, FheEncoder, FheEncrypter, Serialize as FheSerialize, @@ -35,6 +36,20 @@ pub struct BfvEncryptedShare { ciphertexts: Vec, } +/// Per-row encryption witness needed for C3 ZK proofs. +pub struct BfvEncryptionWitness { + /// The share row coefficients that were encrypted. + pub share_row: Vec, + /// The BFV ciphertext produced. + pub ciphertext: Ciphertext, + /// Encryption randomness u (RNS form). + pub u_rns: Poly, + /// Encryption error e0 (RNS form). + pub e0_rns: Poly, + /// Encryption error e1 (RNS form). + pub e1_rns: Poly, +} + /// Debug helper for Vec fn debug_vec_arcbytes(v: &Vec, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( @@ -84,6 +99,46 @@ impl BfvEncryptedShare { Ok(Self { ciphertexts }) } + /// Encrypt a Shamir share and return encryption randomness for ZK proofs. + /// + /// Same as `encrypt` but uses `try_encrypt_extended` to capture the + /// encryption randomness (u, e0, e1) needed for C3a/C3b share encryption proofs. + pub fn encrypt_extended( + share: &ShamirShare, + recipient_pk: &PublicKey, + params: &Arc, + rng: &mut R, + ) -> Result<(Self, Vec)> { + let data: &Array2 = share.deref(); + let num_moduli = data.nrows(); + + let mut ciphertexts = Vec::with_capacity(num_moduli); + let mut witnesses = Vec::with_capacity(num_moduli); + + for m in 0..num_moduli { + let row = data.row(m); + let share_vec: Vec = row.to_vec(); + + let pt = Plaintext::try_encode(&share_vec, Encoding::poly(), params) + .context("Failed to encode share as plaintext")?; + + let (ct, u_rns, e0_rns, e1_rns) = recipient_pk + .try_encrypt_extended(&pt, rng) + .context("Failed to encrypt share (extended)")?; + + ciphertexts.push(ArcBytes::from_bytes(&ct.to_bytes())); + witnesses.push(BfvEncryptionWitness { + share_row: share_vec, + ciphertext: ct, + u_rns, + e0_rns, + e1_rns, + }); + } + + Ok((Self { ciphertexts }, witnesses)) + } + /// Decrypt an encrypted share using the recipient's secret key. /// /// # Arguments @@ -173,6 +228,35 @@ impl BfvEncryptedShares { Ok(Self { shares }) } + /// Encrypt shares for all recipients and return encryption randomness for ZK proofs. + /// + /// Same as `encrypt_all` but captures encryption randomness (u, e0, e1) per row per recipient. + /// Returns `(encrypted_shares, witnesses)` where `witnesses[recipient_idx][row_idx]`. + pub fn encrypt_all_extended( + secret: &SharedSecret, + recipient_pks: &[PublicKey], + params: &Arc, + rng: &mut R, + ) -> Result<(Self, Vec>)> { + let num_parties = recipient_pks.len(); + let mut shares = Vec::with_capacity(num_parties); + let mut all_witnesses = Vec::with_capacity(num_parties); + + for party_id in 0..num_parties { + let share = secret + .extract_party_share(party_id) + .context(format!("Failed to extract share for party {}", party_id))?; + + let (encrypted, witnesses) = + BfvEncryptedShare::encrypt_extended(&share, &recipient_pks[party_id], params, rng)?; + + shares.push(encrypted); + all_witnesses.push(witnesses); + } + + Ok((Self { shares }, all_witnesses)) + } + /// Get the encrypted share for a specific recipient. pub fn get_share(&self, party_id: usize) -> Option<&BfvEncryptedShare> { self.shares.get(party_id) diff --git a/crates/zk-prover/src/actors/proof_request.rs b/crates/zk-prover/src/actors/proof_request.rs index a78ade263c..79c9cd6c53 100644 --- a/crates/zk-prover/src/actors/proof_request.rs +++ b/crates/zk-prover/src/actors/proof_request.rs @@ -11,11 +11,11 @@ use actix::{Actor, Addr, Context, Handler}; use alloy::signers::local::PrivateKeySigner; use e3_events::{ BusHandle, ComputeRequest, ComputeRequestError, ComputeRequestErrorKind, ComputeResponse, - ComputeResponseKind, CorrelationId, E3id, EnclaveEvent, EnclaveEventData, EncryptionKey, - EncryptionKeyCreated, EncryptionKeyPending, EventContext, EventPublisher, EventSubscriber, - EventType, PkBfvProofRequest, PkGenerationProofSigned, Proof, ProofPayload, ProofType, - Sequenced, ShareComputationProofSigned, SignedProofPayload, ThresholdShare, - ThresholdShareCreated, ThresholdSharePending, TypedEvent, ZkRequest, ZkResponse, + ComputeResponseKind, CorrelationId, DkgProofSigned, E3id, EnclaveEvent, EnclaveEventData, + EncryptionKey, EncryptionKeyCreated, EncryptionKeyPending, EventContext, EventPublisher, + EventSubscriber, EventType, PkBfvProofRequest, PkGenerationProofSigned, Proof, ProofPayload, + ProofType, Sequenced, SignedProofPayload, ThresholdShare, ThresholdShareCreated, + ThresholdSharePending, TypedEvent, ZkRequest, ZkResponse, }; use e3_utils::NotifySync; use tracing::{error, info}; @@ -25,6 +25,14 @@ enum ThresholdProofKind { PkGeneration, SkShareComputation, ESmShareComputation, + SkShareEncryption { + recipient_party_id: usize, + row_index: usize, + }, + ESmShareEncryption { + recipient_party_id: usize, + row_index: usize, + }, } #[derive(Clone, Debug)] @@ -41,10 +49,22 @@ struct PendingThresholdProofs { pk_generation_proof: Option, sk_share_computation_proof: Option, e_sm_share_computation_proof: Option, + /// C3a proofs: keyed by (recipient_party_id, row_index) + sk_share_encryption_proofs: HashMap<(usize, usize), Proof>, + expected_sk_enc_count: usize, + /// C3b proofs: keyed by (recipient_party_id, row_index) + e_sm_share_encryption_proofs: HashMap<(usize, usize), Proof>, + expected_e_sm_enc_count: usize, } impl PendingThresholdProofs { - fn new(e3_id: E3id, full_share: Arc, ec: EventContext) -> Self { + fn new( + e3_id: E3id, + full_share: Arc, + ec: EventContext, + expected_sk_enc_count: usize, + expected_e_sm_enc_count: usize, + ) -> Self { Self { e3_id, full_share, @@ -52,6 +72,10 @@ impl PendingThresholdProofs { pk_generation_proof: None, sk_share_computation_proof: None, e_sm_share_computation_proof: None, + sk_share_encryption_proofs: HashMap::new(), + expected_sk_enc_count, + e_sm_share_encryption_proofs: HashMap::new(), + expected_e_sm_enc_count, } } @@ -59,6 +83,8 @@ impl PendingThresholdProofs { self.pk_generation_proof.is_some() && self.sk_share_computation_proof.is_some() && self.e_sm_share_computation_proof.is_some() + && self.sk_share_encryption_proofs.len() == self.expected_sk_enc_count + && self.e_sm_share_encryption_proofs.len() == self.expected_e_sm_enc_count } fn store_proof(&mut self, kind: &ThresholdProofKind, proof: Proof) { @@ -68,8 +94,38 @@ impl PendingThresholdProofs { ThresholdProofKind::ESmShareComputation => { self.e_sm_share_computation_proof = Some(proof) } + ThresholdProofKind::SkShareEncryption { + recipient_party_id, + row_index, + } => { + self.sk_share_encryption_proofs + .insert((*recipient_party_id, *row_index), proof); + } + ThresholdProofKind::ESmShareEncryption { + recipient_party_id, + row_index, + } => { + self.e_sm_share_encryption_proofs + .insert((*recipient_party_id, *row_index), proof); + } } } + + fn total_expected(&self) -> usize { + 3 + self.expected_sk_enc_count + self.expected_e_sm_enc_count + } + + fn total_received(&self) -> usize { + let base = [ + self.pk_generation_proof.is_some(), + self.sk_share_computation_proof.is_some(), + self.e_sm_share_computation_proof.is_some(), + ] + .iter() + .filter(|&&v| v) + .count(); + base + self.sk_share_encryption_proofs.len() + self.e_sm_share_encryption_proofs.len() + } } /// Core actor that handles encryption key proof requests. @@ -136,9 +192,18 @@ impl ProofRequestActor { let (msg, ec) = msg.into_components(); let e3_id = msg.e3_id.clone(); + let sk_enc_count = msg.sk_share_encryption_requests.len(); + let e_sm_enc_count = msg.e_sm_share_encryption_requests.len(); + self.pending_threshold.insert( e3_id.clone(), - PendingThresholdProofs::new(e3_id.clone(), msg.full_share.clone(), ec.clone()), + PendingThresholdProofs::new( + e3_id.clone(), + msg.full_share.clone(), + ec.clone(), + sk_enc_count, + e_sm_enc_count, + ), ); // C1: PkGeneration @@ -201,6 +266,65 @@ impl ProofRequestActor { self.threshold_correlation .retain(|_, (eid, _)| *eid != e3_id); self.pending_threshold.remove(&e3_id); + return; + } + + // C3a: SkShareEncryption proofs + info!( + "Requesting {} T3a SkShareEncryption proofs for E3 {}", + sk_enc_count, e3_id + ); + for req in msg.sk_share_encryption_requests { + let corr = CorrelationId::new(); + self.threshold_correlation.insert( + corr, + ( + e3_id.clone(), + ThresholdProofKind::SkShareEncryption { + recipient_party_id: req.recipient_party_id, + row_index: req.row_index, + }, + ), + ); + if let Err(err) = self.bus.publish( + ComputeRequest::zk(ZkRequest::ShareEncryption(req), corr, e3_id.clone()), + ec.clone(), + ) { + error!("Failed to publish C3a proof request: {err}"); + self.threshold_correlation + .retain(|_, (eid, _)| *eid != e3_id); + self.pending_threshold.remove(&e3_id); + return; + } + } + + // C3b: ESmShareEncryption proofs + info!( + "Requesting {} T3b ESmShareEncryption proofs for E3 {}", + e_sm_enc_count, e3_id + ); + for req in msg.e_sm_share_encryption_requests { + let corr = CorrelationId::new(); + self.threshold_correlation.insert( + corr, + ( + e3_id.clone(), + ThresholdProofKind::ESmShareEncryption { + recipient_party_id: req.recipient_party_id, + row_index: req.row_index, + }, + ), + ); + if let Err(err) = self.bus.publish( + ComputeRequest::zk(ZkRequest::ShareEncryption(req), corr, e3_id.clone()), + ec.clone(), + ) { + error!("Failed to publish C3b proof request: {err}"); + self.threshold_correlation + .retain(|_, (eid, _)| *eid != e3_id); + self.pending_threshold.remove(&e3_id); + return; + } } } @@ -216,6 +340,9 @@ impl ProofRequestActor { ComputeResponseKind::Zk(ZkResponse::ShareComputation(resp)) => { self.handle_threshold_proof_response(&msg.correlation_id, resp.proof.clone()); } + ComputeResponseKind::Zk(ZkResponse::ShareEncryption(resp)) => { + self.handle_threshold_proof_response(&msg.correlation_id, resp.proof.clone()); + } _ => {} } } @@ -233,11 +360,21 @@ impl ProofRequestActor { return; }; - info!("Received {:?} proof for E3 {}", kind, e3_id); pending.store_proof(&kind, proof); + info!( + "Received {:?} proof for E3 {} ({}/{})", + kind, + e3_id, + pending.total_received(), + pending.total_expected() + ); if pending.is_complete() { - info!("All 3 threshold proofs complete for E3 {}", e3_id); + info!( + "All {} threshold proofs complete for E3 {}", + pending.total_expected(), + e3_id + ); let pending = self.pending_threshold.remove(&e3_id).unwrap(); self.publish_threshold_share_with_proofs(pending); } @@ -317,25 +454,61 @@ impl ProofRequestActor { } if let Err(err) = self.bus.publish( - ShareComputationProofSigned { + DkgProofSigned { e3_id: e3_id.clone(), party_id, signed_proof: signed_sk_share, }, ec.clone(), ) { - error!("Failed to publish SkShareComputationProofSigned: {err}"); + error!("Failed to publish SkDkgProofSigned: {err}"); } if let Err(err) = self.bus.publish( - ShareComputationProofSigned { + DkgProofSigned { e3_id: e3_id.clone(), party_id, signed_proof: signed_e_sm_share, }, ec.clone(), ) { - error!("Failed to publish ESmShareComputationProofSigned: {err}"); + error!("Failed to publish ESmDkgProofSigned: {err}"); + } + + // Sign and publish C3a proofs (SkShareEncryption) + for ((_recipient, _row), proof) in &pending.sk_share_encryption_proofs { + if let Some(signed) = + self.sign_proof(e3_id, ProofType::T1SkShareEncryption, proof.clone()) + { + if let Err(err) = self.bus.publish( + DkgProofSigned { + e3_id: e3_id.clone(), + party_id, + signed_proof: signed, + }, + ec.clone(), + ) { + error!("Failed to publish SkShareEncryptionProofSigned: {err}"); + } + } + } + + // Sign and publish C3b proofs (ESmShareEncryption) + for ((_recipient, _row), proof) in &pending.e_sm_share_encryption_proofs { + if let Some(signed) = + self.sign_proof(e3_id, ProofType::T1ESmShareEncryption, proof.clone()) + { + if let Err(err) = self.bus.publish( + DkgProofSigned { + e3_id: e3_id.clone(), + party_id, + signed_proof: signed, + }, + ec.clone(), + ) { + error!("Failed to publish ESmShareEncryptionProofSigned: {err}"); + } + } } info!( diff --git a/examples/CRISP/server/.env.example b/examples/CRISP/server/.env.example index 0cd31bbe47..e76ddbc9c8 100644 --- a/examples/CRISP/server/.env.example +++ b/examples/CRISP/server/.env.example @@ -23,7 +23,7 @@ FEE_TOKEN_ADDRESS="0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" # After this interval, the computation phase starts automatically # After activation + this interval, ciphernodes are then not responsing to # any more decryption requests -E3_DURATION=180 +E3_DURATION=300 E3_THRESHOLD_MIN=2 E3_THRESHOLD_MAX=5 diff --git a/examples/CRISP/test/crisp.spec.ts b/examples/CRISP/test/crisp.spec.ts index 448028ef97..8d9e40ed80 100644 --- a/examples/CRISP/test/crisp.spec.ts +++ b/examples/CRISP/test/crisp.spec.ts @@ -48,7 +48,7 @@ async function checkE3Ready(e3id: number): Promise { } } -async function waitForE3Ready(e3id: number, maxWaitMs: number = 80000): Promise { +async function waitForE3Ready(e3id: number, maxWaitMs: number = 300000): Promise { const startTime = Date.now() while (Date.now() - startTime < maxWaitMs) { const isActivated = await checkE3Ready(e3id) @@ -56,7 +56,7 @@ async function waitForE3Ready(e3id: number, maxWaitMs: number = 80000): Promise< console.log(`E3 ${e3id} is ready`) return } - await new Promise((resolve) => setTimeout(resolve, 2000)) + await new Promise((resolve) => setTimeout(resolve, 5000)) } throw new Error(`E3 ${e3id} was not ready within ${maxWaitMs}ms`) } diff --git a/templates/default/tests/integration.spec.ts b/templates/default/tests/integration.spec.ts index 01c27e423a..7f0b047a71 100644 --- a/templates/default/tests/integration.spec.ts +++ b/templates/default/tests/integration.spec.ts @@ -162,7 +162,7 @@ describe('Integration', () => { const { waitForEvent } = await setupEventListeners(sdk, store) const threshold: [number, number] = [DEFAULT_E3_CONFIG.threshold_min, DEFAULT_E3_CONFIG.threshold_max] - const duration = 70 + const duration = 160 const inputWindow = await calculateInputWindow(publicClient, duration) const thresholdBfvParams = await sdk.getThresholdBfvParamsSet() const e3ProgramParams = encodeBfvParams(thresholdBfvParams) From 4d2073fec78aa6dc7c5ea464b65484cb2bf55095 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:16:17 +0000 Subject: [PATCH 15/23] chore: cleanup --- .../src/enclave_event/compute_request/zk.rs | 14 ++--- .../events/src/enclave_event/signed_proof.rs | 60 +++++++++---------- crates/keyshare/src/threshold_keyshare.rs | 14 ++--- crates/tests/tests/integration.rs | 10 ++-- crates/zk-prover/src/actors/proof_request.rs | 36 +++++------ 5 files changed, 67 insertions(+), 67 deletions(-) diff --git a/crates/events/src/enclave_event/compute_request/zk.rs b/crates/events/src/enclave_event/compute_request/zk.rs index 695af699e1..99361b4d3f 100644 --- a/crates/events/src/enclave_event/compute_request/zk.rs +++ b/crates/events/src/enclave_event/compute_request/zk.rs @@ -17,15 +17,15 @@ use serde::{Deserialize, Serialize}; pub enum ZkRequest { /// Generate proof for BFV public key (C0). PkBfv(PkBfvProofRequest), - /// Generate proof for PK generation (T1). + /// Generate proof for PK generation (C1). PkGeneration(PkGenerationProofRequest), - /// Generate proof for share and esm computation (T2a and T2b). + /// Generate proof for share and esm computation (C2a and C2b). ShareComputation(ShareComputationProofRequest), /// Generate proof for share encryption (C3a/C3b). ShareEncryption(ShareEncryptionProofRequest), } -/// Request to generate a proof for share computation (T2a or T2b). +/// Request to generate a proof for share computation (C2a or C2b). #[derive(Derivative, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derivative(Debug)] pub struct ShareComputationProofRequest { @@ -75,7 +75,7 @@ pub struct ShareEncryptionProofRequest { pub row_index: usize, } -/// Request to generate a proof for BFV public key generation (T0). +/// Request to generate a proof for BFV public key generation (C0). #[derive(Derivative, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derivative(Debug)] pub struct PkBfvProofRequest { @@ -136,11 +136,11 @@ impl PkGenerationProofRequest { /// ZK proof generation response variants. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum ZkResponse { - /// Proof for BFV public key (T0). + /// Proof for BFV public key (C0). PkBfv(PkBfvProofResponse), - /// Proof for PK generation (T1a). + /// Proof for PK generation (C1). PkGeneration(PkGenerationProofResponse), - /// Proof for share and esm computation (T2a and T2b). + /// Proof for share and esm computation (C2a and C2b). ShareComputation(ShareComputationProofResponse), /// Proof for share encryption (C3a/C3b). ShareEncryption(ShareEncryptionProofResponse), diff --git a/crates/events/src/enclave_event/signed_proof.rs b/crates/events/src/enclave_event/signed_proof.rs index a667e9870c..0d2234ac30 100644 --- a/crates/events/src/enclave_event/signed_proof.rs +++ b/crates/events/src/enclave_event/signed_proof.rs @@ -30,17 +30,17 @@ use std::fmt::{self, Display}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum ProofType { /// T0 — BFV public key proof (Proof 0). - T0PkBfv = 0, - /// T1 — TrBFV public key generation proof (Proof 1). - T1PkGeneration = 1, - /// T1 — Secret key share computation proof (Proof 2a). - T1SkShareComputation = 2, - /// T1 — Smudging noise share computation proof (Proof 2b). - T1ESmShareComputation = 3, - /// T1 — Share encryption proof (Proof 3a). - T1SkShareEncryption = 4, - /// T1 — Smudging noise Share encryption proof (Proof 3b). - T1ESmShareEncryption = 5, + C0PkBfv = 0, + /// C1 — TrBFV public key generation proof (Proof 1). + C1PkGeneration = 1, + /// C2a — Secret key share computation proof (Proof 2a). + C2aSkShareComputation = 2, + /// C2b — Smudging noise share computation proof (Proof 2b). + C2bESmShareComputation = 3, + /// C3a — Share encryption proof (Proof 3a). + 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). @@ -53,12 +53,12 @@ impl ProofType { /// Map this proof type to its corresponding circuit names. pub fn circuit_names(&self) -> Vec { match self { - ProofType::T0PkBfv => vec![CircuitName::PkBfv], - ProofType::T1PkGeneration => vec![CircuitName::PkGeneration], - ProofType::T1SkShareComputation => vec![CircuitName::SkShareComputation], - ProofType::T1ESmShareComputation => vec![CircuitName::ESmShareComputation], - ProofType::T1SkShareEncryption => vec![CircuitName::ShareEncryption], - ProofType::T1ESmShareEncryption => vec![CircuitName::ShareEncryption], + ProofType::C0PkBfv => vec![CircuitName::PkBfv], + ProofType::C1PkGeneration => vec![CircuitName::PkGeneration], + ProofType::C2aSkShareComputation => vec![CircuitName::SkShareComputation], + 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![ @@ -71,12 +71,12 @@ impl ProofType { /// Slash reason identifier for on-chain policies. pub fn slash_reason(&self) -> &'static str { match self { - ProofType::T0PkBfv - | ProofType::T1PkGeneration - | ProofType::T1SkShareComputation - | ProofType::T1ESmShareComputation - | ProofType::T1SkShareEncryption - | ProofType::T1ESmShareEncryption + ProofType::C0PkBfv + | ProofType::C1PkGeneration + | ProofType::C2aSkShareComputation + | ProofType::C2bESmShareComputation + | ProofType::C3aSkShareEncryption + | ProofType::C3bESmShareEncryption | ProofType::T2DkgShareDecryption => "E3_BAD_DKG_PROOF", ProofType::T5ShareDecryption => "E3_BAD_DECRYPTION_PROOF", ProofType::T6DecryptedSharesAggregation => "E3_BAD_AGGREGATION_PROOF", @@ -224,7 +224,7 @@ mod tests { fn test_payload() -> ProofPayload { ProofPayload { e3_id: E3id::new("1", 42), - proof_type: ProofType::T0PkBfv, + proof_type: ProofType::C0PkBfv, proof: Proof::new( CircuitName::PkBfv, ArcBytes::from_bytes(&[10, 20, 30]), @@ -275,7 +275,7 @@ mod tests { fn different_payloads_produce_different_digests() { let p1 = test_payload(); let mut p2 = test_payload(); - p2.proof_type = ProofType::T1PkGeneration; + p2.proof_type = ProofType::C1PkGeneration; assert_ne!( p1.digest().expect("digest should succeed"), @@ -291,7 +291,7 @@ mod tests { let mut signed = SignedProofPayload::sign(payload, &signer).expect("signing should succeed"); // Tamper with the payload after signing - signed.payload.proof_type = ProofType::T1PkGeneration; + signed.payload.proof_type = ProofType::C1PkGeneration; let recovered = signed.recover_address().expect("recovery should succeed"); // Recovered address won't match the signer because payload was tampered @@ -300,17 +300,17 @@ mod tests { #[test] fn proof_type_circuit_names_mapping() { - assert_eq!(ProofType::T0PkBfv.circuit_names(), vec![CircuitName::PkBfv]); + assert_eq!(ProofType::C0PkBfv.circuit_names(), vec![CircuitName::PkBfv]); assert_eq!( - ProofType::T1PkGeneration.circuit_names(), + ProofType::C1PkGeneration.circuit_names(), vec![CircuitName::PkGeneration] ); assert_eq!( - ProofType::T1SkShareEncryption.circuit_names(), + ProofType::C3aSkShareEncryption.circuit_names(), vec![CircuitName::ShareEncryption] ); assert_eq!( - ProofType::T1ESmShareEncryption.circuit_names(), + ProofType::C3bESmShareEncryption.circuit_names(), vec![CircuitName::ShareEncryption] ); assert_eq!( diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index 29ee208ae9..2f6d92c4d1 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -432,7 +432,7 @@ impl ThresholdKeyshare { Ok(()) } - /// Handle PkGenerationProofSigned - stores the signed T1 proof in state + /// Handle PkGenerationProofSigned - stores the signed C1 proof in state pub fn handle_pk_generation_proof_signed( &mut self, msg: TypedEvent, @@ -485,22 +485,22 @@ impl ThresholdKeyshare { self.state.try_mutate(&ec, |s| { let current: AggregatingDecryptionKey = s.clone().try_into()?; let updated = match proof_type { - ProofType::T1SkShareComputation => AggregatingDecryptionKey { + ProofType::C2aSkShareComputation => AggregatingDecryptionKey { signed_sk_share_computation_proof: Some(msg.signed_proof), ..current }, - ProofType::T1ESmShareComputation => AggregatingDecryptionKey { + ProofType::C2bESmShareComputation => AggregatingDecryptionKey { signed_e_sm_share_computation_proof: Some(msg.signed_proof), ..current }, - ProofType::T1SkShareEncryption => { + ProofType::C3aSkShareEncryption => { let mut updated = current; updated .signed_sk_share_encryption_proofs .push(msg.signed_proof); updated } - ProofType::T1ESmShareEncryption => { + ProofType::C3bESmShareEncryption => { let mut updated = current; updated .signed_e_sm_share_encryption_proofs @@ -847,7 +847,7 @@ impl ThresholdKeyshare { .map(|s| s.decrypt(&self.cipher)) .collect::>()?; - // Serialize for T2a/T2b proof requests (encrypted at rest) + // Serialize for C2a/C2b proof requests (encrypted at rest) let sk_sss_raw = SensitiveBytes::new( bincode::serialize(&decrypted_sk_sss) .map_err(|e| anyhow!("Failed to serialize sk_sss: {}", e))?, @@ -974,7 +974,7 @@ impl ThresholdKeyshare { let total_proofs = 3 + sk_share_encryption_requests.len() + e_sm_share_encryption_requests.len(); info!( - "Publishing ThresholdSharePending for E3 {} ({} proofs: T1, T2a, T2b + {} C3a + {} C3b)", + "Publishing ThresholdSharePending for E3 {} ({} proofs: C1, C2a, C2b + {} C3a + {} C3b)", e3_id, total_proofs, sk_share_encryption_requests.len(), e_sm_share_encryption_requests.len() diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index 6ce0d530b0..96f5e26255 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -113,7 +113,7 @@ async fn setup_test_zk_backend() -> (ZkBackend, tempfile::TempDir) { .await .unwrap(); - // Copy T1 (pk_generation) circuit + // Copy C1 (pk_generation) circuit let pk_gen_circuit_dir = circuits_dir.join("threshold").join("pk_generation"); tokio::fs::create_dir_all(&pk_gen_circuit_dir) .await @@ -132,7 +132,7 @@ async fn setup_test_zk_backend() -> (ZkBackend, tempfile::TempDir) { .await .unwrap(); - // Copy T2a (sk_share_computation) circuit + // Copy C2a (sk_share_computation) circuit let sk_share_comp_circuit_dir = circuits_dir.join("dkg").join("sk_share_computation"); tokio::fs::create_dir_all(&sk_share_comp_circuit_dir) .await @@ -150,7 +150,7 @@ async fn setup_test_zk_backend() -> (ZkBackend, tempfile::TempDir) { .await .unwrap(); - // Copy T2b (e_sm_share_computation) circuit + // Copy C2b (e_sm_share_computation) circuit let e_sm_share_comp_circuit_dir = circuits_dir.join("dkg").join("e_sm_share_computation"); tokio::fs::create_dir_all(&e_sm_share_comp_circuit_dir) .await @@ -570,12 +570,12 @@ async fn test_trbfv_actor() -> Result<()> { // - ComputeRequest + ComputeResponse for GenPkShareAndSkSss = 10 // - ComputeRequest + ComputeResponse for GenEsiSss = 10 // - ThresholdSharePending = 5 - // - ComputeRequest + ComputeResponse for T1 ZK proof = 10 + // - ComputeRequest + ComputeResponse for C1 ZK proof = 10 // - 5 ThresholdShareCreated events (one per target party) = 25 total // - 1 PkGenerationProofSigned event = 5 total // Total: 10 + 10 + 5 + 10 + 25 + 5 = 65 events let shares_timer = Instant::now(); - let expected_count = 10 + 10 + 5 + 10 + 25 + 5; // GenPk + GenEsi + TSPending + T1 ZK + TSCreated + PkGenProof + let expected_count = 10 + 10 + 5 + 10 + 25 + 5; // GenPk + GenEsi + TSPending + C1 ZK + TSCreated + PkGenProof println!( "DEBUG: Waiting for {} share generation events...", expected_count diff --git a/crates/zk-prover/src/actors/proof_request.rs b/crates/zk-prover/src/actors/proof_request.rs index 79c9cd6c53..18b39355b7 100644 --- a/crates/zk-prover/src/actors/proof_request.rs +++ b/crates/zk-prover/src/actors/proof_request.rs @@ -210,7 +210,7 @@ impl ProofRequestActor { let t1_corr = CorrelationId::new(); self.threshold_correlation .insert(t1_corr, (e3_id.clone(), ThresholdProofKind::PkGeneration)); - info!("Requesting T1 PkGeneration proof"); + info!("Requesting C1 PkGeneration proof"); if let Err(err) = self.bus.publish( ComputeRequest::zk( ZkRequest::PkGeneration(msg.proof_request), @@ -219,7 +219,7 @@ impl ProofRequestActor { ), ec.clone(), ) { - error!("Failed to publish T1 proof request: {err}"); + error!("Failed to publish C1 proof request: {err}"); self.threshold_correlation.remove(&t1_corr); self.pending_threshold.remove(&e3_id); return; @@ -231,7 +231,7 @@ impl ProofRequestActor { t2a_corr, (e3_id.clone(), ThresholdProofKind::SkShareComputation), ); - info!("Requesting T2a SkShareComputation proof"); + info!("Requesting C2a SkShareComputation proof"); if let Err(err) = self.bus.publish( ComputeRequest::zk( ZkRequest::ShareComputation(msg.sk_share_computation_request), @@ -240,7 +240,7 @@ impl ProofRequestActor { ), ec.clone(), ) { - error!("Failed to publish T2a proof request: {err}"); + error!("Failed to publish C2a proof request: {err}"); self.threshold_correlation .retain(|_, (eid, _)| *eid != e3_id); self.pending_threshold.remove(&e3_id); @@ -253,7 +253,7 @@ impl ProofRequestActor { t2b_corr, (e3_id.clone(), ThresholdProofKind::ESmShareComputation), ); - info!("Requesting T2b ESmShareComputation proof"); + info!("Requesting C2b ESmShareComputation proof"); if let Err(err) = self.bus.publish( ComputeRequest::zk( ZkRequest::ShareComputation(msg.e_sm_share_computation_request), @@ -262,7 +262,7 @@ impl ProofRequestActor { ), ec.clone(), ) { - error!("Failed to publish T2b proof request: {err}"); + error!("Failed to publish C2b proof request: {err}"); self.threshold_correlation .retain(|_, (eid, _)| *eid != e3_id); self.pending_threshold.remove(&e3_id); @@ -271,7 +271,7 @@ impl ProofRequestActor { // C3a: SkShareEncryption proofs info!( - "Requesting {} T3a SkShareEncryption proofs for E3 {}", + "Requesting {} C3a SkShareEncryption proofs for E3 {}", sk_enc_count, e3_id ); for req in msg.sk_share_encryption_requests { @@ -300,7 +300,7 @@ impl ProofRequestActor { // C3b: ESmShareEncryption proofs info!( - "Requesting {} T3b ESmShareEncryption proofs for E3 {}", + "Requesting {} C3b ESmShareEncryption proofs for E3 {}", e_sm_enc_count, e3_id ); for req in msg.e_sm_share_encryption_requests { @@ -407,28 +407,28 @@ impl ProofRequestActor { let Some(signed_pk_gen) = self.sign_proof( e3_id, - ProofType::T1PkGeneration, + ProofType::C1PkGeneration, pending.pk_generation_proof.expect("checked"), ) else { - error!("Failed to sign T1 proof — shares will not be published"); + error!("Failed to sign C1 proof — shares will not be published"); return; }; let Some(signed_sk_share) = self.sign_proof( e3_id, - ProofType::T1SkShareComputation, + ProofType::C2aSkShareComputation, pending.sk_share_computation_proof.expect("checked"), ) else { - error!("Failed to sign T2a proof — shares will not be published"); + error!("Failed to sign C2a proof — shares will not be published"); return; }; let Some(signed_e_sm_share) = self.sign_proof( e3_id, - ProofType::T1ESmShareComputation, + ProofType::C2bESmShareComputation, pending.e_sm_share_computation_proof.expect("checked"), ) else { - error!("Failed to sign T2b proof — shares will not be published"); + error!("Failed to sign C2b proof — shares will not be published"); return; }; @@ -478,7 +478,7 @@ impl ProofRequestActor { // Sign and publish C3a proofs (SkShareEncryption) for ((_recipient, _row), proof) in &pending.sk_share_encryption_proofs { if let Some(signed) = - self.sign_proof(e3_id, ProofType::T1SkShareEncryption, proof.clone()) + self.sign_proof(e3_id, ProofType::C3aSkShareEncryption, proof.clone()) { if let Err(err) = self.bus.publish( DkgProofSigned { @@ -496,7 +496,7 @@ impl ProofRequestActor { // Sign and publish C3b proofs (ESmShareEncryption) for ((_recipient, _row), proof) in &pending.e_sm_share_encryption_proofs { if let Some(signed) = - self.sign_proof(e3_id, ProofType::T1ESmShareEncryption, proof.clone()) + self.sign_proof(e3_id, ProofType::C3bESmShareEncryption, proof.clone()) { if let Err(err) = self.bus.publish( DkgProofSigned { @@ -558,7 +558,7 @@ impl ProofRequestActor { // Always sign the proof payload — unsigned proofs are not published let payload = ProofPayload { e3_id: pending.e3_id.clone(), - proof_type: ProofType::T0PkBfv, + proof_type: ProofType::C0PkBfv, proof: proof.clone(), }; @@ -603,7 +603,7 @@ impl ProofRequestActor { if let Some((e3_id, kind)) = self.threshold_correlation.remove(msg.correlation_id()) { error!( - "T1 {:?} proof request failed for E3 {}: {err} — threshold share will not be published without proof", + "DKG {:?} proof request failed for E3 {}: {err} — threshold share will not be published without proof", kind, e3_id ); self.threshold_correlation From e44de277bc25d4c856d2ff404a6e6fe15be43033 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:49:05 +0000 Subject: [PATCH 16/23] chore: adjust timeouts --- crates/tests/tests/integration.rs | 2 +- templates/default/tests/integration.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index 96f5e26255..e9228da812 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -581,7 +581,7 @@ async fn test_trbfv_actor() -> Result<()> { expected_count ); let h = nodes - .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) + .take_history_with_timeout(0, expected_count, Duration::from_secs(3000)) .await?; println!( "DEBUG: Share generation phase events: {:?}", diff --git a/templates/default/tests/integration.spec.ts b/templates/default/tests/integration.spec.ts index 7f0b047a71..0872c244ae 100644 --- a/templates/default/tests/integration.spec.ts +++ b/templates/default/tests/integration.spec.ts @@ -162,7 +162,7 @@ describe('Integration', () => { const { waitForEvent } = await setupEventListeners(sdk, store) const threshold: [number, number] = [DEFAULT_E3_CONFIG.threshold_min, DEFAULT_E3_CONFIG.threshold_max] - const duration = 160 + const duration = 300 const inputWindow = await calculateInputWindow(publicClient, duration) const thresholdBfvParams = await sdk.getThresholdBfvParamsSet() const e3ProgramParams = encodeBfvParams(thresholdBfvParams) From f65fdc449d494b516a59d06f6f402f879969ace2 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:23:31 +0000 Subject: [PATCH 17/23] chore: coderabbit comments --- crates/tests/tests/integration.rs | 110 ++++++++----------- crates/zk-prover/src/actors/proof_request.rs | 52 +++++---- examples/CRISP/server/.env.example | 2 +- 3 files changed, 75 insertions(+), 89 deletions(-) diff --git a/crates/tests/tests/integration.rs b/crates/tests/tests/integration.rs index e9228da812..e8ca694818 100644 --- a/crates/tests/tests/integration.rs +++ b/crates/tests/tests/integration.rs @@ -483,7 +483,7 @@ async fn test_trbfv_actor() -> Result<()> { // - n=5 // - lambda=2 // - error_size -> calculate using calculate_error_size - // - esi_per_ciphertext = 3 + // - esi_per_ciphertext = 1 /////////////////////////////////////////////////////////////////////////////////// // Prepare round @@ -544,68 +544,53 @@ async fn test_trbfv_actor() -> Result<()> { )); // First, wait for all EncryptionKeyCreated events (BFV key exchange) - // Each of the 5 parties: - // - EncryptionKeyPending = 5 - // - ComputeRequest (T0 ZK proof) = 5 - // - ComputeResponse (T0 ZK proof) = 5 - // - EncryptionKeyCreated = 5 - // Total: 20 events + // The collector (node 0) only sees events forwarded by simulate_libp2p: + // - EncryptionKeyCreated × 5 (one per party, passes is_document_publisher_event filter) + // Internal events (EncryptionKeyPending, ComputeRequest/Response) stay on committee nodes' local buses. let encryption_keys_timer = Instant::now(); - let expected_count = 5 + 5 + 5 + 5; // EncKeyPending + T0 ComputeReq + T0 ComputeResp + EncKeyCreated - println!( - "DEBUG: Waiting for {} encryption key events...", - expected_count - ); - let h = nodes - .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) + let expected = vec![ + "EncryptionKeyCreated", + "EncryptionKeyCreated", + "EncryptionKeyCreated", + "EncryptionKeyCreated", + "EncryptionKeyCreated", + ]; + let _ = nodes + .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) .await?; - println!("DEBUG: EncryptionKey phase events: {:?}", h.event_types()); report.push(( "All EncryptionKeyCreated events", encryption_keys_timer.elapsed(), )); - // Then wait for share generation compute events + ThresholdShareCreated + PkGenerationProofSigned - // Each of the 5 parties: - // - ComputeRequest + ComputeResponse for GenPkShareAndSkSss = 10 - // - ComputeRequest + ComputeResponse for GenEsiSss = 10 - // - ThresholdSharePending = 5 - // - ComputeRequest + ComputeResponse for C1 ZK proof = 10 - // - 5 ThresholdShareCreated events (one per target party) = 25 total - // - 1 PkGenerationProofSigned event = 5 total - // Total: 10 + 10 + 5 + 10 + 25 + 5 = 65 events + // Then wait for all ThresholdShareCreated events + // Each of the 5 parties publishes 5 events (one per target party) = 25 total + // Only ThresholdShareCreated passes the simulate_libp2p filter (is_document_publisher_event). + // Internal events (ComputeRequest/Response for GenPk, GenEsi, ZK proofs, ThresholdSharePending, + // PkGenerationProofSigned, DkgProofSigned) stay on committee nodes' local buses. let shares_timer = Instant::now(); - let expected_count = 10 + 10 + 5 + 10 + 25 + 5; // GenPk + GenEsi + TSPending + C1 ZK + TSCreated + PkGenProof - println!( - "DEBUG: Waiting for {} share generation events...", - expected_count - ); - let h = nodes - .take_history_with_timeout(0, expected_count, Duration::from_secs(3000)) + let expected: Vec<&str> = (0..25).map(|_| "ThresholdShareCreated").collect(); + let _ = nodes + .take_history_with_timeout(0, expected.len(), Duration::from_secs(3000)) .await?; - println!( - "DEBUG: Share generation phase events: {:?}", - h.event_types() - ); - report.push(("All share generation events", shares_timer.elapsed())); - - // Wait for CalculateDecryptionKey compute events + KeyshareCreated + PublicKeyAggregated - // Each of the 5 parties: - // - ComputeRequest for CalculateDecryptionKey - // - ComputeResponse for CalculateDecryptionKey - // - KeyshareCreated - // Plus 1 PublicKeyAggregated at the end - // Total: 5 + 5 + 5 + 1 = 16 events + report.push(("All ThresholdShareCreated events", shares_timer.elapsed())); + + // Wait for KeyshareCreated + PublicKeyAggregated + // - KeyshareCreated × 5 (passes is_forwardable_event filter) + // - PublicKeyAggregated × 1 (passes is_forwardable_event filter) + // Internal events (ComputeRequest/Response for CalculateDecryptionKey) stay on local buses. let shares_to_pubkey_agg_timer = Instant::now(); - let expected_count = 5 + 5 + 5 + 1; // ComputeRequest + ComputeResponse + KeyshareCreated + PublicKeyAggregated - println!( - "DEBUG: Waiting for {} decryption key events...", - expected_count - ); + let expected = vec![ + "KeyshareCreated", + "KeyshareCreated", + "KeyshareCreated", + "KeyshareCreated", + "KeyshareCreated", + "PublicKeyAggregated", + ]; let h = nodes - .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) + .take_history_with_timeout(0, expected.len(), Duration::from_secs(1000)) .await?; - println!("DEBUG: Decryption key phase events: {:?}", h.event_types()); report.push(( "ThresholdShares -> PublicKeyAggregated", @@ -674,23 +659,20 @@ async fn test_trbfv_actor() -> Result<()> { println!("CiphertextOutputPublished event has been dispatched!"); // Lets grab decryption share events - // Each of the 5 parties: - // - ComputeRequest for CalculateDecryptionShare - // - ComputeResponse for CalculateDecryptionShare - // - DecryptionshareCreated - // Plus aggregation: - // - 1 CiphertextOutputPublished - // - 1 ComputeRequest (PlaintextAggregation) - // - 1 ComputeResponse (PlaintextAggregation) - // - 1 PlaintextAggregated - // Total: 1 + 5*3 + 3 = 19 events - let expected_count = 1 + (5 * 3) + 3; - println!("DEBUG: Waiting for {} decryption events...", expected_count); + // The collector sees: + // - 1 CiphertextOutputPublished (from shared bus) + // - 5 DecryptionshareCreated (from simulate_libp2p, passes is_forwardable_event) + // - 1 ComputeRequest (PlaintextAggregation on node 0's own bus) + // - 1 ComputeResponse (PlaintextAggregation on node 0's own bus) + // - 1 PlaintextAggregated (from node 0's own aggregation actor) + // Internal events from committee nodes (ComputeRequest/Response for CalculateDecryptionShare) + // stay on their local buses. + // Total: 1 + 5 + 1 + 1 + 1 = 9 events + let expected_count = 1 + 5 + 1 + 1 + 1; let h = nodes .take_history_with_timeout(0, expected_count, Duration::from_secs(1000)) .await?; - println!("DEBUG: Decryption phase events: {:?}", h.event_types()); report.push(( "Ciphertext published -> PlaintextAggregated", diff --git a/crates/zk-prover/src/actors/proof_request.rs b/crates/zk-prover/src/actors/proof_request.rs index 18b39355b7..2ebcd48374 100644 --- a/crates/zk-prover/src/actors/proof_request.rs +++ b/crates/zk-prover/src/actors/proof_request.rs @@ -477,37 +477,41 @@ impl ProofRequestActor { // Sign and publish C3a proofs (SkShareEncryption) for ((_recipient, _row), proof) in &pending.sk_share_encryption_proofs { - if let Some(signed) = + let Some(signed) = self.sign_proof(e3_id, ProofType::C3aSkShareEncryption, proof.clone()) - { - if let Err(err) = self.bus.publish( - DkgProofSigned { - e3_id: e3_id.clone(), - party_id, - signed_proof: signed, - }, - ec.clone(), - ) { - error!("Failed to publish SkShareEncryptionProofSigned: {err}"); - } + else { + error!("Failed to sign C3a proof — shares will not be published"); + return; + }; + if let Err(err) = self.bus.publish( + DkgProofSigned { + e3_id: e3_id.clone(), + party_id, + signed_proof: signed, + }, + ec.clone(), + ) { + error!("Failed to publish SkShareEncryptionProofSigned: {err}"); } } // Sign and publish C3b proofs (ESmShareEncryption) for ((_recipient, _row), proof) in &pending.e_sm_share_encryption_proofs { - if let Some(signed) = + let Some(signed) = self.sign_proof(e3_id, ProofType::C3bESmShareEncryption, proof.clone()) - { - if let Err(err) = self.bus.publish( - DkgProofSigned { - e3_id: e3_id.clone(), - party_id, - signed_proof: signed, - }, - ec.clone(), - ) { - error!("Failed to publish ESmShareEncryptionProofSigned: {err}"); - } + else { + error!("Failed to sign C3b proof — shares will not be published"); + return; + }; + if let Err(err) = self.bus.publish( + DkgProofSigned { + e3_id: e3_id.clone(), + party_id, + signed_proof: signed, + }, + ec.clone(), + ) { + error!("Failed to publish ESmShareEncryptionProofSigned: {err}"); } } diff --git a/examples/CRISP/server/.env.example b/examples/CRISP/server/.env.example index e76ddbc9c8..6c68d455a1 100644 --- a/examples/CRISP/server/.env.example +++ b/examples/CRISP/server/.env.example @@ -23,7 +23,7 @@ FEE_TOKEN_ADDRESS="0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" # After this interval, the computation phase starts automatically # After activation + this interval, ciphernodes are then not responsing to # any more decryption requests -E3_DURATION=300 +E3_DURATION=320 E3_THRESHOLD_MIN=2 E3_THRESHOLD_MAX=5 From 030407f5854858420ead8f9369a10d539775f709 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Tue, 24 Feb 2026 22:09:37 +0000 Subject: [PATCH 18/23] chore: use anvil for template tests --- examples/CRISP/enclave.config.yaml | 36 +++---- packages/enclave-contracts/scripts/utils.ts | 2 + templates/default/deploy/default.ts | 30 +++++- templates/default/deployed_contracts.json | 99 ++++++++++--------- templates/default/enclave.config.yaml | 39 +++----- templates/default/scripts/dev_ciphernodes.sh | 19 +--- templates/default/scripts/dev_frontend.sh | 4 +- templates/default/scripts/dev_server.sh | 6 +- templates/default/scripts/test_integration.sh | 4 +- templates/default/tests/integration.spec.ts | 4 +- 10 files changed, 127 insertions(+), 116 deletions(-) diff --git a/examples/CRISP/enclave.config.yaml b/examples/CRISP/enclave.config.yaml index ada7040313..8a72a9885b 100644 --- a/examples/CRISP/enclave.config.yaml +++ b/examples/CRISP/enclave.config.yaml @@ -1,55 +1,55 @@ chains: - - name: localhost - rpc_url: ws://localhost:8545 + - name: "localhost" + rpc_url: "ws://localhost:8545" contracts: e3_program: - address: '0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690' - deploy_block: 31 + address: "0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690" + deploy_block: 35 enclave: - address: '0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e' - deploy_block: 16 + address: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" + deploy_block: 21 ciphernode_registry: - address: '0x8A791620dd6260079BF849Dc5567aDC3F2FdC318' - deploy_block: 14 + address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" + deploy_block: 19 bonding_registry: - address: '0xa513E6E4b8f2a923D98304ec87F64353C4D5C853' - deploy_block: 10 + address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" + deploy_block: 14 fee_token: - address: '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512' + address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" deploy_block: 6 program: dev: true nodes: cn1: - address: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' + address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" quic_port: 9201 autonetkey: true autopassword: true cn2: - address: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC' + address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" quic_port: 9202 autonetkey: true autopassword: true cn3: - address: '0x90F79bf6EB2c4f870365E785982E1f101E93b906' + address: "0x90F79bf6EB2c4f870365E785982E1f101E93b906" quic_port: 9203 autonetkey: true autopassword: true cn4: - address: '0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65' + address: "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65" quic_port: 9204 autonetkey: true autopassword: true cn5: - address: '0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc' + address: "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" quic_port: 9205 autonetkey: true autopassword: true ag: - address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" quic_port: 9206 autonetkey: true autopassword: true role: - type: aggregator + type: "aggregator" diff --git a/packages/enclave-contracts/scripts/utils.ts b/packages/enclave-contracts/scripts/utils.ts index a86684fe5d..f790fcfa5c 100644 --- a/packages/enclave-contracts/scripts/utils.ts +++ b/packages/enclave-contracts/scripts/utils.ts @@ -223,6 +223,8 @@ export const updateE3Config = ( const yamlStr = yaml.dump(config, { indent: 2, lineWidth: -1, // Don't wrap lines + quotingType: '"', + forceQuotes: true, }); fs.writeFileSync(pathToConfigFile, yamlStr + "\n", "utf8"); diff --git a/templates/default/deploy/default.ts b/templates/default/deploy/default.ts index ff98cc8b8a..7805afe11f 100644 --- a/templates/default/deploy/default.ts +++ b/templates/default/deploy/default.ts @@ -4,10 +4,25 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -import { readDeploymentArgs, storeDeploymentArgs } from '@enclave-e3/contracts/scripts' +import { readDeploymentArgs, storeDeploymentArgs, updateE3Config } from '@enclave-e3/contracts/scripts' import { Enclave__factory as EnclaveFactory } from '@enclave-e3/contracts/types' import { MyProgram__factory as MyProgramFactory } from '../types/factories/contracts' import hre from 'hardhat' +import path from 'path' +import { fileURLToPath } from 'url' + +// Map contract names to config keys +const contractMapping: Record = { + MyProgram: 'e3_program', + Enclave: 'enclave', + CiphernodeRegistryOwnable: 'ciphernode_registry', + BondingRegistry: 'bonding_registry', + MockUSDC: 'fee_token', +} + +// Get __dirname equivalent in ES modules +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) export const deployTemplate = async () => { const { ethers } = await hre.network.connect() @@ -35,6 +50,7 @@ export const deployTemplate = async () => { storeDeploymentArgs( { address: await imageId.getAddress(), + blockNumber: await ethers.provider.getBlockNumber(), }, 'ImageID', chain, @@ -64,4 +80,16 @@ export const deployTemplate = async () => { Deployed MockRISC0Verifier at address: ${await verifier.getAddress()} `, ) + + storeDeploymentArgs( + { + address: await e3Program.getAddress(), + blockNumber: await ethers.provider.getBlockNumber(), + }, + 'MyProgram', + chain, + ) + + // this expects you to run it from CRISP's root + updateE3Config(chain, path.join(__dirname, '..', 'enclave.config.yaml'), contractMapping) } diff --git a/templates/default/deployed_contracts.json b/templates/default/deployed_contracts.json index 6149fa1dab..eed1dbf9a2 100644 --- a/templates/default/deployed_contracts.json +++ b/templates/default/deployed_contracts.json @@ -21,45 +21,45 @@ }, "localhost": { "PoseidonT3": { - "blockNumber": 3, + "blockNumber": 6, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 4, - "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" + "blockNumber": 7, + "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" }, "EnclaveToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 5, - "address": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" + "blockNumber": 8, + "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" }, "EnclaveTicketToken": { "constructorArgs": { - "baseToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "baseToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 7, - "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + "blockNumber": 10, + "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, "SlashingManager": { "constructorArgs": { "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "bondingRegistry": "0x0000000000000000000000000000000000000001" }, - "blockNumber": 8, - "address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" + "blockNumber": 11, + "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" }, "BondingRegistry": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "ticketToken": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", - "licenseToken": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", + "ticketToken": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", + "licenseToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", "registry": "0x0000000000000000000000000000000000000001", "slashedFundsTreasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "ticketPrice": "10000000", @@ -68,14 +68,14 @@ "exitDelay": "604800" }, "proxyRecords": { - "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f875707000000000000000000000000cf7ed3acca5a467e9e704c703e8d87f634fb0fc90000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", + "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000dc64a140aa3e981100a9beca4e685f962f0cf6c90000000000000000000000009fe46736679d2d9a65f0992f2272de9f3c7fa6e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", - "proxyAdminAddress": "0x94099942864EA81cCF197E9D71ac53310b1468D8", - "implementationAddress": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" + "proxyAddress": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", + "proxyAdminAddress": "0x9bd03768a7DCc129555dE410FF8E85528A4F88b5", + "implementationAddress": "0x0165878A594ca255338adfa4d48449f69242Eb8F" }, - "blockNumber": 8, - "address": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" + "blockNumber": 11, + "address": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, "CiphernodeRegistryOwnable": { "constructorArgs": { @@ -86,66 +86,71 @@ "proxyRecords": { "initData": "0x1794bb3c000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000a", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - "proxyAdminAddress": "0x6F1216D1BFe15c98520CA1434FC1d9D57AC95321", - "implementationAddress": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" + "proxyAddress": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", + "proxyAdminAddress": "0x8aCd85898458400f7Db866d53FCFF6f0D49741FF", + "implementationAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" }, - "blockNumber": 11, - "address": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + "blockNumber": 14, + "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" }, "Enclave": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "registry": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - "bondingRegistry": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", + "registry": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", + "bondingRegistry": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", "e3RefundManager": "0x0000000000000000000000000000000000000001", - "feeToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "feeToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", "maxDuration": "2592000", "timeoutConfig": "{\"committeeFormationWindow\":3600,\"dkgWindow\":7200,\"computeWindow\":86400,\"decryptionWindow\":3600,\"gracePeriod\":600}", "params": [ - "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000ffffee0010000000000000000000000000000000000000000000000000000000ffffc400100000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000" + "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000ffffee0010000000000000000000000000000000000000000000000000000000ffffc400100000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000" ] }, "proxyRecords": { - "initData": "0x8d158aa7000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000610178da211fef7d417bc0e6fed39f05609ad7880000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000010000000000000000000000009fe46736679d2d9a65f0992f2272de9f3c7fa6e00000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000000e100000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e10000000000000000000000000000000000000000000000000000000000000025800000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000fc00100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000003fffffff000001", + "initData": "0x69c5b347000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000008a791620dd6260079bf849dc5567adc3f2fdc318000000000000000000000000a513e6e4b8f2a923d98304ec87f64353c4d5c8530000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e1000000000000000000000000000000000000000000000000000000000000002580000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000ffffee0010000000000000000000000000000000000000000000000000000000ffffc400100000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "proxyAdminAddress": "0x1F708C24a0D3A740cD47cC0444E9480899f3dA7D", - "implementationAddress": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" + "proxyAddress": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", + "proxyAdminAddress": "0x8dAF17A20c9DBA35f005b6324F493785D239719d", + "implementationAddress": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" }, - "blockNumber": 13, - "address": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + "blockNumber": 16, + "address": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" }, "E3RefundManager": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "enclave": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "enclave": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", "treasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, "proxyRecords": { - "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000a51c1fc2f0d1a1b8494ed1fe312d7c3a78ed91c0000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000b7f8bc63bbcad18155201308c8f3540b07f84f5e000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508", - "proxyAdminAddress": "0x8e80FFe6Dc044F4A766Afd6e5a8732Fe0977A493", - "implementationAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + "proxyAddress": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "proxyAdminAddress": "0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08", + "implementationAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" }, - "blockNumber": 15, - "address": "0x9A676e781A523b5d0C0e43731313A708CB607508" + "blockNumber": 18, + "address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" }, "MockComputeProvider": { - "blockNumber": 26, - "address": "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f" + "blockNumber": 20, + "address": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" }, "MockDecryptionVerifier": { - "blockNumber": 27, - "address": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" + "blockNumber": 21, + "address": "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f" }, "MockE3Program": { - "blockNumber": 28, - "address": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F" + "blockNumber": 22, + "address": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" }, "ImageID": { - "address": "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E" + "address": "0x67d269191c92Caf3cD7723F116c85e6E9bf55933", + "blockNumber": 26 + }, + "MyProgram": { + "address": "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E", + "blockNumber": 28 } } } \ No newline at end of file diff --git a/templates/default/enclave.config.yaml b/templates/default/enclave.config.yaml index aaa84e54db..3b035e27dc 100644 --- a/templates/default/enclave.config.yaml +++ b/templates/default/enclave.config.yaml @@ -1,36 +1,24 @@ chains: - - name: "hardhat" + - name: "localhost" rpc_url: "ws://localhost:8545" contracts: - e3_program: - address: "0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690" - deploy_block: 1 # Set to actual deploy block enclave: - address: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" - deploy_block: 1 # Set to actual deploy block + address: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" + deploy_block: 16 ciphernode_registry: - address: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" - deploy_block: 1 # Set to actual deploy block + address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" + deploy_block: 14 bonding_registry: - address: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" - deploy_block: 1 # Set to actual deploy block + address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" + deploy_block: 11 fee_token: - address: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" - deploy_block: 1 # Set to actual deploy block - + address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" + deploy_block: 7 + e3_program: + address: "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E" + deploy_block: 28 program: dev: true - # risc0: - # risc0_dev_mode: 1 # 0 = production (Boundless), 1 = dev mode (fake proofs) - # - # # Boundless configuration (for production) - # boundless: - # rpc_url: "https://sepolia.infura.io/v3/YOUR_KEY" - # private_key: "PRIVATE_KEY" # Use env vars for secrets - # pinata_jwt: "PINATA_JWT" # For uploading programs - # program_url: "https://gateway.pinata.cloud/ipfs/YOUR_CID" # Pre-uploaded program - # onchain: true # true = onchain requests, false = offchain - nodes: cn1: address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" @@ -63,4 +51,5 @@ nodes: autonetkey: true autopassword: true role: - type: aggregator + type: "aggregator" + diff --git a/templates/default/scripts/dev_ciphernodes.sh b/templates/default/scripts/dev_ciphernodes.sh index 3f424bd477..97339c349f 100755 --- a/templates/default/scripts/dev_ciphernodes.sh +++ b/templates/default/scripts/dev_ciphernodes.sh @@ -18,7 +18,7 @@ rm -rf $SIGNAL_FILE trap cleanup INT TERM echo "Waiting for local evm node..." -pnpm wait-on http://localhost:8545 +pnpm wait-on tcp:localhost:8545 # nuke past installations as we are adding these nodes to the contract rm -rf .enclave/data @@ -54,28 +54,13 @@ CN5=$(grep -A 1 'cn5:' enclave.config.yaml | grep 'address:' | sed 's/.*address: # Add ciphernodes using variables from config.sh pnpm run deploy && sleep 2 + pnpm hardhat ciphernode:admin-add --ciphernode-address $CN1 --network localhost pnpm hardhat ciphernode:admin-add --ciphernode-address $CN2 --network localhost pnpm hardhat ciphernode:admin-add --ciphernode-address $CN3 --network localhost pnpm hardhat ciphernode:admin-add --ciphernode-address $CN4 --network localhost pnpm hardhat ciphernode:admin-add --ciphernode-address $CN5 --network localhost -# Function to send RPC request. -send_rpc() { - local method="$1" - local params="$2" - curl -X POST \ - -H "Content-Type: application/json" \ - -d "{\"jsonrpc\":\"2.0\",\"method\":\"$method\",\"params\":$params,\"id\":1}" \ - http://localhost:8545 > /dev/null 2>&1 -} - -# Configure mining settings for development environment -# Disable automatic mining and set interval mining to 1 second for predictable block times. -send_rpc "evm_setAutomine" "[false]" -send_rpc "evm_increaseTime" "[10]" -send_rpc "evm_setIntervalMining" "[1000]" - touch $SIGNAL_FILE wait diff --git a/templates/default/scripts/dev_frontend.sh b/templates/default/scripts/dev_frontend.sh index c99770ee22..703be656b4 100755 --- a/templates/default/scripts/dev_frontend.sh +++ b/templates/default/scripts/dev_frontend.sh @@ -3,9 +3,9 @@ set -euo pipefail echo "Waiting for local evm node..." -pnpm wait-on http://localhost:8545 +pnpm wait-on tcp:localhost:8545 echo "Waiting for program runner..." pnpm wait-on http://localhost:13151/health -cd client && (export $(enclave print-env --vite --chain hardhat) && pnpm dev) +cd client && (export $(enclave print-env --vite --chain anvil) && pnpm dev) diff --git a/templates/default/scripts/dev_server.sh b/templates/default/scripts/dev_server.sh index f5aeaefbf5..1f5020cb41 100755 --- a/templates/default/scripts/dev_server.sh +++ b/templates/default/scripts/dev_server.sh @@ -3,8 +3,10 @@ set -euo pipefail echo "Waiting for evm node..." -pnpm wait-on http://localhost:8545 && \ +pnpm wait-on tcp:localhost:8545 +echo "Waiting for contracts to be deployed..." +pnpm wait-on file:/tmp/enclave_ciphernodes_ready && \ (export PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" CHAIN_ID=31337 && \ - export $(enclave print-env --chain hardhat) && \ + export $(enclave print-env --chain localhost) && \ export RPC_URL="http://localhost:8545" && \ pnpm tsx ./server/index.ts) diff --git a/templates/default/scripts/test_integration.sh b/templates/default/scripts/test_integration.sh index e9f087cdac..296a357e7f 100755 --- a/templates/default/scripts/test_integration.sh +++ b/templates/default/scripts/test_integration.sh @@ -19,14 +19,14 @@ failed_message() { exit 1 } -export $(enclave print-env --chain hardhat) +export $(enclave print-env --chain localhost) (pnpm concurrently \ --names "TEST,EVM,CIPHER,SERVER,PROGRAM" \ --prefix-colors "blue,cyan,magenta,yellow,green" \ --kill-others \ --success first \ "wait-on http://localhost:13151/health && pnpm vitest run ./tests/integration.spec.ts" \ - "pnpm dev:evm" \ + "anvil --host 0.0.0.0 --chain-id 31337 --block-time 1 --mnemonic 'test test test test test test test test test test test junk'" \ "pnpm dev:ciphernodes" \ "TEST_MODE=1 pnpm dev:server" \ "pnpm dev:program" && passed_message) || failed_message diff --git a/templates/default/tests/integration.spec.ts b/templates/default/tests/integration.spec.ts index 0872c244ae..bd4fd5e050 100644 --- a/templates/default/tests/integration.spec.ts +++ b/templates/default/tests/integration.spec.ts @@ -23,7 +23,7 @@ import assert from 'assert' import { describe, expect, it } from 'vitest' import { publishInput } from '../server/input' import { privateKeyToAccount } from 'viem/accounts' -import { hardhat } from 'viem/chains' +import { anvil } from 'viem/chains' export function getContractAddresses() { return { @@ -154,7 +154,7 @@ describe('Integration', () => { const walletClient = createWalletClient({ account, - chain: hardhat, + chain: anvil, transport: http('http://localhost:8545'), }) From bad4823c0ed46a4cb993b3a5d17a353897556a63 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Tue, 24 Feb 2026 22:12:47 +0000 Subject: [PATCH 19/23] ci: trigger empty commit From 69dc4d998d20264d5b0d7203f34f6f2631f97b81 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Tue, 24 Feb 2026 22:24:06 +0000 Subject: [PATCH 20/23] chore: install foundry in ci --- .github/workflows/ci.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef9940ef22..1008a85a12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,6 @@ jobs: with: toolchain: ${{ env.RUST_TOOLCHAIN }} - # We must install foundry in order to be able to test anvil - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 @@ -840,34 +839,45 @@ jobs: node-version: ${{ env.NODE_VERSION }} cache: 'pnpm' cache-dependency-path: pnpm-lock.yaml + - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} + - name: Install node dependencies run: pnpm install --frozen-lockfile + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + - name: Download enclave binary uses: actions/download-artifact@v4 with: name: enclave-binary path: ~/.cargo/bin/ + - name: Download support scripts artifacts uses: actions/download-artifact@v4 with: name: support-scripts-artifacts path: templates/default/target/ + - name: Download SDK artifacts uses: actions/download-artifact@v4 with: name: sdk-artifacts path: ./ + - name: Make binaries executable run: | chmod +x ~/.cargo/bin/enclave chmod +x templates/default/target/debug/e3-support-scripts-dev + - name: Setup ZK prover run: | enclave noir setup + - name: Verify downloaded artifacts run: | echo "Checking downloaded artifacts:" @@ -877,6 +887,7 @@ jobs: ls -la crates/wasm/dist || echo "WASM dist not found" echo "Support scripts permissions:" ls -la templates/default/target/debug/e3-support-scripts-dev + - name: Test Template run: | cd templates/default From 98eea708ff7884c67dc33a902e1b81c56873bc3a Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 26 Feb 2026 14:23:24 +0500 Subject: [PATCH 21/23] fix: add esi_index to compute request --- crates/events/src/enclave_event/compute_request/zk.rs | 4 ++++ crates/keyshare/src/threshold_keyshare.rs | 4 +++- crates/multithread/src/multithread.rs | 1 + crates/zk-prover/src/actors/proof_request.rs | 11 +++++++---- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/events/src/enclave_event/compute_request/zk.rs b/crates/events/src/enclave_event/compute_request/zk.rs index 99361b4d3f..99facaeef2 100644 --- a/crates/events/src/enclave_event/compute_request/zk.rs +++ b/crates/events/src/enclave_event/compute_request/zk.rs @@ -73,6 +73,8 @@ pub struct ShareEncryptionProofRequest { pub recipient_party_id: usize, /// Modulus row index (for correlation tracking). pub row_index: usize, + /// ESI index (for C3b only; 0 for C3a). Disambiguates proofs across multiple ESI entries. + pub esi_index: usize, } /// Request to generate a proof for BFV public key generation (C0). @@ -160,6 +162,8 @@ pub struct ShareEncryptionProofResponse { pub dkg_input_type: DkgInputType, pub recipient_party_id: usize, pub row_index: usize, + /// ESI index (for C3b only; 0 for C3a). + pub esi_index: usize, } /// Response containing a generated BFV public key proof. diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index 2f6d92c4d1..a6990811d3 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -940,13 +940,14 @@ impl ThresholdKeyshare { committee_size: CiphernodesCommitteeSize::Small, recipient_party_id: recipient_idx, row_index: row_idx, + esi_index: 0, }); } } // Build C3b proof requests (E_SM share encryption) from witnesses let mut e_sm_share_encryption_requests = Vec::new(); - for esi_recipient_witnesses in esi_witnesses.iter() { + for (esi_idx, esi_recipient_witnesses) in esi_witnesses.iter().enumerate() { for (recipient_idx, recipient_witnesses) in esi_recipient_witnesses.iter().enumerate() { for (row_idx, witness) in recipient_witnesses.iter().enumerate() { e_sm_share_encryption_requests.push(ShareEncryptionProofRequest { @@ -966,6 +967,7 @@ impl ThresholdKeyshare { committee_size: CiphernodesCommitteeSize::Small, recipient_party_id: recipient_idx, row_index: row_idx, + esi_index: esi_idx, }); } } diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index 0964a03e14..c0ab4234c8 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -682,6 +682,7 @@ fn handle_share_encryption_proof( dkg_input_type: req.dkg_input_type, recipient_party_id: req.recipient_party_id, row_index: req.row_index, + esi_index: req.esi_index, }), request.correlation_id, request.e3_id, diff --git a/crates/zk-prover/src/actors/proof_request.rs b/crates/zk-prover/src/actors/proof_request.rs index 2ebcd48374..ab4642a4ff 100644 --- a/crates/zk-prover/src/actors/proof_request.rs +++ b/crates/zk-prover/src/actors/proof_request.rs @@ -30,6 +30,7 @@ enum ThresholdProofKind { row_index: usize, }, ESmShareEncryption { + esi_index: usize, recipient_party_id: usize, row_index: usize, }, @@ -52,8 +53,8 @@ struct PendingThresholdProofs { /// C3a proofs: keyed by (recipient_party_id, row_index) sk_share_encryption_proofs: HashMap<(usize, usize), Proof>, expected_sk_enc_count: usize, - /// C3b proofs: keyed by (recipient_party_id, row_index) - e_sm_share_encryption_proofs: HashMap<(usize, usize), Proof>, + /// C3b proofs: keyed by (esi_index, recipient_party_id, row_index) + e_sm_share_encryption_proofs: HashMap<(usize, usize, usize), Proof>, expected_e_sm_enc_count: usize, } @@ -102,11 +103,12 @@ impl PendingThresholdProofs { .insert((*recipient_party_id, *row_index), proof); } ThresholdProofKind::ESmShareEncryption { + esi_index, recipient_party_id, row_index, } => { self.e_sm_share_encryption_proofs - .insert((*recipient_party_id, *row_index), proof); + .insert((*esi_index, *recipient_party_id, *row_index), proof); } } } @@ -310,6 +312,7 @@ impl ProofRequestActor { ( e3_id.clone(), ThresholdProofKind::ESmShareEncryption { + esi_index: req.esi_index, recipient_party_id: req.recipient_party_id, row_index: req.row_index, }, @@ -496,7 +499,7 @@ impl ProofRequestActor { } // Sign and publish C3b proofs (ESmShareEncryption) - for ((_recipient, _row), proof) in &pending.e_sm_share_encryption_proofs { + for ((_esi, _recipient, _row), proof) in &pending.e_sm_share_encryption_proofs { let Some(signed) = self.sign_proof(e3_id, ProofType::C3bESmShareEncryption, proof.clone()) else { From 0cff990d7a7330a39e154316472be84bb86cfa79 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Thu, 26 Feb 2026 09:39:09 +0000 Subject: [PATCH 22/23] chore: use sensitive bytes --- .../src/enclave_event/compute_request/zk.rs | 20 +++++------ crates/keyshare/src/threshold_keyshare.rs | 26 ++++++++------- crates/multithread/src/multithread.rs | 33 +++++++++++++++---- 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/crates/events/src/enclave_event/compute_request/zk.rs b/crates/events/src/enclave_event/compute_request/zk.rs index 99facaeef2..a3ea9a2483 100644 --- a/crates/events/src/enclave_event/compute_request/zk.rs +++ b/crates/events/src/enclave_event/compute_request/zk.rs @@ -45,24 +45,20 @@ pub struct ShareComputationProofRequest { #[derive(Derivative, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derivative(Debug)] pub struct ShareEncryptionProofRequest { - /// Bincode-serialized Vec share row coefficients. - #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] - pub share_row_raw: ArcBytes, + /// Bincode-serialized Vec share row coefficients (witness — encrypted at rest). + pub share_row_raw: SensitiveBytes, /// Serialized BFV Ciphertext bytes (via fhe_traits::Serialize). #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] pub ciphertext_raw: ArcBytes, /// Serialized recipient BFV PublicKey bytes. #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] pub recipient_pk_raw: ArcBytes, - /// Serialized u_rns Poly bytes. - #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] - pub u_rns_raw: ArcBytes, - /// Serialized e0_rns Poly bytes. - #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] - pub e0_rns_raw: ArcBytes, - /// Serialized e1_rns Poly bytes. - #[derivative(Debug(format_with = "e3_utils::formatters::hexf"))] - pub e1_rns_raw: ArcBytes, + /// Serialized u_rns Poly bytes (witness — encrypted at rest). + pub u_rns_raw: SensitiveBytes, + /// Serialized e0_rns Poly bytes (witness — encrypted at rest). + pub e0_rns_raw: SensitiveBytes, + /// Serialized e1_rns Poly bytes (witness — encrypted at rest). + pub e1_rns_raw: SensitiveBytes, /// SecretKey or SmudgingNoise. pub dkg_input_type: DkgInputType, /// Threshold BFV preset (handler derives DKG params via build_pair_for_preset). diff --git a/crates/keyshare/src/threshold_keyshare.rs b/crates/keyshare/src/threshold_keyshare.rs index a6990811d3..426be036d7 100644 --- a/crates/keyshare/src/threshold_keyshare.rs +++ b/crates/keyshare/src/threshold_keyshare.rs @@ -924,17 +924,18 @@ impl ThresholdKeyshare { for (recipient_idx, recipient_witnesses) in sk_witnesses.iter().enumerate() { for (row_idx, witness) in recipient_witnesses.iter().enumerate() { sk_share_encryption_requests.push(ShareEncryptionProofRequest { - share_row_raw: ArcBytes::from_bytes( - &bincode::serialize(&witness.share_row) + share_row_raw: SensitiveBytes::new( + bincode::serialize(&witness.share_row) .map_err(|e| anyhow!("Failed to serialize share_row: {}", e))?, - ), + &self.cipher, + )?, ciphertext_raw: ArcBytes::from_bytes(&witness.ciphertext.to_bytes()), recipient_pk_raw: ArcBytes::from_bytes( &recipient_pks[recipient_idx].to_bytes(), ), - u_rns_raw: ArcBytes::from_bytes(&witness.u_rns.to_bytes()), - e0_rns_raw: ArcBytes::from_bytes(&witness.e0_rns.to_bytes()), - e1_rns_raw: ArcBytes::from_bytes(&witness.e1_rns.to_bytes()), + u_rns_raw: SensitiveBytes::new(witness.u_rns.to_bytes(), &self.cipher)?, + e0_rns_raw: SensitiveBytes::new(witness.e0_rns.to_bytes(), &self.cipher)?, + e1_rns_raw: SensitiveBytes::new(witness.e1_rns.to_bytes(), &self.cipher)?, dkg_input_type: DkgInputType::SecretKey, params_preset: threshold_preset, committee_size: CiphernodesCommitteeSize::Small, @@ -951,17 +952,18 @@ impl ThresholdKeyshare { for (recipient_idx, recipient_witnesses) in esi_recipient_witnesses.iter().enumerate() { for (row_idx, witness) in recipient_witnesses.iter().enumerate() { e_sm_share_encryption_requests.push(ShareEncryptionProofRequest { - share_row_raw: ArcBytes::from_bytes( - &bincode::serialize(&witness.share_row) + share_row_raw: SensitiveBytes::new( + bincode::serialize(&witness.share_row) .map_err(|e| anyhow!("Failed to serialize share_row: {}", e))?, - ), + &self.cipher, + )?, ciphertext_raw: ArcBytes::from_bytes(&witness.ciphertext.to_bytes()), recipient_pk_raw: ArcBytes::from_bytes( &recipient_pks[recipient_idx].to_bytes(), ), - u_rns_raw: ArcBytes::from_bytes(&witness.u_rns.to_bytes()), - e0_rns_raw: ArcBytes::from_bytes(&witness.e0_rns.to_bytes()), - e1_rns_raw: ArcBytes::from_bytes(&witness.e1_rns.to_bytes()), + u_rns_raw: SensitiveBytes::new(witness.u_rns.to_bytes(), &self.cipher)?, + e0_rns_raw: SensitiveBytes::new(witness.e0_rns.to_bytes(), &self.cipher)?, + e1_rns_raw: SensitiveBytes::new(witness.e1_rns.to_bytes(), &self.cipher)?, dkg_input_type: DkgInputType::SmudgingNoise, params_preset: threshold_preset, committee_size: CiphernodesCommitteeSize::Small, diff --git a/crates/multithread/src/multithread.rs b/crates/multithread/src/multithread.rs index c0ab4234c8..bd3b822c7c 100644 --- a/crates/multithread/src/multithread.rs +++ b/crates/multithread/src/multithread.rs @@ -412,7 +412,7 @@ fn handle_zk_request( handle_share_computation_proof(&prover, &cipher, req, request.clone()) }), ZkRequest::ShareEncryption(req) => timefunc("zk_share_encryption", id, || { - handle_share_encryption_proof(&prover, req, request.clone()) + handle_share_encryption_proof(&prover, &cipher, req, request.clone()) }), } } @@ -624,6 +624,7 @@ fn handle_pk_bfv_proof( fn handle_share_encryption_proof( prover: &ZkProver, + cipher: &Cipher, req: ShareEncryptionProofRequest, request: ComputeRequest, ) -> Result { @@ -631,22 +632,40 @@ fn handle_share_encryption_proof( let (_threshold_params, dkg_params) = build_pair_for_preset(req.params_preset) .map_err(|e| make_zk_error(&request, format!("build_pair_for_preset: {}", e)))?; - // 2. Deserialize share row and re-encode as Plaintext - let share_row: Vec = bincode::deserialize(&req.share_row_raw) + // 2. Decrypt sensitive witness data + let share_row_bytes = req + .share_row_raw + .access_raw(cipher) + .map_err(|e| make_zk_error(&request, format!("share_row decrypt: {}", e)))?; + let u_rns_bytes = req + .u_rns_raw + .access_raw(cipher) + .map_err(|e| make_zk_error(&request, format!("u_rns decrypt: {}", e)))?; + let e0_rns_bytes = req + .e0_rns_raw + .access_raw(cipher) + .map_err(|e| make_zk_error(&request, format!("e0_rns decrypt: {}", e)))?; + let e1_rns_bytes = req + .e1_rns_raw + .access_raw(cipher) + .map_err(|e| make_zk_error(&request, format!("e1_rns decrypt: {}", e)))?; + + // 3. Deserialize share row and re-encode as Plaintext + let share_row: Vec = bincode::deserialize(&share_row_bytes) .map_err(|e| make_zk_error(&request, format!("share_row: {}", e)))?; let plaintext = Plaintext::try_encode(&share_row, Encoding::poly(), &dkg_params) .map_err(|e| make_zk_error(&request, format!("plaintext encode: {:?}", e)))?; - // 3. Deserialize ciphertext, public key, polys using DKG params + // 4. Deserialize ciphertext, public key, polys using DKG params let ciphertext = Ciphertext::from_bytes(&req.ciphertext_raw, &dkg_params) .map_err(|e| make_zk_error(&request, format!("ciphertext: {:?}", e)))?; let public_key = PublicKey::from_bytes(&req.recipient_pk_raw, &dkg_params) .map_err(|e| make_zk_error(&request, format!("recipient_pk: {:?}", e)))?; - let u_rns = try_poly_from_bytes(&req.u_rns_raw, &dkg_params) + let u_rns = try_poly_from_bytes(&u_rns_bytes, &dkg_params) .map_err(|e| make_zk_error(&request, format!("u_rns: {}", e)))?; - let e0_rns = try_poly_from_bytes(&req.e0_rns_raw, &dkg_params) + let e0_rns = try_poly_from_bytes(&e0_rns_bytes, &dkg_params) .map_err(|e| make_zk_error(&request, format!("e0_rns: {}", e)))?; - let e1_rns = try_poly_from_bytes(&req.e1_rns_raw, &dkg_params) + let e1_rns = try_poly_from_bytes(&e1_rns_bytes, &dkg_params) .map_err(|e| make_zk_error(&request, format!("e1_rns: {}", e)))?; // 4. Dummy SecretKey (not used by Inputs::compute) From 4dd1b0d99d852c4d169a1ee586a59e1d305502c6 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:06:54 +0000 Subject: [PATCH 23/23] chore: update template integration tests --- templates/default/scripts/test_integration.sh | 2 +- templates/default/server/input.ts | 4 ++-- templates/default/tests/integration.spec.ts | 9 ++++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/templates/default/scripts/test_integration.sh b/templates/default/scripts/test_integration.sh index 296a357e7f..171f776cde 100755 --- a/templates/default/scripts/test_integration.sh +++ b/templates/default/scripts/test_integration.sh @@ -26,7 +26,7 @@ export $(enclave print-env --chain localhost) --kill-others \ --success first \ "wait-on http://localhost:13151/health && pnpm vitest run ./tests/integration.spec.ts" \ - "anvil --host 0.0.0.0 --chain-id 31337 --block-time 1 --mnemonic 'test test test test test test test test test test test junk'" \ + "anvil --host 0.0.0.0 --chain-id 31337 --block-time 1 --mnemonic 'test test test test test test test test test test test junk' --silent" \ "pnpm dev:ciphernodes" \ "TEST_MODE=1 pnpm dev:server" \ "pnpm dev:program" && passed_message) || failed_message diff --git a/templates/default/server/input.ts b/templates/default/server/input.ts index aeefa6708b..188de168fa 100644 --- a/templates/default/server/input.ts +++ b/templates/default/server/input.ts @@ -21,8 +21,8 @@ export const publishInput = async ( input: `0x${string}`, sender: `0x${string}`, programAddress: `0x${string}`, -): Promise => { - await walletClient.writeContract({ +): Promise<`0x${string}`> => { + return await walletClient.writeContract({ address: programAddress as `0x${string}`, abi: MyProgram.abi, functionName: 'publishInput', diff --git a/templates/default/tests/integration.spec.ts b/templates/default/tests/integration.spec.ts index bd4fd5e050..5aa52de380 100644 --- a/templates/default/tests/integration.spec.ts +++ b/templates/default/tests/integration.spec.ts @@ -162,7 +162,7 @@ describe('Integration', () => { const { waitForEvent } = await setupEventListeners(sdk, store) const threshold: [number, number] = [DEFAULT_E3_CONFIG.threshold_min, DEFAULT_E3_CONFIG.threshold_max] - const duration = 300 + const duration = 180 const inputWindow = await calculateInputWindow(publicClient, duration) const thresholdBfvParams = await sdk.getThresholdBfvParamsSet() const e3ProgramParams = encodeBfvParams(thresholdBfvParams) @@ -221,20 +221,23 @@ describe('Integration', () => { const enc1 = await sdk.encryptNumber(num1, publicKeyBytes) const enc2 = await sdk.encryptNumber(num2, publicKeyBytes) - await publishInput( + let txHash = await publishInput( walletClient, e3Id, `0x${Array.from(enc1, (b) => b.toString(16).padStart(2, '0')).join('')}` as `0x${string}`, account.address, contracts.e3Program, ) - await publishInput( + await publicClient.waitForTransactionReceipt({ hash: txHash }) + txHash = await publishInput( walletClient, e3Id, `0x${Array.from(enc2, (b) => b.toString(16).padStart(2, '0')).join('')}` as `0x${string}`, account.address, contracts.e3Program, ) + await publicClient.waitForTransactionReceipt({ hash: txHash }) + const plaintextEvent = await waitForEvent(EnclaveEventType.PLAINTEXT_OUTPUT_PUBLISHED) const parsed = hexToUint8Array(plaintextEvent.data.plaintextOutput)