Skip to content

Commit 22f3641

Browse files
committed
Allow to construct ShutdownScript from OP_RETURN script
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`.
1 parent db456be commit 22f3641

File tree

1 file changed

+123
-8
lines changed

1 file changed

+123
-8
lines changed

lightning/src/ln/script.rs

Lines changed: 123 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//! Abstractions for scripts used in the Lightning Network.
22
3+
use bitcoin::blockdata::script::Instruction;
34
use bitcoin::hashes::Hash;
4-
use bitcoin::opcodes::all::OP_PUSHBYTES_0 as SEGWIT_V0;
5-
use bitcoin::script::{Script, ScriptBuf};
5+
use bitcoin::opcodes::all::{OP_PUSHBYTES_0 as SEGWIT_V0, OP_RETURN};
6+
use bitcoin::script::{PushBytes, Script, ScriptBuf};
67
use bitcoin::secp256k1::PublicKey;
78
use bitcoin::{WPubkeyHash, WScriptHash, WitnessProgram};
89

@@ -75,6 +76,20 @@ impl ShutdownScript {
7576
Self(ShutdownScriptImpl::Bolt2(ScriptBuf::new_p2wsh(script_hash)))
7677
}
7778

79+
/// Generates an `OP_RETURN` script pubkey from the given `data` bytes.
80+
///
81+
/// This is only needed and valid for channels supporting `option_simple_close`. Please refer
82+
/// to [BOLT-2] for more information.
83+
///
84+
/// # Errors
85+
///
86+
/// This function may return an error if `data` is not [BOLT-2] compliant.
87+
///
88+
/// [BOLT-2]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#closing-negotiation-closing_complete-and-closing_sig
89+
pub fn new_op_return<T: AsRef<PushBytes>>(data: T) -> Result<Self, InvalidShutdownScript> {
90+
Self::try_from(ScriptBuf::new_op_return(data))
91+
}
92+
7893
/// Generates a witness script pubkey from the given segwit version and program.
7994
///
8095
/// Note for version-zero witness scripts you must use [`ShutdownScript::new_p2wpkh`] or
@@ -104,7 +119,8 @@ impl ShutdownScript {
104119

105120
/// Returns whether the shutdown script is compatible with the features as defined by BOLT #2.
106121
///
107-
/// Specifically, checks for compliance with feature `option_shutdown_anysegwit`.
122+
/// Specifically, checks for compliance with feature `option_shutdown_anysegwit` and/or
123+
/// `option_simple_close`.
108124
pub fn is_compatible(&self, features: &InitFeatures) -> bool {
109125
match &self.0 {
110126
ShutdownScriptImpl::Legacy(_) => true,
@@ -116,10 +132,47 @@ impl ShutdownScript {
116132
/// Check if a given script is compliant with BOLT 2's shutdown script requirements for the given
117133
/// counterparty features.
118134
pub(crate) fn is_bolt2_compliant(script: &Script, features: &InitFeatures) -> bool {
135+
// BOLT2:
136+
// 1. `OP_0` `20` 20-bytes (version 0 pay to witness pubkey hash), OR
137+
// 2. `OP_0` `32` 32-bytes (version 0 pay to witness script hash), OR
119138
if script.is_p2pkh() || script.is_p2sh() || script.is_p2wpkh() || script.is_p2wsh() {
120139
true
121-
} else if features.supports_shutdown_anysegwit() {
122-
script.is_witness_program() && script.as_bytes()[0] != SEGWIT_V0.to_u8()
140+
} else if features.supports_shutdown_anysegwit() && script.is_witness_program() {
141+
// 3. if (and only if) `option_shutdown_anysegwit` is negotiated:
142+
// * `OP_1` through `OP_16` inclusive, followed by a single push of 2 to 40 bytes
143+
// (witness program versions 1 through 16)
144+
script.as_bytes()[0] != SEGWIT_V0.to_u8()
145+
} else if features.supports_simple_close() && script.is_op_return() {
146+
// 4. if (and only if) `option_simple_close` is negotiated:
147+
let mut instruction_iter = script.instructions();
148+
if let Some(Ok(Instruction::Op(opcode))) = instruction_iter.next() {
149+
// * `OP_RETURN` followed by one of:
150+
if opcode != OP_RETURN {
151+
return false;
152+
}
153+
154+
match instruction_iter.next() {
155+
Some(Ok(Instruction::PushBytes(bytes))) => {
156+
// * `6` to `75` inclusive followed by exactly that many bytes
157+
if (6..=75).contains(&bytes.len()) {
158+
return instruction_iter.next().is_none();
159+
}
160+
161+
// `rust-bitcoin` interprets `OP_PUSHDATA1` as `Instruction::PushBytes`, having
162+
// us land here in this case, too.
163+
//
164+
// * `76` followed by `76` to `80` followed by exactly that many bytes
165+
if (76..=80).contains(&bytes.len()) {
166+
return instruction_iter.next().is_none();
167+
}
168+
169+
false
170+
},
171+
_ => false,
172+
}
173+
} else {
174+
false
175+
}
123176
} else {
124177
false
125178
}
@@ -142,7 +195,7 @@ impl TryFrom<(ScriptBuf, &InitFeatures)> for ShutdownScript {
142195
type Error = InvalidShutdownScript;
143196

144197
fn try_from((script, features): (ScriptBuf, &InitFeatures)) -> Result<Self, Self::Error> {
145-
if is_bolt2_compliant(&script, features) && script.is_witness_program() {
198+
if is_bolt2_compliant(&script, features) {
146199
Ok(Self(ShutdownScriptImpl::Bolt2(script)))
147200
} else {
148201
Err(InvalidShutdownScript { script })
@@ -175,7 +228,7 @@ mod shutdown_script_tests {
175228
use super::ShutdownScript;
176229

177230
use bitcoin::opcodes;
178-
use bitcoin::script::{Builder, ScriptBuf};
231+
use bitcoin::script::{Builder, PushBytes, ScriptBuf};
179232
use bitcoin::secp256k1::Secp256k1;
180233
use bitcoin::secp256k1::{PublicKey, SecretKey};
181234
use bitcoin::{WitnessProgram, WitnessVersion};
@@ -210,6 +263,13 @@ mod shutdown_script_tests {
210263
features
211264
}
212265

266+
#[cfg(simple_close)]
267+
fn simple_close_features() -> InitFeatures {
268+
let mut features = InitFeatures::empty();
269+
features.set_simple_close_optional();
270+
features
271+
}
272+
213273
#[test]
214274
fn generates_p2wpkh_from_pubkey() {
215275
let pubkey = pubkey();
@@ -246,6 +306,42 @@ mod shutdown_script_tests {
246306
assert!(ShutdownScript::try_from(p2wsh_script).is_ok());
247307
}
248308

309+
#[cfg(simple_close)]
310+
#[test]
311+
fn generates_op_return_from_data() {
312+
let data = [6; 6];
313+
let op_return_script = ScriptBuf::new_op_return(&data);
314+
let shutdown_script = ShutdownScript::new_op_return(&data).unwrap();
315+
assert!(shutdown_script.is_compatible(&simple_close_features()));
316+
assert!(!shutdown_script.is_compatible(&InitFeatures::empty()));
317+
assert_eq!(shutdown_script.into_inner(), op_return_script);
318+
assert!(ShutdownScript::try_from(op_return_script).is_ok());
319+
320+
let assert_pushdata_script_compat = |len| {
321+
let mut pushdata_vec = Builder::new()
322+
.push_opcode(opcodes::all::OP_RETURN)
323+
.push_opcode(opcodes::all::OP_PUSHDATA1)
324+
.into_bytes();
325+
pushdata_vec.push(len as u8);
326+
pushdata_vec.extend_from_slice(&vec![1u8; len]);
327+
let pushdata_script = ScriptBuf::from_bytes(pushdata_vec);
328+
let pushdata_shutdown_script = ShutdownScript::try_from(pushdata_script).unwrap();
329+
assert!(pushdata_shutdown_script.is_compatible(&simple_close_features()));
330+
assert!(!pushdata_shutdown_script.is_compatible(&InitFeatures::empty()));
331+
};
332+
333+
// For `option_simple_close` we assert compatibility with `OP_PUSHDATA1` scripts for the
334+
// intended length range of 76 to 80 bytes.
335+
assert_pushdata_script_compat(76);
336+
assert_pushdata_script_compat(80);
337+
338+
// While the `option_simple_close` spec prescribes the use of `OP_PUSHBYTES_0` up to 75
339+
// bytes, we follow Postel's law and accept if our counterparty would create an
340+
// `OP_PUSHDATA1` script for less than 76 bytes of payload.
341+
assert_pushdata_script_compat(75);
342+
assert_pushdata_script_compat(6);
343+
}
344+
249345
#[test]
250346
fn generates_segwit_from_non_v0_witness_program() {
251347
let witness_program = WitnessProgram::new(WitnessVersion::V16, &[0; 40]).unwrap();
@@ -258,7 +354,26 @@ mod shutdown_script_tests {
258354

259355
#[test]
260356
fn fails_from_unsupported_script() {
261-
let op_return = ScriptBuf::new_op_return(&[0; 42]);
357+
// For `option_simple_close` we assert we fail when:
358+
//
359+
// - The first byte of the OP_RETURN data (interpreted as u8 int) is not equal to the
360+
// remaining number of bytes (i.e., `[5; 6]` would succeed here).
361+
let op_return = ScriptBuf::new_op_return(&[5; 5]);
262362
assert!(ShutdownScript::try_from(op_return).is_err());
363+
364+
// - The OP_RETURN data will fail if it's longer than 80 bytes.
365+
let mut pushdata_vec = Builder::new()
366+
.push_opcode(opcodes::all::OP_RETURN)
367+
.push_opcode(opcodes::all::OP_PUSHDATA1)
368+
.into_bytes();
369+
pushdata_vec.push(81);
370+
pushdata_vec.extend_from_slice(&[1u8; 81]);
371+
let pushdata_script = ScriptBuf::from_bytes(pushdata_vec);
372+
assert!(ShutdownScript::try_from(pushdata_script).is_err());
373+
374+
// - In `ShutdownScript::new_op_return` the OP_RETURN data is longer than 80 bytes.
375+
let big_buffer = &[1u8; 81][..];
376+
let push_bytes: &PushBytes = big_buffer.try_into().unwrap();
377+
assert!(ShutdownScript::new_op_return(&push_bytes).is_err());
263378
}
264379
}

0 commit comments

Comments
 (0)