Skip to content

M-I02: normalize input hex addresses#663

Merged
philanton merged 1 commit intofix/audit-findingsfrom
fix/clearnode-i-02
Apr 9, 2026
Merged

M-I02: normalize input hex addresses#663
philanton merged 1 commit intofix/audit-findingsfrom
fix/clearnode-i-02

Conversation

@philanton
Copy link
Copy Markdown
Contributor

@philanton philanton commented Apr 8, 2026

Description

The clearnode API accepts wallet addresses without the 0x prefix. common.IsHexAddress validates format but accepts both "0xabc123..." (42 chars) and "abc123..." (40 chars) as valid. Both representations decode to the same 20-byte address via common.HexToAddress, producing identical channel IDs, signatures, and on-chain behavior — but they are treated as different strings in the database.

The signature verification in ChannelSigValidator.Verify (pkg/core/channel_signer.go:213) compares the recovered address (always "0x..."-prefixed via EthereumAddress.String()) against the wallet using strings.EqualFold. A no-prefix wallet will always fail this comparison, causing the entire database transaction to roll back.

However, this is an accidental guard — the rejection happens at signature validation (line 150), not at input sanitization (line 27). The code between those lines (channel ID computation, existing channel lookup, state advancement validation) all execute with a wallet string that doesn't match the database's expected format before the transaction is rolled back.

Summary by CodeRabbit

Bug Fixes

  • Improved Ethereum address validation with clearer, more detailed error messages across API endpoints for session and channel operations.
  • Standardized address formatting for consistent handling across the platform.

Tests

  • Added comprehensive test coverage for address validation and normalization functionality.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

📝 Walkthrough

Walkthrough

This pull request replaces go-ethereum/common hex address validation with a new core.NormalizeHexAddress utility function across multiple API handlers. The change standardizes address normalization to lowercase and provides more detailed error messages while maintaining existing control flow for downstream operations.

Changes

Cohort / File(s) Summary
API Handler Address Normalization
clearnode/api/app_session_v1/submit_session_key_state.go, clearnode/api/channel_v1/handler.go, clearnode/api/channel_v1/request_creation.go, clearnode/api/channel_v1/submit_session_key_state.go
Replaced common.IsHexAddress validation with core.NormalizeHexAddress normalization for user address and session key fields. Updated error messages to report normalization errors instead of generic validation failures.
Core Utility Enhancement
pkg/app/app_v1.go
Updated PackAppV1 to normalize app.OwnerWallet via core.NormalizeHexAddress and return normalization errors, replacing boolean address validation with error-based handling.
Normalization Utility & Tests
pkg/core/utils.go, pkg/core/utils_test.go
Added new NormalizeHexAddress function that enforces strict Ethereum address formatting (0x prefix + 40 hex chars), validates hex character validity, and returns descriptive errors. Includes comprehensive unit tests covering valid inputs (various cases), missing prefix, incorrect length, empty string, and invalid characters.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hops through hex so bright,
Normalizing addresses left and right,
Gone are the days of loose validation loose,
Now errors speak truth, no more excuse,
From 0xABC to 0xabc we glide,
Consistent addresses with nowhere to hide! 🔐

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly matches the main change: adding normalization of input hex addresses via the new NormalizeHexAddress function applied across multiple API handlers.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/clearnode-i-02

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

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 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.

@nksazonov
Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 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: 3

🧹 Nitpick comments (2)
pkg/app/app_v1.go (1)

35-37: Wrap normalization error with %w to preserve error chain.

Current formatting drops typed error semantics for upstream handling.

♻️ Suggested patch
-		return nil, fmt.Errorf("invalid owner wallet address: %v", err)
+		return nil, fmt.Errorf("invalid owner wallet address: %w", err)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/app/app_v1.go` around lines 35 - 37, The error returned from
core.NormalizeHexAddress when assigning app.OwnerWallet should be wrapped using
%w to preserve the error chain; update the error return in the block that calls
core.NormalizeHexAddress (the assignment to app.OwnerWallet) so the fmt.Errorf
uses "%w" and returns the original error (err) instead of "%v", ensuring callers
can use errors.Is / errors.As on the wrapped error.
clearnode/api/channel_v1/handler.go (1)

92-93: Preserve underlying DB error details in both receiver-state reads.

Both branches now lose err, which makes incident debugging harder.

♻️ Suggested patch
-		return nil, rpc.Errorf("failed to get last %s user state for transfer receiver with address %s", senderState.Asset, receiverWallet)
+		return nil, rpc.Errorf("failed to get last %s user state for transfer receiver with address %s: %v", senderState.Asset, receiverWallet, err)
...
-		return nil, rpc.Errorf("failed to get last %s user state for transfer receiver with address %s", senderState.Asset, receiverWallet)
+		return nil, rpc.Errorf("failed to get last %s user state for transfer receiver with address %s: %v", senderState.Asset, receiverWallet, err)

Also applies to: 109-110

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

In `@clearnode/api/channel_v1/handler.go` around lines 92 - 93, The two
error-return branches that currently call rpc.Errorf("failed to get last %s user
state for transfer receiver with address %s", senderState.Asset, receiverWallet)
are dropping the underlying err; update both failing branches (the
receiver-state read branches that call rpc.Errorf) to include the original err
details in the message (e.g., pass err as an argument or format it into the
string) so rpc.Errorf includes the underlying DB error, referencing the existing
variables err, senderState.Asset, and receiverWallet in each updated call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@clearnode/api/channel_v1/submit_session_key_state.go`:
- Around line 35-45: Normalization of user/session keys via
core.NormalizeHexAddress (used on coreState.UserAddress and
coreState.SessionKey) can cause misses in GetLastChannelSessionKeyVersion(...)
because historical rows may use legacy (non-canonical) keys; add a migration
plan or implement a temporary dual-read reconciliation: when reading last
session version (the code path invoking GetLastChannelSessionKeyVersion),
attempt lookup by the normalized key first and if not found perform a fallback
lookup using the legacy/raw key and reconcile results (or mark for migration),
and when writing persist both canonical and legacy keys or schedule a DB
backfill to update existing rows before switching to normalized-only keys.
Ensure changes are applied around the functions NormalizeHexAddress, the submit
handler (where coreState is normalized), and the GetLastChannelSessionKeyVersion
lookup to avoid forking during rollout.

In `@pkg/core/utils_test.go`:
- Around line 42-45: The "missing_0x_prefix" test currently expects an error for
a non-0x-prefixed address; update the test to accept normalization instead: set
wantErr to false for the "missing_0x_prefix" case and assert that the function
under test returns the normalized form (i.e., the input prefixed with "0x" —
"0x1234567890abcdef1234567890abcdef12345678") or otherwise matches the canonical
normalized value produced by your NormalizeAddress/NormalizeHexAddress helper;
keep the original input and only change the expectation and add the
normalization assertion.

In `@pkg/core/utils.go`:
- Around line 28-43: NormalizeHexAddress currently rejects 40-char hex addresses
without the "0x" prefix; update it to accept and canonicalize those by
lowercasing input, allowing either a 42-char string with "0x" prefix or a
40-char hex string, validating hex characters in either case and returning the
lowercased address with a "0x" prefix; adjust the validation logic in
NormalizeHexAddress to check for len==40 (no prefix) or len==42 with prefix,
validate characters starting at the appropriate index, and return the canonical
"0x"+lowercasedHex string on success.

---

Nitpick comments:
In `@clearnode/api/channel_v1/handler.go`:
- Around line 92-93: The two error-return branches that currently call
rpc.Errorf("failed to get last %s user state for transfer receiver with address
%s", senderState.Asset, receiverWallet) are dropping the underlying err; update
both failing branches (the receiver-state read branches that call rpc.Errorf) to
include the original err details in the message (e.g., pass err as an argument
or format it into the string) so rpc.Errorf includes the underlying DB error,
referencing the existing variables err, senderState.Asset, and receiverWallet in
each updated call.

In `@pkg/app/app_v1.go`:
- Around line 35-37: The error returned from core.NormalizeHexAddress when
assigning app.OwnerWallet should be wrapped using %w to preserve the error
chain; update the error return in the block that calls core.NormalizeHexAddress
(the assignment to app.OwnerWallet) so the fmt.Errorf uses "%w" and returns the
original error (err) instead of "%v", ensuring callers can use errors.Is /
errors.As on the wrapped error.
🪄 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: 570d4409-d395-40ff-b255-656f68d044d4

📥 Commits

Reviewing files that changed from the base of the PR and between 38d2ad1 and ad81ee7.

📒 Files selected for processing (7)
  • clearnode/api/app_session_v1/submit_session_key_state.go
  • clearnode/api/channel_v1/handler.go
  • clearnode/api/channel_v1/request_creation.go
  • clearnode/api/channel_v1/submit_session_key_state.go
  • pkg/app/app_v1.go
  • pkg/core/utils.go
  • pkg/core/utils_test.go

Comment thread clearnode/api/channel_v1/submit_session_key_state.go
Comment thread pkg/core/utils_test.go
Comment thread pkg/core/utils.go
@philanton philanton merged commit 80e303c into fix/audit-findings Apr 9, 2026
3 checks passed
@philanton philanton deleted the fix/clearnode-i-02 branch April 9, 2026 11:43
nksazonov added a commit that referenced this pull request Apr 16, 2026
H-H01: fix(contracts): disallow challenge with CLOSE or FIN_MIG intents (#631)

H-M01: fix(contracts/ChannelHub): remove transferred amount check (#636)

fix(contracts/ChannelHub): allow unblocking escrow ops after migration (#637)

M-C01: feat(contracts): add token check between states (#639)

H-L03: fix(contracts/ChannelHub): skip disputed escrow during purge (#640)

M-H04: feat(contracts/ChannelHub): purge during escrow challenge finalization, count both skip and purge (#641)

M-M02: fix(contracts): require zero allocs on close (#643)

M-C02: fix(rpc/core): apply finalize escrow deposit correctly (#644)

H-L02: fix(contracts/ChannelHub): revert on withdrawFromVault failure (#651)

M-H03: fix(clearnode): log correct fields on failure (#647)

M-C03: fix(pkg/core): disallow negative amount transitions (#645)

H-L01: fix(contracts/ChannelHub): add CH address to prevent val addition replay (#650)

M-H06(clearnode): restrict max channel challenge duration (#654)

M-M03(clearnode): revert EscrowLock on insufficient home user balance (#655)

M-M04(clearnode): support default signer even if it is not approved (#656)

M-M05(clearnode): require correct intent on FinalizeEscrowWithdrawal (#657)

M-M09: reject asset decimals exceeding its token's decimals (#659)

M-L01: return correct errors during state advancement validation (#660)

M-L03: limit number of related ids per session key state (#662)

M-I02: normalize input hex addresses (#663)

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

M-H07: fix(contracts/ChannelHub): emit stored, not arbitrary candidate state (#664)

M-I01: feat(contracts/ChannelHub): remove updateLastState flag (#665)

H-L06: fix(contracts/ChannelHub): remove payable from methods, clarify in create (#666)

H-L09: feat(contracts/ChannelEngine): add non-home migration version check (#669)

YNU-839: fix blockchain listener lifecycle (#658)

M-H09: use channel signer for all channel state node sigs (#667)

H-L07: fix(contracts/ChannelHub): restrict createChannel to non-existing channels (#668)

H-I02: docs(contracts): add a note that rebasing tokens are not supported (#670)

H-I03: fix(contracts/ChannelHub): use CIE pattern in depositToHub (#671)

M-H11: revert empty signatures on quorum verification (#672)

M-H11: reject issuance of receiver state during escrow ops (#674)

H-I01: docs(contracts): note that fee-on-transfer tokens are not supported (#675)

M-L04: docs: mention liquidity monitoring (#677)

fix(contracts/ChannelHub): fix initialize escrow deposit dos (#679)

M-H08: enforce strict transition ordering after MutualLock and EscrowLock (#680)

fix: run forge fmt (#681)

M-L05: docs(contracts): document native token deposits (#685)

fix(clearnode): resolve a set of audit findings (#686)

M-H13: feat(contract): add validateChallengeSignature, revert in SK validator (#688)
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