Skip to content

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

Merged
philanton merged 2 commits into
fix/audit-findingsfrom
fix/clearnode-h-09
Apr 9, 2026
Merged

M-H09: use channel signer for all channel state node sigs#667
philanton merged 2 commits into
fix/audit-findingsfrom
fix/clearnode-h-09

Conversation

@philanton
Copy link
Copy Markdown
Contributor

@philanton philanton commented Apr 9, 2026

Description

The app-session router constructs two different signing paths. The channel API is initialized with a channel signer, while the app-session API is initialized with the generic signer directly.

channelV1Handler := channel_v1.NewHandler(useChannelV1StoreInTx, memoryStore, actionGateway, nodeChannelSigner, stateAdvancer, statePacker, nodeAddress, cfg.MinChallenge, runtimeMetrics, cfg.MaxSessionKeyIDs)
appSessionV1Handler := app_session_v1.NewHandler(useAppSessionV1StoreInTx, memoryStore, actionGateway, signer, stateAdvancer, statePacker, nodeAddress, runtimeMetrics, cfg.MaxParticipants, cfg.MaxSessionDataLen, cfg.MaxSessionKeyIDs, cfg.MaxRebalanceSignedUpdates)

When an app deposit is processed, the handler verifies that the submitted user channel state uses the channel signature format, then packs that channel state and signs NodeSig with the generic app-session signer before storing it. The same pattern is used when app withdrawals and app closes issue release channel states. The release helper packs a channel state, signs it with the generic signer, and stores it as the node signature for that channel state.
This is inconsistent with the channel approach. Channel signatures are not plain Ethereum signatures. The channel signer wraps the underlying signature with a leading signer-type byte.

func (s *ChannelDefaultSigner) Sign(data []byte) (sign.Signature, error) {
    sig, err := s.Signer.Sign(data)
    if err != nil {
        return sign.Signature{}, err
    }

    return append([]byte{byte(ChannelSignerType_Default)}, sig...), nil
}

That prefix is required because both the off-chain verifier and the ChannelHub contract treat the first byte of the signature as the validator/signer identifier and verify only the remaining bytes. Because the app-session flows write raw signatures into core.State.NodeSig, the first byte of the signature body is misinterpreted as the signer type. The stored value is therefore not a valid channel signature, and it is persisted as if it were.
As a result, app deposits, withdrawals, and closes can store channel states with invalid NodeSig values. Later checkpoint, challenge, or settlement flows will reject those states, making the latest app-produced state unusable.

Summary by CodeRabbit

  • Tests

    • Updated test fixtures across multiple test suites to use channel-specific mock signer implementation.
  • Refactor

    • Updated handler signer dependency type to channel-specific implementation.
    • Adjusted RPC router configuration to align signer routing with handler requirements.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 9, 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: 07011f92-e75b-4ec5-8d1f-09feeb7a5837

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/clearnode-h-09

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.

Copy link
Copy Markdown
Contributor

@nksazonov nksazonov left a comment

Choose a reason for hiding this comment

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

LGTM
Still, is it possible to create a (non-integration) test that will verify that a signed state produced by an app session operation is valid to be submitted on-chain?

@philanton philanton merged commit 4f85489 into fix/audit-findings Apr 9, 2026
3 checks passed
@philanton philanton deleted the fix/clearnode-h-09 branch April 9, 2026 15:53
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.

2 participants