Skip to content

Commit

Permalink
Add strict shutdown exchange for taproot channels
Browse files Browse the repository at this point in the history
It was previously unclear whether a node could send `shutdown` and
`closing_complete` immediately after that whenever RBF-ing their
previous closing transaction. While this worked for non-taproot
channels, it doesn't allow a clean exchange of fresh musig2 nonces
for taproot channels. We now require that whenever a node wants to
start a new signing round, `shutdown` must be sent *and* received
before sending `closing_complete`.
  • Loading branch information
t-bast committed Oct 22, 2024
1 parent 8cfc619 commit a8fd1ab
Showing 1 changed file with 29 additions and 14 deletions.
43 changes: 29 additions & 14 deletions 02-peer-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -1530,29 +1530,32 @@ Closing happens in two stages:
| | | |
| | shutdown(scriptA2) | |
| |----------------------------->| |
| | shutdown(scriptB1) | | (*) Bob doesn't update his script
| |<-----------------------------| |
| | closing_complete | |
| |----------------------------->| |
| | closing_sig | |
| |<-----------------------------| |
| | | |
| | <Both update their script> | | (*) This is a concurrent update
| | <Bob updates his script> | |
| | | |
| | shutdown(scriptA3) | |
| |-------------------> | |
| | closing_complete | |
| |-------------------> | |
| | shutdown(scriptB2) | |
| | <-------------------| |
| |<-----------------------------| |
| | shutdown(scriptA2) | |
| |----------------------------->| |
| | closing_complete | |
| | <-------------------| |
| | shutdown(scriptA3) | |
| | ------------------->| |
| | closing_complete | |
| | ------------------->| | (*) B doesn't answer with closing_sig because A's sig doesn't use scriptB2
| | shutdown(scriptB2) | |
| |<------------------- | |
| | | |
| | <Alice updates her script> | | (*) This is a concurrent update while Bob is sending closing_complete
| | | |
| | shutdown(scriptA3) | |
| |-------------------> | |
| | closing_complete | |
| |<------------------- | | (*) A doesn't answer with closing_sig because B's sig doesn't use scriptA3
| | shutdown(scriptA3) | |
| | ------------------->| |
| | shutdown(scriptB2) | | (*) B answers A's shutdown with his own shutdown, without any changes
| |<-----------------------------| |
| | closing_complete | |
| |----------------------------->| | (*) A now uses scriptB2 and scriptA3 for closing_complete
| | closing_complete | |
Expand Down Expand Up @@ -1806,8 +1809,11 @@ Note: the details and requirements for the transaction being signed are in [BOLT
An output is *dust* if the amount is less than the [Bitcoin Core Dust Thresholds](03-transactions.md#dust-limits).

Both nodes:
- After a `shutdown` has been received, AND no HTLCs remain in either commitment transaction:
- After a `shutdown` has been sent and received, AND no HTLCs remain in either commitment transaction:
- SHOULD send a `closing_complete` message.
- When receiving `shutdown` again, if it did not send `shutdown` first:
- MUST respond with `shutdown`.
- MAY send `closing_complete` afterwards.

The sender of `closing_complete` (aka. "the closer"):
- MUST set `fee_satoshis` to a fee less than or equal to its outstanding balance, rounded down to whole satoshis.
Expand All @@ -1828,6 +1834,10 @@ The sender of `closing_complete` (aka. "the closer"):
- SHOULD NOT set `closer_and_closee_outputs`.
- Otherwise:
- MUST set both `closer_output_only` and `closer_and_closee_outputs`.
- If it wants to send another `closing_complete` (e.g. with a different `fee_satoshis`):
- MUST send `shutdown` first.
- MUST receive `shutdown` from the remote node in response.
- MUST use the `scriptpubkey`s from those `shutdown` messages.

The receiver of `closing_complete` (aka. "the closee"):
- If `fee_satoshis` is greater than the closer's outstanding balance:
Expand Down Expand Up @@ -1885,7 +1895,12 @@ there's a race where you could receive `closing_complete` for the previous outpu
signature won't validate. In this case, ignoring the `closing_complete` is the correct behaviour,
as the new `shutdown` will trigger a new `closing_complete` with the correct signature. This
assumption that we only remember the last-sent of any message is why so many cases of bad
signatures are simply ignored.
signatures are simply ignored.

When sending a new `shutdown`, we must receive a new `shutdown` from the remote node before
sending `closing_complete`. This is necessary to be compatible with future taproot channels
that use musig2 and need to exchange random nonces every time a transaction spending the channel
output is signed.

If the closer proposes a transaction which will not relay (an output is dust, or the fee rate it
proposes is too low), it doesn't harm the closee to sign the transaction.
Expand Down

0 comments on commit a8fd1ab

Please sign in to comment.