From 38a131956520bcc38aec925dac9643082ca4f452 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Tue, 5 Sep 2023 19:01:20 -0700 Subject: [PATCH 01/23] first draft of dynamic commitment preamble, detailing the problem scope --- ext-dynamic-commitments.md | 954 +++++++++++++++++++++++++++++++++++++ 1 file changed, 954 insertions(+) create mode 100644 ext-dynamic-commitments.md diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md new file mode 100644 index 000000000..4e0ff2537 --- /dev/null +++ b/ext-dynamic-commitments.md @@ -0,0 +1,954 @@ +# Extension Bolt ZZZ: Dynamic Commitments + +Authors: + * Keagan McClelland + * Olaoluwa Osuntokun + * Eugene Siegel + +Created: TODO + +# Table of Contents + +TODO + +# Introduction +## Abstract +This document describes a protocol for changing channel parameters that were +negotiated at the conception of the channel. Implementation of the protocol +described in this document will enable channel peers to re-negotiate channel +terms as if the channel was being opened for the first time while avoiding UTXO +churn whenever possible and therefore preserving the continuity of identity for +the channel whose terms are being changed. + +## Motivation +It is well understood that closing channels is a costly thing to do. Not only is +it costly from a chain fees perspective where we pay for moving the funds from +the channel UTXO back to the main wallet collection, it is also costly from a +service availability and reputation perspective. + +After channels are closed they are no longer usable for forwarding HTLC traffic +and even if we were to immediately replace the channel with another equally +capable one, the closure event is visible to the entire network. Since routes +are computed by the source, the network-wide visibility of channel closures +directly impacts whether or not the sender will be able to use a channel. + +Beyond that, one of the pathfinding heuristics that is broadly used to assess +channel reliability is the length of time a channel has existed. The longevity +of a channel is therefore a key asset that any running Lightning node should +want to preserve, if possible. + +It follows from the above that we should try to minimize channel closure events +when we can manage to do so. This motivates part of this proposal. Prior to this +extension BOLT, there is no way to change some of the channel parameters +established in the `{open|accept}_channel` messages without resorting to a full +channel closure. This limitation can be remediated by introducing a protocol to +renegotiate these parameters. + +Notable in particular is that one of the channel parameters that we wish to +renegotiate is the the `channel_type` itself. With the advent of Simple Taproot +Channels (STCs), we have the opportunity to take advantage of the cost savings +and privacy capabilities afforded by the 2021 Taproot Soft Fork. With further +aspirations to be able to deploy Point Time-Lock Contracts (PTLCs) to the +Lightning Network, the sooner that network participants can upgrade to STCs the +more we will have the necessary network infrastructure to be able to make +effective PTLCs when the protocols for them are specified. + +Due to the design of STCs and the fact that they are designed to make full use +of the capabilities afforded by Schnorr Signatures, there is no way to construct +a valid `channel_announcement` message that references the output corresponding +to the nodes' joint public key. As such, even if we were to directly spend an +existing channel point to a new STC channel point, and even with the provision +in BOLT 7 to delay graph pruning by 12 blocks after the channel point is spent, +we have no way to make the STC known to the network at the time of writing of +this proposal. + +Concurrently with this proposal is a proposal for a new gossip system that is +capable of understanding announcements of the new STCs. However, in an effort to +allow nodes to upgrade to STCs without impacting the rest of the network, the +rest of the network would already need to be capable of understanding messages +from this new gossip system, the pervasive deployment of which remains far in +the future. To remove this disincentive of these channel upgrades to the +involved parties, this proposal to enable the change of these channel parameters +(including channel types) without requiring channel closure and reopening is +submitted. + +## Preliminaries +This proposal includes a detailed section on the preliminaries to document to +reviewers and posterity some of the rationale for the design that is presented +later. If you are a Bitcoin and Lightning Network protocol expert or you are +uninterested in the thought process behind what is presented here, you may wish +to skip to the Design Overview section to save time + +### Channel Opening Parameters +As described in BOLT 2, during the channel opening procedure there are a number +of parameters specified in the `open_channel` and `accept_channel` messages that +remain static over the lifetime of the channel. An exhaustive list of these is +provided below: + +TODO: clean up this table +| | open|accept|open2|accept2|dyncomm| +|-| -------------- | ---------------- | --------------- | ---------------- |-| +|chain_hash|x||x||unnecessary| +|temporary_channel_id|x|x|x|x|real_channel_id| +|funding_feerate_per_kw|||x||kickoff_feerate| +|commitment_feerate_perkw|x||x||x| +|funding_satoshis|x||x|x|x| +|push_msat|x||||?| +|dust_limit_satoshis|x|x|x|x|x| +|max_htlc_value_in_flight_msat|x|x|x|x|x| +|channel_reserve_satoshis|x|x|||x| +|htlc_minimum_msat|x|x|x|x|x| +|minimum_depth||x||x|0| +|to_self_delay|x|x|x|x|x| +|max_accepted_htlcs|x|x|x|x|x| +|funding_pubkey|x|x|x|x|x| +|revocation_basepoint|x|x|x|x|?| +|payment_basepoint|x|x|x|x|?| +|delayed_payment_basepoint|x|x|x|x|?| +|htlc_basepoint|x|x|x|x|?| +|first_per_commitment_point|x|x|x|x|?| +|channel_flags|x||x||| +|upfront_shutdown|x|x|x|x|| +|channel_type|x|x|x|x|x| + +A subset of these are updatable using other messages defined in the protocol. +However, after accounting for the channel parameters that can be changed there +is a list of parameters which remain unchangeable which we list below. + +- dust_limit_satoshis +- max_htlc_value_in_flight_msat +- channel_reserve_satoshis +- htlc_minimum_msat +- to_self_delay +- max_accepted_htlcs +- funding_pubkey +- revocation_basepoint +- payment_basepoint +- delayed_payment_basepoint +- htlc_basepoint +- first_per_commitment_point +- channel_flags +- upfront_shutdown +- channel_type + +After some analysis during the development of this proposal it is determined +that many of these values don't make sense to rotate. There is no value and +additional administrative costs to rotating the basepoints of a channel. +Additionally, changing the upfront_shutdown script over the lifetime of the +channel is self-defeating and so we exclude it as well. The list of channel +parameters remaining after we filter out these values is thus. + +- dust_limit_satoshis +- max_htlc_value_in_flight_msat +- channel_reserve_satoshis +- htlc_minimum_msat +- to_self_delay +- max_accepted_htlcs +- funding_pubkey +- channel_flags +- channel_type + +The design presented later is intended to allow for arbitrary changes to these +values that currently have no facilities for change in any other way. + +### Gossip Verification +It is at this point that we need to take a brief detour and review how the +broader Lightning Network comes to discover and verify the existence of public +channels. When the funding transaction for a channel has confirmed, the +participating parties will jointly produce a message that attests to their +ownership of the UTXO and its viability as a routing edge for payment senders. + +BOLT 7 details all of the specifics of this message and how it is computed but +one of the notable aspects of this process is that the receivers of these gossip +messages verify that the UTXO being referenced for underwriting the existence of +a channel must be a P2WSH output with a pre-defined script using the +participants' public keys, specified in BOLT 3. This will present issues for us +which will become clearer in the next section. + +While alternative Gossip systems are being designed, they have not been deployed +in any known implementation of the Lightning Network Protocol and even if they +were there will be a prolonged period of time wherein some nodes on the network +will remain unable to process messages of this variety, rendering useless any +channels that are confined to being announced in this manner. + +### Taproot +This brings us to talking about what channel constructions are actually +inexpressible by the existing gossip system. As we alluded to earlier, Taproot +channels cannot be discovered using the existing gossip message structure and +interpretation. + +In November of 2021 the "Taproot" upgrade was activated on Bitcoin's mainnet, +creating a new output type that is subsequently useful to higher layer protocols +such as the Lightning Network. Since then, the Lightning Network protocol +designers have offered a proposal for a channel construction that makes use of +the Taproot output type. It is beyond the scope of this document to make a +thorough case for why such a channel construction is useful but we assume that +it is for our purposes here. + +While Taproot channels are useful, they present some novel challenges with +respect to network-wide interoperability. Notably, a useful Taproot channel +construction must by definition make use of the new Taproot output type, which +is not and cannot be a P2WSH output of the format detailed in BOLT 3 for the +funding output. Pairing this fact with what we described in the previous +section, it is necessarily the case that the funding output of a Taproot channel +cannot be properly announced by the current gossip system. + +With this background out of the way we are finally fully primed to understand +the nuances of the design. + +## Design Overview +The main goal of this proposal is to be able to change all of the historically +"static" channel parameters, including the channel type, which includes channels +built off of output types that our gossip system currently doesn't understand, +in a world where we are trying to preserve the channel identity of the original +channel. This is a tall order. + +Most of these parameters can be changed by simply expressing the desire to +change them, and should the recipient agree, we apply these changes, and +exchange new commitment transactions making any necessary adjustments implied +by the channel parameter changes. + +The exception to this is certain changes to the channel type. As detailed in the +preliminaries, the funding output of a Taproot transaction is fundamentally +different from the funding output of the other channel types that are currently +defined. This means that we conceptually must spend the funding output of the +original channel into a new Taproot output before we have a functioning Taproot +channel. + +The key insight in this design is that we extend the conception of a commitment +transaction to include the possibility of a pair of transactions wherein we have +a "kickoff transaction" that is comprised of a single input (the original +funding output) and a single output (the new funding output) and then building +the new commitment transaction off of the new funding output in whatever manner +is detailed in the specification for the target channel type. This may not +always be necessary, but it is certainly necessary for using this proposal to +convert existing channels into Taproot channels. + +# Specification + +## Feature Bits (BOLT 9 Amendments) + +## Peer Protocol (BOLT 2 Amendments) + +## Transactions (BOLT 3 Amendments) + +## On-Chain Handling (BOLT 5 Amendments) + +# *EUGENE'S ORIGINAL DRAFT FOLLOWS* + +# Specification + +## Proposal Messages + +Three new messages are introduced that are common to all dynamic commitment flows. +They let each side propose what they want to change about the channel. + +### `dyn_begin_propose` + +This message is sent when a node wants to begin the dynamic commitment negotiation +process. This is a signaling message, similar to `shutdown` in the cooperative close +flow. + +1. type: 111 (`dyn_begin_propose`) +2. data: + * [`32*byte`:`channel_id`] + * [`byte`: `begin_propose_flags`] + +Only the least-significant-bit of `begin_propose_flags` is defined, the `reject` bit. + +#### Requirements + +The sending node: + - MUST set `channel_id` to a valid channel they have with the + recipient. + - MUST set undefined bits in `begin_propose_flags` to 0. + - MUST set the `reject` bit in `begin_propose_flags` if they are + rejecting the + dynamic commitment negotiation request. + - MUST NOT send `update_add_htlc` messages after sending this unless + one of the following is true: + - dynamic commitment negotiation has finished + - a `dyn_begin_propose` with the `reject` bit has been received. + - a reconnection has occurred. + - MUST only send one `dyn_begin_propose` during a single negotiation. + - MUST fail to forward additional incoming HTLCs from the peer. + +The receiving node: + - if `channel_id` does not match an existing channel it has with the + peer: + - MUST close the connection. + - if the `reject` bit is set, but it hasn't sent a `dyn_begin_propose`: + - MUST send an `error` and fail the channel. + - if an `update_add_htlc` is received after this point and negotiation hasn't + finished or terminated: + - MUST send an `error` and fail the channel. + - if it has not already sent `dyn_begin_propose` in this round of negotiation: + - MUST reply with `dyn_begin_propose` either rejecting or accepting the negotiation + request. + +#### Rationale + +This has similar semantics to the `shutdown` message where the channel comes to a state +where updates may only be removed. The `reject` bit is necessary to avoid having to +reconnect in order to have a useable channel state again. + +### `dyn_propose` + +This message is sent when neither side owes the other either a `revoke_and_ack` or +`commitment_signed` message and each side's commitment has no HTLCs. For now, only the +`recipients_new_self_delay` parameter is defined in negotiation. After negotiation completes, +commitment signatures will use these parameters. The overall message flow looks like this: + + +-------+ +-------+ + | |--(1)---dyn_begin_propose ---->| | + | | | | + | |<-(2)---dyn_begin_propose------| | + | | | | + | |--(3)----- dyn_propose ------->| | + | A | | B | + | |<-(4)----- dyn_propose --------| | + | | | | + | |--(5)--- dyn_propose_reply --->| | + | | | | + | |<-(6)--- dyn_propose_reply ----| | + +-------+ +-------+ + +1. type: 113 (`dyn_propose`) +2. data: + * [`32*byte`:`channel_id`] + * [`dyn_propose_tlvs`:`tlvs`] + +1. `tlv_stream`: `dyn_propose_tlvs` +2. types: + 1. type: 0 (`to_self_delay`) + 2. data: + * [`u16`:`recipients_new_self_delay`] + +#### Requirements + +The sending node: + - MUST set `channel_id` to an existing one it has with the recipient. + - MUST NOT send a `dyn_propose` if a prior one is waiting for `dyn_propose_reply`. + - MUST remember its last sent `dyn_propose` parameters. + - MUST send this message as soon as both side's commitment transaction is free of + any HTLCs and both sides have sent `dyn_begin_propose`. + +The receiving node: + - if `channel_id` does not match an existing channel it has with the sender: + - MUST send an `error` and close the connection. + - if it does not agree with a parameter: + - MUST send a `dyn_propose_reply` with the `reject` bit set. + - else: + - MUST send a `dyn_propose_reply` without the `reject` bit set. + +#### Rationale + +The requirement to not allow trimming outputs is just to make the dynamic commitment +flow as uninvasive as possible to the commitment transaction. A similar requirement +should be added for any new parameter such as the `channel_reserve`. + +The requirement for a node to remember what it last _sent_ and for it to remember +what it _accepted_ is necessary to recover on reestablish. See the reestablish section +for more details. + +### `dyn_propose_reply` + +This message is sent in response to a `dyn_propose`. It may either accept or reject +the `dyn_propose`. If it rejects a `dyn_propose`, it allows the counterparty to send +another `dyn_propose` to try again. If for some reason, negotiation is taking too long, +it is possible to exit this phase by reconnecting as long as the exiting node hasn't +sent `dyn_propose_reply` without the `reject` bit. + +1. type: 115 (`dyn_propose_reply`) +2. data: + * [`32*byte`:`channel_id`] + * [`byte`: `propose_reply_flags`] + +The least-significant bit of `propose_reply_flags` is defined as the `reject` bit. + +#### Requirements + +The sending node: + - MUST set `channel_id` to a valid channel they have with the recipient. + - MUST set undefined bits in `propose_reply_flags` to 0. + - MUST set the `reject` bit in `propose_reply_flags` if they are rejecting the newest + `dyn_propose`. + - MUST NOT send this message if there is no outstanding `dyn_propose` from the + counterparty. + - if the `reject` bit is not set: + - MUST remember the related `dyn_propose` parameters and the local and remote commitment + heights for the next `propose_height`. + +The receiving node: + - if `channel_id` does not match an existing channel it has with the peer: + - MUST close the connection. + - if there isn't an outstanding `dyn_propose` it has sent: + - MUST send an `error` and fail the channel. + - if the `reject` bit was set: + - MUST forget its last sent `dyn_propose` parameters. + +A node: + - once it has both sent and received `dyn_propose_reply` without the `reject` bit set: + - MUST increment their `propose_height`. + +#### Rationale + +The `propose_height` starts at 0 for a channel and is incremented by 1 every time the +dynamic commitment proposal phase completes for a channel. See the reestablish section +for why this is needed. + +## Reestablish + +### `channel_reestablish` + +A new TLV that denotes the node's current `propose_height` is included. + +1. `tlv_stream`: `channel_reestablish_tlvs` +2. types: + 1. type: 20 (`propose_height`) + 2. data: + * [`u64`:`propose_height`] + +#### Requirements + +The sending node: + - MUST set `propose_height` to the number of dynamic proposal negotiations it has + completed. The point at which it is incremented is described in the `dyn_propose_reply` + section. + +The receiving node: + - if the received `propose_height` equals its own `propose_height`: + - MUST forget any stored proposal state for `propose_height`+1 in case negotiation didn't + complete. Can continue using the channel. + - SHOULD forget any state that is unnecessary for heights <= `propose_height`. + - if the received `propose_height` is 1 greater than its own `propose_height`: + - if it does not have any remote parameters stored for the received `propose_height`: + - MUST send an `error` and fail the channel. The remote node is either lying about the + `propose_height` or the recipient has lost data since its not possible to advance the + height without the recipient storing the remote's parameters. + - resume using the channel with its last-sent `dyn_propose` and the stored `dyn_propose` + parameters and increment its `propose_height`. + - if the received `propose_height` is 1 less than its own `propose_height`: + - resume using the channel with the new parameters. + - else: + - MUST send an `error` and fail the channel. State was lost. + +#### Rationale + +If both sides have sent and received `dyn_propose_reply` without the `reject` bit before the +connection closed, it is simple to continue. If one side has sent and received +`dyn_propose_reply` without the `reject` bit and the other side has only sent `dyn_propose_reply`, +the flow is recoverable on reconnection as the side that hasn't received `dyn_propose_reply` knows +that the other side accepted their last sent `dyn_propose` based on the `propose_height` in the +reestablish message. + +## Musig2 Taproot + +This section describes how dynamic commitments can upgrade regular channels to simple +taproot channels. The regular dynamic proposal phase is executed followed by a signing phase. +A channel-type of `option_taproot` will be included in `dyn_propose` and both sides must agree +on it. The funder of the channel will also propose a set of feerates to use for an intermediate +"kickoff" transaction. + +### Extensions to `dyn_propose`: + +1. `tlv_stream`: `dyn_propose_tlvs` +2. types: + 1. type: 2 (`channel_type`) + 2. data: + * [`...*byte`:`type`] + 1. type: 4 (`kickoff_feerates`) + 2. data: + * [`...u32`:`kickoff_feerate_per_kw`] + 1. type: 6 (`taproot_funding_key`) + 2. data: + * [`point`:`funding_key`] + +#### Requirements + +The sending node: + - if it is the funder: + - MUST only send `kickoff_feerate` if they can pay for each kickoff transaction + fee and the anchor outputs, while adhering to the `channel_reserve` restriction. + - MUST set `taproot_funding_key` to a valid secp256k1 compressed public key. + - SHOULD use a sufficient number of `kickoff_feerates` to be prepared for worst-case + fee environment scenarios. + +The receiving node: + - if it is the fundee: + - MUST reject the `dyn_propose` if the funder cannot pay for each kickoff transaction + fee and the anchor outputs. + - MUST reject the `dyn_propose` if, after calculating the amount of the new funding output, + the new commmitment transaction would not be able to pay for any outputs at the current + commitment feerate. + - MUST reject the `dyn_propose` if `taproot_funding_key` is not a valid secp256k1 compressed + public key. + - MAY reject the `dyn_propose` if it does not agree with the `channel_type` + - MAY reject the `dyn_propose` if there are too many `kickoff_feerates` such that it would + be a burden to track the potential confirmation of each kickoff and commitment transaction + pair. + +#### Rationale + +The `dyn_propose` renegotiates the funding keys as otherwise signatures for the funding keys +would be exchanged in both the ECDSA and Schnorr contexts. This can lead to an attack outlined +in [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#alternative-signing). +Renegotiating funding keys avoids this issue. Note that the various basepoints exchanged in +`open_channel` and `accept_channel` are not renegotiated. Because the private keys _change_ with +each commitment transaction they sign due to the `per_commitment_point` construction, the +basepoints can be used in both ECDSA and Schnorr contexts. + +The funder sends multiple fee-rates in order to be deal with high-fee environments. Without this, +the channel may not be able to upgrade commitment types until the fee environment changes. + +### Extensions to `dyn_propose_reply`: + +1. `tlv_stream`: `dyn_propose_reply_tlvs` +2. types: + 1. type: 0 (`local_musig2_pubnonce`) + 2. data: + * [`66*byte`:`nonces`] + +#### Requirements + +The sending node: + - if it is accepting a `channel_type` of `simple_taproot_channel`: + - MUST set `local_musig2_pubnonce` to the nonce that it will use to verify local + commitments. + +The receiving node: + - MUST send an `error` and fail the channel if `local_musig2_pubnonce` cannot be parsed + as two compressed secp256k1 points. + +### Signing Phase + +The signing phase is after the negotiation phase. The original funding output spends to an +intermediate transaction that pays to a v1 witness script with an aggregated musig2 key derived from +both parties `taproot_funding_key` sent in `dyn_propose`. As in the simple-taproot-channels proposal, +the `commitment_signed`, `revoke_and_ack`, and `channel_reestablish` messages include nonces. + +#### Commitment Transaction + +* version: 2 +* locktime: upper 8 bits are 0x20, lower 24 bits are the lower 24 bits of the obscured commitment number +* txin count: 1 + * `txin[0]` outpoint: the matching kickoff transaction's musig2 funding outpoint. + * `txin[0]` sequence: upper 8 bits are 0x80, lower 24 bits are upper 24 bits of the obscured commitment number + * `txin[0]` script bytes: 0 + * `txin[0]` witness: `` + +The 48-bit commitment number is computed by `XOR` as described in BOLT#03. + +#### Commitment Transaction Construction + +1. Initialize the commitment transaction version and locktime. +2. Initialize the commitment transaction input. +3. Calculate which committed HTLCs need to be trimmed. +4. Calculate the commitment transaction fee via + commitment feerate * `commitment_transaction_weight`/1000, making sure to round down. Subtract + this from the funder's output. +5. Subtract four times the fixed anchor size of 330 satoshis from the funder's output. Two of + the anchors are from the commitment transaction and two are from the kickoff transaction. +6. Subtract the matching kickoff transaction's fee from the funder's output. +7. For every offered HTLC, if it is not trimmed, add an offered HTLC output. +8. For every received HTLC, if it is not trimmed, add a received HTLC output. +9. If the `to_local` output is greater or equal to the dust limit, add a `to_local` output. +10. If the `to_remote` output is greater or equal to the dust limit, add a `to_remote` output. +11. If `to_local` exists or there are untrimmed HTLCs, add a `to_local_anchor`. +12. If `to_remote` exists or there are untrimmed HTLCs, add a `to_remote_anchor`. The + `to_remote_anchor` uses the remote party's `taproot_funding_key`. +13. Sort the outputs into BIP 69+CLTV order. + +#### commitment_signed + +The `commitment_signed` message does not change, but adds a nonce in the TLV section per the +simple-taproot-channels proposal. It changes what it signs in the following ways: + +1. `tlv_stream`: `commit_sig_tlvs` +2. types: + 1. type: 2 (`partial_signature_with_nonce`) + 2. data: + * [`98*byte`:`partial_signature || public_nonce`] + 1. type: 4 (`local_musig2_pubnonce`) + 2. data: + * [`66*byte`: `nonces`] + +##### Requirements + +The sending node: + - MUST NOT increment the commitment number when signing. + - MUST sign for any negotiated parameters that modified the commitment transaction + (e.g. `to_self_delay`). + +The receiving node: + - MUST send an `error` and fail the channel if the signature does not sign the commitment + transaction as constructed above. + - MUST send an `error` and fail the channel if `partial_signature` is not a valid Schnorr + signature. + - MUST send an `error` and fail the channel if `public_nonce` cannot be parsed as two + compressed secp256k1 points. + - MUST send an `error` and fail the chanel if `local_musig2_pubnonce` cannot be parsed as + two compressed secp256k1 points. + +##### Rationale + +The commitment number is not incremented while signing because if there are N kickoff +transactions and the N-2 kickoff transaction confirms, then implementations will need to +rewind their commitment number to N-2. We avoid this complexity by keeping the commitment +numbers static until the signing phase is complete. + +A set of local nonces is included because each signed commitment transaction shares the +same commitment number as the pre-dynamic-commitment commitment transaction. For this +reason, `revoke_and_ack` is omitted and thus local nonces need to be sent in +`commitment_signed`. + +#### Kickoff Transaction(s) + +* version: 2 +* locktime: 0 +* txin count: 1 + * `txin[0]` outpoint: `txid` and `output_index` from `funding_created` message + * `txin[0]` sequence: 0xfffffffd + * `txin[0]` script bytes: 0 + * `txin[0]` witness: `0 ` +* txout count: 3 + * `txout[0]`: `anchor_output_1` or `anchor_output_2` + * `txout[1]`: `anchor_output_1` or `anchor_output_2` + * `txout[2]`: `p2tr_funding_output` + +The anchor outputs have a value of 330 satoshis. They are encumbered by a version 1 witness +script: +* `OP_1 anchor_output_key` +* where: + * `anchor_internal_key = original_local_funding_pubkey/original_remote_funding_pubkey` + * `anchor_output_key = anchor_internal_key + tagged_hash("TapTweak", anchor_internal_key || anchor_script_root)` + * `anchor_script_root = tapscript_root([anchor_script])` + * `anchor_script`: + ``` + OP_16 OP_CHECKSEQUENCEVERIFY + ``` + +The new funding output has a value of the original funding output minus the sum of 660 satoshis +and this kickoff transaction's fee. It is encumbered by a version 1 witness script where +`taproot_funding_key1/taproot_funding_key2` are from `dyn_propose_reply`: +* `OP_1 funding_key` +* where: + * `funding_key = combined_funding_key + tagged_hash("TapTweak", combined_funding_key)*G` + * `combined_funding_key = musig2.KeyAgg(musig2.KeySort(taproot_funding_key1, taproot_funding_key2))` + +#### Kickoff Transaction Construction + +1. Initialize the commitment transaction version and locktime. +2. Initialize the commitment transaction input. +3. Calculate this kickoff transaction's fee via `kickoff_feerate_per_kw`*`kickoff_transaction_weight`/1000, + making sure to round down. Subtract this value from the new funding output. +5. Subtract two times the fixed anchor size of 330 satoshis from the new funding output. +6. Add a funding output with the new funding amount. +7. Add an anchor output for each party. +8. Sort the outputs into BIP 69+CLTV order. + +#### kickoff_sig + +The kickoff_sig is a message containing a signature that the fundee sends to the funder who +then combines it with their own signature to spend from the original funding outpoint into the new +musig2 output. To keep things simple, no additional inputs are added to the intermediate +transaction. An anchor output is attached to either side for fee-bumping. + +![Cannot display image](./dynamic-commits/kickoff%20tx.png "Kickoff transaction") + +1. type: 777 (`kickoff_sig`) +2. data: + * [`32*byte`:`channel_id`] + * [`signature`:`signature`] + +##### Requirements + +The sending node (the fundee): + - MUST set `channel_id` to a valid channel they have with the recipient. + - MUST NOT send this message before receiving the peer's `commitment_signed`. + +The receiving node (the funder): + - MUST send an `error` and fail the channel if `channel_id` does not match an existing + channel it has with the sender. + - MUST send an `error` and fail the channel if `signature` is not valid for the kickoff + transaction as constructed above OR non-compliant with the LOW-S-standard rule. [LOWS](https://github.com/bitcoin/bitcoin/pull/6769) + +##### Rationale + +To avoid the fundee griefing the funder by broadcasting the highest-fee kickoff transaction, +only the fundee sends `kickoff_sig`. This ensures that only the funder can broadcast the kickoff +transaction. + +Even though only the funder is able to broadcast the kickoff transaction, we include anchors +such that the fundee can broadcast fee-bumping transactions if they notice any of the kickoff +transactions in the mempool. + +#### Message flow to upgrade a channel to simple-taproot: + + +-------+ +-------+ + | |--(1)---- commit_signed------->| | + | | | | + | A |<-(2)---- commit_signed -------| B | + | |<-(3)----- kickoff_sig --------| | + | | | | + | |--(4)---- commit_signed------->| | + | |<-(5)---- commit_signed -------| | + | |<-(6)----- kickoff_sig --------| | + +-------+ +-------+ + +The above message ordering is important. If `kickoff_sig` is sent before `commit_sig`, a +griefing attack is possible: + + +-------+ +-------+ + | A |<-(1)----- kickoff_sig --------| B | + +-------+ +-------+ + +Here, A stops sending messages and instead immediately broadcasts the kickoff transaction. +Since neither side has exchanged `commitment_signed`, the new funding output is unclaimable and +is effectively burned. The majority of the channel could be in B's outputs, making the loss +of funds disproportionately on B's side. + +### Reestablish during simple-taproot upgrade + +#### channel_reestablish + +The `channel_reestablish` message does not change, but adds a nonce in the TLV section per the +simple-taproot-channels proposal. + +1. `tlv_stream`: `channel_reestablish_tlvs` +2. types: + 1. type: 4 (`next_local_nonce`) + 2. data: + * [`66*byte`:`public_nonce`] + 1. type: 6 (`num_sent_commit_sigs`) + 2. data: + * [`u16`:`num_sigs`] + 1. type: 8 (`num_recv_commit_sigs`) + 2. data: + * [`u16`:`num_sigs`] + 1. type: 10 (`num_kickoff_sigs`) + 2. data: + * [`u16`:`num_sigs`] + +The sending node: + - MUST set `next_local_nonce` if the sender sees it has persisted a `channel_type` of + `option_simple_taproot` from the `dyn_propose` / `dyn_propose_reply` negotiation steps. + - MUST set `num_sent_commit_sigs` to the number of `commitment_signed` it has sent for this + negotiation session. + - MUST set `num_recv_commit_sigs` to the number of `commitment_signed` it has received for + this negotiation session. + - if it is the funder: + - MUST set `num_kickoff_sigs` to the number of `kickoff_sig` messages it has received. + - otherwise (it is the fundee): + - MUST set `num_kickoff_sigs` to the number of `kickoff_sig` messages it has sent. + +The receiving node: + - MUST send an `error` and fail the channel if `next_local_nonce` cannot be parsed as + two compressed secp256k1 points. + - if its sent `num_sent_commit_sigs` is one greater than the received `num_recv_commit_sigs`: + - MUST retransmit the missing `commitment_signed`. + - if it is the fundee: + - if its sent `num_kickoff_sigs` is one greater than the received `num_kickoff_sigs`: + - MUST retransmit the missing `kickoff_sig`. + - if messages were retransmitted: + - MUST continue with the rest of the signing flow until a `kickoff_sig` has been sent for + each fee-rate in `kickoff_feerates`. + +The signing phase is complete when the funder's sent `num_kickoff_sigs` is equal to the +fundee's sent `num_kickoff_sigs` and is also equal to the number of fee-rates in +`kickoff_feerates` from the persisted `dyn_propose` parameters. + +# Appendix + +## Pinning + +![Cannot display image](./dynamic-commits/kickoff%20pinning.png "Kickoff transaction pinned") + +Originally, Bitcoin Core's default mempool settings allowed an unconfirmed transaction to have up +to 25 decendants in the mempool. Past this limit, any descendants would be rejected. This was used +as a DoS mitigation in Bitcoin Core and affected the security of LN channels. Before the anchors +commitment type was introduced, pinning in the LN was where a counterparty broadcasted the +commitment transaction and created a chain of 25 descendants spending from one of the commitment's +outputs. The time-sensitive commitment transaction could be "pinned" to the bottom of the mempool. +This was addressed with a change to Bitcoin Core called CPFP Carve-out. + +### CPFP Carve-out + +CPFP Carve-out was introduced to Bitcoin Core in https://github.com/bitcoin/bitcoin/pull/15681. If +a Bitcoin node receives a transaction that is rejected due to any of the mempool size or ancestor/ +descendant restrictions being hit, it will try to accept the transaction again. This second try +will succeed only if: + - the transaction is 40kWU or less + - it has only one ancestor in the mempool + +This change, in conjunction with the anchor commitment type, decreases the efficacy of the +pinning attack since the honest party can still attach an anchor despite the descendant size +limit being hit. + +### Dynamic Commitments & CPFP Carve-out + +The safety guarantees of CPFP Carve-out break due to the structure of the kickoff transaction. +The kickoff transaction contains 3 spendable outputs: the local party's anchor, the remote +party's anchor, and the new funding output. All three of these outputs can be spent immediately. +A malicious counterparty can pin the kickoff transaction by: + - spending from their anchor output to create a descendant chain of 25 transactions + - then spending from the new funding output, "using up" the CPFP Carve-out slot designated + for the honest party. + +Depending on fee conditions, it may not be possible for the honest party to get these +transactions confirmed until the mempool clears up. + +If we were to get rid of the kickoff transaction's anchor outputs, the problem still arises. +A malicious counterparty could still pin the kickoff transaction by: + - broadcasting the commitment transaction + - spending from their anchor output and creating a descendant chain of 25 transactions + +The honest party is unable to use their anchor on the commitment transaction as: + - the descendant limit of 25 transactions has been hit + - the anchor spend would have 2 ancestors (the commitment and kickoff transactions) + +### Reducing Risk + +The above pinning scenarios highlight the complexity of second-layer protocols and mempool +restrictions. In this proposal, pinning is _still_ possible, but risk is mitigated because: + - the kickoff transaction MUST confirm before HTLCs can be added to the commitment + transaction + - no HTLCs exist on the commitment transaction while the kickoff transaction is unconfirmed + +If we allowed adding HTLCs _before_ the kickoff transaction confirmed on-chain, the pinning +attack would now have a tangible benefit: the ability to steal the value of an HTLC. The second +requirement above is very similar to the first requirement: by disallowing HTLCs when +`dyn_propose` is sent, we ensure that the counterparty has no incentive to pin the kickoff +transaction. + +## Weights + +Since DER-encoded signatures vary in size, we assume a worst-case signature size of 73 bytes to +keep things simple. The kickoff transaction has an _expected weight_ of 944WU and the +commitment transaction has an _expected weight_ of 960WU. + +General weights: + * p2tr: 34 bytes + - OP_1: 1 byte + - OP_DATA: 1 byte (witness_script_SHA256 length) + - witness_script_SHA256: 32 bytes + + * witness_header: 2 bytes + - flag: 1 byte + - marker: 1 byte + +### Kickoff Transaction Weights + * funding_output_script: 71 bytes + - OP_2: 1 byte + - OP_DATA: 1 byte (pub_key_alice length) + - pub_key_alice: 33 bytes + - OP_DATA: 1 byte (pub_key_bob length) + - pub_key_bob: 33 bytes + - OP_2: 1 byte + - OP_CHECKMULTISIG: 1 byte + + * funding_input_witness: 222 bytes + - number_of_witness_elements: 1 byte + - nil_length: 1 byte + - sig_alice_length: 1 byte + - sig_alice: 73 bytes + - sig_bob_length: 1 byte + - sig_bob: 73 bytes + - witness_script_length: 1 byte + - witness_script: 71 bytes (funding_output_script) + + * kickoff_txin_0: 41 bytes (excl. witness) + - previous_out_point: 36 bytes + - hash: 32 bytes + - index: 4 bytes + - var_int: 1 byte (script_sig length) + - script_sig: 0 bytes + - witness: <---- part of the witness data + - sequence: 4 bytes + + * musig2_funding_output: 43 bytes + - value: 8 bytes + - var_int: 1 byte (pk_script length) + - pk_script (p2tr): 34 bytes + + * anchor_output: 43 bytes + - value: 8 bytes + - var_int: 1 byte (pk_script length) + - pk_script (p2tr): 34 bytes + + * kickoff_transaction: 180 bytes (excl. witness) + - version: 4 bytes + - witness_header: <---- part of the witness data + - count_tx_in: 1 byte + - tx_in: 41 bytes + - kickoff_txin_0: 41 bytes + - count_tx_out: 1 byte + - tx_out: 129 bytes + - musig2_funding_output: 43 bytes + - anchor_output_local: 43 bytes + - anchor_output_remote: 43 bytes + - lock_time: 4 bytes + + - Multiplying non-witness data by 4 gives a weight of: + - kickoff_transaction_weight = 180vbytes * 4 = 720WU + - Adding the witness data: + - kickoff_transaction_weight += (funding_input_witness + witness_header) + - kickoff_transaction_weight = 944WU + +### Commitment Transaction Weights +Here we assume that both parties have an output on the commitment transaction. This is to keep the +weight consistent across potentially different commitment transactions. + + * musig2_funding_input_witness: 66 bytes + - number_of_witness_elements: 1 byte + - musig2_signature_length: 1 byte + - musig2_signature: 64 bytes + + * commitment_txin_0: 41 bytes (excl. witness) + - previous_out_point: 36 bytes + - hash: 32 bytes + - index: 4 bytes + - var_int: 1 byte (script_sig length) + - script_sig: 0 bytes + - witness: <---- part of the witness data + - sequence: 4 bytes + + * to_local: 43 bytes + - value: 8 bytes + - var_int: 1 byte (pk_script length) + - pk_script (p2tr): 34 bytes + + * to_remote: 43 bytes + - value: 8 bytes + - var_int: 1 byte (pk_script length) + - pk_script (p2tr): 34 bytes + + * to_local_anchor: 43 bytes + - value: 8 bytes + - var_int: 1 byte (pk_script length) + - pk_script (p2tr): 34 bytes + + * to_remote_anchor: 43 bytes + - value: 8 bytes + - var_int: 1 byte (pk_script length) + - pk_script (p2tr): 34 bytes + + * commitment_transaction: 225 bytes (excl. witness) + - version: 4 bytes + - witness_header: <---- part of the witness data + - count_tx_in: 1 byte + - tx_in: 41 bytes + - commitment_txin_0: 41 bytes + - count_tx_out: 3 byte + - tx_out: 172 bytes + - to_local: 43 bytes + - to_remote: 43 bytes + - to_local_anchor: 43 bytes + - to_remote_anchor: 43 bytes + - lock_time: 4 bytes + + - Multiplying non-witness data by 4 gives a weight of: + - commitment_transaction_weight = 223vbytes * 4 = 892WU + - Adding the witness data: + - commitment_transaction_weight += (musig2_funding_input_witness + witness_header) + - commitment_transaction_weight = 960WU From 1b807474734c9fe6c5f015fa8ba18620a7afc8f2 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Wed, 20 Sep 2023 14:46:21 -0700 Subject: [PATCH 02/23] reformats specification to be in column limit --- ext-dynamic-commitments.md | 501 ++++++++++++++++++++----------------- 1 file changed, 267 insertions(+), 234 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index 4e0ff2537..a2c4018be 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -226,47 +226,34 @@ convert existing channels into Taproot channels. # Specification -## Feature Bits (BOLT 9 Amendments) - -## Peer Protocol (BOLT 2 Amendments) - -## Transactions (BOLT 3 Amendments) - -## On-Chain Handling (BOLT 5 Amendments) - -# *EUGENE'S ORIGINAL DRAFT FOLLOWS* - -# Specification - ## Proposal Messages -Three new messages are introduced that are common to all dynamic commitment flows. -They let each side propose what they want to change about the channel. +Three new messages are introduced that are common to all dynamic commitment +flows. They let each side propose what they want to change about the channel. ### `dyn_begin_propose` -This message is sent when a node wants to begin the dynamic commitment negotiation -process. This is a signaling message, similar to `shutdown` in the cooperative close -flow. +This message is sent when a node wants to begin the dynamic commitment +negotiation process. This is a signaling message, similar to `shutdown` in the +cooperative close flow. 1. type: 111 (`dyn_begin_propose`) 2. data: * [`32*byte`:`channel_id`] * [`byte`: `begin_propose_flags`] -Only the least-significant-bit of `begin_propose_flags` is defined, the `reject` bit. +Only the least-significant-bit of `begin_propose_flags` is defined, the `reject` +bit. #### Requirements The sending node: - - MUST set `channel_id` to a valid channel they have with the - recipient. + - MUST set `channel_id` to a valid channel they have with the recipient. - MUST set undefined bits in `begin_propose_flags` to 0. - - MUST set the `reject` bit in `begin_propose_flags` if they are - rejecting the + - MUST set the `reject` bit in `begin_propose_flags` if they are rejecting the dynamic commitment negotiation request. - - MUST NOT send `update_add_htlc` messages after sending this unless - one of the following is true: + - MUST NOT send `update_add_htlc` messages after sending this unless one of + the following is true: - dynamic commitment negotiation has finished - a `dyn_begin_propose` with the `reject` bit has been received. - a reconnection has occurred. @@ -274,8 +261,7 @@ The sending node: - MUST fail to forward additional incoming HTLCs from the peer. The receiving node: - - if `channel_id` does not match an existing channel it has with the - peer: + - if `channel_id` does not match an existing channel it has with the peer: - MUST close the connection. - if the `reject` bit is set, but it hasn't sent a `dyn_begin_propose`: - MUST send an `error` and fail the channel. @@ -283,21 +269,22 @@ The receiving node: finished or terminated: - MUST send an `error` and fail the channel. - if it has not already sent `dyn_begin_propose` in this round of negotiation: - - MUST reply with `dyn_begin_propose` either rejecting or accepting the negotiation - request. + - MUST reply with `dyn_begin_propose` either rejecting or accepting the + negotiation request. #### Rationale -This has similar semantics to the `shutdown` message where the channel comes to a state -where updates may only be removed. The `reject` bit is necessary to avoid having to -reconnect in order to have a useable channel state again. +This has similar semantics to the `shutdown` message where the channel comes to +a state where updates may only be removed. The `reject` bit is necessary to +avoid having to reconnect in order to have a useable channel state again. ### `dyn_propose` -This message is sent when neither side owes the other either a `revoke_and_ack` or -`commitment_signed` message and each side's commitment has no HTLCs. For now, only the -`recipients_new_self_delay` parameter is defined in negotiation. After negotiation completes, -commitment signatures will use these parameters. The overall message flow looks like this: +This message is sent when neither side owes the other either a `revoke_and_ack` +or `commitment_signed` message and each side's commitment has no HTLCs. For now, +only the `recipients_new_self_delay` parameter is defined in negotiation. After +negotiation completes, commitment signatures will use these parameters. The +overall message flow looks like this: +-------+ +-------+ | |--(1)---dyn_begin_propose ---->| | @@ -328,10 +315,11 @@ commitment signatures will use these parameters. The overall message flow looks The sending node: - MUST set `channel_id` to an existing one it has with the recipient. - - MUST NOT send a `dyn_propose` if a prior one is waiting for `dyn_propose_reply`. + - MUST NOT send a `dyn_propose` if a prior one is waiting for + `dyn_propose_reply`. - MUST remember its last sent `dyn_propose` parameters. - - MUST send this message as soon as both side's commitment transaction is free of - any HTLCs and both sides have sent `dyn_begin_propose`. + - MUST send this message as soon as both side's commitment transaction is free + of any HTLCs and both sides have sent `dyn_begin_propose`. The receiving node: - if `channel_id` does not match an existing channel it has with the sender: @@ -343,41 +331,44 @@ The receiving node: #### Rationale -The requirement to not allow trimming outputs is just to make the dynamic commitment -flow as uninvasive as possible to the commitment transaction. A similar requirement -should be added for any new parameter such as the `channel_reserve`. +The requirement to not allow trimming outputs is just to make the dynamic +commitment flow as uninvasive as possible to the commitment transaction. A +similar requirement should be added for any new parameter such as the +`channel_reserve`. -The requirement for a node to remember what it last _sent_ and for it to remember -what it _accepted_ is necessary to recover on reestablish. See the reestablish section -for more details. +The requirement for a node to remember what it last _sent_ and for it to +remember what it _accepted_ is necessary to recover on reestablish. See the +reestablish section for more details. ### `dyn_propose_reply` -This message is sent in response to a `dyn_propose`. It may either accept or reject -the `dyn_propose`. If it rejects a `dyn_propose`, it allows the counterparty to send -another `dyn_propose` to try again. If for some reason, negotiation is taking too long, -it is possible to exit this phase by reconnecting as long as the exiting node hasn't -sent `dyn_propose_reply` without the `reject` bit. +This message is sent in response to a `dyn_propose`. It may either accept or +reject the `dyn_propose`. If it rejects a `dyn_propose`, it allows the +counterparty to send another `dyn_propose` to try again. If for some reason, +negotiation is taking too long, it is possible to exit this phase by +reconnecting as long as the exiting node hasn't sent `dyn_propose_reply` without +the `reject` bit. 1. type: 115 (`dyn_propose_reply`) 2. data: * [`32*byte`:`channel_id`] * [`byte`: `propose_reply_flags`] -The least-significant bit of `propose_reply_flags` is defined as the `reject` bit. +The least-significant bit of `propose_reply_flags` is defined as the `reject` +bit. #### Requirements The sending node: - MUST set `channel_id` to a valid channel they have with the recipient. - MUST set undefined bits in `propose_reply_flags` to 0. - - MUST set the `reject` bit in `propose_reply_flags` if they are rejecting the newest - `dyn_propose`. + - MUST set the `reject` bit in `propose_reply_flags` if they are rejecting the + newest `dyn_propose`. - MUST NOT send this message if there is no outstanding `dyn_propose` from the counterparty. - if the `reject` bit is not set: - - MUST remember the related `dyn_propose` parameters and the local and remote commitment - heights for the next `propose_height`. + - MUST remember the related `dyn_propose` parameters and the local and + remote commitment heights for the next `propose_height`. The receiving node: - if `channel_id` does not match an existing channel it has with the peer: @@ -388,14 +379,15 @@ The receiving node: - MUST forget its last sent `dyn_propose` parameters. A node: - - once it has both sent and received `dyn_propose_reply` without the `reject` bit set: + - once it has both sent and received `dyn_propose_reply` without the `reject` + bit set: - MUST increment their `propose_height`. #### Rationale -The `propose_height` starts at 0 for a channel and is incremented by 1 every time the -dynamic commitment proposal phase completes for a channel. See the reestablish section -for why this is needed. +The `propose_height` starts at 0 for a channel and is incremented by 1 every +time the dynamic commitment proposal phase completes for a channel. See the +reestablish section for why this is needed. ## Reestablish @@ -412,22 +404,25 @@ A new TLV that denotes the node's current `propose_height` is included. #### Requirements The sending node: - - MUST set `propose_height` to the number of dynamic proposal negotiations it has - completed. The point at which it is incremented is described in the `dyn_propose_reply` - section. + - MUST set `propose_height` to the number of dynamic proposal negotiations it + has completed. The point at which it is incremented is described in the + `dyn_propose_reply` section. The receiving node: - if the received `propose_height` equals its own `propose_height`: - - MUST forget any stored proposal state for `propose_height`+1 in case negotiation didn't - complete. Can continue using the channel. - - SHOULD forget any state that is unnecessary for heights <= `propose_height`. + - MUST forget any stored proposal state for `propose_height`+1 in case + negotiation didn't complete. Can continue using the channel. + - SHOULD forget any state that is unnecessary for heights <= + `propose_height`. - if the received `propose_height` is 1 greater than its own `propose_height`: - - if it does not have any remote parameters stored for the received `propose_height`: - - MUST send an `error` and fail the channel. The remote node is either lying about the - `propose_height` or the recipient has lost data since its not possible to advance the - height without the recipient storing the remote's parameters. - - resume using the channel with its last-sent `dyn_propose` and the stored `dyn_propose` - parameters and increment its `propose_height`. + - if it does not have any remote parameters stored for the received + `propose_height`: + - MUST send an `error` and fail the channel. The remote node is either + lying about the `propose_height` or the recipient has lost data since + its not possible to advance the height without the recipient storing the + remote's parameters. + - resume using the channel with its last-sent `dyn_propose` and the stored + `dyn_propose` parameters and increment its `propose_height`. - if the received `propose_height` is 1 less than its own `propose_height`: - resume using the channel with the new parameters. - else: @@ -435,20 +430,21 @@ The receiving node: #### Rationale -If both sides have sent and received `dyn_propose_reply` without the `reject` bit before the -connection closed, it is simple to continue. If one side has sent and received -`dyn_propose_reply` without the `reject` bit and the other side has only sent `dyn_propose_reply`, -the flow is recoverable on reconnection as the side that hasn't received `dyn_propose_reply` knows -that the other side accepted their last sent `dyn_propose` based on the `propose_height` in the -reestablish message. +If both sides have sent and received `dyn_propose_reply` without the `reject` +bit before the connection closed, it is simple to continue. If one side has sent +and received `dyn_propose_reply` without the `reject` bit and the other side has +only sent `dyn_propose_reply`, the flow is recoverable on reconnection as the +side that hasn't received `dyn_propose_reply` knows that the other side accepted +their last sent `dyn_propose` based on the `propose_height` in the reestablish +message. ## Musig2 Taproot -This section describes how dynamic commitments can upgrade regular channels to simple -taproot channels. The regular dynamic proposal phase is executed followed by a signing phase. -A channel-type of `option_taproot` will be included in `dyn_propose` and both sides must agree -on it. The funder of the channel will also propose a set of feerates to use for an intermediate -"kickoff" transaction. +This section describes how dynamic commitments can upgrade regular channels to +simple taproot channels. The regular dynamic proposal phase is executed followed +by a signing phase. A channel-type of `option_taproot` will be included in +`dyn_propose` and both sides must agree on it. The funder of the channel will +also propose a set of feerates to use for an intermediate "kickoff" transaction. ### Extensions to `dyn_propose`: @@ -468,38 +464,41 @@ on it. The funder of the channel will also propose a set of feerates to use for The sending node: - if it is the funder: - - MUST only send `kickoff_feerate` if they can pay for each kickoff transaction - fee and the anchor outputs, while adhering to the `channel_reserve` restriction. + - MUST only send `kickoff_feerate` if they can pay for each kickoff + transaction fee and the anchor outputs, while adhering to the + `channel_reserve` restriction. - MUST set `taproot_funding_key` to a valid secp256k1 compressed public key. - - SHOULD use a sufficient number of `kickoff_feerates` to be prepared for worst-case - fee environment scenarios. + - SHOULD use a sufficient number of `kickoff_feerates` to be prepared for + worst-case fee environment scenarios. The receiving node: - if it is the fundee: - - MUST reject the `dyn_propose` if the funder cannot pay for each kickoff transaction - fee and the anchor outputs. - - MUST reject the `dyn_propose` if, after calculating the amount of the new funding output, - the new commmitment transaction would not be able to pay for any outputs at the current - commitment feerate. - - MUST reject the `dyn_propose` if `taproot_funding_key` is not a valid secp256k1 compressed - public key. + - MUST reject the `dyn_propose` if the funder cannot pay for each kickoff + transaction fee and the anchor outputs. + - MUST reject the `dyn_propose` if, after calculating the amount of the new + funding output, the new commmitment transaction would not be able to pay + for any outputs at the current commitment feerate. + - MUST reject the `dyn_propose` if `taproot_funding_key` is not a valid + secp256k1 compressed public key. - MAY reject the `dyn_propose` if it does not agree with the `channel_type` - - MAY reject the `dyn_propose` if there are too many `kickoff_feerates` such that it would - be a burden to track the potential confirmation of each kickoff and commitment transaction - pair. + - MAY reject the `dyn_propose` if there are too many `kickoff_feerates` such + that it would be a burden to track the potential confirmation of each + kickoff and commitment transaction pair. #### Rationale -The `dyn_propose` renegotiates the funding keys as otherwise signatures for the funding keys -would be exchanged in both the ECDSA and Schnorr contexts. This can lead to an attack outlined -in [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#alternative-signing). -Renegotiating funding keys avoids this issue. Note that the various basepoints exchanged in -`open_channel` and `accept_channel` are not renegotiated. Because the private keys _change_ with -each commitment transaction they sign due to the `per_commitment_point` construction, the -basepoints can be used in both ECDSA and Schnorr contexts. +The `dyn_propose` renegotiates the funding keys as otherwise signatures for the +funding keys would be exchanged in both the ECDSA and Schnorr contexts. This can +lead to an attack outlined in [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#alternative-signing). +Renegotiating funding keys avoids this issue. Note that the various basepoints +exchanged in `open_channel` and `accept_channel` are not renegotiated. Because +the private keys _change_ with each commitment transaction they sign due to the +`per_commitment_point` construction, the basepoints can be used in both ECDSA +and Schnorr contexts. -The funder sends multiple fee-rates in order to be deal with high-fee environments. Without this, -the channel may not be able to upgrade commitment types until the fee environment changes. +The funder sends multiple fee-rates in order to be deal with high-fee +environments. Without this, the channel may not be able to upgrade commitment +types until the fee environment changes. ### Extensions to `dyn_propose_reply`: @@ -513,27 +512,32 @@ the channel may not be able to upgrade commitment types until the fee environmen The sending node: - if it is accepting a `channel_type` of `simple_taproot_channel`: - - MUST set `local_musig2_pubnonce` to the nonce that it will use to verify local - commitments. + - MUST set `local_musig2_pubnonce` to the nonce that it will use to verify + local commitments. The receiving node: - - MUST send an `error` and fail the channel if `local_musig2_pubnonce` cannot be parsed - as two compressed secp256k1 points. + - MUST send an `error` and fail the channel if `local_musig2_pubnonce` cannot + be parsed as two compressed secp256k1 points. ### Signing Phase -The signing phase is after the negotiation phase. The original funding output spends to an -intermediate transaction that pays to a v1 witness script with an aggregated musig2 key derived from -both parties `taproot_funding_key` sent in `dyn_propose`. As in the simple-taproot-channels proposal, -the `commitment_signed`, `revoke_and_ack`, and `channel_reestablish` messages include nonces. +The signing phase is after the negotiation phase. The original funding output +spends to an intermediate transaction that pays to a v1 witness script with an +aggregated musig2 key derived from both parties `taproot_funding_key` sent in +`dyn_propose`. As in the simple-taproot-channels proposal, the +`commitment_signed`, `revoke_and_ack`, and `channel_reestablish` messages +include nonces. #### Commitment Transaction * version: 2 -* locktime: upper 8 bits are 0x20, lower 24 bits are the lower 24 bits of the obscured commitment number +* locktime: upper 8 bits are 0x20, lower 24 bits are the lower 24 bits of the + obscured commitment number * txin count: 1 - * `txin[0]` outpoint: the matching kickoff transaction's musig2 funding outpoint. - * `txin[0]` sequence: upper 8 bits are 0x80, lower 24 bits are upper 24 bits of the obscured commitment number + * `txin[0]` outpoint: the matching kickoff transaction's musig2 funding + outpoint. + * `txin[0]` sequence: upper 8 bits are 0x80, lower 24 bits are upper 24 bits + of the obscured commitment number * `txin[0]` script bytes: 0 * `txin[0]` witness: `` @@ -545,24 +549,29 @@ The 48-bit commitment number is computed by `XOR` as described in BOLT#03. 2. Initialize the commitment transaction input. 3. Calculate which committed HTLCs need to be trimmed. 4. Calculate the commitment transaction fee via - commitment feerate * `commitment_transaction_weight`/1000, making sure to round down. Subtract - this from the funder's output. -5. Subtract four times the fixed anchor size of 330 satoshis from the funder's output. Two of - the anchors are from the commitment transaction and two are from the kickoff transaction. + commitment feerate * `commitment_transaction_weight`/1000, making sure to + round down. Subtract this from the funder's output. +5. Subtract four times the fixed anchor size of 330 satoshis from the funder's + output. Two of the anchors are from the commitment transaction and two are + from the kickoff transaction. 6. Subtract the matching kickoff transaction's fee from the funder's output. 7. For every offered HTLC, if it is not trimmed, add an offered HTLC output. 8. For every received HTLC, if it is not trimmed, add a received HTLC output. -9. If the `to_local` output is greater or equal to the dust limit, add a `to_local` output. -10. If the `to_remote` output is greater or equal to the dust limit, add a `to_remote` output. +9. If the `to_local` output is greater or equal to the dust limit, add a + `to_local` output. +10. If the `to_remote` output is greater or equal to the dust limit, add a + `to_remote` output. 11. If `to_local` exists or there are untrimmed HTLCs, add a `to_local_anchor`. -12. If `to_remote` exists or there are untrimmed HTLCs, add a `to_remote_anchor`. The - `to_remote_anchor` uses the remote party's `taproot_funding_key`. +12. If `to_remote` exists or there are untrimmed HTLCs, add a + `to_remote_anchor`. The `to_remote_anchor` uses the remote party's + `taproot_funding_key`. 13. Sort the outputs into BIP 69+CLTV order. #### commitment_signed -The `commitment_signed` message does not change, but adds a nonce in the TLV section per the -simple-taproot-channels proposal. It changes what it signs in the following ways: +The `commitment_signed` message does not change, but adds a nonce in the TLV +section per the simple-taproot-channels proposal. It changes what it signs in +the following ways: 1. `tlv_stream`: `commit_sig_tlvs` 2. types: @@ -577,30 +586,31 @@ simple-taproot-channels proposal. It changes what it signs in the following ways The sending node: - MUST NOT increment the commitment number when signing. - - MUST sign for any negotiated parameters that modified the commitment transaction - (e.g. `to_self_delay`). + - MUST sign for any negotiated parameters that modified the commitment + transaction (e.g. `to_self_delay`). The receiving node: - - MUST send an `error` and fail the channel if the signature does not sign the commitment - transaction as constructed above. - - MUST send an `error` and fail the channel if `partial_signature` is not a valid Schnorr - signature. - - MUST send an `error` and fail the channel if `public_nonce` cannot be parsed as two - compressed secp256k1 points. - - MUST send an `error` and fail the chanel if `local_musig2_pubnonce` cannot be parsed as - two compressed secp256k1 points. + - MUST send an `error` and fail the channel if the signature does not sign the + commitment transaction as constructed above. + - MUST send an `error` and fail the channel if `partial_signature` is not a + valid Schnorr signature. + - MUST send an `error` and fail the channel if `public_nonce` cannot be parsed + as two compressed secp256k1 points. + - MUST send an `error` and fail the chanel if `local_musig2_pubnonce` cannot + be parsed as two compressed secp256k1 points. ##### Rationale -The commitment number is not incremented while signing because if there are N kickoff -transactions and the N-2 kickoff transaction confirms, then implementations will need to -rewind their commitment number to N-2. We avoid this complexity by keeping the commitment -numbers static until the signing phase is complete. +The commitment number is not incremented while signing because if there are N +kickoff transactions and the N-2 kickoff transaction confirms, then +implementations will need to rewind their commitment number to N-2. We avoid +this complexity by keeping the commitment numbers static until the signing phase +is complete. -A set of local nonces is included because each signed commitment transaction shares the -same commitment number as the pre-dynamic-commitment commitment transaction. For this -reason, `revoke_and_ack` is omitted and thus local nonces need to be sent in -`commitment_signed`. +A set of local nonces is included because each signed commitment transaction +shares the same commitment number as the pre-dynamic-commitment commitment +transaction. For this reason, `revoke_and_ack` is omitted and thus local nonces +need to be sent in `commitment_signed`. #### Kickoff Transaction(s) @@ -616,8 +626,8 @@ reason, `revoke_and_ack` is omitted and thus local nonces need to be sent in * `txout[1]`: `anchor_output_1` or `anchor_output_2` * `txout[2]`: `p2tr_funding_output` -The anchor outputs have a value of 330 satoshis. They are encumbered by a version 1 witness -script: +The anchor outputs have a value of 330 satoshis. They are encumbered by a +version 1 witness script: * `OP_1 anchor_output_key` * where: * `anchor_internal_key = original_local_funding_pubkey/original_remote_funding_pubkey` @@ -628,9 +638,10 @@ script: OP_16 OP_CHECKSEQUENCEVERIFY ``` -The new funding output has a value of the original funding output minus the sum of 660 satoshis -and this kickoff transaction's fee. It is encumbered by a version 1 witness script where -`taproot_funding_key1/taproot_funding_key2` are from `dyn_propose_reply`: +The new funding output has a value of the original funding output minus the sum +of 660 satoshis and this kickoff transaction's fee. It is encumbered by a +version 1 witness script where `taproot_funding_key1/taproot_funding_key2` are +from `dyn_propose_reply`: * `OP_1 funding_key` * where: * `funding_key = combined_funding_key + tagged_hash("TapTweak", combined_funding_key)*G` @@ -640,19 +651,22 @@ and this kickoff transaction's fee. It is encumbered by a version 1 witness scri 1. Initialize the commitment transaction version and locktime. 2. Initialize the commitment transaction input. -3. Calculate this kickoff transaction's fee via `kickoff_feerate_per_kw`*`kickoff_transaction_weight`/1000, - making sure to round down. Subtract this value from the new funding output. -5. Subtract two times the fixed anchor size of 330 satoshis from the new funding output. +3. Calculate this kickoff transaction's fee via `kickoff_feerate_per_kw`* + `kickoff_transaction_weight`/1000, making sure to round down. Subtract this + value from the new funding output. +5. Subtract two times the fixed anchor size of 330 satoshis from the new funding + output. 6. Add a funding output with the new funding amount. 7. Add an anchor output for each party. 8. Sort the outputs into BIP 69+CLTV order. #### kickoff_sig -The kickoff_sig is a message containing a signature that the fundee sends to the funder who -then combines it with their own signature to spend from the original funding outpoint into the new -musig2 output. To keep things simple, no additional inputs are added to the intermediate -transaction. An anchor output is attached to either side for fee-bumping. +The kickoff_sig is a message containing a signature that the fundee sends to the +funder who then combines it with their own signature to spend from the original +funding outpoint into the new musig2 output. To keep things simple, no +additional inputs are added to the intermediate transaction. An anchor output is +attached to either side for fee-bumping. ![Cannot display image](./dynamic-commits/kickoff%20tx.png "Kickoff transaction") @@ -668,20 +682,21 @@ The sending node (the fundee): - MUST NOT send this message before receiving the peer's `commitment_signed`. The receiving node (the funder): - - MUST send an `error` and fail the channel if `channel_id` does not match an existing - channel it has with the sender. - - MUST send an `error` and fail the channel if `signature` is not valid for the kickoff - transaction as constructed above OR non-compliant with the LOW-S-standard rule. [LOWS](https://github.com/bitcoin/bitcoin/pull/6769) + - MUST send an `error` and fail the channel if `channel_id` does not match an + existing channel it has with the sender. + - MUST send an `error` and fail the channel if `signature` is not valid for + the kickoff transaction as constructed above OR non-compliant with the + LOW-S-standard rule. [LOWS](https://github.com/bitcoin/bitcoin/pull/6769) ##### Rationale -To avoid the fundee griefing the funder by broadcasting the highest-fee kickoff transaction, -only the fundee sends `kickoff_sig`. This ensures that only the funder can broadcast the kickoff -transaction. +To avoid the fundee griefing the funder by broadcasting the highest-fee kickoff +transaction, only the fundee sends `kickoff_sig`. This ensures that only the +funder can broadcast the kickoff transaction. -Even though only the funder is able to broadcast the kickoff transaction, we include anchors -such that the fundee can broadcast fee-bumping transactions if they notice any of the kickoff -transactions in the mempool. +Even though only the funder is able to broadcast the kickoff transaction, we +include anchors such that the fundee can broadcast fee-bumping transactions if +they notice any of the kickoff transactions in the mempool. #### Message flow to upgrade a channel to simple-taproot: @@ -696,24 +711,25 @@ transactions in the mempool. | |<-(6)----- kickoff_sig --------| | +-------+ +-------+ -The above message ordering is important. If `kickoff_sig` is sent before `commit_sig`, a -griefing attack is possible: +The above message ordering is important. If `kickoff_sig` is sent before +`commit_sig`, a griefing attack is possible: +-------+ +-------+ | A |<-(1)----- kickoff_sig --------| B | +-------+ +-------+ -Here, A stops sending messages and instead immediately broadcasts the kickoff transaction. -Since neither side has exchanged `commitment_signed`, the new funding output is unclaimable and -is effectively burned. The majority of the channel could be in B's outputs, making the loss -of funds disproportionately on B's side. +Here, A stops sending messages and instead immediately broadcasts the kickoff +transaction. Since neither side has exchanged `commitment_signed`, the new +funding output is unclaimable and is effectively burned. The majority of the +channel could be in B's outputs, making the loss of funds disproportionately on +B's side. ### Reestablish during simple-taproot upgrade #### channel_reestablish -The `channel_reestablish` message does not change, but adds a nonce in the TLV section per the -simple-taproot-channels proposal. +The `channel_reestablish` message does not change, but adds a nonce in the TLV +section per the simple-taproot-channels proposal. 1. `tlv_stream`: `channel_reestablish_tlvs` 2. types: @@ -731,32 +747,37 @@ simple-taproot-channels proposal. * [`u16`:`num_sigs`] The sending node: - - MUST set `next_local_nonce` if the sender sees it has persisted a `channel_type` of - `option_simple_taproot` from the `dyn_propose` / `dyn_propose_reply` negotiation steps. - - MUST set `num_sent_commit_sigs` to the number of `commitment_signed` it has sent for this - negotiation session. - - MUST set `num_recv_commit_sigs` to the number of `commitment_signed` it has received for - this negotiation session. + - MUST set `next_local_nonce` if the sender sees it has persisted a + `channel_type` of `option_simple_taproot` from the `dyn_propose` / + `dyn_propose_reply` negotiation steps. + - MUST set `num_sent_commit_sigs` to the number of `commitment_signed` it has + sent for this negotiation session. + - MUST set `num_recv_commit_sigs` to the number of `commitment_signed` it has + received for this negotiation session. - if it is the funder: - - MUST set `num_kickoff_sigs` to the number of `kickoff_sig` messages it has received. + - MUST set `num_kickoff_sigs` to the number of `kickoff_sig` messages it has + received. - otherwise (it is the fundee): - - MUST set `num_kickoff_sigs` to the number of `kickoff_sig` messages it has sent. + - MUST set `num_kickoff_sigs` to the number of `kickoff_sig` messages it has + sent. The receiving node: - - MUST send an `error` and fail the channel if `next_local_nonce` cannot be parsed as - two compressed secp256k1 points. - - if its sent `num_sent_commit_sigs` is one greater than the received `num_recv_commit_sigs`: + - MUST send an `error` and fail the channel if `next_local_nonce` cannot be + parsed as two compressed secp256k1 points. + - if its sent `num_sent_commit_sigs` is one greater than the received + `num_recv_commit_sigs`: - MUST retransmit the missing `commitment_signed`. - if it is the fundee: - - if its sent `num_kickoff_sigs` is one greater than the received `num_kickoff_sigs`: + - if its sent `num_kickoff_sigs` is one greater than the received + `num_kickoff_sigs`: - MUST retransmit the missing `kickoff_sig`. - if messages were retransmitted: - - MUST continue with the rest of the signing flow until a `kickoff_sig` has been sent for - each fee-rate in `kickoff_feerates`. + - MUST continue with the rest of the signing flow until a `kickoff_sig` has + been sent for each fee-rate in `kickoff_feerates`. -The signing phase is complete when the funder's sent `num_kickoff_sigs` is equal to the -fundee's sent `num_kickoff_sigs` and is also equal to the number of fee-rates in -`kickoff_feerates` from the persisted `dyn_propose` parameters. +The signing phase is complete when the funder's sent `num_kickoff_sigs` is equal +to the fundee's sent `num_kickoff_sigs` and is also equal to the number of +fee-rates in `kickoff_feerates` from the persisted `dyn_propose` parameters. # Appendix @@ -764,68 +785,79 @@ fundee's sent `num_kickoff_sigs` and is also equal to the number of fee-rates in ![Cannot display image](./dynamic-commits/kickoff%20pinning.png "Kickoff transaction pinned") -Originally, Bitcoin Core's default mempool settings allowed an unconfirmed transaction to have up -to 25 decendants in the mempool. Past this limit, any descendants would be rejected. This was used -as a DoS mitigation in Bitcoin Core and affected the security of LN channels. Before the anchors -commitment type was introduced, pinning in the LN was where a counterparty broadcasted the -commitment transaction and created a chain of 25 descendants spending from one of the commitment's -outputs. The time-sensitive commitment transaction could be "pinned" to the bottom of the mempool. -This was addressed with a change to Bitcoin Core called CPFP Carve-out. +Originally, Bitcoin Core's default mempool settings allowed an unconfirmed +transaction to have up to 25 decendants in the mempool. Past this limit, any +descendants would be rejected. This was used as a DoS mitigation in Bitcoin Core +and affected the security of LN channels. Before the anchors commitment type was +introduced, pinning in the LN was where a counterparty broadcasted the +commitment transaction and created a chain of 25 descendants spending from one +of the commitment's outputs. The time-sensitive commitment transaction could be +"pinned" to the bottom of the mempool. This was addressed with a change to +Bitcoin Core called CPFP Carve-out. ### CPFP Carve-out -CPFP Carve-out was introduced to Bitcoin Core in https://github.com/bitcoin/bitcoin/pull/15681. If -a Bitcoin node receives a transaction that is rejected due to any of the mempool size or ancestor/ -descendant restrictions being hit, it will try to accept the transaction again. This second try -will succeed only if: +CPFP Carve-out was introduced to Bitcoin Core in +https://github.com/bitcoin/bitcoin/pull/15681. If a Bitcoin node receives a +transaction that is rejected due to any of the mempool size or +ancestor/descendant restrictions being hit, it will try to accept the +transaction again. This second try will succeed only if: - the transaction is 40kWU or less - it has only one ancestor in the mempool -This change, in conjunction with the anchor commitment type, decreases the efficacy of the -pinning attack since the honest party can still attach an anchor despite the descendant size -limit being hit. +This change, in conjunction with the anchor commitment type, decreases the +efficacy of the pinning attack since the honest party can still attach an anchor +despite the descendant size limit being hit. ### Dynamic Commitments & CPFP Carve-out -The safety guarantees of CPFP Carve-out break due to the structure of the kickoff transaction. -The kickoff transaction contains 3 spendable outputs: the local party's anchor, the remote -party's anchor, and the new funding output. All three of these outputs can be spent immediately. -A malicious counterparty can pin the kickoff transaction by: - - spending from their anchor output to create a descendant chain of 25 transactions - - then spending from the new funding output, "using up" the CPFP Carve-out slot designated - for the honest party. - -Depending on fee conditions, it may not be possible for the honest party to get these -transactions confirmed until the mempool clears up. - -If we were to get rid of the kickoff transaction's anchor outputs, the problem still arises. -A malicious counterparty could still pin the kickoff transaction by: +The safety guarantees of CPFP Carve-out break due to the structure of the +kickoff transaction. The kickoff transaction contains 3 spendable outputs: the +local party's anchor, the remote party's anchor, and the new funding output. All +three of these outputs can be spent immediately. A malicious counterparty can +pin the kickoff transaction by: + - spending from their anchor output to create a descendant chain of 25 + transactions + - then spending from the new funding output, "using up" the CPFP Carve-out + slot designated for the honest party. + +Depending on fee conditions, it may not be possible for the honest party to get +these transactions confirmed until the mempool clears up. + +If we were to get rid of the kickoff transaction's anchor outputs, the problem +still arises. A malicious counterparty could still pin the kickoff transaction +by: - broadcasting the commitment transaction - - spending from their anchor output and creating a descendant chain of 25 transactions + - spending from their anchor output and creating a descendant chain of 25 + transactions The honest party is unable to use their anchor on the commitment transaction as: - the descendant limit of 25 transactions has been hit - - the anchor spend would have 2 ancestors (the commitment and kickoff transactions) + - the anchor spend would have 2 ancestors (the commitment and kickoff + transactions) ### Reducing Risk -The above pinning scenarios highlight the complexity of second-layer protocols and mempool -restrictions. In this proposal, pinning is _still_ possible, but risk is mitigated because: - - the kickoff transaction MUST confirm before HTLCs can be added to the commitment - transaction - - no HTLCs exist on the commitment transaction while the kickoff transaction is unconfirmed +The above pinning scenarios highlight the complexity of second-layer protocols +and mempool restrictions. In this proposal, pinning is _still_ possible, but +risk is mitigated because: + - the kickoff transaction MUST confirm before HTLCs can be added to the + commitment transaction + - no HTLCs exist on the commitment transaction while the kickoff transaction + is unconfirmed -If we allowed adding HTLCs _before_ the kickoff transaction confirmed on-chain, the pinning -attack would now have a tangible benefit: the ability to steal the value of an HTLC. The second -requirement above is very similar to the first requirement: by disallowing HTLCs when -`dyn_propose` is sent, we ensure that the counterparty has no incentive to pin the kickoff -transaction. +If we allowed adding HTLCs _before_ the kickoff transaction confirmed on-chain, +the pinning attack would now have a tangible benefit: the ability to steal the +value of an HTLC. The second requirement above is very similar to the first +requirement: by disallowing HTLCs when `dyn_propose` is sent, we ensure that the +counterparty has no incentive to pin the kickoff transaction. ## Weights -Since DER-encoded signatures vary in size, we assume a worst-case signature size of 73 bytes to -keep things simple. The kickoff transaction has an _expected weight_ of 944WU and the -commitment transaction has an _expected weight_ of 960WU. +Since DER-encoded signatures vary in size, we assume a worst-case signature size +of 73 bytes to keep things simple. The kickoff transaction has an +_expected weight_ of 944WU and the commitment transaction has an +_expected weight_ of 960WU. General weights: * p2tr: 34 bytes @@ -896,8 +928,9 @@ General weights: - kickoff_transaction_weight = 944WU ### Commitment Transaction Weights -Here we assume that both parties have an output on the commitment transaction. This is to keep the -weight consistent across potentially different commitment transactions. +Here we assume that both parties have an output on the commitment transaction. +This is to keep the weight consistent across potentially different commitment +transactions. * musig2_funding_input_witness: 66 bytes - number_of_witness_elements: 1 byte From 61569667a521f09dfc37a9c6761fc6944d35e773 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Fri, 22 Sep 2023 19:30:35 -0700 Subject: [PATCH 03/23] remove table and fix some wording --- ext-dynamic-commitments.md | 112 +++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index a2c4018be..b9595b932 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -82,38 +82,10 @@ to skip to the Design Overview section to save time ### Channel Opening Parameters As described in BOLT 2, during the channel opening procedure there are a number of parameters specified in the `open_channel` and `accept_channel` messages that -remain static over the lifetime of the channel. An exhaustive list of these is -provided below: - -TODO: clean up this table -| | open|accept|open2|accept2|dyncomm| -|-| -------------- | ---------------- | --------------- | ---------------- |-| -|chain_hash|x||x||unnecessary| -|temporary_channel_id|x|x|x|x|real_channel_id| -|funding_feerate_per_kw|||x||kickoff_feerate| -|commitment_feerate_perkw|x||x||x| -|funding_satoshis|x||x|x|x| -|push_msat|x||||?| -|dust_limit_satoshis|x|x|x|x|x| -|max_htlc_value_in_flight_msat|x|x|x|x|x| -|channel_reserve_satoshis|x|x|||x| -|htlc_minimum_msat|x|x|x|x|x| -|minimum_depth||x||x|0| -|to_self_delay|x|x|x|x|x| -|max_accepted_htlcs|x|x|x|x|x| -|funding_pubkey|x|x|x|x|x| -|revocation_basepoint|x|x|x|x|?| -|payment_basepoint|x|x|x|x|?| -|delayed_payment_basepoint|x|x|x|x|?| -|htlc_basepoint|x|x|x|x|?| -|first_per_commitment_point|x|x|x|x|?| -|channel_flags|x||x||| -|upfront_shutdown|x|x|x|x|| -|channel_type|x|x|x|x|x| - -A subset of these are updatable using other messages defined in the protocol. -However, after accounting for the channel parameters that can be changed there -is a list of parameters which remain unchangeable which we list below. +remain static over the lifetime of the channel. A subset of these are updatable +using other messages defined in the protocol. However, after accounting for the +channel parameters that can be changed there is a list of parameters which +remain unchangeable, which we list below. - dust_limit_satoshis - max_htlc_value_in_flight_msat @@ -131,12 +103,12 @@ is a list of parameters which remain unchangeable which we list below. - upfront_shutdown - channel_type -After some analysis during the development of this proposal it is determined -that many of these values don't make sense to rotate. There is no value and -additional administrative costs to rotating the basepoints of a channel. -Additionally, changing the upfront_shutdown script over the lifetime of the -channel is self-defeating and so we exclude it as well. The list of channel -parameters remaining after we filter out these values is thus. +After some analysis during the development of this proposal, we determined that +many of the basepoint values don't make sense to rotate. There is no obvious +value in doing so and it carries additional administrative costs to rotate them. +Finally, changing the upfront_shutdown script over the lifetime of the channel +is self-defeating and so we exclude it as well. The list of channel parameters +remaining after we filter out these values is thus. - dust_limit_satoshis - max_htlc_value_in_flight_msat @@ -225,13 +197,20 @@ always be necessary, but it is certainly necessary for using this proposal to convert existing channels into Taproot channels. # Specification +There are two phases to this channel upgrade process: proposal and execution. +During the proposal phase the only goal is to agree on a set of updates to the +current channel state machine. During the execution phase, we apply the updates +to the channel state machine, exchanging the necessary information to be able +to apply those updates -## Proposal Messages +## Proposal Phase + +### Proposal Messages Three new messages are introduced that are common to all dynamic commitment flows. They let each side propose what they want to change about the channel. -### `dyn_begin_propose` +#### `dyn_begin_propose` This message is sent when a node wants to begin the dynamic commitment negotiation process. This is a signaling message, similar to `shutdown` in the @@ -245,7 +224,7 @@ cooperative close flow. Only the least-significant-bit of `begin_propose_flags` is defined, the `reject` bit. -#### Requirements +##### Requirements The sending node: - MUST set `channel_id` to a valid channel they have with the recipient. @@ -272,13 +251,16 @@ The receiving node: - MUST reply with `dyn_begin_propose` either rejecting or accepting the negotiation request. -#### Rationale +##### Rationale This has similar semantics to the `shutdown` message where the channel comes to -a state where updates may only be removed. The `reject` bit is necessary to -avoid having to reconnect in order to have a useable channel state again. +a state where updates may only be removed. Since, for simplicity, we require +there to be no outstanding HTLCs on the commitment during the following +negotiation, we must first signal that no new HTLCs may be added. The `reject` +bit is necessary to avoid having to reconnect in order to have a useable channel +state again. -### `dyn_propose` +#### `dyn_propose` This message is sent when neither side owes the other either a `revoke_and_ack` or `commitment_signed` message and each side's commitment has no HTLCs. For now, @@ -307,11 +289,35 @@ overall message flow looks like this: 1. `tlv_stream`: `dyn_propose_tlvs` 2. types: - 1. type: 0 (`to_self_delay`) + 1. type: 0 (`dust_limit_satoshis`) + 2. data: + * [`u64`:`dust_limit_satoshis`] + 1. type: 2 (`max_htlc_value_in_flight_msat`) + 2. data: + * [`u64`:`senders_max_htlc_value_in_flight_msat`] + 1. type: 4 (`channel_reserve_satoshis`) + 2. data: + * [`u64`:`recipients_channel_reserve_satoshis`] + 1. type: 6 (`htlc_minimum_msat`) + 2. data: + * [`u64`:`senders_htlc_minimum_msat`] + 1. type: 8 (`to_self_delay`) + 2. data: + * [`u16`:`recipients_to_self_delay`] + 1. type: 10 (`max_accepted_htlcs`) + 2. data: + * [`u16`:`senders_max_accepted_htlcs`] + 1. type: 12 (`funding_pubkey`) 2. data: - * [`u16`:`recipients_new_self_delay`] + * [`point`:`senders_funding_pubkey`] + 1. type: 14 (`channel_type`) + 2. data: + * [`...*byte`:`channel_type`] + 1. type: 16 (`kickoff_feerate`) + 2. data: + * [`...*u32`:`kickoff_feerate_per_kw`] -#### Requirements +##### Requirements The sending node: - MUST set `channel_id` to an existing one it has with the recipient. @@ -329,7 +335,7 @@ The receiving node: - else: - MUST send a `dyn_propose_reply` without the `reject` bit set. -#### Rationale +##### Rationale The requirement to not allow trimming outputs is just to make the dynamic commitment flow as uninvasive as possible to the commitment transaction. A @@ -340,7 +346,7 @@ The requirement for a node to remember what it last _sent_ and for it to remember what it _accepted_ is necessary to recover on reestablish. See the reestablish section for more details. -### `dyn_propose_reply` +#### `dyn_propose_reply` This message is sent in response to a `dyn_propose`. It may either accept or reject the `dyn_propose`. If it rejects a `dyn_propose`, it allows the @@ -357,7 +363,7 @@ the `reject` bit. The least-significant bit of `propose_reply_flags` is defined as the `reject` bit. -#### Requirements +##### Requirements The sending node: - MUST set `channel_id` to a valid channel they have with the recipient. @@ -383,7 +389,7 @@ A node: bit set: - MUST increment their `propose_height`. -#### Rationale +##### Rationale The `propose_height` starts at 0 for a channel and is incremented by 1 every time the dynamic commitment proposal phase completes for a channel. See the @@ -442,7 +448,7 @@ message. This section describes how dynamic commitments can upgrade regular channels to simple taproot channels. The regular dynamic proposal phase is executed followed -by a signing phase. A channel-type of `option_taproot` will be included in +by a signing phase. A `channel_type` of `option_taproot` will be included in `dyn_propose` and both sides must agree on it. The funder of the channel will also propose a set of feerates to use for an intermediate "kickoff" transaction. From 2d5eccc6f24dd28ca38737b6744892a94906d935 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Mon, 9 Oct 2023 11:57:49 -0700 Subject: [PATCH 04/23] wip, this commit is solely to save work, will squash later --- ext-dynamic-commitments.md | 225 ++++++++++++++++++++----------------- 1 file changed, 122 insertions(+), 103 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index b9595b932..8054b9af1 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -51,46 +51,44 @@ and privacy capabilities afforded by the 2021 Taproot Soft Fork. With further aspirations to be able to deploy Point Time-Lock Contracts (PTLCs) to the Lightning Network, the sooner that network participants can upgrade to STCs the more we will have the necessary network infrastructure to be able to make -effective PTLCs when the protocols for them are specified. +effective use of PTLCs when the protocols for them are specified. -Due to the design of STCs and the fact that they are designed to make full use -of the capabilities afforded by Schnorr Signatures, there is no way to construct -a valid `channel_announcement` message that references the output corresponding -to the nodes' joint public key. As such, even if we were to directly spend an +Due to the design of STCs and the fact that they take full advantage of the +capabilities afforded by Schnorr Signatures, there is no way to construct a +valid `channel_announcement` message that references the output corresponding to +the nodes' joint public key. As such, even if we were to directly spend an existing channel point to a new STC channel point, and even with the provision in BOLT 7 to delay graph pruning by 12 blocks after the channel point is spent, we have no way to make the STC known to the network at the time of writing of this proposal. Concurrently with this proposal is a proposal for a new gossip system that is -capable of understanding announcements of the new STCs. However, in an effort to -allow nodes to upgrade to STCs without impacting the rest of the network, the -rest of the network would already need to be capable of understanding messages -from this new gossip system, the pervasive deployment of which remains far in -the future. To remove this disincentive of these channel upgrades to the -involved parties, this proposal to enable the change of these channel parameters -(including channel types) without requiring channel closure and reopening is -submitted. +capable of understanding the announcements of new STCs. However, even with a new +gossip system capable of understanding the STC construction and announcement, +it will take quite some time for such a system to be broadly deployed across the +Lightning Network. In the interim, to remove this disincentive of these channel +upgrades to the involved parties, this proposal to enable the change of these +channel parameters (including channel types) without requiring channel closure +and reopening is submitted. ## Preliminaries -This proposal includes a detailed section on the preliminaries to document to -reviewers and posterity some of the rationale for the design that is presented -later. If you are a Bitcoin and Lightning Network protocol expert or you are -uninterested in the thought process behind what is presented here, you may wish -to skip to the Design Overview section to save time +This proposal includes a detailed section on the preliminaries to document some +of the rationale for the design that is presented later. If you are a Bitcoin +and Lightning Network protocol expert or you are uninterested in the thought +process behind what is presented here, you may wish to skip to the Design +Overview section to save time. ### Channel Opening Parameters As described in BOLT 2, during the channel opening procedure there are a number of parameters specified in the `open_channel` and `accept_channel` messages that remain static over the lifetime of the channel. A subset of these are updatable using other messages defined in the protocol. However, after accounting for the -channel parameters that can be changed there is a list of parameters which -remain unchangeable, which we list below. +channel parameters that can be changed using existing mechanisms, there remains +a list of parameters which are unchangeable. We list these below: - dust_limit_satoshis - max_htlc_value_in_flight_msat - channel_reserve_satoshis -- htlc_minimum_msat - to_self_delay - max_accepted_htlcs - funding_pubkey @@ -104,16 +102,15 @@ remain unchangeable, which we list below. - channel_type After some analysis during the development of this proposal, we determined that -many of the basepoint values don't make sense to rotate. There is no obvious -value in doing so and it carries additional administrative costs to rotate them. -Finally, changing the upfront_shutdown script over the lifetime of the channel -is self-defeating and so we exclude it as well. The list of channel parameters -remaining after we filter out these values is thus. +the basepoint values don't make sense to rotate. There is no obvious value in +doing so and it carries additional administrative costs to rotate them. Finally, +changing the upfront_shutdown script over the lifetime of the channel is +self-defeating and so we exclude it as well. The list of channel parameters +remaining after we filter out these values is thus: - dust_limit_satoshis - max_htlc_value_in_flight_msat - channel_reserve_satoshis -- htlc_minimum_msat - to_self_delay - max_accepted_htlcs - funding_pubkey @@ -205,86 +202,87 @@ to apply those updates ## Proposal Phase -### Proposal Messages +### Node Roles -Three new messages are introduced that are common to all dynamic commitment -flows. They let each side propose what they want to change about the channel. +In every dynamic commitment negotiation, there are two roles: the `initiator` +and the `responder`. It is necessary for both nodes to agree on which node is +the `initiator` and which node is the `responder`. This is important because if +the dynamic commitment negotiation results in a re-anchoring step (described +later), it is the initiator that is responsible for paying the fees for the +kickoff transaction. -#### `dyn_begin_propose` +### Negotiation TLVs -This message is sent when a node wants to begin the dynamic commitment -negotiation process. This is a signaling message, similar to `shutdown` in the -cooperative close flow. +The following TLVs are used throughout the negotiation phase of the protocol +and are common to all messages in the negotiation phase. -1. type: 111 (`dyn_begin_propose`) -2. data: - * [`32*byte`:`channel_id`] - * [`byte`: `begin_propose_flags`] +#### dust_limit_satoshis +- type: 0 + data: + * [`u64`:`dust_limit_satoshis`] -Only the least-significant-bit of `begin_propose_flags` is defined, the `reject` -bit. +#### max_htlc_value_in_flight_msat +- type: 1 + data: + * [`u64`:`senders_max_htlc_value_in_flight_msat`] -##### Requirements +#### channel_reserve_satoshis +- type: 2 + data: + * [`u64`:`recipients_channel_reserve_satoshis`] -The sending node: - - MUST set `channel_id` to a valid channel they have with the recipient. - - MUST set undefined bits in `begin_propose_flags` to 0. - - MUST set the `reject` bit in `begin_propose_flags` if they are rejecting the - dynamic commitment negotiation request. - - MUST NOT send `update_add_htlc` messages after sending this unless one of - the following is true: - - dynamic commitment negotiation has finished - - a `dyn_begin_propose` with the `reject` bit has been received. - - a reconnection has occurred. - - MUST only send one `dyn_begin_propose` during a single negotiation. - - MUST fail to forward additional incoming HTLCs from the peer. +#### to_self_delay +- type: 3 + data: + * [`u16`:`recipients_to_self_delay`] -The receiving node: - - if `channel_id` does not match an existing channel it has with the peer: - - MUST close the connection. - - if the `reject` bit is set, but it hasn't sent a `dyn_begin_propose`: - - MUST send an `error` and fail the channel. - - if an `update_add_htlc` is received after this point and negotiation hasn't - finished or terminated: - - MUST send an `error` and fail the channel. - - if it has not already sent `dyn_begin_propose` in this round of negotiation: - - MUST reply with `dyn_begin_propose` either rejecting or accepting the - negotiation request. +#### max_accepted_htlcs +- type: 4 + data: + * [`u16`:`senders_max_accepted_htlcs`] -##### Rationale +#### funding_pubkey +- type: 5 + data: + * [`point`:`senders_funding_pubkey`] + +#### channel_type +- type: 6 + data: + * [`...*byte`:`channel_type`] -This has similar semantics to the `shutdown` message where the channel comes to -a state where updates may only be removed. Since, for simplicity, we require -there to be no outstanding HTLCs on the commitment during the following -negotiation, we must first signal that no new HTLCs may be added. The `reject` -bit is necessary to avoid having to reconnect in order to have a useable channel -state again. +#### kickoff_feerate_per_kw +- type: 7 + data: + * [`...*u32`:`kickoff_feerate_per_kw`] + + +### Proposal Messages + +Three new messages are introduced that are common to all dynamic commitment +flows. They let each channel party propose which channel parameters they wish to +change as well as accept or reject the proposal made by their counterparty. #### `dyn_propose` -This message is sent when neither side owes the other either a `revoke_and_ack` -or `commitment_signed` message and each side's commitment has no HTLCs. For now, -only the `recipients_new_self_delay` parameter is defined in negotiation. After -negotiation completes, commitment signatures will use these parameters. The -overall message flow looks like this: +This message is sent to initiate the negotiation of a dynamic commitment +upgrade. The overall protocol flow looks similar to what is depicted below. +This message is always sent by the initiator and MAY be sent by the responder +-------+ +-------+ - | |--(1)---dyn_begin_propose ---->| | - | | | | - | |<-(2)---dyn_begin_propose------| | + | |--(1)---- dyn_propose -------->| | | | | | - | |--(3)----- dyn_propose ------->| | + | |<-(2)---- dyn_propose ---------| | | A | | B | - | |<-(4)----- dyn_propose --------| | + | |--(3)------ dyn_ack ---------->| | | | | | - | |--(5)--- dyn_propose_reply --->| | - | | | | - | |<-(6)--- dyn_propose_reply ----| | + | |<-(4)------ dyn_ack -----------| | +-------+ +-------+ 1. type: 113 (`dyn_propose`) 2. data: * [`32*byte`:`channel_id`] + * [`u8`:`initiator`] * [`dyn_propose_tlvs`:`tlvs`] 1. `tlv_stream`: `dyn_propose_tlvs` @@ -292,48 +290,65 @@ overall message flow looks like this: 1. type: 0 (`dust_limit_satoshis`) 2. data: * [`u64`:`dust_limit_satoshis`] - 1. type: 2 (`max_htlc_value_in_flight_msat`) + 1. type: 1 (`max_htlc_value_in_flight_msat`) 2. data: * [`u64`:`senders_max_htlc_value_in_flight_msat`] - 1. type: 4 (`channel_reserve_satoshis`) + 1. type: 2 (`channel_reserve_satoshis`) 2. data: * [`u64`:`recipients_channel_reserve_satoshis`] - 1. type: 6 (`htlc_minimum_msat`) - 2. data: - * [`u64`:`senders_htlc_minimum_msat`] - 1. type: 8 (`to_self_delay`) + 1. type: 3 (`to_self_delay`) 2. data: * [`u16`:`recipients_to_self_delay`] - 1. type: 10 (`max_accepted_htlcs`) + 1. type: 4 (`max_accepted_htlcs`) 2. data: * [`u16`:`senders_max_accepted_htlcs`] - 1. type: 12 (`funding_pubkey`) + 1. type: 5 (`funding_pubkey`) 2. data: * [`point`:`senders_funding_pubkey`] - 1. type: 14 (`channel_type`) + 1. type: 6 (`channel_type`) 2. data: * [`...*byte`:`channel_type`] - 1. type: 16 (`kickoff_feerate`) + 1. type: 7 (`kickoff_feerate`) 2. data: * [`...*u32`:`kickoff_feerate_per_kw`] ##### Requirements +TODO: handle edge case where both nodes send `dyn_propose` as `initiator` + The sending node: - MUST set `channel_id` to an existing one it has with the recipient. - - MUST NOT send a `dyn_propose` if a prior one is waiting for - `dyn_propose_reply`. + - MUST NOT send a set of TLV parameters that would violate the requirements + of the identically named parameters in BOLT 2 - MUST remember its last sent `dyn_propose` parameters. - - MUST send this message as soon as both side's commitment transaction is free - of any HTLCs and both sides have sent `dyn_begin_propose`. + - if it is currently waiting for a response (`dyn_ack` or `dyn_reject`): + - MUST NOT send another `dyn_propose` + - SHOULD close the connection if it exceeds an acceptable time frame. + - if it is the `initiator`: + - MUST set `initiator` to 1 + - if it is the `responder`: + - MUST set `initiator` to 0 + - MUST NOT set the `channel_type` TLV + - MUST NOT set the `kickoff_feerate` TLV + - MUST NOT send a set of TLV parameters that would violate the requirements + of the identically named parameters in BOLT 2 **assuming** the acceptance + of the parameters it received in the `initiator`'s `dyn_propose` message. The receiving node: - if `channel_id` does not match an existing channel it has with the sender: - MUST send an `error` and close the connection. - - if it does not agree with a parameter: - - MUST send a `dyn_propose_reply` with the `reject` bit set. - - else: - - MUST send a `dyn_propose_reply` without the `reject` bit set. + - if it will not accept **any** dynamic commitment negotiation: + - MUST send a `dyn_reject` **with an empty TLV stream** + - if it does not agree with one or more parameters: + - MUST send a `dyn_reject` with the set TLV records it rejects + - if it wishes to update additional parameters as part of the *same* dynamic + commitment negotiation AND has not yet sent a `dyn_ack` message: + - MUST send a `dyn_propose` with its desired parameters + - MUST NOT send a `dyn_propose` after a `dyn_ack` for the same negotiation + - MUST send a `dyn_ack` to accept the parameters it was sent + + +TODO: go through the rest of this and make it consistent ##### Rationale @@ -346,7 +361,7 @@ The requirement for a node to remember what it last _sent_ and for it to remember what it _accepted_ is necessary to recover on reestablish. See the reestablish section for more details. -#### `dyn_propose_reply` +#### `dyn_ack` This message is sent in response to a `dyn_propose`. It may either accept or reject the `dyn_propose`. If it rejects a `dyn_propose`, it allows the @@ -395,6 +410,10 @@ The `propose_height` starts at 0 for a channel and is incremented by 1 every time the dynamic commitment proposal phase completes for a channel. See the reestablish section for why this is needed. +#### `dyn_reject` +##### Requirements +##### Rationale + ## Reestablish ### `channel_reestablish` From 4f5258aee417a020bd8a9667c51c4886aa9abadf Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Mon, 9 Oct 2023 16:38:09 -0700 Subject: [PATCH 05/23] wip, this commit is solely to save work, will squash later --- ext-dynamic-commitments.md | 150 +++++++++++++++++++++++++------------ 1 file changed, 104 insertions(+), 46 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index 8054b9af1..c87912f23 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -194,11 +194,14 @@ always be necessary, but it is certainly necessary for using this proposal to convert existing channels into Taproot channels. # Specification -There are two phases to this channel upgrade process: proposal and execution. -During the proposal phase the only goal is to agree on a set of updates to the -current channel state machine. During the execution phase, we apply the updates -to the channel state machine, exchanging the necessary information to be able -to apply those updates +There are three phases to this channel upgrade process: proposal, flushing, and +execution. During the proposal phase the only goal is to agree on a set of +updates to the current channel state machine. During the flushing phase, we +proceed with channel operation, allowing only `update_fulfill_htlc` and +`update_fail_htlc` messages until all HTLCs have been cleared, similar to +`shutdown`. During the execution phase, we apply the updates to the channel +state machine, exchanging the necessary information to be able to apply those +updates. ## Proposal Phase @@ -279,7 +282,7 @@ This message is always sent by the initiator and MAY be sent by the responder | |<-(4)------ dyn_ack -----------| | +-------+ +-------+ -1. type: 113 (`dyn_propose`) +1. type: 111 (`dyn_propose`) 2. data: * [`32*byte`:`channel_id`] * [`u8`:`initiator`] @@ -326,6 +329,9 @@ The sending node: - SHOULD close the connection if it exceeds an acceptable time frame. - if it is the `initiator`: - MUST set `initiator` to 1 + - if it sets `channel_type` and the `channel_type` conversion requires + re-anchoring (see appendix for conversions that require re-anchoring) + - MUST set `kickoff_feerate` - if it is the `responder`: - MUST set `initiator` to 0 - MUST NOT set the `channel_type` TLV @@ -337,25 +343,26 @@ The sending node: The receiving node: - if `channel_id` does not match an existing channel it has with the sender: - MUST send an `error` and close the connection. - - if it will not accept **any** dynamic commitment negotiation: - - MUST send a `dyn_reject` **with an empty TLV stream** - - if it does not agree with one or more parameters: - - MUST send a `dyn_reject` with the set TLV records it rejects - if it wishes to update additional parameters as part of the *same* dynamic commitment negotiation AND has not yet sent a `dyn_ack` message: - MUST send a `dyn_propose` with its desired parameters - MUST NOT send a `dyn_propose` after a `dyn_ack` for the same negotiation - MUST send a `dyn_ack` to accept the parameters it was sent - - -TODO: go through the rest of this and make it consistent + - MUST NOT send a `dyn_reject` ##### Rationale -The requirement to not allow trimming outputs is just to make the dynamic -commitment flow as uninvasive as possible to the commitment transaction. A -similar requirement should be added for any new parameter such as the -`channel_reserve`. +The set of parameters used in this message to renegotiate channel parameters +can't violate the invariants set out in BOLT 2. This is because we are simply +trying change channel parameters without a close event. BOLT 2 specifies +constraints on these parameters to make sure they are internally consistent and +secure in all contexts. + +Since the initiator is the one that is responsible for paying the fees for the +kickoff transaction if it is required (like for certain `channel_type` changes), +it follows that the responder cannot change the `channel_type`. Since the +`kickoff_feerate` is solely for these scenarios it follows that it should only +be set when the `channel_type` is set. The requirement for a node to remember what it last _sent_ and for it to remember what it _accepted_ is necessary to recover on reestablish. See the @@ -363,46 +370,36 @@ reestablish section for more details. #### `dyn_ack` -This message is sent in response to a `dyn_propose`. It may either accept or -reject the `dyn_propose`. If it rejects a `dyn_propose`, it allows the -counterparty to send another `dyn_propose` to try again. If for some reason, -negotiation is taking too long, it is possible to exit this phase by -reconnecting as long as the exiting node hasn't sent `dyn_propose_reply` without -the `reject` bit. +This message is sent in response to a `dyn_propose` indicating that it has +accepted the proposal. -1. type: 115 (`dyn_propose_reply`) +1. type: 113 (`dyn_ack`) 2. data: * [`32*byte`:`channel_id`] - * [`byte`: `propose_reply_flags`] - -The least-significant bit of `propose_reply_flags` is defined as the `reject` -bit. ##### Requirements The sending node: - - MUST set `channel_id` to a valid channel they have with the recipient. - - MUST set undefined bits in `propose_reply_flags` to 0. - - MUST set the `reject` bit in `propose_reply_flags` if they are rejecting the - newest `dyn_propose`. - - MUST NOT send this message if there is no outstanding `dyn_propose` from the - counterparty. - - if the `reject` bit is not set: - - MUST remember the related `dyn_propose` parameters and the local and - remote commitment heights for the next `propose_height`. + - MUST set `channel_id` to a valid channel it has with the recipient. + - MUST NOT send this message if it has not received a `dyn_propose` + - MUST NOT send this message if it has already sent a `dyn_ack` for the + current negotiation. + - MUST NOT send this message if it has already sent a `dyn_reject` for the + current negotiation. + - MUST remember the parameters of `dyn_propose` message to which the `dyn_ack` + is responding for the next `propose_height`. + - MUST remember the local and remote commitment heights for the next + `propose_height`. The receiving node: - if `channel_id` does not match an existing channel it has with the peer: - - MUST close the connection. + - MUST send an `error` and close the connection. - if there isn't an outstanding `dyn_propose` it has sent: - MUST send an `error` and fail the channel. - - if the `reject` bit was set: - - MUST forget its last sent `dyn_propose` parameters. A node: - - once it has both sent and received `dyn_propose_reply` without the `reject` - bit set: - - MUST increment their `propose_height`. + - once it has both sent and received `dyn_ack` + - MUST increment its `propose_height`. ##### Rationale @@ -411,9 +408,70 @@ time the dynamic commitment proposal phase completes for a channel. See the reestablish section for why this is needed. #### `dyn_reject` + +This message is sent in response to a `dyn_propose` indicating that it rejects +the proposal. + +1. type: 115 (`dyn_reject`) +2. data: + * [`32*byte`:`channel_id`] + +1. `tlv_stream`: `dyn_propose_tlvs` +2. types: See `dyn_propose` for TLV breakdown + ##### Requirements + +The sending node: + - MUST set `channel_id` to a valid channel it has with the recipient. + - MUST NOT send this message if it has not received a `dyn_propose` + - MUST NOT send this message if it has already sent a `dyn_ack` for the + current negotiation. + - MUST NOT send this message if it has already sent a `dyn_reject` for the + current negotiation. + - if it will not accept **any** dynamic commitment negotiation: + - SHOULD send a `dyn_reject` **with an empty TLV stream** + - if it does not agree with one or more parameters: + - MUST send a `dyn_reject` with the set TLV records it rejects + - if it has sent a `dyn_propose` in the current negotiation + - MUST forget its last sent `dyn_propose` parameters + - MUST forget the parameters of the `dyn_propose` message to which the + `dyn_reject` is responding. + +The receiving node: + - if `channel_id` does not match an existing channel it has with the peer + - MUST close the connection + - if there isn't an outstanding `dyn_propose` it has sent + - MUST send an `error` and fail the channel + - MUST forget its last sent `dyn_propose` parameters. + - if the TLV stream is empty + - SHOULD NOT re-attempt another dynamic commitment negotation for the + remaining lifecycle of the connection + - if the TLV stream is NOT empty + - MAY re-attempt another dynamic commitment negotiation + - if a dynamic commitment negotiation is re-attempted: + - SHOULD relax any parameters that were specified in the TLV stream of + the `dyn_reject` message. + - if no sensible interpretation of "relax" exists: + - SHOULD NOT re-attempt a dynamic commitment negotiation with this + parameter set. + ##### Rationale +By sending back the TLVs that a node explicitly rejects makes it easier to come +to an agreement on a proposal that will work. By not sending back any TLVs in +the `dyn_reject`, a node signals it is not interested in moving the negotiation +forward at all and further negotiation should not be attempted. + +## Flushing Phase + +TODO: describe flushing + +## Execution Phase + +TODO: incorporate prior proposal into this section + +# ORIGINAL UNINCORPORATED PROPOSAL TEXT FOLLOWS AFTER THIS POINT + ## Reestablish ### `channel_reestablish` @@ -429,8 +487,8 @@ A new TLV that denotes the node's current `propose_height` is included. #### Requirements The sending node: - - MUST set `propose_height` to the number of dynamic proposal negotiations it - has completed. The point at which it is incremented is described in the + - MUST set `propose_height` to the number of dynamic commitment negotiations + it has completed. The point at which it is incremented is described in the `dyn_propose_reply` section. The receiving node: @@ -703,7 +761,7 @@ attached to either side for fee-bumping. ##### Requirements The sending node (the fundee): - - MUST set `channel_id` to a valid channel they have with the recipient. + - MUST set `channel_id` to a valid channel it has with the recipient. - MUST NOT send this message before receiving the peer's `commitment_signed`. The receiving node (the funder): From 7e3bca7911e7b1b817ef7315d2064e9d3453ff8a Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Mon, 23 Oct 2023 09:43:14 -0700 Subject: [PATCH 06/23] wip --- ext-dynamic-commitments.md | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index c87912f23..0ec55a2e5 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -462,16 +462,6 @@ to an agreement on a proposal that will work. By not sending back any TLVs in the `dyn_reject`, a node signals it is not interested in moving the negotiation forward at all and further negotiation should not be attempted. -## Flushing Phase - -TODO: describe flushing - -## Execution Phase - -TODO: incorporate prior proposal into this section - -# ORIGINAL UNINCORPORATED PROPOSAL TEXT FOLLOWS AFTER THIS POINT - ## Reestablish ### `channel_reestablish` @@ -489,7 +479,7 @@ A new TLV that denotes the node's current `propose_height` is included. The sending node: - MUST set `propose_height` to the number of dynamic commitment negotiations it has completed. The point at which it is incremented is described in the - `dyn_propose_reply` section. + `dyn_ack` section. The receiving node: - if the received `propose_height` equals its own `propose_height`: @@ -513,15 +503,25 @@ The receiving node: #### Rationale -If both sides have sent and received `dyn_propose_reply` without the `reject` -bit before the connection closed, it is simple to continue. If one side has sent -and received `dyn_propose_reply` without the `reject` bit and the other side has -only sent `dyn_propose_reply`, the flow is recoverable on reconnection as the -side that hasn't received `dyn_propose_reply` knows that the other side accepted -their last sent `dyn_propose` based on the `propose_height` in the reestablish +If both sides have sent and received `dyn_ack` before the connection closed, it +is simple to continue. If one side has sent and received `dyn_ack` the other +side has only sent `dyn_ack`, the flow is recoverable on reconnection as the +side that hasn't received `dyn_ack` knows that the other side accepted their +last sent `dyn_propose` based on the `propose_height` in the reestablish message. -## Musig2 Taproot +## Flushing Phase + +Once the Negotiation Phase is complete, the channel enters a Flushing Phase +similar to the procedure that occurs during `shutdown`. During this phase, no +new HTLCs may be added by either party, but they may be removed, either by a +fulfill or fail operation. Once all HTLCs have been cleared from both sides of +the channel, and the `revoke_and_ack`'s have been exchanged to commit to this +empty state, we enter the execution phase. + +## Execution Phase + +### * -> Musig2 Taproot This section describes how dynamic commitments can upgrade regular channels to simple taproot channels. The regular dynamic proposal phase is executed followed @@ -529,7 +529,7 @@ by a signing phase. A `channel_type` of `option_taproot` will be included in `dyn_propose` and both sides must agree on it. The funder of the channel will also propose a set of feerates to use for an intermediate "kickoff" transaction. -### Extensions to `dyn_propose`: +#### Extensions to `dyn_propose`: 1. `tlv_stream`: `dyn_propose_tlvs` 2. types: @@ -583,9 +583,9 @@ The funder sends multiple fee-rates in order to be deal with high-fee environments. Without this, the channel may not be able to upgrade commitment types until the fee environment changes. -### Extensions to `dyn_propose_reply`: +### Extensions to `dyn_ack`: -1. `tlv_stream`: `dyn_propose_reply_tlvs` +1. `tlv_stream`: `dyn_ack_tlvs` 2. types: 1. type: 0 (`local_musig2_pubnonce`) 2. data: @@ -724,7 +724,7 @@ version 1 witness script: The new funding output has a value of the original funding output minus the sum of 660 satoshis and this kickoff transaction's fee. It is encumbered by a version 1 witness script where `taproot_funding_key1/taproot_funding_key2` are -from `dyn_propose_reply`: +from `dyn_ack`: * `OP_1 funding_key` * where: * `funding_key = combined_funding_key + tagged_hash("TapTweak", combined_funding_key)*G` @@ -832,7 +832,7 @@ section per the simple-taproot-channels proposal. The sending node: - MUST set `next_local_nonce` if the sender sees it has persisted a `channel_type` of `option_simple_taproot` from the `dyn_propose` / - `dyn_propose_reply` negotiation steps. + `dyn_ack` negotiation steps. - MUST set `num_sent_commit_sigs` to the number of `commitment_signed` it has sent for this negotiation session. - MUST set `num_recv_commit_sigs` to the number of `commitment_signed` it has From f1d7a65a8319848803a64cd9f62f7d6f44aea5b1 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Tue, 24 Oct 2023 20:39:26 -0700 Subject: [PATCH 07/23] major cleanup of document --- ext-dynamic-commitments.md | 514 +++++++++++++++++-------------------- 1 file changed, 235 insertions(+), 279 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index 0ec55a2e5..2dc0c3dbb 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -15,34 +15,35 @@ TODO ## Abstract This document describes a protocol for changing channel parameters that were negotiated at the conception of the channel. Implementation of the protocol -described in this document will enable channel peers to re-negotiate channel -terms as if the channel was being opened for the first time while avoiding UTXO -churn whenever possible and therefore preserving the continuity of identity for -the channel whose terms are being changed. +described in this document will enable channel peers to re-negotiate and update +channel terms as if the channel was being opened for the first time while +avoiding UTXO churn whenever possible and therefore preserving the continuity of +identity for the channel whose terms are being changed. ## Motivation It is well understood that closing channels is a costly thing to do. Not only is -it costly from a chain fees perspective where we pay for moving the funds from -the channel UTXO back to the main wallet collection, it is also costly from a +it costly from a chain fees perspective (where we pay for moving the funds from +the channel UTXO back to the main wallet collection), it is also costly from a service availability and reputation perspective. -After channels are closed they are no longer usable for forwarding HTLC traffic +After channels are closed, they are no longer usable for forwarding HTLC traffic and even if we were to immediately replace the channel with another equally capable one, the closure event is visible to the entire network. Since routes are computed by the source, the network-wide visibility of channel closures directly impacts whether or not the sender will be able to use a channel. -Beyond that, one of the pathfinding heuristics that is broadly used to assess -channel reliability is the length of time a channel has existed. The longevity -of a channel is therefore a key asset that any running Lightning node should -want to preserve, if possible. +Beyond that, one of the pathfinding heuristics that is frequently used to assess +channel reliability is the channel age. The longevity of a channel is therefore +a key asset that any running Lightning node should want to preserve, if +possible. It follows from the above that we should try to minimize channel closure events -when we can manage to do so. This motivates part of this proposal. Prior to this -extension BOLT, there is no way to change some of the channel parameters -established in the `{open|accept}_channel` messages without resorting to a full -channel closure. This limitation can be remediated by introducing a protocol to -renegotiate these parameters. +when we can manage to do so. This is the main motivation of this proposal. Prior +to this extension BOLT, there hasn't been a way to change some of the channel +parameters established in the `{open|accept}_channel` messages without resorting +to a full channel closure, even if the channel counterparty consents. This +limitation can be remediated by introducing a protocol to renegotiate these +parameters. Notable in particular is that one of the channel parameters that we wish to renegotiate is the the `channel_type` itself. With the advent of Simple Taproot @@ -51,25 +52,25 @@ and privacy capabilities afforded by the 2021 Taproot Soft Fork. With further aspirations to be able to deploy Point Time-Lock Contracts (PTLCs) to the Lightning Network, the sooner that network participants can upgrade to STCs the more we will have the necessary network infrastructure to be able to make -effective use of PTLCs when the protocols for them are specified. +effective use of PTLCs when the protocols for them are ready for deployment. -Due to the design of STCs and the fact that they take full advantage of the +Due to the design of STCs, and the fact that they take full advantage of the capabilities afforded by Schnorr Signatures, there is no way to construct a valid `channel_announcement` message that references the output corresponding to the nodes' joint public key. As such, even if we were to directly spend an -existing channel point to a new STC channel point, and even with the provision +existing funding output to a new STC funding output, and even with the provision in BOLT 7 to delay graph pruning by 12 blocks after the channel point is spent, we have no way to make the STC known to the network at the time of writing of this proposal. -Concurrently with this proposal is a proposal for a new gossip system that is -capable of understanding the announcements of new STCs. However, even with a new -gossip system capable of understanding the STC construction and announcement, -it will take quite some time for such a system to be broadly deployed across the -Lightning Network. In the interim, to remove this disincentive of these channel -upgrades to the involved parties, this proposal to enable the change of these -channel parameters (including channel types) without requiring channel closure -and reopening is submitted. +Concurrent with th writing of this proposal is another proposal for a new gossip +system that is capable of understanding the announcements of new STCs. However, +even with a new gossip system capable of understanding the STC construction and +announcement, it will take quite some time for such a system to be broadly +deployed across the Lightning Network. In the interim, to remove this +disincentive of these channel upgrades to the involved parties, this proposal to +enable the change of these channel parameters (including channel types) without +requiring channel turnover is submitted. ## Preliminaries This proposal includes a detailed section on the preliminaries to document some @@ -114,7 +115,6 @@ remaining after we filter out these values is thus: - to_self_delay - max_accepted_htlcs - funding_pubkey -- channel_flags - channel_type The design presented later is intended to allow for arbitrary changes to these @@ -313,7 +313,7 @@ This message is always sent by the initiator and MAY be sent by the responder * [`...*byte`:`channel_type`] 1. type: 7 (`kickoff_feerate`) 2. data: - * [`...*u32`:`kickoff_feerate_per_kw`] + * [`u32`:`kickoff_feerate_per_kw`] ##### Requirements @@ -470,33 +470,33 @@ A new TLV that denotes the node's current `propose_height` is included. 1. `tlv_stream`: `channel_reestablish_tlvs` 2. types: - 1. type: 20 (`propose_height`) + 1. type: 20 (`dyn_height`) 2. data: - * [`u64`:`propose_height`] + * [`u64`:`dyn_height`] #### Requirements The sending node: - - MUST set `propose_height` to the number of dynamic commitment negotiations + - MUST set `dyn_height` to the number of dynamic commitment negotiations it has completed. The point at which it is incremented is described in the `dyn_ack` section. The receiving node: - - if the received `propose_height` equals its own `propose_height`: + - if the received `dyn_height` equals its own `dyn_height`: - MUST forget any stored proposal state for `propose_height`+1 in case negotiation didn't complete. Can continue using the channel. - SHOULD forget any state that is unnecessary for heights <= `propose_height`. - - if the received `propose_height` is 1 greater than its own `propose_height`: + - if the received `dyn_height` is 1 greater than its own `dyn_height`: - if it does not have any remote parameters stored for the received - `propose_height`: + `dyn_height`: - MUST send an `error` and fail the channel. The remote node is either - lying about the `propose_height` or the recipient has lost data since + lying about the `dyn_height` or the recipient has lost data since its not possible to advance the height without the recipient storing the remote's parameters. - resume using the channel with its last-sent `dyn_propose` and the stored `dyn_propose` parameters and increment its `propose_height`. - - if the received `propose_height` is 1 less than its own `propose_height`: + - if the received `dyn_height` is 1 less than its own `dyn_height`: - resume using the channel with the new parameters. - else: - MUST send an `error` and fail the channel. State was lost. @@ -507,7 +507,7 @@ If both sides have sent and received `dyn_ack` before the connection closed, it is simple to continue. If one side has sent and received `dyn_ack` the other side has only sent `dyn_ack`, the flow is recoverable on reconnection as the side that hasn't received `dyn_ack` knows that the other side accepted their -last sent `dyn_propose` based on the `propose_height` in the reestablish +last sent `dyn_propose` based on the `dyn_height` in the reestablish message. ## Flushing Phase @@ -516,186 +516,94 @@ Once the Negotiation Phase is complete, the channel enters a Flushing Phase similar to the procedure that occurs during `shutdown`. During this phase, no new HTLCs may be added by either party, but they may be removed, either by a fulfill or fail operation. Once all HTLCs have been cleared from both sides of -the channel, and the `revoke_and_ack`'s have been exchanged to commit to this -empty state, we enter the execution phase. +the channel, and both channel parties have irrevocably committed to this empty +state, we enter the execution phase. ## Execution Phase -### * -> Musig2 Taproot - -This section describes how dynamic commitments can upgrade regular channels to -simple taproot channels. The regular dynamic proposal phase is executed followed -by a signing phase. A `channel_type` of `option_taproot` will be included in -`dyn_propose` and both sides must agree on it. The funder of the channel will -also propose a set of feerates to use for an intermediate "kickoff" transaction. - -#### Extensions to `dyn_propose`: - -1. `tlv_stream`: `dyn_propose_tlvs` -2. types: - 1. type: 2 (`channel_type`) - 2. data: - * [`...*byte`:`type`] - 1. type: 4 (`kickoff_feerates`) - 2. data: - * [`...u32`:`kickoff_feerate_per_kw`] - 1. type: 6 (`taproot_funding_key`) - 2. data: - * [`point`:`funding_key`] - -#### Requirements - -The sending node: - - if it is the funder: - - MUST only send `kickoff_feerate` if they can pay for each kickoff - transaction fee and the anchor outputs, while adhering to the - `channel_reserve` restriction. - - MUST set `taproot_funding_key` to a valid secp256k1 compressed public key. - - SHOULD use a sufficient number of `kickoff_feerates` to be prepared for - worst-case fee environment scenarios. - -The receiving node: - - if it is the fundee: - - MUST reject the `dyn_propose` if the funder cannot pay for each kickoff - transaction fee and the anchor outputs. - - MUST reject the `dyn_propose` if, after calculating the amount of the new - funding output, the new commmitment transaction would not be able to pay - for any outputs at the current commitment feerate. - - MUST reject the `dyn_propose` if `taproot_funding_key` is not a valid - secp256k1 compressed public key. - - MAY reject the `dyn_propose` if it does not agree with the `channel_type` - - MAY reject the `dyn_propose` if there are too many `kickoff_feerates` such - that it would be a burden to track the potential confirmation of each - kickoff and commitment transaction pair. - -#### Rationale - -The `dyn_propose` renegotiates the funding keys as otherwise signatures for the -funding keys would be exchanged in both the ECDSA and Schnorr contexts. This can -lead to an attack outlined in [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#alternative-signing). -Renegotiating funding keys avoids this issue. Note that the various basepoints -exchanged in `open_channel` and `accept_channel` are not renegotiated. Because -the private keys _change_ with each commitment transaction they sign due to the -`per_commitment_point` construction, the basepoints can be used in both ECDSA -and Schnorr contexts. - -The funder sends multiple fee-rates in order to be deal with high-fee -environments. Without this, the channel may not be able to upgrade commitment -types until the fee environment changes. - -### Extensions to `dyn_ack`: - -1. `tlv_stream`: `dyn_ack_tlvs` -2. types: - 1. type: 0 (`local_musig2_pubnonce`) - 2. data: - * [`66*byte`:`nonces`] - -#### Requirements - -The sending node: - - if it is accepting a `channel_type` of `simple_taproot_channel`: - - MUST set `local_musig2_pubnonce` to the nonce that it will use to verify - local commitments. - -The receiving node: - - MUST send an `error` and fail the channel if `local_musig2_pubnonce` cannot - be parsed as two compressed secp256k1 points. - -### Signing Phase - -The signing phase is after the negotiation phase. The original funding output -spends to an intermediate transaction that pays to a v1 witness script with an -aggregated musig2 key derived from both parties `taproot_funding_key` sent in -`dyn_propose`. As in the simple-taproot-channels proposal, the -`commitment_signed`, `revoke_and_ack`, and `channel_reestablish` messages -include nonces. - -#### Commitment Transaction - -* version: 2 -* locktime: upper 8 bits are 0x20, lower 24 bits are the lower 24 bits of the - obscured commitment number -* txin count: 1 - * `txin[0]` outpoint: the matching kickoff transaction's musig2 funding - outpoint. - * `txin[0]` sequence: upper 8 bits are 0x80, lower 24 bits are upper 24 bits - of the obscured commitment number - * `txin[0]` script bytes: 0 - * `txin[0]` witness: `` - -The 48-bit commitment number is computed by `XOR` as described in BOLT#03. - -#### Commitment Transaction Construction +There are three fundamental types of execution paths following the flushing of +HTLCs: +1. Rules Change - No additional state change is required, the next state will be +expected to follow the new rules that have been negotiated. Channel may resume +normal operation. +2. Commitment Update - Additional state change is required, a new commitment +transaction is expected to be exchanged at this point, following the expected +parameters. Nodes will exchange `commitment_signed` and `revoke_and_ack`s for +transactions agreeing to the new rules, then Channel may resume normal +operation. +3. Funding Output Update - Signatures for a transaction that spends the original +funding output into a new funding output will be exchanged. + - NOTE FOR REVIEWERS: This transaction is currently symmetric which burdens + us with the constraint that a reanchoring step can only be done once over the + lifetime of the channel. If we want to be able to securely do this multiple + times, we must make kickoff transactions revocable, and therefore asymmetric, + and therefore must start issuing commitment signatures in pairs. See Appendix + for details. + +For execution we try and have the smallest execution overhead. The option that +is selected from the list above will be the one with the highest number that is +triggered by the below rules: Funding Output Update > Commitment Update > Rules +Change. + +- If either channel party changes `dust_limit_satoshis`: Rules Change +- If either channel party changes `channel_reserve_satoshis`: Rules Change +- If either channel party changes `to_self_delay`: Commitment Update +- If either channel party changes `max_accepted_htlcs`: Rules Change +- If either channel party changes `funding_pubkey`: Funding Output Update +- If new `channel_type` requires different funding output script than the old +`channel_type`: Funding Output Update + +### Rules Change + +If all that is required to execute the terms of the dynamic commitment +negotiation is a rules change, then channel operation may resume as normal +under the new rules as soon as both channel parties have irrevocably committed +to a state with no HTLC outputs. + +### Commitment Update + +If an update to the commitment transaction is required to execute the terms of +the dynamic commitment negotiation, then once both channel parties have +irrevocably committed to a state with no HTLC outputs, new commitment signatures +MUST be exchanged. This requires both parties to send `commitment_signed` +messages that adhere to the new channel parameters. Once a node has received +a `commitment_signed` in accordance with the new channel parameters, it MUST +issue a `revoke_and_ack` as it normally would. Once both nodes have done this, +normal channel operation is resumed. + +### Funding Output Change: General Protocol + +If a Funding Output Change is required, then once both channel parties have +irrevocably committed to a state with no HTLC outputs, new commitment signatures +AND kickoff signatures MUST be exchanged. To accomplish this the following steps +are taken: + +1. Build kickoff transaction +1. Build commitment transaction that spends kickoff output +1. Issue a `commitment_signed` message _according to new channel parameters_ +1. Upon receipt of the remote party's `commitment_signed` message, issue a +`kickoff_sig` message. +1. Upon receipt of the remote party's `kickoff_sig` message, issue a +`revoke_and_ack` for the _final commitment_ built off of the _original funding +output_. -1. Initialize the commitment transaction version and locktime. -2. Initialize the commitment transaction input. -3. Calculate which committed HTLCs need to be trimmed. -4. Calculate the commitment transaction fee via - commitment feerate * `commitment_transaction_weight`/1000, making sure to - round down. Subtract this from the funder's output. -5. Subtract four times the fixed anchor size of 330 satoshis from the funder's - output. Two of the anchors are from the commitment transaction and two are - from the kickoff transaction. -6. Subtract the matching kickoff transaction's fee from the funder's output. -7. For every offered HTLC, if it is not trimmed, add an offered HTLC output. -8. For every received HTLC, if it is not trimmed, add a received HTLC output. -9. If the `to_local` output is greater or equal to the dust limit, add a - `to_local` output. -10. If the `to_remote` output is greater or equal to the dust limit, add a - `to_remote` output. -11. If `to_local` exists or there are untrimmed HTLCs, add a `to_local_anchor`. -12. If `to_remote` exists or there are untrimmed HTLCs, add a - `to_remote_anchor`. The `to_remote_anchor` uses the remote party's - `taproot_funding_key`. -13. Sort the outputs into BIP 69+CLTV order. - -#### commitment_signed - -The `commitment_signed` message does not change, but adds a nonce in the TLV -section per the simple-taproot-channels proposal. It changes what it signs in -the following ways: - -1. `tlv_stream`: `commit_sig_tlvs` -2. types: - 1. type: 2 (`partial_signature_with_nonce`) - 2. data: - * [`98*byte`:`partial_signature || public_nonce`] - 1. type: 4 (`local_musig2_pubnonce`) - 2. data: - * [`66*byte`: `nonces`] - -##### Requirements - -The sending node: - - MUST NOT increment the commitment number when signing. - - MUST sign for any negotiated parameters that modified the commitment - transaction (e.g. `to_self_delay`). - -The receiving node: - - MUST send an `error` and fail the channel if the signature does not sign the - commitment transaction as constructed above. - - MUST send an `error` and fail the channel if `partial_signature` is not a - valid Schnorr signature. - - MUST send an `error` and fail the channel if `public_nonce` cannot be parsed - as two compressed secp256k1 points. - - MUST send an `error` and fail the chanel if `local_musig2_pubnonce` cannot - be parsed as two compressed secp256k1 points. +#### Message flow to upgrade a channel to simple-taproot: -##### Rationale + +-------+ +-------+ + | |--(1)---- commit_signed------->| | + | |<-(2)---- commit_signed -------| | + | |<-(3)----- kickoff_sig --------| | + | A |--(4)----- kickoff_sig ------->| B | + | | | | + | |--(5)---- revoke_and_ack ----->| | + | |<-(6)---- revoke_and_ack ------| | + +-------+ +-------+ -The commitment number is not incremented while signing because if there are N -kickoff transactions and the N-2 kickoff transaction confirms, then -implementations will need to rewind their commitment number to N-2. We avoid -this complexity by keeping the commitment numbers static until the signing phase -is complete. +TODO: Explain why this message ordering is important -A set of local nonces is included because each signed commitment transaction -shares the same commitment number as the pre-dynamic-commitment commitment -transaction. For this reason, `revoke_and_ack` is omitted and thus local nonces -need to be sent in `commitment_signed`. +#### Building the Kickoff Transaction -#### Kickoff Transaction(s) +##### Kickoff Transaction Structure * version: 2 * locktime: 0 @@ -730,7 +638,7 @@ from `dyn_ack`: * `funding_key = combined_funding_key + tagged_hash("TapTweak", combined_funding_key)*G` * `combined_funding_key = musig2.KeyAgg(musig2.KeySort(taproot_funding_key1, taproot_funding_key2))` -#### Kickoff Transaction Construction +##### Kickoff Transaction Construction Algorithm 1. Initialize the commitment transaction version and locktime. 2. Initialize the commitment transaction input. @@ -743,7 +651,56 @@ from `dyn_ack`: 7. Add an anchor output for each party. 8. Sort the outputs into BIP 69+CLTV order. -#### kickoff_sig +#### Building the Commitment Transaction + +##### Commitment Transaction Structure + +* version: 2 +* locktime: upper 8 bits are 0x20, lower 24 bits are the lower 24 bits of the + obscured commitment number +* txin count: 1 + * `txin[0]` outpoint: the matching kickoff transaction's funding outpoint. + * `txin[0]` sequence: upper 8 bits are 0x80, lower 24 bits are upper 24 bits + of the obscured commitment number + * `txin[0]` script bytes: 0 + * `txin[0]` witness: `` + +The 48-bit commitment number is computed by `XOR` as described in BOLT#03. + +##### Commitment Transaction Construction Algorithm + +1. Initialize the commitment transaction version and locktime. +2. Initialize the commitment transaction input. +3. Calculate which committed HTLCs need to be trimmed. +4. Calculate the commitment transaction fee via + commitment feerate * `commitment_transaction_weight`/1000, making sure to + round down. Subtract this from the funder's output. +5. Subtract four times the fixed anchor size of 330 satoshis from the funder's + output. Two of the anchors are from the commitment transaction and two are + from the kickoff transaction. +6. Subtract the matching kickoff transaction's fee from the funder's output. +7. For every offered HTLC, if it is not trimmed, add an offered HTLC output. +8. For every received HTLC, if it is not trimmed, add a received HTLC output. +9. If the `to_local` output is greater or equal to the dust limit, add a + `to_local` output. +10. If the `to_remote` output is greater or equal to the dust limit, add a + `to_remote` output. +11. If `to_local` exists or there are untrimmed HTLCs, add a `to_local_anchor`. +12. If `to_remote` exists or there are untrimmed HTLCs, add a + `to_remote_anchor`. The `to_remote_anchor` uses the remote party's + `taproot_funding_key`. +13. Sort the outputs into BIP 69+CLTV order. + +#### Issuing the `commitment_signed` message + +Commitment signed messages are exchanged as normal with the exception of a +different construction procedure detailed in the prior step. NOTE: "as normal" +means that this message MUST include all TLVs that would be required for +the updated `channel_type` e.g. Musig2 Taproot. + +#### Issuing the `kickoff_sig` message + +##### kickoff_sig The kickoff_sig is a message containing a signature that the fundee sends to the funder who then combines it with their own signature to spend from the original @@ -781,86 +738,85 @@ Even though only the funder is able to broadcast the kickoff transaction, we include anchors such that the fundee can broadcast fee-bumping transactions if they notice any of the kickoff transactions in the mempool. -#### Message flow to upgrade a channel to simple-taproot: +### Additional Requirements: ~Musig2 Taproot -> Musig2 Taproot - +-------+ +-------+ - | |--(1)---- commit_signed------->| | - | | | | - | A |<-(2)---- commit_signed -------| B | - | |<-(3)----- kickoff_sig --------| | - | | | | - | |--(4)---- commit_signed------->| | - | |<-(5)---- commit_signed -------| | - | |<-(6)----- kickoff_sig --------| | - +-------+ +-------+ +This section describes how dynamic commitments can upgrade regular channels to +simple taproot channels. The regular dynamic proposal phase is executed followed +by a signing phase. A `channel_type` of `option_taproot` will be included in +`dyn_propose` and both sides must agree on it. The initiator of the upgrade will +also propose a feerate to use for an intermediate "kickoff" transaction. -The above message ordering is important. If `kickoff_sig` is sent before -`commit_sig`, a griefing attack is possible: +#### Extensions to `dyn_propose`: - +-------+ +-------+ - | A |<-(1)----- kickoff_sig --------| B | - +-------+ +-------+ +1. `tlv_stream`: `dyn_propose_tlvs` +2. types: + 1. type: 5 (`funding_pubkey`) + 2. data: + * [`point`:`senders_funding_pubkey`] + 1. type: 6 (`channel_type`) + 2. data: + * [`...*byte`:`type`] + 1. type: 7 (`kickoff_feerate`) + 2. data: + * [`u32`:`kickoff_feerate_per_kw`] -Here, A stops sending messages and instead immediately broadcasts the kickoff -transaction. Since neither side has exchanged `commitment_signed`, the new -funding output is unclaimable and is effectively burned. The majority of the -channel could be in B's outputs, making the loss of funds disproportionately on -B's side. +#### Requirements + +The sending node: + - if it is the initiator: + - MUST only send `kickoff_feerate` if they can pay for the kickoff + transaction fee and the anchor outputs, while adhering to the + `channel_reserve` restriction. + - MUST set `taproot_funding_key` to a valid secp256k1 compressed public key. + - SHOULD use a sufficiently high `kickoff_feerate` to be prepared for + worst-case fee environment scenarios. + - *NOTE FOR REVIEWERS*: We can also add a message to update the kickoff fee + rate if we have revocable kickoffs, similar to `update_fee` for commitment + transactions to make sure the kickoff has a sufficient fee to enter the + mempool. Anchors can be used to fee bump the kickoff beyond the min mempool + fee. Revocable kickoffs are possible but significantly increase the design + complexity. + +The receiving node: + - if it is the responder: + - MUST reject the `dyn_propose` if the initiator cannot pay for the kickoff + transaction fee and the anchor outputs. + - MUST reject the `dyn_propose` if, after calculating the amount of the new + funding output, the new commmitment transaction would not be able to pay + for any outputs at the current commitment feerate. + - MUST reject the `dyn_propose` if `taproot_funding_key` is not a valid + secp256k1 compressed public key. + - MAY reject the `dyn_propose` if it does not agree with the `channel_type` -### Reestablish during simple-taproot upgrade +#### Rationale -#### channel_reestablish +The `dyn_propose` renegotiates the funding keys as otherwise signatures for the +funding keys would be exchanged in both the ECDSA and Schnorr contexts. This can +lead to an attack outlined in [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#alternative-signing). +Renegotiating funding keys avoids this issue. Note that the various basepoints +exchanged in `open_channel` and `accept_channel` are not renegotiated. Because +the private keys _change_ with each commitment transaction they sign due to the +`per_commitment_point` construction, the basepoints can be used in both ECDSA +and Schnorr contexts. -The `channel_reestablish` message does not change, but adds a nonce in the TLV -section per the simple-taproot-channels proposal. +#### Extensions to `dyn_ack`: -1. `tlv_stream`: `channel_reestablish_tlvs` +1. `tlv_stream`: `dyn_ack_tlvs` 2. types: - 1. type: 4 (`next_local_nonce`) - 2. data: - * [`66*byte`:`public_nonce`] - 1. type: 6 (`num_sent_commit_sigs`) - 2. data: - * [`u16`:`num_sigs`] - 1. type: 8 (`num_recv_commit_sigs`) - 2. data: - * [`u16`:`num_sigs`] - 1. type: 10 (`num_kickoff_sigs`) + 1. type: 0 (`local_musig2_pubnonce`) 2. data: - * [`u16`:`num_sigs`] + * [`66*byte`:`nonces`] + +#### Requirements The sending node: - - MUST set `next_local_nonce` if the sender sees it has persisted a - `channel_type` of `option_simple_taproot` from the `dyn_propose` / - `dyn_ack` negotiation steps. - - MUST set `num_sent_commit_sigs` to the number of `commitment_signed` it has - sent for this negotiation session. - - MUST set `num_recv_commit_sigs` to the number of `commitment_signed` it has - received for this negotiation session. - - if it is the funder: - - MUST set `num_kickoff_sigs` to the number of `kickoff_sig` messages it has - received. - - otherwise (it is the fundee): - - MUST set `num_kickoff_sigs` to the number of `kickoff_sig` messages it has - sent. + - if it is accepting a `channel_type` of `simple_taproot_channel`: + - MUST set `local_musig2_pubnonce` to the nonce that it will use to verify + local commitments. The receiving node: - - MUST send an `error` and fail the channel if `next_local_nonce` cannot be - parsed as two compressed secp256k1 points. - - if its sent `num_sent_commit_sigs` is one greater than the received - `num_recv_commit_sigs`: - - MUST retransmit the missing `commitment_signed`. - - if it is the fundee: - - if its sent `num_kickoff_sigs` is one greater than the received - `num_kickoff_sigs`: - - MUST retransmit the missing `kickoff_sig`. - - if messages were retransmitted: - - MUST continue with the rest of the signing flow until a `kickoff_sig` has - been sent for each fee-rate in `kickoff_feerates`. - -The signing phase is complete when the funder's sent `num_kickoff_sigs` is equal -to the fundee's sent `num_kickoff_sigs` and is also equal to the number of -fee-rates in `kickoff_feerates` from the persisted `dyn_propose` parameters. + - MUST send an `error` and fail the channel if `local_musig2_pubnonce` cannot + be parsed as two compressed secp256k1 points. # Appendix @@ -1062,7 +1018,7 @@ transactions. - to_local_anchor: 43 bytes - to_remote_anchor: 43 bytes - lock_time: 4 bytes - + - Multiplying non-witness data by 4 gives a weight of: - commitment_transaction_weight = 223vbytes * 4 = 892WU - Adding the witness data: From bbd4c0c7731662a97a84f78a2a30baf8b0615b51 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Wed, 25 Oct 2023 14:45:23 -0700 Subject: [PATCH 08/23] minor cleanup of document --- ext-dynamic-commitments.md | 152 +++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 75 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index 2dc0c3dbb..63366d5f8 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -42,15 +42,15 @@ when we can manage to do so. This is the main motivation of this proposal. Prior to this extension BOLT, there hasn't been a way to change some of the channel parameters established in the `{open|accept}_channel` messages without resorting to a full channel closure, even if the channel counterparty consents. This -limitation can be remediated by introducing a protocol to renegotiate these +limitation can be remediated by introducing a protocol to renegotiate these parameters. -Notable in particular is that one of the channel parameters that we wish to +Notable in particular, is that one of the channel parameters we wish to renegotiate is the the `channel_type` itself. With the advent of Simple Taproot Channels (STCs), we have the opportunity to take advantage of the cost savings -and privacy capabilities afforded by the 2021 Taproot Soft Fork. With further +and privacy capabilities afforded by the 2021 Taproot Soft Fork, with further aspirations to be able to deploy Point Time-Lock Contracts (PTLCs) to the -Lightning Network, the sooner that network participants can upgrade to STCs the +Lightning Network. The sooner that network participants can upgrade to STCs the more we will have the necessary network infrastructure to be able to make effective use of PTLCs when the protocols for them are ready for deployment. @@ -60,17 +60,17 @@ valid `channel_announcement` message that references the output corresponding to the nodes' joint public key. As such, even if we were to directly spend an existing funding output to a new STC funding output, and even with the provision in BOLT 7 to delay graph pruning by 12 blocks after the channel point is spent, -we have no way to make the STC known to the network at the time of writing of +we have no way of advertising the STC to the network at the time of writing of this proposal. -Concurrent with th writing of this proposal is another proposal for a new gossip +That said, there is a development effort, concurrent with this, for a new gossip system that is capable of understanding the announcements of new STCs. However, even with a new gossip system capable of understanding the STC construction and announcement, it will take quite some time for such a system to be broadly -deployed across the Lightning Network. In the interim, to remove this -disincentive of these channel upgrades to the involved parties, this proposal to -enable the change of these channel parameters (including channel types) without -requiring channel turnover is submitted. +deployed across the Lightning Network. In the interim, to combat the +disincentive of upgrading to STCs, this proposal to enable the change of these +channel parameters (including channel types) without requiring channel turnover +is submitted. ## Preliminaries This proposal includes a detailed section on the preliminaries to document some @@ -117,7 +117,7 @@ remaining after we filter out these values is thus: - funding_pubkey - channel_type -The design presented later is intended to allow for arbitrary changes to these +The design presented here is intended to allow for arbitrary changes to these values that currently have no facilities for change in any other way. ### Gossip Verification @@ -129,16 +129,17 @@ ownership of the UTXO and its viability as a routing edge for payment senders. BOLT 7 details all of the specifics of this message and how it is computed but one of the notable aspects of this process is that the receivers of these gossip -messages verify that the UTXO being referenced for underwriting the existence of -a channel must be a P2WSH output with a pre-defined script using the -participants' public keys, specified in BOLT 3. This will present issues for us -which will become clearer in the next section. - -While alternative Gossip systems are being designed, they have not been deployed -in any known implementation of the Lightning Network Protocol and even if they -were there will be a prolonged period of time wherein some nodes on the network -will remain unable to process messages of this variety, rendering useless any -channels that are confined to being announced in this manner. +messages verify that the UTXO underwriting the channel must be a P2WSH output +with a pre-defined script using the participants' public keys, specified in +BOLT 3. This will present issues for us which will become clearer in the next +section. + +While alternative gossip systems that can describe STCs are being designed, they +have not been deployed in any known implementation of the Lightning Network +Protocol and even when such a design is implemented, there will be a prolonged +period of time wherein a substantial number of nodes on the network will remain +unable to process messages of this variety, rendering useless any channels that +can only be announced in this manner. ### Taproot This brings us to talking about what channel constructions are actually @@ -157,14 +158,11 @@ it is for our purposes here. While Taproot channels are useful, they present some novel challenges with respect to network-wide interoperability. Notably, a useful Taproot channel construction must by definition make use of the new Taproot output type, which -is not and cannot be a P2WSH output of the format detailed in BOLT 3 for the +does not and cannot use the output script format detailed in BOLT 3 for the funding output. Pairing this fact with what we described in the previous section, it is necessarily the case that the funding output of a Taproot channel cannot be properly announced by the current gossip system. -With this background out of the way we are finally fully primed to understand -the nuances of the design. - ## Design Overview The main goal of this proposal is to be able to change all of the historically "static" channel parameters, including the channel type, which includes channels @@ -174,17 +172,16 @@ channel. This is a tall order. Most of these parameters can be changed by simply expressing the desire to change them, and should the recipient agree, we apply these changes, and -exchange new commitment transactions making any necessary adjustments implied +exchange new commitment transactions making any necessary adjustments prescribed by the channel parameter changes. The exception to this is certain changes to the channel type. As detailed in the preliminaries, the funding output of a Taproot transaction is fundamentally different from the funding output of the other channel types that are currently -defined. This means that we conceptually must spend the funding output of the -original channel into a new Taproot output before we have a functioning Taproot -channel. +defined. This means that we must spend the funding output of the original +channel into a new Taproot output before we have a functioning Taproot channel. -The key insight in this design is that we extend the conception of a commitment +The key insight in this design is that we extend the concept of a commitment transaction to include the possibility of a pair of transactions wherein we have a "kickoff transaction" that is comprised of a single input (the original funding output) and a single output (the new funding output) and then building @@ -196,12 +193,13 @@ convert existing channels into Taproot channels. # Specification There are three phases to this channel upgrade process: proposal, flushing, and execution. During the proposal phase the only goal is to agree on a set of -updates to the current channel state machine. During the flushing phase, we +updates to the current channel state machine. Assuming an agreement can be +reached, we will proceed to the other two phases. During the flushing phase, we proceed with channel operation, allowing only `update_fulfill_htlc` and `update_fail_htlc` messages until all HTLCs have been cleared, similar to -`shutdown`. During the execution phase, we apply the updates to the channel -state machine, exchanging the necessary information to be able to apply those -updates. +`shutdown`. Finally, during the execution phase, we apply the updates to the +channel state machine, exchanging the necessary information to be able to apply +those updates. ## Proposal Phase @@ -211,7 +209,7 @@ In every dynamic commitment negotiation, there are two roles: the `initiator` and the `responder`. It is necessary for both nodes to agree on which node is the `initiator` and which node is the `responder`. This is important because if the dynamic commitment negotiation results in a re-anchoring step (described -later), it is the initiator that is responsible for paying the fees for the +later), it is the `initiator` that is responsible for paying the fees for the kickoff transaction. ### Negotiation TLVs @@ -268,9 +266,9 @@ change as well as accept or reject the proposal made by their counterparty. #### `dyn_propose` -This message is sent to initiate the negotiation of a dynamic commitment +This message is sent to negotiate the parameters of a dynamic commitment upgrade. The overall protocol flow looks similar to what is depicted below. -This message is always sent by the initiator and MAY be sent by the responder +This message is always sent by the `initiator` and the `responder`. +-------+ +-------+ | |--(1)---- dyn_propose -------->| | @@ -322,7 +320,7 @@ TODO: handle edge case where both nodes send `dyn_propose` as `initiator` The sending node: - MUST set `channel_id` to an existing one it has with the recipient. - MUST NOT send a set of TLV parameters that would violate the requirements - of the identically named parameters in BOLT 2 + of the identically named parameters in BOLT 2 or associated extensions. - MUST remember its last sent `dyn_propose` parameters. - if it is currently waiting for a response (`dyn_ack` or `dyn_reject`): - MUST NOT send another `dyn_propose` @@ -334,7 +332,8 @@ The sending node: - MUST set `kickoff_feerate` - if it is the `responder`: - MUST set `initiator` to 0 - - MUST NOT set the `channel_type` TLV + - MUST set the `channel_type` TLV to the same value as the one sent by the + `initiator` - MUST NOT set the `kickoff_feerate` TLV - MUST NOT send a set of TLV parameters that would violate the requirements of the identically named parameters in BOLT 2 **assuming** the acceptance @@ -342,7 +341,7 @@ The sending node: The receiving node: - if `channel_id` does not match an existing channel it has with the sender: - - MUST send an `error` and close the connection. + - SHOULD send an `error` and close the connection. - if it wishes to update additional parameters as part of the *same* dynamic commitment negotiation AND has not yet sent a `dyn_ack` message: - MUST send a `dyn_propose` with its desired parameters @@ -350,6 +349,10 @@ The receiving node: - MUST send a `dyn_ack` to accept the parameters it was sent - MUST NOT send a `dyn_reject` +_NOTE FOR REVIEWERS_: These messages all interact with each other, so feedback +is welcome for how to restructure this section so that the invariants it +prescribes are found in the most intuitive place. + ##### Rationale The set of parameters used in this message to renegotiate channel parameters @@ -358,11 +361,11 @@ trying change channel parameters without a close event. BOLT 2 specifies constraints on these parameters to make sure they are internally consistent and secure in all contexts. -Since the initiator is the one that is responsible for paying the fees for the +Since the `initiator` is the one that is responsible for paying the fees for the kickoff transaction if it is required (like for certain `channel_type` changes), -it follows that the responder cannot change the `channel_type`. Since the -`kickoff_feerate` is solely for these scenarios it follows that it should only -be set when the `channel_type` is set. +it follows that the `responder` cannot change the `channel_type`. Since the +`kickoff_feerate` paid by the `initiator`, it should be set only if the sender +is the `initiator`. The requirement for a node to remember what it last _sent_ and for it to remember what it _accepted_ is necessary to recover on reestablish. See the @@ -517,7 +520,7 @@ similar to the procedure that occurs during `shutdown`. During this phase, no new HTLCs may be added by either party, but they may be removed, either by a fulfill or fail operation. Once all HTLCs have been cleared from both sides of the channel, and both channel parties have irrevocably committed to this empty -state, we enter the execution phase. +state, we enter the Execution Phase. ## Execution Phase @@ -591,11 +594,17 @@ output_. +-------+ +-------+ | |--(1)---- commit_signed------->| | + | | | | | |<-(2)---- commit_signed -------| | + | | | | + | | | | | |<-(3)----- kickoff_sig --------| | - | A |--(4)----- kickoff_sig ------->| B | + | A | | B | + | |--(4)----- kickoff_sig ------->| | + | | | | | | | | | |--(5)---- revoke_and_ack ----->| | + | | | | | |<-(6)---- revoke_and_ack ------| | +-------+ +-------+ @@ -730,23 +739,17 @@ The receiving node (the funder): ##### Rationale -To avoid the fundee griefing the funder by broadcasting the highest-fee kickoff -transaction, only the fundee sends `kickoff_sig`. This ensures that only the -funder can broadcast the kickoff transaction. - -Even though only the funder is able to broadcast the kickoff transaction, we -include anchors such that the fundee can broadcast fee-bumping transactions if -they notice any of the kickoff transactions in the mempool. +TODO: Explain some shit here ### Additional Requirements: ~Musig2 Taproot -> Musig2 Taproot This section describes how dynamic commitments can upgrade regular channels to simple taproot channels. The regular dynamic proposal phase is executed followed by a signing phase. A `channel_type` of `option_taproot` will be included in -`dyn_propose` and both sides must agree on it. The initiator of the upgrade will -also propose a feerate to use for an intermediate "kickoff" transaction. +`dyn_propose` and both sides must agree on it. The `initiator` of the upgrade +will also propose a feerate to use for an intermediate "kickoff" transaction. -#### Extensions to `dyn_propose`: +#### Required `dyn_propose` TLVs: 1. `tlv_stream`: `dyn_propose_tlvs` 2. types: @@ -763,7 +766,7 @@ also propose a feerate to use for an intermediate "kickoff" transaction. #### Requirements The sending node: - - if it is the initiator: + - if it is the `initiator`: - MUST only send `kickoff_feerate` if they can pay for the kickoff transaction fee and the anchor outputs, while adhering to the `channel_reserve` restriction. @@ -778,8 +781,8 @@ The sending node: complexity. The receiving node: - - if it is the responder: - - MUST reject the `dyn_propose` if the initiator cannot pay for the kickoff + - if it is the `responder`: + - MUST reject the `dyn_propose` if the `initiator` cannot pay for the kickoff transaction fee and the anchor outputs. - MUST reject the `dyn_propose` if, after calculating the amount of the new funding output, the new commmitment transaction would not be able to pay @@ -856,9 +859,13 @@ local party's anchor, the remote party's anchor, and the new funding output. All three of these outputs can be spent immediately. A malicious counterparty can pin the kickoff transaction by: - spending from their anchor output to create a descendant chain of 25 - transactions - - then spending from the new funding output, "using up" the CPFP Carve-out - slot designated for the honest party. + transactions _AND_ + - spending from the new funding output using the new commitment transaction, + "using up" the CPFP Carve-out slot designated for the honest party. +_NOTE FOR REVIEWERS_: The semantics of CPFP carve-out are not entirely clear +as to whether or not there is only _one_ CPFP-Carve-Out "slot" or if the only +two requirements are the 40kWU limit and a single unconfirmed ancestor. If we +have more than one "slot" available, this is no longer a concern. Depending on fee conditions, it may not be possible for the honest party to get these transactions confirmed until the mempool clears up. @@ -867,8 +874,8 @@ If we were to get rid of the kickoff transaction's anchor outputs, the problem still arises. A malicious counterparty could still pin the kickoff transaction by: - broadcasting the commitment transaction - - spending from their anchor output and creating a descendant chain of 25 - transactions + - spending from their commitment anchor output and creating a descendant chain + of 25 transactions The honest party is unable to use their anchor on the commitment transaction as: - the descendant limit of 25 transactions has been hit @@ -879,17 +886,12 @@ The honest party is unable to use their anchor on the commitment transaction as: The above pinning scenarios highlight the complexity of second-layer protocols and mempool restrictions. In this proposal, pinning is _still_ possible, but -risk is mitigated because: - - the kickoff transaction MUST confirm before HTLCs can be added to the - commitment transaction - - no HTLCs exist on the commitment transaction while the kickoff transaction - is unconfirmed - -If we allowed adding HTLCs _before_ the kickoff transaction confirmed on-chain, -the pinning attack would now have a tangible benefit: the ability to steal the -value of an HTLC. The second requirement above is very similar to the first -requirement: by disallowing HTLCs when `dyn_propose` is sent, we ensure that the -counterparty has no incentive to pin the kickoff transaction. +risk can be controlled if nodes reduce their max_htlc_value_in_flight_msat +values while the kickoff transaction is unconfirmed + +If we allow adding HTLCs _before_ the kickoff transaction confirmed on-chain, +the pinning attack has a tangible benefit: the ability to steal the value of an +HTLC. ## Weights From c1bc648cca144949c27bb0df8c95ed737620f758 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Wed, 25 Oct 2023 16:06:33 -0700 Subject: [PATCH 09/23] nits --- ext-dynamic-commitments.md | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index 63366d5f8..bfee8998a 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -255,7 +255,7 @@ and are common to all messages in the negotiation phase. #### kickoff_feerate_per_kw - type: 7 data: - * [`...*u32`:`kickoff_feerate_per_kw`] + * [`u32`:`kickoff_feerate_per_kw`] ### Proposal Messages @@ -608,7 +608,20 @@ output_. | |<-(6)---- revoke_and_ack ------| | +-------+ +-------+ -TODO: Explain why this message ordering is important +##### Rationale + +The commitment signed message has to be issued first to ensure that the money +locked to the new funding output (created by the kickoff transaction) can be +unilaterally recovered. If the `kickoff_sig` were sent first, the receiver could +stop responding and broadcast the kickoff transaction, burning the funds for +both parties. If the channel balance is overwhelmingly imbalanced towards the +side issuing the `kickoff_sig`, this could be costly to the victim while being +comparatively cheap for the attacker. + +Similarly, if we `revoke_and_ack` prior to receiving a `kickoff_sig` then we +may have a situation where we remove our ability to broadcast the old commitment +transaction before the path to the new commitment transaction has been fully +signed. #### Building the Kickoff Transaction @@ -726,20 +739,28 @@ attached to either side for fee-bumping. ##### Requirements -The sending node (the fundee): +The sending node: - MUST set `channel_id` to a valid channel it has with the recipient. - MUST NOT send this message before receiving the peer's `commitment_signed`. -The receiving node (the funder): +The receiving node: - MUST send an `error` and fail the channel if `channel_id` does not match an existing channel it has with the sender. - MUST send an `error` and fail the channel if `signature` is not valid for the kickoff transaction as constructed above OR non-compliant with the LOW-S-standard rule. [LOWS](https://github.com/bitcoin/bitcoin/pull/6769) + - MUST NOT send a `revoke_and_ack` for the final pre-dynamic commitment + transaction until it has received a valid `kickoff_sig` ##### Rationale -TODO: Explain some shit here +The `kickoff_sig` cannot be issued until the `commitment_signed` message has +been received to prevent griefing by broadcasting a kickoff for which there is +no exit. The `revoke_and_ack` for the last pre-dynamic commitment has to wait +for the `kickoff_sig` because if the last commitment built off of the original +funding output is revoked before the `kickoff_sig` has been received, then if +a peer becomes non-cooperative from that point forward, funds are effectively +burned. ### Additional Requirements: ~Musig2 Taproot -> Musig2 Taproot From fbb1998af0c26670bd2460653ef0a4b951fe0fa9 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Fri, 27 Oct 2023 15:10:54 -0700 Subject: [PATCH 10/23] change to bitvector rejection --- ext-dynamic-commitments.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index bfee8998a..2f5d93b61 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -418,9 +418,7 @@ the proposal. 1. type: 115 (`dyn_reject`) 2. data: * [`32*byte`:`channel_id`] - -1. `tlv_stream`: `dyn_propose_tlvs` -2. types: See `dyn_propose` for TLV breakdown + * [`...*byte`:`update_rejections`] ##### Requirements @@ -432,9 +430,13 @@ The sending node: - MUST NOT send this message if it has already sent a `dyn_reject` for the current negotiation. - if it will not accept **any** dynamic commitment negotiation: - - SHOULD send a `dyn_reject` **with an empty TLV stream** + - SHOULD send a `dyn_reject` with zero value for `update_rejections` - if it does not agree with one or more parameters: - - MUST send a `dyn_reject` with the set TLV records it rejects + - MUST send a `dyn_reject` with the bit index (using the same layout as + feature negotiation) set corresponding to the TLV type number. + - Example: an objection to the `dust_limit` would be encoded as + 0b00000001, an objection to `max_value_in_flight` would be encoded as + 0b00000010, and an objection to both would be encoded as 0b00000011. - if it has sent a `dyn_propose` in the current negotiation - MUST forget its last sent `dyn_propose` parameters - MUST forget the parameters of the `dyn_propose` message to which the @@ -446,14 +448,14 @@ The receiving node: - if there isn't an outstanding `dyn_propose` it has sent - MUST send an `error` and fail the channel - MUST forget its last sent `dyn_propose` parameters. - - if the TLV stream is empty + - if the `update_rejections` is a zero value - SHOULD NOT re-attempt another dynamic commitment negotation for the remaining lifecycle of the connection - - if the TLV stream is NOT empty + - if the `update_rejections` is a non-zero value: - MAY re-attempt another dynamic commitment negotiation - if a dynamic commitment negotiation is re-attempted: - - SHOULD relax any parameters that were specified in the TLV stream of - the `dyn_reject` message. + - SHOULD relax the parameters whose TLV types match the bits that were set + in the `update_rejections` value. - if no sensible interpretation of "relax" exists: - SHOULD NOT re-attempt a dynamic commitment negotiation with this parameter set. From f1d1963be0d8686b476242bc03b2c4aff6164b57 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Mon, 20 Nov 2023 11:55:32 -0700 Subject: [PATCH 11/23] add section describing what it'd take to do with live HTLCs --- ext-dynamic-commitments.md | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index 2f5d93b61..ea53b0c6f 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -846,6 +846,54 @@ The receiving node: # Appendix +## NOTE FOR REVIEWERS: Dropping the Flush Requirement + +This proposal currently requires that the HTLCs on the channel are flushed +before new channel parameters are applied. To drop this requirement we will need +to be able to apply each of these channel parameter changes to channels with +in-flight HTLCs. The following possible issues arise of the channel still has +HTLCs on it when we try to change parameters + +### `dust_limit_satoshis` +If this parameter changes with live HTLCs it can result in pruning or unpruning +HTLCs from the commitment transaction when the next one is signed. We can either +handle this by immediately signing a new commitment transaction and revoking the +old one, or we can let the pruning/unpruning take place on the next channel +state change. + +### `max_htlc_value_in_flight_msat` +If this parameter changes with live HTLCs it can result in the new ceiling being +too low for the current in-flight amount. If this occurs we can either reject +the negotiation, or we can apply it immediately and then only use it to gate new +HTLC adds. + +### `channel_reserve_satoshis` +If this parameter changes with live HTLCs there are no repercussions. + +### `to_self_delay` +If this parameter changes with live HTLCs we will need to sign a new commitment +transaction similar to without live HTLCs, however, it will also require using +the new `to_self_delay` in all of the HTLC signatures in the `commitment_signed` +message. + +### `max_accepted_htlcs` +Similar to the `max_htlc_value_in_flight_msat`, if the current number of live +HTLCs on the commitment transaction exceeds the new threshold, we still only +need to apply the check on a new HTLC add. + +### `funding_pubkey` +Changing the funding pubkey requires a re-anchoring step but does not impact the +commitment transaction itself. There are no repercussions here because the +result is the same as if we changed the `funding_pubkey` and then added HTLCs +to the channel. + +### `channel_type` +It is not out of the question that future channel types could make certain +`channel_type` conversions untenable (PTLCs), however, with the change from +option_anchor to STCs and the change from option_static_remote_key to either of +those behave exactly like changes to `funding_pubkey` for the purposes of doing +it with live HTLCs. + ## Pinning ![Cannot display image](./dynamic-commits/kickoff%20pinning.png "Kickoff transaction pinned") From c165c5f2f8b4e8d30bd92b4546b8a18e58f522c2 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Mon, 4 Dec 2023 13:11:00 -0800 Subject: [PATCH 12/23] update proposal to allow for in-flight HTLCs --- ext-dynamic-commitments.md | 84 ++++++-------------------------------- 1 file changed, 13 insertions(+), 71 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index ea53b0c6f..1df127a87 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -191,15 +191,12 @@ always be necessary, but it is certainly necessary for using this proposal to convert existing channels into Taproot channels. # Specification -There are three phases to this channel upgrade process: proposal, flushing, and -execution. During the proposal phase the only goal is to agree on a set of -updates to the current channel state machine. Assuming an agreement can be -reached, we will proceed to the other two phases. During the flushing phase, we -proceed with channel operation, allowing only `update_fulfill_htlc` and -`update_fail_htlc` messages until all HTLCs have been cleared, similar to -`shutdown`. Finally, during the execution phase, we apply the updates to the -channel state machine, exchanging the necessary information to be able to apply -those updates. +There are two phases to this channel upgrade process: proposal, and execution. +During the proposal phase the only goal is to agree on a set of updates to the +current channel state machine. Assuming an agreement can be reached, we will +proceed to the execution phase. During the execution phase, we apply the updates +to the channel state machine, exchanging the necessary information to be able to +apply those updates. ## Proposal Phase @@ -515,19 +512,9 @@ side that hasn't received `dyn_ack` knows that the other side accepted their last sent `dyn_propose` based on the `dyn_height` in the reestablish message. -## Flushing Phase - -Once the Negotiation Phase is complete, the channel enters a Flushing Phase -similar to the procedure that occurs during `shutdown`. During this phase, no -new HTLCs may be added by either party, but they may be removed, either by a -fulfill or fail operation. Once all HTLCs have been cleared from both sides of -the channel, and both channel parties have irrevocably committed to this empty -state, we enter the Execution Phase. - ## Execution Phase -There are three fundamental types of execution paths following the flushing of -HTLCs: +There are three fundamental types of execution paths: 1. Rules Change - No additional state change is required, the next state will be expected to follow the new rules that have been negotiated. Channel may resume normal operation. @@ -550,7 +537,8 @@ is selected from the list above will be the one with the highest number that is triggered by the below rules: Funding Output Update > Commitment Update > Rules Change. -- If either channel party changes `dust_limit_satoshis`: Rules Change +- If either channel party changes `dust_limit_satoshis`: Commitment Update +- If either channel party changes `max_htlc_value_in_flight_msat`: Rules Change - If either channel party changes `channel_reserve_satoshis`: Rules Change - If either channel party changes `to_self_delay`: Commitment Update - If either channel party changes `max_accepted_htlcs`: Rules Change @@ -562,8 +550,10 @@ Change. If all that is required to execute the terms of the dynamic commitment negotiation is a rules change, then channel operation may resume as normal -under the new rules as soon as both channel parties have irrevocably committed -to a state with no HTLC outputs. +under the new rules. It is possible that the current channel state would violate +the constraints specified by the new rules. When we execute a rules change, only +new channel states will be evaluated against the new rules. As long as the new +channel state moves towards the constraint boundary, it will be accepted. ### Commitment Update @@ -846,54 +836,6 @@ The receiving node: # Appendix -## NOTE FOR REVIEWERS: Dropping the Flush Requirement - -This proposal currently requires that the HTLCs on the channel are flushed -before new channel parameters are applied. To drop this requirement we will need -to be able to apply each of these channel parameter changes to channels with -in-flight HTLCs. The following possible issues arise of the channel still has -HTLCs on it when we try to change parameters - -### `dust_limit_satoshis` -If this parameter changes with live HTLCs it can result in pruning or unpruning -HTLCs from the commitment transaction when the next one is signed. We can either -handle this by immediately signing a new commitment transaction and revoking the -old one, or we can let the pruning/unpruning take place on the next channel -state change. - -### `max_htlc_value_in_flight_msat` -If this parameter changes with live HTLCs it can result in the new ceiling being -too low for the current in-flight amount. If this occurs we can either reject -the negotiation, or we can apply it immediately and then only use it to gate new -HTLC adds. - -### `channel_reserve_satoshis` -If this parameter changes with live HTLCs there are no repercussions. - -### `to_self_delay` -If this parameter changes with live HTLCs we will need to sign a new commitment -transaction similar to without live HTLCs, however, it will also require using -the new `to_self_delay` in all of the HTLC signatures in the `commitment_signed` -message. - -### `max_accepted_htlcs` -Similar to the `max_htlc_value_in_flight_msat`, if the current number of live -HTLCs on the commitment transaction exceeds the new threshold, we still only -need to apply the check on a new HTLC add. - -### `funding_pubkey` -Changing the funding pubkey requires a re-anchoring step but does not impact the -commitment transaction itself. There are no repercussions here because the -result is the same as if we changed the `funding_pubkey` and then added HTLCs -to the channel. - -### `channel_type` -It is not out of the question that future channel types could make certain -`channel_type` conversions untenable (PTLCs), however, with the change from -option_anchor to STCs and the change from option_static_remote_key to either of -those behave exactly like changes to `funding_pubkey` for the purposes of doing -it with live HTLCs. - ## Pinning ![Cannot display image](./dynamic-commits/kickoff%20pinning.png "Kickoff transaction pinned") From 21d39260302315ebd03510ae5230cb8eea8f9e4b Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Mon, 4 Dec 2023 13:27:02 -0800 Subject: [PATCH 13/23] add quiescent prerequisite --- ext-dynamic-commitments.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index 1df127a87..ffef8c83c 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -200,6 +200,10 @@ apply those updates. ## Proposal Phase +As a prerequisite to the proposal phase of a Dynamic Commitment negotiation, the +channel must be in a [quiesced](https://github.com/lightning/bolts/pull/869) +state. + ### Node Roles In every dynamic commitment negotiation, there are two roles: the `initiator` From c4eedc0359d9dd1dba2456b3929e7f59257d983f Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Mon, 5 Feb 2024 16:26:14 -0800 Subject: [PATCH 14/23] add appendix for asymmetric kickoffs --- ext-dynamic-commitments.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index ffef8c83c..b2ff51160 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -910,6 +910,39 @@ If we allow adding HTLCs _before_ the kickoff transaction confirmed on-chain, the pinning attack has a tangible benefit: the ability to steal the value of an HTLC. +### Asymmetric Kickoffs + +The above proposal specifies a process wherein we can reanchor the funding +output exactly once. This is because even if we revoke all commitment +transactions built off of the first kickoff transaction, we still are vulnerable +to griefing if we do not revoke the kickoff transaction itself. In this case +one party may choose to burn all funds in a channel by broadcasting the kickoff +transaction when no unrevoked commitment transactions remain. To deal with this +we can either only reanchor once, as proposed above, allowing us to guarantee we +will never encounter a situation where there are no valid commitment +transactions, or we can make the kickoff transactions revocable. + +To make them revocable we can reuse the same scheme that we use for commitment +transactions. In this case Alice's kickoff transaction would allow Bob to claim +all funds if Bob knows Alice's revocation secret. Similarly, Alice could claim +all channel funds if Bob broadcasts his kickoff transaction and Alice knows +Bob's revocation secret. + +An unfortunate consequence of this scheme is that since we now have two possible +"new" funding outputs (one for each of the potential kickoff transactions), we +now have to send all of our commitment signatures in pairs. At any given time +there would be four valid commitment transactions: + +1. Alice's commitment built off of Alice's kickoff +2. Alice's commitment built off of Bob's kickoff +3. Bob's commitment built off of Alice's kickoff +4. Bob's commitment built off of Bob's kickoff + +_NOTE FOR REVIEWERS_: There may be an opportunity to make the kickoff +transactions symmetric while still allowing them to be revocable using adaptor +signature tricks, but this will require more research from those with a deeper +understanding of the cryptographic primitives. + ## Weights Since DER-encoded signatures vary in size, we assume a worst-case signature size From d75918a101508a529b4bd7f86c8ef149b723fe19 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Tue, 16 Apr 2024 16:05:09 -0600 Subject: [PATCH 15/23] minor formatting changes --- ext-dynamic-commitments.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index b2ff51160..e400daea9 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -12,7 +12,9 @@ Created: TODO TODO # Introduction + ## Abstract + This document describes a protocol for changing channel parameters that were negotiated at the conception of the channel. Implementation of the protocol described in this document will enable channel peers to re-negotiate and update @@ -21,6 +23,7 @@ avoiding UTXO churn whenever possible and therefore preserving the continuity of identity for the channel whose terms are being changed. ## Motivation + It is well understood that closing channels is a costly thing to do. Not only is it costly from a chain fees perspective (where we pay for moving the funds from the channel UTXO back to the main wallet collection), it is also costly from a @@ -73,6 +76,7 @@ channel parameters (including channel types) without requiring channel turnover is submitted. ## Preliminaries + This proposal includes a detailed section on the preliminaries to document some of the rationale for the design that is presented later. If you are a Bitcoin and Lightning Network protocol expert or you are uninterested in the thought @@ -80,6 +84,7 @@ process behind what is presented here, you may wish to skip to the Design Overview section to save time. ### Channel Opening Parameters + As described in BOLT 2, during the channel opening procedure there are a number of parameters specified in the `open_channel` and `accept_channel` messages that remain static over the lifetime of the channel. A subset of these are updatable @@ -121,6 +126,7 @@ The design presented here is intended to allow for arbitrary changes to these values that currently have no facilities for change in any other way. ### Gossip Verification + It is at this point that we need to take a brief detour and review how the broader Lightning Network comes to discover and verify the existence of public channels. When the funding transaction for a channel has confirmed, the @@ -142,6 +148,7 @@ unable to process messages of this variety, rendering useless any channels that can only be announced in this manner. ### Taproot + This brings us to talking about what channel constructions are actually inexpressible by the existing gossip system. As we alluded to earlier, Taproot channels cannot be discovered using the existing gossip message structure and @@ -164,6 +171,7 @@ section, it is necessarily the case that the funding output of a Taproot channel cannot be properly announced by the current gossip system. ## Design Overview + The main goal of this proposal is to be able to change all of the historically "static" channel parameters, including the channel type, which includes channels built off of output types that our gossip system currently doesn't understand, @@ -191,6 +199,7 @@ always be necessary, but it is certainly necessary for using this proposal to convert existing channels into Taproot channels. # Specification + There are two phases to this channel upgrade process: proposal, and execution. During the proposal phase the only goal is to agree on a set of updates to the current channel state machine. Assuming an agreement can be reached, we will @@ -219,41 +228,49 @@ The following TLVs are used throughout the negotiation phase of the protocol and are common to all messages in the negotiation phase. #### dust_limit_satoshis + - type: 0 data: * [`u64`:`dust_limit_satoshis`] #### max_htlc_value_in_flight_msat + - type: 1 data: * [`u64`:`senders_max_htlc_value_in_flight_msat`] #### channel_reserve_satoshis + - type: 2 data: * [`u64`:`recipients_channel_reserve_satoshis`] #### to_self_delay + - type: 3 data: * [`u16`:`recipients_to_self_delay`] #### max_accepted_htlcs + - type: 4 data: * [`u16`:`senders_max_accepted_htlcs`] #### funding_pubkey + - type: 5 data: * [`point`:`senders_funding_pubkey`] #### channel_type + - type: 6 data: * [`...*byte`:`channel_type`] #### kickoff_feerate_per_kw + - type: 7 data: * [`u32`:`kickoff_feerate_per_kw`] @@ -961,6 +978,7 @@ General weights: - marker: 1 byte ### Kickoff Transaction Weights + * funding_output_script: 71 bytes - OP_2: 1 byte - OP_DATA: 1 byte (pub_key_alice length) @@ -1019,6 +1037,7 @@ General weights: - kickoff_transaction_weight = 944WU ### Commitment Transaction Weights + Here we assume that both parties have an output on the commitment transaction. This is to keep the weight consistent across potentially different commitment transactions. From ac8ad0ac8faa6141812ff83dc2364285d64cd2df Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Tue, 16 Apr 2024 17:17:54 -0600 Subject: [PATCH 16/23] simplify negotiation protocol --- ext-dynamic-commitments.md | 61 ++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index e400daea9..fa58cdb39 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -220,7 +220,8 @@ and the `responder`. It is necessary for both nodes to agree on which node is the `initiator` and which node is the `responder`. This is important because if the dynamic commitment negotiation results in a re-anchoring step (described later), it is the `initiator` that is responsible for paying the fees for the -kickoff transaction. +kickoff transaction. The `initiator` is determined by who has the `initiator` +role established by the quiescence process. ### Negotiation TLVs @@ -285,23 +286,18 @@ change as well as accept or reject the proposal made by their counterparty. #### `dyn_propose` This message is sent to negotiate the parameters of a dynamic commitment -upgrade. The overall protocol flow looks similar to what is depicted below. -This message is always sent by the `initiator` and the `responder`. +upgrade. The overall protocol flow is depicted below. This message is always +sent by the `initiator`. +-------+ +-------+ - | |--(1)---- dyn_propose -------->| | - | | | | - | |<-(2)---- dyn_propose ---------| | + | |--(1)------ dyn_propose ------>| | | A | | B | - | |--(3)------ dyn_ack ---------->| | - | | | | - | |<-(4)------ dyn_ack -----------| | + | |<-(4)---{dyn_ack|dyn_reject}---| | +-------+ +-------+ 1. type: 111 (`dyn_propose`) 2. data: * [`32*byte`:`channel_id`] - * [`u8`:`initiator`] * [`dyn_propose_tlvs`:`tlvs`] 1. `tlv_stream`: `dyn_propose_tlvs` @@ -333,39 +329,30 @@ This message is always sent by the `initiator` and the `responder`. ##### Requirements -TODO: handle edge case where both nodes send `dyn_propose` as `initiator` - The sending node: + - MUST be the `initiator` established in the preceding quiescence protocol. - MUST set `channel_id` to an existing one it has with the recipient. - MUST NOT send a set of TLV parameters that would violate the requirements of the identically named parameters in BOLT 2 or associated extensions. - MUST remember its last sent `dyn_propose` parameters. - if it is currently waiting for a response (`dyn_ack` or `dyn_reject`): - - MUST NOT send another `dyn_propose` + - MUST NOT send another `dyn_propose`. - SHOULD close the connection if it exceeds an acceptable time frame. - - if it is the `initiator`: - - MUST set `initiator` to 1 - - if it sets `channel_type` and the `channel_type` conversion requires - re-anchoring (see appendix for conversions that require re-anchoring) - - MUST set `kickoff_feerate` - - if it is the `responder`: - - MUST set `initiator` to 0 - - MUST set the `channel_type` TLV to the same value as the one sent by the - `initiator` - - MUST NOT set the `kickoff_feerate` TLV - - MUST NOT send a set of TLV parameters that would violate the requirements - of the identically named parameters in BOLT 2 **assuming** the acceptance - of the parameters it received in the `initiator`'s `dyn_propose` message. + - if it sets `channel_type` and the `channel_type` conversion requires + re-anchoring (see appendix for conversions that require re-anchoring): + - MUST set `kickoff_feerate`. The receiving node: - if `channel_id` does not match an existing channel it has with the sender: - SHOULD send an `error` and close the connection. - - if it wishes to update additional parameters as part of the *same* dynamic - commitment negotiation AND has not yet sent a `dyn_ack` message: - - MUST send a `dyn_propose` with its desired parameters - - MUST NOT send a `dyn_propose` after a `dyn_ack` for the same negotiation - - MUST send a `dyn_ack` to accept the parameters it was sent - - MUST NOT send a `dyn_reject` + - MUST respond with either a `dyn_ack` or `dyn_reject`. + - if the TLV parameters of the `dyn_propose` are acceptable and the receiver + intends to execute those parameter changes: + - MUST respond with `dyn_ack`. + - MUST remember its last received `dyn_propose` parameters. + - if the TLV parameters of the `dyn_propose` are NOT acceptable and the + receiver refuses to execute those parameter changes: + - MUST respond with `dyn_reject`. _NOTE FOR REVIEWERS_: These messages all interact with each other, so feedback is welcome for how to restructure this section so that the invariants it @@ -419,8 +406,9 @@ The receiving node: - MUST send an `error` and fail the channel. A node: - - once it has both sent and received `dyn_ack` + - once it has sent or received `dyn_ack` - MUST increment its `propose_height`. + - MUST proceed to the Execution Phase. ##### Rationale @@ -481,9 +469,10 @@ The receiving node: ##### Rationale By sending back the TLVs that a node explicitly rejects makes it easier to come -to an agreement on a proposal that will work. By not sending back any TLVs in -the `dyn_reject`, a node signals it is not interested in moving the negotiation -forward at all and further negotiation should not be attempted. +to an agreement on a proposal that will work. By sending back a zero value for +`update_rejections`, a node signals it is not interested in moving any dynamic +commitment negotiation forward at all and further negotiation should not be +attempted. ## Reestablish From a1963f7cdb54ae2616b4d34c0ca1a16604919811 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Tue, 16 Apr 2024 17:20:45 -0600 Subject: [PATCH 17/23] remove all refs to `propose_height` --- ext-dynamic-commitments.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index fa58cdb39..e284972a9 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -395,9 +395,9 @@ The sending node: - MUST NOT send this message if it has already sent a `dyn_reject` for the current negotiation. - MUST remember the parameters of `dyn_propose` message to which the `dyn_ack` - is responding for the next `propose_height`. + is responding for the next `dyn_height`. - MUST remember the local and remote commitment heights for the next - `propose_height`. + `dyn_height`. The receiving node: - if `channel_id` does not match an existing channel it has with the peer: @@ -407,12 +407,12 @@ The receiving node: A node: - once it has sent or received `dyn_ack` - - MUST increment its `propose_height`. + - MUST increment its `dyn_height`. - MUST proceed to the Execution Phase. ##### Rationale -The `propose_height` starts at 0 for a channel and is incremented by 1 every +The `dyn_height` starts at 0 for a channel and is incremented by 1 every time the dynamic commitment proposal phase completes for a channel. See the reestablish section for why this is needed. @@ -478,7 +478,7 @@ attempted. ### `channel_reestablish` -A new TLV that denotes the node's current `propose_height` is included. +A new TLV that denotes the node's current `dyn_height` is included. 1. `tlv_stream`: `channel_reestablish_tlvs` 2. types: @@ -495,10 +495,10 @@ The sending node: The receiving node: - if the received `dyn_height` equals its own `dyn_height`: - - MUST forget any stored proposal state for `propose_height`+1 in case + - MUST forget any stored proposal state for `dyn_height`+1 in case negotiation didn't complete. Can continue using the channel. - SHOULD forget any state that is unnecessary for heights <= - `propose_height`. + `dyn_height`. - if the received `dyn_height` is 1 greater than its own `dyn_height`: - if it does not have any remote parameters stored for the received `dyn_height`: @@ -507,7 +507,7 @@ The receiving node: its not possible to advance the height without the recipient storing the remote's parameters. - resume using the channel with its last-sent `dyn_propose` and the stored - `dyn_propose` parameters and increment its `propose_height`. + `dyn_propose` parameters and increment its `dyn_height`. - if the received `dyn_height` is 1 less than its own `dyn_height`: - resume using the channel with the new parameters. - else: From a83ef15bae99821a62f9abbff328d1f4f2ebcba0 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Fri, 3 May 2024 18:27:50 -0700 Subject: [PATCH 18/23] update to incorporate yy's feedback --- ext-dynamic-commitments.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index e284972a9..c64b88e93 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -369,8 +369,8 @@ secure in all contexts. Since the `initiator` is the one that is responsible for paying the fees for the kickoff transaction if it is required (like for certain `channel_type` changes), it follows that the `responder` cannot change the `channel_type`. Since the -`kickoff_feerate` paid by the `initiator`, it should be set only if the sender -is the `initiator`. +`kickoff_feerate` is paid by the `initiator`, it should be set only if the +sender is the `initiator`. The requirement for a node to remember what it last _sent_ and for it to remember what it _accepted_ is necessary to recover on reestablish. See the @@ -389,13 +389,15 @@ accepted the proposal. The sending node: - MUST set `channel_id` to a valid channel it has with the recipient. - - MUST NOT send this message if it has not received a `dyn_propose` + - MUST NOT send this message if it has not received a `dyn_propose` for this + `channel_id` - MUST NOT send this message if it has already sent a `dyn_ack` for the current negotiation. - MUST NOT send this message if it has already sent a `dyn_reject` for the current negotiation. - MUST remember the parameters of `dyn_propose` message to which the `dyn_ack` - is responding for the next `dyn_height`. + is responding for the next `dyn_height`. (See `channel_reestablish` + requirements) - MUST remember the local and remote commitment heights for the next `dyn_height`. @@ -443,8 +445,6 @@ The sending node: - Example: an objection to the `dust_limit` would be encoded as 0b00000001, an objection to `max_value_in_flight` would be encoded as 0b00000010, and an objection to both would be encoded as 0b00000011. - - if it has sent a `dyn_propose` in the current negotiation - - MUST forget its last sent `dyn_propose` parameters - MUST forget the parameters of the `dyn_propose` message to which the `dyn_reject` is responding. @@ -554,7 +554,9 @@ Change. - If either channel party changes `max_accepted_htlcs`: Rules Change - If either channel party changes `funding_pubkey`: Funding Output Update - If new `channel_type` requires different funding output script than the old -`channel_type`: Funding Output Update + `channel_type`: Funding Output Update +- If new `channel_type` requires the same funding output script as the old + `channel_type`: Commitment Update ### Rules Change @@ -578,9 +580,8 @@ normal channel operation is resumed. ### Funding Output Change: General Protocol -If a Funding Output Change is required, then once both channel parties have -irrevocably committed to a state with no HTLC outputs, new commitment signatures -AND kickoff signatures MUST be exchanged. To accomplish this the following steps +If a Funding Output Change is required, then new commitment signatures AND +kickoff signatures MUST be exchanged. To accomplish this, the following steps are taken: 1. Build kickoff transaction @@ -639,7 +640,7 @@ signed. * txout count: 3 * `txout[0]`: `anchor_output_1` or `anchor_output_2` * `txout[1]`: `anchor_output_1` or `anchor_output_2` - * `txout[2]`: `p2tr_funding_output` + * `txout[2]`: `kickoff_funding_output` The anchor outputs have a value of 330 satoshis. They are encumbered by a version 1 witness script: @@ -683,7 +684,7 @@ from `dyn_ack`: * locktime: upper 8 bits are 0x20, lower 24 bits are the lower 24 bits of the obscured commitment number * txin count: 1 - * `txin[0]` outpoint: the matching kickoff transaction's funding outpoint. + * `txin[0]` outpoint: the `kickoff_funding_output` * `txin[0]` sequence: upper 8 bits are 0x80, lower 24 bits are upper 24 bits of the obscured commitment number * `txin[0]` script bytes: 0 From 0ce9ee5551aeeee01ff7830fe172e90c37e73c44 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Mon, 26 Aug 2024 13:17:53 -0600 Subject: [PATCH 19/23] slim down document to exclude funding output changes --- ext-dynamic-commitments.md | 802 ++++--------------------------------- 1 file changed, 69 insertions(+), 733 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index c64b88e93..31d9a6948 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -48,33 +48,6 @@ to a full channel closure, even if the channel counterparty consents. This limitation can be remediated by introducing a protocol to renegotiate these parameters. -Notable in particular, is that one of the channel parameters we wish to -renegotiate is the the `channel_type` itself. With the advent of Simple Taproot -Channels (STCs), we have the opportunity to take advantage of the cost savings -and privacy capabilities afforded by the 2021 Taproot Soft Fork, with further -aspirations to be able to deploy Point Time-Lock Contracts (PTLCs) to the -Lightning Network. The sooner that network participants can upgrade to STCs the -more we will have the necessary network infrastructure to be able to make -effective use of PTLCs when the protocols for them are ready for deployment. - -Due to the design of STCs, and the fact that they take full advantage of the -capabilities afforded by Schnorr Signatures, there is no way to construct a -valid `channel_announcement` message that references the output corresponding to -the nodes' joint public key. As such, even if we were to directly spend an -existing funding output to a new STC funding output, and even with the provision -in BOLT 7 to delay graph pruning by 12 blocks after the channel point is spent, -we have no way of advertising the STC to the network at the time of writing of -this proposal. - -That said, there is a development effort, concurrent with this, for a new gossip -system that is capable of understanding the announcements of new STCs. However, -even with a new gossip system capable of understanding the STC construction and -announcement, it will take quite some time for such a system to be broadly -deployed across the Lightning Network. In the interim, to combat the -disincentive of upgrading to STCs, this proposal to enable the change of these -channel parameters (including channel types) without requiring channel turnover -is submitted. - ## Preliminaries This proposal includes a detailed section on the preliminaries to document some @@ -125,78 +98,22 @@ remaining after we filter out these values is thus: The design presented here is intended to allow for arbitrary changes to these values that currently have no facilities for change in any other way. -### Gossip Verification - -It is at this point that we need to take a brief detour and review how the -broader Lightning Network comes to discover and verify the existence of public -channels. When the funding transaction for a channel has confirmed, the -participating parties will jointly produce a message that attests to their -ownership of the UTXO and its viability as a routing edge for payment senders. - -BOLT 7 details all of the specifics of this message and how it is computed but -one of the notable aspects of this process is that the receivers of these gossip -messages verify that the UTXO underwriting the channel must be a P2WSH output -with a pre-defined script using the participants' public keys, specified in -BOLT 3. This will present issues for us which will become clearer in the next -section. - -While alternative gossip systems that can describe STCs are being designed, they -have not been deployed in any known implementation of the Lightning Network -Protocol and even when such a design is implemented, there will be a prolonged -period of time wherein a substantial number of nodes on the network will remain -unable to process messages of this variety, rendering useless any channels that -can only be announced in this manner. - -### Taproot - -This brings us to talking about what channel constructions are actually -inexpressible by the existing gossip system. As we alluded to earlier, Taproot -channels cannot be discovered using the existing gossip message structure and -interpretation. - -In November of 2021 the "Taproot" upgrade was activated on Bitcoin's mainnet, -creating a new output type that is subsequently useful to higher layer protocols -such as the Lightning Network. Since then, the Lightning Network protocol -designers have offered a proposal for a channel construction that makes use of -the Taproot output type. It is beyond the scope of this document to make a -thorough case for why such a channel construction is useful but we assume that -it is for our purposes here. - -While Taproot channels are useful, they present some novel challenges with -respect to network-wide interoperability. Notably, a useful Taproot channel -construction must by definition make use of the new Taproot output type, which -does not and cannot use the output script format detailed in BOLT 3 for the -funding output. Pairing this fact with what we described in the previous -section, it is necessarily the case that the funding output of a Taproot channel -cannot be properly announced by the current gossip system. - ## Design Overview The main goal of this proposal is to be able to change all of the historically -"static" channel parameters, including the channel type, which includes channels -built off of output types that our gossip system currently doesn't understand, -in a world where we are trying to preserve the channel identity of the original -channel. This is a tall order. +"static" channel parameters, including the channel type, in a world where we are +trying to preserve the channel identity of the original channel. Most of these parameters can be changed by simply expressing the desire to -change them, and should the recipient agree, we apply these changes, and -exchange new commitment transactions making any necessary adjustments prescribed -by the channel parameter changes. - -The exception to this is certain changes to the channel type. As detailed in the -preliminaries, the funding output of a Taproot transaction is fundamentally -different from the funding output of the other channel types that are currently -defined. This means that we must spend the funding output of the original -channel into a new Taproot output before we have a functioning Taproot channel. - -The key insight in this design is that we extend the concept of a commitment -transaction to include the possibility of a pair of transactions wherein we have -a "kickoff transaction" that is comprised of a single input (the original -funding output) and a single output (the new funding output) and then building -the new commitment transaction off of the new funding output in whatever manner -is detailed in the specification for the target channel type. This may not -always be necessary, but it is certainly necessary for using this proposal to -convert existing channels into Taproot channels. +change them, and if the responder agrees, we proceed with a mutual understanding +of the new parameters. + +However, there are exceptions to this protocol flow. Changing the funding pubkey +and in certain cases, changing the channel type requires a funding output +conversion. This proposal does not cover how to safely accomplish a funding +output conversion and so for the purposes of the remainder of this document, it +is considered prohibited. NOTE: follow-on documents will elaborate on how to +execute changes that require funding output conversions. # Specification @@ -204,8 +121,7 @@ There are two phases to this channel upgrade process: proposal, and execution. During the proposal phase the only goal is to agree on a set of updates to the current channel state machine. Assuming an agreement can be reached, we will proceed to the execution phase. During the execution phase, we apply the updates -to the channel state machine, exchanging the necessary information to be able to -apply those updates. +to the channel state machine. ## Proposal Phase @@ -217,11 +133,7 @@ state. In every dynamic commitment negotiation, there are two roles: the `initiator` and the `responder`. It is necessary for both nodes to agree on which node is -the `initiator` and which node is the `responder`. This is important because if -the dynamic commitment negotiation results in a re-anchoring step (described -later), it is the `initiator` that is responsible for paying the fees for the -kickoff transaction. The `initiator` is determined by who has the `initiator` -role established by the quiescence process. +the `initiator` and which node is the `responder`. ### Negotiation TLVs @@ -232,51 +144,38 @@ and are common to all messages in the negotiation phase. - type: 0 data: - * [`u64`:`dust_limit_satoshis`] + * [`u64`:`senders_dust_limit_satoshis`] #### max_htlc_value_in_flight_msat -- type: 1 +- type: 2 data: * [`u64`:`senders_max_htlc_value_in_flight_msat`] #### channel_reserve_satoshis -- type: 2 +- type: 4 data: * [`u64`:`recipients_channel_reserve_satoshis`] #### to_self_delay -- type: 3 +- type: 6 data: * [`u16`:`recipients_to_self_delay`] #### max_accepted_htlcs -- type: 4 +- type: 8 data: * [`u16`:`senders_max_accepted_htlcs`] -#### funding_pubkey - -- type: 5 - data: - * [`point`:`senders_funding_pubkey`] - #### channel_type -- type: 6 +- type: 10 data: * [`...*byte`:`channel_type`] -#### kickoff_feerate_per_kw - -- type: 7 - data: - * [`u32`:`kickoff_feerate_per_kw`] - - ### Proposal Messages Three new messages are introduced that are common to all dynamic commitment @@ -305,27 +204,21 @@ sent by the `initiator`. 1. type: 0 (`dust_limit_satoshis`) 2. data: * [`u64`:`dust_limit_satoshis`] - 1. type: 1 (`max_htlc_value_in_flight_msat`) + 1. type: 2 (`max_htlc_value_in_flight_msat`) 2. data: * [`u64`:`senders_max_htlc_value_in_flight_msat`] - 1. type: 2 (`channel_reserve_satoshis`) + 1. type: 4 (`channel_reserve_satoshis`) 2. data: * [`u64`:`recipients_channel_reserve_satoshis`] - 1. type: 3 (`to_self_delay`) + 1. type: 6 (`to_self_delay`) 2. data: * [`u16`:`recipients_to_self_delay`] - 1. type: 4 (`max_accepted_htlcs`) + 1. type: 8 (`max_accepted_htlcs`) 2. data: * [`u16`:`senders_max_accepted_htlcs`] - 1. type: 5 (`funding_pubkey`) - 2. data: - * [`point`:`senders_funding_pubkey`] - 1. type: 6 (`channel_type`) + 1. type: 10 (`channel_type`) 2. data: * [`...*byte`:`channel_type`] - 1. type: 7 (`kickoff_feerate`) - 2. data: - * [`u32`:`kickoff_feerate_per_kw`] ##### Requirements @@ -338,9 +231,13 @@ The sending node: - if it is currently waiting for a response (`dyn_ack` or `dyn_reject`): - MUST NOT send another `dyn_propose`. - SHOULD close the connection if it exceeds an acceptable time frame. - - if it sets `channel_type` and the `channel_type` conversion requires - re-anchoring (see appendix for conversions that require re-anchoring): - - MUST set `kickoff_feerate`. + - MUST NOT set a `channel_type` with a different funding output script than + the current funding output script UNLESS otherwise negotiated by another + feature bit. + - _NOTE FOR REVIEWERS_: do we need to save `dyn_propose` parameters before + sending them? It complicates the design, but if we don't we need to be able + to discern between a faulty/malicious peer and a dropped connection while + the ack is in flight. The receiving node: - if `channel_id` does not match an existing channel it has with the sender: @@ -366,12 +263,6 @@ trying change channel parameters without a close event. BOLT 2 specifies constraints on these parameters to make sure they are internally consistent and secure in all contexts. -Since the `initiator` is the one that is responsible for paying the fees for the -kickoff transaction if it is required (like for certain `channel_type` changes), -it follows that the `responder` cannot change the `channel_type`. Since the -`kickoff_feerate` is paid by the `initiator`, it should be set only if the -sender is the `initiator`. - The requirement for a node to remember what it last _sent_ and for it to remember what it _accepted_ is necessary to recover on reestablish. See the reestablish section for more details. @@ -388,7 +279,7 @@ accepted the proposal. ##### Requirements The sending node: - - MUST set `channel_id` to a valid channel it has with the recipient. + - MUST set `channel_id` to the `channel_id` it received in the `dyn_propose`. - MUST NOT send this message if it has not received a `dyn_propose` for this `channel_id` - MUST NOT send this message if it has already sent a `dyn_ack` for the @@ -396,10 +287,8 @@ The sending node: - MUST NOT send this message if it has already sent a `dyn_reject` for the current negotiation. - MUST remember the parameters of `dyn_propose` message to which the `dyn_ack` - is responding for the next `dyn_height`. (See `channel_reestablish` + is responding for the next `dyn_epoch_height`. (See `channel_reestablish` requirements) - - MUST remember the local and remote commitment heights for the next - `dyn_height`. The receiving node: - if `channel_id` does not match an existing channel it has with the peer: @@ -409,12 +298,14 @@ The receiving node: A node: - once it has sent or received `dyn_ack` - - MUST increment its `dyn_height`. + - MUST increment its `dyn_epoch_height`. - MUST proceed to the Execution Phase. + - MUST remember the local and remote commitment heights for the next + `dyn_epoch_height`. ##### Rationale -The `dyn_height` starts at 0 for a channel and is incremented by 1 every +The `dyn_epoch_height` starts at 0 for a channel and is incremented by 1 every time the dynamic commitment proposal phase completes for a channel. See the reestablish section for why this is needed. @@ -431,7 +322,7 @@ the proposal. ##### Requirements The sending node: - - MUST set `channel_id` to a valid channel it has with the recipient. + - MUST set `channel_id` to the `channel_id` it received in the `dyn_propose`. - MUST NOT send this message if it has not received a `dyn_propose` - MUST NOT send this message if it has already sent a `dyn_ack` for the current negotiation. @@ -444,7 +335,7 @@ The sending node: feature negotiation) set corresponding to the TLV type number. - Example: an objection to the `dust_limit` would be encoded as 0b00000001, an objection to `max_value_in_flight` would be encoded as - 0b00000010, and an objection to both would be encoded as 0b00000011. + 0b00000100, and an objection to both would be encoded as 0b00000101. - MUST forget the parameters of the `dyn_propose` message to which the `dyn_reject` is responding. @@ -478,610 +369,55 @@ attempted. ### `channel_reestablish` -A new TLV that denotes the node's current `dyn_height` is included. +A new TLV that denotes the node's current `dyn_epoch_height` is included. 1. `tlv_stream`: `channel_reestablish_tlvs` 2. types: - 1. type: 20 (`dyn_height`) + 1. type: 20 (`dyn_epoch_height`) 2. data: - * [`u64`:`dyn_height`] + * [`u64`:`dyn_epoch_height`] #### Requirements The sending node: - - MUST set `dyn_height` to the number of dynamic commitment negotiations + - MUST set `dyn_epoch_height` to the number of dynamic commitment negotiations it has completed. The point at which it is incremented is described in the `dyn_ack` section. The receiving node: - - if the received `dyn_height` equals its own `dyn_height`: - - MUST forget any stored proposal state for `dyn_height`+1 in case - negotiation didn't complete. Can continue using the channel. - - SHOULD forget any state that is unnecessary for heights <= - `dyn_height`. - - if the received `dyn_height` is 1 greater than its own `dyn_height`: - - if it does not have any remote parameters stored for the received - `dyn_height`: - - MUST send an `error` and fail the channel. The remote node is either - lying about the `dyn_height` or the recipient has lost data since - its not possible to advance the height without the recipient storing the - remote's parameters. - - resume using the channel with its last-sent `dyn_propose` and the stored - `dyn_propose` parameters and increment its `dyn_height`. - - if the received `dyn_height` is 1 less than its own `dyn_height`: - - resume using the channel with the new parameters. + - if the received `dyn_epoch_height` equals its own `dyn_epoch_height`: + - MAY forget any state that is unnecessary for heights <= `dyn_epoch_height` + - NOTE: some channel parameters show up in the chain footprint of + commitment transactions, and therefore it is likely necessary to + remember these parameters to be able to recognize and react to breach + transactions. + - resume using channel with the parameters associated with + `dyn_epoch_height` + - if the received `dyn_epoch_height` is 1 greater than its own + `dyn_epoch_height`: + - resume using the channel with the current parameters + - if the received `dyn_epoch_height` is 1 less than its own + `dyn_epoch_height`: + - resume using the channel with the old parameters. + - MUST forget the most recent `dyn_epoch_height` parameters. - else: - MUST send an `error` and fail the channel. State was lost. #### Rationale -If both sides have sent and received `dyn_ack` before the connection closed, it -is simple to continue. If one side has sent and received `dyn_ack` the other -side has only sent `dyn_ack`, the flow is recoverable on reconnection as the -side that hasn't received `dyn_ack` knows that the other side accepted their -last sent `dyn_propose` based on the `dyn_height` in the reestablish -message. +If `dyn_ack` has been sent and received before the connection closed, it +is simple to continue. If one side has sent `dyn_ack` and the other hasn't +received it, the flow is recoverable on reconnection by rolling back to the +previous set of parameters. ## Execution Phase -There are three fundamental types of execution paths: -1. Rules Change - No additional state change is required, the next state will be -expected to follow the new rules that have been negotiated. Channel may resume -normal operation. -2. Commitment Update - Additional state change is required, a new commitment -transaction is expected to be exchanged at this point, following the expected -parameters. Nodes will exchange `commitment_signed` and `revoke_and_ack`s for -transactions agreeing to the new rules, then Channel may resume normal -operation. -3. Funding Output Update - Signatures for a transaction that spends the original -funding output into a new funding output will be exchanged. - - NOTE FOR REVIEWERS: This transaction is currently symmetric which burdens - us with the constraint that a reanchoring step can only be done once over the - lifetime of the channel. If we want to be able to securely do this multiple - times, we must make kickoff transactions revocable, and therefore asymmetric, - and therefore must start issuing commitment signatures in pairs. See Appendix - for details. - -For execution we try and have the smallest execution overhead. The option that -is selected from the list above will be the one with the highest number that is -triggered by the below rules: Funding Output Update > Commitment Update > Rules -Change. - -- If either channel party changes `dust_limit_satoshis`: Commitment Update -- If either channel party changes `max_htlc_value_in_flight_msat`: Rules Change -- If either channel party changes `channel_reserve_satoshis`: Rules Change -- If either channel party changes `to_self_delay`: Commitment Update -- If either channel party changes `max_accepted_htlcs`: Rules Change -- If either channel party changes `funding_pubkey`: Funding Output Update -- If new `channel_type` requires different funding output script than the old - `channel_type`: Funding Output Update -- If new `channel_type` requires the same funding output script as the old - `channel_type`: Commitment Update - -### Rules Change - -If all that is required to execute the terms of the dynamic commitment -negotiation is a rules change, then channel operation may resume as normal -under the new rules. It is possible that the current channel state would violate -the constraints specified by the new rules. When we execute a rules change, only -new channel states will be evaluated against the new rules. As long as the new -channel state moves towards the constraint boundary, it will be accepted. - -### Commitment Update - -If an update to the commitment transaction is required to execute the terms of -the dynamic commitment negotiation, then once both channel parties have -irrevocably committed to a state with no HTLC outputs, new commitment signatures -MUST be exchanged. This requires both parties to send `commitment_signed` -messages that adhere to the new channel parameters. Once a node has received -a `commitment_signed` in accordance with the new channel parameters, it MUST -issue a `revoke_and_ack` as it normally would. Once both nodes have done this, -normal channel operation is resumed. - -### Funding Output Change: General Protocol - -If a Funding Output Change is required, then new commitment signatures AND -kickoff signatures MUST be exchanged. To accomplish this, the following steps -are taken: - -1. Build kickoff transaction -1. Build commitment transaction that spends kickoff output -1. Issue a `commitment_signed` message _according to new channel parameters_ -1. Upon receipt of the remote party's `commitment_signed` message, issue a -`kickoff_sig` message. -1. Upon receipt of the remote party's `kickoff_sig` message, issue a -`revoke_and_ack` for the _final commitment_ built off of the _original funding -output_. - -#### Message flow to upgrade a channel to simple-taproot: - - +-------+ +-------+ - | |--(1)---- commit_signed------->| | - | | | | - | |<-(2)---- commit_signed -------| | - | | | | - | | | | - | |<-(3)----- kickoff_sig --------| | - | A | | B | - | |--(4)----- kickoff_sig ------->| | - | | | | - | | | | - | |--(5)---- revoke_and_ack ----->| | - | | | | - | |<-(6)---- revoke_and_ack ------| | - +-------+ +-------+ - -##### Rationale - -The commitment signed message has to be issued first to ensure that the money -locked to the new funding output (created by the kickoff transaction) can be -unilaterally recovered. If the `kickoff_sig` were sent first, the receiver could -stop responding and broadcast the kickoff transaction, burning the funds for -both parties. If the channel balance is overwhelmingly imbalanced towards the -side issuing the `kickoff_sig`, this could be costly to the victim while being -comparatively cheap for the attacker. - -Similarly, if we `revoke_and_ack` prior to receiving a `kickoff_sig` then we -may have a situation where we remove our ability to broadcast the old commitment -transaction before the path to the new commitment transaction has been fully -signed. - -#### Building the Kickoff Transaction - -##### Kickoff Transaction Structure - -* version: 2 -* locktime: 0 -* txin count: 1 - * `txin[0]` outpoint: `txid` and `output_index` from `funding_created` message - * `txin[0]` sequence: 0xfffffffd - * `txin[0]` script bytes: 0 - * `txin[0]` witness: `0 ` -* txout count: 3 - * `txout[0]`: `anchor_output_1` or `anchor_output_2` - * `txout[1]`: `anchor_output_1` or `anchor_output_2` - * `txout[2]`: `kickoff_funding_output` - -The anchor outputs have a value of 330 satoshis. They are encumbered by a -version 1 witness script: -* `OP_1 anchor_output_key` -* where: - * `anchor_internal_key = original_local_funding_pubkey/original_remote_funding_pubkey` - * `anchor_output_key = anchor_internal_key + tagged_hash("TapTweak", anchor_internal_key || anchor_script_root)` - * `anchor_script_root = tapscript_root([anchor_script])` - * `anchor_script`: - ``` - OP_16 OP_CHECKSEQUENCEVERIFY - ``` - -The new funding output has a value of the original funding output minus the sum -of 660 satoshis and this kickoff transaction's fee. It is encumbered by a -version 1 witness script where `taproot_funding_key1/taproot_funding_key2` are -from `dyn_ack`: -* `OP_1 funding_key` -* where: - * `funding_key = combined_funding_key + tagged_hash("TapTweak", combined_funding_key)*G` - * `combined_funding_key = musig2.KeyAgg(musig2.KeySort(taproot_funding_key1, taproot_funding_key2))` - -##### Kickoff Transaction Construction Algorithm - -1. Initialize the commitment transaction version and locktime. -2. Initialize the commitment transaction input. -3. Calculate this kickoff transaction's fee via `kickoff_feerate_per_kw`* - `kickoff_transaction_weight`/1000, making sure to round down. Subtract this - value from the new funding output. -5. Subtract two times the fixed anchor size of 330 satoshis from the new funding - output. -6. Add a funding output with the new funding amount. -7. Add an anchor output for each party. -8. Sort the outputs into BIP 69+CLTV order. - -#### Building the Commitment Transaction - -##### Commitment Transaction Structure - -* version: 2 -* locktime: upper 8 bits are 0x20, lower 24 bits are the lower 24 bits of the - obscured commitment number -* txin count: 1 - * `txin[0]` outpoint: the `kickoff_funding_output` - * `txin[0]` sequence: upper 8 bits are 0x80, lower 24 bits are upper 24 bits - of the obscured commitment number - * `txin[0]` script bytes: 0 - * `txin[0]` witness: `` - -The 48-bit commitment number is computed by `XOR` as described in BOLT#03. - -##### Commitment Transaction Construction Algorithm - -1. Initialize the commitment transaction version and locktime. -2. Initialize the commitment transaction input. -3. Calculate which committed HTLCs need to be trimmed. -4. Calculate the commitment transaction fee via - commitment feerate * `commitment_transaction_weight`/1000, making sure to - round down. Subtract this from the funder's output. -5. Subtract four times the fixed anchor size of 330 satoshis from the funder's - output. Two of the anchors are from the commitment transaction and two are - from the kickoff transaction. -6. Subtract the matching kickoff transaction's fee from the funder's output. -7. For every offered HTLC, if it is not trimmed, add an offered HTLC output. -8. For every received HTLC, if it is not trimmed, add a received HTLC output. -9. If the `to_local` output is greater or equal to the dust limit, add a - `to_local` output. -10. If the `to_remote` output is greater or equal to the dust limit, add a - `to_remote` output. -11. If `to_local` exists or there are untrimmed HTLCs, add a `to_local_anchor`. -12. If `to_remote` exists or there are untrimmed HTLCs, add a - `to_remote_anchor`. The `to_remote_anchor` uses the remote party's - `taproot_funding_key`. -13. Sort the outputs into BIP 69+CLTV order. - -#### Issuing the `commitment_signed` message - -Commitment signed messages are exchanged as normal with the exception of a -different construction procedure detailed in the prior step. NOTE: "as normal" -means that this message MUST include all TLVs that would be required for -the updated `channel_type` e.g. Musig2 Taproot. - -#### Issuing the `kickoff_sig` message - -##### kickoff_sig - -The kickoff_sig is a message containing a signature that the fundee sends to the -funder who then combines it with their own signature to spend from the original -funding outpoint into the new musig2 output. To keep things simple, no -additional inputs are added to the intermediate transaction. An anchor output is -attached to either side for fee-bumping. - -![Cannot display image](./dynamic-commits/kickoff%20tx.png "Kickoff transaction") - -1. type: 777 (`kickoff_sig`) -2. data: - * [`32*byte`:`channel_id`] - * [`signature`:`signature`] +For the parameter changes outlined in this proposal, no specific execution is +required. The next commitment transaction issued by each peer is expected to +abide by the new parameters. -##### Requirements - -The sending node: - - MUST set `channel_id` to a valid channel it has with the recipient. - - MUST NOT send this message before receiving the peer's `commitment_signed`. +_NOTE FOR REVIEWERS_: Should we eagerly issue new commitment transactions here? +As writtent we commit to the new values lazily in the next commitment +transaction that would normally be issued. -The receiving node: - - MUST send an `error` and fail the channel if `channel_id` does not match an - existing channel it has with the sender. - - MUST send an `error` and fail the channel if `signature` is not valid for - the kickoff transaction as constructed above OR non-compliant with the - LOW-S-standard rule. [LOWS](https://github.com/bitcoin/bitcoin/pull/6769) - - MUST NOT send a `revoke_and_ack` for the final pre-dynamic commitment - transaction until it has received a valid `kickoff_sig` - -##### Rationale - -The `kickoff_sig` cannot be issued until the `commitment_signed` message has -been received to prevent griefing by broadcasting a kickoff for which there is -no exit. The `revoke_and_ack` for the last pre-dynamic commitment has to wait -for the `kickoff_sig` because if the last commitment built off of the original -funding output is revoked before the `kickoff_sig` has been received, then if -a peer becomes non-cooperative from that point forward, funds are effectively -burned. - -### Additional Requirements: ~Musig2 Taproot -> Musig2 Taproot - -This section describes how dynamic commitments can upgrade regular channels to -simple taproot channels. The regular dynamic proposal phase is executed followed -by a signing phase. A `channel_type` of `option_taproot` will be included in -`dyn_propose` and both sides must agree on it. The `initiator` of the upgrade -will also propose a feerate to use for an intermediate "kickoff" transaction. - -#### Required `dyn_propose` TLVs: - -1. `tlv_stream`: `dyn_propose_tlvs` -2. types: - 1. type: 5 (`funding_pubkey`) - 2. data: - * [`point`:`senders_funding_pubkey`] - 1. type: 6 (`channel_type`) - 2. data: - * [`...*byte`:`type`] - 1. type: 7 (`kickoff_feerate`) - 2. data: - * [`u32`:`kickoff_feerate_per_kw`] - -#### Requirements - -The sending node: - - if it is the `initiator`: - - MUST only send `kickoff_feerate` if they can pay for the kickoff - transaction fee and the anchor outputs, while adhering to the - `channel_reserve` restriction. - - MUST set `taproot_funding_key` to a valid secp256k1 compressed public key. - - SHOULD use a sufficiently high `kickoff_feerate` to be prepared for - worst-case fee environment scenarios. - - *NOTE FOR REVIEWERS*: We can also add a message to update the kickoff fee - rate if we have revocable kickoffs, similar to `update_fee` for commitment - transactions to make sure the kickoff has a sufficient fee to enter the - mempool. Anchors can be used to fee bump the kickoff beyond the min mempool - fee. Revocable kickoffs are possible but significantly increase the design - complexity. - -The receiving node: - - if it is the `responder`: - - MUST reject the `dyn_propose` if the `initiator` cannot pay for the kickoff - transaction fee and the anchor outputs. - - MUST reject the `dyn_propose` if, after calculating the amount of the new - funding output, the new commmitment transaction would not be able to pay - for any outputs at the current commitment feerate. - - MUST reject the `dyn_propose` if `taproot_funding_key` is not a valid - secp256k1 compressed public key. - - MAY reject the `dyn_propose` if it does not agree with the `channel_type` - -#### Rationale - -The `dyn_propose` renegotiates the funding keys as otherwise signatures for the -funding keys would be exchanged in both the ECDSA and Schnorr contexts. This can -lead to an attack outlined in [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#alternative-signing). -Renegotiating funding keys avoids this issue. Note that the various basepoints -exchanged in `open_channel` and `accept_channel` are not renegotiated. Because -the private keys _change_ with each commitment transaction they sign due to the -`per_commitment_point` construction, the basepoints can be used in both ECDSA -and Schnorr contexts. - -#### Extensions to `dyn_ack`: - -1. `tlv_stream`: `dyn_ack_tlvs` -2. types: - 1. type: 0 (`local_musig2_pubnonce`) - 2. data: - * [`66*byte`:`nonces`] - -#### Requirements - -The sending node: - - if it is accepting a `channel_type` of `simple_taproot_channel`: - - MUST set `local_musig2_pubnonce` to the nonce that it will use to verify - local commitments. - -The receiving node: - - MUST send an `error` and fail the channel if `local_musig2_pubnonce` cannot - be parsed as two compressed secp256k1 points. - -# Appendix - -## Pinning - -![Cannot display image](./dynamic-commits/kickoff%20pinning.png "Kickoff transaction pinned") - -Originally, Bitcoin Core's default mempool settings allowed an unconfirmed -transaction to have up to 25 decendants in the mempool. Past this limit, any -descendants would be rejected. This was used as a DoS mitigation in Bitcoin Core -and affected the security of LN channels. Before the anchors commitment type was -introduced, pinning in the LN was where a counterparty broadcasted the -commitment transaction and created a chain of 25 descendants spending from one -of the commitment's outputs. The time-sensitive commitment transaction could be -"pinned" to the bottom of the mempool. This was addressed with a change to -Bitcoin Core called CPFP Carve-out. - -### CPFP Carve-out - -CPFP Carve-out was introduced to Bitcoin Core in -https://github.com/bitcoin/bitcoin/pull/15681. If a Bitcoin node receives a -transaction that is rejected due to any of the mempool size or -ancestor/descendant restrictions being hit, it will try to accept the -transaction again. This second try will succeed only if: - - the transaction is 40kWU or less - - it has only one ancestor in the mempool - -This change, in conjunction with the anchor commitment type, decreases the -efficacy of the pinning attack since the honest party can still attach an anchor -despite the descendant size limit being hit. - -### Dynamic Commitments & CPFP Carve-out - -The safety guarantees of CPFP Carve-out break due to the structure of the -kickoff transaction. The kickoff transaction contains 3 spendable outputs: the -local party's anchor, the remote party's anchor, and the new funding output. All -three of these outputs can be spent immediately. A malicious counterparty can -pin the kickoff transaction by: - - spending from their anchor output to create a descendant chain of 25 - transactions _AND_ - - spending from the new funding output using the new commitment transaction, - "using up" the CPFP Carve-out slot designated for the honest party. -_NOTE FOR REVIEWERS_: The semantics of CPFP carve-out are not entirely clear -as to whether or not there is only _one_ CPFP-Carve-Out "slot" or if the only -two requirements are the 40kWU limit and a single unconfirmed ancestor. If we -have more than one "slot" available, this is no longer a concern. - -Depending on fee conditions, it may not be possible for the honest party to get -these transactions confirmed until the mempool clears up. - -If we were to get rid of the kickoff transaction's anchor outputs, the problem -still arises. A malicious counterparty could still pin the kickoff transaction -by: - - broadcasting the commitment transaction - - spending from their commitment anchor output and creating a descendant chain - of 25 transactions - -The honest party is unable to use their anchor on the commitment transaction as: - - the descendant limit of 25 transactions has been hit - - the anchor spend would have 2 ancestors (the commitment and kickoff - transactions) - -### Reducing Risk - -The above pinning scenarios highlight the complexity of second-layer protocols -and mempool restrictions. In this proposal, pinning is _still_ possible, but -risk can be controlled if nodes reduce their max_htlc_value_in_flight_msat -values while the kickoff transaction is unconfirmed - -If we allow adding HTLCs _before_ the kickoff transaction confirmed on-chain, -the pinning attack has a tangible benefit: the ability to steal the value of an -HTLC. - -### Asymmetric Kickoffs - -The above proposal specifies a process wherein we can reanchor the funding -output exactly once. This is because even if we revoke all commitment -transactions built off of the first kickoff transaction, we still are vulnerable -to griefing if we do not revoke the kickoff transaction itself. In this case -one party may choose to burn all funds in a channel by broadcasting the kickoff -transaction when no unrevoked commitment transactions remain. To deal with this -we can either only reanchor once, as proposed above, allowing us to guarantee we -will never encounter a situation where there are no valid commitment -transactions, or we can make the kickoff transactions revocable. - -To make them revocable we can reuse the same scheme that we use for commitment -transactions. In this case Alice's kickoff transaction would allow Bob to claim -all funds if Bob knows Alice's revocation secret. Similarly, Alice could claim -all channel funds if Bob broadcasts his kickoff transaction and Alice knows -Bob's revocation secret. - -An unfortunate consequence of this scheme is that since we now have two possible -"new" funding outputs (one for each of the potential kickoff transactions), we -now have to send all of our commitment signatures in pairs. At any given time -there would be four valid commitment transactions: - -1. Alice's commitment built off of Alice's kickoff -2. Alice's commitment built off of Bob's kickoff -3. Bob's commitment built off of Alice's kickoff -4. Bob's commitment built off of Bob's kickoff - -_NOTE FOR REVIEWERS_: There may be an opportunity to make the kickoff -transactions symmetric while still allowing them to be revocable using adaptor -signature tricks, but this will require more research from those with a deeper -understanding of the cryptographic primitives. - -## Weights - -Since DER-encoded signatures vary in size, we assume a worst-case signature size -of 73 bytes to keep things simple. The kickoff transaction has an -_expected weight_ of 944WU and the commitment transaction has an -_expected weight_ of 960WU. - -General weights: - * p2tr: 34 bytes - - OP_1: 1 byte - - OP_DATA: 1 byte (witness_script_SHA256 length) - - witness_script_SHA256: 32 bytes - - * witness_header: 2 bytes - - flag: 1 byte - - marker: 1 byte - -### Kickoff Transaction Weights - - * funding_output_script: 71 bytes - - OP_2: 1 byte - - OP_DATA: 1 byte (pub_key_alice length) - - pub_key_alice: 33 bytes - - OP_DATA: 1 byte (pub_key_bob length) - - pub_key_bob: 33 bytes - - OP_2: 1 byte - - OP_CHECKMULTISIG: 1 byte - - * funding_input_witness: 222 bytes - - number_of_witness_elements: 1 byte - - nil_length: 1 byte - - sig_alice_length: 1 byte - - sig_alice: 73 bytes - - sig_bob_length: 1 byte - - sig_bob: 73 bytes - - witness_script_length: 1 byte - - witness_script: 71 bytes (funding_output_script) - - * kickoff_txin_0: 41 bytes (excl. witness) - - previous_out_point: 36 bytes - - hash: 32 bytes - - index: 4 bytes - - var_int: 1 byte (script_sig length) - - script_sig: 0 bytes - - witness: <---- part of the witness data - - sequence: 4 bytes - - * musig2_funding_output: 43 bytes - - value: 8 bytes - - var_int: 1 byte (pk_script length) - - pk_script (p2tr): 34 bytes - - * anchor_output: 43 bytes - - value: 8 bytes - - var_int: 1 byte (pk_script length) - - pk_script (p2tr): 34 bytes - - * kickoff_transaction: 180 bytes (excl. witness) - - version: 4 bytes - - witness_header: <---- part of the witness data - - count_tx_in: 1 byte - - tx_in: 41 bytes - - kickoff_txin_0: 41 bytes - - count_tx_out: 1 byte - - tx_out: 129 bytes - - musig2_funding_output: 43 bytes - - anchor_output_local: 43 bytes - - anchor_output_remote: 43 bytes - - lock_time: 4 bytes - - - Multiplying non-witness data by 4 gives a weight of: - - kickoff_transaction_weight = 180vbytes * 4 = 720WU - - Adding the witness data: - - kickoff_transaction_weight += (funding_input_witness + witness_header) - - kickoff_transaction_weight = 944WU - -### Commitment Transaction Weights - -Here we assume that both parties have an output on the commitment transaction. -This is to keep the weight consistent across potentially different commitment -transactions. - - * musig2_funding_input_witness: 66 bytes - - number_of_witness_elements: 1 byte - - musig2_signature_length: 1 byte - - musig2_signature: 64 bytes - - * commitment_txin_0: 41 bytes (excl. witness) - - previous_out_point: 36 bytes - - hash: 32 bytes - - index: 4 bytes - - var_int: 1 byte (script_sig length) - - script_sig: 0 bytes - - witness: <---- part of the witness data - - sequence: 4 bytes - - * to_local: 43 bytes - - value: 8 bytes - - var_int: 1 byte (pk_script length) - - pk_script (p2tr): 34 bytes - - * to_remote: 43 bytes - - value: 8 bytes - - var_int: 1 byte (pk_script length) - - pk_script (p2tr): 34 bytes - - * to_local_anchor: 43 bytes - - value: 8 bytes - - var_int: 1 byte (pk_script length) - - pk_script (p2tr): 34 bytes - - * to_remote_anchor: 43 bytes - - value: 8 bytes - - var_int: 1 byte (pk_script length) - - pk_script (p2tr): 34 bytes - - * commitment_transaction: 225 bytes (excl. witness) - - version: 4 bytes - - witness_header: <---- part of the witness data - - count_tx_in: 1 byte - - tx_in: 41 bytes - - commitment_txin_0: 41 bytes - - count_tx_out: 3 byte - - tx_out: 172 bytes - - to_local: 43 bytes - - to_remote: 43 bytes - - to_local_anchor: 43 bytes - - to_remote_anchor: 43 bytes - - lock_time: 4 bytes - - - Multiplying non-witness data by 4 gives a weight of: - - commitment_transaction_weight = 223vbytes * 4 = 892WU - - Adding the witness data: - - commitment_transaction_weight += (musig2_funding_input_witness + witness_header) - - commitment_transaction_weight = 960WU +At this point the channel is no longer considered quiescent. From 54eeb5ba973980c8bdc1db280ec4616a57f16cf9 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Mon, 26 Aug 2024 16:09:42 -0600 Subject: [PATCH 20/23] re-add dynamic funding output specification in new doc --- ext-dynamic-funding-outputs.md | 850 +++++++++++++++++++++++++++++++++ 1 file changed, 850 insertions(+) create mode 100644 ext-dynamic-funding-outputs.md diff --git a/ext-dynamic-funding-outputs.md b/ext-dynamic-funding-outputs.md new file mode 100644 index 000000000..e91792720 --- /dev/null +++ b/ext-dynamic-funding-outputs.md @@ -0,0 +1,850 @@ +# Extension Bolt ZZZ: Dynamic Funding Outputs + +Authors: + * Keagan McClelland + * Olaoluwa Osuntokun + * Eugene Siegel + +Created: TODO + +# Table of Contents + +TODO + +# Introduction + +## Abstract + +This document describes a protocol for changing channel parameters that affect +the funding output script of the channel. Implementation of the protocol +described in this document will enable channel peers to re-negotiate and update +the `funding_pubkey` as well as certain conversions between `channel_type`s +while avoiding UTXO churn therefore preserving the continuity of identity for +the channel whose terms are being changed. This proposal depends on +[Dynamic Commitments](/ext-dynamic-commitments.md). + +## Motivation + +While the Dynamic Commitments proposal allows for a number of channel parameters +to be changed after it has been opened, it leaves a few of them out because +changing these parameters would require a funding output change. + +Notable in particular, is that one of the channel parameters we wish to +renegotiate is the the `channel_type` itself. While certain `channel_type` +conversions are possible with the Dynamic Commitments proposal, one of them that +isn't is converting to Simple Taproot Channels (STCs). With STCs, we have the +opportunity to take advantage of the cost savings and privacy capabilities +afforded by the 2021 Taproot Soft Fork, with further aspirations to be able to +deploy Point Time-Lock Contracts (PTLCs) to the Lightning Network. The sooner +that network participants can upgrade to STCs the more we will have the +necessary network infrastructure to be able to make effective use of PTLCs when +the protocols for them are ready for deployment. + +Due to the design of STCs, and the fact that they take full advantage of the +capabilities afforded by Schnorr Signatures, there is no way to construct a +valid `channel_announcement` message that references the output corresponding to +the nodes' joint public key. As such, even if we were to directly spend an +existing funding output to a new STC funding output, and even with the provision +in BOLT 7 to delay graph pruning by 12 blocks after the channel point is spent, +we have no way of advertising the STC to the network at the time of writing of +this proposal. + +That said, there is a development effort, concurrent with this, for a new gossip +system that is capable of understanding the announcements of new STCs. However, +even with a new gossip system capable of understanding the STC construction and +announcement, it will take quite some time for such a system to be broadly +deployed across the Lightning Network. In the interim, to combat the +disincentive of upgrading to STCs, this proposal to enable the change of these +channel parameters (including channel types) without requiring channel turnover +is submitted. + +## Preliminaries + +This proposal includes a detailed section on the preliminaries to document some +of the rationale for the design that is presented later. If you are a Bitcoin +and Lightning Network protocol expert or you are uninterested in the thought +process behind what is presented here, you may wish to skip to the Design +Overview section to save time. + +### Channel Opening Parameters + +As described in the Dynamic Commitments proposal, during the channel opening +procedure there are a number of parameters specified in the `open_channel` and +`accept_channel` messages that remain static over the lifetime of the channel. +A subset of these are updatable using other messages defined in the protocol. +However, after accounting for the channel parameters that can be changed using +existing mechanisms, there remains a list of parameters which are unchangeable. + +- funding_pubkey +- revocation_basepoint +- payment_basepoint +- delayed_payment_basepoint +- htlc_basepoint +- first_per_commitment_point +- channel_flags +- upfront_shutdown +- channel_type* + +*`channel_type` can be updated using Dynamic Commitments only if the +funding output scripts are identical between the `channel_type` prior to the +change and following it. + +As mentioned in Dynamic Commitments, we determined that the basepoint values +don't make sense to rotate. There is no obvious value in doing so and it carries +additional administrative costs to rotate them. Finally, changing the +`upfront_shutdown` script over the lifetime of the channel is self-defeating and +so we continue to exclude it as well. The list of channel parameters +remaining after we filter out these values is thus: + +- funding_pubkey +- channel_type (~Musig2 Taproot -> Musig2 Taproot) + +### Gossip Verification + +It is at this point that we need to take a brief detour and review how the +broader Lightning Network comes to discover and verify the existence of public +channels. When the funding transaction for a channel has confirmed, the +participating parties will jointly produce a message that attests to their +ownership of the UTXO and its viability as a routing edge for payment senders. + +BOLT 7 details all of the specifics of this message and how it is computed but +one of the notable aspects of this process is that the receivers of these gossip +messages verify that the UTXO underwriting the channel must be a P2WSH output +with a pre-defined script using the participants' public keys, specified in +BOLT 3. This will present issues for us which will become clearer in the next +section. + +While alternative gossip systems that can describe STCs are being designed, they +have not been deployed in any known implementation of the Lightning Network +Protocol and even when such a design is implemented, there will be a prolonged +period of time wherein a substantial number of nodes on the network will remain +unable to process messages of this variety, rendering useless any channels that +can only be announced in this manner. + +### Taproot + +This brings us to talking about what channel constructions are actually +inexpressible by the existing gossip system. As we alluded to earlier, Taproot +channels cannot be discovered using the existing gossip message structure and +interpretation. + +In November of 2021 the "Taproot" upgrade was activated on Bitcoin's mainnet, +creating a new output type that is subsequently useful to higher layer protocols +such as the Lightning Network. Since then, the Lightning Network protocol +designers have offered a proposal for a channel construction that makes use of +the Taproot output type. It is beyond the scope of this document to make a +thorough case for why such a channel construction is useful but we assume that +it is for our purposes here. + +While Taproot channels are useful, they present some novel challenges with +respect to network-wide interoperability. Notably, a useful Taproot channel +construction must by definition make use of the new Taproot output type, which +does not and cannot use the output script format detailed in BOLT 3 for the +funding output. Pairing this fact with what we described in the previous +section, it is necessarily the case that the funding output of a Taproot channel +cannot be properly announced by the current gossip system. + +## Design Overview + +The main goal of this proposal is to be able to change all of the channel +parameters that affect the funding output script of the channel. + +The key insight in this design is that we extend the concept of a commitment +transaction to include the possibility of a pair of transactions wherein we have +a "kickoff transaction" that is comprised of a single input (the original +funding output) and a single output (the new funding output) and then building +the new commitment transaction off of the new funding output in whatever manner +is detailed in the specification for the target channel type. This may not +always be necessary, but it is certainly necessary for using this proposal to +convert existing channels into Taproot channels. + +# Specification + +There are two phases to this channel upgrade process: proposal, and execution. +During the proposal phase the only goal is to agree on a set of updates to the +current channel state machine. The proposal phase is identical to that of +Dynamic Commitments and can be rolled into the same negotiation. + +Assuming an agreement can be reached, we will proceed to the execution phase. +During the execution phase, we apply the updates to the channel state machine, +exchanging the necessary information to be able to apply those updates. + +## Proposal Phase + +As a prerequisite to the proposal phase of a Dynamic Commitment negotiation, the +channel must be in a [quiesced](https://github.com/lightning/bolts/pull/869) +state. + +### Node Roles + +In every dynamic commitment negotiation, there are two roles: the `initiator` +and the `responder`. It is necessary for both nodes to agree on which node is +the `initiator` and which node is the `responder`. This is important because if +this flavor of dynamic commitment negotiation results in a re-anchoring step +and it is the `initiator` that is responsible for paying the fees for the +kickoff transaction. The `initiator` is determined by who has the `initiator` +role established by the quiescence process. + +### Additional Negotiation TLVs + +The following TLVs are used throughout the negotiation phase of the protocol +and are common to all messages in the negotiation phase. + +#### channel_type + +- type: 10 + data: + * [`...*byte`:`channel_type`] + +#### funding_pubkey + +- type: 12 + data: + * [`point`:`funding_pubkey`] + +- #### kickoff_feerate_per_kw + +- type: 14 + data: + * [`u32`:`kickoff_feerate_per_kw`] + +### Proposal Messages + +The proposal messages are reused from Dynamic Commitments. + +#### `dyn_propose` + +This message is sent to negotiate the parameters of a dynamic commitment +upgrade. The overall protocol flow is depicted below. This message is always +sent by the `initiator`. The new TLVs above can be sent along with the TLVs +detailed in the original Dynamic Commitments proposal. + + +-------+ +-------+ + | |--(1)------ dyn_propose ------>| | + | A | | B | + | |<-(4)---{dyn_ack|dyn_reject}---| | + +-------+ +-------+ + +1. type: 111 (`dyn_propose`) +2. data: + * [`32*byte`:`channel_id`] + * [`dyn_propose_tlvs`:`tlvs`] + +1. `tlv_stream`: `dyn_propose_tlvs` +2. types: + 1. type: 0 (`dust_limit_satoshis`) + 2. data: + * [`u64`:`dust_limit_satoshis`] + 1. type: 2 (`max_htlc_value_in_flight_msat`) + 2. data: + * [`u64`:`senders_max_htlc_value_in_flight_msat`] + 1. type: 4 (`channel_reserve_satoshis`) + 2. data: + * [`u64`:`recipients_channel_reserve_satoshis`] + 1. type: 6 (`to_self_delay`) + 2. data: + * [`u16`:`recipients_to_self_delay`] + 1. type: 8 (`max_accepted_htlcs`) + 2. data: + * [`u16`:`senders_max_accepted_htlcs`] + 1. type: 10 (`channel_type`) + 2. data: + * [`...*byte`:`channel_type`] + 1. type: 12 (`funding_pubkey`) + 2. data: + * [`point`:`senders_funding_pubkey`] + 1. type: 14 (`kickoff_feerate_per_kw`) + 2. data: + * [`u32`:`kickoff_feerate_per_kw`] + +##### Requirements + +The sending node: + - MUST conform to all requirements in Dynamic Commitments + - if either `funding_pubkey` is set or the new `channel_type` and the old + `channel_type` differ in their funding output scripts: + - MUST set `kickoff_feerate_per_kw` + +The receiving node: + - MUST conform to all requirements in Dynamic Commitments + +##### Rationale + +Since the funding output change implies a new transaction that is used to move +the channel funds from the old funding output script to the new one, this +transaction needs to have a mutually agreed upon feerate. + +#### `dyn_ack` + +This message is sent in response to a `dyn_propose` indicating that it has +accepted the proposal. + +1. type: 113 (`dyn_ack`) +2. data: + * [`32*byte`:`channel_id`] + +##### Requirements + +The requirements for `dyn_ack` are identical to those in Dynamic Commitments. + +#### `dyn_reject` + +This message is sent in response to a `dyn_propose` indicating that it rejects +the proposal. + +1. type: 115 (`dyn_reject`) +2. data: + * [`32*byte`:`channel_id`] + * [`...*byte`:`update_rejections`] + +##### Requirements + +The requirements for `dyn_reject` are identical to those in Dynamic Commitments. + +## Reestablish + +### `channel_reestablish` + +A new TLV that denotes the node's current `dyn_epoch_height` is included. + +1. `tlv_stream`: `channel_reestablish_tlvs` +2. types: + 1. type: 20 (`dyn_epoch_height`) + 2. data: + * [`u64`:`dyn_epoch_height`] + +#### Requirements + +The requirements for `channel_reestablish` are identical to those in Dynamic +Commitments. + +## Execution Phase + +A Funding Output Update is executed by exchanging signatures for a transaction +that spends the original funding output into a new funding output. + - NOTE FOR REVIEWERS: This transaction is currently symmetric which burdens + us with the constraint that a reanchoring step can only be done once over the + lifetime of the channel. If we want to be able to securely do this multiple + times, we must make kickoff transactions revocable, and therefore asymmetric, + and therefore must start issuing commitment signatures in pairs. See Appendix + for details. + +To remain congruent with the Dynamic Commitments proposal, these extra +signature messages should be exchanged exactly once per funding output change +and they should be exchanged during the next commitment signature exchange. + +### Funding Output Change: General Protocol + +If a Funding Output Change is required, then new commitment signatures AND +kickoff signatures MUST be exchanged. To accomplish this, the following steps +are taken: + +1. Build kickoff transaction +1. Build commitment transaction that spends kickoff output +1. Issue a `commitment_signed` message _according to new channel parameters_ +1. Upon receipt of the remote party's `commitment_signed` message, issue a +`kickoff_sig` message. +1. Upon receipt of the remote party's `kickoff_sig` message, issue a +`revoke_and_ack` for the _final commitment_ built off of the _original funding +output_. + +#### Message flow to upgrade a channel to simple-taproot: + + +-------+ +-------+ + | |--(1)---- commit_signed------->| | + | | | | + | |<-(2)---- commit_signed -------| | + | | | | + | | | | + | |<-(3)----- kickoff_sig --------| | + | A | | B | + | |--(4)----- kickoff_sig ------->| | + | | | | + | | | | + | |--(5)---- revoke_and_ack ----->| | + | | | | + | |<-(6)---- revoke_and_ack ------| | + +-------+ +-------+ + +##### Rationale + +The commitment signed message has to be issued first to ensure that the money +locked to the new funding output (created by the kickoff transaction) can be +unilaterally recovered. If the `kickoff_sig` were sent first, the receiver could +stop responding and broadcast the kickoff transaction, burning the funds for +both parties. If the channel balance is overwhelmingly imbalanced towards the +side issuing the `kickoff_sig`, this could be costly to the victim while being +comparatively cheap for the attacker. + +Similarly, if we `revoke_and_ack` prior to receiving a `kickoff_sig` then we +may have a situation where we remove our ability to broadcast the old commitment +transaction before the path to the new commitment transaction has been fully +signed. + +#### Building the Kickoff Transaction + +##### Kickoff Transaction Structure + +* version: 2 +* locktime: 0 +* txin count: 1 + * `txin[0]` outpoint: `txid` and `output_index` from `funding_created` message + * `txin[0]` sequence: 0xfffffffd + * `txin[0]` script bytes: 0 + * `txin[0]` witness: `0 ` +* txout count: 3 + * `txout[0]`: `anchor_output_1` or `anchor_output_2` + * `txout[1]`: `anchor_output_1` or `anchor_output_2` + * `txout[2]`: `kickoff_funding_output` + +The anchor outputs have a value of 330 satoshis. They are encumbered by a +version 1 witness script: +* `OP_1 anchor_output_key` +* where: + * `anchor_internal_key = original_local_funding_pubkey/original_remote_funding_pubkey` + * `anchor_output_key = anchor_internal_key + tagged_hash("TapTweak", anchor_internal_key || anchor_script_root)` + * `anchor_script_root = tapscript_root([anchor_script])` + * `anchor_script`: + ``` + OP_16 OP_CHECKSEQUENCEVERIFY + ``` + +The new funding output has a value of the original funding output minus the sum +of 660 satoshis and this kickoff transaction's fee. It is encumbered by a +version 1 witness script where `taproot_funding_key1/taproot_funding_key2` are +from `dyn_ack`: +* `OP_1 funding_key` +* where: + * `funding_key = combined_funding_key + tagged_hash("TapTweak", combined_funding_key)*G` + * `combined_funding_key = musig2.KeyAgg(musig2.KeySort(taproot_funding_key1, taproot_funding_key2))` + +##### Kickoff Transaction Construction Algorithm + +1. Initialize the commitment transaction version and locktime. +2. Initialize the commitment transaction input. +3. Calculate this kickoff transaction's fee via `kickoff_feerate_per_kw`* + `kickoff_transaction_weight`/1000, making sure to round down. Subtract this + value from the new funding output. +5. Subtract two times the fixed anchor size of 330 satoshis from the new funding + output. +6. Add a funding output with the new funding amount. +7. Add an anchor output for each party. +8. Sort the outputs into BIP 69+CLTV order. + +#### Building the Commitment Transaction + +##### Commitment Transaction Structure + +* version: 2 +* locktime: upper 8 bits are 0x20, lower 24 bits are the lower 24 bits of the + obscured commitment number +* txin count: 1 + * `txin[0]` outpoint: the `kickoff_funding_output` + * `txin[0]` sequence: upper 8 bits are 0x80, lower 24 bits are upper 24 bits + of the obscured commitment number + * `txin[0]` script bytes: 0 + * `txin[0]` witness: `` + +The 48-bit commitment number is computed by `XOR` as described in BOLT#03. + +##### Commitment Transaction Construction Algorithm + +1. Initialize the commitment transaction version and locktime. +2. Initialize the commitment transaction input. +3. Calculate which committed HTLCs need to be trimmed. +4. Calculate the commitment transaction fee via + commitment feerate * `commitment_transaction_weight`/1000, making sure to + round down. Subtract this from the funder's output. +5. Subtract four times the fixed anchor size of 330 satoshis from the funder's + output. Two of the anchors are from the commitment transaction and two are + from the kickoff transaction. +6. Subtract the matching kickoff transaction's fee from the funder's output. +7. For every offered HTLC, if it is not trimmed, add an offered HTLC output. +8. For every received HTLC, if it is not trimmed, add a received HTLC output. +9. If the `to_local` output is greater or equal to the dust limit, add a + `to_local` output. +10. If the `to_remote` output is greater or equal to the dust limit, add a + `to_remote` output. +11. If `to_local` exists or there are untrimmed HTLCs, add a `to_local_anchor`. +12. If `to_remote` exists or there are untrimmed HTLCs, add a + `to_remote_anchor`. The `to_remote_anchor` uses the remote party's + `taproot_funding_key`. +13. Sort the outputs into BIP 69+CLTV order. + +#### Issuing the `commitment_signed` message + +Commitment signed messages are exchanged as normal with the exception of a +different construction procedure detailed in the prior step. NOTE: "as normal" +means that this message MUST include all TLVs that would be required for +the updated `channel_type` e.g. Musig2 Taproot. + +#### Issuing the `kickoff_sig` message + +##### kickoff_sig + +The kickoff_sig is a message containing a signature that the fundee sends to the +funder who then combines it with their own signature to spend from the original +funding outpoint into the new musig2 output. To keep things simple, no +additional inputs are added to the intermediate transaction. An anchor output is +attached to either side for fee-bumping. + +![Cannot display image](./dynamic-commits/kickoff%20tx.png "Kickoff transaction") + +1. type: 777 (`kickoff_sig`) +2. data: + * [`32*byte`:`channel_id`] + * [`signature`:`signature`] + +##### Requirements + +The sending node: + - MUST set `channel_id` to a valid channel it has with the recipient. + - MUST NOT send this message before receiving the peer's `commitment_signed`. + +The receiving node: + - MUST send an `error` and fail the channel if `channel_id` does not match an + existing channel it has with the sender. + - MUST send an `error` and fail the channel if `signature` is not valid for + the kickoff transaction as constructed above OR non-compliant with the + LOW-S-standard rule. [LOWS](https://github.com/bitcoin/bitcoin/pull/6769) + - MUST NOT send a `revoke_and_ack` for the final pre-dynamic commitment + transaction until it has received a valid `kickoff_sig` + +##### Rationale + +The `kickoff_sig` cannot be issued until the `commitment_signed` message has +been received to prevent griefing by broadcasting a kickoff for which there is +no exit. The `revoke_and_ack` for the last pre-dynamic commitment has to wait +for the `kickoff_sig` because if the last commitment built off of the original +funding output is revoked before the `kickoff_sig` has been received, then if +a peer becomes non-cooperative from that point forward, funds are effectively +burned. + +### Additional Requirements: ~Musig2 Taproot -> Musig2 Taproot + +This section describes how dynamic commitments can upgrade regular channels to +simple taproot channels. The regular dynamic proposal phase is executed followed +by a signing phase. A `channel_type` of `option_taproot` will be included in +`dyn_propose` and both sides must agree on it. The `initiator` of the upgrade +will also propose a feerate to use for an intermediate "kickoff" transaction. + +#### Required `dyn_propose` TLVs: + +1. `tlv_stream`: `dyn_propose_tlvs` +2. types: + 1. type: 10 (`channel_type`) + 2. data: + * [`...*byte`:`type`] + 1. type: 12 (`funding_pubkey`) + 2. data: + * [`point`:`senders_funding_pubkey`] + 1. type: 14 (`kickoff_feerate`) + 2. data: + * [`u32`:`kickoff_feerate_per_kw`] + 1. type: 16 (`local_musig2_pubnonce`) + 2. data: + * [`66*byte`:`nonces`] + +#### Requirements + +The sending node: + - if it is the `initiator`: + - MUST only send `kickoff_feerate` if they can pay for the kickoff + transaction fee and the anchor outputs, while adhering to the + `channel_reserve` restriction. + - MUST set `funding_pubkey` to a valid secp256k1 compressed public key. + - MUST set `local_musig2_pubnonce` to the nonce that it will use to verify + local commitments. + - SHOULD use a sufficiently high `kickoff_feerate` to be prepared for + worst-case fee environment scenarios. + - *NOTE FOR REVIEWERS*: We can also add a message to update the kickoff fee + rate if we have revocable kickoffs, similar to `update_fee` for commitment + transactions to make sure the kickoff has a sufficient fee to enter the + mempool. Anchors can be used to fee bump the kickoff beyond the min mempool + fee. Revocable kickoffs are possible but significantly increase the design + complexity. + - if it is proposing a `channel_type` of `simple_taproot_channel`: + +The receiving node: + - if it is the `responder`: + - MUST reject the `dyn_propose` if the `initiator` cannot pay for the kickoff + transaction fee and the anchor outputs. + - MUST reject the `dyn_propose` if, after calculating the amount of the new + funding output, the new commmitment transaction would not be able to pay + for any outputs at the current commitment feerate. + - MUST reject the `dyn_propose` if `taproot_funding_key` is not a valid + secp256k1 compressed public key. + - MAY reject the `dyn_propose` if it does not agree with the `channel_type` + - MUST send an `error` and fail the channel if `local_musig2_pubnonce` cannot + be parsed as two compressed secp256k1 points. + +#### Rationale + +The `dyn_propose` renegotiates the funding keys as otherwise signatures for the +funding keys would be exchanged in both the ECDSA and Schnorr contexts. This can +lead to an attack outlined in [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#alternative-signing). +Renegotiating funding keys avoids this issue. Note that the various basepoints +exchanged in `open_channel` and `accept_channel` are not renegotiated. Because +the private keys _change_ with each commitment transaction they sign due to the +`per_commitment_point` construction, the basepoints can be used in both ECDSA +and Schnorr contexts. + +#### Extensions to `dyn_ack`: + +1. `tlv_stream`: `dyn_ack_tlvs` +2. types: + 1. type: 0 (`local_musig2_pubnonce`) + 2. data: + * [`66*byte`:`nonces`] + +#### Requirements + +The sending node: + - if it is accepting a `channel_type` of `simple_taproot_channel`: + - MUST set `local_musig2_pubnonce` to the nonce that it will use to verify + local commitments. + +The receiving node: + - MUST send an `error` and fail the channel if `local_musig2_pubnonce` cannot + be parsed as two compressed secp256k1 points. + +# Appendix + +## Pinning + +![Cannot display image](./dynamic-commits/kickoff%20pinning.png "Kickoff transaction pinned") + +Originally, Bitcoin Core's default mempool settings allowed an unconfirmed +transaction to have up to 25 decendants in the mempool. Past this limit, any +descendants would be rejected. This was used as a DoS mitigation in Bitcoin Core +and affected the security of LN channels. Before the anchors commitment type was +introduced, pinning in the LN was where a counterparty broadcasted the +commitment transaction and created a chain of 25 descendants spending from one +of the commitment's outputs. The time-sensitive commitment transaction could be +"pinned" to the bottom of the mempool. This was addressed with a change to +Bitcoin Core called CPFP Carve-out. + +### CPFP Carve-out + +CPFP Carve-out was introduced to Bitcoin Core in +https://github.com/bitcoin/bitcoin/pull/15681. If a Bitcoin node receives a +transaction that is rejected due to any of the mempool size or +ancestor/descendant restrictions being hit, it will try to accept the +transaction again. This second try will succeed only if: + - the transaction is 40kWU or less + - it has only one ancestor in the mempool + +This change, in conjunction with the anchor commitment type, decreases the +efficacy of the pinning attack since the honest party can still attach an anchor +despite the descendant size limit being hit. + +### Dynamic Commitments & CPFP Carve-out + +The safety guarantees of CPFP Carve-out break due to the structure of the +kickoff transaction. The kickoff transaction contains 3 spendable outputs: the +local party's anchor, the remote party's anchor, and the new funding output. All +three of these outputs can be spent immediately. A malicious counterparty can +pin the kickoff transaction by: + - spending from their anchor output to create a descendant chain of 25 + transactions _AND_ + - spending from the new funding output using the new commitment transaction, + "using up" the CPFP Carve-out slot designated for the honest party. +_NOTE FOR REVIEWERS_: The semantics of CPFP carve-out are not entirely clear +as to whether or not there is only _one_ CPFP-Carve-Out "slot" or if the only +two requirements are the 40kWU limit and a single unconfirmed ancestor. If we +have more than one "slot" available, this is no longer a concern. + +Depending on fee conditions, it may not be possible for the honest party to get +these transactions confirmed until the mempool clears up. + +If we were to get rid of the kickoff transaction's anchor outputs, the problem +still arises. A malicious counterparty could still pin the kickoff transaction +by: + - broadcasting the commitment transaction + - spending from their commitment anchor output and creating a descendant chain + of 25 transactions + +The honest party is unable to use their anchor on the commitment transaction as: + - the descendant limit of 25 transactions has been hit + - the anchor spend would have 2 ancestors (the commitment and kickoff + transactions) + +### Reducing Risk + +The above pinning scenarios highlight the complexity of second-layer protocols +and mempool restrictions. In this proposal, pinning is _still_ possible, but +risk can be controlled if nodes reduce their max_htlc_value_in_flight_msat +values while the kickoff transaction is unconfirmed + +If we allow adding HTLCs _before_ the kickoff transaction confirmed on-chain, +the pinning attack has a tangible benefit: the ability to steal the value of an +HTLC. + +### Asymmetric Kickoffs + +The above proposal specifies a process wherein we can reanchor the funding +output exactly once. This is because even if we revoke all commitment +transactions built off of the first kickoff transaction, we still are vulnerable +to griefing if we do not revoke the kickoff transaction itself. In this case +one party may choose to burn all funds in a channel by broadcasting the kickoff +transaction when no unrevoked commitment transactions remain. To deal with this +we can either only reanchor once, as proposed above, allowing us to guarantee we +will never encounter a situation where there are no valid commitment +transactions, or we can make the kickoff transactions revocable. + +To make them revocable we can reuse the same scheme that we use for commitment +transactions. In this case Alice's kickoff transaction would allow Bob to claim +all funds if Bob knows Alice's revocation secret. Similarly, Alice could claim +all channel funds if Bob broadcasts his kickoff transaction and Alice knows +Bob's revocation secret. + +An unfortunate consequence of this scheme is that since we now have two possible +"new" funding outputs (one for each of the potential kickoff transactions), we +now have to send all of our commitment signatures in pairs. At any given time +there would be four valid commitment transactions: + +1. Alice's commitment built off of Alice's kickoff +2. Alice's commitment built off of Bob's kickoff +3. Bob's commitment built off of Alice's kickoff +4. Bob's commitment built off of Bob's kickoff + +_NOTE FOR REVIEWERS_: There may be an opportunity to make the kickoff +transactions symmetric while still allowing them to be revocable using adaptor +signature tricks, but this will require more research from those with a deeper +understanding of the cryptographic primitives. + +## Weights + +Since DER-encoded signatures vary in size, we assume a worst-case signature size +of 73 bytes to keep things simple. The kickoff transaction has an +_expected weight_ of 944WU and the commitment transaction has an +_expected weight_ of 960WU. + +General weights: + * p2tr: 34 bytes + - OP_1: 1 byte + - OP_DATA: 1 byte (witness_script_SHA256 length) + - witness_script_SHA256: 32 bytes + + * witness_header: 2 bytes + - flag: 1 byte + - marker: 1 byte + +### Kickoff Transaction Weights + + * funding_output_script: 71 bytes + - OP_2: 1 byte + - OP_DATA: 1 byte (pub_key_alice length) + - pub_key_alice: 33 bytes + - OP_DATA: 1 byte (pub_key_bob length) + - pub_key_bob: 33 bytes + - OP_2: 1 byte + - OP_CHECKMULTISIG: 1 byte + + * funding_input_witness: 222 bytes + - number_of_witness_elements: 1 byte + - nil_length: 1 byte + - sig_alice_length: 1 byte + - sig_alice: 73 bytes + - sig_bob_length: 1 byte + - sig_bob: 73 bytes + - witness_script_length: 1 byte + - witness_script: 71 bytes (funding_output_script) + + * kickoff_txin_0: 41 bytes (excl. witness) + - previous_out_point: 36 bytes + - hash: 32 bytes + - index: 4 bytes + - var_int: 1 byte (script_sig length) + - script_sig: 0 bytes + - witness: <---- part of the witness data + - sequence: 4 bytes + + * musig2_funding_output: 43 bytes + - value: 8 bytes + - var_int: 1 byte (pk_script length) + - pk_script (p2tr): 34 bytes + + * anchor_output: 43 bytes + - value: 8 bytes + - var_int: 1 byte (pk_script length) + - pk_script (p2tr): 34 bytes + + * kickoff_transaction: 180 bytes (excl. witness) + - version: 4 bytes + - witness_header: <---- part of the witness data + - count_tx_in: 1 byte + - tx_in: 41 bytes + - kickoff_txin_0: 41 bytes + - count_tx_out: 1 byte + - tx_out: 129 bytes + - musig2_funding_output: 43 bytes + - anchor_output_local: 43 bytes + - anchor_output_remote: 43 bytes + - lock_time: 4 bytes + + - Multiplying non-witness data by 4 gives a weight of: + - kickoff_transaction_weight = 180vbytes * 4 = 720WU + - Adding the witness data: + - kickoff_transaction_weight += (funding_input_witness + witness_header) + - kickoff_transaction_weight = 944WU + +### Commitment Transaction Weights + +Here we assume that both parties have an output on the commitment transaction. +This is to keep the weight consistent across potentially different commitment +transactions. + + * musig2_funding_input_witness: 66 bytes + - number_of_witness_elements: 1 byte + - musig2_signature_length: 1 byte + - musig2_signature: 64 bytes + + * commitment_txin_0: 41 bytes (excl. witness) + - previous_out_point: 36 bytes + - hash: 32 bytes + - index: 4 bytes + - var_int: 1 byte (script_sig length) + - script_sig: 0 bytes + - witness: <---- part of the witness data + - sequence: 4 bytes + + * to_local: 43 bytes + - value: 8 bytes + - var_int: 1 byte (pk_script length) + - pk_script (p2tr): 34 bytes + + * to_remote: 43 bytes + - value: 8 bytes + - var_int: 1 byte (pk_script length) + - pk_script (p2tr): 34 bytes + + * to_local_anchor: 43 bytes + - value: 8 bytes + - var_int: 1 byte (pk_script length) + - pk_script (p2tr): 34 bytes + + * to_remote_anchor: 43 bytes + - value: 8 bytes + - var_int: 1 byte (pk_script length) + - pk_script (p2tr): 34 bytes + + * commitment_transaction: 225 bytes (excl. witness) + - version: 4 bytes + - witness_header: <---- part of the witness data + - count_tx_in: 1 byte + - tx_in: 41 bytes + - commitment_txin_0: 41 bytes + - count_tx_out: 3 byte + - tx_out: 172 bytes + - to_local: 43 bytes + - to_remote: 43 bytes + - to_local_anchor: 43 bytes + - to_remote_anchor: 43 bytes + - lock_time: 4 bytes + + - Multiplying non-witness data by 4 gives a weight of: + - commitment_transaction_weight = 223vbytes * 4 = 892WU + - Adding the witness data: + - commitment_transaction_weight += (musig2_funding_input_witness + witness_header) + - commitment_transaction_weight = 960WU From 9765aafddd79ff1dc205aac5a7e045f39bda730e Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Wed, 11 Sep 2024 17:55:49 -0700 Subject: [PATCH 21/23] rework the protocol to simplify channel reestablishment --- ext-dynamic-commitments.md | 168 +++++++++++++++++++++++-------------- 1 file changed, 104 insertions(+), 64 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index 31d9a6948..79dcae617 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -190,8 +190,10 @@ sent by the `initiator`. +-------+ +-------+ | |--(1)------ dyn_propose ------>| | - | A | | B | - | |<-(4)---{dyn_ack|dyn_reject}---| | + | | | | + | A |<-(2)---{dyn_ack|dyn_reject}---| B | + | | | | + | |--(3)------ dyn_commit ------->| | +-------+ +-------+ 1. type: 111 (`dyn_propose`) @@ -234,10 +236,6 @@ The sending node: - MUST NOT set a `channel_type` with a different funding output script than the current funding output script UNLESS otherwise negotiated by another feature bit. - - _NOTE FOR REVIEWERS_: do we need to save `dyn_propose` parameters before - sending them? It complicates the design, but if we don't we need to be able - to discern between a faulty/malicious peer and a dropped connection while - the ack is in flight. The receiving node: - if `channel_id` does not match an existing channel it has with the sender: @@ -246,7 +244,6 @@ The receiving node: - if the TLV parameters of the `dyn_propose` are acceptable and the receiver intends to execute those parameter changes: - MUST respond with `dyn_ack`. - - MUST remember its last received `dyn_propose` parameters. - if the TLV parameters of the `dyn_propose` are NOT acceptable and the receiver refuses to execute those parameter changes: - MUST respond with `dyn_reject`. @@ -263,10 +260,6 @@ trying change channel parameters without a close event. BOLT 2 specifies constraints on these parameters to make sure they are internally consistent and secure in all contexts. -The requirement for a node to remember what it last _sent_ and for it to -remember what it _accepted_ is necessary to recover on reestablish. See the -reestablish section for more details. - #### `dyn_ack` This message is sent in response to a `dyn_propose` indicating that it has @@ -275,6 +268,7 @@ accepted the proposal. 1. type: 113 (`dyn_ack`) 2. data: * [`32*byte`:`channel_id`] + * [`signature`:`signature`] ##### Requirements @@ -286,28 +280,24 @@ The sending node: current negotiation. - MUST NOT send this message if it has already sent a `dyn_reject` for the current negotiation. - - MUST remember the parameters of `dyn_propose` message to which the `dyn_ack` - is responding for the next `dyn_epoch_height`. (See `channel_reestablish` - requirements) + - MUST set the `signature` field to a valid signature described in + [Appendix A](#appendix-a-dyn_ack-signature-definition) The receiving node: - if `channel_id` does not match an existing channel it has with the peer: - MUST send an `error` and close the connection. - if there isn't an outstanding `dyn_propose` it has sent: - MUST send an `error` and fail the channel. - -A node: - - once it has sent or received `dyn_ack` - - MUST increment its `dyn_epoch_height`. - - MUST proceed to the Execution Phase. - - MUST remember the local and remote commitment heights for the next - `dyn_epoch_height`. + - MUST verify the `signature` is valid for the same set of parameters proposed + and signed by the channel peer's node identity private key. + - MUST respond with a `dyn_commit` message. ##### Rationale -The `dyn_epoch_height` starts at 0 for a channel and is incremented by 1 every -time the dynamic commitment proposal phase completes for a channel. See the -reestablish section for why this is needed. +We include a signature here so that during the reestablish process we can +dispense with having to renegotiate quiescence and parameters. This allows the +`responder` to commit to its acceptance of the new parameters in a way that the +`initiator` can later _unilaterally_ recall. #### `dyn_reject` @@ -365,59 +355,109 @@ to an agreement on a proposal that will work. By sending back a zero value for commitment negotiation forward at all and further negotiation should not be attempted. -## Reestablish - -### `channel_reestablish` +#### `dyn_commit` -A new TLV that denotes the node's current `dyn_epoch_height` is included. +This message is sent after receiving a `dyn_ack` to unify the parameters and +the signature into a single message. -1. `tlv_stream`: `channel_reestablish_tlvs` -2. types: - 1. type: 20 (`dyn_epoch_height`) - 2. data: - * [`u64`:`dyn_epoch_height`] +1. type: 117 +2. data: + * [`32*byte`:`channel_id`] + * [`signature`:`dyn_ack_signature`] + * [`dyn_propose_tlvs`:`tlvs`] -#### Requirements +##### Requirements The sending node: - - MUST set `dyn_epoch_height` to the number of dynamic commitment negotiations - it has completed. The point at which it is incremented is described in the - `dyn_ack` section. + - MUST set `channel_id` to a valid channel id that it has with a peer. + - MUST set `signature` to a valid signature that matches the + `dyn_propose_tlvs` and the receiver's node identity private key. + message. + - MUST NOT send `dyn_commit` with a signature that is not valid for the next + commitment number that the receiving node expects to receive. + - MAY send `dyn_commit` _even if_ the channel is NOT quiescent. + - MUST proceed to the Execution Phase. The receiving node: - - if the received `dyn_epoch_height` equals its own `dyn_epoch_height`: - - MAY forget any state that is unnecessary for heights <= `dyn_epoch_height` - - NOTE: some channel parameters show up in the chain footprint of - commitment transactions, and therefore it is likely necessary to - remember these parameters to be able to recognize and react to breach - transactions. - - resume using channel with the parameters associated with - `dyn_epoch_height` - - if the received `dyn_epoch_height` is 1 greater than its own - `dyn_epoch_height`: - - resume using the channel with the current parameters - - if the received `dyn_epoch_height` is 1 less than its own - `dyn_epoch_height`: - - resume using the channel with the old parameters. - - MUST forget the most recent `dyn_epoch_height` parameters. - - else: - - MUST send an `error` and fail the channel. State was lost. + - MUST validate that the `signature` was previously signed by its own node + identity pubkey for the next commitment number it expects to receive for + the `channel_id` specified. + - MUST consider this message an "update" for the purposes of retransmission + as well as allowing enabling the receipt of a `commitment_signed` message. + - MUST proceed to the Execution Phase. + +##### Rationale + +This message simplifies the process of resolving issues with retransmission. +This message captures all of the necessary information to resolve honest +discrepencies in channel state. With this message the effects of the Dynamic +Commitment negotiation can be reapplied without retransmitting the negotiation +messages themselves. + +## Reestablish + +The channel reestablish that needs to include a Dynamic Commitment upgrade +proceeds similarly to the way other updates are retransmitted, though not +identically. + +### Requirements + +If a node has previously sent a `dyn_commit` message that contains a signature +bound to the commitment number that its channel peer specified in the +`channel_reestablish` message: + - MUST retransmit `dyn_commit` + - MUST NOT retransmit `dyn_propose` + - MUST proceed to Execution Phase #### Rationale -If `dyn_ack` has been sent and received before the connection closed, it -is simple to continue. If one side has sent `dyn_ack` and the other hasn't -received it, the flow is recoverable on reconnection by rolling back to the -previous set of parameters. +During the reestablish process we explicitly specify the next commitment +numbers we expect to receive. Since the new parameter changes are locked in +by an exchange of `commitment_signed` messages, if our channel peer tells us +that the next commit number it expects is the same one as the commit number +bound in the signature in `dyn_commit` we need to reissue that commitment as +well as all of the updates that commitment includes. + +As a side note, since we establish quiescence prior to Dynamic Commitment +negotiation, there cannot be any `update_` messages in the batch that the +`commitment_signed` message covers if it covers a `dyn_commit`. This may be +a useful invariant to check during implementation. ## Execution Phase -For the parameter changes outlined in this proposal, no specific execution is -required. The next commitment transaction issued by each peer is expected to -abide by the new parameters. +For the parameter changes outlined in this proposal, the specific execution +required is simply to exchange `commitment_signed` and `revoke_and_ack` +messages. After the `responder` has issued a `revoke_and_ack` these parameters +are considered locked in on the `responder`'s side. From there, the +`responder`'s subsequent updates will be expected to abide by the new +constraints. A sketch is provided below: -_NOTE FOR REVIEWERS_: Should we eagerly issue new commitment transactions here? -As writtent we commit to the new values lazily in the next commitment -transaction that would normally be issued. + +-------+ +-------+ + | |--(1)------ dyn_commit ------->| | + | | | | + | A |--(2)------ commit_sig ------->| B | + | | | | + | |<-(3)---- revoke_and_ack ------| | + +-------+ +-------+ At this point the channel is no longer considered quiescent. + +_NOTE FOR REVIEWERS_: Should we require that the `responder` immediately issues +a `commitment_signed` of its own? As far as I can tell this doesn't accomplish +anything except in the case where the `initiator` requests a change to its +`dust_limit` which would give it _immediate_ (as opposed to _eventual_) access +to a commitment transaction that abided by the new limit. + +## Appendix A: `dyn_ack` signature definition + +The signature included in the `dyn_ack` message covers the following message: + +`channel_id || u64(next_commitment_number) || dyn_propose_tlvs` + +Like with `channel_reestablish`, the `next_commitment_number` refers to the +immediate next commitment number the responder expects to receive. For the +TLV stream, since BOLT1 specifies that types must be sent in strictly ascending +order, the encoding is fully deterministic and will ensure that the signature +can be verified without ambiguity. + +The key used to sign this message is the node's identity private key. From 6f7cb42d411eeff193036aa7045c111bff6e9421 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Wed, 11 Sep 2024 18:05:59 -0700 Subject: [PATCH 22/23] rename dyn_* to chan_param_* --- ext-dynamic-commitments.md | 144 +++++++++++++++++---------------- ext-dynamic-funding-outputs.md | 68 ++++++++-------- 2 files changed, 109 insertions(+), 103 deletions(-) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index 79dcae617..e2b6fc3e9 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -182,26 +182,26 @@ Three new messages are introduced that are common to all dynamic commitment flows. They let each channel party propose which channel parameters they wish to change as well as accept or reject the proposal made by their counterparty. -#### `dyn_propose` +#### `chan_param_propose` This message is sent to negotiate the parameters of a dynamic commitment upgrade. The overall protocol flow is depicted below. This message is always sent by the `initiator`. - +-------+ +-------+ - | |--(1)------ dyn_propose ------>| | - | | | | - | A |<-(2)---{dyn_ack|dyn_reject}---| B | - | | | | - | |--(3)------ dyn_commit ------->| | - +-------+ +-------+ + +-------+ +-------+ + | |--(1)---------- chan_param_propose --------->| | + | | | | + | A |<-(2)---{chan_param_ack|chan_param_reject}---| B | + | | | | + | |--(3)----------- chan_param_commit --------->| | + +-------+ +-------+ -1. type: 111 (`dyn_propose`) +1. type: 111 (`chan_param_propose`) 2. data: * [`32*byte`:`channel_id`] - * [`dyn_propose_tlvs`:`tlvs`] + * [`chan_param_propose_tlvs`:`tlvs`] -1. `tlv_stream`: `dyn_propose_tlvs` +1. `tlv_stream`: `chan_param_propose_tlvs` 2. types: 1. type: 0 (`dust_limit_satoshis`) 2. data: @@ -229,9 +229,10 @@ The sending node: - MUST set `channel_id` to an existing one it has with the recipient. - MUST NOT send a set of TLV parameters that would violate the requirements of the identically named parameters in BOLT 2 or associated extensions. - - MUST remember its last sent `dyn_propose` parameters. - - if it is currently waiting for a response (`dyn_ack` or `dyn_reject`): - - MUST NOT send another `dyn_propose`. + - MUST remember its last sent `chan_param_propose` parameters. + - if it is currently waiting for a response (`chan_param_ack` or + `chan_param_reject`): + - MUST NOT send another `chan_param_propose`. - SHOULD close the connection if it exceeds an acceptable time frame. - MUST NOT set a `channel_type` with a different funding output script than the current funding output script UNLESS otherwise negotiated by another @@ -240,13 +241,13 @@ The sending node: The receiving node: - if `channel_id` does not match an existing channel it has with the sender: - SHOULD send an `error` and close the connection. - - MUST respond with either a `dyn_ack` or `dyn_reject`. - - if the TLV parameters of the `dyn_propose` are acceptable and the receiver - intends to execute those parameter changes: - - MUST respond with `dyn_ack`. - - if the TLV parameters of the `dyn_propose` are NOT acceptable and the + - MUST respond with either a `chan_param_ack` or `chan_param_reject`. + - if the TLV parameters of the `chan_param_propose` are acceptable and the + receiver intends to execute those parameter changes: + - MUST respond with `chan_param_ack`. + - if the TLV parameters of the `chan_param_propose` are NOT acceptable and the receiver refuses to execute those parameter changes: - - MUST respond with `dyn_reject`. + - MUST respond with `chan_param_reject`. _NOTE FOR REVIEWERS_: These messages all interact with each other, so feedback is welcome for how to restructure this section so that the invariants it @@ -260,12 +261,12 @@ trying change channel parameters without a close event. BOLT 2 specifies constraints on these parameters to make sure they are internally consistent and secure in all contexts. -#### `dyn_ack` +#### `chan_param_ack` -This message is sent in response to a `dyn_propose` indicating that it has -accepted the proposal. +This message is sent in response to a `chan_param_propose` indicating that it +has accepted the proposal. -1. type: 113 (`dyn_ack`) +1. type: 113 (`chan_param_ack`) 2. data: * [`32*byte`:`channel_id`] * [`signature`:`signature`] @@ -273,24 +274,25 @@ accepted the proposal. ##### Requirements The sending node: - - MUST set `channel_id` to the `channel_id` it received in the `dyn_propose`. - - MUST NOT send this message if it has not received a `dyn_propose` for this - `channel_id` - - MUST NOT send this message if it has already sent a `dyn_ack` for the - current negotiation. - - MUST NOT send this message if it has already sent a `dyn_reject` for the + - MUST set `channel_id` to the `channel_id` it received in the + `chan_param_propose`. + - MUST NOT send this message if it has not received a `chan_param_propose` for + this `channel_id` + - MUST NOT send this message if it has already sent a `chan_param_ack` for the current negotiation. + - MUST NOT send this message if it has already sent a `chan_param_reject` for + the current negotiation. - MUST set the `signature` field to a valid signature described in - [Appendix A](#appendix-a-dyn_ack-signature-definition) + [Appendix A](#appendix-a-chan_param_ack-signature-definition) The receiving node: - if `channel_id` does not match an existing channel it has with the peer: - MUST send an `error` and close the connection. - - if there isn't an outstanding `dyn_propose` it has sent: + - if there isn't an outstanding `chan_param_propose` it has sent: - MUST send an `error` and fail the channel. - MUST verify the `signature` is valid for the same set of parameters proposed and signed by the channel peer's node identity private key. - - MUST respond with a `dyn_commit` message. + - MUST respond with a `chan_param_commit` message. ##### Rationale @@ -299,12 +301,12 @@ dispense with having to renegotiate quiescence and parameters. This allows the `responder` to commit to its acceptance of the new parameters in a way that the `initiator` can later _unilaterally_ recall. -#### `dyn_reject` +#### `chan_param_reject` -This message is sent in response to a `dyn_propose` indicating that it rejects -the proposal. +This message is sent in response to a `chan_param_propose` indicating that it +rejects the proposal. -1. type: 115 (`dyn_reject`) +1. type: 115 (`chan_param_reject`) 2. data: * [`32*byte`:`channel_id`] * [`...*byte`:`update_rejections`] @@ -312,29 +314,30 @@ the proposal. ##### Requirements The sending node: - - MUST set `channel_id` to the `channel_id` it received in the `dyn_propose`. - - MUST NOT send this message if it has not received a `dyn_propose` - - MUST NOT send this message if it has already sent a `dyn_ack` for the - current negotiation. - - MUST NOT send this message if it has already sent a `dyn_reject` for the + - MUST set `channel_id` to the `channel_id` it received in the + `chan_param_propose`. + - MUST NOT send this message if it has not received a `chan_param_propose` + - MUST NOT send this message if it has already sent a `chan_param_ack` for the current negotiation. + - MUST NOT send this message if it has already sent a `chan_param_reject` for + the current negotiation. - if it will not accept **any** dynamic commitment negotiation: - - SHOULD send a `dyn_reject` with zero value for `update_rejections` + - SHOULD send a `chan_param_reject` with zero value for `update_rejections` - if it does not agree with one or more parameters: - - MUST send a `dyn_reject` with the bit index (using the same layout as - feature negotiation) set corresponding to the TLV type number. + - MUST send a `chan_param_reject` with the bit index (using the same layout + as feature negotiation) set corresponding to the TLV type number. - Example: an objection to the `dust_limit` would be encoded as 0b00000001, an objection to `max_value_in_flight` would be encoded as 0b00000100, and an objection to both would be encoded as 0b00000101. - - MUST forget the parameters of the `dyn_propose` message to which the - `dyn_reject` is responding. + - MUST forget the parameters of the `chan_param_propose` message to which the + `chan_param_reject` is responding. The receiving node: - if `channel_id` does not match an existing channel it has with the peer - MUST close the connection - - if there isn't an outstanding `dyn_propose` it has sent + - if there isn't an outstanding `chan_param_propose` it has sent - MUST send an `error` and fail the channel - - MUST forget its last sent `dyn_propose` parameters. + - MUST forget its last sent `chan_param_propose` parameters. - if the `update_rejections` is a zero value - SHOULD NOT re-attempt another dynamic commitment negotation for the remaining lifecycle of the connection @@ -355,27 +358,27 @@ to an agreement on a proposal that will work. By sending back a zero value for commitment negotiation forward at all and further negotiation should not be attempted. -#### `dyn_commit` +#### `chan_param_commit` -This message is sent after receiving a `dyn_ack` to unify the parameters and -the signature into a single message. +This message is sent after receiving a `chan_param_ack` to unify the parameters +and the signature into a single message. 1. type: 117 2. data: * [`32*byte`:`channel_id`] - * [`signature`:`dyn_ack_signature`] - * [`dyn_propose_tlvs`:`tlvs`] + * [`signature`:`chan_param_ack_signature`] + * [`chan_param_propose_tlvs`:`tlvs`] ##### Requirements The sending node: - MUST set `channel_id` to a valid channel id that it has with a peer. - MUST set `signature` to a valid signature that matches the - `dyn_propose_tlvs` and the receiver's node identity private key. + `chan_param_propose_tlvs` and the receiver's node identity private key. message. - - MUST NOT send `dyn_commit` with a signature that is not valid for the next - commitment number that the receiving node expects to receive. - - MAY send `dyn_commit` _even if_ the channel is NOT quiescent. + - MUST NOT send `chan_param_commit` with a signature that is not valid for the + next commitment number that the receiving node expects to receive. + - MAY send `chan_param_commit` _even if_ the channel is NOT quiescent. - MUST proceed to the Execution Phase. The receiving node: @@ -402,11 +405,11 @@ identically. ### Requirements -If a node has previously sent a `dyn_commit` message that contains a signature -bound to the commitment number that its channel peer specified in the +If a node has previously sent a `chan_param_commit` message that contains a +signature bound to the commitment number that its channel peer specified in the `channel_reestablish` message: - - MUST retransmit `dyn_commit` - - MUST NOT retransmit `dyn_propose` + - MUST retransmit `chan_param_commit` + - MUST NOT retransmit `chan_param_propose` - MUST proceed to Execution Phase #### Rationale @@ -415,13 +418,13 @@ During the reestablish process we explicitly specify the next commitment numbers we expect to receive. Since the new parameter changes are locked in by an exchange of `commitment_signed` messages, if our channel peer tells us that the next commit number it expects is the same one as the commit number -bound in the signature in `dyn_commit` we need to reissue that commitment as -well as all of the updates that commitment includes. +bound in the signature in `chan_param_commit` we need to reissue that commitment +as well as all of the updates that commitment includes. As a side note, since we establish quiescence prior to Dynamic Commitment negotiation, there cannot be any `update_` messages in the batch that the -`commitment_signed` message covers if it covers a `dyn_commit`. This may be -a useful invariant to check during implementation. +`commitment_signed` message covers if it covers a `chan_param_commit`. This may +be a useful invariant to check during implementation. ## Execution Phase @@ -433,7 +436,7 @@ are considered locked in on the `responder`'s side. From there, the constraints. A sketch is provided below: +-------+ +-------+ - | |--(1)------ dyn_commit ------->| | + | |--(1)--- chan_param_commit --->| | | | | | | A |--(2)------ commit_sig ------->| B | | | | | @@ -448,11 +451,12 @@ anything except in the case where the `initiator` requests a change to its `dust_limit` which would give it _immediate_ (as opposed to _eventual_) access to a commitment transaction that abided by the new limit. -## Appendix A: `dyn_ack` signature definition +## Appendix A: `chan_param_ack` signature definition -The signature included in the `dyn_ack` message covers the following message: +The signature included in the `chan_param_ack` message covers the following +message: -`channel_id || u64(next_commitment_number) || dyn_propose_tlvs` +`channel_id || u64(next_commitment_number) || chan_param_propose_tlvs` Like with `channel_reestablish`, the `next_commitment_number` refers to the immediate next commitment number the responder expects to receive. For the diff --git a/ext-dynamic-funding-outputs.md b/ext-dynamic-funding-outputs.md index e91792720..993185a81 100644 --- a/ext-dynamic-funding-outputs.md +++ b/ext-dynamic-funding-outputs.md @@ -212,25 +212,25 @@ and are common to all messages in the negotiation phase. The proposal messages are reused from Dynamic Commitments. -#### `dyn_propose` +#### `chan_param_propose` This message is sent to negotiate the parameters of a dynamic commitment upgrade. The overall protocol flow is depicted below. This message is always sent by the `initiator`. The new TLVs above can be sent along with the TLVs detailed in the original Dynamic Commitments proposal. - +-------+ +-------+ - | |--(1)------ dyn_propose ------>| | - | A | | B | - | |<-(4)---{dyn_ack|dyn_reject}---| | - +-------+ +-------+ + +-------+ +-------+ + | |--(1)---------- chan_param_propose --------->| | + | A | | B | + | |<-(4)---{chan_param_ack|chan_param_reject}---| | + +-------+ +-------+ -1. type: 111 (`dyn_propose`) +1. type: 111 (`chan_param_propose`) 2. data: * [`32*byte`:`channel_id`] - * [`dyn_propose_tlvs`:`tlvs`] + * [`chan_param_propose_tlvs`:`tlvs`] -1. `tlv_stream`: `dyn_propose_tlvs` +1. `tlv_stream`: `chan_param_propose_tlvs` 2. types: 1. type: 0 (`dust_limit_satoshis`) 2. data: @@ -274,44 +274,46 @@ Since the funding output change implies a new transaction that is used to move the channel funds from the old funding output script to the new one, this transaction needs to have a mutually agreed upon feerate. -#### `dyn_ack` +#### `chan_param_ack` -This message is sent in response to a `dyn_propose` indicating that it has -accepted the proposal. +This message is sent in response to a `chan_param_propose` indicating that it +has accepted the proposal. -1. type: 113 (`dyn_ack`) +1. type: 113 (`chan_param_ack`) 2. data: * [`32*byte`:`channel_id`] ##### Requirements -The requirements for `dyn_ack` are identical to those in Dynamic Commitments. +The requirements for `chan_param_ack` are identical to those in Dynamic +Commitments. -#### `dyn_reject` +#### `chan_param_reject` -This message is sent in response to a `dyn_propose` indicating that it rejects -the proposal. +This message is sent in response to a `chan_param_propose` indicating that it +rejects the proposal. -1. type: 115 (`dyn_reject`) +1. type: 115 (`chan_param_reject`) 2. data: * [`32*byte`:`channel_id`] * [`...*byte`:`update_rejections`] ##### Requirements -The requirements for `dyn_reject` are identical to those in Dynamic Commitments. +The requirements for `chan_param_reject` are identical to those in Dynamic +Commitments. ## Reestablish ### `channel_reestablish` -A new TLV that denotes the node's current `dyn_epoch_height` is included. +A new TLV that denotes the node's current `chan_param_epoch_height` is included. 1. `tlv_stream`: `channel_reestablish_tlvs` 2. types: - 1. type: 20 (`dyn_epoch_height`) + 1. type: 20 (`chan_param_epoch_height`) 2. data: - * [`u64`:`dyn_epoch_height`] + * [`u64`:`chan_param_epoch_height`] #### Requirements @@ -412,7 +414,7 @@ version 1 witness script: The new funding output has a value of the original funding output minus the sum of 660 satoshis and this kickoff transaction's fee. It is encumbered by a version 1 witness script where `taproot_funding_key1/taproot_funding_key2` are -from `dyn_ack`: +from `chan_param_ack`: * `OP_1 funding_key` * where: * `funding_key = combined_funding_key + tagged_hash("TapTweak", combined_funding_key)*G` @@ -525,12 +527,12 @@ burned. This section describes how dynamic commitments can upgrade regular channels to simple taproot channels. The regular dynamic proposal phase is executed followed by a signing phase. A `channel_type` of `option_taproot` will be included in -`dyn_propose` and both sides must agree on it. The `initiator` of the upgrade +`chan_param_propose` and both sides must agree on it. The `initiator` of the upgrade will also propose a feerate to use for an intermediate "kickoff" transaction. -#### Required `dyn_propose` TLVs: +#### Required `chan_param_propose` TLVs: -1. `tlv_stream`: `dyn_propose_tlvs` +1. `tlv_stream`: `chan_param_propose_tlvs` 2. types: 1. type: 10 (`channel_type`) 2. data: @@ -567,20 +569,20 @@ The sending node: The receiving node: - if it is the `responder`: - - MUST reject the `dyn_propose` if the `initiator` cannot pay for the kickoff + - MUST reject the `chan_param_propose` if the `initiator` cannot pay for the kickoff transaction fee and the anchor outputs. - - MUST reject the `dyn_propose` if, after calculating the amount of the new + - MUST reject the `chan_param_propose` if, after calculating the amount of the new funding output, the new commmitment transaction would not be able to pay for any outputs at the current commitment feerate. - - MUST reject the `dyn_propose` if `taproot_funding_key` is not a valid + - MUST reject the `chan_param_propose` if `taproot_funding_key` is not a valid secp256k1 compressed public key. - - MAY reject the `dyn_propose` if it does not agree with the `channel_type` + - MAY reject the `chan_param_propose` if it does not agree with the `channel_type` - MUST send an `error` and fail the channel if `local_musig2_pubnonce` cannot be parsed as two compressed secp256k1 points. #### Rationale -The `dyn_propose` renegotiates the funding keys as otherwise signatures for the +The `chan_param_propose` renegotiates the funding keys as otherwise signatures for the funding keys would be exchanged in both the ECDSA and Schnorr contexts. This can lead to an attack outlined in [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#alternative-signing). Renegotiating funding keys avoids this issue. Note that the various basepoints @@ -589,9 +591,9 @@ the private keys _change_ with each commitment transaction they sign due to the `per_commitment_point` construction, the basepoints can be used in both ECDSA and Schnorr contexts. -#### Extensions to `dyn_ack`: +#### Extensions to `chan_param_ack`: -1. `tlv_stream`: `dyn_ack_tlvs` +1. `tlv_stream`: `chan_param_ack_tlvs` 2. types: 1. type: 0 (`local_musig2_pubnonce`) 2. data: From ae7ea06dc789099e41af2fc3a3ec8d3397253f3a Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Wed, 11 Sep 2024 18:07:08 -0700 Subject: [PATCH 23/23] fix mistake of omitting htlc_minimum_msat --- ext-dynamic-commitments.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext-dynamic-commitments.md b/ext-dynamic-commitments.md index e2b6fc3e9..a157d4afb 100644 --- a/ext-dynamic-commitments.md +++ b/ext-dynamic-commitments.md @@ -67,6 +67,7 @@ a list of parameters which are unchangeable. We list these below: - dust_limit_satoshis - max_htlc_value_in_flight_msat +- htlc_minimum_msat - channel_reserve_satoshis - to_self_delay - max_accepted_htlcs @@ -89,6 +90,7 @@ remaining after we filter out these values is thus: - dust_limit_satoshis - max_htlc_value_in_flight_msat +- htlc_minimum_msat - channel_reserve_satoshis - to_self_delay - max_accepted_htlcs