Skip to content

Commit dfcd328

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 982b90b commit dfcd328

File tree

1 file changed

+103
-6
lines changed

1 file changed

+103
-6
lines changed

lightning/src/ln/script.rs

Lines changed: 103 additions & 6 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+
/// Note this only supports creating a script with data of up to 76 bytes length via
85+
/// [`PushBytes`].
86+
///
87+
/// [BOLT-2]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#closing-negotiation-closing_complete-and-closing_sig
88+
pub fn new_op_return<T: AsRef<PushBytes>>(data: T) -> Self {
89+
let script = ScriptBuf::new_op_return(data);
90+
Self(ShutdownScriptImpl::Bolt2(script))
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
@@ -116,10 +131,48 @@ impl ShutdownScript {
116131
/// Check if a given script is compliant with BOLT 2's shutdown script requirements for the given
117132
/// counterparty features.
118133
pub(crate) fn is_bolt2_compliant(script: &Script, features: &InitFeatures) -> bool {
134+
// BOLT2:
135+
// 1. `OP_0` `20` 20-bytes (version 0 pay to witness pubkey hash), OR
136+
// 2. `OP_0` `32` 32-bytes (version 0 pay to witness script hash), OR
119137
if script.is_p2pkh() || script.is_p2sh() || script.is_p2wpkh() || script.is_p2wsh() {
120138
true
121-
} else if features.supports_shutdown_anysegwit() {
122-
script.is_witness_program() && script.as_bytes()[0] != SEGWIT_V0.to_u8()
139+
} else if features.supports_shutdown_anysegwit() && script.is_witness_program() {
140+
// 3. if (and only if) `option_shutdown_anysegwit` is negotiated:
141+
// * `OP_1` through `OP_16` inclusive, followed by a single push of 2 to 40 bytes
142+
// (witness program versions 1 through 16)
143+
script.as_bytes()[0] != SEGWIT_V0.to_u8()
144+
} else if features.supports_simple_close() && script.is_op_return() {
145+
// 4. if (and only if) `option_simple_close` is negotiated:
146+
let mut instruction_iter = script.instructions();
147+
if let Some(Ok(Instruction::Op(opcode))) = instruction_iter.next() {
148+
// * `OP_RETURN` followed by one of:
149+
if opcode != OP_RETURN {
150+
return false;
151+
}
152+
153+
match instruction_iter.next() {
154+
Some(Ok(Instruction::PushBytes(bytes))) => {
155+
// * `6` to `75` inclusive followed by exactly that many bytes
156+
if (6..=75).contains(&bytes.len()) {
157+
return instruction_iter.next().is_none();
158+
}
159+
160+
// While `rust-bitcoin` doesn't allow to construct `PushBytes` from arrays
161+
// longer than 75 bytes, itself curiously interprets `OP_PUSHDATA1` as
162+
// `Instruction::PushBytes`, having 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 })
@@ -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,29 @@ 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);
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 mut pushdata_vec = Builder::new()
321+
.push_opcode(opcodes::all::OP_RETURN)
322+
.push_opcode(opcodes::all::OP_PUSHDATA1)
323+
.into_bytes();
324+
pushdata_vec.push(80);
325+
pushdata_vec.extend_from_slice(&[1u8; 80]);
326+
let pushdata_script = ScriptBuf::from_bytes(pushdata_vec);
327+
let pushdata_shutdown_script = ShutdownScript::try_from(pushdata_script).unwrap();
328+
assert!(pushdata_shutdown_script.is_compatible(&simple_close_features()));
329+
assert!(!pushdata_shutdown_script.is_compatible(&InitFeatures::empty()));
330+
}
331+
249332
#[test]
250333
fn generates_segwit_from_non_v0_witness_program() {
251334
let witness_program = WitnessProgram::new(WitnessVersion::V16, &[0; 40]).unwrap();
@@ -258,7 +341,21 @@ mod shutdown_script_tests {
258341

259342
#[test]
260343
fn fails_from_unsupported_script() {
261-
let op_return = ScriptBuf::new_op_return(&[0; 42]);
344+
// For `option_simple_close` we assert we fail when:
345+
//
346+
// - The first byte of the OP_RETURN data (interpreted as u8 int) is not equal to the
347+
// remaining number of bytes (i.e., `[5; 6]` would succeed here).
348+
let op_return = ScriptBuf::new_op_return(&[5; 5]);
262349
assert!(ShutdownScript::try_from(op_return).is_err());
350+
351+
// - The OP_RETURN data will fail if it's longer than 80 bytes.
352+
let mut pushdata_vec = Builder::new()
353+
.push_opcode(opcodes::all::OP_RETURN)
354+
.push_opcode(opcodes::all::OP_PUSHDATA1)
355+
.into_bytes();
356+
pushdata_vec.push(81);
357+
pushdata_vec.extend_from_slice(&[1u8; 81]);
358+
let pushdata_script = ScriptBuf::from_bytes(pushdata_vec);
359+
assert!(ShutdownScript::try_from(pushdata_script).is_err());
263360
}
264361
}

0 commit comments

Comments
 (0)