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::{PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash};
+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(Debug)]
+#[derive(Clone, Debug)]
pub struct InvalidShutdownScript {
/// The script that did not meet the requirements from [BOLT #2].
///
pub script: Script
}
+#[derive(Clone, PartialEq)]
enum ShutdownScriptImpl {
/// [`PublicKey`] used to form a P2WPKH script pubkey. Used to support backward-compatible
/// serialization.
Bolt2(Script),
}
-impl ShutdownScript {
- /// Generates a P2WPKH script pubkey from the given [`PublicKey`].
- pub fn new_p2wpkh_from_pubkey(pubkey: PublicKey) -> Self {
- Self(ShutdownScriptImpl::Legacy(pubkey))
+impl Writeable for ShutdownScript {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ self.0.write(w)
}
- /// Generates a P2PKH script pubkey from the given [`PubkeyHash`].
- pub fn new_p2pkh(pubkey_hash: &PubkeyHash) -> Self {
- Self(ShutdownScriptImpl::Bolt2(Script::new_p2pkh(pubkey_hash)))
+ fn serialized_length(&self) -> usize {
+ self.0.serialized_length()
}
+}
- /// Generates a P2SH script pubkey from the given [`ScriptHash`].
- pub fn new_p2sh(script_hash: &ScriptHash) -> Self {
- Self(ShutdownScriptImpl::Bolt2(Script::new_p2sh(script_hash)))
+impl Readable for ShutdownScript {
+ fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
+ 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`].
Self(ShutdownScriptImpl::Bolt2(Script::new_v0_wsh(script_hash)))
}
- /// Generates a P2WSH script pubkey from the given segwit version and program.
+ /// 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
///
}
}
-fn is_bolt2_compliant(script: &Script, features: &InitFeatures) -> bool {
+/// 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() {
}
}
+// 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<Script> for ShutdownScript {
type Error = InvalidShutdownScript;
}
}
+// 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<(Script, &InitFeatures)> for ShutdownScript {
type Error = InvalidShutdownScript;
fn try_from((script, features): (Script, &InitFeatures)) -> Result<Self, Self::Error> {
- if is_bolt2_compliant(&script, features) {
+ if is_bolt2_compliant(&script, features) && script.is_witness_program() {
Ok(Self(ShutdownScriptImpl::Bolt2(script)))
} else {
Err(InvalidShutdownScript { script })
}
}
+impl core::fmt::Display for ShutdownScript{
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+ match &self.0 {
+ ShutdownScriptImpl::Legacy(_) => self.clone().into_inner().fmt(f),
+ ShutdownScriptImpl::Bolt2(script) => script.fmt(f),
+ }
+ }
+}
+
#[cfg(test)]
mod shutdown_script_tests {
use super::ShutdownScript;
assert_eq!(shutdown_script.into_inner(), p2wpkh_script);
}
- #[test]
- fn generates_p2pkh_from_pubkey_hash() {
- let pubkey_hash = pubkey().pubkey_hash();
- let p2pkh_script = Script::new_p2pkh(&pubkey_hash);
-
- let shutdown_script = ShutdownScript::new_p2pkh(&pubkey_hash);
- assert!(shutdown_script.is_compatible(&InitFeatures::known()));
- assert!(shutdown_script.is_compatible(&InitFeatures::known().clear_shutdown_anysegwit()));
- assert_eq!(shutdown_script.into_inner(), p2pkh_script);
- assert!(ShutdownScript::try_from(p2pkh_script).is_ok());
- }
-
- #[test]
- fn generates_p2sh_from_script_hash() {
- let script_hash = redeem_script().script_hash();
- let p2sh_script = Script::new_p2sh(&script_hash);
-
- let shutdown_script = ShutdownScript::new_p2sh(&script_hash);
- assert!(shutdown_script.is_compatible(&InitFeatures::known()));
- assert!(shutdown_script.is_compatible(&InitFeatures::known().clear_shutdown_anysegwit()));
- assert_eq!(shutdown_script.into_inner(), p2sh_script);
- assert!(ShutdownScript::try_from(p2sh_script).is_ok());
- }
-
#[test]
fn generates_p2wpkh_from_pubkey_hash() {
let pubkey_hash = pubkey().wpubkey_hash().unwrap();