Skip to content

Commit 3196617

Browse files
committed
lightning: canonicalize htlc claim ids
Hash HTLC claim outpoints in canonical order so the same logical HTLC set produces the same ClaimId regardless of descriptor order. Add a unit test covering reversed descriptor order.
1 parent f6fe277 commit 3196617

1 file changed

Lines changed: 61 additions & 3 deletions

File tree

lightning/src/chain/mod.rs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -563,10 +563,18 @@ pub struct ClaimId(pub [u8; 32]);
563563

564564
impl ClaimId {
565565
pub(crate) fn from_htlcs(htlcs: &[HTLCDescriptor]) -> ClaimId {
566+
let mut htlc_outpoints = htlcs
567+
.iter()
568+
.map(|htlc| {
569+
(htlc.commitment_txid.to_byte_array(), htlc.htlc.transaction_output_index.unwrap())
570+
})
571+
.collect::<Vec<_>>();
572+
htlc_outpoints.sort_unstable();
573+
566574
let mut engine = Sha256::engine();
567-
for htlc in htlcs {
568-
engine.input(&htlc.commitment_txid.to_byte_array());
569-
engine.input(&htlc.htlc.transaction_output_index.unwrap().to_be_bytes());
575+
for (commitment_txid, transaction_output_index) in htlc_outpoints {
576+
engine.input(&commitment_txid);
577+
engine.input(&transaction_output_index.to_be_bytes());
570578
}
571579
ClaimId(Sha256::from_engine(engine).to_byte_array())
572580
}
@@ -581,8 +589,45 @@ impl ClaimId {
581589
#[cfg(test)]
582590
mod tests {
583591
use super::*;
592+
use crate::ln::chan_utils::{
593+
ChannelTransactionParameters, HTLCOutputInCommitment, HolderCommitmentTransaction,
594+
};
595+
use crate::sign::ChannelDerivationParameters;
596+
use crate::types::payment::{PaymentHash, PaymentPreimage};
584597
use bitcoin::hashes::Hash;
585598

599+
fn dummy_htlc_descriptor(
600+
commitment_txid: Txid, transaction_output_index: u32,
601+
) -> HTLCDescriptor {
602+
let channel_parameters = ChannelTransactionParameters::test_dummy(100_000);
603+
let htlc = HTLCOutputInCommitment {
604+
offered: true,
605+
amount_msat: 1000,
606+
cltv_expiry: 100,
607+
payment_hash: PaymentHash::from(PaymentPreimage([1; 32])),
608+
transaction_output_index: Some(transaction_output_index),
609+
};
610+
let funding_outpoint = channel_parameters.funding_outpoint.unwrap();
611+
let commitment_tx =
612+
HolderCommitmentTransaction::dummy(100_000, funding_outpoint, vec![htlc.clone()]);
613+
let trusted_tx = commitment_tx.trust();
614+
615+
HTLCDescriptor {
616+
channel_derivation_parameters: ChannelDerivationParameters {
617+
value_satoshis: channel_parameters.channel_value_satoshis,
618+
keys_id: [1; 32],
619+
transaction_parameters: channel_parameters,
620+
},
621+
commitment_txid,
622+
per_commitment_number: trusted_tx.commitment_number(),
623+
per_commitment_point: trusted_tx.per_commitment_point(),
624+
feerate_per_kw: trusted_tx.negotiated_feerate_per_kw(),
625+
htlc,
626+
preimage: None,
627+
counterparty_sig: commitment_tx.counterparty_htlc_sigs[0],
628+
}
629+
}
630+
586631
#[test]
587632
fn test_best_block() {
588633
let hash1 = BlockHash::from_slice(&[1; 32]).unwrap();
@@ -618,4 +663,17 @@ mod tests {
618663
let chain_c = BlockLocator::new(hash_other, 200);
619664
assert_eq!(chain_a.find_common_ancestor(&chain_c), None);
620665
}
666+
667+
#[test]
668+
fn test_htlc_claim_id_is_descriptor_order_independent() {
669+
// Use opposite txid and vout ordering so the assertion would fail if
670+
// ClaimId still hashed descriptors in caller-provided order.
671+
let first = dummy_htlc_descriptor(Txid::from_slice(&[1; 32]).unwrap(), 2);
672+
let second = dummy_htlc_descriptor(Txid::from_slice(&[2; 32]).unwrap(), 1);
673+
674+
assert_eq!(
675+
ClaimId::from_htlcs(&[first.clone(), second.clone()]),
676+
ClaimId::from_htlcs(&[second, first])
677+
);
678+
}
621679
}

0 commit comments

Comments
 (0)