lightning/ln/
script.rs

1//! Abstractions for scripts used in the Lightning Network.
2
3use 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/// A script pubkey for shutting down a channel as defined by [BOLT #2].
22///
23/// [BOLT #2]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md
24#[derive(Clone, PartialEq, Eq)]
25pub struct ShutdownScript(ShutdownScriptImpl);
26
27/// An error occurring when converting from [`ScriptBuf`] to [`ShutdownScript`].
28#[derive(Clone, Debug)]
29pub struct InvalidShutdownScript {
30	/// The script that did not meet the requirements from [BOLT #2].
31	///
32	/// [BOLT #2]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md
33	pub script: ScriptBuf,
34}
35
36#[derive(Clone, PartialEq, Eq)]
37enum ShutdownScriptImpl {
38	/// [`PublicKey`] used to form a P2WPKH script pubkey. Used to support backward-compatible
39	/// serialization.
40	Legacy(PublicKey),
41
42	/// [`ScriptBuf`] adhering to a script pubkey format specified in BOLT #2.
43	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	/// Generates a P2WPKH script pubkey from the given [`PublicKey`].
65	pub(crate) fn new_p2wpkh_from_pubkey(pubkey: PublicKey) -> Self {
66		Self(ShutdownScriptImpl::Legacy(pubkey))
67	}
68
69	/// Generates a P2WPKH script pubkey from the given [`WPubkeyHash`].
70	pub fn new_p2wpkh(pubkey_hash: &WPubkeyHash) -> Self {
71		Self(ShutdownScriptImpl::Bolt2(ScriptBuf::new_p2wpkh(pubkey_hash)))
72	}
73
74	/// Generates a P2WSH script pubkey from the given [`WScriptHash`].
75	pub fn new_p2wsh(script_hash: &WScriptHash) -> Self {
76		Self(ShutdownScriptImpl::Bolt2(ScriptBuf::new_p2wsh(script_hash)))
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
93	/// Generates a witness script pubkey from the given segwit version and program.
94	///
95	/// Note for version-zero witness scripts you must use [`ShutdownScript::new_p2wpkh`] or
96	/// [`ShutdownScript::new_p2wsh`] instead.
97	///
98	/// # Errors
99	///
100	/// This function may return an error if `program` is invalid for the segwit `version`.
101	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	/// Converts the shutdown script into the underlying [`ScriptBuf`].
108	pub fn into_inner(self) -> ScriptBuf {
109		self.into()
110	}
111
112	/// Returns the [`PublicKey`] used for a P2WPKH shutdown script if constructed directly from it.
113	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	/// Returns whether the shutdown script is compatible with the features as defined by BOLT #2.
121	///
122	/// Specifically, checks for compliance with feature `option_shutdown_anysegwit` and/or
123	/// `option_simple_close`.
124	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
132/// Check if a given script is compliant with BOLT 2's shutdown script requirements for the given
133/// counterparty features.
134pub(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
138	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		// 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		}
176	} else {
177		false
178	}
179}
180
181// Note that this is only for our own shutdown scripts. Counterparties are still allowed to send us
182// non-witness shutdown scripts which this rejects.
183impl 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
192// Note that this is only for our own shutdown scripts. Counterparties are still allowed to send us
193// non-witness shutdown scripts which this rejects.
194impl 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		// 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
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		// 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]);
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());
378	}
379}