@@ -2301,6 +2301,91 @@ fn test_splice_confirms_on_both_sides_while_disconnected() {
23012301 . remove_watched_txn_and_outputs ( prev_funding_outpoint, prev_funding_script) ;
23022302}
23032303
2304+ #[ test]
2305+ fn test_stale_announcement_signatures_ignored_after_splice_lock ( ) {
2306+ // Regression test: a peer may transmit `announcement_signatures` signed over a pre-splice
2307+ // `short_channel_id` (for example, a stale retransmission or a peer implementation that
2308+ // hasn't yet caught up to our post-splice promotion). Verifying those sigs against the
2309+ // post-splice `UnsignedChannelAnnouncement` will always fail the hash check, but that is not
2310+ // a protocol violation — the spec permits ignoring and the channel should stay open.
2311+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
2312+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
2313+ let mut config = test_default_channel_config ( ) ;
2314+ config. channel_handshake_config . announced_channel_max_inbound_htlc_value_in_flight_percentage =
2315+ 100 ;
2316+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , Some ( config) ] ) ;
2317+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
2318+
2319+ let node_id_0 = nodes[ 0 ] . node . get_our_node_id ( ) ;
2320+ let node_id_1 = nodes[ 1 ] . node . get_our_node_id ( ) ;
2321+
2322+ let initial_channel_value_sat = 100_000 ;
2323+ // Use the lower-level helper so we get the signed `ChannelAnnouncement` back — the test
2324+ // needs node 1's pre-splice announcement signatures to replay later.
2325+ let chan_announcement =
2326+ create_chan_between_nodes_with_value ( & nodes[ 0 ] , & nodes[ 1 ] , initial_channel_value_sat, 0 ) ;
2327+ let channel_id = chan_announcement. 3 ;
2328+ update_nodes_with_chan_announce (
2329+ & nodes,
2330+ 0 ,
2331+ 1 ,
2332+ & chan_announcement. 0 ,
2333+ & chan_announcement. 1 ,
2334+ & chan_announcement. 2 ,
2335+ ) ;
2336+
2337+ // Extract node 1's pre-splice signatures from the ChannelAnnouncement. `UnsignedChannelAnnouncement`
2338+ // orders `node_id_1`/`node_id_2` by serialized pubkey; node 1's sigs are in slot 1 iff node 1's
2339+ // pubkey is lexicographically smaller.
2340+ let node_1_is_node_one = node_id_1. serialize ( ) < node_id_0. serialize ( ) ;
2341+ let ( stale_node_sig, stale_bitcoin_sig) = if node_1_is_node_one {
2342+ ( chan_announcement. 0 . node_signature_1 , chan_announcement. 0 . bitcoin_signature_1 )
2343+ } else {
2344+ ( chan_announcement. 0 . node_signature_2 , chan_announcement. 0 . bitcoin_signature_2 )
2345+ } ;
2346+
2347+ // Capture the pre-splice `short_channel_id` — this is the scid the stale sigs sign over.
2348+ let pre_splice_scid = nodes[ 0 ] . node . list_channels ( ) [ 0 ] . short_channel_id . unwrap ( ) ;
2349+
2350+ let outputs = vec ! [
2351+ TxOut {
2352+ value: Amount :: from_sat( initial_channel_value_sat / 4 ) ,
2353+ script_pubkey: nodes[ 0 ] . wallet_source. get_change_script( ) . unwrap( ) ,
2354+ } ,
2355+ TxOut {
2356+ value: Amount :: from_sat( initial_channel_value_sat / 4 ) ,
2357+ script_pubkey: nodes[ 1 ] . wallet_source. get_change_script( ) . unwrap( ) ,
2358+ } ,
2359+ ] ;
2360+ let funding_contribution =
2361+ initiate_splice_out ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, outputs) . unwrap ( ) ;
2362+ let ( splice_tx, _) = splice_channel ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, funding_contribution) ;
2363+ mine_transaction ( & nodes[ 0 ] , & splice_tx) ;
2364+ mine_transaction ( & nodes[ 1 ] , & splice_tx) ;
2365+ lock_splice_after_blocks ( & nodes[ 0 ] , & nodes[ 1 ] , ANTI_REORG_DELAY - 1 ) ;
2366+
2367+ // The post-splice scid is now different; confirm that.
2368+ let post_splice_scid = nodes[ 0 ] . node . list_channels ( ) [ 0 ] . short_channel_id . unwrap ( ) ;
2369+ assert_ne ! ( pre_splice_scid, post_splice_scid) ;
2370+
2371+ // Replay node 1's pre-splice announcement signatures, now stale (the current scid is the
2372+ // post-splice one). This is the exact shape of message a peer would send if it retransmitted
2373+ // an old `announcement_signatures` across a splice handoff.
2374+ let stale_sigs = msgs:: AnnouncementSignatures {
2375+ channel_id,
2376+ short_channel_id : pre_splice_scid,
2377+ node_signature : stale_node_sig,
2378+ bitcoin_signature : stale_bitcoin_sig,
2379+ } ;
2380+ nodes[ 0 ] . node . handle_announcement_signatures ( node_id_1, & stale_sigs) ;
2381+
2382+ // No force-close, no outbound error, no events. The channel must still be listed and usable.
2383+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_events( ) . is_empty( ) ) ;
2384+ assert ! ( nodes[ 0 ] . node. get_and_clear_pending_msg_events( ) . is_empty( ) ) ;
2385+ assert_eq ! ( nodes[ 0 ] . node. list_channels( ) . len( ) , 1 ) ;
2386+ send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , 1_000_000 ) ;
2387+ }
2388+
23042389#[ test]
23052390fn test_propose_splice_while_disconnected ( ) {
23062391 do_test_propose_splice_while_disconnected ( false ) ;
0 commit comments