Authors:
- Olaoluwa Osuntokun roasbeef@lightning.engineering
- Eugene Siegel eugene@lightning.engineering
Created: 2022-06-24
TODO
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.
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.
- type: 111 (
dyn_begin_propose
) - data:
- [
32*byte
:channel_id
] - [
byte
:begin_propose_flags
]
- [
Only the least-significant-bit of begin_propose_flags
is defined, the reject
bit.
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 inbegin_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 thereject
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 adyn_begin_propose
:- MUST send an
error
and fail the channel.
- MUST send an
- 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.
- MUST send an
- 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
TODO: flow diagram
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 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_propose ------->| |
| | | |
| |<-(2)----- dyn_propose --------| |
| A |--(3)--- dyn_propose_reply --->| B |
| | | |
| |<-(4)--- dyn_propose_reply ----| |
+-------+ +-------+
-
type: 113 (
dyn_propose
) -
data:
- [
32*byte
:channel_id
] - [
dyn_propose_tlvs
:tlvs
]
- [
-
tlv_stream
:dyn_propose_tlvs
-
types:
- type: 0 (
to_self_delay
) - data:
- [
u16
:recipients_new_self_delay
]
- [
- type: 0 (
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 fordyn_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.
- MUST send an
- if it does not agree with a parameter:
- MUST send a
dyn_propose_reply
with thereject
bit set.
- MUST send a
- else:
- MUST send a
dyn_propose_reply
without thereject
bit set.
- MUST send a
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.
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.
- type: 115 (
dyn_propose_reply
) - data:
- [
32*byte
:channel_id
] - [
byte
:propose_reply_flags
]
- [
The least-significant bit of propose_reply_flags
is defined as the reject
bit.
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 inpropose_reply_flags
if they are rejecting the newestdyn_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 nextpropose_height
.
- MUST remember the related
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 send an
- if the
reject
bit was set:- MUST forget its last sent
dyn_propose
parameters.
- MUST forget its last sent
A node:
- once it has both sent and received
dyn_propose_reply
without thereject
bit set:- MUST increment their
propose_height
.
- MUST increment their
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.
A new TLV that denotes the node's current propose_height
is included.
tlv_stream
:channel_reestablish_tlvs
- types:
- type: 20 (
propose_height
) - data:
- [
u64
:propose_height
]
- [
- type: 20 (
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 thedyn_propose_reply
section.
The receiving node:
- if the received
propose_height
equals its ownpropose_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
- if the received
propose_height
is 1 greater than its ownpropose_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 thepropose_height
or the recipient has lost data since its not possible to advance the height without the recipient storing the remote's parameters.
- MUST send an
- resume using the channel with its last-sent
dyn_propose
and the storeddyn_propose
parameters and increment itspropose_height
.
- if it does not have any remote parameters stored for the received
- if the received
propose_height
is 1 less than its ownpropose_height
:- resume using the channel with the new parameters.
- else:
- MUST send an
error
and fail the channel. State was lost.
- MUST send an
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.
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 fee to use for an intermediate "kickoff"
transaction.
tlv_stream
:dyn_propose_tlvs
- types:
- type: 2 (
channel_type
) - data:
- [
...*byte
:type
]
- [
- type: 4 (
kickoff_feerate
) - data:
- [
u32
:kickoff_feerate_per_kw
]
- [
- type: 6 (
taproot_funding_key
) - data:
- [
point
:funding_key
]
- [
- type: 2 (
The sending node:
- if it is the funder:
- MUST only send
kickoff_feerate
if they can pay for the sum of the kickoff transaction fee and the anchor outputs, while adhering to thechannel_reserve
restriction.
- MUST only send
- MUST set
taproot_funding_key
to a valid secp256k1 compressed public key.
The receiving node:
- if it is the fundee:
- MUST reject the
dyn_propose
if the funder cannot pay for the sum of the kickoff transaction fee and the anchor outputs at thekickoff_feerate
. - 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
- MUST reject the
dyn_propose
iftaproot_funding_key
is not a valid secp256k1 compressed public key. - MAY reject the
dyn_propose
if it does not agree with thechannel_type
or thekickoff_feerate
.
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.
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.
tlv_stream
:dyn_propose_reply_tlvs
- types:
- type: 0 (
local_musig2_pubnonce
) - data:
- [
66*byte
:nonces
]
- [
- type: 0 (
The sending node:
- if it is accepting a
channel_type
ofsimple_taproot_channel
:- MUST set
local_musig2_pubnonce
to the nonce that it will use to verify local commitments.
- MUST set
The receiving node:
- MUST send an
error
and fail the channel iflocal_musig2_pubnonce
cannot be parsed as two compressed secp256k1 points.
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.
- 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 transaction's musig2 funding outpoint.txin[0]
sequence: upper 8 bits are 0x80, lower 24 bits are upper 24 bits of the obscured commitment numbertxin[0]
script bytes: 0txin[0]
witness:<key_path_sig>
The 48-bit commitment number is computed by XOR
as described in BOLT#03.
- Initialize the commitment transaction version and locktime.
- Initialize the commitment transaction input.
- Calculate which committed HTLCs need to be trimmed.
- Calculate the commitment transaction fee via
commitment feerate *
commitment_transaction_weight
/1000, making sure to round down. Subtract this from the funder's output. - 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.
- Subtract the kickoff transaction's fee from the funder's output.
- For every offered HTLC, if it is not trimmed, add an offered HTLC output.
- For every received HTLC, if it is not trimmed, add a received HTLC output.
- If the
to_local
output is greater or equal to the dust limit, add ato_local
output. - If the
to_remote
output is greater or equal to the dust limit, add ato_remote
output. - If
to_local
exists or there are untrimmed HTLCs, add ato_local_anchor
. - If
to_remote
exists or there are untrimmed HTLCs, add ato_remote_anchor
. Theto_remote_anchor
uses the remote party'staproot_funding_key
. - Sort the outputs into BIP 69+CLTV order.
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:
tlv_stream
:commit_sig_tlvs
- types:
- type: 2 (
partial_signature_with_nonce
) - data:
- [
98*byte
:partial_signature || public_nonce
]
- [
- type: 2 (
The sending node:
- MUST 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 ifpartial_signature
is not a valid Schnorr signature. - MUST send an
error
and fail the channel ifpublic_nonce
cannot be parsed as two compressed secp256k1 points.
- version: 2
- locktime: 0
- txin count: 1
txin[0]
outpoint:txid
andoutput_index
fromfunding_created
messagetxin[0]
sequence: 0xffffffff-2txin[0]
script bytes: 0txin[0]
witness:0 <signature_for_pubkey1> <signature_for_pubkey2>
- txout count: 3
txout[0]
:anchor_output_1
oranchor_output_2
txout[1]
:anchor_output_1
oranchor_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 the 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))
- Initialize the commitment transaction version and locktime.
- Initialize the commitment transaction input.
- Calculate the kickoff transaction fee via
kickoff_feerate
*kickoff_transaction_weight
/1000, making sure to round down. Subtract this value from the new funding output. - Subtract two times the fixed anchor size of 330 satoshis from the new funding output.
- Add a funding output with the new funding amount.
- Add an anchor output for each party.
- Sort the outputs into BIP 69+CLTV order.
The kickoff_sig is a signature that is combined with the corresponding one from the remote party 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.
- type: 777 (
kickoff_sig
) - data:
- [
32*byte
:channel_id
] - [
signature
:signature
]
- [
The sending node:
- 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:
- MUST send an
error
and fail the channel ifchannel_id
does not match an existing channel it has with the sender. - MUST send an
error
and fail the channel ifsignature
is not valid for the kickoff transaction as constructed above OR non-compliant with the LOW-S-standard rule. LOWS
Per the simple-taproot-channel proposal, the revoke_and_ack
message contains a single
nonce used by the recipient to construct a commitment transaction for the sender.
tlv_stream
:revoke_and_ack_tlvs
- types:
- type: 2 (
local_musig2_pubnonce
) - data:
- [
66*byte
:nonces
]
- [
- type: 2 (
The sending node:
- MUST NOT send this message until the sender has both sent and received
kickoff_sig
.
The receiving node:
- MUST send an
error
and fail the channel iflocal_musig2_pubnonce
cannot be parsed as two compressed secp256k1 points.
+-------+ +-------+
| |--(1)---- commit_signed------->| |
| | | |
| |<-(2)---- commit_signed -------| |
| A |--(3)----- kickoff_sig ------->| B |
| | | |
| |<-(4)----- kickoff_sig --------| |
| |<-(5)----- rev_and_ack --------| |
| |--(6)----- rev_and_ack ------->| |
+-------+ +-------+
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, B 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. If only A has funds at risk, then only Alice loses money here.
revoke_and_ack
must be sent only after sending and receiving kickoff_sig
. Take for example
the following flow where A sends revoke_and_ack
before receiving kickoff_sig
:
+-------+ +-------+
| |--(1)---- commit_signed ------>| |
| | | |
| |<-(2)---- commit_signed -------| |
| A |--(3)----- kickoff_sig ------->| B |
| | | |
| |--(4)----- rev_and_ack ------->| |
+-------+ +-------+
A has revoked their legacy commitment transaction. However, A cannot force close their channel
because they do not have a kickoff_sig
from B to broadcast the kickoff transaction on their
own.
Additionally, including revoke_and_ack
in the signing phase means that it is possible to
determine when the flow is done upon a subsequent reconnect by examining the
next_revocation_number
s sent and received in channel_reestablish
.
Once the proposal phase is complete in the upgrade case, it is not possible to exit without completing the signing stage. Take the below state transition:
+-------+ +-------+
| |--(1)----- dyn_propose ------->| |
| | | |
| |<-(2)----- dyn_propose --------| |
| M |--(3)--- dyn_propose_reply --->| B |
| | | |
| |?-(4)--- dyn_propose_reply ----| |
| |?-(5)---- commit_signed -------| |
+-------+ +-------+
On reconnect, B is not able to tell if M received either dyn_propose_reply
or
commitment_signed
. If we allowed restarting the negotiation flow in the upgrade case,
M might be able to propose N different to_self_delay
parameters and receive signatures for
N different commitment transactions. M could then complete the signing phase up until
receiving kickoff_sig
and broadcast any of the N commitment transactions. To avoid this
complexity, we require that if one side has sent and received dyn_propose_reply
,
the negotiation MUST finish or the channel is force closed. Before this point, neither
node can determine whether negotiation is done.
The flow where M has 3 different broadcastable commitment transactions:
+-------+ +-------+
| |--(1)----- dyn_propose ------->| |
| | | |
| |<-(2)----- dyn_propose --------| |
| M |--(3)--- dyn_propose_reply --->| B |
| | | |
| |?-(4)--- dyn_propose_reply ----| |
| |?-(5)---- commit_signed -------| |
| | | |
| | *reconnect* | |
| | | |
| |--(1)----- dyn_propose ------->| |
| | | |
| |<-(2)----- dyn_propose --------| |
| | | |
| |--(3)--- dyn_propose_reply --->| |
| | | |
| |?-(4)--- dyn_propose_reply ----| |
| |?-(5)---- commit_signed -------| |
| | | |
| | *reconnect* | |
| | | |
| |--(1)----- dyn_propose ------->| |
| | | |
| |<-(2)----- dyn_propose --------| |
| | | |
| |--(3)--- dyn_propose_reply --->| |
| | | |
| |<-(4)--- dyn_propose_reply ----| |
| | | |
| |<-(5)---- commit_signed -------| |
| |--(6)---- commit_signed ------>| |
| |<-(7)----- kickoff_sig --------| |
| | | |
+-------+ +-------+
The downside with requiring the signing phase to complete is that if kickoff_feerate
is
below the minimum relay feerate, another round cannot be attempted. This means the kickoff
transaction will only get into the mempool after it has cleared somewhat. The anchor outputs
on the kickoff transaction should be sufficient for confirmation in most cases.
The channel_reestablish
message does not change, but adds a nonce in the TLV section per the
simple-taproot-channels proposal.
tlv_stream
:channel_reestablish_tlvs
- types:
- type: 4 (
next_local_nonce
) - data:
- [
66*byte
:public_nonce
]
- [
- type: 4 (
The sending node:
- MUST set
next_local_nonce
if the sender sees it has persisted achannel_type
ofoption_simple_taproot
from thedyn_propose
/dyn_propose_reply
negotiation steps.
The receiving node:
- MUST send an
error
and fail the channel ifnext_local_nonce
cannot be parsed as two compressed secp256k1 points.
The signing phase is complete when both of the following are true:
- a node receives a
channel_reestablish
with anext_revocation_number
greater than their local commitment height at the start of the dynamic proposal flow - the node's sent
next_revocation_number
is greater than the remote commitment height at the start of the dynamic proposal flow
When the signing phase is complete, the channel can continue as normal. Note that this requires
each side to remember the starting commitment heights when acknowledging and persisting received
dyn_propose
parameters.
If the connection was closed during the signing phase, the signing phase starts over at the beginning on reconnect:
commitment_signed
MUST sign the same commitment transaction as before reconnect. Only the nonces will change.kickoff_sig
MUST also sign the same kickoff transaction as before reconnect.- MUST tolerate receiving duplicate
commitment_signed
,kickoff_sig
, andrevoke_and_ack
across connection restarts as long as the signing phase is in progress.
This last requirement to tolerate duplicates means that flows like the following are allowed:
+-------+ +-------+
| |--(1)---- commit_signed ----->| |
| | | |
| |<-(2)---- commit_signed -------| |
| M |--(3)----- kickoff_sig ------>| B |
| | | |
| |<-(4)----- kickoff_sig --------| |
| |<-(5)---- revoke_and_ack ------| |
| | | |
| | *reconnect* | |
| | | |
| |--(1)---- commit_signed ----->| |
| |<-(2)---- commit_signed -------| |
| | | |
| |--(3)----- kickoff_sig ------>| |
| |<-(4)----- kickoff_sig --------| |
| | | |
| |<-(5)---- revoke_and_ack ------| |
| |--(6)---- revoke_and_ack ----->| |
+-------+ +-------+
TODO: tx diagram
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 was introduced to Bitcoin Core in bitcoin/bitcoin#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.
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)
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.
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
-
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
- previous_out_point: 36 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
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
- previous_out_point: 36 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