Skip to content

Commit 83e58e9

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 e60adce commit 83e58e9

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());
@@ -1466,7 +1468,7 @@ impl HTLCFailReason {
14661468
{
14671469
match self.0 {
14681470
HTLCFailReasonRepr::LightningError { ref err } => {
1469-
process_onion_failure(secp_ctx, logger, &htlc_source, err.clone())
1471+
process_onion_failure(secp_ctx, logger, &htlc_source, err.clone(), None)
14701472
},
14711473
#[allow(unused)]
14721474
HTLCFailReasonRepr::Reason { ref failure_code, ref data, .. } => {
@@ -2034,11 +2036,11 @@ mod tests {
20342036
use crate::prelude::*;
20352037
use crate::util::test_utils::TestLogger;
20362038

2039+
use super::*;
20372040
use bitcoin::hex::FromHex;
20382041
use bitcoin::secp256k1::Secp256k1;
20392042
use bitcoin::secp256k1::{PublicKey, SecretKey};
2040-
2041-
use super::*;
2043+
use types::features::Features;
20422044

20432045
fn get_test_session_key() -> SecretKey {
20442046
let hex = "4141414141414141414141414141414141414141414141414141414141414141";
@@ -2394,11 +2396,284 @@ mod tests {
23942396

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

23992401
assert_eq!(decrypted_failure.onion_error_code, Some(0x2002));
24002402
}
24012403

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

2487-
let decrypted_failure = process_onion_failure(&ctx_full, &logger, &htlc_source, packet);
2762+
let decrypted_failure =
2763+
process_onion_failure(&ctx_full, &logger, &htlc_source, packet, None);
24882764

24892765
decrypted_failure
24902766
}

0 commit comments

Comments
 (0)