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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/events/src/enclave_event/compute_request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ impl ToString for ComputeRequest {
ZkRequest::ShareEncryption(_) => "ZkShareEncryption",
ZkRequest::DkgShareDecryption(_) => "ZkDkgShareDecryption",
ZkRequest::VerifyShareProofs(_) => "ZkVerifyShareProofs",
ZkRequest::VerifyShareDecryptionProofs(_) => "ZkVerifyShareDecryptionProofs",
},
}
.to_string()
Expand Down
40 changes: 40 additions & 0 deletions crates/events/src/enclave_event/compute_request/zk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ pub enum ZkRequest {
DkgShareDecryption(DkgShareDecryptionProofRequest),
/// Batch-verify C2/C3 proofs from other parties.
VerifyShareProofs(VerifyShareProofsRequest),
/// Batch-verify C4 proofs from DecryptionKeyShared events.
VerifyShareDecryptionProofs(VerifyShareDecryptionProofsRequest),
}

/// Request to generate a proof for share computation (C2a or C2b).
Expand Down Expand Up @@ -172,6 +174,8 @@ pub enum ZkResponse {
DkgShareDecryption(DkgShareDecryptionProofResponse),
/// Batch verification results for C2/C3 proofs.
VerifyShareProofs(VerifyShareProofsResponse),
/// Batch verification results for C4 proofs.
VerifyShareDecryptionProofs(VerifyShareDecryptionProofsResponse),
}

/// Response containing a generated share computation proof.
Expand Down Expand Up @@ -279,6 +283,42 @@ pub struct PartyVerificationResult {
pub failed_signed_payload: Option<SignedProofPayload>,
}

/// Request to batch-verify C4 proofs from DecryptionKeyShared events.
///
/// Grouped by sender so the verifier can report honest/dishonest per party.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct VerifyShareDecryptionProofsRequest {
/// C4 proofs grouped by sender party_id.
pub party_proofs: Vec<PartyShareDecryptionProofsToVerify>,
}

/// C4 proofs from a single sender to verify.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PartyShareDecryptionProofsToVerify {
/// The party that generated these proofs.
pub sender_party_id: u64,
/// C4a proof (SecretKey decryption).
pub sk_decryption_proof: Proof,
/// C4b proofs (SmudgingNoise decryption), one per smudging noise index.
pub esm_decryption_proofs: Vec<Proof>,
}

/// Batch verification results for C4 proofs.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct VerifyShareDecryptionProofsResponse {
/// Per-party verification results.
pub party_results: Vec<PartyShareDecryptionVerificationResult>,
}

/// Verification result for C4 proofs from a single sender.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PartyShareDecryptionVerificationResult {
/// The party whose C4 proofs were verified.
pub sender_party_id: u64,
/// Whether ALL C4 proofs from this party verified successfully.
pub all_verified: bool,
}

/// ZK-specific error variants.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ZkError {
Expand Down
4 changes: 2 additions & 2 deletions crates/events/src/enclave_event/decryption_key_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ pub struct DecryptionKeyShared {
/// Lagrange-interpolated aggregated E_SM polynomials (serialized), one per smudging noise.
pub es_poly_sum: Vec<ArcBytes>,
/// C4a proof (SecretKey decryption).
pub c4a_proof: Proof,
pub sk_decryption_proof: Proof,
/// C4b proofs (SmudgingNoise decryption), one per smudging noise index.
pub c4b_proofs: Vec<Proof>,
pub esm_decryption_proofs: Vec<Proof>,
/// Whether this was received from the network.
pub external: bool,
}
Expand Down
185 changes: 185 additions & 0 deletions crates/keyshare/src/decryption_key_shared_collector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

use std::{
collections::{HashMap, HashSet},
time::{Duration, Instant},
};

use actix::{Actor, ActorContext, Addr, AsyncContext, Handler, Message, SpawnHandle};
use e3_events::{DecryptionKeyShared, E3id, TypedEvent};
use e3_utils::MAILBOX_LIMIT;
use tracing::{info, warn};

use crate::ThresholdKeyshare;

const DEFAULT_COLLECTION_TIMEOUT: Duration = Duration::from_secs(600);

enum CollectorState {
Collecting,
Finished,
TimedOut,
}

/// Message sent when all expected DecryptionKeyShared events have been collected.
#[derive(Message)]
#[rtype(result = "()")]
pub struct AllDecryptionKeySharesCollected {
pub shares: HashMap<u64, DecryptionKeyShared>,
}

/// Message sent when DecryptionKeyShared collection times out.
#[derive(Message, Clone, Debug)]
#[rtype(result = "()")]
pub struct DecryptionKeySharedCollectionTimeout;

/// Message sent when DecryptionKeyShared collection fails.
#[derive(Message, Clone, Debug)]
#[rtype(result = "()")]
pub struct DecryptionKeySharedCollectionFailed {
pub e3_id: E3id,
pub reason: String,
pub missing_parties: Vec<u64>,
}

/// Collects `DecryptionKeyShared` events from expected parties in H (Exchange #3).
///
/// Once all expected events are collected, sends `AllDecryptionKeySharesCollected`
/// to the parent `ThresholdKeyshare` actor for C4 proof verification.
pub struct DecryptionKeySharedCollector {
e3_id: E3id,
/// Party IDs we expect to receive from (H minus self).
expected: HashSet<u64>,
parent: Addr<ThresholdKeyshare>,
state: CollectorState,
shares: HashMap<u64, DecryptionKeyShared>,
timeout_handle: Option<SpawnHandle>,
}

impl DecryptionKeySharedCollector {
pub fn setup(
parent: Addr<ThresholdKeyshare>,
expected_parties: HashSet<u64>,
e3_id: E3id,
) -> Addr<Self> {
let collector = Self {
e3_id,
expected: expected_parties,
parent,
state: CollectorState::Collecting,
shares: HashMap::new(),
timeout_handle: None,
};
collector.start()
}
}

impl Actor for DecryptionKeySharedCollector {
type Context = actix::Context<Self>;

fn started(&mut self, ctx: &mut Self::Context) {
ctx.set_mailbox_capacity(MAILBOX_LIMIT);
info!(
e3_id = %self.e3_id,
"DecryptionKeySharedCollector started, expecting {} parties, timeout {:?}",
self.expected.len(),
DEFAULT_COLLECTION_TIMEOUT
);
let handle = ctx.notify_later(
DecryptionKeySharedCollectionTimeout,
DEFAULT_COLLECTION_TIMEOUT,
);
self.timeout_handle = Some(handle);
}
}

impl Handler<TypedEvent<DecryptionKeyShared>> for DecryptionKeySharedCollector {
type Result = ();
fn handle(
&mut self,
msg: TypedEvent<DecryptionKeyShared>,
ctx: &mut Self::Context,
) -> Self::Result {
let (msg, ec) = msg.into_components();
let start = Instant::now();

if !matches!(self.state, CollectorState::Collecting) {
return;
}

let pid = msg.party_id;
if !self.expected.remove(&pid) {
info!(
"DecryptionKeySharedCollector: party {} not in expected set, ignoring",
pid
);
return;
}

info!(
"DecryptionKeySharedCollector: received from party {}, waiting on {}",
pid,
self.expected.len()
);
self.shares.insert(pid, msg);

if self.expected.is_empty() {
info!("All DecryptionKeyShared events collected");
self.state = CollectorState::Finished;

if let Some(handle) = self.timeout_handle.take() {
ctx.cancel_future(handle);
}

let event: TypedEvent<AllDecryptionKeySharesCollected> = TypedEvent::new(
AllDecryptionKeySharesCollected {
shares: std::mem::take(&mut self.shares),
},
ec,
);
self.parent.do_send(event);
}

info!(
"Finished processing DecryptionKeyShared in {:?}",
start.elapsed()
);
}
}

impl Handler<DecryptionKeySharedCollectionTimeout> for DecryptionKeySharedCollector {
type Result = ();
fn handle(
&mut self,
_: DecryptionKeySharedCollectionTimeout,
ctx: &mut Self::Context,
) -> Self::Result {
if !matches!(self.state, CollectorState::Collecting) {
return;
}

warn!(
e3_id = %self.e3_id,
missing_parties = ?self.expected,
"DecryptionKeyShared collection timed out, {} parties missing",
self.expected.len()
);

self.state = CollectorState::TimedOut;

let missing_parties: Vec<u64> = self.expected.iter().copied().collect();
self.parent.do_send(DecryptionKeySharedCollectionFailed {
e3_id: self.e3_id.clone(),
reason: format!(
"Timeout waiting for DecryptionKeyShared from {} parties",
missing_parties.len()
),
missing_parties,
});

ctx.stop();
}
}
1 change: 1 addition & 0 deletions crates/keyshare/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

mod decryption_key_shared_collector;
mod encryption_key_collector;
pub mod ext;
mod repo;
Expand Down
Loading
Loading