Skip to content

Commit 36f8514

Browse files
committed
Trampoline error decryption and vector tests
Create unit tests covering the hybrid outer and inner onion shared secrets, as well as the Trampoline error test vectors. Additionally, we allow the `outer_session_priv` to be overridden to accommodate the test vector requirements.
1 parent 8405af4 commit 36f8514

File tree

1 file changed

+284
-8
lines changed

1 file changed

+284
-8
lines changed

lightning/src/ln/onion_utils.rs

Lines changed: 284 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,7 @@ pub(crate) struct DecodedOnionFailure {
942942
#[inline]
943943
pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
944944
secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource,
945-
mut encrypted_packet: OnionErrorPacket,
945+
mut encrypted_packet: OnionErrorPacket, secondary_session_priv: Option<SecretKey>,
946946
) -> DecodedOnionFailure
947947
where
948948
L::Target: Logger,
@@ -1004,8 +1004,10 @@ where
10041004

10051005
let outer_session_priv = path.has_trampoline_hops().then(|| {
10061006
// 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!")
1007+
secondary_session_priv.unwrap_or_else(|| {
1008+
let session_priv_hash = Sha256::hash(&session_priv.secret_bytes()).to_byte_array();
1009+
SecretKey::from_slice(&session_priv_hash[..]).expect("You broke SHA-256!")
1010+
})
10091011
});
10101012

10111013
let mut onion_keys = Vec::with_capacity(path.hops.len());
@@ -1469,7 +1471,7 @@ impl HTLCFailReason {
14691471
{
14701472
match self.0 {
14711473
HTLCFailReasonRepr::LightningError { ref err } => {
1472-
process_onion_failure(secp_ctx, logger, &htlc_source, err.clone())
1474+
process_onion_failure(secp_ctx, logger, &htlc_source, err.clone(), None)
14731475
},
14741476
#[allow(unused)]
14751477
HTLCFailReasonRepr::Reason { ref failure_code, ref data, .. } => {
@@ -2037,11 +2039,11 @@ mod tests {
20372039
use crate::prelude::*;
20382040
use crate::util::test_utils::TestLogger;
20392041

2042+
use super::*;
20402043
use bitcoin::hex::FromHex;
20412044
use bitcoin::secp256k1::Secp256k1;
20422045
use bitcoin::secp256k1::{PublicKey, SecretKey};
2043-
2044-
use super::*;
2046+
use types::features::Features;
20452047

20462048
fn get_test_session_key() -> SecretKey {
20472049
let hex = "4141414141414141414141414141414141414141414141414141414141414141";
@@ -2397,11 +2399,284 @@ mod tests {
23972399

23982400
// Assert that the original failure can be retrieved and that all hmacs check out.
23992401
let decrypted_failure =
2400-
process_onion_failure(&ctx_full, &logger, &htlc_source, onion_error);
2402+
process_onion_failure(&ctx_full, &logger, &htlc_source, onion_error, None);
24012403

24022404
assert_eq!(decrypted_failure.onion_error_code, Some(0x2002));
24032405
}
24042406

2407+
#[test]
2408+
fn test_trampoline_onion_error_vectors() {
2409+
// as per https://github.com/lightning/bolts/blob/079f761bf68caa48544bd6bf0a29591d43425b0b/bolt04/trampoline-onion-error-test.json
2410+
// TODO(arik): check intermediate hops' perspectives once we have implemented forwarding
2411+
2412+
let dummy_amt_msat = 150_000_000;
2413+
let path = Path {
2414+
hops: vec![
2415+
// Bob
2416+
RouteHop {
2417+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()).unwrap(),
2418+
node_features: NodeFeatures::empty(),
2419+
short_channel_id: 0,
2420+
channel_features: ChannelFeatures::empty(),
2421+
fee_msat: 3_000,
2422+
cltv_expiry_delta: 24,
2423+
maybe_announced_channel: false,
2424+
},
2425+
2426+
// Carol
2427+
RouteHop {
2428+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()).unwrap(),
2429+
node_features: NodeFeatures::empty(),
2430+
short_channel_id: (572330 << 40) + (42 << 16) + 2821,
2431+
channel_features: ChannelFeatures::empty(),
2432+
fee_msat: 153_000,
2433+
cltv_expiry_delta: 0,
2434+
maybe_announced_channel: false,
2435+
},
2436+
],
2437+
blinded_tail: Some(BlindedTail {
2438+
trampoline_hops: vec![
2439+
// Carol's pubkey
2440+
TrampolineHop {
2441+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()).unwrap(),
2442+
node_features: Features::empty(),
2443+
fee_msat: 2_500,
2444+
cltv_expiry_delta: 24,
2445+
},
2446+
2447+
// Eve's pubkey
2448+
TrampolineHop {
2449+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()).unwrap(),
2450+
node_features: Features::empty(),
2451+
fee_msat: 2_500,
2452+
cltv_expiry_delta: 24,
2453+
},
2454+
],
2455+
2456+
// Dummy blinded hop (because LDK doesn't allow unblinded Trampoline receives)
2457+
hops: vec![
2458+
// Eve's dummy blinded node id
2459+
BlindedHop {
2460+
blinded_node_id: PublicKey::from_slice(&<Vec<u8>>::from_hex("0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be").unwrap()).unwrap(),
2461+
encrypted_payload: vec![],
2462+
}
2463+
],
2464+
blinding_point: PublicKey::from_slice(&<Vec<u8>>::from_hex("02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e").unwrap()).unwrap(),
2465+
excess_final_cltv_expiry_delta: 0,
2466+
final_value_msat: dummy_amt_msat
2467+
}),
2468+
};
2469+
2470+
// all dummy values
2471+
let secp_ctx = Secp256k1::new();
2472+
let trampoline_session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
2473+
let outer_session_priv = SecretKey::from_slice(&[4; 32]).unwrap();
2474+
2475+
let logger: Arc<TestLogger> = Arc::new(TestLogger::new());
2476+
let htlc_source = HTLCSource::OutboundRoute {
2477+
path,
2478+
session_priv: trampoline_session_priv,
2479+
first_hop_htlc_msat: dummy_amt_msat,
2480+
payment_id: PaymentId([1; 32]),
2481+
};
2482+
2483+
let error_packet_hex = "f8941a320b8fde4ad7b9b920c69cbf334114737497d93059d77e591eaa78d6334d3e2aeefcb0cc83402eaaf91d07d695cd895d9cad1018abdaf7d2a49d7657b1612729db7f393f0bb62b25afaaaa326d72a9214666025385033f2ec4605dcf1507467b5726d806da180ea224a7d8631cd31b0bdd08eead8bfe14fc8c7475e17768b1321b54dd4294aecc96da391efe0ca5bd267a45ee085c85a60cf9a9ac152fa4795fff8700a3ea4f848817f5e6943e855ab2e86f6929c9e885d8b20c49b14d2512c59ed21f10bd38691110b0d82c00d9fa48a20f10c7550358724c6e8e2b966e56a0aadf458695b273768062fa7c6e60eb72d4cdc67bf525c194e4a17fdcaa0e9d80480b586bf113f14eea530b6728a1c53fe5cee092e24a90f21f4b764015e7ed5e23";
2484+
let error_packet =
2485+
OnionErrorPacket { data: <Vec<u8>>::from_hex(error_packet_hex).unwrap() };
2486+
let decrypted_failure = process_onion_failure(
2487+
&secp_ctx,
2488+
&logger,
2489+
&htlc_source,
2490+
error_packet,
2491+
Some(outer_session_priv),
2492+
);
2493+
assert_eq!(decrypted_failure.onion_error_code, Some(0x400f));
2494+
}
2495+
2496+
#[test]
2497+
fn test_trampoline_onion_decryption() {
2498+
// In this test, we construct a dummy path that uses Trampoline hops, and ensure that the
2499+
// correct shared secrets are used to decrypt the error packets. The actual path configuration
2500+
// is not particularly relevant.
2501+
2502+
let dummy_amt_msat = 150_000_000;
2503+
2504+
let path = Path {
2505+
hops: vec![
2506+
// Bob
2507+
RouteHop {
2508+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()).unwrap(),
2509+
node_features: NodeFeatures::empty(),
2510+
short_channel_id: 0,
2511+
channel_features: ChannelFeatures::empty(),
2512+
fee_msat: 3_000,
2513+
cltv_expiry_delta: 24,
2514+
maybe_announced_channel: false,
2515+
},
2516+
2517+
// Carol
2518+
RouteHop {
2519+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()).unwrap(),
2520+
node_features: NodeFeatures::empty(),
2521+
short_channel_id: (572330 << 40) + (42 << 16) + 2821,
2522+
channel_features: ChannelFeatures::empty(),
2523+
fee_msat: 153_000,
2524+
cltv_expiry_delta: 0,
2525+
maybe_announced_channel: false,
2526+
},
2527+
],
2528+
blinded_tail: Some(BlindedTail {
2529+
trampoline_hops: vec![
2530+
// Carol's pubkey
2531+
TrampolineHop {
2532+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()).unwrap(),
2533+
node_features: Features::empty(),
2534+
fee_msat: 2_500,
2535+
cltv_expiry_delta: 24,
2536+
},
2537+
// Dave's pubkey
2538+
TrampolineHop {
2539+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("020e2dbadcc2005e859819ddebbe88a834ae8a6d2b049233c07335f15cd1dc5f22").unwrap()).unwrap(),
2540+
node_features: Features::empty(),
2541+
fee_msat: 2_500,
2542+
cltv_expiry_delta: 24,
2543+
},
2544+
// Emily's pubkey (the intro node needs to be duplicated)
2545+
TrampolineHop {
2546+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()).unwrap(),
2547+
node_features: Features::empty(),
2548+
fee_msat: 150_500,
2549+
cltv_expiry_delta: 36,
2550+
}
2551+
],
2552+
hops: vec![
2553+
// Emily's blinded node id
2554+
BlindedHop {
2555+
blinded_node_id: PublicKey::from_slice(&<Vec<u8>>::from_hex("0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be").unwrap()).unwrap(),
2556+
encrypted_payload: vec![],
2557+
},
2558+
// Forrest's blinded node id
2559+
BlindedHop {
2560+
blinded_node_id: PublicKey::from_slice(&<Vec<u8>>::from_hex("020e2dbadcc2005e859819ddebbe88a834ae8a6d2b049233c07335f15cd1dc5f22").unwrap()).unwrap(),
2561+
encrypted_payload: vec![],
2562+
}
2563+
],
2564+
blinding_point: PublicKey::from_slice(&<Vec<u8>>::from_hex("02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e").unwrap()).unwrap(),
2565+
excess_final_cltv_expiry_delta: 0,
2566+
final_value_msat: dummy_amt_msat
2567+
}),
2568+
};
2569+
2570+
// all dummy values
2571+
let secp_ctx = Secp256k1::new();
2572+
let session_priv = get_test_session_key();
2573+
2574+
let trampoline_onion_keys = construct_trampoline_onion_keys(
2575+
&secp_ctx,
2576+
&path.blinded_tail.as_ref().unwrap(),
2577+
&session_priv,
2578+
)
2579+
.unwrap();
2580+
2581+
let outer_onion_keys = {
2582+
let session_priv_hash = Sha256::hash(&session_priv.secret_bytes()).to_byte_array();
2583+
let outer_session_priv = SecretKey::from_slice(&session_priv_hash[..]).unwrap();
2584+
construct_onion_keys(&Secp256k1::new(), &path, &outer_session_priv).unwrap()
2585+
};
2586+
2587+
let logger: Arc<TestLogger> = Arc::new(TestLogger::new());
2588+
let htlc_source = HTLCSource::OutboundRoute {
2589+
path,
2590+
session_priv,
2591+
first_hop_htlc_msat: dummy_amt_msat,
2592+
payment_id: PaymentId([1; 32]),
2593+
};
2594+
2595+
{
2596+
let error_code = 0x2002;
2597+
let mut first_hop_error_packet = build_unencrypted_failure_packet(
2598+
outer_onion_keys[0].shared_secret.as_ref(),
2599+
error_code,
2600+
&[0; 0],
2601+
);
2602+
2603+
crypt_failure_packet(
2604+
outer_onion_keys[0].shared_secret.as_ref(),
2605+
&mut first_hop_error_packet,
2606+
);
2607+
2608+
let decrypted_failure = process_onion_failure(
2609+
&secp_ctx,
2610+
&logger,
2611+
&htlc_source,
2612+
first_hop_error_packet,
2613+
None,
2614+
);
2615+
assert_eq!(decrypted_failure.onion_error_code, Some(error_code));
2616+
};
2617+
2618+
{
2619+
let error_code = 0x2003;
2620+
let mut trampoline_outer_hop_error_packet = build_unencrypted_failure_packet(
2621+
outer_onion_keys[1].shared_secret.as_ref(),
2622+
error_code,
2623+
&[0; 0],
2624+
);
2625+
2626+
crypt_failure_packet(
2627+
outer_onion_keys[1].shared_secret.as_ref(),
2628+
&mut trampoline_outer_hop_error_packet,
2629+
);
2630+
2631+
crypt_failure_packet(
2632+
outer_onion_keys[0].shared_secret.as_ref(),
2633+
&mut trampoline_outer_hop_error_packet,
2634+
);
2635+
2636+
let decrypted_failure = process_onion_failure(
2637+
&secp_ctx,
2638+
&logger,
2639+
&htlc_source,
2640+
trampoline_outer_hop_error_packet,
2641+
None,
2642+
);
2643+
assert_eq!(decrypted_failure.onion_error_code, Some(error_code));
2644+
};
2645+
2646+
{
2647+
let error_code = 0x2004;
2648+
let mut trampoline_inner_hop_error_packet = build_unencrypted_failure_packet(
2649+
trampoline_onion_keys[0].shared_secret.as_ref(),
2650+
error_code,
2651+
&[0; 0],
2652+
);
2653+
2654+
crypt_failure_packet(
2655+
trampoline_onion_keys[0].shared_secret.as_ref(),
2656+
&mut trampoline_inner_hop_error_packet,
2657+
);
2658+
2659+
crypt_failure_packet(
2660+
outer_onion_keys[1].shared_secret.as_ref(),
2661+
&mut trampoline_inner_hop_error_packet,
2662+
);
2663+
2664+
crypt_failure_packet(
2665+
outer_onion_keys[0].shared_secret.as_ref(),
2666+
&mut trampoline_inner_hop_error_packet,
2667+
);
2668+
2669+
let decrypted_failure = process_onion_failure(
2670+
&secp_ctx,
2671+
&logger,
2672+
&htlc_source,
2673+
trampoline_inner_hop_error_packet,
2674+
None,
2675+
);
2676+
assert_eq!(decrypted_failure.onion_error_code, Some(error_code));
2677+
}
2678+
}
2679+
24052680
#[test]
24062681
fn test_non_attributable_failure_packet_onion() {
24072682
// Create a failure packet with bogus data.
@@ -2487,7 +2762,8 @@ mod tests {
24872762
payment_id: PaymentId([1; 32]),
24882763
};
24892764

2490-
let decrypted_failure = process_onion_failure(&ctx_full, &logger, &htlc_source, packet);
2765+
let decrypted_failure =
2766+
process_onion_failure(&ctx_full, &logger, &htlc_source, packet, None);
24912767

24922768
decrypted_failure
24932769
}

0 commit comments

Comments
 (0)