Skip to content

Commit ba13499

Browse files
authored
Merge pull request #2101 from TheBlueMatt/2023-03-one-less-sig
Support future removal of redundant per-HTLC signatures in `CMU`s
2 parents 8fcbe64 + b72f6b1 commit ba13499

File tree

5 files changed

+158
-34
lines changed

5 files changed

+158
-34
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 79 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -494,8 +494,12 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent,
494494
pub(crate) enum ChannelMonitorUpdateStep {
495495
LatestHolderCommitmentTXInfo {
496496
commitment_tx: HolderCommitmentTransaction,
497+
/// Note that LDK after 0.0.115 supports this only containing dust HTLCs (implying the
498+
/// `Signature` field is never filled in). At that point, non-dust HTLCs are implied by the
499+
/// HTLC fields in `commitment_tx` and the sources passed via `nondust_htlc_sources`.
497500
htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>,
498501
claimed_htlcs: Vec<(SentHTLCId, PaymentPreimage)>,
502+
nondust_htlc_sources: Vec<HTLCSource>,
499503
},
500504
LatestCounterpartyCommitmentTXInfo {
501505
commitment_txid: Txid,
@@ -540,6 +544,7 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
540544
(0, commitment_tx, required),
541545
(1, claimed_htlcs, vec_type),
542546
(2, htlc_outputs, vec_type),
547+
(4, nondust_htlc_sources, optional_vec),
543548
},
544549
(1, LatestCounterpartyCommitmentTXInfo) => {
545550
(0, commitment_txid, required),
@@ -1181,7 +1186,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
11811186
&self, holder_commitment_tx: HolderCommitmentTransaction,
11821187
htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>,
11831188
) -> Result<(), ()> {
1184-
self.inner.lock().unwrap().provide_latest_holder_commitment_tx(holder_commitment_tx, htlc_outputs, &Vec::new()).map_err(|_| ())
1189+
self.inner.lock().unwrap().provide_latest_holder_commitment_tx(holder_commitment_tx, htlc_outputs, &Vec::new(), Vec::new()).map_err(|_| ())
11851190
}
11861191

11871192
/// This is used to provide payment preimage(s) out-of-band during startup without updating the
@@ -2150,7 +2155,53 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
21502155
/// is important that any clones of this channel monitor (including remote clones) by kept
21512156
/// up-to-date as our holder commitment transaction is updated.
21522157
/// Panics if set_on_holder_tx_csv has never been called.
2153-
fn provide_latest_holder_commitment_tx(&mut self, holder_commitment_tx: HolderCommitmentTransaction, htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>, claimed_htlcs: &[(SentHTLCId, PaymentPreimage)]) -> Result<(), &'static str> {
2158+
fn provide_latest_holder_commitment_tx(&mut self, holder_commitment_tx: HolderCommitmentTransaction, mut htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>, claimed_htlcs: &[(SentHTLCId, PaymentPreimage)], nondust_htlc_sources: Vec<HTLCSource>) -> Result<(), &'static str> {
2159+
if htlc_outputs.iter().any(|(_, s, _)| s.is_some()) {
2160+
// If we have non-dust HTLCs in htlc_outputs, ensure they match the HTLCs in the
2161+
// `holder_commitment_tx`. In the future, we'll no longer provide the redundant data
2162+
// and just pass in source data via `nondust_htlc_sources`.
2163+
debug_assert_eq!(htlc_outputs.iter().filter(|(_, s, _)| s.is_some()).count(), holder_commitment_tx.trust().htlcs().len());
2164+
for (a, b) in htlc_outputs.iter().filter(|(_, s, _)| s.is_some()).map(|(h, _, _)| h).zip(holder_commitment_tx.trust().htlcs().iter()) {
2165+
debug_assert_eq!(a, b);
2166+
}
2167+
debug_assert_eq!(htlc_outputs.iter().filter(|(_, s, _)| s.is_some()).count(), holder_commitment_tx.counterparty_htlc_sigs.len());
2168+
for (a, b) in htlc_outputs.iter().filter_map(|(_, s, _)| s.as_ref()).zip(holder_commitment_tx.counterparty_htlc_sigs.iter()) {
2169+
debug_assert_eq!(a, b);
2170+
}
2171+
debug_assert!(nondust_htlc_sources.is_empty());
2172+
} else {
2173+
// If we don't have any non-dust HTLCs in htlc_outputs, assume they were all passed via
2174+
// `nondust_htlc_sources`, building up the final htlc_outputs by combining
2175+
// `nondust_htlc_sources` and the `holder_commitment_tx`
2176+
#[cfg(debug_assertions)] {
2177+
let mut prev = -1;
2178+
for htlc in holder_commitment_tx.trust().htlcs().iter() {
2179+
assert!(htlc.transaction_output_index.unwrap() as i32 > prev);
2180+
prev = htlc.transaction_output_index.unwrap() as i32;
2181+
}
2182+
}
2183+
debug_assert!(htlc_outputs.iter().all(|(htlc, _, _)| htlc.transaction_output_index.is_none()));
2184+
debug_assert!(htlc_outputs.iter().all(|(_, sig_opt, _)| sig_opt.is_none()));
2185+
debug_assert_eq!(holder_commitment_tx.trust().htlcs().len(), holder_commitment_tx.counterparty_htlc_sigs.len());
2186+
2187+
let mut sources_iter = nondust_htlc_sources.into_iter();
2188+
2189+
for (htlc, counterparty_sig) in holder_commitment_tx.trust().htlcs().iter()
2190+
.zip(holder_commitment_tx.counterparty_htlc_sigs.iter())
2191+
{
2192+
if htlc.offered {
2193+
let source = sources_iter.next().expect("Non-dust HTLC sources didn't match commitment tx");
2194+
#[cfg(debug_assertions)] {
2195+
assert!(source.possibly_matches_output(htlc));
2196+
}
2197+
htlc_outputs.push((htlc.clone(), Some(counterparty_sig.clone()), Some(source)));
2198+
} else {
2199+
htlc_outputs.push((htlc.clone(), Some(counterparty_sig.clone()), None));
2200+
}
2201+
}
2202+
debug_assert!(sources_iter.next().is_none());
2203+
}
2204+
21542205
let trusted_tx = holder_commitment_tx.trust();
21552206
let txid = trusted_tx.txid();
21562207
let tx_keys = trusted_tx.keys();
@@ -2283,10 +2334,10 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
22832334
let bounded_fee_estimator = LowerBoundedFeeEstimator::new(&*fee_estimator);
22842335
for update in updates.updates.iter() {
22852336
match update {
2286-
ChannelMonitorUpdateStep::LatestHolderCommitmentTXInfo { commitment_tx, htlc_outputs, claimed_htlcs } => {
2337+
ChannelMonitorUpdateStep::LatestHolderCommitmentTXInfo { commitment_tx, htlc_outputs, claimed_htlcs, nondust_htlc_sources } => {
22872338
log_trace!(logger, "Updating ChannelMonitor with latest holder commitment transaction info");
22882339
if self.lockdown_from_offchain { panic!(); }
2289-
if let Err(e) = self.provide_latest_holder_commitment_tx(commitment_tx.clone(), htlc_outputs.clone(), &claimed_htlcs) {
2340+
if let Err(e) = self.provide_latest_holder_commitment_tx(commitment_tx.clone(), htlc_outputs.clone(), &claimed_htlcs, nondust_htlc_sources.clone()) {
22902341
log_error!(logger, "Providing latest holder commitment transaction failed/was refused:");
22912342
log_error!(logger, " {}", e);
22922343
ret = Err(());
@@ -4139,7 +4190,7 @@ mod tests {
41394190
}
41404191
}
41414192

4142-
macro_rules! preimages_slice_to_htlc_outputs {
4193+
macro_rules! preimages_slice_to_htlcs {
41434194
($preimages_slice: expr) => {
41444195
{
41454196
let mut res = Vec::new();
@@ -4150,21 +4201,20 @@ mod tests {
41504201
cltv_expiry: 0,
41514202
payment_hash: preimage.1.clone(),
41524203
transaction_output_index: Some(idx as u32),
4153-
}, None));
4204+
}, ()));
41544205
}
41554206
res
41564207
}
41574208
}
41584209
}
4159-
macro_rules! preimages_to_holder_htlcs {
4210+
macro_rules! preimages_slice_to_htlc_outputs {
41604211
($preimages_slice: expr) => {
4161-
{
4162-
let mut inp = preimages_slice_to_htlc_outputs!($preimages_slice);
4163-
let res: Vec<_> = inp.drain(..).map(|e| { (e.0, None, e.1) }).collect();
4164-
res
4165-
}
4212+
preimages_slice_to_htlcs!($preimages_slice).into_iter().map(|(htlc, _)| (htlc, None)).collect()
41664213
}
41674214
}
4215+
let dummy_sig = crate::util::crypto::sign(&secp_ctx,
4216+
&bitcoin::secp256k1::Message::from_slice(&[42; 32]).unwrap(),
4217+
&SecretKey::from_slice(&[42; 32]).unwrap());
41684218

41694219
macro_rules! test_preimages_exist {
41704220
($preimages_slice: expr, $monitor: expr) => {
@@ -4211,13 +4261,15 @@ mod tests {
42114261
let shutdown_pubkey = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
42124262
let best_block = BestBlock::from_network(Network::Testnet);
42134263
let monitor = ChannelMonitor::new(Secp256k1::new(), keys,
4214-
Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &Script::new(),
4215-
(OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, Script::new()),
4216-
&channel_parameters,
4217-
Script::new(), 46, 0,
4218-
HolderCommitmentTransaction::dummy(), best_block, dummy_key);
4219-
4220-
monitor.provide_latest_holder_commitment_tx(HolderCommitmentTransaction::dummy(), preimages_to_holder_htlcs!(preimages[0..10])).unwrap();
4264+
Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &Script::new(),
4265+
(OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, Script::new()),
4266+
&channel_parameters, Script::new(), 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()),
4267+
best_block, dummy_key);
4268+
4269+
let mut htlcs = preimages_slice_to_htlcs!(preimages[0..10]);
4270+
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(&mut htlcs);
4271+
monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx.clone(),
4272+
htlcs.into_iter().map(|(htlc, _)| (htlc, Some(dummy_sig), None)).collect()).unwrap();
42214273
monitor.provide_latest_counterparty_commitment_tx(Txid::from_inner(Sha256::hash(b"1").into_inner()),
42224274
preimages_slice_to_htlc_outputs!(preimages[5..15]), 281474976710655, dummy_key, &logger);
42234275
monitor.provide_latest_counterparty_commitment_tx(Txid::from_inner(Sha256::hash(b"2").into_inner()),
@@ -4250,15 +4302,21 @@ mod tests {
42504302

42514303
// Now update holder commitment tx info, pruning only element 18 as we still care about the
42524304
// previous commitment tx's preimages too
4253-
monitor.provide_latest_holder_commitment_tx(HolderCommitmentTransaction::dummy(), preimages_to_holder_htlcs!(preimages[0..5])).unwrap();
4305+
let mut htlcs = preimages_slice_to_htlcs!(preimages[0..5]);
4306+
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(&mut htlcs);
4307+
monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx.clone(),
4308+
htlcs.into_iter().map(|(htlc, _)| (htlc, Some(dummy_sig), None)).collect()).unwrap();
42544309
secret[0..32].clone_from_slice(&hex::decode("2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8").unwrap());
42554310
monitor.provide_secret(281474976710653, secret.clone()).unwrap();
42564311
assert_eq!(monitor.inner.lock().unwrap().payment_preimages.len(), 12);
42574312
test_preimages_exist!(&preimages[0..10], monitor);
42584313
test_preimages_exist!(&preimages[18..20], monitor);
42594314

42604315
// But if we do it again, we'll prune 5-10
4261-
monitor.provide_latest_holder_commitment_tx(HolderCommitmentTransaction::dummy(), preimages_to_holder_htlcs!(preimages[0..3])).unwrap();
4316+
let mut htlcs = preimages_slice_to_htlcs!(preimages[0..3]);
4317+
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(&mut htlcs);
4318+
monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx,
4319+
htlcs.into_iter().map(|(htlc, _)| (htlc, Some(dummy_sig), None)).collect()).unwrap();
42624320
secret[0..32].clone_from_slice(&hex::decode("27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116").unwrap());
42634321
monitor.provide_secret(281474976710652, secret.clone()).unwrap();
42644322
assert_eq!(monitor.inner.lock().unwrap().payment_preimages.len(), 5);

lightning/src/ln/chan_utils.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ impl_writeable_tlv_based!(HolderCommitmentTransaction, {
984984

985985
impl HolderCommitmentTransaction {
986986
#[cfg(test)]
987-
pub fn dummy() -> Self {
987+
pub fn dummy(htlcs: &mut Vec<(HTLCOutputInCommitment, ())>) -> Self {
988988
let secp_ctx = Secp256k1::new();
989989
let dummy_key = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
990990
let dummy_sig = sign(&secp_ctx, &secp256k1::Message::from_slice(&[42; 32]).unwrap(), &SecretKey::from_slice(&[42; 32]).unwrap());
@@ -1012,12 +1012,16 @@ impl HolderCommitmentTransaction {
10121012
opt_anchors: None,
10131013
opt_non_zero_fee_anchors: None,
10141014
};
1015-
let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
1016-
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, false, dummy_key.clone(), dummy_key.clone(), keys, 0, &mut htlcs_with_aux, &channel_parameters.as_counterparty_broadcastable());
1015+
let mut counterparty_htlc_sigs = Vec::new();
1016+
for _ in 0..htlcs.len() {
1017+
counterparty_htlc_sigs.push(dummy_sig);
1018+
}
1019+
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, false, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable());
1020+
htlcs.sort_by_key(|htlc| htlc.0.transaction_output_index);
10171021
HolderCommitmentTransaction {
10181022
inner,
10191023
counterparty_sig: dummy_sig,
1020-
counterparty_htlc_sigs: Vec::new(),
1024+
counterparty_htlc_sigs,
10211025
holder_sig_first: false
10221026
}
10231027
}

lightning/src/ln/channel.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3134,9 +3134,24 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
31343134
return Err(ChannelError::Close(format!("Got wrong number of HTLC signatures ({}) from remote. It must be {}", msg.htlc_signatures.len(), commitment_stats.num_nondust_htlcs)));
31353135
}
31363136

3137-
// TODO: Sadly, we pass HTLCs twice to ChannelMonitor: once via the HolderCommitmentTransaction and once via the update
3137+
// Up to LDK 0.0.115, HTLC information was required to be duplicated in the
3138+
// `htlcs_and_sigs` vec and in the `holder_commitment_tx` itself, both of which were passed
3139+
// in the `ChannelMonitorUpdate`. In 0.0.115, support for having a separate set of
3140+
// outbound-non-dust-HTLCSources in the `ChannelMonitorUpdate` was added, however for
3141+
// backwards compatibility, we never use it in production. To provide test coverage, here,
3142+
// we randomly decide (in test/fuzzing builds) to use the new vec sometimes.
3143+
#[allow(unused_assignments, unused_mut)]
3144+
let mut separate_nondust_htlc_sources = false;
3145+
#[cfg(all(feature = "std", any(test, fuzzing)))] {
3146+
use core::hash::{BuildHasher, Hasher};
3147+
// Get a random value using the only std API to do so - the DefaultHasher
3148+
let rand_val = std::collections::hash_map::RandomState::new().build_hasher().finish();
3149+
separate_nondust_htlc_sources = rand_val % 2 == 0;
3150+
}
3151+
3152+
let mut nondust_htlc_sources = Vec::with_capacity(htlcs_cloned.len());
31383153
let mut htlcs_and_sigs = Vec::with_capacity(htlcs_cloned.len());
3139-
for (idx, (htlc, source)) in htlcs_cloned.drain(..).enumerate() {
3154+
for (idx, (htlc, mut source_opt)) in htlcs_cloned.drain(..).enumerate() {
31403155
if let Some(_) = htlc.transaction_output_index {
31413156
let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, commitment_stats.feerate_per_kw,
31423157
self.get_counterparty_selected_contest_delay().unwrap(), &htlc, self.opt_anchors(),
@@ -3151,10 +3166,18 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
31513166
if let Err(_) = self.secp_ctx.verify_ecdsa(&htlc_sighash, &msg.htlc_signatures[idx], &keys.countersignatory_htlc_key) {
31523167
return Err(ChannelError::Close("Invalid HTLC tx signature from peer".to_owned()));
31533168
}
3154-
htlcs_and_sigs.push((htlc, Some(msg.htlc_signatures[idx]), source));
3169+
if !separate_nondust_htlc_sources {
3170+
htlcs_and_sigs.push((htlc, Some(msg.htlc_signatures[idx]), source_opt.take()));
3171+
}
31553172
} else {
3156-
htlcs_and_sigs.push((htlc, None, source));
3173+
htlcs_and_sigs.push((htlc, None, source_opt.take()));
3174+
}
3175+
if separate_nondust_htlc_sources {
3176+
if let Some(source) = source_opt.take() {
3177+
nondust_htlc_sources.push(source);
3178+
}
31573179
}
3180+
debug_assert!(source_opt.is_none(), "HTLCSource should have been put somewhere");
31583181
}
31593182

31603183
let holder_commitment_tx = HolderCommitmentTransaction::new(
@@ -3217,6 +3240,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
32173240
commitment_tx: holder_commitment_tx,
32183241
htlc_outputs: htlcs_and_sigs,
32193242
claimed_htlcs,
3243+
nondust_htlc_sources,
32203244
}]
32213245
};
32223246

lightning/src/ln/channelmanager.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,9 @@ impl core::hash::Hash for HTLCSource {
308308
}
309309
}
310310
}
311-
#[cfg(not(feature = "grind_signatures"))]
312-
#[cfg(test)]
313311
impl HTLCSource {
312+
#[cfg(not(feature = "grind_signatures"))]
313+
#[cfg(test)]
314314
pub fn dummy() -> Self {
315315
HTLCSource::OutboundRoute {
316316
path: Vec::new(),
@@ -320,6 +320,18 @@ impl HTLCSource {
320320
payment_secret: None,
321321
}
322322
}
323+
324+
#[cfg(debug_assertions)]
325+
/// Checks whether this HTLCSource could possibly match the given HTLC output in a commitment
326+
/// transaction. Useful to ensure different datastructures match up.
327+
pub(crate) fn possibly_matches_output(&self, htlc: &super::chan_utils::HTLCOutputInCommitment) -> bool {
328+
if let HTLCSource::OutboundRoute { first_hop_htlc_msat, .. } = self {
329+
*first_hop_htlc_msat == htlc.amount_msat
330+
} else {
331+
// There's nothing we can check for forwarded HTLCs
332+
true
333+
}
334+
}
323335
}
324336

325337
struct ReceiveError {

0 commit comments

Comments
 (0)