Skip to content

Commit 45e131f

Browse files
committed
Trampoline onion construction and vector tests.
1 parent 6d11111 commit 45e131f

File tree

3 files changed

+211
-4
lines changed

3 files changed

+211
-4
lines changed

lightning/src/ln/msgs.rs

+53
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,25 @@ mod fuzzy_internal_msgs {
17401740
}
17411741
}
17421742

1743+
pub(crate) enum OutboundTrampolinePayload {
1744+
#[allow(unused)]
1745+
Forward {
1746+
/// The value, in msat, of the payment after this hop's fee is deducted.
1747+
amt_to_forward: u64,
1748+
outgoing_cltv_value: u32,
1749+
/// The node id to which the trampoline node must find a route
1750+
outgoing_node_id: PublicKey,
1751+
},
1752+
Receive {
1753+
payment_data: Option<FinalOnionHopData>,
1754+
payment_metadata: Option<Vec<u8>>,
1755+
keysend_preimage: Option<PaymentPreimage>,
1756+
custom_tlvs: Vec<(u64, Vec<u8>)>,
1757+
sender_intended_htlc_amt_msat: u64,
1758+
cltv_expiry_height: u32,
1759+
},
1760+
}
1761+
17431762
pub struct DecodedOnionErrorPacket {
17441763
pub(crate) hmac: [u8; 32],
17451764
pub(crate) failuremsg: Vec<u8>,
@@ -2597,6 +2616,40 @@ impl Writeable for OutboundOnionPayload {
25972616
}
25982617
}
25992618

2619+
impl Writeable for OutboundTrampolinePayload {
2620+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
2621+
match self {
2622+
Self::Forward { amt_to_forward, outgoing_cltv_value, outgoing_node_id } => {
2623+
_encode_varint_length_prefixed_tlv!(w, {
2624+
(2, HighZeroBytesDroppedBigSize(*amt_to_forward), required),
2625+
(4, HighZeroBytesDroppedBigSize(*outgoing_cltv_value), required),
2626+
(14, outgoing_node_id, required)
2627+
});
2628+
}
2629+
2630+
Self::Receive {
2631+
ref payment_data, ref payment_metadata, ref keysend_preimage, sender_intended_htlc_amt_msat,
2632+
cltv_expiry_height, ref custom_tlvs,
2633+
} => {
2634+
// We need to update [`ln::outbound_payment::RecipientOnionFields::with_custom_tlvs`]
2635+
// to reject any reserved types in the experimental range if new ones are ever
2636+
// standardized.
2637+
let keysend_tlv = keysend_preimage.map(|preimage| (5482373484, preimage.encode()));
2638+
let mut custom_tlvs: Vec<&(u64, Vec<u8>)> = custom_tlvs.iter().chain(keysend_tlv.iter()).collect();
2639+
custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ);
2640+
_encode_varint_length_prefixed_tlv!(w, {
2641+
(2, HighZeroBytesDroppedBigSize(*sender_intended_htlc_amt_msat), required),
2642+
(4, HighZeroBytesDroppedBigSize(*cltv_expiry_height), required),
2643+
(8, payment_data, option),
2644+
(16, payment_metadata.as_ref().map(|m| WithoutLength(m)), option)
2645+
}, custom_tlvs.iter());
2646+
}
2647+
}
2648+
Ok(())
2649+
}
2650+
}
2651+
2652+
26002653
impl<NS: Deref> ReadableArgs<(Option<PublicKey>, &NS)> for InboundOnionPayload where NS::Target: NodeSigner {
26012654
fn read<R: Read>(r: &mut R, args: (Option<PublicKey>, &NS)) -> Result<Self, DecodeError> {
26022655
let (update_add_blinding_point, node_signer) = args;

lightning/src/ln/onion_route_tests.rs

+141-4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
1919
use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId, RecipientOnionFields};
2020
use crate::ln::onion_utils;
2121
use crate::routing::gossip::{NetworkUpdate, RoutingFees};
22-
use crate::routing::router::{get_route, PaymentParameters, Route, RouteParameters, RouteHint, RouteHintHop};
23-
use crate::ln::features::{InitFeatures, Bolt11InvoiceFeatures};
22+
use crate::routing::router::{get_route, PaymentParameters, Route, RouteParameters, RouteHint, RouteHintHop, Path, RouteHop};
23+
use crate::ln::features::{InitFeatures, Bolt11InvoiceFeatures, NodeFeatures, ChannelFeatures};
2424
use crate::ln::msgs;
25-
use crate::ln::msgs::{ChannelMessageHandler, ChannelUpdate};
25+
use crate::ln::msgs::{ChannelMessageHandler, ChannelUpdate, FinalOnionHopData, OutboundOnionPayload, OutboundTrampolinePayload};
2626
use crate::ln::wire::Encode;
2727
use crate::util::ser::{Writeable, Writer, BigSize};
2828
use crate::util::test_utils;
@@ -35,11 +35,12 @@ use bitcoin::hashes::hmac::{Hmac, HmacEngine};
3535
use bitcoin::hashes::sha256::Hash as Sha256;
3636

3737
use bitcoin::secp256k1;
38-
use bitcoin::secp256k1::{Secp256k1, SecretKey};
38+
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
3939

4040
use crate::io;
4141
use crate::prelude::*;
4242
use core::default::Default;
43+
use bitcoin::hashes::hex::FromHex;
4344

4445
use crate::ln::functional_test_utils::*;
4546

@@ -966,6 +967,142 @@ fn test_always_create_tlv_format_onion_payloads() {
966967
}
967968
}
968969

970+
#[test]
971+
fn test_trampoline_onion_payload_construction_vectors() {
972+
// As per https://github.com/lightning/bolts/blob/c01d2e6267d4a8d1095f0f1188970055a9a22d29/bolt04/trampoline-payment-onion-test.json#L3
973+
974+
let trampoline_payloads = vec![
975+
// Carol
976+
OutboundTrampolinePayload::Forward {
977+
amt_to_forward: 100000000,
978+
outgoing_cltv_value: 800000,
979+
outgoing_node_id: PublicKey::from_slice(&<Vec<u8>>::from_hex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()).unwrap(),
980+
},
981+
982+
// Eve
983+
OutboundTrampolinePayload::Receive {
984+
payment_data: Some(FinalOnionHopData {
985+
payment_secret: PaymentSecret([0x2a; 32]),
986+
total_msat: 100000000
987+
}),
988+
payment_metadata: None,
989+
keysend_preimage: None,
990+
custom_tlvs: vec![],
991+
sender_intended_htlc_amt_msat: 100000000,
992+
cltv_expiry_height: 800000
993+
}
994+
];
995+
996+
let trampoline_hops = Path { hops: vec![
997+
// Carol
998+
RouteHop {
999+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()).unwrap(),
1000+
node_features: NodeFeatures::empty(),
1001+
short_channel_id: 0,
1002+
channel_features: ChannelFeatures::empty(),
1003+
fee_msat: 0,
1004+
cltv_expiry_delta: 0,
1005+
maybe_announced_channel: false,
1006+
},
1007+
1008+
// Eve
1009+
RouteHop {
1010+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145").unwrap()).unwrap(),
1011+
node_features: NodeFeatures::empty(),
1012+
short_channel_id: 0,
1013+
channel_features: ChannelFeatures::empty(),
1014+
fee_msat: 0,
1015+
cltv_expiry_delta: 0,
1016+
maybe_announced_channel: false,
1017+
}
1018+
], blinded_tail: None };
1019+
1020+
let slice_to_hex = |slice: &[u8]| {
1021+
slice.iter()
1022+
.map(|b| format!("{:02x}", b).to_string())
1023+
.collect::<String>()
1024+
};
1025+
1026+
let carol_payload_hex = slice_to_hex(&trampoline_payloads[0].encode());
1027+
println!("Carol Payload Hex: {}", carol_payload_hex);
1028+
assert_eq!(carol_payload_hex, "2e020405f5e10004030c35000e2102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145");
1029+
1030+
let eve_payload_hex = slice_to_hex(&trampoline_payloads[1].encode());
1031+
println!("Eve Payload Hex: {}", eve_payload_hex);
1032+
assert_eq!(eve_payload_hex, "31020405f5e10004030c350008242a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a05f5e100");
1033+
1034+
let trampoline_session_key = SecretKey::from_slice(&[0x03; 32]).unwrap();
1035+
let trampoline_onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &trampoline_hops, &trampoline_session_key).unwrap();
1036+
let associated_data = PaymentHash([0x42; 32]);
1037+
let trampoline_onion_packet = onion_utils::construct_trampoline_onion_packet(trampoline_payloads, trampoline_onion_keys, [0u8; 32], &associated_data, 161).unwrap();
1038+
let trampoline_onion_packet_hex = slice_to_hex(&trampoline_onion_packet.encode());
1039+
println!("Trampoline Onion Packet Hex: {}", trampoline_onion_packet_hex);
1040+
assert_eq!(trampoline_onion_packet_hex, "0002531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe3371860c0749bfd613056cfc5718beecc25a2f255fc7abbea3cd75ff820e9d30807d19b30f33626452fa54bb2d822e918558ed3e6714deb3f9a2a10895e7553c6f088c9a852043530dbc9abcc486030894364b205f5de60171b451ff462664ebce23b672579bf2a444ebfe0a81875c26d2fa16d426795b9b02ccbc4bdf909c583f0c2ebe9136510645917153ecb05181ca0c1b207824578ee841804a148f4c3df7306dcea52d94222907c9187bc31c0880fc084f0d88716e195c0abe7672d15217623");
1041+
1042+
let outer_payloads = vec![
1043+
// Bob
1044+
OutboundOnionPayload::Forward {
1045+
short_channel_id: 1105,
1046+
amt_to_forward: 100005000,
1047+
outgoing_cltv_value: 800250,
1048+
},
1049+
1050+
// Carol
1051+
OutboundOnionPayload::TrampolineEntrypoint {
1052+
amt_to_forward: 100005000,
1053+
outgoing_cltv_value: 800250,
1054+
multipath_trampoline_data: Some(FinalOnionHopData {
1055+
payment_secret: PaymentSecret([0x2b; 32]),
1056+
total_msat: 100005000,
1057+
}),
1058+
trampoline_packet: trampoline_onion_packet,
1059+
}
1060+
];
1061+
1062+
let outer_hops = Path {
1063+
hops: vec![
1064+
// Bob
1065+
RouteHop {
1066+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()).unwrap(),
1067+
node_features: NodeFeatures::empty(),
1068+
short_channel_id: 0,
1069+
channel_features: ChannelFeatures::empty(),
1070+
fee_msat: 0,
1071+
cltv_expiry_delta: 0,
1072+
maybe_announced_channel: false,
1073+
},
1074+
1075+
// Carol
1076+
RouteHop {
1077+
pubkey: PublicKey::from_slice(&<Vec<u8>>::from_hex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()).unwrap(),
1078+
node_features: NodeFeatures::empty(),
1079+
short_channel_id: 0,
1080+
channel_features: ChannelFeatures::empty(),
1081+
fee_msat: 0,
1082+
cltv_expiry_delta: 0,
1083+
maybe_announced_channel: false,
1084+
},
1085+
],
1086+
blinded_tail: None,
1087+
};
1088+
1089+
let bob_payload_hex = slice_to_hex(&outer_payloads[0].encode());
1090+
println!("Bob Payload Hex: {}", bob_payload_hex);
1091+
assert_eq!(bob_payload_hex, "15020405f5f48804030c35fa06080000000000000451");
1092+
1093+
let carol_payload_hex = slice_to_hex(&outer_payloads[1].encode());
1094+
println!("Carol Outer Payload Hex: {}", carol_payload_hex);
1095+
assert_eq!(carol_payload_hex, "fd0116020405f5f48804030c35fa08242b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b05f5f48814e30002531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe3371860c0749bfd613056cfc5718beecc25a2f255fc7abbea3cd75ff820e9d30807d19b30f33626452fa54bb2d822e918558ed3e6714deb3f9a2a10895e7553c6f088c9a852043530dbc9abcc486030894364b205f5de60171b451ff462664ebce23b672579bf2a444ebfe0a81875c26d2fa16d426795b9b02ccbc4bdf909c583f0c2ebe9136510645917153ecb05181ca0c1b207824578ee841804a148f4c3df7306dcea52d94222907c9187bc31c0880fc084f0d88716e195c0abe7672d15217623");
1096+
1097+
let outer_session_key = SecretKey::from_slice(&[0x04; 32]).unwrap();
1098+
let outer_onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &outer_hops, &outer_session_key).unwrap();
1099+
let outer_onion_prng_seed = onion_utils::gen_pad_from_shared_secret(&outer_session_key.secret_bytes());
1100+
let outer_onion_packet = onion_utils::construct_onion_packet(outer_payloads, outer_onion_keys, outer_onion_prng_seed, &associated_data).unwrap();
1101+
let outer_onion_packet_hex = slice_to_hex(&outer_onion_packet.encode());
1102+
println!("Outer Onion Packet Hex: {}", outer_onion_packet_hex);
1103+
assert_eq!(outer_onion_packet_hex, "0003462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b9149ce01cce1709194109ab5940379a84297ab6620025c770527dd8537997e2528082b984fe078a5667978a573abeaf7977d9b8b6ee4f124d3352f7eea52cc66c0e76b8f6d7a25d4501a04ae190b17baff8e6378b36f165815f714559dfef275278eba897f5f229be70fc8a1980cf859d1c25fe90c77f006419770e19d29ba80be8f613d039dd05600734e0d1e218af441fe30877e717a26b7b37c2c071d62bf6d61dd17f7abfb81546d2c722c9a6dc581aa97fb6f3b513e5fbaf0d669fbf0714b2b016a0a8e356d55f267fa144f7501792f2a59269c5a22e555a914e2eb71eba5af519564f246cf58983ea3fa2674e3ab7d9969d8dffbb2bda2b2752657417937d46601eb8ebf1837221d4bdf55a4d6a97ecffde5a09bd409717fa19e440e55d775890aed89f72e65af515757e94a9b501e6bad048af55e1583adb2960a84f60fb5efd0352e77a34045fc6b221498f62810bd8294d995d9f513696f8633f29faaa9668d0c6fa0d0dd7fa13e2c185572485762bd2810dc12187f521fbefa9c320762ac1e107f7988d81c6ee201ab68a95d45d578027e271b6526154317877037dca17134ccd955a22a8481b8e1996d896fc4bf006154ed18ef279d4f255e3f1233d037aea2560011069a0ae56d6bfdd8327054ded12d85d120b8982bff970986db333baae7c95f85677726a8f74cc8bd1e5aca3d433c113048305ecce8e35caf0485a53df00284b52b42291a9ffe686b96442135b3107d8856bc652d674ee9a148e79b02c9972d3ca8c2b02609f3b358c4a67c540ba6769c4d83169bceda640b1d18b74d12b6df605b417dacf6f82d79d43bb40920898f818dc8344c036ae9c8bbf9ef52ea1ccf225c8825a4d8503df190b999e15a4be34c9d7bbf60d3b93bb7d6559f4a5916f5e40c3defeeca9337ccd1280e46d6727c5c91c2d898b685543d4ca7cfee23981323c43260b6387e7febb0fffb200a8c974ef36b3253d0fb9fe0c1c6017f2dbbdc169f3f061d9781521e8118164aeec31c3e59c199016f1025c295d8f7bdeb627d357105a2708c4c3a856b9e83ff37ed69f59f2d2e464ed1db5882925ebe2493a7ddb707e1a308fa445172a24b3ea60732f75f5c69b41fc11467ee93f37c9a6f7285ba42f716e2a0e30909056ea3e4f7985d14ca9ab280cc184ce98e2a0722d0447aa1a2eedc5e53ddfa53731df7eced406b10627b0bebd768a30bde0d470c0f1d10adc070f8d3029cacceec74e4833f4dc8c52c3f41733f5f896fceb425d0737e717a63bfb033df46286d99594dd01e2bd0a942ab792874177b32842f4833bc0340ddb74852e9cd6f29f1d997a4a4bf05dd5d12011f95e6ce18928e3a9b83b24d15f989bdf43370bcc657c3ac6601eaf5e951efdbd7ee69b1623dc5039b2dfc640692378ef032f17bc36cc00293ad90b7e18f5feb8f287a7061ed9713929aed9b14b8d566199fc7822b1c38daa16b6d83077b10af0e2b6e531ccc34ea248ea593128c9ff17defcee6618c29cd2d93cfed99b90319104b1fdcfea91e98b41d792782840fb7b25280d8565b0bcd874e79b1b323139e7fc88eb5f80f690ce30fcd81111076adb31de6aeced879b538c0b5f2b74c027cc582a540133952cb021424510312f13e15d403f700f3e15b41d677c5a1e7c4e692c5880cb4522c48e993381996a29615d2956781509cd74aec6a3c73b8536d1817e473dad4cbb1787e046606b692a44e5d21ef6b5219658b002f674367e90a2b610924e9ac543362257d4567728f2e61f61231cb5d7816e100bb6f6bd9a42329b728b18d7a696711650c16fd476e2f471f38af0f6b00d45c6ecadbc5b61e5bb642046fd1c8ba6168b9499ec8acb3fda46cc37e2e442629cbdf");
1104+
}
1105+
9691106
fn do_test_fail_htlc_backwards_with_reason(failure_code: FailureCode) {
9701107

9711108
let chanmon_cfgs = create_chanmon_cfgs(2);

lightning/src/ln/onion_utils.rs

+17
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,23 @@ pub(super) fn construct_onion_packet(
291291
)
292292
}
293293

294+
pub(super) fn construct_trampoline_onion_packet(
295+
payloads: Vec<msgs::OutboundTrampolinePayload>, onion_keys: Vec<OnionKeys>,
296+
prng_seed: [u8; 32], associated_data: &PaymentHash, length: u16,
297+
) -> Result<msgs::TrampolineOnionPacket, ()> {
298+
let mut packet_data = vec![0u8; length as usize];
299+
300+
let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
301+
chacha.process(&vec![0u8; length as usize], &mut packet_data);
302+
303+
construct_onion_packet_with_init_noise::<_, _>(
304+
payloads,
305+
onion_keys,
306+
packet_data,
307+
Some(associated_data),
308+
)
309+
}
310+
294311
#[cfg(test)]
295312
/// Used in testing to write bogus `BogusOnionHopData` as well as `RawOnionHopData`, which is
296313
/// otherwise not representable in `msgs::OnionHopData`.

0 commit comments

Comments
 (0)