diff --git a/crates/ciphernode-builder/src/ciphernode_builder.rs b/crates/ciphernode-builder/src/ciphernode_builder.rs index 210f827e71..44c7b44899 100644 --- a/crates/ciphernode-builder/src/ciphernode_builder.rs +++ b/crates/ciphernode-builder/src/ciphernode_builder.rs @@ -37,7 +37,9 @@ use e3_sortition::{ }; use e3_sync::sync; use e3_utils::SharedRng; -use e3_zk_prover::{setup_zk_actors, AccusationManagerExtension, ZkBackend}; +use e3_zk_prover::{ + setup_zk_actors, AccusationManagerExtension, CommitmentConsistencyCheckerExtension, ZkBackend, +}; use libp2p::PeerId; use std::time::Duration; use std::{collections::HashMap, path::PathBuf, sync::Arc}; @@ -537,6 +539,12 @@ impl CiphernodeBuilder { e3_builder = e3_builder.with(AccusationManagerExtension::create(&bus, signer)); } + // CommitmentConsistencyChecker extension — per-E3 cross-circuit commitment validation + { + info!("Setting up CommitmentConsistencyCheckerExtension"); + e3_builder = e3_builder.with(CommitmentConsistencyCheckerExtension::create(&bus)); + } + info!("E3Router building..."); e3_builder.build().await?; diff --git a/crates/events/src/enclave_event/proof_verification_passed.rs b/crates/events/src/enclave_event/proof_verification_passed.rs index b44a212741..aed00f2338 100644 --- a/crates/events/src/enclave_event/proof_verification_passed.rs +++ b/crates/events/src/enclave_event/proof_verification_passed.rs @@ -7,13 +7,15 @@ use crate::{E3id, ProofType}; use actix::Message; use alloy::primitives::Address; +use e3_utils::utility_types::ArcBytes; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display}; /// Emitted locally when a node successfully verifies another node's ZK proof. /// /// This allows the [`AccusationManager`] to cache successful verification results -/// so it can vote DISAGREE on false accusations from other nodes. +/// so it can vote DISAGREE on false accusations from other nodes, and the +/// [`CommitmentConsistencyChecker`] to cross-check commitment values across circuits. /// /// Emitted by: /// - [`ProofVerificationActor`] — for C0 (BFV public key) successes @@ -30,6 +32,8 @@ pub struct ProofVerificationPassed { pub proof_type: ProofType, /// keccak256 hash of the received data + proof bytes — for equivocation detection. pub data_hash: [u8; 32], + /// Raw public signals from the verified proof — for commitment consistency checks. + pub public_outputs: ArcBytes, } impl Display for ProofVerificationPassed { diff --git a/crates/request/src/context.rs b/crates/request/src/context.rs index 31a36a619c..e1c71c8286 100644 --- a/crates/request/src/context.rs +++ b/crates/request/src/context.rs @@ -25,6 +25,7 @@ fn init_recipients() -> HashMap>> { ("plaintext".to_owned(), None), ("publickey".to_owned(), None), ("accusation_manager".to_owned(), None), + ("commitment_consistency_checker".to_owned(), None), ]) } diff --git a/crates/zk-prover/src/actors/commitment_consistency_checker.rs b/crates/zk-prover/src/actors/commitment_consistency_checker.rs new file mode 100644 index 0000000000..634c1bafaa --- /dev/null +++ b/crates/zk-prover/src/actors/commitment_consistency_checker.rs @@ -0,0 +1,221 @@ +// 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. + +//! Actor that cross-checks commitment values across different circuit proofs. +//! +//! Subscribes to [`ProofVerificationPassed`] events and, for each registered +//! [`CommitmentLink`], compares commitment field values extracted from public +//! signals of related proof types. +//! +//! ## Architecture +//! +//! - Caches verified proof outputs keyed by `(Address, ProofType)`. +//! - On each new event, evaluates every registered link to see if both sides +//! (source and target) are now available. +//! - For **same-party** links, compares proofs from the same Ethereum address. +//! - For **cross-party** links (e.g. per-node C1 vs aggregator C5), checks all +//! cached source proofs against the newly arrived target (or vice versa). +//! - Logs warnings on mismatch. Future iterations may emit an accusation event. + +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, +}; +use e3_utils::utility_types::ArcBytes; +use e3_utils::NotifySync; +use std::collections::HashMap; +use tracing::{info, warn}; + +/// Cached data from a verified proof. +struct VerifiedProofData { + party_id: u64, + address: Address, + public_outputs: ArcBytes, +} + +/// Per-E3 actor that enforces cross-circuit commitment consistency. +pub struct CommitmentConsistencyChecker { + bus: BusHandle, + e3_id: E3id, + links: Vec>, + /// Verified proof outputs: `(address, proof_type) → data`. + /// + /// For cross-party links the target proof type may come from a different + /// address than the source, so lookups iterate over all entries whose + /// `proof_type` matches. + verified: HashMap<(Address, ProofType), VerifiedProofData>, +} + +impl CommitmentConsistencyChecker { + pub fn new(bus: &BusHandle, e3_id: E3id, links: Vec>) -> Self { + Self { + bus: bus.clone(), + e3_id, + links, + verified: HashMap::new(), + } + } + + pub fn setup(bus: &BusHandle, e3_id: E3id, links: Vec>) -> Addr { + let actor = Self::new(bus, e3_id, links); + let addr = actor.start(); + bus.subscribe(EventType::ProofVerificationPassed, addr.clone().into()); + addr + } + + /// Evaluate all registered links given a newly arrived proof. + fn check_links(&self, new_proof_type: ProofType, new_address: Address) { + for link in &self.links { + match link.scope() { + LinkScope::SameParty => { + self.check_same_party_link(link.as_ref(), new_proof_type, new_address); + } + LinkScope::CrossParty => { + self.check_cross_party_link(link.as_ref(), new_proof_type); + } + } + } + } + + /// Same-party: compare source and target from the same address. + fn check_same_party_link( + &self, + link: &dyn CommitmentLink, + new_proof_type: ProofType, + address: Address, + ) { + let src_type = link.source_proof_type(); + let tgt_type = link.target_proof_type(); + + // Only run when the newly arrived proof completes a pair. + if new_proof_type != src_type && new_proof_type != tgt_type { + return; + } + + let source = self.verified.get(&(address, src_type)); + let target = self.verified.get(&(address, tgt_type)); + + if let (Some(src), Some(tgt)) = (source, target) { + let source_values = link.extract_source_values(&src.public_outputs); + if !link.check_consistency(&source_values, &tgt.public_outputs) { + warn!( + "[{}] Commitment mismatch for E3 {} — party {} ({}): \ + source {:?} vs target {:?} from same address", + link.name(), + self.e3_id, + src.party_id, + address, + src_type, + tgt_type, + ); + } + } + } + + /// 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) { + let src_type = link.source_proof_type(); + let tgt_type = link.target_proof_type(); + + if new_proof_type != src_type && new_proof_type != tgt_type { + return; + } + + // Collect all entries matching the source proof type. + let sources: Vec<&VerifiedProofData> = self + .verified + .iter() + .filter(|((_, pt), _)| *pt == src_type) + .map(|(_, v)| v) + .collect(); + + // Collect all entries matching the target proof type. + let targets: Vec<&VerifiedProofData> = self + .verified + .iter() + .filter(|((_, pt), _)| *pt == tgt_type) + .map(|(_, v)| v) + .collect(); + + // For each (source, target) pair, check consistency. + for src in &sources { + let source_values = link.extract_source_values(&src.public_outputs); + if source_values.is_empty() { + continue; + } + for tgt in &targets { + if !link.check_consistency(&source_values, &tgt.public_outputs) { + warn!( + "[{}] Commitment mismatch for E3 {} — source party {} ({}) {:?} \ + not consistent with target party {} ({}) {:?}", + link.name(), + self.e3_id, + src.party_id, + src.address, + src_type, + tgt.party_id, + tgt.address, + tgt_type, + ); + } + } + } + } +} + +impl Actor for CommitmentConsistencyChecker { + type Context = Context; + + fn started(&mut self, _ctx: &mut Self::Context) { + info!( + "CommitmentConsistencyChecker started for E3 {} with {} link(s)", + self.e3_id, + self.links.len() + ); + } +} + +impl Handler for CommitmentConsistencyChecker { + type Result = (); + + fn handle(&mut self, msg: EnclaveEvent, ctx: &mut Self::Context) -> Self::Result { + let (msg, ec) = msg.into_components(); + if let EnclaveEventData::ProofVerificationPassed(data) = msg { + self.notify_sync(ctx, TypedEvent::new(data, ec)); + } + } +} + +impl Handler> for CommitmentConsistencyChecker { + type Result = (); + + fn handle( + &mut self, + msg: TypedEvent, + _ctx: &mut Self::Context, + ) -> Self::Result { + let (data, _ec) = msg.into_components(); + + let proof_type = data.proof_type; + let address = data.address; + let public_outputs = data.public_outputs; + + self.verified.insert( + (address, proof_type), + VerifiedProofData { + party_id: data.party_id, + address, + public_outputs, + }, + ); + + self.check_links(proof_type, address); + } +} diff --git a/crates/zk-prover/src/actors/commitment_consistency_checker_ext.rs b/crates/zk-prover/src/actors/commitment_consistency_checker_ext.rs new file mode 100644 index 0000000000..94d60e23f3 --- /dev/null +++ b/crates/zk-prover/src/actors/commitment_consistency_checker_ext.rs @@ -0,0 +1,62 @@ +// 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. + +//! E3Extension that wires up the [`CommitmentConsistencyChecker`] per-E3 +//! when the committee is finalized. +//! +//! Follows the same lifecycle pattern as [`AccusationManagerExtension`]: +//! listens for [`CommitteeFinalized`], creates the actor, and registers it +//! in the [`E3Context`] so it receives routed events. + +use super::commitment_consistency_checker::CommitmentConsistencyChecker; +use super::commitment_links; +use anyhow::Result; +use async_trait::async_trait; +use e3_events::{BusHandle, EnclaveEvent, EnclaveEventData, Event}; +use e3_request::{E3Context, E3ContextSnapshot, E3Extension}; +use tracing::info; + +pub struct CommitmentConsistencyCheckerExtension { + bus: BusHandle, +} + +impl CommitmentConsistencyCheckerExtension { + pub fn create(bus: &BusHandle) -> Box { + Box::new(Self { bus: bus.clone() }) + } +} + +#[async_trait] +impl E3Extension for CommitmentConsistencyCheckerExtension { + fn on_event(&self, ctx: &mut E3Context, evt: &EnclaveEvent) { + let EnclaveEventData::CommitteeFinalized(data) = evt.get_data() else { + return; + }; + + // Don't start twice + if ctx + .get_event_recipient("commitment_consistency_checker") + .is_some() + { + return; + } + + let e3_id = data.e3_id.clone(); + + info!("Starting CommitmentConsistencyChecker for E3 {}", e3_id); + + let links = commitment_links::default_links(); + let addr = CommitmentConsistencyChecker::setup(&self.bus, e3_id, links); + + ctx.set_event_recipient("commitment_consistency_checker", Some(addr.into())); + } + + /// Intentionally a no-op — the checker is ephemeral by design (same + /// reasoning as [`AccusationManagerExtension::hydrate`]). + async fn hydrate(&self, _ctx: &mut E3Context, _snapshot: &E3ContextSnapshot) -> Result<()> { + Ok(()) + } +} diff --git a/crates/zk-prover/src/actors/commitment_links/c1_to_c5.rs b/crates/zk-prover/src/actors/commitment_links/c1_to_c5.rs new file mode 100644 index 0000000000..89169a4ee5 --- /dev/null +++ b/crates/zk-prover/src/actors/commitment_links/c1_to_c5.rs @@ -0,0 +1,172 @@ +// 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. + +//! C1 (PkGeneration) → C5 (PkAggregation) pk_commitment consistency link. +//! +//! ## Circuit layouts +//! +//! **C1 (PkGeneration)** outputs `(sk_commitment, pk_commitment, e_sm_commitment)`. +//! Public signals contain 3 fields (no public inputs); `pk_commitment` is at +//! field index 1 (byte offset 32..64). +//! +//! **C5 (PkAggregation)** takes `expected_threshold_pk_commitments: pub [Field; H]` +//! as public inputs and returns `pk_agg_commitment` as a single public output. +//! Public signals contain H+1 fields; the first H fields are per-party +//! `pk_commitment` values and the last field is the aggregated commitment. +//! +//! ## Check +//! +//! Each cipher node's C1 `pk_commitment` must appear somewhere in C5's +//! `expected_threshold_pk_commitments` array. + +use super::{CommitmentLink, FieldValue, LinkScope}; +use e3_events::ProofType; + +/// Size of one BN254 field element in bytes. +const FIELD_SIZE: usize = 32; + +/// C1 → C5 pk_commitment consistency link. +pub struct C1ToC5PkCommitmentLink; + +impl CommitmentLink for C1ToC5PkCommitmentLink { + fn name(&self) -> &'static str { + "C1→C5 pk_commitment" + } + + fn source_proof_type(&self) -> ProofType { + ProofType::C1PkGeneration + } + + fn target_proof_type(&self) -> ProofType { + ProofType::C5PkAggregation + } + + fn scope(&self) -> LinkScope { + LinkScope::CrossParty + } + + fn extract_source_values(&self, public_signals: &[u8]) -> Vec { + // C1 outputs: (sk_commitment, pk_commitment, e_sm_commitment) — 3 fields, no public inputs + // pk_commitment is at field index 1 (bytes 32..64) + if public_signals.len() < 3 * FIELD_SIZE { + return vec![]; + } + let mut value = [0u8; FIELD_SIZE]; + value.copy_from_slice(&public_signals[FIELD_SIZE..2 * FIELD_SIZE]); + vec![value] + } + + fn check_consistency( + &self, + source_values: &[FieldValue], + target_public_signals: &[u8], + ) -> bool { + if source_values.is_empty() { + // No source values to check — vacuously consistent. + return true; + } + + if target_public_signals.len() < 2 * FIELD_SIZE { + // Target proof is present but has malformed/truncated signals — non-consistent. + return false; + } + + let source_pk_commitment = &source_values[0]; + + // C5 public_signals: [expected_pk_commitments[0..H], pk_agg_commitment] + // H = total_fields - 1 (last field is the output) + let total_fields = target_public_signals.len() / FIELD_SIZE; + if total_fields < 2 { + // Target proof present but not enough fields — non-consistent. + return false; + } + let h = total_fields - 1; + + // Check if the source pk_commitment appears in any of the H input fields + for i in 0..h { + let offset = i * FIELD_SIZE; + if target_public_signals[offset..offset + FIELD_SIZE] == *source_pk_commitment { + return true; + } + } + + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_field(val: u8) -> [u8; 32] { + let mut f = [0u8; 32]; + f[31] = val; + f + } + + #[test] + fn extract_pk_commitment_from_c1() { + let link = C1ToC5PkCommitmentLink; + let sk = make_field(1); + let pk = make_field(2); + let esm = make_field(3); + let mut signals = Vec::new(); + signals.extend_from_slice(&sk); + signals.extend_from_slice(&pk); + signals.extend_from_slice(&esm); + + let values = link.extract_source_values(&signals); + assert_eq!(values.len(), 1); + assert_eq!(values[0], pk); + } + + #[test] + fn consistency_passes_when_pk_present_in_c5() { + let link = C1ToC5PkCommitmentLink; + let pk = make_field(42); + let source_values = vec![pk]; + + // C5: [pk_comm_0, pk_comm_1(=42), pk_agg_commitment] + let mut c5_signals = Vec::new(); + c5_signals.extend_from_slice(&make_field(10)); + c5_signals.extend_from_slice(&pk); + c5_signals.extend_from_slice(&make_field(99)); + + assert!(link.check_consistency(&source_values, &c5_signals)); + } + + #[test] + fn consistency_fails_when_pk_missing_from_c5() { + let link = C1ToC5PkCommitmentLink; + let pk = make_field(42); + let source_values = vec![pk]; + + // C5: [pk_comm_0, pk_comm_1, pk_agg_commitment] — neither matches 42 + let mut c5_signals = Vec::new(); + c5_signals.extend_from_slice(&make_field(10)); + c5_signals.extend_from_slice(&make_field(20)); + c5_signals.extend_from_slice(&make_field(99)); + + assert!(!link.check_consistency(&source_values, &c5_signals)); + } + + #[test] + fn short_source_signals_treated_as_consistent() { + let link = C1ToC5PkCommitmentLink; + // Too short for C1 — extract returns empty, so vacuously consistent + assert!(link.extract_source_values(&[0u8; 60]).is_empty()); + assert!(link.check_consistency(&[], &[0u8; 31])); + } + + #[test] + fn short_target_signals_treated_as_inconsistent() { + let link = C1ToC5PkCommitmentLink; + // Source has valid data but target C5 is truncated — non-consistent + assert!(!link.check_consistency(&[make_field(1)], &[0u8; 31])); + // Only one field (< 2 required) — non-consistent + assert!(!link.check_consistency(&[make_field(1)], &make_field(1))); + } +} diff --git a/crates/zk-prover/src/actors/commitment_links/mod.rs b/crates/zk-prover/src/actors/commitment_links/mod.rs new file mode 100644 index 0000000000..c368209c8f --- /dev/null +++ b/crates/zk-prover/src/actors/commitment_links/mod.rs @@ -0,0 +1,62 @@ +// 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. + +//! Cross-circuit commitment consistency links. +//! +//! Each [`CommitmentLink`] defines a relationship between two proof types +//! where a commitment value produced by one circuit must match a value +//! consumed or produced by another circuit. The +//! [`CommitmentConsistencyChecker`](super::commitment_consistency_checker::CommitmentConsistencyChecker) +//! evaluates these links as verified proofs arrive. + +pub mod c1_to_c5; + +use e3_events::ProofType; + +/// A 32-byte BN254 field element extracted from public signals. +pub type FieldValue = [u8; 32]; + +/// Whether the linked proofs come from the same node or different nodes. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum LinkScope { + /// Both proofs are generated by the same party (same Ethereum address). + SameParty, + /// The source proof is per-party while the target is from a different + /// party (e.g. per-node C1 vs aggregator C5). + CrossParty, +} + +/// Defines a cross-circuit commitment consistency check. +/// +/// Implementations extract commitment values from the public signals of a +/// *source* proof type and verify they are consistent with the public signals +/// of a *target* proof type. +pub trait CommitmentLink: Send + Sync { + /// Human-readable name for logging. + fn name(&self) -> &'static str; + + /// The proof type that *produces* the commitment value. + fn source_proof_type(&self) -> ProofType; + + /// The proof type that *consumes* or must agree with the commitment value. + fn target_proof_type(&self) -> ProofType; + + /// Relationship scope between source and target parties. + fn scope(&self) -> LinkScope; + + /// Extract the commitment value(s) from the source proof's public signals. + fn extract_source_values(&self, public_signals: &[u8]) -> Vec; + + /// Return `true` when `source_values` are consistent with + /// `target_public_signals`. + fn check_consistency(&self, source_values: &[FieldValue], target_public_signals: &[u8]) + -> bool; +} + +/// Returns the default set of commitment links to register. +pub fn default_links() -> Vec> { + vec![Box::new(c1_to_c5::C1ToC5PkCommitmentLink)] +} diff --git a/crates/zk-prover/src/actors/mod.rs b/crates/zk-prover/src/actors/mod.rs index 0edbb62178..4c2d999470 100644 --- a/crates/zk-prover/src/actors/mod.rs +++ b/crates/zk-prover/src/actors/mod.rs @@ -35,6 +35,9 @@ pub mod accusation_manager; pub mod accusation_manager_ext; +pub mod commitment_consistency_checker; +pub mod commitment_consistency_checker_ext; +pub mod commitment_links; pub mod node_proof_aggregator; pub mod proof_request; pub mod proof_verification; @@ -43,6 +46,8 @@ pub mod zk_actor; pub use accusation_manager::AccusationManager; pub use accusation_manager_ext::AccusationManagerExtension; +pub use commitment_consistency_checker::CommitmentConsistencyChecker; +pub use commitment_consistency_checker_ext::CommitmentConsistencyCheckerExtension; pub use node_proof_aggregator::NodeProofAggregator; pub use proof_request::ProofRequestActor; pub use proof_verification::{ diff --git a/crates/zk-prover/src/actors/proof_verification.rs b/crates/zk-prover/src/actors/proof_verification.rs index 1b87fb9d63..10b1aa0869 100644 --- a/crates/zk-prover/src/actors/proof_verification.rs +++ b/crates/zk-prover/src/actors/proof_verification.rs @@ -122,6 +122,13 @@ impl ProofVerificationActor { ); return; } + if *proof != signed.payload.proof { + error!( + "Proof mismatch for key from party {}: key.proof differs from signed_payload.payload.proof — rejecting", + msg.key.party_id + ); + return; + } // Store the signed payload so we can reference it in the verification response self.pending.insert( @@ -244,6 +251,7 @@ impl Handler> for ProofVerificationActor { address: recovered_signer, proof_type: ProofType::C0PkBfv, data_hash, + public_outputs: signed_payload.payload.proof.public_signals.clone(), }, ec, ) { diff --git a/crates/zk-prover/src/actors/share_verification.rs b/crates/zk-prover/src/actors/share_verification.rs index 50765d59c3..89ff8c33cb 100644 --- a/crates/zk-prover/src/actors/share_verification.rs +++ b/crates/zk-prover/src/actors/share_verification.rs @@ -35,6 +35,7 @@ use e3_events::{ SignedProofPayload, TypedEvent, VerificationKind, VerifyShareDecryptionProofsRequest, VerifyShareProofsRequest, ZkRequest, ZkResponse, }; +use e3_utils::utility_types::ArcBytes; use e3_utils::NotifySync; use tracing::{error, info, warn}; @@ -86,6 +87,8 @@ struct PendingVerification { party_addresses: HashMap, /// Cached (proof_type, data_hash) per party — for emitting ProofVerificationPassed. party_proof_hashes: HashMap>, + /// Cached (proof_type, public_signals) per party — for commitment consistency checking. + party_public_signals: HashMap>, } /// Actor that handles C2/C3/C4 share proof verification. @@ -236,6 +239,7 @@ impl ShareVerificationActor { // Compute proof hashes for ECDSA-passed parties (for ProofVerificationPassed on success) let mut party_proof_hashes: HashMap> = HashMap::new(); + let mut party_public_signals: HashMap> = HashMap::new(); for party in &ecdsa_passed_parties { let hashes: Vec<(ProofType, [u8; 32])> = party .signed_proofs() @@ -249,7 +253,18 @@ impl ShareVerificationActor { (signed.payload.proof_type, keccak256(&msg).into()) }) .collect(); + let signals: Vec<(ProofType, ArcBytes)> = party + .signed_proofs() + .iter() + .map(|signed| { + ( + signed.payload.proof_type, + signed.payload.proof.public_signals.clone(), + ) + }) + .collect(); party_proof_hashes.insert(party.party_id(), hashes); + party_public_signals.insert(party.party_id(), signals); } self.pending.insert( @@ -263,6 +278,7 @@ impl ShareVerificationActor { dispatched_party_ids, party_addresses, party_proof_hashes, + party_public_signals, }, ); @@ -442,7 +458,12 @@ impl ShareVerificationActor { .get(&result.sender_party_id) .copied() .unwrap_or_default(); - for &(proof_type, data_hash) in hashes { + let signals = pending.party_public_signals.get(&result.sender_party_id); + for (i, &(proof_type, data_hash)) in hashes.iter().enumerate() { + let public_outputs = signals + .and_then(|s| s.get(i)) + .map(|(_, ps)| ps.clone()) + .unwrap_or_default(); if let Err(err) = self.bus.publish( ProofVerificationPassed { e3_id: pending.e3_id.clone(), @@ -450,6 +471,7 @@ impl ShareVerificationActor { address: addr, proof_type, data_hash, + public_outputs, }, pending.ec.clone(), ) { diff --git a/crates/zk-prover/src/lib.rs b/crates/zk-prover/src/lib.rs index 2c65f9509d..fc00caa903 100644 --- a/crates/zk-prover/src/lib.rs +++ b/crates/zk-prover/src/lib.rs @@ -15,9 +15,9 @@ mod traits; mod witness; pub use actors::{ - setup_zk_actors, AccusationManager, AccusationManagerExtension, ProofRequestActor, - ProofVerificationActor, ShareVerificationActor, ZkActors, ZkVerificationRequest, - ZkVerificationResponse, + setup_zk_actors, AccusationManager, AccusationManagerExtension, + CommitmentConsistencyCheckerExtension, ProofRequestActor, ProofVerificationActor, + ShareVerificationActor, ZkActors, ZkVerificationRequest, ZkVerificationResponse, }; pub use backend::{SetupStatus, ZkBackend};