11//! Abstractions for scripts used in the Lightning Network.
22
3+ use bitcoin:: blockdata:: script:: Instruction ;
34use 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 } ;
67use bitcoin:: secp256k1:: PublicKey ;
78use bitcoin:: { WPubkeyHash , WScriptHash , WitnessProgram } ;
89
@@ -75,6 +76,25 @@ 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+ /// Will return [`InvalidShutdownScript`] if the given data is not [BOLT-2] compliant based on
85+ /// the given features.
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 > > (
89+ data : T , features : & InitFeatures ,
90+ ) -> Result < Self , InvalidShutdownScript > {
91+ let script = ScriptBuf :: new_op_return ( data) ;
92+ if !is_bolt2_compliant ( & script, features) {
93+ return Err ( InvalidShutdownScript { script } ) ;
94+ }
95+ Ok ( Self ( ShutdownScriptImpl :: Bolt2 ( script) ) )
96+ }
97+
7898 /// Generates a witness script pubkey from the given segwit version and program.
7999 ///
80100 /// Note for version-zero witness scripts you must use [`ShutdownScript::new_p2wpkh`] or
@@ -116,10 +136,47 @@ impl ShutdownScript {
116136/// Check if a given script is compliant with BOLT 2's shutdown script requirements for the given
117137/// counterparty features.
118138pub ( crate ) fn is_bolt2_compliant ( script : & Script , features : & InitFeatures ) -> bool {
139+ // BOLT2:
140+ // 1. `OP_0` `20` 20-bytes (version 0 pay to witness pubkey hash), OR
141+ // 2. `OP_0` `32` 32-bytes (version 0 pay to witness script hash), OR
119142 if script. is_p2pkh ( ) || script. is_p2sh ( ) || script. is_p2wpkh ( ) || script. is_p2wsh ( ) {
120143 true
121- } else if features. supports_shutdown_anysegwit ( ) {
122- script. is_witness_program ( ) && script. as_bytes ( ) [ 0 ] != SEGWIT_V0 . to_u8 ( )
144+ } else if features. supports_shutdown_anysegwit ( ) && script. is_witness_program ( ) {
145+ // 3. if (and only if) `option_shutdown_anysegwit` is negotiated:
146+ // * `OP_1` through `OP_16` inclusive, followed by a single push of 2 to 40 bytes
147+ // (witness program versions 1 through 16)
148+ script. as_bytes ( ) [ 0 ] != SEGWIT_V0 . to_u8 ( )
149+ } else if features. supports_simple_close ( ) && script. is_op_return ( ) {
150+ // 4. if (and only if) `option_simple_close` is negotiated:
151+ let mut instruction_iter = script. instructions ( ) ;
152+ if let Some ( Ok ( Instruction :: Op ( opcode) ) ) = instruction_iter. next ( ) {
153+ // * `OP_RETURN` followed by one of:
154+ if opcode != OP_RETURN {
155+ return false ;
156+ }
157+
158+ match instruction_iter. next ( ) {
159+ Some ( Ok ( Instruction :: PushBytes ( bytes) ) ) => {
160+ // * `6` to `75` inclusive followed by exactly that many bytes
161+ if ( 6 ..=75 ) . contains ( & bytes. len ( ) ) {
162+ return instruction_iter. next ( ) . is_none ( ) ;
163+ }
164+
165+ // `rust-bitcoin` interprets `OP_PUSHDATA1` as `Instruction::PushBytes`, having
166+ // us land here in this case, too.
167+ //
168+ // * `76` followed by `76` to `80` followed by exactly that many bytes
169+ if ( 76 ..=80 ) . contains ( & bytes. len ( ) ) {
170+ return instruction_iter. next ( ) . is_none ( ) ;
171+ }
172+
173+ false
174+ } ,
175+ _ => false ,
176+ }
177+ } else {
178+ false
179+ }
123180 } else {
124181 false
125182 }
@@ -142,7 +199,7 @@ impl TryFrom<(ScriptBuf, &InitFeatures)> for ShutdownScript {
142199 type Error = InvalidShutdownScript ;
143200
144201 fn try_from ( ( script, features) : ( ScriptBuf , & InitFeatures ) ) -> Result < Self , Self :: Error > {
145- if is_bolt2_compliant ( & script, features) && script . is_witness_program ( ) {
202+ if is_bolt2_compliant ( & script, features) {
146203 Ok ( Self ( ShutdownScriptImpl :: Bolt2 ( script) ) )
147204 } else {
148205 Err ( InvalidShutdownScript { script } )
@@ -175,13 +232,15 @@ mod shutdown_script_tests {
175232 use super :: ShutdownScript ;
176233
177234 use bitcoin:: opcodes;
178- use bitcoin:: script:: { Builder , ScriptBuf } ;
235+ use bitcoin:: script:: { Builder , PushBytes , ScriptBuf } ;
179236 use bitcoin:: secp256k1:: Secp256k1 ;
180237 use bitcoin:: secp256k1:: { PublicKey , SecretKey } ;
181238 use bitcoin:: { WitnessProgram , WitnessVersion } ;
182239
240+ use crate :: ln:: channelmanager:: provided_init_features;
183241 use crate :: prelude:: * ;
184242 use crate :: types:: features:: InitFeatures ;
243+ use crate :: util:: config:: UserConfig ;
185244
186245 fn pubkey ( ) -> bitcoin:: key:: PublicKey {
187246 let secp_ctx = Secp256k1 :: signing_only ( ) ;
@@ -210,6 +269,13 @@ mod shutdown_script_tests {
210269 features
211270 }
212271
272+ #[ cfg( simple_close) ]
273+ fn simple_close_features ( ) -> InitFeatures {
274+ let mut features = InitFeatures :: empty ( ) ;
275+ features. set_simple_close_optional ( ) ;
276+ features
277+ }
278+
213279 #[ test]
214280 fn generates_p2wpkh_from_pubkey ( ) {
215281 let pubkey = pubkey ( ) ;
@@ -246,6 +312,43 @@ mod shutdown_script_tests {
246312 assert ! ( ShutdownScript :: try_from( p2wsh_script) . is_ok( ) ) ;
247313 }
248314
315+ #[ cfg( simple_close) ]
316+ #[ test]
317+ fn generates_op_return_from_data ( ) {
318+ let data = [ 6 ; 6 ] ;
319+ let features = provided_init_features ( & UserConfig :: default ( ) ) ;
320+ let op_return_script = ScriptBuf :: new_op_return ( & data) ;
321+ let shutdown_script = ShutdownScript :: new_op_return ( & data, & features) . unwrap ( ) ;
322+ assert ! ( shutdown_script. is_compatible( & simple_close_features( ) ) ) ;
323+ assert ! ( !shutdown_script. is_compatible( & InitFeatures :: empty( ) ) ) ;
324+ assert_eq ! ( shutdown_script. into_inner( ) , op_return_script) ;
325+ assert ! ( ShutdownScript :: try_from( op_return_script) . is_ok( ) ) ;
326+
327+ let assert_pushdata_script_compat = |len| {
328+ let mut pushdata_vec = Builder :: new ( )
329+ . push_opcode ( opcodes:: all:: OP_RETURN )
330+ . push_opcode ( opcodes:: all:: OP_PUSHDATA1 )
331+ . into_bytes ( ) ;
332+ pushdata_vec. push ( len as u8 ) ;
333+ pushdata_vec. extend_from_slice ( & vec ! [ 1u8 ; len] ) ;
334+ let pushdata_script = ScriptBuf :: from_bytes ( pushdata_vec) ;
335+ let pushdata_shutdown_script = ShutdownScript :: try_from ( pushdata_script) . unwrap ( ) ;
336+ assert ! ( pushdata_shutdown_script. is_compatible( & simple_close_features( ) ) ) ;
337+ assert ! ( !pushdata_shutdown_script. is_compatible( & InitFeatures :: empty( ) ) ) ;
338+ } ;
339+
340+ // For `option_simple_close` we assert compatibility with `OP_PUSHDATA1` scripts for the
341+ // intended length range of 76 to 80 bytes.
342+ assert_pushdata_script_compat ( 76 ) ;
343+ assert_pushdata_script_compat ( 80 ) ;
344+
345+ // While the `option_simple_close` spec prescribes the use of `OP_PUSHBYTES_0` up to 75
346+ // bytes, we follow Postel's law and accept if our counterparty would create an
347+ // `OP_PUSHDATA1` script for less than 76 bytes of payload.
348+ assert_pushdata_script_compat ( 75 ) ;
349+ assert_pushdata_script_compat ( 6 ) ;
350+ }
351+
249352 #[ test]
250353 fn generates_segwit_from_non_v0_witness_program ( ) {
251354 let witness_program = WitnessProgram :: new ( WitnessVersion :: V16 , & [ 0 ; 40 ] ) . unwrap ( ) ;
@@ -258,7 +361,27 @@ mod shutdown_script_tests {
258361
259362 #[ test]
260363 fn fails_from_unsupported_script ( ) {
261- let op_return = ScriptBuf :: new_op_return ( & [ 0 ; 42 ] ) ;
364+ // For `option_simple_close` we assert we fail when:
365+ //
366+ // - The first byte of the OP_RETURN data (interpreted as u8 int) is not equal to the
367+ // remaining number of bytes (i.e., `[5; 6]` would succeed here).
368+ let op_return = ScriptBuf :: new_op_return ( & [ 5 ; 5 ] ) ;
262369 assert ! ( ShutdownScript :: try_from( op_return) . is_err( ) ) ;
370+
371+ // - The OP_RETURN data will fail if it's longer than 80 bytes.
372+ let mut pushdata_vec = Builder :: new ( )
373+ . push_opcode ( opcodes:: all:: OP_RETURN )
374+ . push_opcode ( opcodes:: all:: OP_PUSHDATA1 )
375+ . into_bytes ( ) ;
376+ pushdata_vec. push ( 81 ) ;
377+ pushdata_vec. extend_from_slice ( & [ 1u8 ; 81 ] ) ;
378+ let pushdata_script = ScriptBuf :: from_bytes ( pushdata_vec) ;
379+ assert ! ( ShutdownScript :: try_from( pushdata_script) . is_err( ) ) ;
380+
381+ // - In `ShutdownScript::new_op_return` the OP_RETURN data is longer than 80 bytes.
382+ let features = provided_init_features ( & UserConfig :: default ( ) ) ;
383+ let big_buffer = & [ 1u8 ; 81 ] [ ..] ;
384+ let push_bytes: & PushBytes = big_buffer. try_into ( ) . unwrap ( ) ;
385+ assert ! ( ShutdownScript :: new_op_return( & push_bytes, & features) . is_err( ) ) ;
263386 }
264387}
0 commit comments