//! Abstractions for scripts used in the Lightning Network. use bitcoin::blockdata::opcodes::all::OP_PUSHBYTES_0 as SEGWIT_V0; use bitcoin::blockdata::script::{Builder, Script}; use bitcoin::hashes::Hash; use bitcoin::hash_types::{WPubkeyHash, WScriptHash}; use bitcoin::secp256k1::key::PublicKey; use ln::features::InitFeatures; use ln::msgs::DecodeError; use util::ser::{Readable, Writeable, Writer}; use core::convert::TryFrom; use core::num::NonZeroU8; use io; /// A script pubkey for shutting down a channel as defined by [BOLT #2]. /// /// [BOLT #2]: https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md #[derive(Clone, PartialEq)] pub struct ShutdownScript(ShutdownScriptImpl); /// An error occurring when converting from [`Script`] to [`ShutdownScript`]. #[derive(Clone, Debug)] pub struct InvalidShutdownScript { /// The script that did not meet the requirements from [BOLT #2]. /// /// [BOLT #2]: https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md pub script: Script } #[derive(Clone, PartialEq)] enum ShutdownScriptImpl { /// [`PublicKey`] used to form a P2WPKH script pubkey. Used to support backward-compatible /// serialization. Legacy(PublicKey), /// [`Script`] adhering to a script pubkey format specified in BOLT #2. Bolt2(Script), } impl Writeable for ShutdownScript { fn write(&self, w: &mut W) -> Result<(), io::Error> { self.0.write(w) } fn serialized_length(&self) -> usize { self.0.serialized_length() } } impl Readable for ShutdownScript { fn read(r: &mut R) -> Result { Ok(ShutdownScript(ShutdownScriptImpl::read(r)?)) } } impl_writeable_tlv_based_enum!(ShutdownScriptImpl, ; (0, Legacy), (1, Bolt2), ); impl ShutdownScript { /// Generates a P2WPKH script pubkey from the given [`PublicKey`]. pub(crate) fn new_p2wpkh_from_pubkey(pubkey: PublicKey) -> Self { Self(ShutdownScriptImpl::Legacy(pubkey)) } /// Generates a P2WPKH script pubkey from the given [`WPubkeyHash`]. pub fn new_p2wpkh(pubkey_hash: &WPubkeyHash) -> Self { Self(ShutdownScriptImpl::Bolt2(Script::new_v0_wpkh(pubkey_hash))) } /// Generates a P2WSH script pubkey from the given [`WScriptHash`]. pub fn new_p2wsh(script_hash: &WScriptHash) -> Self { Self(ShutdownScriptImpl::Bolt2(Script::new_v0_wsh(script_hash))) } /// Generates a witness script pubkey from the given segwit version and program. /// /// Note for version-zero witness scripts you must use [`ShutdownScript::new_p2wpkh`] or /// [`ShutdownScript::new_p2wsh`] instead. /// /// # Errors /// /// This function may return an error if `program` is invalid for the segwit `version`. pub fn new_witness_program(version: NonZeroU8, program: &[u8]) -> Result { let script = Builder::new() .push_int(version.get().into()) .push_slice(&program) .into_script(); Self::try_from(script) } /// Converts the shutdown script into the underlying [`Script`]. pub fn into_inner(self) -> Script { self.into() } /// Returns the [`PublicKey`] used for a P2WPKH shutdown script if constructed directly from it. pub fn as_legacy_pubkey(&self) -> Option<&PublicKey> { match &self.0 { ShutdownScriptImpl::Legacy(pubkey) => Some(pubkey), ShutdownScriptImpl::Bolt2(_) => None, } } /// Returns whether the shutdown script is compatible with the features as defined by BOLT #2. /// /// Specifically, checks for compliance with feature `option_shutdown_anysegwit`. pub fn is_compatible(&self, features: &InitFeatures) -> bool { match &self.0 { ShutdownScriptImpl::Legacy(_) => true, ShutdownScriptImpl::Bolt2(script) => is_bolt2_compliant(script, features), } } } /// Check if a given script is compliant with BOLT 2's shutdown script requirements for the given /// counterparty features. pub(crate) fn is_bolt2_compliant(script: &Script, features: &InitFeatures) -> bool { if script.is_p2pkh() || script.is_p2sh() || script.is_v0_p2wpkh() || script.is_v0_p2wsh() { true } else if features.supports_shutdown_anysegwit() { script.is_witness_program() && script.as_bytes()[0] != SEGWIT_V0.into_u8() } else { false } } // Note that this is only for our own shutdown scripts. Counterparties are still allowed to send us // non-witness shutdown scripts which this rejects. impl TryFrom