@@ -937,23 +937,58 @@ pub(crate) struct DecodedOnionFailure {
937
937
pub ( crate ) onion_error_data : Option < Vec < u8 > > ,
938
938
}
939
939
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
+
940
977
/// Process failure we got back from upstream on a payment we sent (implying htlc_source is an
941
978
/// OutboundRoute).
942
979
#[ 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 ,
946
983
) -> DecodedOnionFailure
947
984
where
948
985
L :: Target : Logger ,
949
986
{
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)
956
990
} ,
991
+ _ => unreachable ! ( ) ,
957
992
} ;
958
993
959
994
// Learnings from the HTLC failure to inform future payment retries and scoring.
@@ -1002,12 +1037,6 @@ where
1002
1037
}
1003
1038
}
1004
1039
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
-
1011
1040
let num_blinded_hops = path. blinded_tail . as_ref ( ) . map_or ( 0 , |bt| bt. hops . len ( ) ) ;
1012
1041
1013
1042
// We are first collecting all the unblinded `RouteHop`s inside `onion_keys`. Then, if applicable,
@@ -1018,7 +1047,7 @@ where
1018
1047
& path. hops ,
1019
1048
// if we have Trampoline hops, the blinded hops are part of the inner Trampoline onion
1020
1049
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,
1022
1051
|shared_secret, _, _, route_hop_option : Option < & RouteHop > , _| {
1023
1052
onion_keys. push ( ( route_hop_option. map ( |rh| ErrorHop :: RouteHop ( rh) ) , shared_secret) )
1024
1053
} ,
@@ -1031,7 +1060,7 @@ where
1031
1060
// Trampoline hops are part of the blinded tail, so this can never panic
1032
1061
& path. blinded_tail . as_ref ( ) . unwrap ( ) . trampoline_hops ,
1033
1062
path. blinded_tail . as_ref ( ) ,
1034
- session_priv ,
1063
+ inner_session_priv . expect ( "Trampoline hops always have an inner session priv" ) ,
1035
1064
|shared_secret, _, _, trampoline_hop_option : Option < & TrampolineHop > , _| {
1036
1065
onion_keys. push ( (
1037
1066
trampoline_hop_option. map ( |th| ErrorHop :: TrampolineHop ( th) ) ,
@@ -2039,11 +2068,11 @@ mod tests {
2039
2068
use crate :: prelude:: * ;
2040
2069
use crate :: util:: test_utils:: TestLogger ;
2041
2070
2071
+ use super :: * ;
2042
2072
use bitcoin:: hex:: FromHex ;
2043
2073
use bitcoin:: secp256k1:: Secp256k1 ;
2044
2074
use bitcoin:: secp256k1:: { PublicKey , SecretKey } ;
2045
-
2046
- use super :: * ;
2075
+ use types:: features:: Features ;
2047
2076
2048
2077
fn get_test_session_key ( ) -> SecretKey {
2049
2078
let hex = "4141414141414141414141414141414141414141414141414141414141414141" ;
@@ -2400,10 +2429,218 @@ mod tests {
2400
2429
// Assert that the original failure can be retrieved and that all hmacs check out.
2401
2430
let decrypted_failure =
2402
2431
process_onion_failure ( & ctx_full, & logger, & htlc_source, onion_error) ;
2403
-
2404
2432
assert_eq ! ( decrypted_failure. onion_error_code, Some ( 0x2002 ) ) ;
2405
2433
}
2406
2434
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
+
2407
2644
#[ test]
2408
2645
fn test_non_attributable_failure_packet_onion ( ) {
2409
2646
// Create a failure packet with bogus data.
0 commit comments