@@ -2645,31 +2645,49 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
2645
2645
}
2646
2646
2647
2647
/// Attempts to claim a counterparty HTLC-Success/HTLC-Timeout's outputs using the revocation key
2648
- 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 {
2649
- let htlc_txid = tx. txid ( ) ;
2650
- if tx. input . len ( ) != 1 || tx. output . len ( ) != 1 || tx. input [ 0 ] . witness . len ( ) != 5 {
2651
- return ( Vec :: new ( ) , None )
2652
- }
2653
-
2654
- macro_rules! ignore_error {
2655
- ( $thing : expr ) => {
2656
- match $thing {
2657
- Ok ( a) => a,
2658
- Err ( _) => return ( Vec :: new( ) , None )
2659
- }
2660
- } ;
2661
- }
2662
-
2648
+ fn check_spend_counterparty_htlc < L : Deref > (
2649
+ & mut self , tx : & Transaction , commitment_number : u64 , commitment_txid : & Txid , height : u32 , logger : & L
2650
+ ) -> ( Vec < PackageTemplate > , Option < TransactionOutputs > ) where L :: Target : Logger {
2663
2651
let secret = if let Some ( secret) = self . get_secret ( commitment_number) { secret } else { return ( Vec :: new ( ) , None ) ; } ;
2664
- let per_commitment_key = ignore_error ! ( SecretKey :: from_slice( & secret) ) ;
2652
+ let per_commitment_key = match SecretKey :: from_slice ( & secret) {
2653
+ Ok ( key) => key,
2654
+ Err ( _) => return ( Vec :: new ( ) , None )
2655
+ } ;
2665
2656
let per_commitment_point = PublicKey :: from_secret_key ( & self . secp_ctx , & per_commitment_key) ;
2666
2657
2667
- log_error ! ( logger, "Got broadcast of revoked counterparty HTLC transaction, spending {}:{}" , htlc_txid, 0 ) ;
2668
- 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 ) ;
2669
- 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) ;
2670
- let claimable_outpoints = vec ! ( justice_package) ;
2671
- let outputs = vec ! [ ( 0 , tx. output[ 0 ] . clone( ) ) ] ;
2672
- ( claimable_outpoints, Some ( ( htlc_txid, outputs) ) )
2658
+ let htlc_txid = tx. txid ( ) ;
2659
+ let mut claimable_outpoints = vec ! [ ] ;
2660
+ let mut outputs_to_watch = None ;
2661
+ // Previously, we would only claim HTLCs from revoked HTLC transactions if they had 1 input
2662
+ // with a witness of 5 elements and 1 output. This wasn't enough for anchor outputs, as the
2663
+ // counterparty can now aggregate multiple HTLCs into a single transaction thanks to
2664
+ // `SIGHASH_SINGLE` remote signatures, leading us to not claim any HTLCs upon seeing a
2665
+ // confirmed revoked HTLC transaction (for more details, see
2666
+ // https://lists.linuxfoundation.org/pipermail/lightning-dev/2022-April/003561.html).
2667
+ //
2668
+ // We make sure we're not vulnerable to this case by checking all inputs of the transaction,
2669
+ // and claim those which spend the commitment transaction, have a witness of 5 elements, and
2670
+ // have a corresponding output at the same index within the transaction.
2671
+ for ( idx, input) in tx. input . iter ( ) . enumerate ( ) {
2672
+ if input. previous_output . txid == * commitment_txid && input. witness . len ( ) == 5 && tx. output . get ( idx) . is_some ( ) {
2673
+ log_error ! ( logger, "Got broadcast of revoked counterparty HTLC transaction, spending {}:{}" , htlc_txid, idx) ;
2674
+ let revk_outp = RevokedOutput :: build (
2675
+ per_commitment_point, self . counterparty_commitment_params . counterparty_delayed_payment_base_key ,
2676
+ self . counterparty_commitment_params . counterparty_htlc_base_key , per_commitment_key,
2677
+ tx. output [ idx] . value , self . counterparty_commitment_params . on_counterparty_tx_csv
2678
+ ) ;
2679
+ let justice_package = PackageTemplate :: build_package (
2680
+ htlc_txid, idx as u32 , PackageSolvingData :: RevokedOutput ( revk_outp) ,
2681
+ height + self . counterparty_commitment_params . on_counterparty_tx_csv as u32 , true , height
2682
+ ) ;
2683
+ claimable_outpoints. push ( justice_package) ;
2684
+ if outputs_to_watch. is_none ( ) {
2685
+ outputs_to_watch = Some ( ( htlc_txid, vec ! [ ] ) ) ;
2686
+ }
2687
+ outputs_to_watch. as_mut ( ) . unwrap ( ) . 1 . push ( ( idx as u32 , tx. output [ idx] . clone ( ) ) ) ;
2688
+ }
2689
+ }
2690
+ ( claimable_outpoints, outputs_to_watch)
2673
2691
}
2674
2692
2675
2693
// Returns (1) `PackageTemplate`s that can be given to the OnchainTxHandler, so that the handler can
@@ -2927,9 +2945,9 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
2927
2945
2928
2946
if tx. input . len ( ) == 1 {
2929
2947
// Assuming our keys were not leaked (in which case we're screwed no matter what),
2930
- // commitment transactions and HTLC transactions will all only ever have one input,
2931
- // which is an easy way to filter out any potential non-matching txn for lazy
2932
- // filters.
2948
+ // commitment transactions and HTLC transactions will all only ever have one input
2949
+ // (except for HTLC transactions for channels with anchor outputs), which is an easy
2950
+ // way to filter out any potential non-matching txn for lazy filters.
2933
2951
let prevout = & tx. input [ 0 ] . previous_output ;
2934
2952
if prevout. txid == self . funding_info . 0 . txid && prevout. vout == self . funding_info . 0 . index as u32 {
2935
2953
let mut balance_spendable_csv = None ;
@@ -2967,22 +2985,33 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
2967
2985
commitment_tx_to_counterparty_output,
2968
2986
} ,
2969
2987
} ) ;
2970
- } else {
2971
- if let Some ( & commitment_number) = self . counterparty_commitment_txn_on_chain . get ( & prevout. txid ) {
2972
- let ( mut new_outpoints, new_outputs_option) = self . check_spend_counterparty_htlc ( & tx, commitment_number, height, & logger) ;
2988
+ }
2989
+ }
2990
+ if tx. input . len ( ) >= 1 {
2991
+ // While all commitment transactions have one input, HTLC transactions may have more
2992
+ // if the HTLC was present in an anchor channel. HTLCs can also be resolved in a few
2993
+ // other ways which can have more than one output.
2994
+ for tx_input in & tx. input {
2995
+ let commitment_txid = tx_input. previous_output . txid ;
2996
+ if let Some ( & commitment_number) = self . counterparty_commitment_txn_on_chain . get ( & commitment_txid) {
2997
+ let ( mut new_outpoints, new_outputs_option) = self . check_spend_counterparty_htlc (
2998
+ & tx, commitment_number, & commitment_txid, height, & logger
2999
+ ) ;
2973
3000
claimable_outpoints. append ( & mut new_outpoints) ;
2974
3001
if let Some ( new_outputs) = new_outputs_option {
2975
3002
watch_outputs. push ( new_outputs) ;
2976
3003
}
3004
+ // Since there may be multiple HTLCs (all from the same commitment) being
3005
+ // claimed by the counterparty within the same transaction, and
3006
+ // `check_spend_counterparty_htlc` already checks for all of them, we can
3007
+ // safely break from our loop.
3008
+ break ;
2977
3009
}
2978
3010
}
2979
- }
2980
- // While all commitment/HTLC-Success/HTLC-Timeout transactions have one input, HTLCs
2981
- // can also be resolved in a few other ways which can have more than one output. Thus,
2982
- // we call is_resolving_htlc_output here outside of the tx.input.len() == 1 check.
2983
- self . is_resolving_htlc_output ( & tx, height, & block_hash, & logger) ;
3011
+ self . is_resolving_htlc_output ( & tx, height, & block_hash, & logger) ;
2984
3012
2985
- self . is_paying_spendable_output ( & tx, height, & block_hash, & logger) ;
3013
+ self . is_paying_spendable_output ( & tx, height, & block_hash, & logger) ;
3014
+ }
2986
3015
}
2987
3016
2988
3017
if height > self . best_block . height ( ) {
0 commit comments