Key observation: on-chain fees for off-chain application settlement should be endogenous, not exogenous. In more plain english, there is no good reason to bring funds from "outside" of an off-chain contract to pay the fees necessary to settle that contract on-chain. Any party in the contract is ready to burn at most their contract balance in on-chain fees, but it wouldn't make sense to burn more, even for a scorched earth strategy. This is something I brought up a while ago in lightning/bolts#845
We could achieve this with an extension to SIGHASH_ANYPREVOUT
, which I'm calling SIGHASH_ANYAMOUNT
.
This could be a flag to complement a SIGHASH_ANYPREVOUT
/ SIGHASH_ANYPREVOUTANYSCRIPT
and add the following behavior:
- in some place that is covered by the signature (taproot annex?), commit to:
- the list of outputs for which the amount should be ignored
- when hashing the tx:
- ignore the input
txid
andvout
(this is provided bySIGHASH_ANYPREVOUT
) - ignore the input amount
- set output amount to 0 (or some placeholder) for the committed outputs
- ignore the input
If we had something like this, here is how we would design off-chain contracts:
- set the fee to 0 in all pre-signed shared transactions
- set a CSV on every output
- when sending our signature to our peer, use
SIGHASH_ANYPREVOUT | SIGHASH_ANYAMOUNT
to exclude their balance output from the signed data
If we do that, our peer is able to lower their balance output to set the feerate without invalidating our signature. Because of the CSV on every output, there is no way for an attacker to do any kind of pinning and transactions are very easy to RBF. We don't have to handle the complexity of anchor outputs, and don't have to maintain an external utxo pool for fee-bumping (which really is a huge win).
Let's detail how that would work today for a lightning channel between Alice and Bob. The scripts would be completely unchanged (as detailed in https://github.com/t-bast/lightning-docs/blob/master/lightning-txs.md#anchor-outputs) but we remove the anchor outputs.
The commitment transaction would contain the following outputs:
- Alice's main output
- Bob's main output
- Incoming HTLC outputs
- Outgoing HTLC outputs
When Alice signs Bob's commitment transaction, she uses SIGHASH_ANYPREVOUT | SIGHASH_ANYAMOUNT
to exclude Bob's main output.
When Bob signs Alice's commitment transaction, he uses SIGHASH_ANYPREVOUT | SIGHASH_ANYAMOUNT
to exclude Alice's main output.
The commitment transactions are then very easy to RBF, each participant can simply lower their main output to pay the on-chain fees (they always have such an output because of the channel reserve).
Since the pre-signed transaction pays no fees, participants can only decrease their main output, they can't inflate it to siphon funds from their peer.
The HTLC transactions would contain a single output (as is done today) and we wouldn't use SIGHASH_SINGLE | SIGHASH_ANYONECANPAY
.
Instead, we would just sign them with SIGHASH_ANYPREVOUT | SIGHASH_ANYAMOUNT
applied to the only output.
The SIGHASH_ANYPREVOUT
part ensure that the signature remains valid for a tweaked commitment transaction that contains a lower main output to pay for fees.
The SIGHASH_ANYAMOUNT
part ensures that the HTLC recipient can lower the output value to pay for fees (up to the full HTLC amount for scorched earth strategies against malicious peers).
There are a few subtleties to explore:
- excluding more than one output's amount from the signed data allows the remote peer to shift funds from one output to another: is this a bug or a feature?
- this should probably never be used in conjunction with
SIGHASH_SINGLE
orSIGHASH_ANYONECANPAY
otherwise peers may move funds outside of restrictive output scripts, which is likely a design bug
A few initial observations:
SIGHASH_ANYAMOUNT
has some overlap with what I've seen described asSIGHASH_GROUP
[1][2]. The significant difference is thatSIGHASH_ANYAMOUNT
ignores the amount, but keeps the script_pubkey so that we can ensure the spending tx requires CSV. I like this addition and perhaps the annex can also include a bit field for each excluded output that describes what parts are excluded.SIGHASH_ANYONECANPAY
is currently implicit for any use of APO or APO-AS. Exogenous (bring-your-own-fees) is required forupdate_tx
because otherwise it could uses endogenous funds from one party that in a laterupdate_tx
belongs to the other.Perhaps instead of full
SIGHASH_ANYONECANPAY
behavior, APO-AS should commit to the scripts of all inputs except the one being signed for. This would allow theupdate_tx
to have an additional APO input that is used to pay fees at commit time. Because the script of the second input is committed to in advance, we know the witness size of the fee input is bounded (so no RBF rule #3 issues). APO here also means we don't have to pre-commit to a particular tx that will pay our fees; we can create a UTXO with a matching amount and script when needed.[1] SIGHASH_GROUP discussion: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-July/019243.html
[2] SIGHASH_GROUP BIP draft: https://github.com/ariard/bitcoin/blob/2021-10-annex-sighashgroup/bip-sighashgroup.mediawiki