Skip to content

Commit 1bcfa45

Browse files
committed
Construct Trampoline component in create_payment_onion
1 parent 57d48c8 commit 1bcfa45

File tree

3 files changed

+209
-19
lines changed

3 files changed

+209
-19
lines changed

lightning/src/ln/blinded_payment_tests.rs

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// licenses.
99

1010
use bitcoin::hashes::hex::FromHex;
11+
use bitcoin::hex::DisplayHex;
1112
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, schnorr};
1213
use bitcoin::secp256k1::ecdh::SharedSecret;
1314
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
@@ -30,12 +31,14 @@ use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS};
3031
use crate::offers::invoice::UnsignedBolt12Invoice;
3132
use crate::offers::nonce::Nonce;
3233
use crate::prelude::*;
33-
use crate::routing::router::{BlindedTail, Path, Payee, PaymentParameters, RouteHop, RouteParameters};
34+
use crate::routing::router::{BlindedTail, Path, Payee, PaymentParameters, RouteHop, RouteParameters, TrampolineHop};
3435
use crate::sign::{NodeSigner, Recipient};
3536
use crate::util::config::UserConfig;
36-
use crate::util::ser::WithoutLength;
37+
use crate::util::ser::{WithoutLength, Writeable};
3738
use crate::util::test_utils;
3839
use lightning_invoice::RawBolt11Invoice;
40+
use types::features::Features;
41+
use crate::blinded_path::BlindedHop;
3942

4043
pub fn blinded_payment_path(
4144
payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64,
@@ -1611,3 +1614,82 @@ fn route_blinding_spec_test_vector() {
16111614
_ => panic!("Unexpected error")
16121615
}
16131616
}
1617+
1618+
#[test]
1619+
fn combined_trampoline_onion_creation_test() {
1620+
let mut secp_ctx = Secp256k1::new();
1621+
let session_priv = SecretKey::from_slice(&<Vec<u8>>::from_hex("a64feb81abd58e473df290e9e1c07dc3e56114495cadf33191f44ba5448ebe99").unwrap()).unwrap();
1622+
1623+
let path = Path {
1624+
hops: vec![
1625+
// Bob
1626+
RouteHop {
1627+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()).unwrap(),
1628+
node_features: NodeFeatures::empty(),
1629+
short_channel_id: 0,
1630+
channel_features: ChannelFeatures::empty(),
1631+
fee_msat: 0,
1632+
cltv_expiry_delta: 0,
1633+
maybe_announced_channel: false,
1634+
},
1635+
1636+
// Carol
1637+
RouteHop {
1638+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()).unwrap(),
1639+
node_features: NodeFeatures::empty(),
1640+
short_channel_id: (572330 << 40) + (42 << 16) + 2821,
1641+
channel_features: ChannelFeatures::empty(),
1642+
fee_msat: 150_153_000,
1643+
cltv_expiry_delta: 24,
1644+
maybe_announced_channel: false,
1645+
},
1646+
],
1647+
trampoline_hops: vec![
1648+
// Carol's pubkey
1649+
TrampolineHop {
1650+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()).unwrap(),
1651+
node_features: Features::empty(),
1652+
fee_msat: 0,
1653+
cltv_expiry_delta: 0,
1654+
},
1655+
// Dave's pubkey (the intro node needs to be duplicated)
1656+
TrampolineHop {
1657+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()).unwrap(),
1658+
node_features: Features::empty(),
1659+
fee_msat: 150_500, // incorporate both base and proportional fee
1660+
cltv_expiry_delta: 36,
1661+
}
1662+
],
1663+
blinded_tail: Some(BlindedTail {
1664+
hops: vec![
1665+
BlindedHop {
1666+
blinded_node_id: PublicKey::from_slice(&<Vec<u8>>::from_hex("0295d40514096a8be54859e7dfe947b376eaafea8afe5cb4eb2c13ff857ed0b4be").unwrap()).unwrap(),
1667+
encrypted_payload: <Vec<u8>>::from_hex("0ccf3c8a58deaa603f657ee2a5ed9d604eb5c8ca1e5f801989afa8f3ea6d789bbdde2c7e7a1ef9ca8c38d2c54760febad8446d3f273ddb537569ef56613846ccd3aba78a").unwrap(),
1668+
},
1669+
BlindedHop {
1670+
blinded_node_id: PublicKey::from_slice(&<Vec<u8>>::from_hex("020e2dbadcc2005e859819ddebbe88a834ae8a6d2b049233c07335f15cd1dc5f22").unwrap()).unwrap(),
1671+
encrypted_payload: <Vec<u8>>::from_hex("bcd747394fbd4d99588da075a623316e15a576df5bc785cccc7cd6ec7b398acce6faf520175f9ec920f2ef261cdb83dc28cc3a0eeb970107b3306489bf771ef5b1213bca811d345285405861d08a655b6c237fa247a8b4491beee20c878a60e9816492026d8feb9dafa84585b253978db6a0aa2945df5ef445c61e801fb82f43d5f00716baf9fc9b3de50bc22950a36bda8fc27bfb1242e5860c7e687438d4133e058770361a19b6c271a2a07788d34dccc27e39b9829b061a4d960eac4a2c2b0f4de506c24f9af3868c0aff6dda27281c").unwrap(),
1672+
}
1673+
],
1674+
blinding_point: PublicKey::from_slice(&<Vec<u8>>::from_hex("02988face71e92c345a068f740191fd8e53be14f0bb957ef730d3c5f76087b960e").unwrap()).unwrap(),
1675+
excess_final_cltv_expiry_delta: 0,
1676+
final_value_msat: 150_000_000
1677+
}),
1678+
};
1679+
1680+
let associated_data_slice = SecretKey::from_slice(&<Vec<u8>>::from_hex("e89bc505e84aaca09613833fc58c9069078fb43bfbea0488f34eec9db99b5f82").unwrap()).unwrap();
1681+
let associated_data = PaymentHash(associated_data_slice.secret_bytes());
1682+
let payment_secret = PaymentSecret(SecretKey::from_slice(&<Vec<u8>>::from_hex("7494b65bc092b48a75465e43e29be807eb2cc535ce8aaba31012b8ff1ceac5da").unwrap()).unwrap().secret_bytes());
1683+
let outer_session_key = SecretKey::from_slice(&<Vec<u8>>::from_hex("4f777e8dac16e6dfe333066d9efb014f7a51d11762ff76eca4d3a95ada99ba3e").unwrap()).unwrap();
1684+
let outer_onion_prng_seed = onion_utils::gen_pad_from_shared_secret(&outer_session_key.secret_bytes());
1685+
1686+
let amt_msat = 150_000_000;
1687+
let cur_height = 800_000;
1688+
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret);
1689+
let (bob_onion, htlc_msat, htlc_cltv) = onion_utils::create_payment_onion_internal(&secp_ctx, &path, &session_priv, amt_msat, &recipient_onion_fields, cur_height, &associated_data, &None, None, [0; 32], Some(payment_secret), Some(outer_session_key), Some(outer_onion_prng_seed)).unwrap();
1690+
1691+
let outer_onion_packet_hex = bob_onion.encode().to_lower_hex_string();
1692+
assert_eq!(outer_onion_packet_hex, "00025fd60556c134ae97e4baedba220a644037754ee67c54fd05e93bf40c17cbb73362fb9dee96001ff229945595b6edb59437a6bc143406d3f90f749892a84d8d430c6890437d26d5bfc599d565316ef51347521075bbab87c59c57bcf20af7e63d7192b46cf171e4f73cb11f9f603915389105d91ad630224bea95d735e3988add1e24b5bf28f1d7128db64284d90a839ba340d088c74b1fb1bd21136b1809428ec5399c8649e9bdf92d2dcfc694deae5046fa5b2bdf646847aaad73f5e95275763091c90e71031cae1f9a770fdea559642c9c02f424a2a28163dd0957e3874bd28a97bec67d18c0321b0e68bc804aa8345b17cb626e2348ca06c8312a167c989521056b0f25c55559d446507d6c491d50605cb79fa87929ce64b0a9860926eeaec2c431d926a1cadb9a1186e4061cb01671a122fc1f57602cbef06d6c194ec4b715c2e3dd4120baca3172cd81900b49fef857fb6d6afd24c983b608108b0a5ac0c1c6c52011f23b8778059ffadd1bb7cd06e2525417365f485a7fd1d4a9ba3818ede7cdc9e71afee8532252d08e2531ca52538655b7e8d912f7ec6d37bbcce8d7ec690709dbf9321e92c565b78e7fe2c22edf23e0902153d1ca15a112ad32fb19695ec65ce11ddf670da7915f05ad4b86c154fb908cb567315d1124f303f75fa075ebde8ef7bb12e27737ad9e4924439097338ea6d7a6fc3721b88c9b830a34e8d55f4c582b74a3895cc848fe57f4fe29f115dabeb6b3175be15d94408ed6771109cfaf57067ae658201082eae7605d26b1449af4425ae8e8f58cdda5c6265f1fd7a386fc6cea3074e4f25b909b96175883676f7610a00fdf34df9eb6c7b9a4ae89b839c69fd1f285e38cdceb634d782cc6d81179759bc9fd47d7fd060470d0b048287764c6837963274e708314f017ac7dc26d0554d59bfcfd3136225798f65f0b0fea337c6b256ebbb63a90b994c0ab93fd8b1d6bd4c74aebe535d6110014cd3d525394027dfe8faa98b4e9b2bee7949eb1961f1b026791092f84deea63afab66603dbe9b6365a102a1fef2f6b9744bc1bb091a8da9130d34d4d39f25dbad191649cfb67e10246364b7ce0c6ec072f9690cabb459d9fda0c849e17535de4357e9907270c75953fca3c845bb613926ecf73205219c7057a4b6bb244c184362bb4e2f24279dc4e60b94a5b1ec11c34081a628428ba5646c995b9558821053ba9c84a05afbf00dabd60223723096516d2f5668f3ec7e11612b01eb7a3a0506189a2272b88e89807943adb34291a17f6cb5516ffd6f945a1c42a524b21f096d66f350b1dad4db455741ae3d0e023309fbda5ef55fb0dc74f3297041448b2be76c525141963934c6afc53d263fb7836626df502d7c2ee9e79cbbd87afd84bbb8dfbf45248af3cd61ad5fac827e7683ca4f91dfad507a8eb9c17b2c9ac5ec051fe645a4a6cb37136f6f19b611e0ea8da7960af2d779507e55f57305bc74b7568928c5dd5132990fe54c22117df91c257d8c7b61935a018a28c1c3b17bab8e4294fa699161ec21123c9fc4e71079df31f300c2822e1246561e04765d3aab333eafd026c7431ac7616debb0e022746f4538e1c6348b600c988eeb2d051fc60c468dca260a84c79ab3ab8342dc345a764672848ea234e17332bc124799daf7c5fcb2e2358514a7461357e1c19c802c5ee32deccf1776885dd825bedd5f781d459984370a6b7ae885d4483a76ddb19b30f47ed47cd56aa5a079a89793dbcad461c59f2e002067ac98dd5a534e525c9c46c2af730741bf1f8629357ec0bfc0bc9ecb31af96777e507648ff4260dc3673716e098d9111dfd245f1d7c55a6de340deb8bd7a053e5d62d760f184dc70ca8fa255b9023b9b9aedfb6e419a5b5951ba0f83b603793830ee68d442d7b88ee1bbf6bbd1bcd6f68cc1af");
1693+
assert_eq!(htlc_msat, 150_153_000);
1694+
assert_eq!(htlc_cltv, 800_060);
1695+
}

lightning/src/ln/msgs.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1802,7 +1802,6 @@ mod fuzzy_internal_msgs {
18021802
amt_to_forward: u64,
18031803
outgoing_cltv_value: u32,
18041804
},
1805-
#[allow(unused)]
18061805
TrampolineEntrypoint {
18071806
amt_to_forward: u64,
18081807
outgoing_cltv_value: u32,

lightning/src/ln/onion_utils.rs

Lines changed: 125 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::routing::gossip::NetworkUpdate;
1818
use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters, TrampolineHop};
1919
use crate::sign::NodeSigner;
2020
use crate::types::features::{ChannelFeatures, NodeFeatures};
21-
use crate::types::payment::{PaymentHash, PaymentPreimage};
21+
use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
2222
use crate::util::errors::{self, APIError};
2323
use crate::util::logger::Logger;
2424
use crate::util::ser::{LengthCalculatingWriter, Readable, ReadableArgs, Writeable, Writer};
@@ -32,9 +32,10 @@ use bitcoin::secp256k1;
3232
use bitcoin::secp256k1::ecdh::SharedSecret;
3333
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
3434

35-
use crate::io::{Cursor, Read};
3635
use core::ops::Deref;
3736

37+
use crate::io::{Cursor, Read};
38+
use crate::ln::msgs::{FinalOnionHopData, OutboundOnionPayload};
3839
#[allow(unused_imports)]
3940
use crate::prelude::*;
4041

@@ -347,12 +348,18 @@ pub(super) fn build_onion_payloads<'a>(
347348
let mut res: Vec<msgs::OutboundOnionPayload> = Vec::with_capacity(
348349
path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len()),
349350
);
350-
let blinded_tail_with_hop_iter = path.blinded_tail.as_ref().map(|bt| BlindedTailHopIter {
351-
hops: bt.hops.iter(),
352-
blinding_point: bt.blinding_point,
353-
final_value_msat: bt.final_value_msat,
354-
excess_final_cltv_expiry_delta: bt.excess_final_cltv_expiry_delta,
355-
});
351+
352+
// don't include blinded tail when Trampoline hops are present
353+
let blinded_tail_with_hop_iter = if path.trampoline_hops.is_empty() {
354+
path.blinded_tail.as_ref().map(|bt| BlindedTailHopIter {
355+
hops: bt.hops.iter(),
356+
blinding_point: bt.blinding_point,
357+
final_value_msat: bt.final_value_msat,
358+
excess_final_cltv_expiry_delta: bt.excess_final_cltv_expiry_delta,
359+
})
360+
} else {
361+
None
362+
};
356363

357364
let (value_msat, cltv) = build_onion_payloads_callback(
358365
path.hops.iter(),
@@ -586,7 +593,6 @@ pub(super) fn construct_onion_packet(
586593
)
587594
}
588595

589-
#[allow(unused)]
590596
pub(super) fn construct_trampoline_onion_packet(
591597
payloads: Vec<msgs::OutboundTrampolinePayload>, onion_keys: Vec<OnionKeys>,
592598
prng_seed: [u8; 32], associated_data: &PaymentHash, length: Option<u16>,
@@ -1349,21 +1355,124 @@ pub fn create_payment_onion<T: secp256k1::Signing>(
13491355
keysend_preimage: &Option<PaymentPreimage>, invoice_request: Option<&InvoiceRequest>,
13501356
prng_seed: [u8; 32],
13511357
) -> Result<(msgs::OnionPacket, u64, u32), APIError> {
1352-
let onion_keys = construct_onion_keys(&secp_ctx, &path, &session_priv).map_err(|_| {
1353-
APIError::InvalidRoute { err: "Pubkey along hop was maliciously selected".to_owned() }
1354-
})?;
1355-
let (onion_payloads, htlc_msat, htlc_cltv) = build_onion_payloads(
1356-
&path,
1358+
create_payment_onion_internal(
1359+
secp_ctx,
1360+
path,
1361+
session_priv,
13571362
total_msat,
13581363
recipient_onion,
13591364
cur_block_height,
1365+
payment_hash,
13601366
keysend_preimage,
13611367
invoice_request,
1362-
)?;
1363-
let onion_packet = construct_onion_packet(onion_payloads, onion_keys, prng_seed, payment_hash)
1368+
prng_seed,
1369+
None,
1370+
None,
1371+
None,
1372+
)
1373+
}
1374+
1375+
/// Build a payment onion, returning the first hop msat and cltv values as well.
1376+
/// `cur_block_height` should be set to the best known block height + 1.
1377+
pub(crate) fn create_payment_onion_internal<T: secp256k1::Signing>(
1378+
secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, total_msat: u64,
1379+
recipient_onion: &RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash,
1380+
keysend_preimage: &Option<PaymentPreimage>, invoice_request: Option<&InvoiceRequest>,
1381+
prng_seed: [u8; 32], secondary_payment_secret: Option<PaymentSecret>,
1382+
secondary_session_priv: Option<SecretKey>, secondary_prng_seed: Option<[u8; 32]>,
1383+
) -> Result<(msgs::OnionPacket, u64, u32), APIError> {
1384+
let mut outer_total_msat = total_msat;
1385+
let mut outer_starting_htlc_offset = cur_block_height;
1386+
let mut outer_session_priv_override = None;
1387+
let mut trampoline_packet_option = None;
1388+
1389+
if !path.trampoline_hops.is_empty() {
1390+
let trampoline_payloads;
1391+
(trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) =
1392+
build_trampoline_onion_payloads(
1393+
path,
1394+
total_msat,
1395+
recipient_onion,
1396+
cur_block_height,
1397+
keysend_preimage,
1398+
)?;
1399+
1400+
let onion_keys =
1401+
construct_trampoline_onion_keys(&secp_ctx, &path, &session_priv).map_err(|_| {
1402+
APIError::InvalidRoute {
1403+
err: "Pubkey along hop was maliciously selected".to_owned(),
1404+
}
1405+
})?;
1406+
let trampoline_packet = construct_trampoline_onion_packet(
1407+
trampoline_payloads,
1408+
onion_keys,
1409+
prng_seed,
1410+
payment_hash,
1411+
None,
1412+
)
13641413
.map_err(|_| APIError::InvalidRoute {
13651414
err: "Route size too large considering onion data".to_owned(),
13661415
})?;
1416+
1417+
trampoline_packet_option = Some(trampoline_packet);
1418+
}
1419+
1420+
let (mut onion_payloads, htlc_msat, htlc_cltv) = build_onion_payloads(
1421+
&path,
1422+
outer_total_msat,
1423+
recipient_onion,
1424+
outer_starting_htlc_offset,
1425+
keysend_preimage,
1426+
invoice_request,
1427+
)?;
1428+
1429+
if !path.trampoline_hops.is_empty() {
1430+
let last_payload = onion_payloads.pop().ok_or(APIError::InvalidRoute {
1431+
err: "Non-Trampoline path needs at least one hop".to_owned(),
1432+
})?;
1433+
1434+
match last_payload {
1435+
OutboundOnionPayload::Receive { payment_data, .. } => {
1436+
let fee_delta = path.hops.last().map_or(0, |h| h.fee_msat);
1437+
let cltv_delta = path.hops.last().map_or(0, |h| h.cltv_expiry_delta);
1438+
let multipath_trampoline_data = payment_data.map(|d| {
1439+
let trampoline_payment_secret = secondary_payment_secret.unwrap_or_else(|| {
1440+
PaymentSecret(Sha256::hash(&d.payment_secret.0).to_byte_array())
1441+
});
1442+
let total_msat = fee_delta;
1443+
FinalOnionHopData { payment_secret: trampoline_payment_secret, total_msat }
1444+
});
1445+
onion_payloads.push(OutboundOnionPayload::TrampolineEntrypoint {
1446+
amt_to_forward: fee_delta,
1447+
outgoing_cltv_value: outer_starting_htlc_offset + cltv_delta,
1448+
multipath_trampoline_data,
1449+
trampoline_packet: trampoline_packet_option.unwrap(),
1450+
});
1451+
},
1452+
_ => {
1453+
return Err(APIError::InvalidRoute {
1454+
err: "Last non-Trampoline hop must be of type OutboundOnionPayload::Receive"
1455+
.to_owned(),
1456+
});
1457+
},
1458+
};
1459+
1460+
outer_session_priv_override = Some(secondary_session_priv.unwrap_or_else(|| {
1461+
let session_priv_hash = Sha256::hash(&session_priv.secret_bytes()).to_byte_array();
1462+
SecretKey::from_slice(&session_priv_hash[..]).expect("You broke SHA-256!")
1463+
}));
1464+
}
1465+
1466+
let outer_session_priv = outer_session_priv_override.as_ref().unwrap_or(session_priv);
1467+
let onion_keys = construct_onion_keys(&secp_ctx, &path, outer_session_priv).map_err(|_| {
1468+
APIError::InvalidRoute { err: "Pubkey along hop was maliciously selected".to_owned() }
1469+
})?;
1470+
let outer_onion_prng_seed = secondary_prng_seed.unwrap_or(prng_seed);
1471+
let onion_packet =
1472+
construct_onion_packet(onion_payloads, onion_keys, outer_onion_prng_seed, payment_hash)
1473+
.map_err(|_| APIError::InvalidRoute {
1474+
err: "Route size too large considering onion data".to_owned(),
1475+
})?;
13671476
Ok((onion_packet, htlc_msat, htlc_cltv))
13681477
}
13691478

0 commit comments

Comments
 (0)