Skip to content

option_simple_close: Add feature, messaging types, shutdown script construction #3861

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

tnull
Copy link
Contributor

@tnull tnull commented Jun 16, 2025

This is the initial PR in a series aiming to add support for the new mutual close protocol option_simple_close (lightning/bolts#1205).

In this PR we add the feature bits, message types (including serialization and fuzz targets), as well as support to construct ShutdownScripts from an OP_RETURN payload as per spec.

We also include some commits dropping rustfmt::skips from methods related to channel closing, as we're going to touch them going forward.

Reviewers can find an outline of further planned steps/PRs over at #3862

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Jun 16, 2025

👋 Thanks for assigning @tankyleo as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnull tnull force-pushed the 2025-06-simple-close-1 branch 2 times, most recently from 5b737bd to dfcd328 Compare June 16, 2025 09:32
@tnull tnull requested review from wpaulino and tankyleo and removed request for tankyleo June 16, 2025 09:34
//
// - The first byte of the OP_RETURN data (interpreted as u8 int) is not equal to the
// remaining number of bytes (i.e., `[5; 6]` would succeed here).
let op_return = ScriptBuf::new_op_return(&[5; 5]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also add one for the OP_PUSHDATA1 case below 80 bytes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now added test coverage for OP_PUSHDATA1 scripts with 6..=75 bytes. While the spec says we should use OP_PUSHBYTES for these, I think we should be lenient when we parse them, i.e., should still accept them if our counterparty messes up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But let me know if you think we should enforce minimal pushes, which would be a one-line change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does save a byte, but the spec doesn't seem to have a minimal push requirement, just that it's a single push.

// `Instruction::PushBytes`, having us land here in this case, too.
//
// * `76` followed by `76` to `80` followed by exactly that many bytes
if (76..=80).contains(&bytes.len()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec does not mention this range is inclusive unlike the one above

Copy link
Contributor Author

@tnull tnull Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, '76 to 80' bytes reads as inclusive to me, no? The spec also mentions:

`OP_RETURN` is only standard if followed by PUSH opcodes, and the total script
is 83 bytes or less. We are slightly stricter, to only allow a single PUSH, but
there are two forms in script: one which pushes up to 75 bytes, and a longer
one (`OP_PUSHDATA1`) which is needed for 76-80 bytes.

Making it pretty clear that OP_PUSHDATA1 will be used for any script length of 76 to (including) 80 bytes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's clear enough, I was referring to this though:

  • OP_RETURN followed by one of:
    • 6 to 75 inclusive followed by exactly that many bytes
    • 76 followed by 76 to 80 followed by exactly that many bytes

Just seemed weird to include "inclusive" in one but not the other if both are intended to be.

@ldk-reviews-bot
Copy link

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

Comment on lines 84 to 85
/// Note this only supports creating a script with data of up to 76 bytes length via
/// [`PushBytes`].
Copy link
Contributor

@tankyleo tankyleo Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm this passes though. From the PushBytes docs: "The encoding of Bitcoin script restricts data pushes to be less than 2^32 bytes long. This type represents slices that are guaranteed to be within the limit so they can be put in the script safely."

use bitcoin::script::PushBytes;
use bitcoin::script::ScriptBuf;

fn main() {
    let big_buffer = &[0xfau8; 1000][..];
    let push_bytes: &PushBytes = big_buffer.try_into().unwrap();
    let script_buf = ScriptBuf::new_op_return(push_bytes);
    assert_eq!(script_buf.len(), 1004);
}

Copy link
Contributor Author

@tnull tnull Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that's indeed true.

For 6 up to 75 bytes there are specific OP_PUSHBYTES_X variants, and rust-bitcoin only implements corresponding code to convert of static-sized arrays only for these lengths (https://github.com/rust-bitcoin/rust-bitcoin/blob/f034367bbcdca92b1dee4f86dcd23fcd7f90a880/bitcoin/src/blockdata/script/push_bytes.rs#L137-L189) but indeed it seems you can construct them as you sketched above, not sure how I overlooked this.

Now added a fixup that will check BOLT2-compat in new_op_return, and added a test case to make sure the above example fails to parse.

@tnull tnull force-pushed the 2025-06-simple-close-1 branch 2 times, most recently from 892b169 to 16d6f83 Compare June 17, 2025 11:02
@tnull tnull force-pushed the 2025-06-simple-close-1 branch 3 times, most recently from d66ec30 to 3c32e33 Compare June 17, 2025 11:07
@wpaulino
Copy link
Contributor

Good to squash

tnull added 6 commits June 17, 2025 19:58
We define the feature bits that will be set in `Init` and `Node`
contexts.
.. although behind a `cfg` gate for now
We add a new `ShutdownScript::new_op_return` constructor and extend the
`is_bolt_compliant` check to enforce the new rules introduced by
`option_simple_close`.
@tnull tnull force-pushed the 2025-06-simple-close-1 branch from 3c32e33 to 76971d8 Compare June 17, 2025 17:58
@tnull
Copy link
Contributor Author

tnull commented Jun 17, 2025

Good to squash

Squashed without further changes.

@tnull tnull requested a review from tankyleo June 17, 2025 18:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants