1use bitcoin::blockdata::script::Instruction;
4use bitcoin::hashes::Hash;
5use bitcoin::opcodes::all::{OP_PUSHBYTES_0 as SEGWIT_V0, OP_RETURN};
6use bitcoin::script::{PushBytes, Script, ScriptBuf};
7use bitcoin::secp256k1::PublicKey;
8use bitcoin::{WPubkeyHash, WScriptHash, WitnessProgram};
9
10use crate::ln::channelmanager;
11use crate::ln::msgs::DecodeError;
12use crate::types::features::InitFeatures;
13use crate::util::config::UserConfig;
14use crate::util::ser::{Readable, Writeable, Writer};
15
16use crate::io;
17
18#[allow(unused_imports)]
19use crate::prelude::*;
20
21#[derive(Clone, PartialEq, Eq)]
25pub struct ShutdownScript(ShutdownScriptImpl);
26
27#[derive(Clone, Debug)]
29pub struct InvalidShutdownScript {
30 pub script: ScriptBuf,
34}
35
36#[derive(Clone, PartialEq, Eq)]
37enum ShutdownScriptImpl {
38 Legacy(PublicKey),
41
42 Bolt2(ScriptBuf),
44}
45
46impl Writeable for ShutdownScript {
47 fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
48 self.0.write(w)
49 }
50}
51
52impl Readable for ShutdownScript {
53 fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
54 Ok(ShutdownScript(ShutdownScriptImpl::read(r)?))
55 }
56}
57
58impl_writeable_tlv_based_enum_legacy!(ShutdownScriptImpl, ;
59 (0, Legacy),
60 (1, Bolt2),
61);
62
63impl ShutdownScript {
64 pub(crate) fn new_p2wpkh_from_pubkey(pubkey: PublicKey) -> Self {
66 Self(ShutdownScriptImpl::Legacy(pubkey))
67 }
68
69 pub fn new_p2wpkh(pubkey_hash: &WPubkeyHash) -> Self {
71 Self(ShutdownScriptImpl::Bolt2(ScriptBuf::new_p2wpkh(pubkey_hash)))
72 }
73
74 pub fn new_p2wsh(script_hash: &WScriptHash) -> Self {
76 Self(ShutdownScriptImpl::Bolt2(ScriptBuf::new_p2wsh(script_hash)))
77 }
78
79 pub fn new_op_return<T: AsRef<PushBytes>>(data: T) -> Result<Self, InvalidShutdownScript> {
90 Self::try_from(ScriptBuf::new_op_return(data))
91 }
92
93 pub fn new_witness_program(
102 witness_program: &WitnessProgram,
103 ) -> Result<Self, InvalidShutdownScript> {
104 Self::try_from(ScriptBuf::new_witness_program(witness_program))
105 }
106
107 pub fn into_inner(self) -> ScriptBuf {
109 self.into()
110 }
111
112 pub fn as_legacy_pubkey(&self) -> Option<&PublicKey> {
114 match &self.0 {
115 ShutdownScriptImpl::Legacy(pubkey) => Some(pubkey),
116 ShutdownScriptImpl::Bolt2(_) => None,
117 }
118 }
119
120 pub fn is_compatible(&self, features: &InitFeatures) -> bool {
125 match &self.0 {
126 ShutdownScriptImpl::Legacy(_) => true,
127 ShutdownScriptImpl::Bolt2(script) => is_bolt2_compliant(script, features),
128 }
129 }
130}
131
132pub(crate) fn is_bolt2_compliant(script: &Script, features: &InitFeatures) -> bool {
135 if script.is_p2pkh() || script.is_p2sh() || script.is_p2wpkh() || script.is_p2wsh() {
139 true
140 } else if features.supports_shutdown_anysegwit() && script.is_witness_program() {
141 script.as_bytes()[0] != SEGWIT_V0.to_u8()
145 } else if features.supports_simple_close() && script.is_op_return() {
146 let mut instruction_iter = script.instructions();
148 if let Some(Ok(Instruction::Op(opcode))) = instruction_iter.next() {
149 if opcode != OP_RETURN {
151 return false;
152 }
153
154 match instruction_iter.next() {
155 Some(Ok(Instruction::PushBytes(bytes))) => {
156 if (6..=75).contains(&bytes.len()) {
158 return instruction_iter.next().is_none();
159 }
160
161 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 }
176 } else {
177 false
178 }
179}
180
181impl TryFrom<ScriptBuf> for ShutdownScript {
184 type Error = InvalidShutdownScript;
185
186 fn try_from(script: ScriptBuf) -> Result<Self, Self::Error> {
187 let features = channelmanager::provided_init_features(&UserConfig::default());
188 Self::try_from((script, &features))
189 }
190}
191
192impl TryFrom<(ScriptBuf, &InitFeatures)> for ShutdownScript {
195 type Error = InvalidShutdownScript;
196
197 fn try_from((script, features): (ScriptBuf, &InitFeatures)) -> Result<Self, Self::Error> {
198 if is_bolt2_compliant(&script, features) {
199 Ok(Self(ShutdownScriptImpl::Bolt2(script)))
200 } else {
201 Err(InvalidShutdownScript { script })
202 }
203 }
204}
205
206impl From<ShutdownScript> for ScriptBuf {
207 fn from(value: ShutdownScript) -> Self {
208 match value.0 {
209 ShutdownScriptImpl::Legacy(pubkey) => {
210 ScriptBuf::new_p2wpkh(&WPubkeyHash::hash(&pubkey.serialize()))
211 },
212 ShutdownScriptImpl::Bolt2(script_pubkey) => script_pubkey,
213 }
214 }
215}
216
217impl core::fmt::Display for ShutdownScript {
218 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
219 match &self.0 {
220 ShutdownScriptImpl::Legacy(_) => self.clone().into_inner().fmt(f),
221 ShutdownScriptImpl::Bolt2(script) => script.fmt(f),
222 }
223 }
224}
225
226#[cfg(test)]
227mod shutdown_script_tests {
228 use super::ShutdownScript;
229
230 use bitcoin::opcodes;
231 use bitcoin::script::{Builder, PushBytes, ScriptBuf};
232 use bitcoin::secp256k1::Secp256k1;
233 use bitcoin::secp256k1::{PublicKey, SecretKey};
234 use bitcoin::{WitnessProgram, WitnessVersion};
235
236 use crate::prelude::*;
237 use crate::types::features::InitFeatures;
238
239 fn pubkey() -> bitcoin::key::PublicKey {
240 let secp_ctx = Secp256k1::signing_only();
241 let secret_key = SecretKey::from_slice(&[
242 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
243 0, 0, 1,
244 ])
245 .unwrap();
246 bitcoin::key::PublicKey::new(PublicKey::from_secret_key(&secp_ctx, &secret_key))
247 }
248
249 fn redeem_script() -> ScriptBuf {
250 let pubkey = pubkey();
251 Builder::new()
252 .push_opcode(opcodes::all::OP_PUSHNUM_2)
253 .push_key(&pubkey)
254 .push_key(&pubkey)
255 .push_opcode(opcodes::all::OP_PUSHNUM_2)
256 .push_opcode(opcodes::all::OP_CHECKMULTISIG)
257 .into_script()
258 }
259
260 fn any_segwit_features() -> InitFeatures {
261 let mut features = InitFeatures::empty();
262 features.set_shutdown_any_segwit_optional();
263 features
264 }
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
273 #[test]
274 fn generates_p2wpkh_from_pubkey() {
275 let pubkey = pubkey();
276 let pubkey_hash = pubkey.wpubkey_hash().unwrap();
277 let p2wpkh_script = ScriptBuf::new_p2wpkh(&pubkey_hash);
278
279 let shutdown_script = ShutdownScript::new_p2wpkh_from_pubkey(pubkey.inner);
280 assert!(shutdown_script.is_compatible(&any_segwit_features()));
281 assert!(shutdown_script.is_compatible(&InitFeatures::empty()));
282 assert_eq!(shutdown_script.into_inner(), p2wpkh_script);
283 }
284
285 #[test]
286 fn generates_p2wpkh_from_pubkey_hash() {
287 let pubkey_hash = pubkey().wpubkey_hash().unwrap();
288 let p2wpkh_script = ScriptBuf::new_p2wpkh(&pubkey_hash);
289
290 let shutdown_script = ShutdownScript::new_p2wpkh(&pubkey_hash);
291 assert!(shutdown_script.is_compatible(&any_segwit_features()));
292 assert!(shutdown_script.is_compatible(&InitFeatures::empty()));
293 assert_eq!(shutdown_script.into_inner(), p2wpkh_script);
294 assert!(ShutdownScript::try_from(p2wpkh_script).is_ok());
295 }
296
297 #[test]
298 fn generates_p2wsh_from_script_hash() {
299 let script_hash = redeem_script().wscript_hash();
300 let p2wsh_script = ScriptBuf::new_p2wsh(&script_hash);
301
302 let shutdown_script = ShutdownScript::new_p2wsh(&script_hash);
303 assert!(shutdown_script.is_compatible(&any_segwit_features()));
304 assert!(shutdown_script.is_compatible(&InitFeatures::empty()));
305 assert_eq!(shutdown_script.into_inner(), p2wsh_script);
306 assert!(ShutdownScript::try_from(p2wsh_script).is_ok());
307 }
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 assert_pushdata_script_compat(76);
336 assert_pushdata_script_compat(80);
337
338 assert_pushdata_script_compat(75);
342 assert_pushdata_script_compat(6);
343 }
344
345 #[test]
346 fn generates_segwit_from_non_v0_witness_program() {
347 let witness_program = WitnessProgram::new(WitnessVersion::V16, &[0; 40]).unwrap();
348 let script = ScriptBuf::new_witness_program(&witness_program);
349 let shutdown_script = ShutdownScript::new_witness_program(&witness_program).unwrap();
350 assert!(shutdown_script.is_compatible(&any_segwit_features()));
351 assert!(!shutdown_script.is_compatible(&InitFeatures::empty()));
352 assert_eq!(shutdown_script.into_inner(), script);
353 }
354
355 #[test]
356 fn fails_from_unsupported_script() {
357 let op_return = ScriptBuf::new_op_return(&[5; 5]);
362 assert!(ShutdownScript::try_from(op_return).is_err());
363
364 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 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());
378 }
379}