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+ /// 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.
118133pub ( 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