Skip to content

chore: remove unused keyshare code [skip-line-limit]#1199

Merged
ctrlc03 merged 3 commits into
mainfrom
chore/remove-keyshare-code
Jan 28, 2026
Merged

chore: remove unused keyshare code [skip-line-limit]#1199
ctrlc03 merged 3 commits into
mainfrom
chore/remove-keyshare-code

Conversation

@hmzakhalid

@hmzakhalid hmzakhalid commented Jan 23, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Deprecations

    • Removed legacy plaintext aggregation and legacy keyshare; public legacy aggregation APIs and factory accessors dropped.
    • Ciphernode builder APIs for legacy keyshare/plaintext aggregation removed.
  • Tests

    • Added focused P2P networking tests validating event forwarding and loopback.
    • Removed legacy integration test suite.
  • Chores

    • Simplified CI to skip legacy integration tests and streamlined build wiring.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel

vercel Bot commented Jan 23, 2026

Copy link
Copy Markdown

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

Project Deployment Review Updated (UTC)
crisp Ready Ready Preview, Comment Jan 28, 2026 3:27pm
enclave-docs Ready Ready Preview, Comment Jan 28, 2026 3:27pm

Request Review

@hmzakhalid hmzakhalid requested a review from ryardley January 23, 2026 13:12
@coderabbitai

coderabbitai Bot commented Jan 23, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR removes legacy plaintext aggregation and keyshare implementations, replacing them with threshold-based alternatives, updates repository factories, simplifies CiphernodeBuilder by removing legacy options, removes legacy integration tests and CI execution, and adds new P2P event-forwarding tests.

Changes

Cohort / File(s) Summary
Legacy Plaintext Aggregation Removal
crates/aggregator/src/ext.rs, crates/aggregator/src/lib.rs, crates/aggregator/src/plaintext_aggregator.rs
Deleted PlaintextAggregator module and extension, its state/types, constructor and E3Extension wiring; removed plaintext-related exports.
Aggregator Repository Factory Updates
crates/aggregator/src/repo.rs
Replaced PlaintextRepositoryFactory with TrBfvPlaintextRepositoryFactory and added PublicKeyRepositoryFactory; updated implementations to use scoped stores for threshold/plaintext and publickey state.
Keyshare Module Removal
crates/keyshare/src/keyshare.rs, crates/keyshare/src/lib.rs, crates/keyshare/src/repo.rs
Removed legacy Keyshare module, its public types and actor handlers; removed KeyshareRepositoryFactory and lib re-exports.
Keyshare Extension Replacement
crates/keyshare/src/ext.rs
Replaced KeyshareExtension with ThresholdKeyshareExtension; new E3Extension wiring initializes ThresholdKeyshare using meta and repository, adds share encryption params, and adjusts hydrate logic.
CiphernodeBuilder Simplification
crates/ciphernode-builder/src/ciphernode_builder.rs
Removed plaintext_agg field and builder methods with_plaintext_aggregation/with_keyshare; removed NonThreshold variant from KeyshareKind; simplified FHE/keyshare wiring.
CI & Tests Update
.github/workflows/ci.yml, crates/tests/tests/integration_legacy.rs, crates/tests/tests/integration.rs
Removed execution of legacy integration_legacy tests from CI and deleted integration_legacy.rs; added new P2P networking tests (test_p2p_actor_forwards_events_to_network, test_p2p_actor_forwards_events_to_bus) in integration.rs.

Sequence Diagram(s)

mermaid
sequenceDiagram
rect rgba(200,200,255,0.5)
participant E3 as E3 Runtime
participant Bus as Event Bus
participant Repo as Repository Store
participant Actor as ThresholdKeyshare Actor
participant FHE as FHE Params
end
E3->>Bus: start / extension init
Bus->>Repo: load META_KEY (threshold metadata)
Repo-->>Bus: return threshold params
Bus->>FHE: provide BfvParameters
Bus->>Actor: spawn ThresholdKeyshare(params, repo_state)
Actor-->>Bus: register handlers / subscribe to EnclaveEvent
Bus->>Actor: EnclaveEvent / CiphertextOutputPublished ...
Actor->>Repo: persist ThresholdKeyshare state
Actor-->>E3: emit KeyshareCreated / DecryptionshareCreated

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

ciphernode

Suggested reviewers

  • 0xjei

Poem

🐰 Threads trimmed, old paths made light,
Thresholds hum through day and night,
Plaintext sleeps, keyshares leap,
P2P hops and tests now keep—
The rabbit drums a tidy bite!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: removal of unused keyshare code, which aligns with the substantial deletions across keyshare modules, related aggregator code, and test files throughout the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

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 temporarily deployed to Preview – crisp January 23, 2026 14:15 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs January 23, 2026 14:15 Inactive
@hmzakhalid hmzakhalid changed the title chore: remove unused keyshare code chore: remove unused keyshare code [skip-ci-limit] Jan 23, 2026
@hmzakhalid hmzakhalid changed the title chore: remove unused keyshare code [skip-ci-limit] chore: remove unused keyshare code [skip-line-limit] Jan 23, 2026
@0xjei 0xjei self-requested a review January 23, 2026 17:43
@0xjei

0xjei commented Jan 23, 2026

Copy link
Copy Markdown
Contributor

i need this for #1197

0xjei
0xjei previously approved these changes Jan 23, 2026

@0xjei 0xjei 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.

utACK

@hmzakhalid

Copy link
Copy Markdown
Collaborator Author

i would like a quick look from @ryardley before merging this in

@vercel vercel Bot temporarily deployed to Preview – crisp January 23, 2026 17:55 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs January 23, 2026 17:55 Inactive
Comment thread crates/tests/tests/integration_legacy.rs
Comment thread crates/tests/tests/integration_legacy.rs
Comment thread crates/tests/tests/integration_legacy.rs
Comment thread crates/tests/tests/integration_legacy.rs

@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: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
examples/CRISP/server/.env.example (1)

13-13: Use a clear placeholder for CRON_API_KEY.

Even in .env.example, a realistic-looking token can be mistaken for a real secret. Consider using a placeholder like "CHANGE_ME" to reduce confusion.

💡 Suggested tweak
-CRON_API_KEY=1234567890 
+CRON_API_KEY="CHANGE_ME"
packages/enclave-contracts/scripts/deployAndSave/enclave.ts (1)

65-83: Include timeoutConfig in the reuse guard comparison.
Right now, changing timeoutConfig still reuses the previous deployment, leaving the proxy initialized with stale timeout settings while persisting the new config.

🛠️ Suggested fix
  const preDeployedArgs = readDeploymentArgs("Enclave", chain);
+ const timeoutConfigJson = timeoutConfig
+   ? JSON.stringify(timeoutConfig)
+   : undefined;

  if (
    !params ||
    !owner ||
    !maxDuration ||
    !registry ||
    !bondingRegistry ||
    !e3RefundManager ||
    !feeToken ||
    !timeoutConfig ||
    (preDeployedArgs?.constructorArgs?.owner === owner &&
      preDeployedArgs?.constructorArgs?.maxDuration === maxDuration &&
      preDeployedArgs?.constructorArgs?.registry === registry &&
      preDeployedArgs?.constructorArgs?.bondingRegistry === bondingRegistry &&
      preDeployedArgs?.constructorArgs?.e3RefundManager === e3RefundManager &&
      preDeployedArgs?.constructorArgs?.feeToken === feeToken &&
+     preDeployedArgs?.constructorArgs?.timeoutConfig === timeoutConfigJson &&
      areArraysEqual(
        preDeployedArgs?.constructorArgs?.params as string[],
        params,
      ))
  ) {
    ...
  }

  storeDeploymentArgs(
    {
      constructorArgs: {
        owner,
        registry,
        bondingRegistry,
        e3RefundManager,
        feeToken,
        maxDuration,
-       timeoutConfig: JSON.stringify(timeoutConfig),
+       timeoutConfig: timeoutConfigJson,
        params,
      },
      ...
    },
    "Enclave",
    chain,
  );

Also applies to: 122-133

examples/CRISP/server/src/cli/commands.rs (2)

108-176: Recompute input_deadline after refreshing current_timestamp.

Line 113 computes input_deadline once, but Line 152 refreshes current_timestamp/start_window before the request while reusing the old deadline. If approval takes time, the deadline can become stale (or even earlier than the refreshed start window), which can cause immediate expiry or a revert. Consider recalculating input_deadline just before request_e3 (and re-quoting if the fee depends on the deadline).

🛠️ Suggested fix (keep deadline aligned with refreshed timestamp)
-    let input_deadline = U256::from(current_timestamp) + U256::from(CONFIG.e3_duration);
+    let mut input_deadline = U256::from(current_timestamp) + U256::from(CONFIG.e3_duration);
@@
-    current_timestamp = get_current_timestamp().await?;
+    current_timestamp = get_current_timestamp().await?;
     start_window = [
         U256::from(current_timestamp),
         U256::from(current_timestamp + CONFIG.e3_window_size as u64),
     ];
+    input_deadline = U256::from(current_timestamp) + U256::from(CONFIG.e3_duration);

256-264: Use the CRISP program address when constructing CRISPContract.

Line 256 uses CONFIG.enclave_address when creating CRISPContract. Elsewhere (e.g., CRISP contract factory usage) the CRISP program address is used. If CRISPContract targets the program contract, this will send publish_input to the wrong address.

🛠️ Suggested fix
-        let contract = CRISPContract::new(
-            &CONFIG.http_rpc_url,
-            &CONFIG.private_key,
-            &CONFIG.enclave_address,
-        )
+        let contract = CRISPContract::new(
+            &CONFIG.http_rpc_url,
+            &CONFIG.private_key,
+            &CONFIG.e3_program_address,
+        )
templates/default/server/index.ts (1)

140-150: Potential deadlock: def.resolve() not called when session already exists.

If an E3_ACTIVATED event is received for an e3Id that already has a session (e.g., duplicate events or re-activation scenarios), def.resolve() is never called because it's inside the if (!e3Sessions.has(sessionKey)) block. This will cause handleInputPublishedEvent to hang indefinitely on await getActivationDefer(e3Id).promise (line 174).

🐛 Proposed fix
   if (!e3Sessions.has(sessionKey)) {
     e3Sessions.set(sessionKey, {
       e3Id,
       e3ProgramParams: e3.e3ProgramParams,
       expiration,
       inputs: [],
       isProcessing: false,
       isCompleted: false,
     })
-    def.resolve()
   }
+  def.resolve()
🤖 Fix all issues with AI agents
In `@crates/aggregator/src/committee_finalizer.rs`:
- Around line 90-95: Update the info! log message to reference the renamed field
by replacing "Submission deadline already passed, finalizing with buffer" with
wording that uses "Committee deadline" (e.g., "Committee deadline already
passed, finalizing with buffer") in the info! call that includes e3_id =
%e3_id_for_async, committee_deadline, current_timestamp in
committee_finalizer.rs so the message matches the committee_deadline field.

In `@examples/CRISP/server/.env.example`:
- Around line 16-18: Reorder the dotenv keys so they satisfy dotenv-linter's
alphabetical ordering by moving E3_PROGRAM_ADDRESS above ENCLAVE_ADDRESS; update
the block containing ENCLAVE_ADDRESS, CIPHERNODE_REGISTRY_ADDRESS, and
E3_PROGRAM_ADDRESS so the three entries are alphabetically ordered
(E3_PROGRAM_ADDRESS, CIPHERNODE_REGISTRY_ADDRESS, ENCLAVE_ADDRESS) preserving
the existing values and comment on E3_PROGRAM_ADDRESS.

In `@packages/enclave-contracts/contracts/E3RefundManager.sol`:
- Around line 261-271: The honest-node check in claimHonestNodeReward uses an
O(n) loop over _honestNodes[e3Id] (isHonest variable) and the per-node payout
calculation amount = dist.honestNodeAmount / dist.honestNodeCount can leave
unclaimable remainder; change storage to include a mapping
_isHonestNode[e3Id][address] set during calculateRefund (alongside pushing into
_honestNodes) and replace the loop/require with
require(_isHonestNode[e3Id][msg.sender], NotHonestNode(e3Id, msg.sender)) in
claimHonestNodeReward, and adjust the payout logic to avoid dust—e.g., compute
baseShare and track claimed count or assign the remainder to the last claimant
using dist.honestNodeAmount, dist.honestNodeCount and a claimed counter so total
distributed equals honestNodeAmount.

In `@packages/enclave-contracts/contracts/Enclave.sol`:
- Around line 667-681: Replace the string-require checks in processE3Failure
with custom errors consistent with the contract's pattern: declare new errors
(e.g., E3NotFailed(uint256 e3Id) and E3NoPayment(uint256 e3Id)) near the other
E3 errors, then replace require(stage == E3Stage.Failed, "E3 not failed") with a
check that reverts E3NotFailed(e3Id) and replace require(payment > 0, "No
payment to refund") with a check that reverts E3NoPayment(e3Id); keep the
existing e3Payments clearing, transfer, calculateRefund call, and the
E3FailureProcessed emit unchanged.

In `@packages/enclave-contracts/contracts/interfaces/IE3.sol`:
- Around line 19-22: Update the docstring for the `@param` duration in the IE3
interface to fix the typo "commitee" → "committee"; locate the comment near the
duration parameter declaration in IE3.sol (interface IE3 and the `@param` duration
line) and correct the word so the comment reads "Duration of committee duties".

In `@packages/enclave-contracts/contracts/interfaces/IE3RefundManager.sol`:
- Around line 109-112: Add a `@dev` note to the IE3RefundManager interface for the
function routeSlashedFunds stating that it can only be called by the Enclave
contract (the caller used by the slashing mechanism), so readers know the
implementation enforces access via onlyEnclave; update the comment block above
function routeSlashedFunds in IE3RefundManager.sol to explicitly mention this
caller restriction.

In `@packages/enclave-contracts/contracts/registry/BondingRegistry.sol`:
- Around line 87-88: The new state variable numActiveOperators can be zero after
upgrade and cause underflow when _updateOperatorStatus decrements; add an
explicit migration/initializer to set the correct current active operator count
(or add an admin-only setter) and call it as part of the upgrade path before any
code that can decrement the counter runs. Specifically, provide a protected
function (e.g., initializeNumActiveOperators or setNumActiveOperators) guarded
by onlyOwner/initializer semantics that computes or accepts the correct active
count and assigns numActiveOperators, mark it to be callable only once (or only
by the upgrade controller), and ensure any calls to _updateOperatorStatus occur
only after this seeding step (also update any upgrade scripts/tests to invoke
the migration).

In `@packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol`:
- Around line 348-378: The change to return bool is a breaking interface;
restore the original revert behavior in finalizeCommittee by removing the
"returns (bool success)" from the function signature, move the c.finalized =
true; assignment to after the threshold check, and replace the "if
(!thresholdMet) { ... return false; }" block with a require(thresholdMet,
InsufficientCommitteeMembers()); (or existing appropriate custom error), so the
function reverts when the committee threshold is not met and only finalizes (set
c.finalized, set c.committee, emit CommitteeFinalized, call
enclave.onCommitteeFinalized) when thresholdMet is true.

In `@packages/enclave-contracts/scripts/deployEnclave.ts`:
- Around line 151-163: The call to enclave.setE3RefundManager(...) only waits
for the transaction to be broadcast in ethers v6; capture the
TransactionResponse returned by enclave.setE3RefundManager and call await
tx.wait() before proceeding (i.e., await the mining confirmation). In the same
vein, verify that deployAndSaveE3RefundManager returns a fully-mined contract or
add awaiting there; but at minimum change the sequence around
enclave.setE3RefundManager to store the tx response and await tx.wait() to
ensure the state change is mined before moving on.

In `@packages/enclave-contracts/tasks/enclave.ts`:
- Around line 53-58: The `duration` option added via .addOption has a mismatched
description ("default: 3 days") and defaultValue (86400, which is 1 day); update
the code in the addOption for "duration" to make them consistent—either change
defaultValue to 86400 * 3 (259200) to match "3 days" or change the description
to "default: 1 day" (or equivalent) to match defaultValue; locate the .addOption
call for "duration" to apply the fix.

In `@packages/enclave-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts`:
- Around line 401-421: The test name "reverts if committee has already been
requested for given e3Id" is outdated; either rename the it(...) description to
reflect that it verifies the first request succeeds and rootAt(0) equals root
(update the string to something like "sets rootAt(0) after first request"), or
restore a duplicate-request assertion by invoking the same request twice (use
makeRequest to perform the first request, then call the contract path that would
create a second request for the same e3Id and assert it reverts) and keep the
original title; locate the test block using the it(...) string and the helper
makeRequest plus registry.rootAt/registry.root symbols to apply the change.
🧹 Nitpick comments (22)
examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockEnclave.sol (2)

9-9: Unused import.

IEnclave is imported but not used in this contract. Consider removing it to keep the code clean.

🧹 Proposed fix
-import { IEnclave } from "@enclave-e3/contracts/contracts/interfaces/IEnclave.sol";

50-70: Consider returning the stored E3 directly.

getE3() reconstructs an E3 struct with mostly hardcoded values while only reading duration, expiration, and inputDeadline from storage. For consistency and simpler test scaffolding, consider returning the stored E3 directly.

♻️ Proposed simplification
   function getE3(uint256 e3Id) external view returns (E3 memory) {
-    return
-      E3({
-        seed: 0,
-        threshold: [uint32(1), uint32(2)],
-        requestBlock: 0,
-        startWindow: [uint256(0), uint256(0)],
-        duration: e3s[e3Id].duration,
-        expiration: e3s[e3Id].expiration,
-        inputDeadline: e3s[e3Id].inputDeadline,
-        encryptionSchemeId: bytes32(0),
-        e3Program: IE3Program(address(0)),
-        e3ProgramParams: bytes(""),
-        customParams: bytes(""),
-        decryptionVerifier: IDecryptionVerifier(address(0)),
-        committeePublicKey: committeePublicKey,
-        ciphertextOutput: bytes32(0),
-        plaintextOutput: plaintextOutput,
-        requester: address(0)
-      });
+    E3 memory e3 = e3s[e3Id];
+    e3.committeePublicKey = committeePublicKey;
+    e3.plaintextOutput = plaintextOutput;
+    return e3;
   }
examples/CRISP/server/src/server/models.rs (1)

230-245: Minor field ordering inconsistency.

In E3Crisp, the input_deadline field is placed at the end of the struct, whereas in E3StateLite and E3, it's grouped with other timing fields (start_time, duration, expiration). Consider moving it for consistency.

♻️ Suggested reordering for consistency
 pub struct E3Crisp {
     pub emojis: [String; 2],
     pub has_voted: Vec<String>,
     pub start_time: u64,
+    pub input_deadline: u64,
     pub status: String,
     pub votes_option_1: String,
     pub votes_option_2: String,
     pub token_holder_hashes: Vec<String>,
     pub eligible_addresses: Vec<TokenHolder>,
     pub token_address: String,
     pub balance_threshold: String,
     pub ciphertext_inputs: Vec<(Vec<u8>, u64)>,
     pub requester: String,
-    pub input_deadline: u64,
 }
examples/CRISP/crates/evm_helpers/src/lib.rs (1)

100-116: LGTM! Minor style nit.

The new publish_input method correctly wraps the contract call. One minor observation: line 101 uses // for the comment while line 84 uses /// for doc comments. Consider using /// for consistency.

Also, data.into() on line 109 appears redundant since data is already Bytes and the generated binding expects Bytes. You could pass data directly.

♻️ Optional style fix
-    // publish an input to the CRISPProgram contract
+    /// Publish an input to the CRISPProgram contract
     pub async fn publish_input(
         &self,
         e3_id: U256,
         data: Bytes,
     ) -> Result<TransactionReceipt> {
         let contract = CRISPProgram::new(self.contract_address, self.provider.as_ref());
         let receipt = contract
-            .publishInput(e3_id, data.into())
+            .publishInput(e3_id, data)
             .send()
             .await?
             .get_receipt()
             .await?;

         Ok(receipt)
     }
crates/tests/tests/integration.rs (1)

456-547: LGTM! Well-structured P2P event forwarding test.

This test properly validates that:

  1. Network-destined events (PlaintextAggregated) are forwarded to the network
  2. Local-only events (CiphernodeSelected) are NOT broadcast to the network
  3. Forwarded events from network loopback are NOT re-published to the bus (deduplication)

Minor typo on line 472: "Pas" → "Pass".

📝 Typo fix
-    // Pas cmd and event channels to NetEventTranslator
+    // Pass cmd and event channels to NetEventTranslator
packages/enclave-sdk/src/contract-client.ts (1)

103-147: Clarify inputDeadline semantics for SDK callers.

This is a public SDK parameter now; a short param note helps prevent misuse (e.g., wrong units or meaning) and avoids contract reverts.

📝 Suggested doc tweak
   /**
    * Request a new E3 computation
    * request(address filter, uint32[2] threshold, uint256[2] startWindow, uint256 inputDeadline, uint256 duration, IE3Program e3Program, bytes e3ProgramParams, bytes computeProviderParams, bytes customParams)
+   * `@param` inputDeadline Deadline for input publication (document expected units)
    */
examples/CRISP/packages/crisp-contracts/deployed_contracts.json (1)

135-308: Confirm new deployment blobs aren’t secrets and the undefined env key is intentional.

Gitleaks flagged several new hex blobs (initData) as “generic API keys.” They look like deterministic on-chain data, but please confirm no real secrets were introduced; if false positives, consider allowlisting this file/patterns to keep CI clean. Also sanity-check that the undefined environment key is expected (vs. a missing env var in deployment scripts).

packages/enclave-contracts/test/Registry/CiphernodeRegistryOwnable.spec.ts (1)

307-348: Avoid potential bigint/number mixing in makeRequest timestamps.

If networkHelpers.time.latest() returns bigint (recent Hardhat helpers), currentTime + 100 will throw. Using bigint literals keeps this safe and aligned with uint256 inputs.

🔧 Safer bigint arithmetic
-    const currentTime = await networkHelpers.time.latest();
+    const currentTime = BigInt(await networkHelpers.time.latest());
     const requestParams = {
       threshold: [2, 2] as [number, number],
-      startWindow: [currentTime, currentTime + 100] as [number, number],
+      startWindow: [currentTime, currentTime + 100n] as [bigint, bigint],
       duration: 60 * 60 * 24 * 30, // 30 days
       e3Program: await mockE3Program.mockE3Program.getAddress(),
       e3ProgramParams: encodedE3ProgramParams,
-      inputDeadline: currentTime + 500,
+      inputDeadline: currentTime + 500n,
       computeProviderParams: abiCoder.encode(
packages/enclave-contracts/contracts/interfaces/IE3Program.sol (1)

40-45: Consider calldata for publishInput to avoid extra copying.

For external functions, bytes calldata is the usual pattern; if you adopt it, update implementations/mocks accordingly.

♻️ Suggested signature tweak
-    function publishInput(uint256 e3Id, bytes memory data) external;
+    function publishInput(uint256 e3Id, bytes calldata data) external;
examples/CRISP/server/src/server/routes/rounds.rs (1)

211-217: Use a single timestamp to avoid subtle skew.
Capturing Utc::now() once keeps start_window and input_deadline aligned.

Proposed refactor
-    let start_window: [U256; 2] = [
-        U256::from(Utc::now().timestamp()),
-        U256::from(Utc::now().timestamp() + CONFIG.e3_window_size as i64),
-    ];
-    let input_deadline = U256::from(Utc::now().timestamp()) + U256::from(CONFIG.e3_duration);
+    let now = Utc::now().timestamp();
+    let start_window: [U256; 2] = [
+        U256::from(now),
+        U256::from(now + CONFIG.e3_window_size as i64),
+    ];
+    let input_deadline = U256::from(now) + U256::from(CONFIG.e3_duration);
packages/enclave-contracts/scripts/deployAndSave/e3RefundManager.ts (2)

42-60: Clarify the reuse condition logic.

The condition !owner || !enclave || !treasury || (preDeployedArgs?.constructorArgs... matches) combines two distinct scenarios:

  1. Missing parameters → reuse existing deployment
  2. Parameters match stored values → reuse existing deployment

This is likely intentional (reuse when args not provided OR when they match), but the logic is subtle. Consider adding a comment to clarify intent, or restructuring for readability.

🔧 Suggested clarification
+  // Reuse existing deployment if:
+  // 1. Required parameters are not provided (caller wants to use existing)
+  // 2. Provided parameters match existing deployment
   if (
     !owner ||
     !enclave ||
     !treasury ||
     (preDeployedArgs?.constructorArgs?.owner === owner &&
       preDeployedArgs?.constructorArgs?.enclave === enclave &&
       preDeployedArgs?.constructorArgs?.treasury === treasury)
   ) {

70-71: Block number captured before proxy deployment.

The blockNumber is captured after the implementation deployment but before the proxy deployment. Since the proxy is the actual contract users interact with, consider capturing the block number after proxy deployment for more accurate tracking.

🔧 Suggested fix
-  const blockNumber = await ethers.provider.getBlockNumber();
   const e3RefundManagerAddress = await e3RefundManager.getAddress();

   const initData = e3RefundManagerFactory.interface.encodeFunctionData(
     "initialize",
     [owner, enclave, treasury],
   );

   const ProxyCF = await ethers.getContractFactory(
     "TransparentUpgradeableProxy",
   );
   const proxy = await ProxyCF.deploy(e3RefundManagerAddress, owner, initData);
   await proxy.waitForDeployment();
   const proxyAddress = await proxy.getAddress();

+  const blockNumber = await ethers.provider.getBlockNumber();
+
   const proxyAdminAddress = await getProxyAdmin(ethers.provider, proxyAddress);
packages/enclave-contracts/contracts/E3RefundManager.sol (3)

72-95: Simplify ownership initialization.

The pattern __Ownable_init(msg.sender) followed by conditional transferOwnership(_owner) can be simplified to directly initialize with the owner.

🔧 Suggested simplification
     function initialize(
         address _owner,
         address _enclave,
         address _treasury
     ) public initializer {
-        __Ownable_init(msg.sender);
+        __Ownable_init(_owner);

         require(_enclave != address(0), "Invalid enclave");
         require(_treasury != address(0), "Invalid treasury");

         enclave = IEnclave(_enclave);
         feeToken = enclave.feeToken();
         bondingRegistry = enclave.bondingRegistry();
         treasury = _treasury;

         _workAllocation = WorkValueAllocation({
             committeeFormationBps: 1000,
             dkgBps: 3000,
             decryptionBps: 5500,
             protocolBps: 500
         });
-
-        if (_owner != owner()) transferOwnership(_owner);
     }

133-137: Consider gas optimization for storing honest nodes.

The loop pushes each honest node individually, which incurs storage write costs per iteration. For large committees, this could be expensive. Consider if this is acceptable given expected committee sizes.


275-283: Consider using safeIncreaseAllowance instead of approve.

Using approve after previous approvals can be problematic if there's a leftover allowance (front-running attack vector). While safeApprove from SafeERC20 reverts if allowance is non-zero, safeIncreaseAllowance or resetting to 0 first would be safer.

However, since this immediately calls distributeRewards which should consume the full allowance, the risk is minimal in practice.

🔧 Alternative using forceApprove
-        feeToken.approve(address(bondingRegistry), amount);
+        feeToken.forceApprove(address(bondingRegistry), amount);

Note: forceApprove (available in newer OpenZeppelin) handles non-standard ERC20s better.

packages/enclave-contracts/contracts/interfaces/IEnclave.sol (2)

208-232: Duplicate "Structs" section header.

There's a section header for "Structs" at lines 51-55, and another at lines 208-212. The E3RequestParams struct at line 223 should likely be grouped with the other structs defined earlier, or the duplicate header should be removed.

🔧 Suggested fix
-    ////////////////////////////////////////////////////////////
-    //                                                        //
-    //                  Structs                               //
-    //                                                        //
-    ////////////////////////////////////////////////////////////
-
     /// `@notice` This struct contains the parameters to submit a request to Enclave.

Move E3RequestParams to the structs section near line 57-74, or remove the duplicate header.


379-383: Consider using FailureReason enum type directly for better type safety.

The function accepts uint8 reason which is cast to FailureReason enum. While this is a common cross-contract pattern in Solidity, accepting FailureReason directly would provide compile-time type safety. The current pattern relies on callers correctly mapping valid enum values (0-13), as evidenced in the codebase where callers document the mapping inline (e.g., onE3Failed(e3Id, 2); // FailureReason.InsufficientCommitteeMembers). The trade-off favors uint8 if external contracts with different ABI expectations need to call this function.

packages/enclave-contracts/contracts/registry/CiphernodeRegistryOwnable.sol (2)

363-372: Magic number for failure reason should use a named constant.

The hardcoded 2 passed to enclave.onE3Failed(e3Id, 2) corresponds to FailureReason.InsufficientCommitteeMembers. This is fragile if the enum order changes.

🔧 Suggested fix

Consider defining a constant or using a more explicit pattern:

+    // FailureReason.InsufficientCommitteeMembers = 2
+    uint8 private constant FAILURE_INSUFFICIENT_COMMITTEE = 2;
+
     if (!thresholdMet) {
         c.failed = true;
         emit CommitteeFormationFailed(
             e3Id,
             c.topNodes.length,
             c.threshold[1]
         );
-        enclave.onE3Failed(e3Id, 2); // FailureReason.InsufficientCommitteeMembers
+        enclave.onE3Failed(e3Id, FAILURE_INSUFFICIENT_COMMITTEE);
         return false;
     }

Alternatively, import the IEnclave interface and cast the enum value explicitly.


531-535: Potential off-by-one with c.requestBlock - 1.

Using c.requestBlock - 1 for the snapshot block assumes that requestBlock is always > 0. While this should always be true in practice (blocks start from genesis), consider adding a defensive check or documenting this assumption.

The existing TODO comment on line 529-530 acknowledges this concern.

packages/enclave-contracts/contracts/Enclave.sol (2)

211-218: Consider using a custom error instead of string literal.

The modifier uses a string literal "Only CiphernodeRegistry" which is inconsistent with the rest of the contract that uses custom errors. This increases gas cost and reduces consistency.

🔧 Suggested fix

Add a custom error and use it in the modifier:

+    /// `@notice` Caller is not the CiphernodeRegistry
+    error OnlyCiphernodeRegistry();
+
     /// `@notice` Restricts function to CiphernodeRegistry contract only
     modifier onlyCiphernodeRegistry() {
         require(
             msg.sender == address(ciphernodeRegistry),
-            "Only CiphernodeRegistry"
+            OnlyCiphernodeRegistry()
         );
         _;
     }

651-662: setE3RefundManager uses string literal for error.

Inconsistent with other setters that use custom errors. Also, unlike other setters, this doesn't check if the new value equals the current value.

🔧 Suggested fix
+    /// `@notice` Thrown when attempting to set an invalid E3 refund manager address.
+    /// `@param` e3RefundManager The invalid refund manager address.
+    error InvalidE3RefundManager(IE3RefundManager e3RefundManager);
+
     function setE3RefundManager(
         IE3RefundManager _e3RefundManager
     ) public onlyOwner {
         require(
-            address(_e3RefundManager) != address(0),
-            "Invalid E3RefundManager address"
+            address(_e3RefundManager) != address(0) &&
+                _e3RefundManager != e3RefundManager,
+            InvalidE3RefundManager(_e3RefundManager)
         );
         e3RefundManager = _e3RefundManager;
         emit E3RefundManagerSet(address(_e3RefundManager));
     }
packages/enclave-contracts/contracts/interfaces/IE3RefundManager.sol (1)

20-26: WorkValueAllocation basis points should be documented to sum to a specific total.

The struct uses basis points (bps) where 10000 = 100%. The natspec should clarify whether these four fields should sum to exactly 10000 bps, or if they represent independent allocations. Based on the field names, it appears protocolBps is separate from the work stages (committeeFormation, dkg, decryption).

📝 Suggested documentation improvement
     /// `@notice` Work value allocation in basis points (10000 = 100%)
+    /// `@dev` committeeFormationBps + dkgBps + decryptionBps should equal (10000 - protocolBps)
+    /// `@dev` protocolBps represents the protocol's cut taken before work allocation
     struct WorkValueAllocation {
         uint16 committeeFormationBps;
         uint16 dkgBps;
         uint16 decryptionBps;
         uint16 protocolBps;
     }

Comment thread crates/aggregator/src/committee_finalizer.rs
Comment thread examples/CRISP/server/.env.example Outdated
Comment thread packages/enclave-contracts/contracts/E3RefundManager.sol Outdated
Comment thread packages/enclave-contracts/contracts/Enclave.sol Outdated
Comment thread packages/enclave-contracts/contracts/interfaces/IE3.sol
Comment thread packages/enclave-contracts/contracts/registry/BondingRegistry.sol Outdated
Comment thread packages/enclave-contracts/scripts/deployEnclave.ts Outdated
Comment thread packages/enclave-contracts/tasks/enclave.ts
ryardley
ryardley previously approved these changes Jan 28, 2026

@ryardley ryardley 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.

Looks good to me although you might need to resolve some merge conflicts and get another 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: 1

🤖 Fix all issues with AI agents
In `@crates/tests/tests/integration.rs`:
- Around line 448-536: The test test_p2p_actor_forwards_events_to_network is
racy because the spawned tokio task reading cmd_rx may not process GossipPublish
before the assertions run; add a short timeout-based wait after publishing and
before the assert_eq on msgs to ensure msgs contains the two expected entries.
Locate the spawned loop that pushes into msgs (msgs_loop) and, prior to the
first assert_eq, poll or await with tokio::time::timeout (or a small retry loop
with tokio::time::sleep) until msgs.lock().await.len() == 2 (fail the test if
the timeout elapses) so the assertions are deterministic. Ensure the timeout is
short (e.g., 100-500ms) to keep tests fast.

Comment thread crates/tests/tests/integration.rs
@ctrlc03 ctrlc03 merged commit 1e7e481 into main Jan 28, 2026
26 checks passed
@ctrlc03 ctrlc03 linked an issue Jan 29, 2026 that may be closed by this pull request
@github-actions github-actions Bot deleted the chore/remove-keyshare-code branch February 5, 2026 03:12
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.

Delete BFV actors (actix actors)

4 participants