1
1
//! Abstractions for scripts used in the Lightning Network.
2
2
3
+ use bitcoin:: blockdata:: script:: Instruction ;
3
4
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 } ;
6
7
use bitcoin:: secp256k1:: PublicKey ;
7
8
use bitcoin:: { WPubkeyHash , WScriptHash , WitnessProgram } ;
8
9
@@ -75,6 +76,20 @@ impl ShutdownScript {
75
76
Self ( ShutdownScriptImpl :: Bolt2 ( ScriptBuf :: new_p2wsh ( script_hash) ) )
76
77
}
77
78
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
+
78
93
/// Generates a witness script pubkey from the given segwit version and program.
79
94
///
80
95
/// Note for version-zero witness scripts you must use [`ShutdownScript::new_p2wpkh`] or
@@ -116,10 +131,48 @@ impl ShutdownScript {
116
131
/// Check if a given script is compliant with BOLT 2's shutdown script requirements for the given
117
132
/// counterparty features.
118
133
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
119
137
if script. is_p2pkh ( ) || script. is_p2sh ( ) || script. is_p2wpkh ( ) || script. is_p2wsh ( ) {
120
138
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
+ }
123
176
} else {
124
177
false
125
178
}
@@ -142,7 +195,7 @@ impl TryFrom<(ScriptBuf, &InitFeatures)> for ShutdownScript {
142
195
type Error = InvalidShutdownScript ;
143
196
144
197
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) {
146
199
Ok ( Self ( ShutdownScriptImpl :: Bolt2 ( script) ) )
147
200
} else {
148
201
Err ( InvalidShutdownScript { script } )
@@ -210,6 +263,13 @@ mod shutdown_script_tests {
210
263
features
211
264
}
212
265
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
+
213
273
#[ test]
214
274
fn generates_p2wpkh_from_pubkey ( ) {
215
275
let pubkey = pubkey ( ) ;
@@ -246,6 +306,29 @@ mod shutdown_script_tests {
246
306
assert ! ( ShutdownScript :: try_from( p2wsh_script) . is_ok( ) ) ;
247
307
}
248
308
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
+
249
332
#[ test]
250
333
fn generates_segwit_from_non_v0_witness_program ( ) {
251
334
let witness_program = WitnessProgram :: new ( WitnessVersion :: V16 , & [ 0 ; 40 ] ) . unwrap ( ) ;
@@ -258,7 +341,21 @@ mod shutdown_script_tests {
258
341
259
342
#[ test]
260
343
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 ] ) ;
262
349
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( ) ) ;
263
360
}
264
361
}
0 commit comments