Skip to content
Closed
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
23 changes: 14 additions & 9 deletions agent/flow-trace/00_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
| --- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | [01_REGISTRATION.md](01_REGISTRATION.md) | `setup`, `register`, `activate`, `status` CLI commands. On-chain registration into BondingRegistry → CiphernodeRegistry IMT. Rust-side event detection. |
| 2 | [02_TOKENS_AND_ACTIVATION.md](02_TOKENS_AND_ACTIVATION.md) | ENCL license bonding, USDC→ETK ticket purchasing, unbonding, burning, exit queue, claiming. Activation thresholds and the `_updateOperatorStatus` formula. |
| 3 | [03_E3_REQUEST_AND_COMMITTEE.md](03_E3_REQUEST_AND_COMMITTEE.md) | E3 request on-chain flow, fee payment, committee request, IMT snapshot. Rust-side sortition (score-based), on-chain ticket submission, committee finalization, `CiphernodeSelected` event. |
| 3 | [03_E3_REQUEST_AND_COMMITTEE.md](03_E3_REQUEST_AND_COMMITTEE.md) | E3 request on-chain flow, fee payment, committee request, IMT snapshot. Rust-side sortition (score-based), buffered ticket submission candidates, on-chain committee finalization, `AggregatorSelected`, and `CiphernodeSelected`. |
| 4 | [04_DKG_AND_COMPUTATION.md](04_DKG_AND_COMPUTATION.md) | Full DKG with ZK proof pipeline: BFV keygen → C0 proof → encryption key exchange → TrBFV share generation → C1/C2/C3 proofs → share verification → Shamir secret sharing → encrypted share broadcast → C4 proofs → decryption key reconstruction. C5 proof for PK aggregation. Ciphertext output → C6 proof for decryption shares → C7 proof for plaintext → rewards. |
| 5 | [05_FAILURE_REFUND_SLASHING.md](05_FAILURE_REFUND_SLASHING.md) | Timeout-based failure detection, `markE3Failed`, `processE3Failure`. Refund calculation (work-value allocation). Off-chain AccusationManager quorum protocol (proof failure → accusation → voting → quorum). Lane A (attestation-based, atomic) and Lane B (evidence-based, with appeals) slashing. Ticket/license slashing. Slashed funds escrow and routing. |
| 6 | [06_DEACTIVATION_AND_COMPLETION.md](06_DEACTIVATION_AND_COMPLETION.md) | Voluntary deactivation (ticket/license withdrawal), full deregistration (IMT removal), E3 happy-path completion, node shutdown, sync/restart, exit queue timing, ban/unban. |
Expand Down Expand Up @@ -36,10 +36,12 @@
6. E3 REQUEST Requester calls Enclave.request(params)
→ Fee paid, committee requested, IMT root snapshot

7. SORTITION Ciphernodes compute scores, submit tickets on-chain
→ Top N lowest scores selected
7. SORTITION Ciphernodes compute scores, derive a buffered local candidate list,
and submit tickets on-chain
→ Finalized committee is later sorted by score

8. FINALIZE finalizeCommittee() → committee locked in
→ Sortition emits AggregatorSelected for the first active member

9. DKG Selected nodes perform distributed key generation:
a. BFV keygen → C0 proof (proves keypair valid)
Expand All @@ -49,9 +51,10 @@
e. Collect shares → verify C2/C3 proofs (2-phase)
f. Decrypt shares → calc decryption key → C4a/C4b proofs
g. Exchange DecryptionKeyShared → verify C4 proofs
h. Publish KeyshareCreated → aggregator
h. Publish KeyshareCreated → finalized committee aggregation candidates

10. PK AGG Aggregator aggregates pk_shares → aggregate PK
10. PK AGG Current aggregator aggregates pk_shares
→ all finalized committee members are ordered fallbacks
→ C5 proof (proves aggregation correct)
→ publishCommittee() on-chain → KeyPublished stage

Expand All @@ -60,9 +63,10 @@

12. DECRYPT Committee members produce decryption shares
→ C6 proof per share (proves share correctly derived)
→ Broadcast to aggregator
→ Broadcast to finalized committee aggregation candidates

13. AGGREGATE Aggregator combines M+1 shares → plaintext
13. AGGREGATE Current aggregator combines M+1 shares → plaintext
→ active committee fallbacks submit later if higher-priority ranks miss their slot
→ C7 proof (proves reconstruction correct)

14. COMPLETE publishPlaintextOutput() → rewards distributed
Expand Down Expand Up @@ -172,17 +176,18 @@ _Found during source-code cross-referencing of these trace documents._
| 4 | `activate()` calls `register()` → `registerOperator()` which has `require(!registered, AlreadyRegistered())`. So activate **reverts** for already-registered operators. It only works for re-registration after deregistration. | BondingRegistry.sol:308 | 01_REGISTRATION |
| 5 | `E3Requested` event is `(uint256 e3Id, E3 e3, IE3Program indexed e3Program)` — seed and params are inside the E3 struct, not separate parameters. | IEnclave.sol:82 | 03_E3_REQUEST |
| 6 | `finalizeCommittee()` checks `>=` deadline, not `>`. | CiphernodeRegistryOwnable.sol | 03_E3_REQUEST |
| 7 | `publishCommittee()` is `onlyOwner` restricted — centralized trust assumption acknowledged in contract TODOs. | CiphernodeRegistryOwnable.sol | 04_DKG |
| 7 | `publishCommittee()` is permissionless and proof-gated. Any caller with a valid C5 proof can submit; ownership is no longer part of the access control path. | CiphernodeRegistryOwnable.sol | 04_DKG |
| 8 | `CommitteePublished` event emits `(e3Id, nodes, publicKey, proof)` — full PK bytes and C5 proof, not just pkHash. | CiphernodeRegistryOwnable.sol | 04_DKG |
| 9 | `_validateNodeEligibility` calls `bondingRegistry.getTicketBalanceAtBlock()` (not `ticketToken.getPastVotes()` directly). | CiphernodeRegistryOwnable.sol:668 | 03_E3_REQUEST |
| 10 | Lane A slashing uses **attestation-based** verification (committee quorum votes), not direct ZK proof re-verification on-chain. `proposeSlash()` decodes voter addresses, agrees, data hashes, and ECDSA signatures — not ZK proofs. | SlashingManager.sol | 05_FAILURE |
| 11 | Aggregator failover is derived from the **ordered finalized committee**, not from `threshold_m`. All finalized committee members are ordered fallback candidates; `Sortition` emits `AggregatorSelected` for the first active member and advances it on expulsion. | Sortition + Aggregator writers | 03_E3_REQUEST + 04_DKG |

### Protocol Design Concerns

| # | Concern | Severity | Detail |
| --- | ---------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | **Deregister-before-slash race** | Accepted | SlashingManager Lane B (evidence+appeal) has a window during which the operator can deregister and claim their exit. If they do, the slash executes against 0 funds. The contract comments acknowledge this as an accepted tradeoff for the appeal window design. |
| 2 | **`publishCommittee()` is centralized** | High | Only the contract owner can publish the committee public key. A malicious or compromised owner could publish a fake key. The contract has `// TODO` and `// SECURITY` comments acknowledging this. |
| 2 | **Committee publication is proof-gated** | Info | `publishCommittee()` is now permissionless. Safety depends on the C5 proof and committee-stage checks rather than a privileged owner account, which removes the original centralization risk. |
| 3 | **`gracePeriod` is dead code** | Medium | `gracePeriod` is stored and validated during config updates but never actually used in any timeout check. Either the deadlines already bake in sufficient buffer, or this is a missing feature. |
| 4 | **`activate` CLI command is misleading** | Low | Named "activate" but actually calls "register" — will fail for already-registered operators. There's no standalone way to trigger re-evaluation of active status; instead, `_updateOperatorStatus()` runs automatically inside `addTicketBalance()`, `bondLicense()`, etc. |
| 5 | **Active-job load balancing bug fixed** | Info | The Rust `NodeStateStore.available_tickets()` subtracts `active_jobs` from total tickets, reducing the chance of busy nodes being selected for new E3s. Previously, the `Sortition` actor's `Handler<EnclaveEvent>` was missing match arms for `E3Failed` and `E3StageChanged`, causing these events to fall to the default `_ => ()` — the typed handlers for decrementing jobs were dead code. This has been fixed: E3Failed and E3StageChanged are now routed to their handlers, and `finalized_committees` is cleaned up in `decrement_jobs_for_e3` to prevent unbounded memory growth. |
Expand Down
35 changes: 25 additions & 10 deletions agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,16 @@ EnclaveSolReader decodes IEnclave::E3Requested log
│ └─ Creates Fhe instance from BFV params
│ └─ Stores as dependency in E3Context
├─ PublicKeyAggregatorExtension.on_event(): (aggregator only)
├─ PublicKeyAggregatorExtension.on_event():
│ └─ Spins up PublicKeyAggregator actor
│ └─ State: Collecting (waiting for N keyshares)
│ └─ State: Pending until CommitteeFinalized resolves whether this node is in the finalized committee
└─ Sortition actor receives E3Requested:
├─ Calculates buffer = calculate_buffer_size(M, N)
│ → Produces a LOCAL selected list of N + buffer candidates
│ → This same ordered candidate list drives both ticket submission and
│ fallback `finalizeCommittee()` scheduling
├─ ScoreBackend.get_committee():
│ │
Expand All @@ -150,12 +153,12 @@ EnclaveSolReader decodes IEnclave::E3Requested log
│ │
│ ├─ Sort ALL nodes by their best score (ascending)
│ │
│ └─ Select top N nodes (lowest scores win)
│ → Returns committee list with party indices
│ └─ Select top N + buffer nodes (lowest scores win)
│ → Returns the ordered local candidate list used before finalization
└─ Sends WithSortitionTicket<E3Requested> to CiphernodeSelector
├─ If THIS node is in the selected committee:
├─ If THIS node is in the selected candidate list:
│ ticket_id = Some(TicketId::Score(best_ticket_number))
│ party_index = Some(index_in_committee)
Expand Down Expand Up @@ -233,13 +236,16 @@ CiphernodeRegistrySolWriter receives TicketGenerated event

## Step 3: Committee Finalization

### 3a. Deadline Timer (Rust-Side, Aggregator)
### 3a. Deadline Timer (Rust-Side, Selected Ciphernodes)

```
CommitteeFinalizer actor receives CommitteeRequested event
```text
CommitteeFinalizer actor receives CommitteeRequested event on each selected node
├─ Resolves this node's local score rank from the same buffered sortition list
│ └─ If the node is outside the local selected candidate list, no timer is scheduled
├─ Calculates wait time:
│ wait = committeeDeadline - currentTimestamp + buffer
│ wait = committeeDeadline - currentTimestamp + buffer + local_rank_stagger
├─ Schedules timer
Expand Down Expand Up @@ -309,16 +315,25 @@ CiphernodeRegistrySolReader decodes CommitteeFinalized event
├─ Sortition actor:
│ └─ Stores finalized committee as a `Committee` struct in persistent map
│ → Provides O(1) address→party_id lookup for later expulsion handling
│ └─ Emits AggregatorSelected {
│ e3_id, party_id, node, committee
│ }
│ → Initial selection is the first member in the score-sorted finalized committee
│ → If that member is later expelled, Sortition emits another AggregatorSelected
│ for the next active committee member in order
├─ CiphernodeSelector:
│ ├─ Checks if this node's address is in the committee list
│ ├─ Receives the FIRST AggregatorSelected for this E3
│ ├─ Checks if this node's address is in the finalized committee list
│ ├─ If YES:
│ │ party_id = index of this node in committee array
│ │ Publishes CiphernodeSelected {
│ │ e3_id, threshold_m, threshold_n,
│ │ seed, party_id, ...all E3 metadata
│ │ }
│ └─ If NO: does nothing for this E3
│ → Later AggregatorSelected failover events do NOT re-emit CiphernodeSelected
│ because committee membership did not change
└─ KeyshareCreatedFilterBuffer:
└─ Stores committee set
Expand Down
Loading
Loading