@@ -937,23 +937,54 @@ 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+ let ( outer_session_priv, inner_session_priv) = 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+ (
956+ & SecretKey :: from_slice ( & session_priv_hash[ ..] ) . expect ( "You broke SHA-256!" ) ,
957+ Some ( primary_session_priv) ,
958+ )
959+ } else {
960+ ( primary_session_priv, None )
961+ } ;
962+
963+ process_onion_failure_inner (
964+ secp_ctx,
965+ logger,
966+ htlc_source,
967+ outer_session_priv,
968+ inner_session_priv,
969+ encrypted_packet,
970+ )
971+ }
972+
940973/// Process failure we got back from upstream on a payment we sent (implying htlc_source is an
941974/// OutboundRoute).
942975#[ 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 ,
976+ pub ( super ) fn process_onion_failure_inner < T : secp256k1:: Signing , L : Deref > (
977+ secp_ctx : & Secp256k1 < T > , logger : & L , htlc_source : & HTLCSource , outer_session_priv : & SecretKey ,
978+ inner_session_priv : Option < & SecretKey > , mut encrypted_packet : OnionErrorPacket ,
946979) -> DecodedOnionFailure
947980where
948981 L :: Target : Logger ,
949982{
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 ! ( )
983+ let ( path, first_hop_htlc_msat) = match htlc_source {
984+ HTLCSource :: OutboundRoute { ref path, ref first_hop_htlc_msat, .. } => {
985+ ( path, first_hop_htlc_msat)
956986 } ,
987+ _ => unreachable ! ( ) ,
957988 } ;
958989
959990 // Learnings from the HTLC failure to inform future payment retries and scoring.
@@ -1002,12 +1033,6 @@ where
10021033 }
10031034 }
10041035
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-
10111036 let num_blinded_hops = path. blinded_tail . as_ref ( ) . map_or ( 0 , |bt| bt. hops . len ( ) ) ;
10121037
10131038 // We are first collecting all the unblinded `RouteHop`s inside `onion_keys`. Then, if applicable,
@@ -1018,7 +1043,7 @@ where
10181043 & path. hops ,
10191044 // if we have Trampoline hops, the blinded hops are part of the inner Trampoline onion
10201045 if path. has_trampoline_hops ( ) { None } else { path. blinded_tail . as_ref ( ) } ,
1021- outer_session_priv. as_ref ( ) . unwrap_or ( session_priv ) ,
1046+ outer_session_priv,
10221047 |shared_secret, _, _, route_hop_option : Option < & RouteHop > , _| {
10231048 onion_keys. push ( ( route_hop_option. map ( |rh| ErrorHop :: RouteHop ( rh) ) , shared_secret) )
10241049 } ,
@@ -1031,7 +1056,7 @@ where
10311056 // Trampoline hops are part of the blinded tail, so this can never panic
10321057 & path. blinded_tail . as_ref ( ) . unwrap ( ) . trampoline_hops ,
10331058 path. blinded_tail . as_ref ( ) ,
1034- session_priv ,
1059+ inner_session_priv . expect ( "Trampoline hops always have an inner session priv" ) ,
10351060 |shared_secret, _, _, trampoline_hop_option : Option < & TrampolineHop > , _| {
10361061 onion_keys. push ( (
10371062 trampoline_hop_option. map ( |th| ErrorHop :: TrampolineHop ( th) ) ,
@@ -2039,11 +2064,11 @@ mod tests {
20392064 use crate :: prelude:: * ;
20402065 use crate :: util:: test_utils:: TestLogger ;
20412066
2067+ use super :: * ;
20422068 use bitcoin:: hex:: FromHex ;
20432069 use bitcoin:: secp256k1:: Secp256k1 ;
20442070 use bitcoin:: secp256k1:: { PublicKey , SecretKey } ;
2045-
2046- use super :: * ;
2071+ use types:: features:: Features ;
20472072
20482073 fn get_test_session_key ( ) -> SecretKey {
20492074 let hex = "4141414141414141414141414141414141414141414141414141414141414141" ;
@@ -2400,10 +2425,215 @@ mod tests {
24002425 // Assert that the original failure can be retrieved and that all hmacs check out.
24012426 let decrypted_failure =
24022427 process_onion_failure ( & ctx_full, & logger, & htlc_source, onion_error) ;
2403-
24042428 assert_eq ! ( decrypted_failure. onion_error_code, Some ( 0x2002 ) ) ;
24052429 }
24062430
2431+ fn build_trampoline_test_path ( ) -> Path {
2432+ Path {
2433+ hops : vec ! [
2434+ // Bob
2435+ RouteHop {
2436+ pubkey: PublicKey :: from_slice( & <Vec <u8 >>:: from_hex( "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c" ) . unwrap( ) ) . unwrap( ) ,
2437+ node_features: NodeFeatures :: empty( ) ,
2438+ short_channel_id: 0 ,
2439+ channel_features: ChannelFeatures :: empty( ) ,
2440+ fee_msat: 3_000 ,
2441+ cltv_expiry_delta: 24 ,
2442+ maybe_announced_channel: false ,
2443+ } ,
2444+
2445+ // Carol
2446+ RouteHop {
2447+ pubkey: PublicKey :: from_slice( & <Vec <u8 >>:: from_hex( "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007" ) . unwrap( ) ) . unwrap( ) ,
2448+ node_features: NodeFeatures :: empty( ) ,
2449+ short_channel_id: ( 572330 << 40 ) + ( 42 << 16 ) + 2821 ,
2450+ channel_features: ChannelFeatures :: empty( ) ,
2451+ fee_msat: 153_000 ,
2452+ cltv_expiry_delta: 0 ,
2453+ maybe_announced_channel: false ,
2454+ } ,
2455+ ] ,
2456+ blinded_tail : Some ( BlindedTail {
2457+ trampoline_hops : vec ! [
2458+ // Carol's pubkey
2459+ TrampolineHop {
2460+ pubkey: PublicKey :: from_slice( & <Vec <u8 >>:: from_hex( "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007" ) . unwrap( ) ) . unwrap( ) ,
2461+ node_features: Features :: empty( ) ,
2462+ fee_msat: 2_500 ,
2463+ cltv_expiry_delta: 24 ,
2464+ } ,
2465+
2466+ // Dave's pubkey
2467+ TrampolineHop {
2468+ pubkey: PublicKey :: from_slice( & <Vec <u8 >>:: from_hex( "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145" ) . unwrap( ) ) . unwrap( ) ,
2469+ node_features: Features :: empty( ) ,
2470+ fee_msat: 2_500 ,
2471+ cltv_expiry_delta: 24 ,
2472+ } ,
2473+
2474+ // Emily's pubkey
2475+ TrampolineHop {
2476+ pubkey: PublicKey :: from_slice( & <Vec <u8 >>:: from_hex( "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991" ) . unwrap( ) ) . unwrap( ) ,
2477+ node_features: Features :: empty( ) ,
2478+ fee_msat: 150_500 ,
2479+ cltv_expiry_delta: 36 ,
2480+ } ,
2481+ ] ,
2482+
2483+ // Dummy blinded hop (because LDK doesn't allow unblinded Trampoline receives)
2484+ hops : vec ! [
2485+ // Emily's dummy blinded node id
2486+ BlindedHop {
2487+ blinded_node_id: PublicKey :: from_slice( & <Vec <u8 >>:: from_hex( "0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be" ) . unwrap( ) ) . unwrap( ) ,
2488+ encrypted_payload: vec![ ] ,
2489+ }
2490+ ] ,
2491+ blinding_point : PublicKey :: from_slice ( & <Vec < u8 > >:: from_hex ( "02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e" ) . unwrap ( ) ) . unwrap ( ) ,
2492+ excess_final_cltv_expiry_delta : 0 ,
2493+ final_value_msat : 150_000_000 ,
2494+ } ) ,
2495+ }
2496+ }
2497+
2498+ #[ test]
2499+ fn test_trampoline_onion_error_cryptography ( ) {
2500+ // TODO(arik): check intermediate hops' perspectives once we have implemented forwarding
2501+
2502+ let secp_ctx = Secp256k1 :: new ( ) ;
2503+ let logger: Arc < TestLogger > = Arc :: new ( TestLogger :: new ( ) ) ;
2504+ let dummy_amt_msat = 150_000_000 ;
2505+
2506+ {
2507+ // test vector per https://github.com/lightning/bolts/blob/079f761bf68caa48544bd6bf0a29591d43425b0b/bolt04/trampoline-onion-error-test.json
2508+ // all dummy values
2509+ let trampoline_session_priv = SecretKey :: from_slice ( & [ 3 ; 32 ] ) . unwrap ( ) ;
2510+ let outer_session_priv = SecretKey :: from_slice ( & [ 4 ; 32 ] ) . unwrap ( ) ;
2511+
2512+ let htlc_source = HTLCSource :: OutboundRoute {
2513+ path : build_trampoline_test_path ( ) ,
2514+ session_priv : trampoline_session_priv,
2515+ first_hop_htlc_msat : dummy_amt_msat,
2516+ payment_id : PaymentId ( [ 1 ; 32 ] ) ,
2517+ } ;
2518+
2519+ let error_packet_hex = "f8941a320b8fde4ad7b9b920c69cbf334114737497d93059d77e591eaa78d6334d3e2aeefcb0cc83402eaaf91d07d695cd895d9cad1018abdaf7d2a49d7657b1612729db7f393f0bb62b25afaaaa326d72a9214666025385033f2ec4605dcf1507467b5726d806da180ea224a7d8631cd31b0bdd08eead8bfe14fc8c7475e17768b1321b54dd4294aecc96da391efe0ca5bd267a45ee085c85a60cf9a9ac152fa4795fff8700a3ea4f848817f5e6943e855ab2e86f6929c9e885d8b20c49b14d2512c59ed21f10bd38691110b0d82c00d9fa48a20f10c7550358724c6e8e2b966e56a0aadf458695b273768062fa7c6e60eb72d4cdc67bf525c194e4a17fdcaa0e9d80480b586bf113f14eea530b6728a1c53fe5cee092e24a90f21f4b764015e7ed5e23" ;
2520+ let error_packet =
2521+ OnionErrorPacket { data : <Vec < u8 > >:: from_hex ( error_packet_hex) . unwrap ( ) } ;
2522+ let decrypted_failure = process_onion_failure_inner (
2523+ & secp_ctx,
2524+ & logger,
2525+ & htlc_source,
2526+ & outer_session_priv,
2527+ Some ( & trampoline_session_priv) ,
2528+ error_packet,
2529+ ) ;
2530+ assert_eq ! ( decrypted_failure. onion_error_code, Some ( 0x400f ) ) ;
2531+ }
2532+
2533+ {
2534+ // shared secret cryptography sanity tests
2535+ let session_priv = get_test_session_key ( ) ;
2536+ let path = build_trampoline_test_path ( ) ;
2537+
2538+ let trampoline_onion_keys = construct_trampoline_onion_keys (
2539+ & secp_ctx,
2540+ & path. blinded_tail . as_ref ( ) . unwrap ( ) ,
2541+ & session_priv,
2542+ )
2543+ . unwrap ( ) ;
2544+
2545+ let outer_onion_keys = {
2546+ let session_priv_hash = Sha256 :: hash ( & session_priv. secret_bytes ( ) ) . to_byte_array ( ) ;
2547+ let outer_session_priv = SecretKey :: from_slice ( & session_priv_hash[ ..] ) . unwrap ( ) ;
2548+ construct_onion_keys ( & Secp256k1 :: new ( ) , & path, & outer_session_priv) . unwrap ( )
2549+ } ;
2550+
2551+ let htlc_source = HTLCSource :: OutboundRoute {
2552+ path,
2553+ session_priv,
2554+ first_hop_htlc_msat : dummy_amt_msat,
2555+ payment_id : PaymentId ( [ 1 ; 32 ] ) ,
2556+ } ;
2557+
2558+ {
2559+ let error_code = 0x2002 ;
2560+ let mut first_hop_error_packet = build_unencrypted_failure_packet (
2561+ outer_onion_keys[ 0 ] . shared_secret . as_ref ( ) ,
2562+ error_code,
2563+ & [ 0 ; 0 ] ,
2564+ ) ;
2565+
2566+ crypt_failure_packet (
2567+ outer_onion_keys[ 0 ] . shared_secret . as_ref ( ) ,
2568+ & mut first_hop_error_packet,
2569+ ) ;
2570+
2571+ let decrypted_failure =
2572+ process_onion_failure ( & secp_ctx, & logger, & htlc_source, first_hop_error_packet) ;
2573+ assert_eq ! ( decrypted_failure. onion_error_code, Some ( error_code) ) ;
2574+ } ;
2575+
2576+ {
2577+ let error_code = 0x2003 ;
2578+ let mut trampoline_outer_hop_error_packet = build_unencrypted_failure_packet (
2579+ outer_onion_keys[ 1 ] . shared_secret . as_ref ( ) ,
2580+ error_code,
2581+ & [ 0 ; 0 ] ,
2582+ ) ;
2583+
2584+ crypt_failure_packet (
2585+ outer_onion_keys[ 1 ] . shared_secret . as_ref ( ) ,
2586+ & mut trampoline_outer_hop_error_packet,
2587+ ) ;
2588+
2589+ crypt_failure_packet (
2590+ outer_onion_keys[ 0 ] . shared_secret . as_ref ( ) ,
2591+ & mut trampoline_outer_hop_error_packet,
2592+ ) ;
2593+
2594+ let decrypted_failure = process_onion_failure (
2595+ & secp_ctx,
2596+ & logger,
2597+ & htlc_source,
2598+ trampoline_outer_hop_error_packet,
2599+ ) ;
2600+ assert_eq ! ( decrypted_failure. onion_error_code, Some ( error_code) ) ;
2601+ } ;
2602+
2603+ {
2604+ let error_code = 0x2004 ;
2605+ let mut trampoline_inner_hop_error_packet = build_unencrypted_failure_packet (
2606+ trampoline_onion_keys[ 0 ] . shared_secret . as_ref ( ) ,
2607+ error_code,
2608+ & [ 0 ; 0 ] ,
2609+ ) ;
2610+
2611+ crypt_failure_packet (
2612+ trampoline_onion_keys[ 0 ] . shared_secret . as_ref ( ) ,
2613+ & mut trampoline_inner_hop_error_packet,
2614+ ) ;
2615+
2616+ crypt_failure_packet (
2617+ outer_onion_keys[ 1 ] . shared_secret . as_ref ( ) ,
2618+ & mut trampoline_inner_hop_error_packet,
2619+ ) ;
2620+
2621+ crypt_failure_packet (
2622+ outer_onion_keys[ 0 ] . shared_secret . as_ref ( ) ,
2623+ & mut trampoline_inner_hop_error_packet,
2624+ ) ;
2625+
2626+ let decrypted_failure = process_onion_failure (
2627+ & secp_ctx,
2628+ & logger,
2629+ & htlc_source,
2630+ trampoline_inner_hop_error_packet,
2631+ ) ;
2632+ assert_eq ! ( decrypted_failure. onion_error_code, Some ( error_code) ) ;
2633+ }
2634+ }
2635+ }
2636+
24072637 #[ test]
24082638 fn test_non_attributable_failure_packet_onion ( ) {
24092639 // Create a failure packet with bogus data.
0 commit comments