Skip to content

Commit 98f2d38

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 3da1c88 commit 98f2d38

File tree

1 file changed

+223
-8
lines changed

1 file changed

+223
-8
lines changed

lightning/src/ln/onion_utils.rs

Lines changed: 223 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, outer_session_priv_override: 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+
outer_session_priv_override.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 num_blinded_hops = path.blinded_tail.as_ref().map_or(0, |bt| bt.hops.len());
@@ -1471,7 +1473,7 @@ impl HTLCFailReason {
14711473
{
14721474
match self.0 {
14731475
HTLCFailReasonRepr::LightningError { ref err } => {
1474-
process_onion_failure(secp_ctx, logger, &htlc_source, err.clone())
1476+
process_onion_failure(secp_ctx, logger, &htlc_source, err.clone(), None)
14751477
},
14761478
#[allow(unused)]
14771479
HTLCFailReasonRepr::Reason { ref failure_code, ref data, .. } => {
@@ -2039,11 +2041,11 @@ mod tests {
20392041
use crate::prelude::*;
20402042
use crate::util::test_utils::TestLogger;
20412043

2044+
use super::*;
20422045
use bitcoin::hex::FromHex;
20432046
use bitcoin::secp256k1::Secp256k1;
20442047
use bitcoin::secp256k1::{PublicKey, SecretKey};
2045-
2046-
use super::*;
2048+
use types::features::Features;
20472049

20482050
fn get_test_session_key() -> SecretKey {
20492051
let hex = "4141414141414141414141414141414141414141414141414141414141414141";
@@ -2399,11 +2401,223 @@ mod tests {
23992401

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

24042406
assert_eq!(decrypted_failure.onion_error_code, Some(0x2002));
24052407
}
24062408

2409+
fn build_trampoline_test_path() -> Path {
2410+
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+
// Dave'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+
// Emily's pubkey
2453+
TrampolineHop {
2454+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()).unwrap(),
2455+
node_features: Features::empty(),
2456+
fee_msat: 150_500,
2457+
cltv_expiry_delta: 36,
2458+
},
2459+
],
2460+
2461+
// Dummy blinded hop (because LDK doesn't allow unblinded Trampoline receives)
2462+
hops: vec![
2463+
// Emily's dummy blinded node id
2464+
BlindedHop {
2465+
blinded_node_id: PublicKey::from_slice(&<Vec<u8>>::from_hex("0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be").unwrap()).unwrap(),
2466+
encrypted_payload: vec![],
2467+
}
2468+
],
2469+
blinding_point: PublicKey::from_slice(&<Vec<u8>>::from_hex("02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e").unwrap()).unwrap(),
2470+
excess_final_cltv_expiry_delta: 0,
2471+
final_value_msat: 150_000_000,
2472+
}),
2473+
}
2474+
}
2475+
2476+
#[test]
2477+
fn test_trampoline_onion_error_cryptography() {
2478+
// TODO(arik): check intermediate hops' perspectives once we have implemented forwarding
2479+
2480+
let secp_ctx = Secp256k1::new();
2481+
let logger: Arc<TestLogger> = Arc::new(TestLogger::new());
2482+
let dummy_amt_msat = 150_000_000;
2483+
2484+
{
2485+
// test vector per https://github.com/lightning/bolts/blob/079f761bf68caa48544bd6bf0a29591d43425b0b/bolt04/trampoline-onion-error-test.json
2486+
// all dummy values
2487+
let trampoline_session_priv = SecretKey::from_slice(&[3; 32]).unwrap();
2488+
let outer_session_priv = SecretKey::from_slice(&[4; 32]).unwrap();
2489+
2490+
let htlc_source = HTLCSource::OutboundRoute {
2491+
path: build_trampoline_test_path(),
2492+
session_priv: trampoline_session_priv,
2493+
first_hop_htlc_msat: dummy_amt_msat,
2494+
payment_id: PaymentId([1; 32]),
2495+
};
2496+
2497+
let error_packet_hex = "f8941a320b8fde4ad7b9b920c69cbf334114737497d93059d77e591eaa78d6334d3e2aeefcb0cc83402eaaf91d07d695cd895d9cad1018abdaf7d2a49d7657b1612729db7f393f0bb62b25afaaaa326d72a9214666025385033f2ec4605dcf1507467b5726d806da180ea224a7d8631cd31b0bdd08eead8bfe14fc8c7475e17768b1321b54dd4294aecc96da391efe0ca5bd267a45ee085c85a60cf9a9ac152fa4795fff8700a3ea4f848817f5e6943e855ab2e86f6929c9e885d8b20c49b14d2512c59ed21f10bd38691110b0d82c00d9fa48a20f10c7550358724c6e8e2b966e56a0aadf458695b273768062fa7c6e60eb72d4cdc67bf525c194e4a17fdcaa0e9d80480b586bf113f14eea530b6728a1c53fe5cee092e24a90f21f4b764015e7ed5e23";
2498+
let error_packet =
2499+
OnionErrorPacket { data: <Vec<u8>>::from_hex(error_packet_hex).unwrap() };
2500+
let decrypted_failure = process_onion_failure(
2501+
&secp_ctx,
2502+
&logger,
2503+
&htlc_source,
2504+
error_packet,
2505+
Some(outer_session_priv),
2506+
);
2507+
assert_eq!(decrypted_failure.onion_error_code, Some(0x400f));
2508+
}
2509+
2510+
{
2511+
// shared secret cryptography sanity tests
2512+
let session_priv = get_test_session_key();
2513+
let path = build_trampoline_test_path();
2514+
2515+
let trampoline_onion_keys = construct_trampoline_onion_keys(
2516+
&secp_ctx,
2517+
&path.blinded_tail.as_ref().unwrap(),
2518+
&session_priv,
2519+
)
2520+
.unwrap();
2521+
2522+
let outer_onion_keys = {
2523+
let session_priv_hash = Sha256::hash(&session_priv.secret_bytes()).to_byte_array();
2524+
let outer_session_priv = SecretKey::from_slice(&session_priv_hash[..]).unwrap();
2525+
construct_onion_keys(&Secp256k1::new(), &path, &outer_session_priv).unwrap()
2526+
};
2527+
2528+
let htlc_source = HTLCSource::OutboundRoute {
2529+
path,
2530+
session_priv,
2531+
first_hop_htlc_msat: dummy_amt_msat,
2532+
payment_id: PaymentId([1; 32]),
2533+
};
2534+
2535+
{
2536+
let error_code = 0x2002;
2537+
let mut first_hop_error_packet = build_unencrypted_failure_packet(
2538+
outer_onion_keys[0].shared_secret.as_ref(),
2539+
error_code,
2540+
&[0; 0],
2541+
);
2542+
2543+
crypt_failure_packet(
2544+
outer_onion_keys[0].shared_secret.as_ref(),
2545+
&mut first_hop_error_packet,
2546+
);
2547+
2548+
let decrypted_failure = process_onion_failure(
2549+
&secp_ctx,
2550+
&logger,
2551+
&htlc_source,
2552+
first_hop_error_packet,
2553+
None,
2554+
);
2555+
assert_eq!(decrypted_failure.onion_error_code, Some(error_code));
2556+
};
2557+
2558+
{
2559+
let error_code = 0x2003;
2560+
let mut trampoline_outer_hop_error_packet = build_unencrypted_failure_packet(
2561+
outer_onion_keys[1].shared_secret.as_ref(),
2562+
error_code,
2563+
&[0; 0],
2564+
);
2565+
2566+
crypt_failure_packet(
2567+
outer_onion_keys[1].shared_secret.as_ref(),
2568+
&mut trampoline_outer_hop_error_packet,
2569+
);
2570+
2571+
crypt_failure_packet(
2572+
outer_onion_keys[0].shared_secret.as_ref(),
2573+
&mut trampoline_outer_hop_error_packet,
2574+
);
2575+
2576+
let decrypted_failure = process_onion_failure(
2577+
&secp_ctx,
2578+
&logger,
2579+
&htlc_source,
2580+
trampoline_outer_hop_error_packet,
2581+
None,
2582+
);
2583+
assert_eq!(decrypted_failure.onion_error_code, Some(error_code));
2584+
};
2585+
2586+
{
2587+
let error_code = 0x2004;
2588+
let mut trampoline_inner_hop_error_packet = build_unencrypted_failure_packet(
2589+
trampoline_onion_keys[0].shared_secret.as_ref(),
2590+
error_code,
2591+
&[0; 0],
2592+
);
2593+
2594+
crypt_failure_packet(
2595+
trampoline_onion_keys[0].shared_secret.as_ref(),
2596+
&mut trampoline_inner_hop_error_packet,
2597+
);
2598+
2599+
crypt_failure_packet(
2600+
outer_onion_keys[1].shared_secret.as_ref(),
2601+
&mut trampoline_inner_hop_error_packet,
2602+
);
2603+
2604+
crypt_failure_packet(
2605+
outer_onion_keys[0].shared_secret.as_ref(),
2606+
&mut trampoline_inner_hop_error_packet,
2607+
);
2608+
2609+
let decrypted_failure = process_onion_failure(
2610+
&secp_ctx,
2611+
&logger,
2612+
&htlc_source,
2613+
trampoline_inner_hop_error_packet,
2614+
None,
2615+
);
2616+
assert_eq!(decrypted_failure.onion_error_code, Some(error_code));
2617+
}
2618+
}
2619+
}
2620+
24072621
#[test]
24082622
fn test_non_attributable_failure_packet_onion() {
24092623
// Create a failure packet with bogus data.
@@ -2489,7 +2703,8 @@ mod tests {
24892703
payment_id: PaymentId([1; 32]),
24902704
};
24912705

2492-
let decrypted_failure = process_onion_failure(&ctx_full, &logger, &htlc_source, packet);
2706+
let decrypted_failure =
2707+
process_onion_failure(&ctx_full, &logger, &htlc_source, packet, None);
24932708

24942709
decrypted_failure
24952710
}

0 commit comments

Comments
 (0)