Skip to content

feat: fault attribution and slashing crates [skip-line-limit]#1355

Merged
ctrlc03 merged 32 commits into
mainfrom
feat/fault-attribution-crates
Mar 11, 2026
Merged

feat: fault attribution and slashing crates [skip-line-limit]#1355
ctrlc03 merged 32 commits into
mainfrom
feat/fault-attribution-crates

Conversation

@hmzakhalid

@hmzakhalid hmzakhalid commented Feb 23, 2026

Copy link
Copy Markdown
Collaborator

Depends on #1354

For Committee Size M, we have another issue
Link: #1411

Happy path ZK Proof Generation Flow

C0 — BFV Public Key Proof

  1. EncryptionKeyPending arrives → ProofRequestActor dispatches ComputeRequest::zk(ZkRequest::PkBfv(...)) with a fresh CorrelationId
  2. Multithread ZK worker generates proof → ComputeResponse returned
  3. ProofRequestActor::handle_pk_bfv_response(): creates ProofPayload, signs it via SignedProofPayload::sign(), publishes EncryptionKeyCreated with the proof + signature attached
  4. Other nodes receive this key via DHT, emit EncryptionKeyReceived
  5. ProofVerificationActor validates: (a) proof present, (b) signed payload present, (c) ECDSA address recovery, (d) circuit name matches expected, (e) dispatches ZK verification to ZkActor
  6. On success: EncryptionKeyCreated + ProofVerificationPassed emitted
  7. On failure: SignedProofFailed + ProofVerificationFailed emitted → triggers accusation flow

C1–C3 — Threshold DKG Proofs

  1. ThresholdSharePendingProofRequestActor dispatches multiple parallel proof requests (C1 PkGeneration, C2a SkShareComputation, C2b ESmShareComputation, C3a/C3b ShareEncryption)
  2. All proofs collected in PendingThresholdProofs — when complete, signed and published as ThresholdShareCreated with proofs attached per-recipient

C4 — Decryption Key Share Proofs

  1. DecryptionShareProofsPending → dispatches C4a (secret key) + C4b (smudging noise) proofs
  2. Collected, signed, published as DecryptionKeyShared

C5 — PK Aggregation Proof

  1. PkAggregationProofPending → dispatch → sign → PkAggregationProofSigned

C6 — Threshold Share Decryption Proof

  1. ShareDecryptionProofPending → dispatch → sign → DecryptionshareCreated

C7 — Decrypted Shares Aggregation Proof

  1. AggregationProofPending → dispatch → sign → AggregationProofSigned

Malicious/Invalid ZK Proof Flow

Detection

  1. When any node's proof fails ECDSA or ZK verification, the verifying actor emits ProofVerificationFailed with:
    • accused_party_id, accused_address, proof_type, data_hash (keccak256 of proof data + public signals), signed_payload (for C3a/C3b forwarding)

Accusation Protocol

  1. AccusationManager::on_local_proof_failure():

    • Caches the failed result in received_data
    • Dedup check via accused_proofs HashSet — skip if already accused for (address, proof_type)
    • For C3a/C3b: includes signed_payload in the accusation so other nodes can re-verify
    • Creates ProofFailureAccusation, signs it with ECDSA (accusation_digest), broadcasts via gossip
    • Casts own vote (agrees: true), broadcasts AccusationVote via gossip
    • Starts 5-minute timeout
    • Replays any buffered votes that arrived early
  2. Other nodes receive ProofFailureAccusation via gossip → on_accusation_received():

    • Validates: e3_id match, accuser in committee, accused in committee, not own accusation, ECDSA signature valid
    • Checks local received_data cache:
      • If they have the data and their verification also failed → vote AGREE
      • If they have the data and verification passed → vote DISAGREE
      • If they don't have data but C3a/C3b payload is forwarded → async ZK re-verification
      • If they don't have data and no payload → abstain (return without voting)
  3. Votes collected in PendingAccusation — deduped by voter address

Quorum Decision

  1. check_quorum():
    • >= M agreeing votes: Check for equivocation (different data_hashes among agreeing voters). If equivocation → AccusationOutcome::Equivocation. Otherwise → AccusationOutcome::AccusedFaulted
    • Impossible to reach M: Check data hash diversity → Equivocation, AccuserLied (if only accuser says bad + same data), or Inconclusive
    • Timeout: After 5 minutes, resolve with whatever votes exist

On-Chain Submission

  1. SlashingManagerSolWriter receives AccusationQuorumReached:
    • Only submits if: correct chain, outcome is AccusedFaulted or Equivocation, and this node is the designated submitter (lowest-address agreeing voter)
    • Encodes attestation evidence as abi.encode(proofType, voters[], agrees[], dataHashes[], signatures[])
    • Calls SlashingManager.proposeSlash(e3Id, operator, reason, proof)

On-Chain Verification

  1. SlashingManager.proposeSlash():
    • Validates operator is committee member, policy is enabled and requires proof
    • Evidence replay protection: evidenceConsumed[keccak256(abi.encodePacked(chainId, e3Id, operator, proofType))]
    • _verifyAttestationEvidence(): Decodes votes, checks quorum >= M, verifies each vote signature (EIP-191 over VOTE_TYPEHASH-structured digest), ensures voters are active committee members, sorted ascending (no duplicates), accused cannot vote
    • Atomically creates proposal and executes slash

Slash Execution

  1. _executeSlash():
    • Slashes ticket balance and license bond via BondingRegistry
    • Optionally bans node
    • If affectsCommittee: calls ciphernodeRegistry.expelCommitteeMember() → if activeCount < thresholdM, triggers enclave.onE3Failed()
    • Routes slashed ticket funds to E3RefundManager escrow

Off-Chain Reaction

  1. SlashingManagerSolReader receives SlashExecuted event log → publishes Rust SlashExecuted event
  2. AccusationManager::on_slash_executed(): removes slashed operator from local committee, purges their votes from pending accusations and buffered votes
sequenceDiagram
    autonumber
    participant MN as 🔴 Malicious Node
    participant DHT as DHT / Gossip
    participant N1 as ✅ Committee Node 1<br/>(Accuser)
    participant N2 as ✅ Committee Node 2
    participant N3 as ✅ Committee Node 3
    participant ZK as ZK Backend (bb)
    participant AM as AccusationManager
    participant SW as SlashingManager<br/>SolWriter
    participant SM as SlashingManager<br/>Contract (on-chain)
    participant CR as CiphernodeRegistry<br/>(on-chain)
    participant BR as BondingRegistry<br/>(on-chain)
    participant EV as Enclave<br/>(on-chain)

    Note over MN,EV: Phase 1 — Bad Proof Broadcast

    MN->>MN: Generate ZK proof (e.g. C0 PK BFV)
    MN->>MN: Sign: personal_sign(keccak256(abi.encode(<br/>PROOF_PAYLOAD_TYPEHASH, chainId, e3Id,<br/>proofType, keccak256(zkProof), keccak256(publicSignals))))
    MN->>DHT: Broadcast SignedProofPayload

    Note over MN,EV: Phase 2 — Independent ZK Verification (each node)

    DHT->>N1: Receive signed proof
    DHT->>N2: Receive signed proof
    DHT->>N3: Receive signed proof

    par All nodes verify independently
        N1->>ZK: verify(circuit, proof, publicInputs)
        ZK-->>N1: ❌ FAIL
        N2->>ZK: verify(circuit, proof, publicInputs)
        ZK-->>N2: ❌ FAIL
        N3->>ZK: verify(circuit, proof, publicInputs)
        ZK-->>N3: ❌ FAIL
    end

    N1->>N1: Emit ProofVerificationFailed
    N2->>N2: Cache ReceivedProofData{data_hash, passed=false}
    N3->>N3: Cache ReceivedProofData{data_hash, passed=false}

    Note over MN,EV: Phase 3 — Accusation (first detector broadcasts)

    N1->>AM: on_local_proof_failure()
    AM->>AM: accusation_id = keccak256(<br/>abi.encodePacked(chainId, e3Id, accused, proofType))
    AM->>AM: Sign accusation with ACCUSATION_TYPEHASH
    AM->>AM: Cast own vote (agrees=true)
    AM->>AM: sign_vote_digest: personal_sign(keccak256(<br/>abi.encode(VOTE_TYPEHASH, chainId, e3Id,<br/>accusationId, voter, agrees, dataHash)))
    AM->>DHT: Broadcast ProofFailureAccusation
    AM->>DHT: Broadcast own AccusationVote

    Note over MN,EV: Phase 4 — Committee Attestation Voting

    DHT->>N2: Receive ProofFailureAccusation
    N2->>AM: Verify accusation signature
    N2->>AM: Lookup cached proof result → failed ✓
    AM->>AM: Create AccusationVote{agrees=true, dataHash}
    AM->>AM: sign_vote_digest (EIP-191 personal_sign)
    AM->>DHT: Broadcast AccusationVote

    DHT->>N3: Receive ProofFailureAccusation
    N3->>AM: Verify accusation signature
    N3->>AM: Lookup cached proof result → failed ✓
    AM->>AM: Create AccusationVote{agrees=true, dataHash}
    AM->>AM: sign_vote_digest (EIP-191 personal_sign)
    AM->>DHT: Broadcast AccusationVote

    Note over MN,EV: Phase 5 — Quorum Reached (≥ M votes)

    DHT->>N1: Collect votes from N2, N3
    DHT->>N2: Collect votes from N1, N3
    DHT->>N3: Collect votes from N1, N2

    AM->>AM: votes_for.len() ≥ threshold_M → Quorum!
    AM->>AM: Determine outcome: AccusedFaulted
    AM->>AM: Emit AccusationQuorumReached

    Note over MN,EV: Phase 6 — Designated Submitter Selection

    AM->>AM: Designated submitter = min(voter addresses)<br/>in votes_for (deterministic, exactly 1 node submits)

    Note over MN,EV: Phase 7 — On-Chain Slash Submission

    SW->>SW: encode_attestation_evidence:<br/>sort votes by voter address ascending<br/>abi.encode(proofType, voters[], agrees[],<br/>dataHashes[], signatures[])
    SW->>SM: proposeSlash(e3Id, accused, reason, evidence)

    Note over MN,EV: Phase 8 — On-Chain Attestation Verification

    SM->>SM: Check: policy.enabled ∧ policy.requiresProof
    SM->>SM: evidenceKey = keccak256(e3Id, operator, keccak256(proof))
    SM->>SM: Check: !evidenceConsumed[evidenceKey] (replay protection)

    SM->>SM: _verifyAttestationEvidence()
    SM->>SM: Decode: (proofType, voters[], agrees[], dataHashes[], sigs[])
    SM->>SM: accusationId = keccak256(<br/>abi.encodePacked(block.chainid, e3Id, operator, proofType))

    SM->>CR: getCommitteeViability(e3Id)
    CR-->>SM: (activeCount, thresholdM, thresholdN, viable)
    SM->>SM: Require: numVotes ≥ thresholdM

    loop For each voter (must be sorted ascending)
        SM->>SM: Require: voter > prevVoter (no duplicates)
        SM->>SM: Require: agrees[i] == true
        SM->>CR: isCommitteeMember(e3Id, voter)
        CR-->>SM: true ✓
        SM->>SM: ethSignedHash = toEthSignedMessageHash(<br/>keccak256(abi.encode(VOTE_TYPEHASH,<br/>chainId, e3Id, accusationId, voter, agrees, dataHash)))
        SM->>SM: Require: ECDSA.recover(ethSignedHash, sig) == voter
    end

    Note over MN,EV: Phase 9 — Slash Execution (atomic)

    SM->>SM: Create SlashProposal (proofVerified=true, executableAt=now)
    SM->>SM: _executeSlash()

    SM->>BR: slashTicketBalance(operator, ticketPenalty, reason)
    BR-->>SM: actualTicketSlashed
    SM->>BR: slashLicenseBond(operator, licensePenalty, reason)

    opt policy.banNode == true
        SM->>SM: banned[operator] = true
        SM->>SM: Emit NodeBanUpdated
    end

    opt policy.affectsCommittee == true
        SM->>CR: expelCommitteeMember(e3Id, operator, reason)
        CR-->>SM: (activeCount, thresholdM)
        opt activeCount < thresholdM
            SM->>EV: onE3Failed(e3Id, failureReason)
            Note over EV: E3 marked as failed,<br/>refund flow triggered
        end
    end

    opt actualTicketSlashed > 0
        SM->>SM: escrowSlashedFundsToRefund(e3Id, amount)
        SM->>BR: redirectSlashedTicketFunds(refundManager, amount)
        SM->>EV: escrowSlashedFunds(e3Id, amount)
    end

    SM->>SM: Emit SlashExecuted
    SM-->>SW: Transaction receipt ✅

    Note over MN,EV: 🔴 Malicious node slashed: tickets + license bond confiscated,<br/>expelled from committee, optionally banned from network
Loading

Summary by CodeRabbit

  • New Features

    • Off-chain accusation/quorum voting and automated attestation-based slashing flow.
    • Committee model & member-expulsion events with downstream expulsion handling and E3-stage failure processing.
    • Slashing manager integration for on-chain slash proposals and enclave failure processing.
    • Recovery API to withdraw orphaned slashed funds in terminal E3 states.
    • New verification result events (passed/failed) and vote/accusation event types.
  • Tests

    • Added comprehensive slashing and encoding integration tests.

@vercel

vercel Bot commented Feb 23, 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 11, 2026 7:58pm
enclave-docs Ready Ready Preview, Comment Mar 11, 2026 7:58pm

Request Review

@hmzakhalid hmzakhalid changed the title feat: CN fault attribution and slashing crates [skip-line-limit] feat: fault attribution and slashing crates [skip-line-limit] Feb 23, 2026
@hmzakhalid hmzakhalid marked this pull request as draft February 23, 2026 09:57
@coderabbitai

coderabbitai Bot commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds off‑chain accusation quorum, attestation‑based slashing integration, new EnclaveEvent variants (expulsion/failure/stage), committee abstractions, and wiring across aggregator, zk‑prover, evm, keyshare, sortition, and contracts to support proposing and executing slashes and expelling committee members.

Changes

Cohort / File(s) Summary
Events & Committee types
crates/events/src/enclave_event/... (new files), crates/events/src/enclave_event/mod.rs, crates/events/src/committee.rs, crates/events/src/ordered_set.rs, crates/events/src/enclave_event/threshold_share_pending.rs, crates/events/src/enclave_event/signed_proof.rs
Adds many new EnclaveEvent messages (AccusationVote, AccusationQuorumReached, ProofFailureAccusation, ProofVerificationFailed/Passed, SlashExecuted, CommitteeMemberExpelled), Committee type with O(1) lookup, ordered_set.remove, and ProofPayload.typehash()/encode_fault_evidence.
Accusation & Accuser actors (zk-prover)
crates/zk-prover/src/actors/accusation_manager.rs, .../accusation_manager_ext.rs, .../mod.rs, crates/zk-prover/src/lib.rs
Implements AccusationManager actor and Extension to run off‑chain quorum voting, verify signatures, publish AccusationQuorumReached, and wire startup from CommitteeFinalized. Exports AccusationManager and extension.
Proof verification & share flows (zk-prover)
crates/zk-prover/src/actors/proof_verification.rs, .../share_verification.rs, .../proof_request.rs, .../proof_verification.rs
Refactors verification to emit ProofVerificationPassed/Failed with data_hash, tracks pending verifications and recipient_party_ids, and updates messaging/terminology from T0→C0.
Slashing manager EVM integration
crates/evm/src/slashing_manager_sol_reader.rs, slashing_manager_sol_writer.rs, lib.rs, evm/.../ciphernode_registry_sol.rs, enclave_sol_writer.rs
Adds SlashingManager reader/writer: reader extracts SlashExecuted, writer submits proposeSlash on AccusationQuorumReached (encodes attestation evidence), and EnclaveSolWriter handles E3StageChanged/failure processing.
Aggregator & keyshare expulsion handling
crates/aggregator/src/committee_finalizer.rs, publickey_aggregator.rs, keyshare_created_filter_buffer.rs, crates/keyshare/src/threshold_keyshare.rs, encryption_key_collector.rs, decryption_key_shared_collector.rs, threshold_share_collector.rs
Wires E3Failed/E3StageChanged/CommitteeMemberExpelled into CommitteeFinalizer and aggregators; tracks expelled_before_finalization, updates submission_order for keyshares, emits ExpelParty messages and filters contributions from expelled nodes.
Sortition & repositories
crates/sortition/src/repo.rs, sortition.rs
Switches finalized_committees storage from Vec to Committee, renames/get_committee API, and adds handlers for expulsion/E3 lifecycle events.
Builder, config & startup
crates/ciphernode-builder/src/ciphernode_builder.rs, crates/config/src/contract.rs, crates/entrypoint/src/config/setup.rs, crates/entrypoint/src/start/*.rs, templates/default/enclave.config.yaml
Adds slashing_manager config field, CiphernodeBuilder.with_contract_slashing_manager(), devnet SlashingManager entry and wiring to attach reader/writer and AccusationManagerExtension.
Contracts: Slashing & refund changes
packages/enclave-contracts/contracts/slashing/SlashingManager.sol, interfaces/ISlashingManager.sol, contracts/E3RefundManager.sol, interfaces/IE3RefundManager.sol, interfaces/IEnclave.sol, tests & fixtures under packages/enclave-contracts/test/...
Replaces ZK-proof verification with attestation/quorum-based verification (VOTE_TYPEHASH), adds attestation verification, new error types, processE3Failure hook, withdrawOrphanedSlashedFunds, and extensive test updates to multi-voter attestation flows.
Tests & integration
crates/zk-prover/tests/slashing_integration_tests.rs, crates/tests/tests/integration.rs, packages/enclave-contracts/test/**
Adds comprehensive Rust + on‑chain slashing integration tests and updates expectations for ProofVerificationPassed events and attestation test helpers (signAndEncodeAttestation).
Supporting wiring & small changes
crates/net/src/net_event_translator.rs, crates/request/src/context.rs, crates/request/src/router.rs, crates/evm/src/ciphernode_registry_sol.rs, packages/enclave-contracts/artifacts/*.json
Adds forwardable event variants, accusation_manager recipient, E3StageChanged → E3RequestComplete routing, expel event extraction for chain logs, and artifact ABI updates (IEnclave processE3Failure, ISlashingManager errors).

Sequence Diagram(s)

sequenceDiagram
    participant PV as ProofVerificationActor
    participant AM as AccusationManager
    participant SMW as SlashingManagerSolWriter
    participant SC as SlashingManager (On‑Chain)
    participant ER as E3RefundManager

    PV->>AM: publish ProofVerificationFailed(e3_id, accused, proof_type, data_hash)
    AM->>AM: create & broadcast ProofFailureAccusation / AccusationVote
    loop collect votes
        AM->>AM: receive AccusationVote (validate signature)
    end
    AM->>AM: quorum reached -> emit AccusationQuorumReached
    AM->>SMW: forward AccusationQuorumReached
    SMW->>SMW: encode attestation evidence (voters, agrees, dataHashes, signatures)
    SMW->>SC: proposeSlash(e3Id, operator, reason, proof)
    SC->>SC: verify attestation (VOTE_TYPEHASH, quorum)
    SC->>ER: transfer slash funds / emit SlashExecuted
    ER->>ER: calculate refunds / orphaned handling
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • PR #1354 — closely related: implements slashing/committee‑expulsion lifecycle, SlashingManager and CommitteeMemberExpelled integrations.
  • PR #1377 — related: modifies aggregator/publickey_aggregator and event handling for expulsion/proof events.
  • PR #1161 — related: adds E3 lifecycle event types and wiring (E3Failed, E3StageChanged) used here.

Suggested labels

ciphernode, contracts

Suggested reviewers

  • ctrlc03

Poem

🐰 I hopped through proofs and votes tonight,
I gathered signatures by lantern light,
A quorum thumped, the slashing bell did chime,
Faults were found, expelled — we stitched the rhyme. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: fault attribution and slashing crates' directly summarizes the main change: introducing fault attribution and slashing functionality across multiple crates in the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/fault-attribution-crates

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.

@hmzakhalid

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@vercel vercel Bot temporarily deployed to Preview – crisp March 10, 2026 18:27 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs March 10, 2026 18:27 Inactive
Comment thread crates/zk-prover/src/actors/accusation_manager.rs Outdated
Comment thread crates/zk-prover/src/actors/accusation_manager.rs
Comment thread packages/enclave-contracts/contracts/slashing/SlashingManager.sol
@vercel vercel Bot temporarily deployed to Preview – crisp March 11, 2026 13:01 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs March 11, 2026 13:01 Inactive
Comment thread crates/aggregator/src/publickey_aggregator.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.

2 participants