Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sweep: group pinnable and non-pinnable inputs separately #9427

Open
morehouse opened this issue Jan 17, 2025 · 10 comments
Open

sweep: group pinnable and non-pinnable inputs separately #9427

morehouse opened this issue Jan 17, 2025 · 10 comments
Assignees
Labels
enhancement Improvements to existing features / behaviour security General label for issues/PRs related to the security of the software utxo sweeping
Milestone

Comments

@morehouse
Copy link
Collaborator

The sweeper currently has no concept of pinnable and unpinnable inputs, so HTLC-Timeout (pinnable) and HTLC-Success (not pinnable) inputs can be aggregated into the same transaction if they have the same deadline. As a result, the HTLC-Success inputs effectively become pinnable too.

Another example of this is expired anchors (pinnable) being aggregated with HTLC-Success (not pinnable), though this could also be solved by removing expired anchor sweeping.

I think @yyforyongyu's proposed "sweeper groups" would be all we need to properly implement this.

@morehouse morehouse added enhancement Improvements to existing features / behaviour security General label for issues/PRs related to the security of the software utxo sweeping labels Jan 17, 2025
@yyforyongyu yyforyongyu added this to the v0.19.0 milestone Jan 18, 2025
@yyforyongyu yyforyongyu self-assigned this Jan 18, 2025
@TheBlueMatt
Copy link

TheBlueMatt commented Jan 21, 2025

We did this in LDK in the latest release (I assume that's where @morehouse saw this). One subtle issue, though, is an HTLC can become pinnable once it (gets close to) expiring as suddenly both parties can claim it. Thus it's useful to track the pinnable state rather than making it fixed.

@morehouse
Copy link
Collaborator Author

@TheBlueMatt Will transactions actually relay with an nLockTime in the future? If not, then this isn't a problem until the HTLC actually expires, in which case we're partially screwed already. Of course, splitting expired inputs out at that point helps control the damage.

@TheBlueMatt
Copy link

They will not, no, indeed, for non-revoked transactions its really only an issue after expiry. If you do this for expired counterparty broadcasts, though, it needs consideration.

@KapilSareen
Copy link

Hi @yyforyongyu @morehouse is this issue being worked on? If not, I would love to take it up.

@yyforyongyu yyforyongyu modified the milestones: v0.19.0, v0.20.0 Mar 17, 2025
@yyforyongyu
Copy link
Member

I wonder if this is still needed given that we now always retry failed inputs immediately?

Hi @yyforyongyu @morehouse is this issue being worked on? If not, I would love to take it up.

Thanks for the interest! However I'd recommend looking into some of the good first issues before diving into this one.

@morehouse
Copy link
Collaborator Author

I wonder if this is still needed given that we now always retry failed inputs immediately?

That doesn't fix the issue.

If a low-feerate package is pinned in our mempool, our broadcast will fail with a "not-enough-fees" error, which AFAIK doesn't allow us to remove only the pinned inputs. Additionally, mempools can be partitioned, which means we wouldn't even know about the pin.

Once we have all channels using zero-fee commitments plus all HTLC transactions are v3, then we could potentially batch HTLC-Timeouts with HTLC-Successes and not worry about pinning. But it will probably be several years before we can deprecate and remove anchor channels completely.

@yyforyongyu
Copy link
Member

If a low-feerate package is pinned in our mempool, our broadcast will fail with a "not-enough-fees" error, which AFAIK doesn't allow us to remove only the pinned inputs. Additionally, mempools can be partitioned, which means we wouldn't even know about the pin.

I think it depends on what happens in the following block - if the pinning tx confirms, we'd remove the input and sweep the rest; otherwise, we'd just move the fee func to the next position and perform a potential fee bump. This fee bump may or may not replace the pinning tx, tho I think it's irrelevant here as we only stick to the budget?

@morehouse
Copy link
Collaborator Author

Another thought: if an attacker wants to steal funds, they will just make sure their target HTLCs are pinnable. So grouping pinnable and non-pinnable inputs separately doesn't really reduce the attack surface.

If anything, the separate grouping would help against accidental "pinning" where an honest peer is doing lots of batching. In that case we'd be able to claim the non-pinnable inputs separately at a minimal fee rate and could be sure their deadlines wouldn't be missed. This would reduce the potential damage of the accidental pin.

The main question then is whether this scenario occurs often enough in practice to justify the grouping change. We've already seen this accidental pinning with anchors, so we should at least fix that by removing anchor sweeping. But for HTLC-Timeouts accidental pinning seems much less likely.

@TheBlueMatt
Copy link

Depending on your force-close timing and the specific attack, avoiding batching both HTLC preimage claims and HTLC timeout claims is a really good defense in depth. If you imagine some attack that relies on providing a preimage and then pinning on-chain HTLC preimage claims this can be great. Splitting claims into pinnable and not-pinnable also doesn't materially increase your total cost, whereas trying to avoid these kinds of attacks more generally requires much more aggressive splitting.

@morehouse
Copy link
Collaborator Author

Depending on your force-close timing and the specific attack, avoiding batching both HTLC preimage claims and HTLC timeout claims is a really good defense in depth. If you imagine some attack that relies on providing a preimage and then pinning on-chain HTLC preimage claims this can be great.

I thought through this some more, and I agree. This sort of pinning attack against batched claims requires a pin to be maintained for a much shorter period of time, so it's worth having the extra defense for this case.

Assume a topology like this

M1 -- B -- M2

To pull off this batched pin the attacker would do something like:

  1. Route an HTLC H1 along the path M1 -> B -> M2.
  2. Route another HTLC H2 along the path M2 -> B -> M1.
  3. Wait until H1 is about to expire. Then M1 sends the H2 preimage to B and completes the commitment signed dance. At the same time, M2 refuses to cooperate when B relays the H2 preimage upstream.
  4. H1 expires and B force closes the B -> M2 channel to claim H1 on chain. B batches the H1 timeout and H2 preimage claims into the same transaction.
  5. M2 pins the batched transaction using the preimage for H1.
  6. If the pin succeeds for enough blocks (~10 blocks for LND), M2 is able to claim H2 via HTLC-Timeout.
  7. If the pin succeeds for the full CLTV delta (~80 blocks for LND), M1 is able to claim H1 via HTLC-Timeout.

This sort of attack is more complicated than a straightforward pin for the full CLTV delta, but it also means the attacker only needs to pin for ~10 blocks rather than ~80 blocks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Improvements to existing features / behaviour security General label for issues/PRs related to the security of the software utxo sweeping
Projects
None yet
Development

No branches or pull requests

4 participants