Skip to content

smite: add is_standard_shutdown_script helper - #186

Open
ekzyis wants to merge 1 commit into
lnfuzz:masterfrom
ekzyis:is-standard-shutdown-script
Open

smite: add is_standard_shutdown_script helper#186
ekzyis wants to merge 1 commit into
lnfuzz:masterfrom
ekzyis:is-standard-shutdown-script

Conversation

@ekzyis

@ekzyis ekzyis commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

From the commit message:

BOLT-02 specifies sender requirements for shutdown scripts. They must be
witness v0 (P2WPKH, P2WSH) or following features must be negotiated:

  • option_shutdown_anysegwit: witness v1-v16 with a 2..=40 byte program
  • option_simple_close: OP_RETURN with a single data push of 6..=80
    bytes

Legacy scripts (P2PKH, P2SH) may be accepted for backward compatibility.

This applies to the shutdown and closing_complete messages, and the
upfront_shutdown_script TLV in the open_channel, open_channel2,
accept_channel and accept_channel2 messages.

This commit adds a helper to catch targets that don't comply with the
spec.

As per the note I added to the code, I'm not sure if the fuzzer should also reject legacy scripts, since a target must not send them. update: decided to reject them, see discussion

I haven't wired this into existing code or #163 yet, but I thought the introduction of the helper might be worthwile to review itself, especially considering the question wrt legacy scripts.

@ekzyis
ekzyis force-pushed the is-standard-shutdown-script branch from f33051b to 22217d9 Compare August 4, 2026 11:07

@NishantBansal2003 NishantBansal2003 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks! I was about to add this as a follow-up to #185, but it looks like I don’t have to now

Comment thread smite/src/bolt/shutdown.rs Outdated
Comment on lines +57 to +65
/// Feature bits that widen the set of standard `shutdown` scriptpubkeys.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ShutdownScriptFeatures {
/// Additionally permits witness program versions 1..=16 with a 2..=40 byte
/// program.
pub option_shutdown_anysegwit: bool,
/// Additionally permits a single-push `OP_RETURN` script.
pub option_simple_close: bool,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this can be removed/simplified once #192 gets merged, so we can just use just use: negotiated_features.supports_feature(Features::OPTION_SHUTDOWN_ANYSEGWIT) or negotiated_features.supports_feature(Features::OPTION_SIMPLE_CLOSE)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sounds good, I will review #192 and then rebase this PR after merge

Comment thread smite/src/bolt/shutdown.rs Outdated
Comment thread smite/src/bolt/shutdown.rs Outdated
@ekzyis
ekzyis force-pushed the is-standard-shutdown-script branch from 22217d9 to 220b1bf Compare August 9, 2026 12:30
Comment thread smite/src/bolt/shutdown.rs Outdated
BOLT-02 specifies sender requirements for shutdown scripts. They must be
witness v0 (P2WPKH, P2WSH) or following features must be negotiated:

* `option_shutdown_anysegwit`: witness v1-v16 with a 2..=40 byte program
* `option_simple_close`: `OP_RETURN` with a single minimal data push of
  6..=80 bytes

Receivers may accept legacy scripts (P2PKH, P2SH), but we reject them
since we're judging the sender's output.

This applies to the `shutdown` and `closing_complete` messages, and the
`upfront_shutdown_script` TLV in the `open_channel`, `open_channel2`,
`accept_channel` and `accept_channel2` messages.

This commit adds a helper to catch targets that don't comply with the
spec.
@ekzyis
ekzyis force-pushed the is-standard-shutdown-script branch from 220b1bf to 847c2f1 Compare August 9, 2026 18:00

@NishantBansal2003 NishantBansal2003 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mostly, it looks good. Just a few small comments. I'm not sure how this follows the merge with #192, but if it's merged before, I'll add the updated feature check there

/// Returns `true` if `spk` is a standard `shutdown` scriptpubkey per BOLT 2: P2WPKH or P2WSH.
/// Negotiated `features` widen the accepted set.
///
/// Legacy P2PKH/P2SH are rejected. A receiver may accept them for backward compatibility, but this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In the oracle, we verify what the peer accepted (and whether it was valid), and then we check our side to ensure that the target is actually sending that.

So, when verifying a target-accepted open_channel, I need it to pass even if it contains the legacy script. However, for our received accept_channel, I need to ensure that it does not contain legacy scripts


#[test]
#[allow(clippy::similar_names)]
fn is_standard_shutdown_script_rejects_legacy_accepts_witness_v0() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: I think for testing semantic script types, we should use the bitcoin crate to create the scripts, eg: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()).into_bytes();, but for testing non-standard cases, we can construct the raw bytes manually

}

#[test]
fn is_standard_shutdown_script_accepts_anysegwit() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: spec allows 2..=40 bytes, so we can test the boundary cases as well

assert!(!is_standard_shutdown_script(&too_long, ALL));

// Non-minimal push: OP_PUSHDATA1 used for less than 76 bytes.
let mut non_minimal = vec![OP_RETURN.to_u8(), OP_PUSHDATA1.to_u8(), 10];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: could test it at the boundary, i.e. 75, to be explicit about the boundary

Comment on lines +276 to +278
// OP_RETURN with a 1-byte push: below the simple_close minimum of 6.
let op_return_spk = vec![OP_RETURN.to_u8(), OP_PUSHBYTES_1.to_u8(), 0xff];
assert!(!is_standard_shutdown_script(&op_return_spk, ALL));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: this case is similar to one of the assertions added in is_standard_shutdown_script_rejects_invalid_simple_close_op_return. I think we can follow the same pattern here, remove this OP_RETURN assertion, and rename the test to something like is_standard_shutdown_script_rejects_invalid_segwit or a better name, since this test mostly contains those cases

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.

2 participants