Skip to content

feat: add cross-circuit commitment consistency checker#1485

Merged
ctrlc03 merged 3 commits into
mainfrom
feat/ccc-checker
Mar 26, 2026
Merged

feat: add cross-circuit commitment consistency checker#1485
ctrlc03 merged 3 commits into
mainfrom
feat/ccc-checker

Conversation

@hmzakhalid

@hmzakhalid hmzakhalid commented Mar 25, 2026

Copy link
Copy Markdown
Collaborator

Closes #1472
Adds a generic, extensible framework for detecting cross-circuit commitment mismatches, where a party's ZK proof outputs in one circuit are inconsistent with the public inputs or outputs expected by another circuit. On mismatch, the checker emits ProofVerificationFailed, which flows into the existing AccusationManager quorum -> slash pipeline unchanged.

┌────────────────────────┐
│ ShareVerificationActor │── ProofVerificationPassed ──┐
│ ProofVerificationActor │── ProofVerificationPassed ──┤
└────────────────────────┘                             │
                                                       ▼
                                          ┌─────────────────────────────────┐
                                          │ CommitmentConsistencyChecker    │
                                          │                                 │
                                          │  • Caches verified public_      │
                                          │    outputs per (party, circuit) │
                                          │  • Registered CommitmentLinks   │
                                          │  • When both halves present:    │
                                          │    extract & compare            │
                                          └────────────┬────────────────────┘
                                                       │ mismatch?
                                                       ▼
                                          ProofVerificationFailed
                                                       │
                                                       ▼
                                          ┌─────────────────────────┐
                                          │   AccusationManager     │ ← unchanged
                                          │   (quorum → slash)      │
                                          └─────────────────────────┘

How to add a new cross-circuit check

When implementing a new circuit pair where commitments should be cross-validated, follow these steps:

Step 1: Register the output layout

Add your circuit's output field positions to CircuitOutputLayout::for_circuit():

CircuitName::YourCircuit => Self {
    fields: vec![
        ("your_commitment", 0),  // 0 = last field in public_signals
    ],
},

Step 2: Implement CommitmentLink

pub struct C2ToC3MessageCommitmentLink;

impl CommitmentLink for C2ToC3MessageCommitmentLink {
    fn name(&self) -> &'static str { "C2→C3 message_commitment" }
    fn source_proof_type(&self) -> ProofType { ProofType::C2aSkShareComputation }
    fn target_proof_type(&self) -> ProofType { ProofType::C3aSkShareEncryption }
    fn scope(&self) -> LinkScope { LinkScope::PerParty }

    fn extract_source_commitments(&self, public_signals: &[u8]) -> Option<Vec<[u8; 32]>> {
        // C2 outputs commitment at end-0
        let layout = CircuitOutputLayout::for_circuit(CircuitName::ShareComputation);
        let commitment = layout.extract_field(public_signals, "commitment")?;
        Some(vec![commitment])
    }

    fn extract_target_commitments(&self, public_signals: &[u8]) -> Option<Vec<[u8; 32]>> {
        // C3 takes msg_commitment as pub input at position 1
        // (position 0 is expected_pk_commitment from C0)
        let commitment = CircuitOutputLayout::extract_input_field(public_signals, 1)?;
        Some(vec![commitment])
    }
}

Step 3: Register the link

Add it to the checker's link list in CommitmentConsistencyCheckerExtension::on_event():

vec![
    Box::new(C1ToC5PkCommitmentLink),
    Box::new(C2ToC3MessageCommitmentLink),  // ← new
]

That's it. The checker handles caching, matching, comparison, and emitting ProofVerificationFailed automatically.

Summary by CodeRabbit

  • New Features
    • Added a runtime commitment-consistency checker that monitors completed proof verifications and warns on mismatches.
    • Tracks and forwards verified proof public outputs to enable downstream consistency checks.
    • Introduced cross‑circuit and same‑party commitment links (including a C1→C5 PK link) to validate commitments across proof stages.

@hmzakhalid hmzakhalid requested a review from ctrlc03 March 25, 2026 17:22
@coderabbitai

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 89684034-ff0b-498b-8e41-4fd24ccc6c18

📥 Commits

Reviewing files that changed from the base of the PR and between 21e644d and 67ab890.

📒 Files selected for processing (2)
  • crates/zk-prover/src/actors/commitment_links/c1_to_c5.rs
  • crates/zk-prover/src/actors/proof_verification.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/zk-prover/src/actors/proof_verification.rs
  • crates/zk-prover/src/actors/commitment_links/c1_to_c5.rs

📝 Walkthrough

Walkthrough

Adds a CommitmentConsistencyChecker actor and extension, link definitions, and plumbing to propagate proof public outputs. The checker subscribes to proof verification events, caches verified public outputs by (address, proof type), evaluates registered CommitmentLink rules (same-party and cross-party), and emits warnings on mismatches. Builder wiring registers the extension.

Changes

Cohort / File(s) Summary
Checker actor & extension
crates/zk-prover/src/actors/commitment_consistency_checker.rs, crates/zk-prover/src/actors/commitment_consistency_checker_ext.rs
New Actix actor that subscribes to ProofVerificationPassed, caches (address, proof type) → public outputs, evaluates CommitmentLinks across SameParty and CrossParty scopes, and an extension that starts the actor on CommitteeFinalized while preventing duplicate startups.
Commitment link definitions
crates/zk-prover/src/actors/commitment_links/mod.rs, crates/zk-prover/src/actors/commitment_links/c1_to_c5.rs
New CommitmentLink trait, LinkScope enum, FieldValue type, and default_links(); implements C1ToC5PkCommitmentLink to extract C1 pk_commitment and verify presence in C5 aggregated commitments with unit tests.
Actors module & re-exports
crates/zk-prover/src/actors/mod.rs, crates/zk-prover/src/lib.rs
Added modules and re-exports for the new commitment-checking components (CommitmentConsistencyChecker, CommitmentConsistencyCheckerExtension) to the zk-prover public surface.
Event and context plumbing
crates/events/src/enclave_event/proof_verification_passed.rs, crates/request/src/context.rs
ProofVerificationPassed now includes public_outputs: ArcBytes. E3Context::init_recipients() adds "commitment_consistency_checker" key for recipient registration.
Verification pipeline changes
crates/zk-prover/src/actors/proof_verification.rs, crates/zk-prover/src/actors/share_verification.rs
Proof verification now populates ProofVerificationPassed.public_outputs from proof public signals. Share verification caches per-party ordered public_signals and aligns them to emitted verification events.
Builder integration
crates/ciphernode-builder/src/ciphernode_builder.rs
Registers CommitmentConsistencyCheckerExtension in the E3 router builder during ciphernode setup.

Sequence Diagram

sequenceDiagram
    participant ShareVerification as Share Verification
    participant ProofVerification as Proof Verification
    participant EventBus as Event Bus
    participant ConsistencyChecker as Consistency Checker
    participant CommitmentLink as Commitment Link

    ShareVerification->>ShareVerification: cache public_signals per party
    ShareVerification->>ProofVerification: forward signed proofs / signals
    ProofVerification->>ProofVerification: verify proof, include public_outputs
    ProofVerification->>EventBus: publish ProofVerificationPassed(public_outputs)
    EventBus->>ConsistencyChecker: deliver ProofVerificationPassed
    ConsistencyChecker->>ConsistencyChecker: store (address, proof_type) -> public_outputs + party_id
    ConsistencyChecker->>CommitmentLink: for each link, extract_source_values / check_consistency
    CommitmentLink-->>ConsistencyChecker: consistency result (ok|mismatch)
    ConsistencyChecker->>ConsistencyChecker: warn! on mismatch
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly related PRs

Suggested labels

ciphernode

Suggested reviewers

  • ctrlc03
  • ryardley

Poem

🐰
I nibble on signals, hop through the log,
I stamp out mismatches like carrots in fog.
C1 and C5, I check with delight,
Warnings I whisper when things aren't quite right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main implementation: a new cross-circuit commitment consistency checker framework that detects mismatches between ZK proof outputs across circuits.
Linked Issues check ✅ Passed The PR implementation fully addresses #1472's objective to detect commitment mismatches between proofs and emit ProofVerificationFailed events that feed into the AccusationManager pipeline.
Out of Scope Changes check ✅ Passed All changes directly support the commitment consistency checker feature; no unrelated modifications were introduced outside the defined scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ccc-checker

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel

vercel Bot commented Mar 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
crisp Ready Ready Preview, Comment Mar 26, 2026 6:09am
enclave-docs Ready Ready Preview, Comment Mar 26, 2026 6:09am

Request Review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
crates/zk-prover/src/actors/share_verification.rs (1)

90-91: Keep hash and outputs in the same cached record.

Line 463 rejoins party_proof_hashes and party_public_signals purely by vector index. That works today because both vectors are built from the same iteration order, but any future ordering drift in signed_proofs() will pair the wrong hash with the wrong public_outputs, and the unwrap_or_default() fallback will silently disable consistency checks. A single per-proof cache entry holding proof_type, data_hash, and public_outputs avoids that footgun.

Also applies to: 242-267, 461-466

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/zk-prover/src/actors/share_verification.rs` around lines 90 - 91, The
cache currently stores party_public_signals (HashMap<u64, Vec<(ProofType,
ArcBytes)>>) separately from party_proof_hashes and later rejoins them by vector
index, which is fragile; change the cache to store a single per-proof record
struct (e.g., ProofCacheEntry) that contains proof_type, data_hash (the proof
hash), and public_outputs (ArcBytes) and replace uses of party_public_signals
and party_proof_hashes with a single HashMap<u64, Vec<ProofCacheEntry>>; update
construction sites (where signed_proofs() is iterated to build caches,
referenced in functions around the previous indices at lines ~242-267 and
~461-466) to populate all fields atomically and update lookup/consumption sites
to use the combined entry so no index-based rejoining or unwrap_or_default()
fallbacks are needed.
crates/zk-prover/src/actors/commitment_consistency_checker.rs (1)

121-170: Cross-party check has O(n·m) complexity — acceptable for current committee sizes.

The nested iteration over all sources and targets is correct for the C1→C5 link semantics (each node's pk_commitment must appear in the aggregated C5). For large committees, consider indexing by extracted commitment values to reduce comparisons to O(n+m), but this is a nice-to-have optimization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/zk-prover/src/actors/commitment_consistency_checker.rs` around lines
121 - 170, The nested loops in check_cross_party_link produce O(n·m)
comparisons; to optimize, build an index (HashSet or HashMap) of extracted
commitment values from the targets (or sources when the new proof is a target)
and then probe that index for each source instead of iterating all targets.
Concretely, inside check_cross_party_link, replace the inner loop by first
collecting extracted values for each target via link.extract_target_values (or
link.extract_source_values if the API only provides that for the relevant role),
insert those values (or serialized forms) into a HashSet/HashMap keyed by the
commitment, then for each src call link.extract_source_values and check
membership with O(1) lookups, using link.check_consistency only when a candidate
match exists; keep the existing logging and behavior when no index hit is found.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/ciphernode-builder/src/ciphernode_builder.rs`:
- Around line 542-546: The CommitmentConsistencyCheckerExtension is wired up
(CommitmentConsistencyCheckerExtension::create and e3_builder.with) but the
checker only logs mismatches via warn! in check_same_party_link and
check_cross_party_link and therefore never publishes a ProofVerificationFailed
into the accusation pipeline; modify the checker (the functions
check_same_party_link and check_cross_party_link in
commitment_consistency_checker.rs) to publish a ProofVerificationFailed event to
the bus/accusation pipeline (the same mechanism AccusationManager consumes)
whenever a mismatch is detected, ensuring the published event includes the
offending party ID, commitment details, and context so
AccusationManager/quorum→slash flow can process it.

In `@crates/events/src/enclave_event/proof_verification_passed.rs`:
- Around line 35-36: The new required field public_outputs on the
ProofVerificationPassed event will break deserialization of older persisted
events; make it replay-safe by either adding #[serde(default)] to the
public_outputs field and ensuring ArcBytes implements Default (or provide a
function like default_arc_bytes()), or change the field type to Option<ArcBytes>
with #[serde(default, skip_serializing_if = "Option::is_none")]; update the
struct declaration for ProofVerificationPassed accordingly so older events
without public_outputs can still deserialize.

In `@crates/zk-prover/src/actors/commitment_consistency_checker.rs`:
- Around line 42-53: CommitmentConsistencyChecker currently stores a bus field
that is only used in setup() for subscription and does not emit
ProofVerificationFailed events yet; since this is an intentional phased rollout,
do not remove the bus field—leave CommitmentConsistencyChecker::bus and the
subscription logic in setup() as-is and add or keep a short TODO comment near
the bus field or setup() referencing ProofVerificationFailed to clarify that
event emission will be implemented in a future iteration.

In `@crates/zk-prover/src/actors/commitment_links/c1_to_c5.rs`:
- Around line 67-79: The function check_consistency() currently treats truncated
or missing C5 public_signals as vacuously consistent; change it so any
present-but-too-short target_public_signals is treated as non-consistent.
Concretely, in crates/zk-prover/src/actors/commitment_links/c1_to_c5.rs adjust
the early-return logic around source_values, target_public_signals and
total_fields: do not return true when target_public_signals.len() > 0 but
shorter than expected (i.e., < 2 * FIELD_SIZE or total_fields < 2); instead
return false for those cases while preserving the original true return only for
the case where target_public_signals is completely absent/empty and
source_values is empty (vacuum). Reference symbols: check_consistency(),
source_values, target_public_signals, FIELD_SIZE, total_fields,
source_pk_commitment.

In `@crates/zk-prover/src/actors/proof_verification.rs`:
- Line 247: Add an explicit equality check between msg.key.proof and
signed_payload.payload.proof before publishing the success event: compare the
two byte/proof fields (msg.key.proof vs signed_payload.payload.proof) right
after the existing verification step (the place that currently verifies
msg.key.proof) and, if they differ, return an error or publish a failure event
(and log details) instead of proceeding to publish public_outputs; this ensures
the published public_signals (signed_payload.payload.proof.public_signals) are
only emitted when both proof fields are identical.

---

Nitpick comments:
In `@crates/zk-prover/src/actors/commitment_consistency_checker.rs`:
- Around line 121-170: The nested loops in check_cross_party_link produce O(n·m)
comparisons; to optimize, build an index (HashSet or HashMap) of extracted
commitment values from the targets (or sources when the new proof is a target)
and then probe that index for each source instead of iterating all targets.
Concretely, inside check_cross_party_link, replace the inner loop by first
collecting extracted values for each target via link.extract_target_values (or
link.extract_source_values if the API only provides that for the relevant role),
insert those values (or serialized forms) into a HashSet/HashMap keyed by the
commitment, then for each src call link.extract_source_values and check
membership with O(1) lookups, using link.check_consistency only when a candidate
match exists; keep the existing logging and behavior when no index hit is found.

In `@crates/zk-prover/src/actors/share_verification.rs`:
- Around line 90-91: The cache currently stores party_public_signals
(HashMap<u64, Vec<(ProofType, ArcBytes)>>) separately from party_proof_hashes
and later rejoins them by vector index, which is fragile; change the cache to
store a single per-proof record struct (e.g., ProofCacheEntry) that contains
proof_type, data_hash (the proof hash), and public_outputs (ArcBytes) and
replace uses of party_public_signals and party_proof_hashes with a single
HashMap<u64, Vec<ProofCacheEntry>>; update construction sites (where
signed_proofs() is iterated to build caches, referenced in functions around the
previous indices at lines ~242-267 and ~461-466) to populate all fields
atomically and update lookup/consumption sites to use the combined entry so no
index-based rejoining or unwrap_or_default() fallbacks are needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f7288256-7196-4e44-999d-9bdc335e9af8

📥 Commits

Reviewing files that changed from the base of the PR and between 1f5b4ab and 21e644d.

📒 Files selected for processing (11)
  • crates/ciphernode-builder/src/ciphernode_builder.rs
  • crates/events/src/enclave_event/proof_verification_passed.rs
  • crates/request/src/context.rs
  • crates/zk-prover/src/actors/commitment_consistency_checker.rs
  • crates/zk-prover/src/actors/commitment_consistency_checker_ext.rs
  • crates/zk-prover/src/actors/commitment_links/c1_to_c5.rs
  • crates/zk-prover/src/actors/commitment_links/mod.rs
  • crates/zk-prover/src/actors/mod.rs
  • crates/zk-prover/src/actors/proof_verification.rs
  • crates/zk-prover/src/actors/share_verification.rs
  • crates/zk-prover/src/lib.rs

Comment thread crates/ciphernode-builder/src/ciphernode_builder.rs
Comment thread crates/events/src/enclave_event/proof_verification_passed.rs
Comment thread crates/zk-prover/src/actors/commitment_consistency_checker.rs
Comment thread crates/zk-prover/src/actors/commitment_links/c1_to_c5.rs Outdated
Comment thread crates/zk-prover/src/actors/proof_verification.rs

@ctrlc03 ctrlc03 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's first merge c1-c5 and reuse the utilities to extract signals?

Comment thread crates/zk-prover/src/actors/commitment_consistency_checker.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

expand accusation manager to deal with commitment mismatches between proofs

2 participants