Bringing a nuke to a knife fight: Transaction introspection to stop RBF pinning

Bringing a nuke to a knife fight: Transaction introspection to stop RBF pinning
2022-05-12  7:17 ` David A. Harding
  
From: Greg Sanders @ 2022-05-10 18:53 UTC (permalink / raw)
  To: Bitcoin Dev

[-- Attachment #1: Type: text/plain, Size: 5362 bytes --]

Hello devs,

I've had this thought rattling around and thought it was worth putting to a
wider audience since
I haven't really seen it in other contexts. I've been working on eltoo
designs for Elements and
eventual inclusion into Bitcoin. With that in mind there's been a
reasonable amount of discussion
on the remaining unknowns on how well eltoo could work. To me the biggest
issue is BIP125 rule#3.

To quote:"The replacement transaction pays an absolute fee of at least the
sum paid by the original
transactions."

In the ANYONECANPAY-like scenarios like eltoo that require "bring your own
fees", this essentially
means the counterparty(or anyone, if you don't include chaperone sigs[0])
can post a series of low
feerate update transactions, or the final update, with bloated
inputs/outputs(depending on flags),
and this results in illicit HTLC timeouts as the channel is unable to be
settled in time, unless you fork
over quite a few sats. This is a problem in both "vanilla" eltoo[1] from
the original paper, as well as the
"layered commitments" style of eltoo[2]. This problem is highly reminiscent
of the ANYONECANPAY
pinning that others have discussed for vaults and other usecases, in that
anyone can include new
inputs(and sometimes outputs) to make the overall feerate lower. To
promptly get the final transactions
settled, you are forced to over-pay, and essentially refund your griefing
counterparty by knocking their
inputs out of the mempool.

Fixing BIP125 rule#3 would be great. It's also a while out at a minimum.

There are thoughts on how to mitigate some cases[3] of this pinning using
policy, and could be extended
to cover this particular pinning case(restrict both transaction weight AND
the weight of the descendant
package, or maybe just include the txns weight in the original idea?). This
might be the simplest idea,
if it ends up being deemed incentive compatible and deployed.

In case the above is not incentive compatible, we can use more drastic
measures. Another tactic would
be to use transaction introspection opcodes to smooth around these policy
issues.

Elements has its own set of transaction introspection codes[4], but fairly
standard introspection codes
seem to be sufficient.

This example is using Rusty's quite recent OP_TX proposal[5] with a single
extension but as mentioned
before it's all fairly standard. The actual eltoo-enabling opcode
implementation is basically orthogonal
to this problem, so I'm simply focusing on restricting the size of the
transaction package being
submitted to mempools.

For simplicity of a working example, we'll assume a set of "state" outputs
that are continuously being spent
off-chain and sent to a committed set of outputs. In vanilla eltoo case
this corresponds to the first
input and output you typically see in diagrams. The state transitions
include no fees themselves,
sending inputs of sum value N to outputs that sum to the value of N.
Vanilla eltoo uses SIGHASH_SINGLE
to bind just the first input/ouput pair. To post on-chain, we will need to
include at least one input,
and likely an output for change.

We add OPTX_SELECT_WEIGHT(pushes tx weight to stack, my addition to the
proposal) to the "state" input's script.
This is used in the update transaction to set the upper bound on the final
transaction weight.
In this same input, for each contract participant, we also conditionally
commit to the change output's scriptpubkey
via OPTX_SELECT_OUTPUT_SCRIPTPUBKEY and OPTX_SELECT_OUTPUTCOUNT==2. This
means any participant can send change back
to themselves, but with a catch. Each change output script possibility in
that state input also includes a 1 block
CSV to avoid mempool spending to reintroduce pinning. This allows the
change value to be anything, contra to
what SIGHASH_ALL would give you instead.

With this setup, you can't CPFP-spend the fee change outputs you create,
but you can RBF as much as
you'd like by RBFing at higher feerates, using any number of inputs you'd
like provided the total tx
weight doesn't exceed the OPTX_SELECT_WEIGHT argument.

With more engineering we can re-enable CPFP of this change output as well.
Handwaves here, but we could
encumber change outputs to either the aformentioned 1 block CSV encumbered
outputs or one to another
OPTX_SELECT_WEIGHT, recursively. This would allow each counterparty to CPFP
N times, each transaction
a maximum weight, and use the 1 block CSV as an "escape hatch" to get their
fee output back out from
the covenant structure. We could mix and match strategies here as well
allowing bigger transactions at
each step, or more steps. I suspect you'd want a single weight-bound CPFP
that can later be RBF'd any
number of times under this same weight limit.

TL;DR: Mempool is hard, let's use transaction weight, output count, and
output scriptpubkey,
and ??? introspection to avoid solving life's hard problems.

0:
https://lists.linuxfoundation.org/pipermail/lightning-dev/2019-May/001994.html
1: https://blockstream.com/eltoo.pdf
2:
https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-January/002448.html
3:
https://gist.github.com/glozow/25d9662c52453bd08b4b4b1d3783b9ff?permalink_comment_id=4058140#gistcomment-4058140
4:
https://github.com/ElementsProject/elements/blob/master/doc/tapscript_opcodes.md
5:
https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2022-May/020450.html

[-- Attachment #2: Type: text/html, Size: 6460 bytes --]

 permalink raw reply	[|nested]
2022-05-10 18:53 [bitcoin-dev] Bringing a nuke to a knife fight: Transaction introspection to stop RBF pinning Greg Sanders

  2022-05-12 13:31   ` Greg Sanders
  
From: David A. Harding @ 2022-05-12  7:17 UTC (permalink / raw)
  To: Greg Sanders, Bitcoin Protocol Discussion

On 2022-05-10 08:53, Greg Sanders via bitcoin-dev wrote:
> We add OPTX_SELECT_WEIGHT(pushes tx weight to stack, my addition to
> the proposal) to the "state" input's script.
> This is used in the update transaction to set the upper bound on the
> final transaction weight.
> In this same input, for each contract participant, we also
> conditionally commit to the change output's scriptpubkey
> via OPTX_SELECT_OUTPUT_SCRIPTPUBKEY and OPTX_SELECT_OUTPUTCOUNT==2.
> This means any participant can send change back
> to themselves, but with a catch. Each change output script possibility
> in that state input also includes a 1 block
> CSV to avoid mempool spending to reintroduce pinning.

I like the idea!   However, I'm not sure the `1 CSV` trick helps much.  
Can't an attacker just submit to the mempool their other eltoo state 
updates?  For example, let's assume Bob and Mallory have a channel with 
 >25 updates and Mallory wants to prevent update[-1] from being committed onchain before its (H|P)TLC timeout.  Mallory also has at least 25 unencumbered UTXOs, so she submits to the mempool update[0], update[1], update[...], update[24]---each of them with a different second input to pay fees.

If `OPTX_SELECT_WEIGHT OP_TX` limits each update's weight to 1,000 
vbytes[1] and the default node relay/mempool policy of allowing a 
transaction and up to 24 descendants remains, Mallory can pin the 
unsubmitted update[-1] under 25,000 vbytes of junk---which is 25% of 
what she can pin under current mempool policies.

Alice can't RBF update[0] without paying for update[1..24] (BIP125 rule 
#3), and an RBF of update[24] will have its additional fees divided by 
its size plus the 24,000 vbytes of update[1..24].

To me, that seems like your proposal makes escaping the pinning at most 
75% cheaper than today.  That's certainly an improvement---yay!---but 
I'm not sure it eliminates the underlying concern.  Also depending on 
the mempool ancestor/descendant limits makes it harder to raise those 
limits in the future, which is something I think we might want to do if 
we can ensure raising them won't increase node memory/CPU DoS risk.

I'd love to hear that my analysis is missing something though!

Thanks!,

-Dave

[1] 1,000 vbytes per update seems like a reasonable value to me.  
Obviously there's a tradeoff here: making it smaller limits the amount 
of pinning possible (assuming mempool ancestor/descendant limits remain) 
but also limits the number and complexity of inputs that may be added.  
I don't think we want to discourage people too much from holding 
bitcoins in deep taproot trees or sophisticated tapscripts.


 permalink raw reply	[|nested]
2022-05-12  7:17 ` David A. Harding

  
From: Greg Sanders @ 2022-05-12 13:31 UTC ( / )
  To: David A. Harding;  Bitcoin Protocol Discussion



Great point in this specific case I unfortunately didn't consider! So
basically the design degenerates to the last option I gave, where the
counterparty
can send off N(25) weight-bound packages.

A couple thoughts:

0) Couldn't we relative-time lock update transactions's state input by 1
block as well to close the vector off? People are allowed
one "update transaction package" at a time in mempool, so if detected
in-mempool it can be RBF'd, or in-block can be immediately responded to.
1) other usages of ANYONECANPAY like behavior may not have these issues,
like vault structures.


On Thu, May 12, 2022, 3:17 AM David A. Harding <dave@dtrt•org> wrote:

> On 2022-05-10 08:53, Greg Sanders via bitcoin-dev wrote:
> > We add OPTX_SELECT_WEIGHT(pushes tx weight to stack, my addition to
> > the proposal) to the "state" input's script.
> > This is used in the update transaction to set the upper bound on the
> > final transaction weight.
> > In this same input, for each contract participant, we also
> > conditionally commit to the change output's scriptpubkey
> > via OPTX_SELECT_OUTPUT_SCRIPTPUBKEY and OPTX_SELECT_OUTPUTCOUNT==2.
> > This means any participant can send change back
> > to themselves, but with a catch. Each change output script possibility
> > in that state input also includes a 1 block
> > CSV to avoid mempool spending to reintroduce pinning.
>
> I like the idea!   However, I'm not sure the `1 CSV` trick helps much.
> Can't an attacker just submit to the mempool their other eltoo state
> updates?  For example, let's assume Bob and Mallory have a channel with
>  >25 updates and Mallory wants to prevent update[-1] from being committed
> onchain before its (H|P)TLC timeout.  Mallory also has at least 25
> unencumbered UTXOs, so she submits to the mempool update[0], update[1],
> update[...], update[24]---each of them with a different second input to pay
> fees.
>
> If `OPTX_SELECT_WEIGHT OP_TX` limits each update's weight to 1,000
> vbytes[1] and the default node relay/mempool policy of allowing a
> transaction and up to 24 descendants remains, Mallory can pin the
> unsubmitted update[-1] under 25,000 vbytes of junk---which is 25% of
> what she can pin under current mempool policies.
>
> Alice can't RBF update[0] without paying for update[1..24] (BIP125 rule
> #3), and an RBF of update[24] will have its additional fees divided by
> its size plus the 24,000 vbytes of update[1..24].
>
> To me, that seems like your proposal makes escaping the pinning at most
> 75% cheaper than today.  That's certainly an improvement---yay!---but
> I'm not sure it eliminates the underlying concern.  Also depending on
> the mempool ancestor/descendant limits makes it harder to raise those
> limits in the future, which is something I think we might want to do if
> we can ensure raising them won't increase node memory/CPU DoS risk.
>
> I'd love to hear that my analysis is missing something though!
>
> Thanks!,
>
> -Dave
>
> [1] 1,000 vbytes per update seems like a reasonable value to me.
> Obviously there's a tradeoff here: making it smaller limits the amount
> of pinning possible (assuming mempool ancestor/descendant limits remain)
> but also limits the number and complexity of inputs that may be added.
> I don't think we want to discourage people too much from holding
> bitcoins in deep taproot trees or sophisticated tapscripts.
>