Skip to content

Extend Aux Blob to breach scenarios#10583

Open
GeorgeTsagk wants to merge 6 commits intolightningnetwork:masterfrom
GeorgeTsagk:aux-htlc-revocation
Open

Extend Aux Blob to breach scenarios#10583
GeorgeTsagk wants to merge 6 commits intolightningnetwork:masterfrom
GeorgeTsagk:aux-htlc-revocation

Conversation

@GeorgeTsagk
Copy link
Collaborator

Description

This PR extends the auxiliary channel contract framework to properly handle HTLC outputs during breach scenarios.

Currently, the aux sweeper infrastructure handles commitment output revocations but lacks the plumbing needed for HTLC outputs. This fills in those gaps by:

  • Populating resolution blobs for HTLC retribution info, enabling aux contract resolvers to construct valid sweep transactions for revoked HTLCs.
  • Passing the breach commitment height through to HTLC resolution requests, which external resolvers need to locate and reference the breaching transaction.
  • Deferring aux sweeper notifications until justice transactions are confirmed on-chain, rather than at broadcast time.

Based on #10377

@GeorgeTsagk GeorgeTsagk self-assigned this Feb 16, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello @GeorgeTsagk, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the handling of channel breaches within the Lightning Network by extending the auxiliary channel contract framework. It introduces mechanisms to correctly process HTLC outputs during breach scenarios, ensuring that all funds are properly swept. Key changes include populating resolution blobs for HTLCs, passing crucial breach commitment height information to external resolvers, and refining the aux sweeper notification process to only trigger upon on-chain confirmation of justice transactions. This ensures more robust and reliable breach recovery, particularly for custom channel types.

Highlights

  • HTLC Breach Retribution: Enhanced the auxiliary channel contract framework to properly handle HTLC outputs during breach scenarios, ensuring correct retribution by populating resolution blobs and passing breach commitment height.
  • Deferred Aux Sweeper Notifications: Modified the aux sweeper notification mechanism to defer calls until justice transactions are confirmed on-chain, improving reliability and consistency by avoiding notifications for unconfirmed transactions.
  • Improved Private Key Tweak Application: Corrected the order and application of single and double tweaks to private keys, ensuring cryptographic correctness for complex scenarios where both tweaks are present.
  • Comprehensive Testing: Added new unit tests to validate the correct behavior of confirmed justice transaction notifications and the refined private key tweaking logic, improving code robustness.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • contractcourt/breach_arbitrator.go
    • Added notifyConfirmedJusticeTx function to notify the aux sweeper only after justice transactions are confirmed on-chain.
    • Removed immediate NotifyBroadcast call after broadcasting justice transactions, deferring it to the spend detection logic.
    • Introduced notifiedJusticeTxs map to prevent duplicate notifications.
  • contractcourt/breach_arbitrator_test.go
    • Added mockAuxSweeperNotify to simulate aux sweeper behavior for testing.
    • Implemented TestNotifyConfirmedJusticeTx to verify the new notification logic for confirmed justice transactions.
    • Added TestNotifyConfirmedJusticeTxNoAuxSweeper to test scenarios without an aux sweeper.
  • lnwallet/btcwallet/signer.go
    • Updated maybeTweakPrivKey to correctly apply both double (revocation) and single (HTLC index) tweaks to private keys in the specified order.
  • lnwallet/btcwallet/signer_test.go
    • Added TestMaybeTweakPrivKey with various test cases for single, double, and combined private key tweaking.
  • lnwallet/channel.go
    • Modified createHtlcRetribution to accept spendTx, auxResolver, revokedLog, and breachHeight parameters.
    • Added logic within createHtlcRetribution to determine HTLC witness type, create a ResolutionReq, and obtain a ResolutionBlob from the AuxContractResolver.
    • Included the ResolutionBlob in the returned HtlcRetribution struct.
    • Updated createBreachRetribution and NewBreachRetribution to pass the new auxResolver and breachHeight parameters.
    • Adjusted createBreachRetributionLegacy to also accept auxResolver and breachHeight.
  • lnwallet/channel_test.go
    • Updated calls to createHtlcRetribution, createBreachRetribution, and createBreachRetributionLegacy in tests to match the new function signatures.
  • sweep/fee_bumper.go
    • Modified the NotifyBroadcast call to the AuxSweeper to explicitly pass false for the skipBroadcast parameter, indicating a normal broadcast.
  • sweep/interface.go
    • Updated the AuxSweeper interface by adding a skipBroadcast boolean parameter to the NotifyBroadcast method.
  • sweep/mock_test.go
    • Adjusted the MockAuxSweeper's NotifyBroadcast method signature to accommodate the new skipBroadcast parameter.
Activity
  • No specific activity (comments, reviews, progress updates) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@lightninglabs-deploy lightninglabs-deploy added the severity-critical Requires expert review - security/consensus critical label Feb 16, 2026
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request extends the auxiliary channel contract framework to handle HTLC outputs in breach scenarios. The changes include populating resolution blobs for HTLC retribution, passing breach commitment height to HTLC resolution requests, and deferring auxiliary sweeper notifications until justice transactions are confirmed. The logic appears sound, and the changes are well-tested. I've made a few minor suggestions to improve code clarity and correctness, particularly around handling combined tweaks and clarifying test comments.

Comment on lines +2759 to +2760

// Should not panic and should not mark as notified since there's no

Choose a reason for hiding this comment

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

medium

This comment is misleading. The implementation of notifyConfirmedJusticeTx does mark the transaction as notified even if there is no aux sweeper, and the subsequent comment and test assertion confirm this is the desired behavior. To avoid confusion, I suggest removing this comment block.

Comment on lines 239 to +245
// maybeTweakPrivKey examines the single and double tweak parameters on the
// passed sign descriptor and may perform a mapping on the passed private key
// in order to utilize the tweaks, if populated.
// in order to utilize the tweaks, if populated. If both tweak parameters are
// set, then both are applied in the following order:
//
// a) double tweak
// b) single tweak

Choose a reason for hiding this comment

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

medium

The function comment has been updated to describe the case where both SingleTweak and DoubleTweak are present. According to the new logic, the double tweak is applied first, then the single tweak. It would be clearer to explicitly state this order in the comment for better maintainability.

// maybeTweakPrivKey examines the single and double tweak parameters on the
// passed sign descriptor and may perform a mapping on the passed private key
// in order to utilize the tweaks, if populated. If both tweak parameters are
// set, then they are applied in the order of the double tweak, then the
// single tweak.
//
//  a) double tweak
//  b) single tweak

spendingTxHash := *s.detail.SpenderTxHash

// Skip if we've already notified about this transaction.
if notifiedTxs[spendingTxHash] {
Copy link
Member

Choose a reason for hiding this comment

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

Where will this state be stored/maintained?

Copy link
Collaborator Author

@GeorgeTsagk GeorgeTsagk Feb 26, 2026

Choose a reason for hiding this comment

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

The map is in-memory only and won't survive restarts. On restart, exactRetribution re-enters the spend detection loop and could call NotifyBroadcast again for the same tx.

Will address this on the tapd side by making sure NotifyBroadcast is idempotent / reentrancy-safe

// so we pass skipBroadcast=true.
return aux.NotifyBroadcast(
&bumpReq, s.detail.SpendingTx,
justiceCtx.fee, nil, true,
Copy link
Member

Choose a reason for hiding this comment

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

All values should be populated here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

outpointToTxIndex is only populated for inputs with a non-nil RequiredTxOut() (seem to be pre-signed SINGLE|ANYONECANPAY second-level HTLC txs)

For breach sweeps breachedOutput.RequiredTxOut() always returns nil since we sign fresh with the revocation key

am I missing something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

or were you referring to the bumpReq fields?


// Track which justice transactions we've already notified the aux
// sweeper about, to avoid duplicate NotifyBroadcast calls.
notifiedJusticeTxs := make(map[chainhash.Hash]bool)
Copy link
Member

Choose a reason for hiding this comment

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

How can we have duplicate notifications if only one version can confirm? You have some re-org scenario in mind?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The dedup map is needed because waitForSpendEvent registers a separate RegisterSpendNtfn for each breached output. When a single justice tx (e.g. spendAll) confirms and spends all N outputs, the chain notifier delivers a SpendDetail to each subscription independently.

You can verify by running the itest revoked_uncooperative_close_retribution_remote_hodl and grep'ing for "Detected spend on".

Without it we'd be calling NotifyBroadcast multiple times

Regarding re-orgs:
with the recent N-block confirmation delay changes in chain_watcher, spends reaching this code path are already N-blocks deep, so re-org-triggered duplicates shouldn't be a practical concern here.

@lightninglabs-deploy
Copy link
Collaborator

@gijswijs: review reminder
@GeorgeTsagk, remember to re-request review from reviewers when ready

Previously we'd define either a single or a double tweak for the sign
descriptor. We introduce the option to apply both consecutively (double
tweak first, single tweak second) if both tweak parameters are set. For
callers who define only one of the two parameters we maintain the old
behavior.
Pass the breach height through the call chain from NewBreachRetribution
to createHtlcRetribution, and include it in the ResolutionReq for HTLC
outputs via CommitTxBlockHeight.

This is required for taproot-assets to properly reanchor asset proofs
when sweeping revoked HTLCs. The reanchorAssetOutputs function needs
the block height to fetch the block containing the breach commitment
transaction and construct valid proofs for the sweep.

Without this, the CommitTxBlockHeight defaults to 0, causing tapd to
look for the commitment tx in the genesis block and fail with:
"commit tx not found in block".

See: lightninglabs/taproot-assets@815021fd (tapchannel: add
reanchorAssetOutputs for proof reanchoring)
Add unit tests for the notifyConfirmedJusticeTx function to verify
correct behavior when detecting confirmed justice transactions and
notifying the aux sweeper.

Test cases cover:
- Detection of each justice tx variant (spendAll, spendCommitOuts,
  spendHTLCs)
- Skipping already-notified transactions via notifiedTxs map
- Skipping unconfirmed spends (SpendingHeight == 0)
- No notification for unrelated transactions
- Multiple spends with mixed matching/non-matching
- Graceful handling of nil justice tx contexts
- Operation without an aux sweeper configured
@lightninglabs-deploy lightninglabs-deploy added severity-critical Requires expert review - security/consensus critical and removed severity-critical Requires expert review - security/consensus critical labels Feb 26, 2026
@GeorgeTsagk GeorgeTsagk requested a review from Roasbeef February 26, 2026 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

severity-critical Requires expert review - security/consensus critical

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants