Skip to content

M-H01: feat(contracts/ChannelHub): restrict to one node#649

Open
nksazonov wants to merge 8 commits intofix/audit-findingsfrom
fix/audit-m-h01
Open

M-H01: feat(contracts/ChannelHub): restrict to one node#649
nksazonov wants to merge 8 commits intofix/audit-findingsfrom
fix/audit-m-h01

Conversation

@nksazonov
Copy link
Copy Markdown
Contributor

@nksazonov nksazonov commented Apr 6, 2026

Description

createChannel() accepts a ChannelDefinition from calldata and passes def.approvedSignatureValidators into _validateSignatures(). _validateSignatures() calls _extractValidator() which selects the validator based on the first byte of userSig/nodeSig and only checks that the chosen validatorId is set in approvedSignatureValidators before invoking it. A node can register a malicious ISignatureValidator via registerNodeValidator() that always returns VALIDATION_SUCCESS, then set approvedSignatureValidators in calldata to include that validator. This lets createChannel() succeed with an arbitrary userSig, so the channel can be created without user consent. The same bypass applies to closeChannel() by providing another fake userSig, enabling the node to close the channel and transfer locked funds to itself.

Impact

Unauthorized channel creation and theft of user funds up to the user’s ERC20 allowance/balance. This violates the protocol requirement: “Every state update requires valid signatures from all required participants. No participant can unilaterally change the state.”

Summary by CodeRabbit

  • Security

    • Enforced per-node channel boundaries and introduced a validator activation delay to reduce bootstrap signature-exploit risk.
  • Documentation

    • Added detailed security analysis and a protocol/design document describing the issue, mitigations, and trade-offs.
  • Tests

    • Added and extended tests covering validator registration, activation timing, and validator-selection behavior.
  • Chores

    • Updated deployment tooling and compiler settings to require and record node identity.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed695d0e-d3c3-4b7a-bce8-e14ce5d88d86

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Binds each ChannelHub deployment to an immutable NODE, enforces exact node equality in channel definitions, timestamps node-registered validators with an activation delay, updates deployment script and tests, and adds security/design docs describing the bootstrap vulnerability and mitigation options. (34 words)

Changes

Cohort / File(s) Summary
Documentation
contracts/SECURITY.md, contracts/initial-user-sig-validation.md, protocol-description.md
New security/design documentation describing the initial-user-signature bootstrap vulnerability, attack flow, current per-node mitigation, validator activation-delay defense, and alternative protocol designs.
ChannelHub core
contracts/src/ChannelHub.sol
Introduced NODE immutable (constructor arg), added ValidatorNotActive/IncorrectNode errors, changed validator registry to store registeredAt, enforced activation delay before using node-registered validators, and changed _requireValidDefinition to require def.node == NODE.
Deployment script
contracts/script/DeployChannelHub.s.sol
run() signature changed to run(address defaultValidatorAddr, address nodeAddr), reads NODE_ADDR, requires non-zero nodeAddr, passes it to ChannelHub constructor, and logs the Node address.
Tests & harness
contracts/test/TestChannelHub.sol, contracts/test/ChannelHub_Base.t.sol, contracts/test/ChannelHub_claimFunds.t.sol, contracts/test/ChannelHub_pushFunds.t.sol, contracts/test/ChannelHub_sigValidator.t.sol
Constructors/instantiations updated to accept node arg; added exposed_extractValidator test helper; new sig-validator tests for registration, timestamps, activation delay, and extraction behavior; tests warp time past activation delay where needed.
Config
contracts/foundry.toml
Lowered optimizer_runs for the channelhub compiler profile from 750 to 500.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ChannelHub
  participant Node
  participant ValidatorRegistry
  participant BlockTime as BlockTime

  Client->>ChannelHub: createChannel(def where def.node == NODE, userSig, approvedSignatureValidators)
  ChannelHub->>ValidatorRegistry: extract validatorId from userSig + approvedSignatureValidators
  ValidatorRegistry->>ChannelHub: return (validatorAddr, registeredAt)
  ChannelHub->>BlockTime: check block.timestamp >= registeredAt + VALIDATOR_ACTIVATION_DELAY
  alt validator active
    ChannelHub->>validatorAddr: validate(userSig, payload)
    validatorAddr-->>ChannelHub: validation result
    ChannelHub-->>Client: channel created / funds processed
  else validator inactive
    ChannelHub-->>Client: revert ValidatorNotActive(validator, id, activatesAt)
  end
  Note right of ChannelHub: if def.node != NODE -> revert IncorrectNode()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ihsraham
  • philanton
  • dimast-x

Poem

🐰 I bound a node with quiet cheer,

Validators wait before they steer.
No sly bootstrap hops in here,
Time fences tricksters far from near.
Hooray — safer channels, carrots clear! 🥕

🚥 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 clearly and specifically summarizes the main objective: restricting ChannelHub to operate with a single node address, which is the core mitigation for the bootstrap validator vulnerability affecting createChannel() and closeChannel().
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/audit-m-h01

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.

@nksazonov
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

✅ 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
contracts/initial-user-sig-validation.md (2)

454-465: Clarify session key support for Option H in comparison table.

Line 456 (Option H row) shows "✓" for "Session key (createChannel)", while all validator-restricting options (A-G) show "✗". This is technically correct—Option H doesn't restrict validators, so session keys remain functional—but potentially misleading because they remain insecure (the bound node can still forge signatures using a malicious validator).

Readers might interpret "✓" as "secure session key support" rather than "session keys are not blocked (but vulnerability persists within trust boundary)".

📝 Suggested clarification

Consider adding a footnote or marking this cell differently:

-| **H** (per-node hub) | ✓ (cross-node only) | ✓ (cross-node only) | — | ✓ | ✓ | ✓ | No | **✗** |
+| **H** (per-node hub) | ✓ (cross-node only) | ✓ (cross-node only) | — | ✓ (¹) | ✓ | ✓ | No | **✗** |

And add below the table:

(¹) Session keys remain functional but insecure within the trust boundary—the bound node can still register a malicious validator.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/initial-user-sig-validation.md` around lines 454 - 465, Update the
Options Comparison Table so Option H's "Session key (createChannel)" cell is
explicitly qualified (e.g., change the ✓ to "✓¹" or similar) and add the
suggested footnote text below the table clarifying that session keys remain
functional but insecure within the trust boundary (e.g., "(¹) Session keys
remain functional but insecure within the trust boundary—the bound node can
still register a malicious validator."). Reference the "Option H" row and the
"Session key (createChannel)" column when making the change.

34-51: Constraint 5 conflicts with implemented Option H.

Line 47 lists "Extensible without ChannelHub redeployment" as a constraint, but the implemented solution (Option H, lines 399-451) explicitly requires one deployment per node, creating operational fragmentation. This tension is acknowledged in Option H's "Cons" section (lines 447-450), so it appears the team accepted this trade-off for simplicity. However, readers might expect constraints to be hard requirements rather than preferences.

📝 Suggested clarification

Consider revising the constraints header to distinguish between hard requirements and desirable properties, or add a note that Constraint 5 is relaxed in the chosen solution:

 ## Constraints on the Fix
 
-1. **No additional user interaction.** The existing interaction may change format, but an
+1. **No additional user interaction (hard requirement).** The existing interaction may change format, but an
    extra signing step or extra on-chain transaction is not acceptable.
...
-5. **Extensible without ChannelHub redeployment.** New wallet formats (ERC-4337, future
+5. **Extensible without ChannelHub redeployment (desirable).** New wallet formats (ERC-4337, future
    schemes) must be addable without requiring users to migrate to a new deployment.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/initial-user-sig-validation.md` around lines 34 - 51, The
constraints list currently presents "Extensible without ChannelHub redeployment"
(Constraint 5) as a hard requirement but Option H explicitly requires per-node
ChannelHub deployments, so update the document to clarify requirements: either
split the constraints section into "Hard requirements" and "Desirable
properties" or add a short explicit note referencing Option H and ChannelHub
that Constraint 5 is relaxed for the chosen solution (Option H) and explain this
tradeoff; ensure you mention Constraint 5, Option H, and ChannelHub so readers
can locate and reconcile the tension.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/initial-user-sig-validation.md`:
- Around line 268-274: The Markdown table has a mismatched separator row (4
cells) vs. the header (3 columns) causing formatting break; update the separator
from "|---|---|---|---|" to "|---|---|---|" and ensure each subsequent data row
(e.g., the row starting "Who adds the malicious validator?") has exactly three
pipe-separated cells to match the header.

---

Nitpick comments:
In `@contracts/initial-user-sig-validation.md`:
- Around line 454-465: Update the Options Comparison Table so Option H's
"Session key (createChannel)" cell is explicitly qualified (e.g., change the ✓
to "✓¹" or similar) and add the suggested footnote text below the table
clarifying that session keys remain functional but insecure within the trust
boundary (e.g., "(¹) Session keys remain functional but insecure within the
trust boundary—the bound node can still register a malicious validator.").
Reference the "Option H" row and the "Session key (createChannel)" column when
making the change.
- Around line 34-51: The constraints list currently presents "Extensible without
ChannelHub redeployment" (Constraint 5) as a hard requirement but Option H
explicitly requires per-node ChannelHub deployments, so update the document to
clarify requirements: either split the constraints section into "Hard
requirements" and "Desirable properties" or add a short explicit note
referencing Option H and ChannelHub that Constraint 5 is relaxed for the chosen
solution (Option H) and explain this tradeoff; ensure you mention Constraint 5,
Option H, and ChannelHub so readers can locate and reconcile the tension.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d20dff94-645a-49c4-b029-93fee9ce1933

📥 Commits

Reviewing files that changed from the base of the PR and between e39b229 and f6f76e1.

📒 Files selected for processing (9)
  • contracts/SECURITY.md
  • contracts/initial-user-sig-validation.md
  • contracts/script/DeployChannelHub.s.sol
  • contracts/src/ChannelHub.sol
  • contracts/test/ChannelHub_Base.t.sol
  • contracts/test/ChannelHub_claimFunds.t.sol
  • contracts/test/ChannelHub_pushFunds.t.sol
  • contracts/test/TestChannelHub.sol
  • protocol-description.md

@nksazonov
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

✅ 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
contracts/initial-user-sig-validation.md (1)

268-270: ⚠️ Potential issue | 🟡 Minor

The Option F comparison table is still malformed.

The header has 3 columns, but the separator row still has 4 cells, so this block renders incorrectly.

🔧 Proposed fix
 | | Original attack | Option F attack |
-|---|---|---|---|
+|---|---|---|
 | Who adds the malicious validator? | Any node (permissionless) | Only `bootstrapAdmin` (governance) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/initial-user-sig-validation.md` around lines 268 - 270, The
markdown table header in the comparison block is mismatched: the header defines
three columns but the separator row uses four cells; update the separator row to
have exactly three pipes/cells to match the header (replace the current
"|---|---|---|---|" with a three-cell divider) so the table with "Original
attack" and "Option F attack" renders correctly; ensure the separator aligns
with the header columns and the cells beneath (e.g., the row containing "Who
adds the malicious validator?").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/SECURITY.md`:
- Around line 253-271: Update the earlier validator-selection claim so it
explicitly scopes the protection to already-created channels or previously
agreed channel definitions: change the sentence that says the
`approvedSignatureValidators` bitmask prevents malicious nodes from forging user
signatures to instead state that it prevents forging on existing channels (i.e.,
where `channelId` embeds the agreed `approvedSignatureValidators`), and add a
brief clause noting the bootstrap exception at
`createChannel`/`ChannelDefinition` where the bitmask arrives in calldata and
does not provide protection; also mention `closeChannel` as subject to the same
bootstrap risk.

In `@contracts/src/ChannelHub.sol`:
- Line 183: registerNodeValidator currently allows arbitrary node keys into
_nodeValidatorRegistry and initiateEscrowDeposit/initiateEscrowWithdrawal
validate against def.node/def.approvedSignatureValidators on non-home paths,
leaking foreign-node registries; fix by enforcing the single-node invariant: in
registerNodeValidator and any code paths that write to _nodeValidatorRegistry
ensure def.node == NODE (or call _requireValidDefinition(def)) before
persisting, and in initiateEscrowDeposit/initiateEscrowWithdrawal add a guard
that if !_isChannelHomeChain(channelId) then require def.node == NODE (or call
_requireValidDefinition) before using def.approvedSignatureValidators to
validate signatures or creating escrow records so foreign-node validator
registries cannot be materialized.

---

Duplicate comments:
In `@contracts/initial-user-sig-validation.md`:
- Around line 268-270: The markdown table header in the comparison block is
mismatched: the header defines three columns but the separator row uses four
cells; update the separator row to have exactly three pipes/cells to match the
header (replace the current "|---|---|---|---|" with a three-cell divider) so
the table with "Original attack" and "Option F attack" renders correctly; ensure
the separator aligns with the header columns and the cells beneath (e.g., the
row containing "Who adds the malicious validator?").
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2363ed03-6dd9-436f-b089-dec4ebcc73d6

📥 Commits

Reviewing files that changed from the base of the PR and between f6f76e1 and e09ce12.

📒 Files selected for processing (11)
  • contracts/SECURITY.md
  • contracts/foundry.toml
  • contracts/initial-user-sig-validation.md
  • contracts/script/DeployChannelHub.s.sol
  • contracts/src/ChannelHub.sol
  • contracts/test/ChannelHub_Base.t.sol
  • contracts/test/ChannelHub_claimFunds.t.sol
  • contracts/test/ChannelHub_pushFunds.t.sol
  • contracts/test/ChannelHub_sigValidator.t.sol
  • contracts/test/TestChannelHub.sol
  • protocol-description.md
✅ Files skipped from review due to trivial changes (3)
  • contracts/foundry.toml
  • contracts/test/ChannelHub_claimFunds.t.sol
  • protocol-description.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • contracts/test/ChannelHub_pushFunds.t.sol
  • contracts/test/ChannelHub_Base.t.sol
  • contracts/test/TestChannelHub.sol
  • contracts/script/DeployChannelHub.s.sol

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

✅ 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
contracts/initial-user-sig-validation.md (1)

434-434: Tighten wording for concision.

“has the ability to” can be simplified to “can” without changing meaning.

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

In `@contracts/initial-user-sig-validation.md` at line 434, Replace the phrase
"has the ability to" with the more concise "can" in the sentence under Option H
(the line starting "Within Option H the bound node still has the ability to
register a malicious validator and") so it reads "Within Option H the bound node
still can register a malicious validator and" (adjust surrounding
punctuation/grammar if needed).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/initial-user-sig-validation.md`:
- Around line 418-424: The Option H table is misleadingly scored as fully `✓`
for “Node-independent” and “createChannel admin-proof” despite the text stating
it's only a threat-model shift; update the table cells for Option H to reflect
partial mitigation (e.g., change those cells to “Partial (cross-node only)” or
similar) so readers don't over-read it as a complete validator-level fix; ensure
you update any other occurrences of those same labels (the Option H row and the
related summary at or near the mention of ChannelDefinition and
approvedSignatureValidators) so the table and prose are consistent.

---

Nitpick comments:
In `@contracts/initial-user-sig-validation.md`:
- Line 434: Replace the phrase "has the ability to" with the more concise "can"
in the sentence under Option H (the line starting "Within Option H the bound
node still has the ability to register a malicious validator and") so it reads
"Within Option H the bound node still can register a malicious validator and"
(adjust surrounding punctuation/grammar if needed).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e599af2c-64a3-473f-966e-1e45644db700

📥 Commits

Reviewing files that changed from the base of the PR and between e09ce12 and 0ebc60e.

📒 Files selected for processing (1)
  • contracts/initial-user-sig-validation.md

@nksazonov
Copy link
Copy Markdown
Contributor Author

This PR should also remove core related to multi-node support. However, it is unclear whether it should be removed from events as well. @philanton , WDYT?

Copy link
Copy Markdown
Contributor

@philanton philanton left a comment

Choose a reason for hiding this comment

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

I'm not against validator activation delay, but it seems to be just a delay, so until there is a clear incident response mechanism, unregistering this validator, this design doesn't seem to be complete.
The only entity with the ability to register a validator is node signer, and if it is compromised, there is no way to stop the exploit right now.

For golang side, the only thing that I see required for update is ABI bindings, there is not really something related to multi-node setup right now.

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.

3 participants