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
+ /// # 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
+
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
@@ -104,7 +119,8 @@ impl ShutdownScript {
104
119
105
120
/// Returns whether the shutdown script is compatible with the features as defined by BOLT #2.
106
121
///
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`.
108
124
pub fn is_compatible ( & self , features : & InitFeatures ) -> bool {
109
125
match & self . 0 {
110
126
ShutdownScriptImpl :: Legacy ( _) => true ,
@@ -116,10 +132,47 @@ impl ShutdownScript {
116
132
/// Check if a given script is compliant with BOLT 2's shutdown script requirements for the given
117
133
/// counterparty features.
118
134
pub ( 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
119
138
if script. is_p2pkh ( ) || script. is_p2sh ( ) || script. is_p2wpkh ( ) || script. is_p2wsh ( ) {
120
139
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
+ }
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 } )
@@ -175,7 +228,7 @@ mod shutdown_script_tests {
175
228
use super :: ShutdownScript ;
176
229
177
230
use bitcoin:: opcodes;
178
- use bitcoin:: script:: { Builder , ScriptBuf } ;
231
+ use bitcoin:: script:: { Builder , PushBytes , ScriptBuf } ;
179
232
use bitcoin:: secp256k1:: Secp256k1 ;
180
233
use bitcoin:: secp256k1:: { PublicKey , SecretKey } ;
181
234
use bitcoin:: { WitnessProgram , WitnessVersion } ;
@@ -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,42 @@ 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) . 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
+
249
345
#[ test]
250
346
fn generates_segwit_from_non_v0_witness_program ( ) {
251
347
let witness_program = WitnessProgram :: new ( WitnessVersion :: V16 , & [ 0 ; 40 ] ) . unwrap ( ) ;
@@ -258,7 +354,26 @@ mod shutdown_script_tests {
258
354
259
355
#[ test]
260
356
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 ] ) ;
262
362
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( ) ) ;
263
378
}
264
379
}
0 commit comments