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,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.
118134pub ( 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