@@ -2646,31 +2646,49 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
26462646 }
26472647
26482648 /// Attempts to claim a counterparty HTLC-Success/HTLC-Timeout's outputs using the revocation key
2649- fn check_spend_counterparty_htlc < L : Deref > ( & mut self , tx : & Transaction , commitment_number : u64 , height : u32 , logger : & L ) -> ( Vec < PackageTemplate > , Option < TransactionOutputs > ) where L :: Target : Logger {
2650- let htlc_txid = tx. txid ( ) ;
2651- if tx. input . len ( ) != 1 || tx. output . len ( ) != 1 || tx. input [ 0 ] . witness . len ( ) != 5 {
2652- return ( Vec :: new ( ) , None )
2653- }
2654-
2655- macro_rules! ignore_error {
2656- ( $thing : expr ) => {
2657- match $thing {
2658- Ok ( a) => a,
2659- Err ( _) => return ( Vec :: new( ) , None )
2660- }
2661- } ;
2662- }
2663-
2649+ fn check_spend_counterparty_htlc < L : Deref > (
2650+ & mut self , tx : & Transaction , commitment_number : u64 , commitment_txid : & Txid , height : u32 , logger : & L
2651+ ) -> ( Vec < PackageTemplate > , Option < TransactionOutputs > ) where L :: Target : Logger {
26642652 let secret = if let Some ( secret) = self . get_secret ( commitment_number) { secret } else { return ( Vec :: new ( ) , None ) ; } ;
2665- let per_commitment_key = ignore_error ! ( SecretKey :: from_slice( & secret) ) ;
2653+ let per_commitment_key = match SecretKey :: from_slice ( & secret) {
2654+ Ok ( key) => key,
2655+ Err ( _) => return ( Vec :: new ( ) , None )
2656+ } ;
26662657 let per_commitment_point = PublicKey :: from_secret_key ( & self . secp_ctx , & per_commitment_key) ;
26672658
2668- log_error ! ( logger, "Got broadcast of revoked counterparty HTLC transaction, spending {}:{}" , htlc_txid, 0 ) ;
2669- let revk_outp = RevokedOutput :: build ( per_commitment_point, self . counterparty_commitment_params . counterparty_delayed_payment_base_key , self . counterparty_commitment_params . counterparty_htlc_base_key , per_commitment_key, tx. output [ 0 ] . value , self . counterparty_commitment_params . on_counterparty_tx_csv ) ;
2670- let justice_package = PackageTemplate :: build_package ( htlc_txid, 0 , PackageSolvingData :: RevokedOutput ( revk_outp) , height + self . counterparty_commitment_params . on_counterparty_tx_csv as u32 , true , height) ;
2671- let claimable_outpoints = vec ! ( justice_package) ;
2672- let outputs = vec ! [ ( 0 , tx. output[ 0 ] . clone( ) ) ] ;
2673- ( claimable_outpoints, Some ( ( htlc_txid, outputs) ) )
2659+ let htlc_txid = tx. txid ( ) ;
2660+ let mut claimable_outpoints = vec ! [ ] ;
2661+ let mut outputs_to_watch = None ;
2662+ // Previously, we would only claim HTLCs from revoked HTLC transactions if they had 1 input
2663+ // with a witness of 5 elements and 1 output. This wasn't enough for anchor outputs, as the
2664+ // counterparty can now aggregate multiple HTLCs into a single transaction thanks to
2665+ // `SIGHASH_SINGLE` remote signatures, leading us to not claim any HTLCs upon seeing a
2666+ // confirmed revoked HTLC transaction (for more details, see
2667+ // https://lists.linuxfoundation.org/pipermail/lightning-dev/2022-April/003561.html).
2668+ //
2669+ // We make sure we're not vulnerable to this case by checking all inputs of the transaction,
2670+ // and claim those which spend the commitment transaction, have a witness of 5 elements, and
2671+ // have a corresponding output at the same index within the transaction.
2672+ for ( idx, input) in tx. input . iter ( ) . enumerate ( ) {
2673+ if input. previous_output . txid == * commitment_txid && input. witness . len ( ) == 5 && tx. output . get ( idx) . is_some ( ) {
2674+ log_error ! ( logger, "Got broadcast of revoked counterparty HTLC transaction, spending {}:{}" , htlc_txid, idx) ;
2675+ let revk_outp = RevokedOutput :: build (
2676+ per_commitment_point, self . counterparty_commitment_params . counterparty_delayed_payment_base_key ,
2677+ self . counterparty_commitment_params . counterparty_htlc_base_key , per_commitment_key,
2678+ tx. output [ 0 ] . value , self . counterparty_commitment_params . on_counterparty_tx_csv
2679+ ) ;
2680+ let justice_package = PackageTemplate :: build_package (
2681+ htlc_txid, idx as u32 , PackageSolvingData :: RevokedOutput ( revk_outp) ,
2682+ height + self . counterparty_commitment_params . on_counterparty_tx_csv as u32 , true , height
2683+ ) ;
2684+ claimable_outpoints. push ( justice_package) ;
2685+ if outputs_to_watch. is_none ( ) {
2686+ outputs_to_watch = Some ( ( htlc_txid, vec ! [ ] ) ) ;
2687+ }
2688+ outputs_to_watch. as_mut ( ) . unwrap ( ) . 1 . push ( ( idx as u32 , tx. output [ idx] . clone ( ) ) ) ;
2689+ }
2690+ }
2691+ ( claimable_outpoints, outputs_to_watch)
26742692 }
26752693
26762694 // Returns (1) `PackageTemplate`s that can be given to the OnchainTxHandler, so that the handler can
@@ -2908,9 +2926,9 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
29082926 for tx in & txn_matched {
29092927 if tx. input . len ( ) == 1 {
29102928 // Assuming our keys were not leaked (in which case we're screwed no matter what),
2911- // commitment transactions and HTLC transactions will all only ever have one input,
2912- // which is an easy way to filter out any potential non-matching txn for lazy
2913- // filters.
2929+ // commitment transactions and HTLC transactions will all only ever have one input
2930+ // (except for HTLC transactions for channels with anchor outputs), which is an easy
2931+ // way to filter out any potential non-matching txn for lazy filters.
29142932 let prevout = & tx. input [ 0 ] . previous_output ;
29152933 if prevout. txid == self . funding_info . 0 . txid && prevout. vout == self . funding_info . 0 . index as u32 {
29162934 let mut balance_spendable_csv = None ;
@@ -2951,7 +2969,9 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
29512969 } ) ;
29522970 } else {
29532971 if let Some ( & commitment_number) = self . counterparty_commitment_txn_on_chain . get ( & prevout. txid ) {
2954- let ( mut new_outpoints, new_outputs_option) = self . check_spend_counterparty_htlc ( & tx, commitment_number, height, & logger) ;
2972+ let ( mut new_outpoints, new_outputs_option) = self . check_spend_counterparty_htlc (
2973+ & tx, commitment_number, & prevout. txid , height, & logger
2974+ ) ;
29552975 claimable_outpoints. append ( & mut new_outpoints) ;
29562976 if let Some ( new_outputs) = new_outputs_option {
29572977 watch_outputs. push ( new_outputs) ;
0 commit comments