Skip to content

Commit e15ab02

Browse files
committed
Make commitment transaction signing a part of ChannelKeys.
This adds a new fn to ChannelKeys which is called when we generte a new remote commitment transaction for signing. While it may be theoretically possible to unwind state updates by disconnecting and reconnecting as well as making appropriate state machine changes, the effort required to get it correct likely outweighs the UX cost of "preflighting" the requests to hardwre wallets.
1 parent d8cd80c commit e15ab02

File tree

4 files changed

+102
-29
lines changed

4 files changed

+102
-29
lines changed

lightning/src/chain/keysinterface.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,28 @@
22
//! spendable on-chain outputs which the user owns and is responsible for using just as any other
33
//! on-chain output which is theirs.
44
5-
use bitcoin::blockdata::transaction::{OutPoint, TxOut};
5+
use bitcoin::blockdata::transaction::{Transaction, OutPoint, TxOut};
66
use bitcoin::blockdata::script::{Script, Builder};
77
use bitcoin::blockdata::opcodes;
88
use bitcoin::network::constants::Network;
99
use bitcoin::util::bip32::{ExtendedPrivKey, ExtendedPubKey, ChildNumber};
10+
use bitcoin::util::bip143;
1011

1112
use bitcoin_hashes::{Hash, HashEngine};
1213
use bitcoin_hashes::sha256::HashEngine as Sha256State;
1314
use bitcoin_hashes::sha256::Hash as Sha256;
1415
use bitcoin_hashes::hash160::Hash as Hash160;
1516

1617
use secp256k1::key::{SecretKey, PublicKey};
17-
use secp256k1::Secp256k1;
18+
use secp256k1::{Secp256k1, Signature};
1819
use secp256k1;
1920

2021
use util::byte_utils;
2122
use util::logger::Logger;
2223

24+
use ln::chan_utils;
25+
use ln::chan_utils::{TxCreationKeys, HTLCOutputInCommitment};
26+
2327
use std::sync::Arc;
2428
use std::sync::atomic::{AtomicUsize, Ordering};
2529

@@ -110,6 +114,15 @@ pub trait ChannelKeys : Clone + Send {
110114
fn htlc_base_key<'a>(&'a self) -> &'a SecretKey;
111115
/// Gets the commitment seed
112116
fn commitment_seed<'a>(&'a self) -> &'a [u8; 32];
117+
118+
/// Create a signature for a remote commitment transaction and associated HTLC transactions.
119+
///
120+
/// Note that if signing fails or is rejected, the channel will be force-closed.
121+
///
122+
/// TODO: Document the things someone using this interface should enforce before signing.
123+
/// TODO: Add more input vars to enable better checking (preferably removing commitment_tx and
124+
/// making the callee generate it via some util function we expose)!
125+
fn sign_remote_commitment<T: secp256k1::Signing>(&self, channel_value_satoshis: u64, channel_funding_script: &Script, feerate_per_kw: u64, commitment_tx: &Transaction, keys: &TxCreationKeys, htlcs: &[&HTLCOutputInCommitment], to_self_delay: u16, secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()>;
113126
}
114127

115128
#[derive(Clone)]
@@ -136,6 +149,31 @@ impl ChannelKeys for InMemoryChannelKeys {
136149
fn delayed_payment_base_key(&self) -> &SecretKey { &self.delayed_payment_base_key }
137150
fn htlc_base_key(&self) -> &SecretKey { &self.htlc_base_key }
138151
fn commitment_seed(&self) -> &[u8; 32] { &self.commitment_seed }
152+
153+
154+
fn sign_remote_commitment<T: secp256k1::Signing>(&self, channel_value_satoshis: u64, channel_funding_script: &Script, feerate_per_kw: u64, commitment_tx: &Transaction, keys: &TxCreationKeys, htlcs: &[&HTLCOutputInCommitment], to_self_delay: u16, secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()> {
155+
if commitment_tx.input.len() != 1 { return Err(()); }
156+
let commitment_sighash = hash_to_message!(&bip143::SighashComponents::new(&commitment_tx).sighash_all(&commitment_tx.input[0], &channel_funding_script, channel_value_satoshis)[..]);
157+
let commitment_sig = secp_ctx.sign(&commitment_sighash, &self.funding_key);
158+
159+
let commitment_txid = commitment_tx.txid();
160+
161+
let mut htlc_sigs = Vec::with_capacity(htlcs.len());
162+
for ref htlc in htlcs {
163+
if let Some(_) = htlc.transaction_output_index {
164+
let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, feerate_per_kw, to_self_delay, htlc, &keys.a_delayed_payment_key, &keys.revocation_key);
165+
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, &keys);
166+
let htlc_sighash = hash_to_message!(&bip143::SighashComponents::new(&htlc_tx).sighash_all(&htlc_tx.input[0], &htlc_redeemscript, htlc.amount_msat / 1000)[..]);
167+
let our_htlc_key = match chan_utils::derive_private_key(&secp_ctx, &keys.per_commitment_point, &self.htlc_base_key) {
168+
Ok(s) => s,
169+
Err(_) => return Err(()),
170+
};
171+
htlc_sigs.push(secp_ctx.sign(&htlc_sighash, &our_htlc_key));
172+
}
173+
}
174+
175+
Ok((commitment_sig, htlc_sigs))
176+
}
139177
}
140178

141179
impl_writeable!(InMemoryChannelKeys, 0, {

lightning/src/ln/chan_utils.rs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
//! Various utilities for building scripts and deriving keys related to channels. These are
2+
//! largely of interest for those implementing chain::keysinterface::ChannelKeys message signing
3+
//! by hand.
4+
15
use bitcoin::blockdata::script::{Script,Builder};
26
use bitcoin::blockdata::opcodes;
37
use bitcoin::blockdata::transaction::{TxIn,TxOut,OutPoint,Transaction};
@@ -14,13 +18,13 @@ use secp256k1::key::{PublicKey,SecretKey};
1418
use secp256k1::Secp256k1;
1519
use secp256k1;
1620

17-
pub const HTLC_SUCCESS_TX_WEIGHT: u64 = 703;
18-
pub const HTLC_TIMEOUT_TX_WEIGHT: u64 = 663;
21+
pub(super) const HTLC_SUCCESS_TX_WEIGHT: u64 = 703;
22+
pub(super) const HTLC_TIMEOUT_TX_WEIGHT: u64 = 663;
1923

2024
// Various functions for key derivation and transaction creation for use within channels. Primarily
2125
// used in Channel and ChannelMonitor.
2226

23-
pub fn build_commitment_secret(commitment_seed: &[u8; 32], idx: u64) -> [u8; 32] {
27+
pub(super) fn build_commitment_secret(commitment_seed: &[u8; 32], idx: u64) -> [u8; 32] {
2428
let mut res: [u8; 32] = commitment_seed.clone();
2529
for i in 0..48 {
2630
let bitpos = 47 - i;
@@ -32,6 +36,8 @@ pub fn build_commitment_secret(commitment_seed: &[u8; 32], idx: u64) -> [u8; 32]
3236
res
3337
}
3438

39+
/// Derives a per-commitment-transaction private key (eg an htlc key or payment key) from the base
40+
/// private key for that type of key and the per_commitment_point (available in TxCreationKeys)
3541
pub fn derive_private_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, base_secret: &SecretKey) -> Result<SecretKey, secp256k1::Error> {
3642
let mut sha = Sha256::engine();
3743
sha.input(&per_commitment_point.serialize());
@@ -43,7 +49,7 @@ pub fn derive_private_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_co
4349
Ok(key)
4450
}
4551

46-
pub fn derive_public_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, base_point: &PublicKey) -> Result<PublicKey, secp256k1::Error> {
52+
pub(super) fn derive_public_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, base_point: &PublicKey) -> Result<PublicKey, secp256k1::Error> {
4753
let mut sha = Sha256::engine();
4854
sha.input(&per_commitment_point.serialize());
4955
sha.input(&base_point.serialize());
@@ -54,7 +60,7 @@ pub fn derive_public_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_com
5460
}
5561

5662
/// Derives a revocation key from its constituent parts
57-
pub fn derive_private_revocation_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_commitment_secret: &SecretKey, revocation_base_secret: &SecretKey) -> Result<SecretKey, secp256k1::Error> {
63+
pub(super) fn derive_private_revocation_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1<T>, per_commitment_secret: &SecretKey, revocation_base_secret: &SecretKey) -> Result<SecretKey, secp256k1::Error> {
5864
let revocation_base_point = PublicKey::from_secret_key(&secp_ctx, &revocation_base_secret);
5965
let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret);
6066

@@ -81,7 +87,7 @@ pub fn derive_private_revocation_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1
8187
Ok(part_a)
8288
}
8389

84-
pub fn derive_public_revocation_key<T: secp256k1::Verification>(secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, revocation_base_point: &PublicKey) -> Result<PublicKey, secp256k1::Error> {
90+
pub(super) fn derive_public_revocation_key<T: secp256k1::Verification>(secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, revocation_base_point: &PublicKey) -> Result<PublicKey, secp256k1::Error> {
8591
let rev_append_commit_hash_key = {
8692
let mut sha = Sha256::engine();
8793
sha.input(&revocation_base_point.serialize());
@@ -104,17 +110,26 @@ pub fn derive_public_revocation_key<T: secp256k1::Verification>(secp_ctx: &Secp2
104110
part_a.combine(&part_b)
105111
}
106112

113+
/// The set of public keys which are used in the creation of one commitment transaction.
114+
/// These are derived from the channel base keys and per-commitment data.
107115
pub struct TxCreationKeys {
116+
/// The per-commitment public key which was used to derive the other keys.
108117
pub per_commitment_point: PublicKey,
118+
/// The revocation key which is used to allow the owner of the commitment transaction to
119+
/// provide their counterparty the ability to punish them if they broadcast an old state.
109120
pub revocation_key: PublicKey,
121+
/// A's HTLC Key
110122
pub a_htlc_key: PublicKey,
123+
/// B's HTLC Key
111124
pub b_htlc_key: PublicKey,
125+
/// A's Payment Key (which isn't allowed to be spent from for some delay)
112126
pub a_delayed_payment_key: PublicKey,
127+
/// B's Payment Key
113128
pub b_payment_key: PublicKey,
114129
}
115130

116131
impl TxCreationKeys {
117-
pub fn new<T: secp256k1::Signing + secp256k1::Verification>(secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, a_delayed_payment_base: &PublicKey, a_htlc_base: &PublicKey, b_revocation_base: &PublicKey, b_payment_base: &PublicKey, b_htlc_base: &PublicKey) -> Result<TxCreationKeys, secp256k1::Error> {
132+
pub(super) fn new<T: secp256k1::Signing + secp256k1::Verification>(secp_ctx: &Secp256k1<T>, per_commitment_point: &PublicKey, a_delayed_payment_base: &PublicKey, a_htlc_base: &PublicKey, b_revocation_base: &PublicKey, b_payment_base: &PublicKey, b_htlc_base: &PublicKey) -> Result<TxCreationKeys, secp256k1::Error> {
118133
Ok(TxCreationKeys {
119134
per_commitment_point: per_commitment_point.clone(),
120135
revocation_key: derive_public_revocation_key(&secp_ctx, &per_commitment_point, &b_revocation_base)?,
@@ -128,7 +143,7 @@ impl TxCreationKeys {
128143

129144
/// Gets the "to_local" output redeemscript, ie the script which is time-locked or spendable by
130145
/// the revocation key
131-
pub fn get_revokeable_redeemscript(revocation_key: &PublicKey, to_self_delay: u16, delayed_payment_key: &PublicKey) -> Script {
146+
pub(super) fn get_revokeable_redeemscript(revocation_key: &PublicKey, to_self_delay: u16, delayed_payment_key: &PublicKey) -> Script {
132147
Builder::new().push_opcode(opcodes::all::OP_IF)
133148
.push_slice(&revocation_key.serialize())
134149
.push_opcode(opcodes::all::OP_ELSE)
@@ -142,16 +157,28 @@ pub fn get_revokeable_redeemscript(revocation_key: &PublicKey, to_self_delay: u1
142157
}
143158

144159
#[derive(Clone, PartialEq)]
160+
/// Information about an HTLC as it appears in a commitment transaction
145161
pub struct HTLCOutputInCommitment {
162+
/// Whether the HTLC was "offered" (ie outbound in relation to this commitment transaction).
163+
/// Note that this is not the same as whether it is ountbound *from us*. To determine that you
164+
/// need to compare this value to whether the commitment transaction in question is that of
165+
/// the remote party or our own.
146166
pub offered: bool,
167+
/// The value, in msat, of the HTLC. The value as it appears in the commitment transaction is
168+
/// this divided by 1000.
147169
pub amount_msat: u64,
170+
/// The CLTV lock-time at which this HTLC expires.
148171
pub cltv_expiry: u32,
172+
/// The hash of the preimage which unlocks this HTLC.
149173
pub payment_hash: PaymentHash,
174+
/// The position within the commitment transactions' outputs. This may be None if the value is
175+
/// below the dust limit (in which case no output appears in the commitment transaction and the
176+
/// value is spent to additional transaction fees).
150177
pub transaction_output_index: Option<u32>,
151178
}
152179

153180
#[inline]
154-
pub fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommitment, a_htlc_key: &PublicKey, b_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script {
181+
pub(super) fn get_htlc_redeemscript_with_explicit_keys(htlc: &HTLCOutputInCommitment, a_htlc_key: &PublicKey, b_htlc_key: &PublicKey, revocation_key: &PublicKey) -> Script {
155182
let payment_hash160 = Ripemd160::hash(&htlc.payment_hash.0[..]).into_inner();
156183
if htlc.offered {
157184
Builder::new().push_opcode(opcodes::all::OP_DUP)

lightning/src/ln/channel.rs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3519,8 +3519,6 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
35193519
/// Only fails in case of bad keys. Used for channel_reestablish commitment_signed generation
35203520
/// when we shouldn't change HTLC/channel state.
35213521
fn send_commitment_no_state_update(&self) -> Result<(msgs::CommitmentSigned, (Transaction, Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)>)), ChannelError> {
3522-
let funding_script = self.get_funding_redeemscript();
3523-
35243522
let mut feerate_per_kw = self.feerate_per_kw;
35253523
if let Some(feerate) = self.pending_update_fee {
35263524
if self.channel_outbound {
@@ -3530,27 +3528,37 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
35303528

35313529
let remote_keys = self.build_remote_transaction_keys()?;
35323530
let remote_commitment_tx = self.build_commitment_transaction(self.cur_remote_commitment_transaction_number, &remote_keys, false, true, feerate_per_kw);
3533-
let remote_commitment_txid = remote_commitment_tx.0.txid();
3534-
let remote_sighash = hash_to_message!(&bip143::SighashComponents::new(&remote_commitment_tx.0).sighash_all(&remote_commitment_tx.0.input[0], &funding_script, self.channel_value_satoshis)[..]);
3535-
let our_sig = self.secp_ctx.sign(&remote_sighash, self.local_keys.funding_key());
3536-
log_trace!(self, "Signing remote commitment tx {} with redeemscript {} with pubkey {} -> {}", encode::serialize_hex(&remote_commitment_tx.0), encode::serialize_hex(&funding_script), log_bytes!(PublicKey::from_secret_key(&self.secp_ctx, self.local_keys.funding_key()).serialize()), log_bytes!(our_sig.serialize_compact()[..]));
3531+
let (signature, htlc_signatures);
35373532

3538-
let mut htlc_sigs = Vec::with_capacity(remote_commitment_tx.1);
3539-
for &(ref htlc, _) in remote_commitment_tx.2.iter() {
3540-
if let Some(_) = htlc.transaction_output_index {
3541-
let htlc_tx = self.build_htlc_transaction(&remote_commitment_txid, htlc, false, &remote_keys, feerate_per_kw);
3542-
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, &remote_keys);
3543-
let htlc_sighash = hash_to_message!(&bip143::SighashComponents::new(&htlc_tx).sighash_all(&htlc_tx.input[0], &htlc_redeemscript, htlc.amount_msat / 1000)[..]);
3544-
let our_htlc_key = secp_check!(chan_utils::derive_private_key(&self.secp_ctx, &remote_keys.per_commitment_point, self.local_keys.htlc_base_key()), "Derived invalid key, peer is maliciously selecting parameters");
3545-
htlc_sigs.push(self.secp_ctx.sign(&htlc_sighash, &our_htlc_key));
3546-
log_trace!(self, "Signing remote HTLC tx {} with redeemscript {} with pubkey {} -> {}", encode::serialize_hex(&htlc_tx), encode::serialize_hex(&htlc_redeemscript), log_bytes!(PublicKey::from_secret_key(&self.secp_ctx, &our_htlc_key).serialize()), log_bytes!(htlc_sigs.last().unwrap().serialize_compact()[..]));
3533+
{
3534+
let mut htlcs = Vec::with_capacity(remote_commitment_tx.2.len());
3535+
for &(ref htlc, _) in remote_commitment_tx.2.iter() {
3536+
htlcs.push(htlc);
3537+
}
3538+
3539+
let res = self.local_keys.sign_remote_commitment(self.channel_value_satoshis, &self.get_funding_redeemscript(), feerate_per_kw, &remote_commitment_tx.0, &remote_keys, &htlcs, self.our_to_self_delay, &self.secp_ctx)
3540+
.map_err(|_| ChannelError::Close("Failed to get signatures for new commitment_signed"))?;
3541+
signature = res.0;
3542+
htlc_signatures = res.1;
3543+
3544+
log_trace!(self, "Signed remote commitment tx {} with redeemscript {} -> {}",
3545+
encode::serialize_hex(&remote_commitment_tx.0),
3546+
encode::serialize_hex(&self.get_funding_redeemscript()),
3547+
log_bytes!(signature.serialize_compact()[..]));
3548+
3549+
for (ref htlc_sig, ref htlc) in htlc_signatures.iter().zip(htlcs) {
3550+
log_trace!(self, "Signed remote HTLC tx {} with redeemscript {} with pubkey {} -> {}",
3551+
encode::serialize_hex(&chan_utils::build_htlc_transaction(&remote_commitment_tx.0.txid(), feerate_per_kw, self.our_to_self_delay, htlc, &remote_keys.a_delayed_payment_key, &remote_keys.revocation_key)),
3552+
encode::serialize_hex(&chan_utils::get_htlc_redeemscript(&htlc, &remote_keys)),
3553+
log_bytes!(remote_keys.a_htlc_key.serialize()),
3554+
log_bytes!(htlc_sig.serialize_compact()[..]));
35473555
}
35483556
}
35493557

35503558
Ok((msgs::CommitmentSigned {
35513559
channel_id: self.channel_id,
3552-
signature: our_sig,
3553-
htlc_signatures: htlc_sigs,
3560+
signature,
3561+
htlc_signatures,
35543562
}, (remote_commitment_tx.0, remote_commitment_tx.2)))
35553563
}
35563564

lightning/src/ln/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ pub mod channelmonitor;
1414
pub mod msgs;
1515
pub mod router;
1616
pub mod peer_handler;
17+
pub mod chan_utils;
1718

1819
#[cfg(feature = "fuzztarget")]
1920
pub mod peer_channel_encryptor;
2021
#[cfg(not(feature = "fuzztarget"))]
2122
pub(crate) mod peer_channel_encryptor;
2223

2324
mod channel;
24-
mod chan_utils;
2525
mod onion_utils;
2626

2727
#[cfg(test)]

0 commit comments

Comments
 (0)