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

test: poc that locks up a channel #3498

Closed

Conversation

m-schmoock
Copy link
Collaborator

@m-schmoock m-schmoock commented Feb 10, 2020

This poc demonstrates how a remote channel can be locked by an attacker,
so that no one can use this channel again except by making a small trimmed
payment first. This is based on test_pay test_channel_drainage.

Impact: Network health affected. One could build a plugin that locks up
strategic channels to gain advantage over routing fees or make certain routes
unusable. The attacker can find out by trying which side was the founder
and use several circular payments through higher capacity routes that contains
the victims bottleneck in order to lock up a channel.

Suggestion: Prevent running into the 'trimmed only state'
by raising CAPACITY_EXCEEDED when a channel is already so low that
only a trimmed HTLC would fit. Thus, trimmed HTLCs will only be
possible if there is enough remaining capacity to let the next HTLC
to be untrimmed.

This poc demonstrates how a remote channel can be locked by an attacker,
so that no one can use this channel again except by making a small trimmed
payment first. This is based on test_pay test_channel_drainage.

Impact: Network health affected. One could build a plugin that locks up
strategic channels to gain advantage over routing fees or make certain routes
unusable. The attacker can find out by trying which side was the founder
and use several circular payments throug higher capacity routes through
the victims bottleneck in order to lock up a channel.

Suggestion: Prevent running into the 'trimmed only state'
by raising CAPACITY_EXCEEDED when a channel is too low already.

Changelog-None
@m-schmoock m-schmoock requested a review from cdecker as a code owner February 10, 2020 09:19
@m-schmoock m-schmoock requested a review from ZmnSCPxj February 10, 2020 10:46
@ZmnSCPxj
Copy link
Contributor

Have not seen the code yet, but then my grasp of the Python test framework is iffy at best. In any case @rustyrussell is reportedly coding a fix (which I presume he will push on top of this PR).

For those watching, the explanation is this:

Remember three things:

  • "Initiator Pays" means that the Initiator of the channel --- i.e. the channel funder --- pays for all onchain fees, for both funding and closing actions. This includes fees needed to instantiate an HTLC on each commitment transaction, because a commitment transaction is a unilateral close.
    • Initiator Pays is important protection against active attacks: I could grief your node by creating a new throwaway node making a channel to you, sending out my money, then taking my throwaway node permanently offline and making another throwaway, until all your money is stuck in channels to nodes that will never come online. If you had to spend your own money to pay for the fees of closing those channels, that would greatly worsen this griefing attack, since now you have to spend a lot more funds on onchain fees to recover your own money. With initiator pays, at least the fees to close are from me, the attacker, so I am punished that way.
  • A "channel reserve" must always be had, and if you have funds below the channel reserve, you are not allowed to go to a state where you have less than what you have. If your funds are at or above the channel reserve, you are not allowed to go to a state where you are below the channel reserve.
    • Channel Reserve is needed as something you could always potentially lose, in order to deter theft attempts. Since you will always have some value in the channel, of at least the reserve amount, you always have something you risk losing in case you try to steal but fail. The only time we allow you to have an amount less than the channel reserve is if you started out with less than the channel reserve (i.e. the other side funded you), but in that case you cannot steal because there is no past state you can steal with via replay, because either this is the first state (the channel was funded to you and this is a new channel) or all previous states have even less money in your side than now (so you would not be stealing if you replayed those).
  • HTLCs have a cost: when instantiated on the commitment transaction, the commitment transaction becomes bigger because it is an additional output. So adding an HTLC to a channel increases the fee for the commitment transaction, which according to Initiator Pays is deducted from the funder of the channel.
    • A wrinkle here is that we have a concept of "trimmed" outputs: an output which the channel participants treat as real, but which they do not actually instantiate on the commitment transactions. An HTLC is trimmed if the value it sends is below the dust threshold, some 500-something satoshi. Such outputs are considered so low-value that if instantiated onchain, they would not be practical to redeem; instead their value is donated as onchain fee to the miners who mine the commitment transaction they would have been on.

This lockup condition in this PR is triggered by doing the following:

  • The funder (by itself, or induced by a remote forwarding request as in this PR) sends out most of the value on its side in an HTLC that is claimed by the non-funding side.
    • This allocates some onchain fee from the funder side, to pay for the HTLC.
  • The HTLC is claimed offchain.
    • The onchain fee that was allocated for the HTLC is returned back to the funding side.
  • The funder still has the reserve, plus the onchain fee from the previous HTLC.
  • The funder (by itself, or induced by a remote forwarding request) sends out the amount of the previous onchain fee for the previous (now claimed) HTLC in a new HTLC that is below the dust limit (i.e. a trimmed HTLC).
    • If the remaining money of the funder (beyond the reserve) is still higher than the dust limit, multiple trimmed HTLCs can be sent to transfer the entire value.
  • The trimmed HTLC(s) are claimed offchain, again.
  • Because these were trimmed HTLC(s), there was no onchain fee allocated for that HTLC (it was trimmed), so nothing is returned to the funder.
  • The channel is now in a locked state:
    • The funder cannot forward anything because its side of the channel is exactly the reserve, so it has nothing it can send without breaking the channel security.
    • The non-funding side has almost all the funds, but can only forward trimmed HTLCs (i.e. low-value HTLCs).
      • If the non-funding side were to attempt to fund a non-trimmed large HTLC, someone would have to fund the HTLC, which under Initiator Pays is the funding side. But the funding side has only the reserve, which we cannot allow to spend.

@m-schmoock please check correctness of my explanation.

A basic solution would be to have a higher-than-reserve "soft" limit, and the funder will refuse to send out outgoing HTLCs via that channel if the funder has less than this soft limit on its side of this channel; this "soft reserve" will instead be used only for paying for incoming HTLCs (i.e. HTLCs from the non-funding side will spend it to fund the onchain fees for those HTLCs). @rustyrussell please check my understanding of the proposed solution.

@rustyrussell
Copy link
Contributor

Related: lightning/bolts#728

@ZmnSCPxj
Copy link
Contributor

ZmnSCPxj commented Feb 11, 2020

The proposal to uses 5 * htlcFee seems reasonable as well; we were thinking 1.5 * htlcFee, but sudden spikes in onchain feerate should probably be protected against as well, so 5 * seems better.

@rustyrussell
Copy link
Contributor

We already have a lot of padding in fees: if we make the multiplier too great, we end up with small channels being completely unusable.

On my node, we're already paying a fee of $0.45 USD for unilateral close. Requiring $5 as extra reserve is pretty bad :(

rustyrussell added a commit to rustyrussell/lightning that referenced this pull request Feb 11, 2020
This is inspired by @m-schmook's ElementsProject#3498
except this is simply a two-channel version which probes for the amount.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
rustyrussell added a commit to rustyrussell/lightning that referenced this pull request Feb 11, 2020
This is inspired by @m-schmook's ElementsProject#3498
except this is simply a two-channel version which probes for the amount.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
rustyrussell added a commit to rustyrussell/lightning that referenced this pull request Feb 11, 2020
This is inspired by @m-schmook's ElementsProject#3498
except this is simply a two-channel version which probes for the amount.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
@m-schmoock
Copy link
Collaborator Author

m-schmoock commented Feb 11, 2020

@ZmnSCPxj your summary is correct. The problem is known (to me at least) since May 19 where we added the test drainage case: #2688

Edit: I moved my questions to Rustys PR

cdecker pushed a commit that referenced this pull request Feb 11, 2020
This is inspired by @m-schmook's #3498
except this is simply a two-channel version which probes for the amount.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
@m-schmoock m-schmoock closed this Feb 12, 2020
@m-schmoock m-schmoock deleted the test/trimmed_lockup_poc branch February 15, 2020 15:38
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