Skip to content

Commit b554040

Browse files
committed
Batch-sign local HTLC txn with a well-doc'd API, returning sigs
1107ab0 introduced an API to have a ChannelKeys implementer sign HTLC transactions by calling into the LocalCommitmentTransaction object, which would then store the tx. This API was incredibly awkward, both because it required an external signer trust our own internal interfaces, but also because it didn't allow for any inspection of what was about to be signed. Further, it signed the HTLC transactions one-by-one in a somewhat inefficient way, and there isn't a clear way to resolve this (as the which-HTLC parameter has to refer to something in between the HTLC's arbitrary index, and its index in the commitment tx, which has "holes" for the non-HTLC outputs and skips some HTLCs). We replace it with a new function in ChannelKeys which allows us to sign all HTLCs in a given commitment transaction (which allows for a bit more effeciency on the signers' part, as well as sidesteps the which-HTLC issue). This may also simplify the signer implementation as we will always want to sign all HTLCs spending a given commitment transaction at once anyway. We also de-mut the LocalCommitmentTransaction passed to the ChanKeys, instead opting to make LocalCommitmentTransaction const and avoid storing any new HTLC-related data in it.
1 parent 06d9202 commit b554040

File tree

7 files changed

+273
-96
lines changed

7 files changed

+273
-96
lines changed

lightning/src/chain/keysinterface.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ use util::ser::{Writeable, Writer, Readable};
2525

2626
use ln::chan_utils;
2727
use ln::chan_utils::{TxCreationKeys, HTLCOutputInCommitment, make_funding_redeemscript, ChannelPublicKeys, LocalCommitmentTransaction};
28-
use ln::channelmanager::PaymentPreimage;
2928
use ln::msgs;
3029

3130
use std::sync::Arc;
@@ -231,10 +230,21 @@ pub trait ChannelKeys : Send+Clone {
231230
#[cfg(test)]
232231
fn unsafe_sign_local_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, local_commitment_tx: &LocalCommitmentTransaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()>;
233232

234-
/// Signs a transaction created by build_htlc_transaction. If the transaction is an
235-
/// HTLC-Success transaction, preimage must be set!
236-
/// TODO: should be merged with sign_local_commitment as a slice of HTLC transactions to sign
237-
fn sign_htlc_transaction<T: secp256k1::Signing>(&self, local_commitment_tx: &mut LocalCommitmentTransaction, htlc_index: u32, preimage: Option<PaymentPreimage>, local_csv: u16, secp_ctx: &Secp256k1<T>);
233+
/// Create a signature for each HTLC transaction spending a local commitment transaction.
234+
///
235+
/// Unlike sign_local_commitment, this may be called multiple times with *different*
236+
/// local_commitment_tx values. While this will never be called with a revoked
237+
/// local_commitment_tx, it is possible that it is called with the second-latest
238+
/// local_commitment_tx (only if we haven't yet revoked it) if some watchtower/secondary
239+
/// ChannelMonitor decided to broadcast before it had been updated to the latest.
240+
///
241+
/// Either an Err should be returned, or a Vec with one entry for each HTLC which exists in
242+
/// local_commitment_tx. For those HTLCs which have transaction_output_index set to None
243+
/// (implying they were considered dust at the time the commitment transaction was negotiated),
244+
/// a corresponding None should be included in the return value. All other positions in the
245+
/// return value must contain a signature.
246+
fn sign_local_commitment_htlc_transactions<T: secp256k1::Signing + secp256k1::Verification>(&self, local_commitment_tx: &LocalCommitmentTransaction, local_csv: u16, secp_ctx: &Secp256k1<T>) -> Result<Vec<Option<Signature>>, ()>;
247+
238248
/// Create a signature for a (proposed) closing transaction.
239249
///
240250
/// Note that, due to rounding, there may be one "missing" satoshi, and either party may have
@@ -379,8 +389,8 @@ impl ChannelKeys for InMemoryChannelKeys {
379389
Ok(local_commitment_tx.get_local_sig(&self.funding_key, &channel_funding_redeemscript, self.channel_value_satoshis, secp_ctx))
380390
}
381391

382-
fn sign_htlc_transaction<T: secp256k1::Signing>(&self, local_commitment_tx: &mut LocalCommitmentTransaction, htlc_index: u32, preimage: Option<PaymentPreimage>, local_csv: u16, secp_ctx: &Secp256k1<T>) {
383-
local_commitment_tx.add_htlc_sig(&self.htlc_base_key, htlc_index, preimage, local_csv, secp_ctx);
392+
fn sign_local_commitment_htlc_transactions<T: secp256k1::Signing + secp256k1::Verification>(&self, local_commitment_tx: &LocalCommitmentTransaction, local_csv: u16, secp_ctx: &Secp256k1<T>) -> Result<Vec<Option<Signature>>, ()> {
393+
local_commitment_tx.get_htlc_sigs(&self.htlc_base_key, local_csv, secp_ctx)
384394
}
385395

386396
fn sign_closing_transaction<T: secp256k1::Signing>(&self, closing_tx: &Transaction, secp_ctx: &Secp256k1<T>) -> Result<Signature, ()> {

lightning/src/ln/chan_utils.rs

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ impl LocalCommitmentTransaction {
524524

525525
/// Generate a new LocalCommitmentTransaction based on a raw commitment transaction,
526526
/// remote signature and both parties keys
527-
pub(crate) fn new_missing_local_sig(mut tx: Transaction, their_sig: &Signature, our_funding_key: &PublicKey, their_funding_key: &PublicKey, local_keys: TxCreationKeys, feerate_per_kw: u64, mut htlc_data: Vec<(HTLCOutputInCommitment, Option<Signature>)>) -> LocalCommitmentTransaction {
527+
pub(crate) fn new_missing_local_sig(mut tx: Transaction, their_sig: &Signature, our_funding_key: &PublicKey, their_funding_key: &PublicKey, local_keys: TxCreationKeys, feerate_per_kw: u64, htlc_data: Vec<(HTLCOutputInCommitment, Option<Signature>)>) -> LocalCommitmentTransaction {
528528
if tx.input.len() != 1 { panic!("Tried to store a commitment transaction that had input count != 1!"); }
529529
if tx.input[0].witness.len() != 0 { panic!("Tried to store a signed commitment transaction?"); }
530530

@@ -543,8 +543,7 @@ impl LocalCommitmentTransaction {
543543
Self { tx,
544544
local_keys,
545545
feerate_per_kw,
546-
// TODO: Avoid the conversion of a Vec created likely just for this:
547-
per_htlc: htlc_data.drain(..).map(|(a, b)| (a, b, None)).collect(),
546+
per_htlc: htlc_data,
548547
}
549548
}
550549

@@ -606,55 +605,68 @@ impl LocalCommitmentTransaction {
606605
&self.tx
607606
}
608607

609-
/// Add local signature for a htlc transaction, do nothing if a cached signed transaction is
610-
/// already present
611-
pub fn add_htlc_sig<T: secp256k1::Signing>(&mut self, htlc_base_key: &SecretKey, htlc_index: u32, preimage: Option<PaymentPreimage>, local_csv: u16, secp_ctx: &Secp256k1<T>) {
608+
/// Get a signature for each HTLC which was included in the commitment transaction (ie for
609+
/// which HTLCOutputInCommitment::transaction_output_index.is_some()).
610+
///
611+
/// The returned Vec has one entry for each HTLC, and in the same order. For HTLCs which were
612+
/// considered dust and not included, a None entry exists, for all others a signature is
613+
/// included.
614+
pub fn get_htlc_sigs<T: secp256k1::Signing + secp256k1::Verification>(&self, htlc_base_key: &SecretKey, local_csv: u16, secp_ctx: &Secp256k1<T>) -> Result<Vec<Option<Signature>>, ()> {
612615
let txid = self.txid();
613-
for this_htlc in self.per_htlc.iter_mut() {
614-
if this_htlc.0.transaction_output_index == Some(htlc_index) {
615-
if this_htlc.2.is_some() { return; } // we already have a cached htlc transaction at provided index
616-
let mut htlc_tx = build_htlc_transaction(&txid, self.feerate_per_kw, local_csv, &this_htlc.0, &self.local_keys.a_delayed_payment_key, &self.local_keys.revocation_key);
617-
if !this_htlc.0.offered && preimage.is_none() { return; } // if we don't have preimage for HTLC-Success, don't try to generate
618-
let htlc_secret = if !this_htlc.0.offered { preimage } else { None }; // if we have a preimage for HTLC-Timeout, don't use it that's likely a duplicate HTLC hash
619-
if this_htlc.1.is_none() { return; } // we don't have any remote signature for this htlc
620-
if htlc_tx.input.len() != 1 { return; }
621-
if htlc_tx.input[0].witness.len() != 0 { return; }
622-
623-
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc.0, &self.local_keys.a_htlc_key, &self.local_keys.b_htlc_key, &self.local_keys.revocation_key);
624-
625-
if let Ok(our_htlc_key) = derive_private_key(secp_ctx, &self.local_keys.per_commitment_point, htlc_base_key) {
626-
let sighash = hash_to_message!(&bip143::SighashComponents::new(&htlc_tx).sighash_all(&htlc_tx.input[0], &htlc_redeemscript, this_htlc.0.amount_msat / 1000)[..]);
627-
let our_sig = secp_ctx.sign(&sighash, &our_htlc_key);
616+
let mut ret = Vec::with_capacity(self.per_htlc.len());
617+
let our_htlc_key = derive_private_key(secp_ctx, &self.local_keys.per_commitment_point, htlc_base_key).map_err(|_| ())?;
628618

629-
htlc_tx.input[0].witness.push(Vec::new()); // First is the multisig dummy
630-
631-
htlc_tx.input[0].witness.push(this_htlc.1.unwrap().serialize_der().to_vec());
632-
htlc_tx.input[0].witness.push(our_sig.serialize_der().to_vec());
633-
htlc_tx.input[0].witness[1].push(SigHashType::All as u8);
634-
htlc_tx.input[0].witness[2].push(SigHashType::All as u8);
635-
636-
if this_htlc.0.offered {
637-
htlc_tx.input[0].witness.push(Vec::new());
638-
assert!(htlc_secret.is_none());
639-
} else {
640-
htlc_tx.input[0].witness.push(htlc_secret.unwrap().0.to_vec());
641-
}
619+
for this_htlc in self.per_htlc.iter() {
620+
if this_htlc.0.transaction_output_index.is_some() {
621+
let htlc_tx = build_htlc_transaction(&txid, self.feerate_per_kw, local_csv, &this_htlc.0, &self.local_keys.a_delayed_payment_key, &self.local_keys.revocation_key);
622+
assert_eq!(htlc_tx.input.len(), 1);
623+
assert_eq!(htlc_tx.input[0].witness.len(), 0);
642624

643-
htlc_tx.input[0].witness.push(htlc_redeemscript.as_bytes().to_vec());
625+
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc.0, &self.local_keys.a_htlc_key, &self.local_keys.b_htlc_key, &self.local_keys.revocation_key);
644626

645-
this_htlc.2 = Some(htlc_tx);
646-
} else { return; }
627+
let sighash = hash_to_message!(&bip143::SighashComponents::new(&htlc_tx).sighash_all(&htlc_tx.input[0], &htlc_redeemscript, this_htlc.0.amount_msat / 1000)[..]);
628+
ret.push(Some(secp_ctx.sign(&sighash, &our_htlc_key)));
629+
} else {
630+
ret.push(None);
647631
}
648632
}
633+
Ok(ret)
649634
}
650-
/// Expose raw htlc transaction, guarante witness is complete if non-empty
651-
pub fn htlc_with_valid_witness(&self, htlc_index: u32) -> &Option<Transaction> {
652-
for this_htlc in self.per_htlc.iter() {
653-
if this_htlc.0.transaction_output_index.unwrap() == htlc_index {
654-
return &this_htlc.2;
655-
}
635+
636+
/// Gets a signed HTLC transaction given a preimage (for !htlc.offered) and the local HTLC transaction signature.
637+
pub(crate) fn get_signed_htlc_tx(&self, htlc_index: usize, signature: &Signature, preimage: &Option<PaymentPreimage>, local_csv: u16) -> Transaction {
638+
let txid = self.txid();
639+
let this_htlc = &self.per_htlc[htlc_index];
640+
assert!(this_htlc.0.transaction_output_index.is_some());
641+
// if we don't have preimage for an HTLC-Success, we can't generate an HTLC transaction.
642+
if !this_htlc.0.offered && preimage.is_none() { unreachable!(); }
643+
// Further, we should never be provided the preimage for an HTLC-Timeout transaction.
644+
if this_htlc.0.offered && preimage.is_some() { unreachable!(); }
645+
646+
let mut htlc_tx = build_htlc_transaction(&txid, self.feerate_per_kw, local_csv, &this_htlc.0, &self.local_keys.a_delayed_payment_key, &self.local_keys.revocation_key);
647+
// Channel should have checked that we have a remote signature for this HTLC at
648+
// creation, and we should have a sensible htlc transaction:
649+
assert!(this_htlc.1.is_some());
650+
assert_eq!(htlc_tx.input.len(), 1);
651+
assert_eq!(htlc_tx.input[0].witness.len(), 0);
652+
653+
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc.0, &self.local_keys.a_htlc_key, &self.local_keys.b_htlc_key, &self.local_keys.revocation_key);
654+
655+
htlc_tx.input[0].witness.push(Vec::new()); // First is the multisig dummy
656+
657+
htlc_tx.input[0].witness.push(this_htlc.1.unwrap().serialize_der().to_vec());
658+
htlc_tx.input[0].witness.push(signature.serialize_der().to_vec());
659+
htlc_tx.input[0].witness[1].push(SigHashType::All as u8);
660+
htlc_tx.input[0].witness[2].push(SigHashType::All as u8);
661+
662+
if this_htlc.0.offered {
663+
htlc_tx.input[0].witness.push(Vec::new());
664+
} else {
665+
htlc_tx.input[0].witness.push(preimage.unwrap().0.to_vec());
656666
}
657-
&None
667+
668+
htlc_tx.input[0].witness.push(htlc_redeemscript.as_bytes().to_vec());
669+
htlc_tx
658670
}
659671
}
660672
impl PartialEq for LocalCommitmentTransaction {
@@ -674,10 +686,9 @@ impl Writeable for LocalCommitmentTransaction {
674686
self.local_keys.write(writer)?;
675687
self.feerate_per_kw.write(writer)?;
676688
writer.write_all(&byte_utils::be64_to_array(self.per_htlc.len() as u64))?;
677-
for &(ref htlc, ref sig, ref htlc_tx) in self.per_htlc.iter() {
689+
for &(ref htlc, ref sig) in self.per_htlc.iter() {
678690
htlc.write(writer)?;
679691
sig.write(writer)?;
680-
htlc_tx.write(writer)?;
681692
}
682693
Ok(())
683694
}
@@ -694,12 +705,11 @@ impl Readable for LocalCommitmentTransaction {
694705
let local_keys = Readable::read(reader)?;
695706
let feerate_per_kw = Readable::read(reader)?;
696707
let htlcs_count: u64 = Readable::read(reader)?;
697-
let mut per_htlc = Vec::with_capacity(cmp::min(htlcs_count as usize, MAX_ALLOC_SIZE / mem::size_of::<(HTLCOutputInCommitment, Option<Signature>, Option<Transaction>)>()));
708+
let mut per_htlc = Vec::with_capacity(cmp::min(htlcs_count as usize, MAX_ALLOC_SIZE / mem::size_of::<(HTLCOutputInCommitment, Option<Signature>)>()));
698709
for _ in 0..htlcs_count {
699710
let htlc: HTLCOutputInCommitment = Readable::read(reader)?;
700711
let sigs = Readable::read(reader)?;
701-
let htlc_tx = Readable::read(reader)?;
702-
per_htlc.push((htlc, sigs, htlc_tx));
712+
per_htlc.push((htlc, sigs));
703713
}
704714

705715
if tx.input.len() != 1 {

lightning/src/ln/channel.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4527,6 +4527,9 @@ mod tests {
45274527
assert_eq!(serialize(localtx.with_valid_witness())[..],
45284528
hex::decode($tx_hex).unwrap()[..]);
45294529

4530+
let htlc_sigs = chan_keys.sign_local_commitment_htlc_transactions(&localtx, chan.their_to_self_delay, &chan.secp_ctx).unwrap();
4531+
let mut htlc_sig_iter = localtx.per_htlc.iter().zip(htlc_sigs.iter().enumerate());
4532+
45304533
$({
45314534
let remote_signature = Signature::from_der(&hex::decode($their_htlc_sig_hex).unwrap()[..]).unwrap();
45324535

@@ -4548,11 +4551,18 @@ mod tests {
45484551
assert!(preimage.is_some());
45494552
}
45504553

4551-
chan_keys.sign_htlc_transaction(&mut localtx, $htlc_idx, preimage, chan.their_to_self_delay, &chan.secp_ctx);
4554+
let mut htlc_sig = htlc_sig_iter.next().unwrap();
4555+
while (htlc_sig.1).1.is_none() { htlc_sig = htlc_sig_iter.next().unwrap(); }
4556+
assert_eq!((htlc_sig.0).0.transaction_output_index, Some($htlc_idx));
45524557

4553-
assert_eq!(serialize(localtx.htlc_with_valid_witness($htlc_idx).as_ref().unwrap())[..],
4558+
assert_eq!(serialize(&localtx.get_signed_htlc_tx((htlc_sig.1).0, &(htlc_sig.1).1.unwrap(), &preimage, chan.their_to_self_delay))[..],
45544559
hex::decode($htlc_tx_hex).unwrap()[..]);
45554560
})*
4561+
loop {
4562+
let htlc_sig = htlc_sig_iter.next();
4563+
if htlc_sig.is_none() { break; }
4564+
assert!((htlc_sig.unwrap().1).1.is_none());
4565+
}
45564566
}
45574567
}
45584568

lightning/src/ln/channelmonitor.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,8 +1677,18 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
16771677

16781678
for &(ref htlc, _, _) in local_tx.htlc_outputs.iter() {
16791679
if let Some(transaction_output_index) = htlc.transaction_output_index {
1680-
let preimage = if let Some(preimage) = self.payment_preimages.get(&htlc.payment_hash) { Some(*preimage) } else { None };
1681-
claim_requests.push(ClaimRequest { absolute_timelock: ::std::u32::MAX, aggregable: false, outpoint: BitcoinOutPoint { txid: local_tx.txid, vout: transaction_output_index as u32 }, witness_data: InputMaterial::LocalHTLC { preimage, amount: htlc.amount_msat / 1000 }});
1680+
claim_requests.push(ClaimRequest { absolute_timelock: ::std::u32::MAX, aggregable: false, outpoint: BitcoinOutPoint { txid: local_tx.txid, vout: transaction_output_index as u32 },
1681+
witness_data: InputMaterial::LocalHTLC {
1682+
preimage: if !htlc.offered {
1683+
if let Some(preimage) = self.payment_preimages.get(&htlc.payment_hash) {
1684+
Some(preimage.clone())
1685+
} else {
1686+
// We can't build an HTLC-Success transaction without the preimage
1687+
continue;
1688+
}
1689+
} else { None },
1690+
amount: htlc.amount_msat,
1691+
}});
16821692
watch_outputs.push(commitment_tx.output[transaction_output_index as usize].clone());
16831693
}
16841694
}
@@ -1780,9 +1790,15 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
17801790
let txid = commitment_tx.txid();
17811791
let mut res = vec![commitment_tx];
17821792
for htlc in self.current_local_commitment_tx.htlc_outputs.iter() {
1783-
if let Some(htlc_index) = htlc.0.transaction_output_index {
1784-
let preimage = if let Some(preimage) = self.payment_preimages.get(&htlc.0.payment_hash) { Some(*preimage) } else { None };
1785-
if let Some(htlc_tx) = self.onchain_tx_handler.get_fully_signed_htlc_tx(txid, htlc_index, preimage) {
1793+
if let Some(vout) = htlc.0.transaction_output_index {
1794+
let preimage = if !htlc.0.offered {
1795+
if let Some(preimage) = self.payment_preimages.get(&htlc.0.payment_hash) { Some(preimage.clone()) } else {
1796+
// We can't build an HTLC-Success transaction without the preimage
1797+
continue;
1798+
}
1799+
} else { None };
1800+
if let Some(htlc_tx) = self.onchain_tx_handler.get_fully_signed_htlc_tx(
1801+
&::bitcoin::OutPoint { txid, vout }, &preimage) {
17861802
res.push(htlc_tx);
17871803
}
17881804
}
@@ -1804,9 +1820,15 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
18041820
let txid = commitment_tx.txid();
18051821
let mut res = vec![commitment_tx];
18061822
for htlc in self.current_local_commitment_tx.htlc_outputs.iter() {
1807-
if let Some(htlc_index) = htlc.0.transaction_output_index {
1808-
let preimage = if let Some(preimage) = self.payment_preimages.get(&htlc.0.payment_hash) { Some(*preimage) } else { None };
1809-
if let Some(htlc_tx) = self.onchain_tx_handler.get_fully_signed_htlc_tx(txid, htlc_index, preimage) {
1823+
if let Some(vout) = htlc.0.transaction_output_index {
1824+
let preimage = if !htlc.0.offered {
1825+
if let Some(preimage) = self.payment_preimages.get(&htlc.0.payment_hash) { Some(preimage.clone()) } else {
1826+
// We can't build an HTLC-Success transaction without the preimage
1827+
continue;
1828+
}
1829+
} else { None };
1830+
if let Some(htlc_tx) = self.onchain_tx_handler.unsafe_get_fully_signed_htlc_tx(
1831+
&::bitcoin::OutPoint { txid, vout }, &preimage) {
18101832
res.push(htlc_tx);
18111833
}
18121834
}

0 commit comments

Comments
 (0)