Skip to content
6 changes: 3 additions & 3 deletions crates/aggregator/src/publickey_aggregator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ use e3_events::{
prelude::*, BusHandle, ComputeResponse, ComputeResponseKind, DKGRecursiveAggregationComplete,
Die, E3Failed, E3Stage, E3id, EnclaveEvent, EnclaveEventData, EventContext, FailureReason,
KeyshareCreated, OrderedSet, PartyProofsToVerify, PkAggregationProofPending,
PkAggregationProofRequest, PkAggregationProofSigned, Proof, ProofType, ProofVerificationPassed,
PublicKeyAggregated, Seed, Sequenced, ShareVerificationComplete, ShareVerificationDispatched,
SignedProofFailed, SignedProofPayload, TypedEvent, VerificationKind, ZkResponse,
PkAggregationProofRequest, PkAggregationProofSigned, Proof, ProofType, PublicKeyAggregated,
Seed, Sequenced, ShareVerificationComplete, ShareVerificationDispatched, SignedProofFailed,
SignedProofPayload, TypedEvent, VerificationKind, ZkResponse,
};
use e3_events::{trap, EType};
use e3_fhe::{Fhe, GetAggregatePublicKey};
Expand Down
3 changes: 3 additions & 0 deletions crates/events/src/enclave_event/compute_request/zk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ pub struct ThresholdShareDecryptionProofRequest {
/// When false, skip wrapper proofs for recursive C6 folding (mirrors DKG `proof_aggregation_enabled`).
#[serde(default = "default_proof_aggregation_enabled")]
pub proof_aggregation_enabled: bool,
/// C5 output commitment — used to verify the aggregated PK matches what C5 certified.
#[serde(default)]
pub c5_pk_commitment: Option<ArcBytes>,
}

fn default_proof_aggregation_enabled() -> bool {
Expand Down
29 changes: 27 additions & 2 deletions crates/events/src/enclave_event/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
use derivative::Derivative;
use e3_utils::utility_types::ArcBytes;
use e3_zk_helpers::{
CircuitOutputLayout, DKG_SHARE_DECRYPTION_OUTPUTS, PK_AGGREGATION_OUTPUTS, PK_BFV_OUTPUTS,
PK_GENERATION_OUTPUTS, SHARE_COMPUTATION_CHUNK_BATCH_OUTPUTS, SHARE_COMPUTATION_OUTPUTS,
CircuitInputLayout, CircuitOutputLayout, DKG_SHARE_DECRYPTION_OUTPUTS, PK_AGGREGATION_OUTPUTS,
PK_BFV_OUTPUTS, PK_GENERATION_OUTPUTS, SHARE_COMPUTATION_CHUNK_BATCH_OUTPUTS,
SHARE_COMPUTATION_OUTPUTS, SHARE_ENCRYPTION_INPUTS, THRESHOLD_SHARE_DECRYPTION_INPUTS,
THRESHOLD_SHARE_DECRYPTION_OUTPUTS,
};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -48,6 +49,17 @@ impl Proof {
.extract_field(&self.public_signals, field_name)
.map(ArcBytes::from_bytes)
}

/// Extract a named public input field from this proof's public signals.
///
/// Public inputs sit at the **start** of `public_signals`, before any
/// return values.
pub fn extract_input(&self, field_name: &str) -> Option<ArcBytes> {
let layout = self.circuit.input_layout();
layout
.extract_field(&self.public_signals, field_name)
.map(ArcBytes::from_bytes)
}
}

/// Circuit variants determine the hash oracle used for VK generation and proving.
Expand Down Expand Up @@ -202,6 +214,19 @@ impl CircuitName {
CircuitName::Fold => CircuitOutputLayout::None,
}
}

/// Public input layout for this circuit (fields at the start of public_signals).
pub fn input_layout(&self) -> CircuitInputLayout {
match self {
CircuitName::ShareEncryption => CircuitInputLayout::Fixed {
fields: SHARE_ENCRYPTION_INPUTS,
},
CircuitName::ThresholdShareDecryption => CircuitInputLayout::Fixed {
fields: THRESHOLD_SHARE_DECRYPTION_INPUTS,
},
_ => CircuitInputLayout::None,
}
}
}

impl fmt::Display for CircuitName {
Expand Down
4 changes: 3 additions & 1 deletion crates/events/src/enclave_event/proof_verification_passed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

use crate::{E3id, ProofType};
use crate::{E3id, ProofType, SignedProofPayload};
use actix::Message;
use alloy::primitives::Address;
use e3_utils::utility_types::ArcBytes;
Expand Down Expand Up @@ -34,6 +34,8 @@ pub struct ProofVerificationPassed {
pub data_hash: [u8; 32],
/// Raw public signals from the verified proof — for commitment consistency checks.
pub public_signals: ArcBytes,
/// The full signed proof — for fault evidence if a commitment mismatch is detected.
pub signed_payload: SignedProofPayload,
}

impl Display for ProofVerificationPassed {
Expand Down
25 changes: 17 additions & 8 deletions crates/events/src/enclave_event/signed_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,16 @@ pub enum ProofType {
C3aSkShareEncryption = 4,
/// C3b — Smudging noise share encryption proof (Proof 3b).
C3bESmShareEncryption = 5,
/// C4 — DKG share decryption proof (Proof 4).
C4DkgShareDecryption = 6,
/// C4a — SK share decryption proof (Proof 4a).
C4aSkShareDecryption = 6,
/// C4b — Smudging noise share decryption proof (Proof 4b).
C4bESmShareDecryption = 7,
/// C6 — Threshold share decryption proof (Proof 6).
C6ThresholdShareDecryption = 7,
C6ThresholdShareDecryption = 8,
/// C7 — Decrypted shares aggregation proof (Proof 7).
C7DecryptedSharesAggregation = 8,
C7DecryptedSharesAggregation = 9,
/// C5 — Public key aggregation proof (Proof 5).
C5PkAggregation = 9,
C5PkAggregation = 10,
Comment thread
ctrlc03 marked this conversation as resolved.
}

impl ProofType {
Expand All @@ -59,7 +61,9 @@ impl ProofType {
ProofType::C2bESmShareComputation => vec![CircuitName::ShareComputation],
ProofType::C3aSkShareEncryption => vec![CircuitName::ShareEncryption],
ProofType::C3bESmShareEncryption => vec![CircuitName::ShareEncryption],
ProofType::C4DkgShareDecryption => vec![CircuitName::DkgShareDecryption],
ProofType::C4aSkShareDecryption | ProofType::C4bESmShareDecryption => {
vec![CircuitName::DkgShareDecryption]
}
ProofType::C6ThresholdShareDecryption => vec![CircuitName::ThresholdShareDecryption],
ProofType::C7DecryptedSharesAggregation => {
vec![CircuitName::DecryptedSharesAggregation]
Expand All @@ -77,7 +81,8 @@ impl ProofType {
| ProofType::C2bESmShareComputation
| ProofType::C3aSkShareEncryption
| ProofType::C3bESmShareEncryption
| ProofType::C4DkgShareDecryption => "E3_BAD_DKG_PROOF",
| ProofType::C4aSkShareDecryption
| ProofType::C4bESmShareDecryption => "E3_BAD_DKG_PROOF",
ProofType::C6ThresholdShareDecryption => "E3_BAD_DECRYPTION_PROOF",
ProofType::C7DecryptedSharesAggregation => "E3_BAD_AGGREGATION_PROOF",
ProofType::C5PkAggregation => "E3_BAD_PK_AGGREGATION_PROOF",
Expand Down Expand Up @@ -391,7 +396,11 @@ mod tests {
vec![CircuitName::ShareEncryption]
);
assert_eq!(
ProofType::C4DkgShareDecryption.circuit_names(),
ProofType::C4aSkShareDecryption.circuit_names(),
vec![CircuitName::DkgShareDecryption]
);
assert_eq!(
ProofType::C4bESmShareDecryption.circuit_names(),
vec![CircuitName::DkgShareDecryption]
);
assert_eq!(
Expand Down
1 change: 1 addition & 0 deletions crates/keyshare/src/threshold_keyshare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2151,6 +2151,7 @@ impl ThresholdKeyshare {
d_share_bytes: d_share_poly.clone(),
params_preset: threshold_preset,
proof_aggregation_enabled: state.proof_aggregation_enabled,
c5_pk_commitment: None,
},
},
ec.clone(),
Expand Down
40 changes: 40 additions & 0 deletions crates/zk-helpers/src/circuits/output_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,46 @@ impl CircuitOutputLayout {
}
}

// ── Public input layout (fields at the HEAD of public_signals) ──────────────

/// Describes the public input fields of a circuit.
/// Inputs sit at the **start** of `public_signals`, before any return values.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CircuitInputLayout {
/// Fixed number of named `Field`-sized inputs, known at compile time.
Fixed { fields: &'static [OutputField] },
/// No known public input layout.
None,
}

impl CircuitInputLayout {
/// Extract a named public input field from raw `public_signals` bytes.
/// Inputs sit at the **start** of `public_signals`.
pub fn extract_field<'a>(&self, public_signals: &'a [u8], name: &str) -> Option<&'a [u8]> {
let fields = match self {
CircuitInputLayout::Fixed { fields } => fields,
_ => return None,
};
let idx = fields.iter().position(|f| f.name == name)?;
let offset = idx * FIELD_BYTE_LEN;
let end = offset + FIELD_BYTE_LEN;
if public_signals.len() < end {
return None;
}
Some(&public_signals[offset..end])
}
}

/// C3 — Share encryption public inputs.
pub const SHARE_ENCRYPTION_INPUTS: &[OutputField] = &[
f("expected_pk_commitment"),
f("expected_message_commitment"),
];

/// C6 — Threshold share decryption public inputs.
pub const THRESHOLD_SHARE_DECRYPTION_INPUTS: &[OutputField] =
&[f("expected_sk_commitment"), f("expected_e_sm_commitment")];

// ── Per-circuit output field constants ──────────────────────────────────────

const fn f(name: &'static str) -> OutputField {
Expand Down
55 changes: 46 additions & 9 deletions crates/zk-prover/src/actors/commitment_consistency_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,20 @@ use super::commitment_links::{CommitmentLink, LinkScope};
use actix::{Actor, Addr, Context, Handler};
use alloy::primitives::Address;
use e3_events::{
BusHandle, E3id, EnclaveEvent, EnclaveEventData, EventSubscriber, EventType, ProofType,
ProofVerificationPassed, TypedEvent,
BusHandle, E3id, EnclaveEvent, EnclaveEventData, EventPublisher, EventSubscriber, EventType,
ProofType, ProofVerificationPassed, SignedProofFailed, SignedProofPayload, TypedEvent,
};
use e3_utils::utility_types::ArcBytes;
use e3_utils::NotifySync;
use std::collections::HashMap;
use tracing::{info, warn};
use tracing::{error, info, warn};

/// Cached data from a verified proof.
struct VerifiedProofData {
party_id: u64,
address: Address,
public_signals: ArcBytes,
signed_payload: SignedProofPayload,
}

/// Per-E3 actor that enforces cross-circuit commitment consistency.
Expand Down Expand Up @@ -69,15 +70,39 @@ impl CommitmentConsistencyChecker {
addr
}

/// Emit SignedProofFailed for a party whose proof is inconsistent.
fn emit_fault(
&self,
data: &VerifiedProofData,
ec: &e3_events::EventContext<e3_events::Sequenced>,
) {
if let Err(e) = self.bus.publish(
SignedProofFailed {
e3_id: self.e3_id.clone(),
faulting_node: data.address,
proof_type: data.signed_payload.payload.proof_type,
signed_payload: data.signed_payload.clone(),
},
ec.clone(),
) {
error!("Failed to publish SignedProofFailed: {e}");
}
}

/// Evaluate all registered links given a newly arrived proof.
fn check_links(&self, new_proof_type: ProofType, new_address: Address) {
fn check_links(
&self,
new_proof_type: ProofType,
new_address: Address,
ec: &e3_events::EventContext<e3_events::Sequenced>,
) {
for link in &self.links {
match link.scope() {
LinkScope::SameParty => {
self.check_same_party_link(link.as_ref(), new_proof_type, new_address);
self.check_same_party_link(link.as_ref(), new_proof_type, new_address, ec);
}
LinkScope::CrossParty => {
self.check_cross_party_link(link.as_ref(), new_proof_type);
self.check_cross_party_link(link.as_ref(), new_proof_type, ec);
}
}
}
Expand All @@ -89,6 +114,7 @@ impl CommitmentConsistencyChecker {
link: &dyn CommitmentLink,
new_proof_type: ProofType,
address: Address,
ec: &e3_events::EventContext<e3_events::Sequenced>,
) {
let src_type = link.source_proof_type();
let tgt_type = link.target_proof_type();
Expand All @@ -114,13 +140,21 @@ impl CommitmentConsistencyChecker {
src_type,
tgt_type,
);
// Report the target proof as faulting — its inputs don't match
// the source's outputs.
self.emit_fault(tgt, ec);
}
}
}

/// Cross-party: check all cached sources against the target (or the new
/// source against all cached targets).
fn check_cross_party_link(&self, link: &dyn CommitmentLink, new_proof_type: ProofType) {
fn check_cross_party_link(
&self,
link: &dyn CommitmentLink,
new_proof_type: ProofType,
ec: &e3_events::EventContext<e3_events::Sequenced>,
) {
let src_type = link.source_proof_type();
let tgt_type = link.target_proof_type();

Expand Down Expand Up @@ -164,6 +198,7 @@ impl CommitmentConsistencyChecker {
tgt.address,
tgt_type,
);
self.emit_fault(src, ec);
}
}
}
Expand Down Expand Up @@ -201,21 +236,23 @@ impl Handler<TypedEvent<ProofVerificationPassed>> for CommitmentConsistencyCheck
msg: TypedEvent<ProofVerificationPassed>,
_ctx: &mut Self::Context,
) -> Self::Result {
let (data, _ec) = msg.into_components();
let (data, ec) = msg.into_components();

let proof_type = data.proof_type;
let address = data.address;
let public_signals = data.public_signals;
let signed_payload = data.signed_payload;

self.verified.insert(
(address, proof_type),
VerifiedProofData {
party_id: data.party_id,
address,
public_signals,
signed_payload,
},
);

self.check_links(proof_type, address);
self.check_links(proof_type, address, &ec);
}
}
Loading
Loading