Skip to content

Commit ce27553

Browse files
committed
Add ShutdownScript for BOLT 2 acceptable scripts
BOLT 2 enumerates the script formats that may be used for a shutdown script. KeysInterface::get_shutdown_pubkey returns a PublicKey used to form one of the acceptable formats (P2WPKH). Add a ShutdownScript abstraction to encapsulate all accept formats and be backwards compatible with P2WPKH scripts serialized as the corresponding PublicKey.
1 parent 9caabc3 commit ce27553

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed

lightning/src/ln/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub mod msgs;
2727
pub mod peer_handler;
2828
pub mod chan_utils;
2929
pub mod features;
30+
pub mod script;
3031

3132
#[cfg(feature = "fuzztarget")]
3233
pub mod peer_channel_encryptor;

lightning/src/ln/script.rs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
//! Abstractions for scripts used in the Lightning Network.
2+
3+
use bitcoin::bech32::u5;
4+
use bitcoin::blockdata::opcodes::all::OP_PUSHBYTES_0 as SEGWIT_V0;
5+
use bitcoin::blockdata::script::Script;
6+
use bitcoin::hashes::Hash;
7+
use bitcoin::hash_types::{PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash};
8+
use bitcoin::secp256k1::key::PublicKey;
9+
use std::convert::From;
10+
use std::convert::TryFrom;
11+
12+
/// A script pubkey for shutting down a channel as defined by [BOLT #2].
13+
///
14+
/// [BOLT #2]: https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md
15+
pub struct ShutdownScript(ShutdownScriptImpl);
16+
17+
/// An error occurring when converting from [`Script`] to [`ShutdownScript`].
18+
#[derive(Debug)]
19+
pub struct InvalidShutdownScript(Script);
20+
21+
enum ShutdownScriptImpl {
22+
/// [`PublicKey`] used to form a P2WPKH script pubkey. Used to support backward-compatible
23+
/// serialization.
24+
Legacy(PublicKey),
25+
26+
/// [`Script`] adhering to a script pubkey format specified in BOLT #2.
27+
Bolt2(Script),
28+
}
29+
30+
impl ShutdownScript {
31+
/// Generates a P2PKH script pubkey from the given [`PubkeyHash`].
32+
pub fn new_p2pkh(pubkey_hash: &PubkeyHash) -> Self {
33+
Self(ShutdownScriptImpl::Bolt2(Script::new_p2pkh(pubkey_hash)))
34+
}
35+
36+
/// Generates a P2SH script pubkey from the given [`ScriptHash`].
37+
pub fn new_p2sh(script_hash: &ScriptHash) -> Self {
38+
Self(ShutdownScriptImpl::Bolt2(Script::new_p2sh(script_hash)))
39+
}
40+
41+
/// Generates a P2WPKH script pubkey from the given [`WPubkeyHash`].
42+
pub fn new_p2wpkh(pubkey_hash: &WPubkeyHash) -> Self {
43+
Self(ShutdownScriptImpl::Bolt2(Script::new_v0_wpkh(pubkey_hash)))
44+
}
45+
46+
/// Generates a P2WSH script pubkey from the given [`WScriptHash`].
47+
pub fn new_p2wsh(script_hash: &WScriptHash) -> Self {
48+
Self(ShutdownScriptImpl::Bolt2(Script::new_v0_wsh(script_hash)))
49+
}
50+
51+
/// Generates a P2WSH script pubkey from the given segwit version and program.
52+
///
53+
/// # Panics
54+
///
55+
/// This function may panic if given a segwit program with an invalid length.
56+
pub fn new_witness_program(version: u5, program: &[u8]) -> Self {
57+
let script = Script::new_witness_program(version, program);
58+
Self::try_from(script).expect("Invalid segwit program")
59+
}
60+
61+
/// Converts the shutdown script into the underlying [`Script`].
62+
pub fn into_inner(self) -> Script {
63+
self.into()
64+
}
65+
}
66+
67+
impl From<PublicKey> for ShutdownScript {
68+
fn from(pubkey: PublicKey) -> Self {
69+
Self(ShutdownScriptImpl::Legacy(pubkey))
70+
}
71+
}
72+
73+
impl TryFrom<Script> for ShutdownScript {
74+
type Error = InvalidShutdownScript;
75+
76+
fn try_from(script: Script) -> Result<Self, Self::Error> {
77+
if script.is_p2pkh() || script.is_p2sh() || script.is_v0_p2wpkh() || script.is_v0_p2wsh() {
78+
Ok(Self(ShutdownScriptImpl::Bolt2(script)))
79+
} else if script.is_witness_program() && script.as_bytes()[0] != SEGWIT_V0.into_u8() {
80+
Ok(Self(ShutdownScriptImpl::Bolt2(script))) // option_shutdown_anysegwit
81+
} else {
82+
Err(InvalidShutdownScript(script))
83+
}
84+
}
85+
}
86+
87+
impl Into<Script> for ShutdownScript {
88+
fn into(self) -> Script {
89+
match self.0 {
90+
ShutdownScriptImpl::Legacy(pubkey) =>
91+
Script::new_v0_wpkh(&WPubkeyHash::hash(&pubkey.serialize())),
92+
ShutdownScriptImpl::Bolt2(script_pubkey) => script_pubkey,
93+
}
94+
}
95+
}
96+
97+
#[cfg(test)]
98+
mod shutdown_script_tests {
99+
use super::ShutdownScript;
100+
use bitcoin::bech32::u5;
101+
use bitcoin::blockdata::opcodes;
102+
use bitcoin::blockdata::script::{Builder, Script};
103+
use bitcoin::secp256k1::Secp256k1;
104+
use bitcoin::secp256k1::key::{PublicKey, SecretKey};
105+
use std::convert::TryFrom;
106+
107+
fn pubkey() -> bitcoin::util::ecdsa::PublicKey {
108+
let secp_ctx = Secp256k1::signing_only();
109+
let secret_key = SecretKey::from_slice(&[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, 0, 0, 1]).unwrap();
110+
bitcoin::util::ecdsa::PublicKey::new(PublicKey::from_secret_key(&secp_ctx, &secret_key))
111+
}
112+
113+
fn redeem_script() -> Script {
114+
let pubkey = pubkey();
115+
Builder::new()
116+
.push_opcode(opcodes::all::OP_PUSHNUM_2)
117+
.push_key(&pubkey)
118+
.push_key(&pubkey)
119+
.push_opcode(opcodes::all::OP_PUSHNUM_2)
120+
.push_opcode(opcodes::all::OP_CHECKMULTISIG)
121+
.into_script()
122+
}
123+
124+
#[test]
125+
fn generates_p2wpkh_from_pubkey() {
126+
let pubkey = pubkey();
127+
let pubkey_hash = pubkey.wpubkey_hash().unwrap();
128+
let p2wpkh_script = Script::new_v0_wpkh(&pubkey_hash);
129+
130+
let shutdown_script = ShutdownScript::from(pubkey.key);
131+
assert_eq!(shutdown_script.into_inner(), p2wpkh_script);
132+
}
133+
134+
#[test]
135+
fn generates_p2pkh_from_pubkey_hash() {
136+
let pubkey_hash = pubkey().pubkey_hash();
137+
let p2pkh_script = Script::new_p2pkh(&pubkey_hash);
138+
139+
let shutdown_script = ShutdownScript::new_p2pkh(&pubkey_hash);
140+
assert_eq!(shutdown_script.into_inner(), p2pkh_script);
141+
assert!(ShutdownScript::try_from(p2pkh_script).is_ok());
142+
}
143+
144+
#[test]
145+
fn generates_p2sh_from_script_hash() {
146+
let script_hash = redeem_script().script_hash();
147+
let p2sh_script = Script::new_p2sh(&script_hash);
148+
149+
let shutdown_script = ShutdownScript::new_p2sh(&script_hash);
150+
assert_eq!(shutdown_script.into_inner(), p2sh_script);
151+
assert!(ShutdownScript::try_from(p2sh_script).is_ok());
152+
}
153+
154+
#[test]
155+
fn generates_p2wpkh_from_pubkey_hash() {
156+
let pubkey_hash = pubkey().wpubkey_hash().unwrap();
157+
let p2wpkh_script = Script::new_v0_wpkh(&pubkey_hash);
158+
159+
let shutdown_script = ShutdownScript::new_p2wpkh(&pubkey_hash);
160+
assert_eq!(shutdown_script.into_inner(), p2wpkh_script);
161+
assert!(ShutdownScript::try_from(p2wpkh_script).is_ok());
162+
}
163+
164+
#[test]
165+
fn generates_p2wsh_from_script_hash() {
166+
let script_hash = redeem_script().wscript_hash();
167+
let p2wsh_script = Script::new_v0_wsh(&script_hash);
168+
169+
let shutdown_script = ShutdownScript::new_p2wsh(&script_hash);
170+
assert_eq!(shutdown_script.into_inner(), p2wsh_script);
171+
assert!(ShutdownScript::try_from(p2wsh_script).is_ok());
172+
}
173+
174+
#[test]
175+
fn fails_from_unsupported_script() {
176+
let op_return = Script::new_op_return(&[0; 42]);
177+
assert!(ShutdownScript::try_from(op_return).is_err());
178+
}
179+
180+
#[test]
181+
fn fails_from_invalid_segwit_v0_program() {
182+
let witness_program = Script::new_witness_program(u5::try_from_u8(0).unwrap(), &[0; 2]);
183+
assert!(ShutdownScript::try_from(witness_program).is_err());
184+
}
185+
}

0 commit comments

Comments
 (0)