@@ -937,23 +937,58 @@ pub(crate) struct DecodedOnionFailure {
937937 pub ( crate ) onion_error_data : Option < Vec < u8 > > ,
938938}
939939
940+ pub ( super ) fn process_onion_failure < T : secp256k1:: Signing , L : Deref > (
941+ secp_ctx : & Secp256k1 < T > , logger : & L , htlc_source : & HTLCSource ,
942+ encrypted_packet : OnionErrorPacket ,
943+ ) -> DecodedOnionFailure
944+ where
945+ L :: Target : Logger ,
946+ {
947+ let ( path, primary_session_priv) = match htlc_source {
948+ HTLCSource :: OutboundRoute { ref path, ref session_priv, .. } => ( path, session_priv) ,
949+ _ => unreachable ! ( ) ,
950+ } ;
951+
952+ if path. has_trampoline_hops ( ) {
953+ // If we have Trampoline hops, the outer onion session_priv is a hash of the inner one.
954+ let session_priv_hash = Sha256 :: hash ( & primary_session_priv. secret_bytes ( ) ) . to_byte_array ( ) ;
955+ let outer_session_priv =
956+ SecretKey :: from_slice ( & session_priv_hash[ ..] ) . expect ( "You broke SHA-256!" ) ;
957+ process_onion_failure_inner (
958+ secp_ctx,
959+ logger,
960+ htlc_source,
961+ & outer_session_priv,
962+ Some ( primary_session_priv) ,
963+ encrypted_packet,
964+ )
965+ } else {
966+ process_onion_failure_inner (
967+ secp_ctx,
968+ logger,
969+ htlc_source,
970+ primary_session_priv,
971+ None ,
972+ encrypted_packet,
973+ )
974+ }
975+ }
976+
940977/// Process failure we got back from upstream on a payment we sent (implying htlc_source is an
941978/// OutboundRoute).
942979#[ inline]
943- pub ( super ) fn process_onion_failure < T : secp256k1:: Signing , L : Deref > (
944- secp_ctx : & Secp256k1 < T > , logger : & L , htlc_source : & HTLCSource ,
945- mut encrypted_packet : OnionErrorPacket ,
980+ pub ( super ) fn process_onion_failure_inner < T : secp256k1:: Signing , L : Deref > (
981+ secp_ctx : & Secp256k1 < T > , logger : & L , htlc_source : & HTLCSource , outer_session_priv : & SecretKey ,
982+ inner_session_priv : Option < & SecretKey > , mut encrypted_packet : OnionErrorPacket ,
946983) -> DecodedOnionFailure
947984where
948985 L :: Target : Logger ,
949986{
950- let ( path, session_priv, first_hop_htlc_msat) = match htlc_source {
951- HTLCSource :: OutboundRoute {
952- ref path, ref session_priv, ref first_hop_htlc_msat, ..
953- } => ( path, session_priv, first_hop_htlc_msat) ,
954- _ => {
955- unreachable ! ( )
987+ let ( path, first_hop_htlc_msat) = match htlc_source {
988+ HTLCSource :: OutboundRoute { ref path, ref first_hop_htlc_msat, .. } => {
989+ ( path, first_hop_htlc_msat)
956990 } ,
991+ _ => unreachable ! ( ) ,
957992 } ;
958993
959994 // Learnings from the HTLC failure to inform future payment retries and scoring.
@@ -1002,12 +1037,6 @@ where
10021037 }
10031038 }
10041039
1005- let outer_session_priv = path. has_trampoline_hops ( ) . then ( || {
1006- // if we have Trampoline hops, the outer onion session_priv is a hash of the inner one
1007- let session_priv_hash = Sha256 :: hash ( & session_priv. secret_bytes ( ) ) . to_byte_array ( ) ;
1008- SecretKey :: from_slice ( & session_priv_hash[ ..] ) . expect ( "You broke SHA-256!" )
1009- } ) ;
1010-
10111040 let num_blinded_hops = path. blinded_tail . as_ref ( ) . map_or ( 0 , |bt| bt. hops . len ( ) ) ;
10121041
10131042 // We are first collecting all the unblinded `RouteHop`s inside `onion_keys`. Then, if applicable,
@@ -1018,7 +1047,7 @@ where
10181047 & path. hops ,
10191048 // if we have Trampoline hops, the blinded hops are part of the inner Trampoline onion
10201049 if path. has_trampoline_hops ( ) { None } else { path. blinded_tail . as_ref ( ) } ,
1021- outer_session_priv. as_ref ( ) . unwrap_or ( session_priv ) ,
1050+ outer_session_priv,
10221051 |shared_secret, _, _, route_hop_option : Option < & RouteHop > , _| {
10231052 onion_keys. push ( ( route_hop_option. map ( |rh| ErrorHop :: RouteHop ( rh) ) , shared_secret) )
10241053 } ,
@@ -1031,7 +1060,7 @@ where
10311060 // Trampoline hops are part of the blinded tail, so this can never panic
10321061 & path. blinded_tail . as_ref ( ) . unwrap ( ) . trampoline_hops ,
10331062 path. blinded_tail . as_ref ( ) ,
1034- session_priv ,
1063+ inner_session_priv . expect ( "Trampoline hops always have an inner session priv" ) ,
10351064 |shared_secret, _, _, trampoline_hop_option : Option < & TrampolineHop > , _| {
10361065 onion_keys. push ( (
10371066 trampoline_hop_option. map ( |th| ErrorHop :: TrampolineHop ( th) ) ,
@@ -2039,11 +2068,11 @@ mod tests {
20392068 use crate :: prelude:: * ;
20402069 use crate :: util:: test_utils:: TestLogger ;
20412070
2071+ use super :: * ;
20422072 use bitcoin:: hex:: FromHex ;
20432073 use bitcoin:: secp256k1:: Secp256k1 ;
20442074 use bitcoin:: secp256k1:: { PublicKey , SecretKey } ;
2045-
2046- use super :: * ;
2075+ use types:: features:: Features ;
20472076
20482077 fn get_test_session_key ( ) -> SecretKey {
20492078 let hex = "4141414141414141414141414141414141414141414141414141414141414141" ;
@@ -2400,10 +2429,218 @@ mod tests {
24002429 // Assert that the original failure can be retrieved and that all hmacs check out.
24012430 let decrypted_failure =
24022431 process_onion_failure ( & ctx_full, & logger, & htlc_source, onion_error) ;
2403-
24042432 assert_eq ! ( decrypted_failure. onion_error_code, Some ( 0x2002 ) ) ;
24052433 }
24062434
2435+ fn build_trampoline_test_path ( ) -> Path {
2436+ Path {
2437+ hops : vec ! [
2438+ // Bob
2439+ RouteHop {
2440+ pubkey: PublicKey :: from_slice( & <Vec <u8 >>:: from_hex( "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c" ) . unwrap( ) ) . unwrap( ) ,
2441+ node_features: NodeFeatures :: empty( ) ,
2442+ short_channel_id: 0 ,
2443+ channel_features: ChannelFeatures :: empty( ) ,
2444+ fee_msat: 3_000 ,
2445+ cltv_expiry_delta: 24 ,
2446+ maybe_announced_channel: false ,
2447+ } ,
2448+
2449+ // Carol
2450+ RouteHop {
2451+ pubkey: PublicKey :: from_slice( & <Vec <u8 >>:: from_hex( "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007" ) . unwrap( ) ) . unwrap( ) ,
2452+ node_features: NodeFeatures :: empty( ) ,
2453+ short_channel_id: ( 572330 << 40 ) + ( 42 << 16 ) + 2821 ,
2454+ channel_features: ChannelFeatures :: empty( ) ,
2455+ fee_msat: 153_000 ,
2456+ cltv_expiry_delta: 0 ,
2457+ maybe_announced_channel: false ,
2458+ } ,
2459+ ] ,
2460+ blinded_tail : Some ( BlindedTail {
2461+ trampoline_hops : vec ! [
2462+ // Carol's pubkey
2463+ TrampolineHop {
2464+ pubkey: PublicKey :: from_slice( & <Vec <u8 >>:: from_hex( "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007" ) . unwrap( ) ) . unwrap( ) ,
2465+ node_features: Features :: empty( ) ,
2466+ fee_msat: 2_500 ,
2467+ cltv_expiry_delta: 24 ,
2468+ } ,
2469+
2470+ // Dave's pubkey
2471+ TrampolineHop {
2472+ pubkey: PublicKey :: from_slice( & <Vec <u8 >>:: from_hex( "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145" ) . unwrap( ) ) . unwrap( ) ,
2473+ node_features: Features :: empty( ) ,
2474+ fee_msat: 2_500 ,
2475+ cltv_expiry_delta: 24 ,
2476+ } ,
2477+
2478+ // Emily's pubkey
2479+ TrampolineHop {
2480+ pubkey: PublicKey :: from_slice( & <Vec <u8 >>:: from_hex( "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991" ) . unwrap( ) ) . unwrap( ) ,
2481+ node_features: Features :: empty( ) ,
2482+ fee_msat: 150_500 ,
2483+ cltv_expiry_delta: 36 ,
2484+ } ,
2485+ ] ,
2486+
2487+ // Dummy blinded hop (because LDK doesn't allow unblinded Trampoline receives)
2488+ hops : vec ! [
2489+ // Emily's dummy blinded node id
2490+ BlindedHop {
2491+ blinded_node_id: PublicKey :: from_slice( & <Vec <u8 >>:: from_hex( "0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be" ) . unwrap( ) ) . unwrap( ) ,
2492+ encrypted_payload: vec![ ] ,
2493+ }
2494+ ] ,
2495+ blinding_point : PublicKey :: from_slice ( & <Vec < u8 > >:: from_hex ( "02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e" ) . unwrap ( ) ) . unwrap ( ) ,
2496+ excess_final_cltv_expiry_delta : 0 ,
2497+ final_value_msat : 150_000_000 ,
2498+ } ) ,
2499+ }
2500+ }
2501+
2502+ #[ test]
2503+ fn test_trampoline_onion_error_cryptography ( ) {
2504+ // TODO(arik): check intermediate hops' perspectives once we have implemented forwarding
2505+
2506+ let secp_ctx = Secp256k1 :: new ( ) ;
2507+ let logger: Arc < TestLogger > = Arc :: new ( TestLogger :: new ( ) ) ;
2508+ let dummy_amt_msat = 150_000_000 ;
2509+
2510+ {
2511+ // test vector per https://github.com/lightning/bolts/blob/079f761bf68caa48544bd6bf0a29591d43425b0b/bolt04/trampoline-onion-error-test.json
2512+ // all dummy values
2513+ let trampoline_session_priv = SecretKey :: from_slice ( & [ 3 ; 32 ] ) . unwrap ( ) ;
2514+ let outer_session_priv = SecretKey :: from_slice ( & [ 4 ; 32 ] ) . unwrap ( ) ;
2515+
2516+ let htlc_source = HTLCSource :: OutboundRoute {
2517+ path : build_trampoline_test_path ( ) ,
2518+ session_priv : trampoline_session_priv,
2519+ first_hop_htlc_msat : dummy_amt_msat,
2520+ payment_id : PaymentId ( [ 1 ; 32 ] ) ,
2521+ } ;
2522+
2523+ let error_packet_hex = "f8941a320b8fde4ad7b9b920c69cbf334114737497d93059d77e591eaa78d6334d3e2aeefcb0cc83402eaaf91d07d695cd895d9cad1018abdaf7d2a49d7657b1612729db7f393f0bb62b25afaaaa326d72a9214666025385033f2ec4605dcf1507467b5726d806da180ea224a7d8631cd31b0bdd08eead8bfe14fc8c7475e17768b1321b54dd4294aecc96da391efe0ca5bd267a45ee085c85a60cf9a9ac152fa4795fff8700a3ea4f848817f5e6943e855ab2e86f6929c9e885d8b20c49b14d2512c59ed21f10bd38691110b0d82c00d9fa48a20f10c7550358724c6e8e2b966e56a0aadf458695b273768062fa7c6e60eb72d4cdc67bf525c194e4a17fdcaa0e9d80480b586bf113f14eea530b6728a1c53fe5cee092e24a90f21f4b764015e7ed5e23" ;
2524+ let error_packet =
2525+ OnionErrorPacket { data : <Vec < u8 > >:: from_hex ( error_packet_hex) . unwrap ( ) } ;
2526+ let decrypted_failure = process_onion_failure_inner (
2527+ & secp_ctx,
2528+ & logger,
2529+ & htlc_source,
2530+ & outer_session_priv,
2531+ Some ( & trampoline_session_priv) ,
2532+ error_packet,
2533+ ) ;
2534+ assert_eq ! ( decrypted_failure. onion_error_code, Some ( 0x400f ) ) ;
2535+ }
2536+
2537+ {
2538+ // shared secret cryptography sanity tests
2539+ let session_priv = get_test_session_key ( ) ;
2540+ let path = build_trampoline_test_path ( ) ;
2541+
2542+ let trampoline_onion_keys = construct_trampoline_onion_keys (
2543+ & secp_ctx,
2544+ & path. blinded_tail . as_ref ( ) . unwrap ( ) ,
2545+ & session_priv,
2546+ )
2547+ . unwrap ( ) ;
2548+
2549+ let outer_onion_keys = {
2550+ let session_priv_hash = Sha256 :: hash ( & session_priv. secret_bytes ( ) ) . to_byte_array ( ) ;
2551+ let outer_session_priv = SecretKey :: from_slice ( & session_priv_hash[ ..] ) . unwrap ( ) ;
2552+ construct_onion_keys ( & Secp256k1 :: new ( ) , & path, & outer_session_priv) . unwrap ( )
2553+ } ;
2554+
2555+ let htlc_source = HTLCSource :: OutboundRoute {
2556+ path,
2557+ session_priv,
2558+ first_hop_htlc_msat : dummy_amt_msat,
2559+ payment_id : PaymentId ( [ 1 ; 32 ] ) ,
2560+ } ;
2561+
2562+ {
2563+ // Ensure error decryption works without the Trampoline hops having been hit.
2564+ let error_code = 0x2002 ;
2565+ let mut first_hop_error_packet = build_unencrypted_failure_packet (
2566+ outer_onion_keys[ 0 ] . shared_secret . as_ref ( ) ,
2567+ error_code,
2568+ & [ 0 ; 0 ] ,
2569+ ) ;
2570+
2571+ crypt_failure_packet (
2572+ outer_onion_keys[ 0 ] . shared_secret . as_ref ( ) ,
2573+ & mut first_hop_error_packet,
2574+ ) ;
2575+
2576+ let decrypted_failure =
2577+ process_onion_failure ( & secp_ctx, & logger, & htlc_source, first_hop_error_packet) ;
2578+ assert_eq ! ( decrypted_failure. onion_error_code, Some ( error_code) ) ;
2579+ } ;
2580+
2581+ {
2582+ // Ensure error decryption works from the first Trampoline hop, but at the outer onion.
2583+ let error_code = 0x2003 ;
2584+ let mut trampoline_outer_hop_error_packet = build_unencrypted_failure_packet (
2585+ outer_onion_keys[ 1 ] . shared_secret . as_ref ( ) ,
2586+ error_code,
2587+ & [ 0 ; 0 ] ,
2588+ ) ;
2589+
2590+ crypt_failure_packet (
2591+ outer_onion_keys[ 1 ] . shared_secret . as_ref ( ) ,
2592+ & mut trampoline_outer_hop_error_packet,
2593+ ) ;
2594+
2595+ crypt_failure_packet (
2596+ outer_onion_keys[ 0 ] . shared_secret . as_ref ( ) ,
2597+ & mut trampoline_outer_hop_error_packet,
2598+ ) ;
2599+
2600+ let decrypted_failure = process_onion_failure (
2601+ & secp_ctx,
2602+ & logger,
2603+ & htlc_source,
2604+ trampoline_outer_hop_error_packet,
2605+ ) ;
2606+ assert_eq ! ( decrypted_failure. onion_error_code, Some ( error_code) ) ;
2607+ } ;
2608+
2609+ {
2610+ // Ensure error decryption works from the Trampoline inner onion.
2611+ let error_code = 0x2004 ;
2612+ let mut trampoline_inner_hop_error_packet = build_unencrypted_failure_packet (
2613+ trampoline_onion_keys[ 0 ] . shared_secret . as_ref ( ) ,
2614+ error_code,
2615+ & [ 0 ; 0 ] ,
2616+ ) ;
2617+
2618+ crypt_failure_packet (
2619+ trampoline_onion_keys[ 0 ] . shared_secret . as_ref ( ) ,
2620+ & mut trampoline_inner_hop_error_packet,
2621+ ) ;
2622+
2623+ crypt_failure_packet (
2624+ outer_onion_keys[ 1 ] . shared_secret . as_ref ( ) ,
2625+ & mut trampoline_inner_hop_error_packet,
2626+ ) ;
2627+
2628+ crypt_failure_packet (
2629+ outer_onion_keys[ 0 ] . shared_secret . as_ref ( ) ,
2630+ & mut trampoline_inner_hop_error_packet,
2631+ ) ;
2632+
2633+ let decrypted_failure = process_onion_failure (
2634+ & secp_ctx,
2635+ & logger,
2636+ & htlc_source,
2637+ trampoline_inner_hop_error_packet,
2638+ ) ;
2639+ assert_eq ! ( decrypted_failure. onion_error_code, Some ( error_code) ) ;
2640+ }
2641+ }
2642+ }
2643+
24072644 #[ test]
24082645 fn test_non_attributable_failure_packet_onion ( ) {
24092646 // Create a failure packet with bogus data.
0 commit comments