@@ -2697,3 +2697,189 @@ fn test_unblinded_trampoline_forward() {
26972697 do_test_unblinded_trampoline_forward ( true ) ;
26982698 do_test_unblinded_trampoline_forward ( false ) ;
26992699}
2700+
2701+ #[ test]
2702+ fn test_blinded_trampoline_forward ( ) {
2703+ // Simulate a payment of A (0) -> B (1) -> C(Trampoline (blinded forward)) (2) -> D(Trampoline(blinded receive)) (3)
2704+ // trampoline hops C -> T0 (4) -> D
2705+ // make it fail at B, then at C's outer onion, then at C's inner onion
2706+ const TOTAL_NODE_COUNT : usize = 5 ;
2707+ let secp_ctx = Secp256k1 :: new ( ) ;
2708+
2709+ let chanmon_cfgs = create_chanmon_cfgs ( TOTAL_NODE_COUNT ) ;
2710+ let node_cfgs = create_node_cfgs ( TOTAL_NODE_COUNT , & chanmon_cfgs) ;
2711+ let node_chanmgrs = create_node_chanmgrs ( TOTAL_NODE_COUNT , & node_cfgs, & vec ! [ None ; TOTAL_NODE_COUNT ] ) ;
2712+ let mut nodes = create_network ( TOTAL_NODE_COUNT , & node_cfgs, & node_chanmgrs) ;
2713+
2714+ let ( _, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1_000_000 , 0 ) ;
2715+ let ( _, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value ( & nodes, 1 , 2 , 1_000_000 , 0 ) ;
2716+ let ( _, _, _, _) = create_announced_chan_between_nodes_with_value ( & nodes, 2 , 4 , 1_000_000 , 0 ) ;
2717+ let ( _, _, _, _) = create_announced_chan_between_nodes_with_value ( & nodes, 4 , 3 , 1_000_000 , 0 ) ;
2718+
2719+ for i in 0 ..TOTAL_NODE_COUNT { // connect all nodes' blocks
2720+ connect_blocks ( & nodes[ i] , ( TOTAL_NODE_COUNT as u32 ) * CHAN_CONFIRM_DEPTH + 1 - nodes[ i] . best_block_info ( ) . 1 ) ;
2721+ }
2722+
2723+ let alice_node_id = nodes[ 0 ] . node ( ) . get_our_node_id ( ) ;
2724+ let bob_node_id = nodes[ 1 ] . node ( ) . get_our_node_id ( ) ;
2725+ let carol_node_id = nodes[ 2 ] . node ( ) . get_our_node_id ( ) ;
2726+ let dave_node_id = nodes[ 3 ] . node ( ) . get_our_node_id ( ) ;
2727+
2728+ let alice_bob_scid = nodes[ 0 ] . node ( ) . list_channels ( ) . iter ( ) . find ( |c| c. channel_id == chan_id_alice_bob) . unwrap ( ) . short_channel_id . unwrap ( ) ;
2729+ let bob_carol_scid = nodes[ 1 ] . node ( ) . list_channels ( ) . iter ( ) . find ( |c| c. channel_id == chan_id_bob_carol) . unwrap ( ) . short_channel_id . unwrap ( ) ;
2730+
2731+ let amt_msat = 1000 ;
2732+ let ( payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash ( & nodes[ 3 ] , Some ( amt_msat) , None ) ;
2733+
2734+ let alice_carol_trampoline_shared_secret = secret_from_hex ( "a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03" ) ;
2735+ let carol_blinding_point = PublicKey :: from_secret_key ( & secp_ctx, & alice_carol_trampoline_shared_secret) ;
2736+
2737+ let forwarding_tlvs = blinded_path:: payment:: TrampolineForwardTlvs {
2738+ next_trampoline : dave_node_id,
2739+ payment_relay : PaymentRelay {
2740+ cltv_expiry_delta : 224 ,
2741+ fee_proportional_millionths : 0 ,
2742+ fee_base_msat : 2000 ,
2743+ } ,
2744+ payment_constraints : PaymentConstraints {
2745+ max_cltv_expiry : u32:: max_value ( ) ,
2746+ htlc_minimum_msat : amt_msat
2747+ } ,
2748+ features : BlindedHopFeatures :: empty ( ) ,
2749+ next_blinding_override : None ,
2750+ } ;
2751+ let carol_unblinded_tlvs = forwarding_tlvs. encode ( ) ;
2752+
2753+ let payee_tlvs = UnauthenticatedReceiveTlvs {
2754+ payment_secret,
2755+ payment_constraints : PaymentConstraints {
2756+ max_cltv_expiry : u32:: max_value ( ) ,
2757+ htlc_minimum_msat : amt_msat,
2758+ } ,
2759+ payment_context : PaymentContext :: Bolt12Refund ( Bolt12RefundContext { } ) ,
2760+ } ;
2761+
2762+ let nonce = Nonce ( [ 42u8 ; 16 ] ) ;
2763+ let expanded_key = nodes[ 3 ] . keys_manager . get_inbound_payment_key ( ) ;
2764+ let payee_tlvs = payee_tlvs. authenticate ( nonce, & expanded_key) ;
2765+ let dave_unblinded_tlvs = payee_tlvs. encode ( ) ;
2766+
2767+ let path = [ ( carol_node_id, WithoutLength ( & carol_unblinded_tlvs) ) , ( dave_node_id, WithoutLength ( & dave_unblinded_tlvs) ) ] ;
2768+ let blinded_hops = blinded_path:: utils:: construct_blinded_hops (
2769+ & secp_ctx, path. into_iter ( ) , & alice_carol_trampoline_shared_secret,
2770+ ) . unwrap ( ) ;
2771+
2772+ let route = Route {
2773+ paths : vec ! [ Path {
2774+ hops: vec![
2775+ // Bob
2776+ RouteHop {
2777+ pubkey: bob_node_id,
2778+ node_features: NodeFeatures :: empty( ) ,
2779+ short_channel_id: alice_bob_scid,
2780+ channel_features: ChannelFeatures :: empty( ) ,
2781+ fee_msat: 1000 , // forwarding fee to Carol
2782+ cltv_expiry_delta: 48 ,
2783+ maybe_announced_channel: false ,
2784+ } ,
2785+
2786+ // Carol
2787+ RouteHop {
2788+ pubkey: carol_node_id,
2789+ node_features: NodeFeatures :: empty( ) ,
2790+ short_channel_id: bob_carol_scid,
2791+ channel_features: ChannelFeatures :: empty( ) ,
2792+ fee_msat: 2000 , // fee for the usage of the entire blinded path, including Trampoline
2793+ cltv_expiry_delta: 48 ,
2794+ maybe_announced_channel: false ,
2795+ }
2796+ ] ,
2797+ blinded_tail: Some ( BlindedTail {
2798+ trampoline_hops: vec![
2799+ // Carol
2800+ TrampolineHop {
2801+ pubkey: carol_node_id,
2802+ node_features: Features :: empty( ) ,
2803+ fee_msat: amt_msat,
2804+ cltv_expiry_delta: 176 , // let her cook
2805+ } ,
2806+ ] ,
2807+ hops: blinded_hops,
2808+ blinding_point: carol_blinding_point,
2809+ excess_final_cltv_expiry_delta: 39 ,
2810+ final_value_msat: amt_msat,
2811+ } )
2812+ } ] ,
2813+ route_params : None ,
2814+ } ;
2815+
2816+ nodes[ 0 ] . node . send_payment_with_route ( route. clone ( ) , payment_hash, RecipientOnionFields :: spontaneous_empty ( ) , PaymentId ( payment_hash. 0 ) ) . unwrap ( ) ;
2817+
2818+ let replacement_onion = {
2819+ // create a substitute onion where the last Trampoline hop is an unblinded receive, which we
2820+ // (deliberately) do not support out of the box, therefore necessitating this workaround
2821+ let trampoline_secret_key = secret_from_hex ( "0134928f7b7ca6769080d70f16be84c812c741f545b49a34db47ce338a205799" ) ;
2822+ let prng_seed = secret_from_hex ( "fe02b4b9054302a3ddf4e1e9f7c411d644aebbd295218ab009dca94435f775a9" ) ;
2823+ let recipient_onion_fields = RecipientOnionFields :: spontaneous_empty ( ) ;
2824+
2825+ let blinded_tail = route. paths [ 0 ] . blinded_tail . clone ( ) . unwrap ( ) ;
2826+ let ( mut trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = onion_utils:: build_trampoline_onion_payloads ( & blinded_tail, amt_msat, & recipient_onion_fields, 32 , & None ) . unwrap ( ) ;
2827+
2828+ // pop the last dummy hop
2829+ trampoline_payloads. pop ( ) ;
2830+
2831+ trampoline_payloads. push ( msgs:: OutboundTrampolinePayload :: Receive {
2832+ payment_data : Some ( msgs:: FinalOnionHopData {
2833+ payment_secret,
2834+ total_msat : amt_msat,
2835+ } ) ,
2836+ sender_intended_htlc_amt_msat : amt_msat,
2837+ cltv_expiry_height : 96 ,
2838+ } ) ;
2839+
2840+ let trampoline_onion_keys = onion_utils:: construct_trampoline_onion_keys ( & secp_ctx, & route. paths [ 0 ] . blinded_tail . as_ref ( ) . unwrap ( ) , & trampoline_secret_key) . unwrap ( ) ;
2841+ let trampoline_packet = onion_utils:: construct_trampoline_onion_packet (
2842+ trampoline_payloads,
2843+ trampoline_onion_keys,
2844+ prng_seed. secret_bytes ( ) ,
2845+ & payment_hash,
2846+ None ,
2847+ ) . unwrap ( ) ;
2848+
2849+ let outer_session_priv = secret_from_hex ( "e52c20461ed7acd46c4e7b591a37610519179482887bd73bf3b94617f8f03677" ) ;
2850+
2851+ let ( outer_payloads, _, _) = onion_utils:: build_onion_payloads ( & route. paths [ 0 ] , outer_total_msat, & recipient_onion_fields, outer_starting_htlc_offset, & None , None , Some ( trampoline_packet) ) . unwrap ( ) ;
2852+ let outer_onion_keys = onion_utils:: construct_onion_keys ( & secp_ctx, & route. clone ( ) . paths [ 0 ] , & outer_session_priv) . unwrap ( ) ;
2853+ let outer_packet = onion_utils:: construct_onion_packet (
2854+ outer_payloads,
2855+ outer_onion_keys,
2856+ prng_seed. secret_bytes ( ) ,
2857+ & payment_hash,
2858+ ) . unwrap ( ) ;
2859+
2860+ outer_packet
2861+ } ;
2862+
2863+ check_added_monitors ! ( & nodes[ 0 ] , 1 ) ;
2864+
2865+ let mut events = nodes[ 0 ] . node . get_and_clear_pending_msg_events ( ) ;
2866+ assert_eq ! ( events. len( ) , 1 ) ;
2867+ let mut first_message_event = remove_first_msg_event_to_node ( & nodes[ 1 ] . node . get_our_node_id ( ) , & mut events) ;
2868+ let mut update_message = match first_message_event {
2869+ MessageSendEvent :: UpdateHTLCs { ref mut updates, .. } => {
2870+ assert_eq ! ( updates. update_add_htlcs. len( ) , 1 ) ;
2871+ updates. update_add_htlcs . get_mut ( 0 )
2872+ } ,
2873+ _ => panic ! ( )
2874+ } ;
2875+ update_message. map ( |msg| {
2876+ // don't replace the onion, just let it forward in peace
2877+ // msg.onion_routing_packet = replacement_onion.clone();
2878+ } ) ;
2879+
2880+ let route: & [ & Node ] = & [ & nodes[ 1 ] , & nodes[ 2 ] , & nodes[ 4 ] , & nodes[ 3 ] ] ;
2881+ let args = PassAlongPathArgs :: new ( & nodes[ 0 ] , route, amt_msat, payment_hash, first_message_event)
2882+ . with_payment_secret ( payment_secret) ;
2883+ do_pass_along_path ( args) ;
2884+ claim_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] , & nodes[ 2 ] , & nodes[ 4 ] , & nodes[ 3 ] ] , payment_preimage) ;
2885+ }
0 commit comments