From 91c65887deb85cc2256d1adfe4631d4c3a34627a Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Tue, 28 Nov 2023 13:23:15 -0800 Subject: [PATCH 1/6] Add trampoline feature flag. --- lightning/src/ln/features.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lightning/src/ln/features.rs b/lightning/src/ln/features.rs index d10c3a71927..500d5c0eea1 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -166,6 +166,8 @@ mod sealed { ChannelType | SCIDPrivacy, // Byte 6 ZeroConf | Keysend, + // Byte 7 + Trampoline, ]); define_context!(ChannelContext, []); define_context!(Bolt11InvoiceContext, [ @@ -415,6 +417,9 @@ mod sealed { define_feature!(55, Keysend, [NodeContext], "Feature flags for keysend payments.", set_keysend_optional, set_keysend_required, supports_keysend, requires_keysend); + define_feature!(57, Trampoline, [NodeContext], + "Feature flags for Trampoline routing.", set_trampoline_routing_optional, set_trampoline_routing_required, + supports_trampoline_routing, requires_trampoline_routing); // Note: update the module-level docs when a new feature bit is added! #[cfg(test)] From 7a1888a9bbbd596f92b8eafa1a3993deefa52b63 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Tue, 28 Nov 2023 12:17:17 -0800 Subject: [PATCH 2/6] Add trampoline packet field to `OutboundOnionPayload::Receive`. --- lightning/src/ln/msgs.rs | 14 ++++++++++++-- lightning/src/ln/onion_utils.rs | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 2d871b354a2..039b4c361ac 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1670,6 +1670,7 @@ mod fuzzy_internal_msgs { use crate::prelude::*; use crate::ln::{PaymentPreimage, PaymentSecret}; use crate::ln::features::BlindedHopFeatures; + use crate::ln::msgs::OnionPacket; // These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize // them from untrusted input): @@ -1727,6 +1728,7 @@ mod fuzzy_internal_msgs { custom_tlvs: Vec<(u64, Vec)>, amt_msat: u64, outgoing_cltv_value: u32, + trampoline_packet: Option }, BlindedForward { encrypted_tlvs: Vec, @@ -2277,13 +2279,17 @@ impl Writeable for OutboundOnionPayload { }, Self::Receive { ref payment_data, ref payment_metadata, ref keysend_preimage, amt_msat, - outgoing_cltv_value, ref custom_tlvs, + outgoing_cltv_value, ref trampoline_packet, ref custom_tlvs } => { // We need to update [`ln::outbound_payment::RecipientOnionFields::with_custom_tlvs`] // to reject any reserved types in the experimental range if new ones are ever // standardized. let keysend_tlv = keysend_preimage.map(|preimage| (5482373484, preimage.encode())); - let mut custom_tlvs: Vec<&(u64, Vec)> = custom_tlvs.iter().chain(keysend_tlv.iter()).collect(); + let trampoline_tlv = trampoline_packet.as_ref().map(|trampoline| (66100, trampoline.encode())); + let mut custom_tlvs: Vec<&(u64, Vec)> = custom_tlvs.iter() + .chain(keysend_tlv.iter()) + .chain(trampoline_tlv.iter()) + .collect(); custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ); _encode_varint_length_prefixed_tlv!(w, { (2, HighZeroBytesDroppedBigSize(*amt_msat), required), @@ -4002,6 +4008,7 @@ mod tests { amt_msat: 0x0badf00d01020304, outgoing_cltv_value: 0xffffffff, custom_tlvs: vec![], + trampoline_packet: None }; let encoded_value = outbound_msg.encode(); let target_value = >::from_hex("1002080badf00d010203040404ffffffff").unwrap(); @@ -4030,6 +4037,7 @@ mod tests { amt_msat: 0x0badf00d01020304, outgoing_cltv_value: 0xffffffff, custom_tlvs: vec![], + trampoline_packet: None }; let encoded_value = outbound_msg.encode(); let target_value = >::from_hex("3602080badf00d010203040404ffffffff082442424242424242424242424242424242424242424242424242424242424242421badca1f").unwrap(); @@ -4069,6 +4077,7 @@ mod tests { custom_tlvs: bad_type_range_tlvs, amt_msat: 0x0badf00d01020304, outgoing_cltv_value: 0xffffffff, + trampoline_packet: None }; let encoded_value = msg.encode(); let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet); @@ -4101,6 +4110,7 @@ mod tests { custom_tlvs: expected_custom_tlvs.clone(), amt_msat: 0x0badf00d01020304, outgoing_cltv_value: 0xffffffff, + trampoline_packet: None }; let encoded_value = msg.encode(); let target_value = >::from_hex("2e02080badf00d010203040404ffffffffff0000000146c6616b021234ff0000000146c6616f084242424242424242").unwrap(); diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 051e78c46ee..f5e80a12dce 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -216,6 +216,7 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o custom_tlvs: recipient_onion.custom_tlvs.clone(), amt_msat: value_msat, outgoing_cltv_value: cltv, + trampoline_packet: None }); } } else { From 1225322d2c8d6e0220c63b5cd8fc75b6d9d36ec9 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Mon, 4 Dec 2023 10:28:39 -0800 Subject: [PATCH 3/6] Use variable-length onions for Trampoline. --- lightning/src/ln/msgs.rs | 43 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 039b4c361ac..feb76828845 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1670,7 +1670,7 @@ mod fuzzy_internal_msgs { use crate::prelude::*; use crate::ln::{PaymentPreimage, PaymentSecret}; use crate::ln::features::BlindedHopFeatures; - use crate::ln::msgs::OnionPacket; + use crate::ln::msgs::VariableLengthOnionPacket; // These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize // them from untrusted input): @@ -1728,7 +1728,7 @@ mod fuzzy_internal_msgs { custom_tlvs: Vec<(u64, Vec)>, amt_msat: u64, outgoing_cltv_value: u32, - trampoline_packet: Option + trampoline_packet: Option }, BlindedForward { encrypted_tlvs: Vec, @@ -1789,6 +1789,29 @@ impl fmt::Debug for OnionPacket { } } +/// BOLT 4 onion packet including hop data for the next peer. +#[derive(Clone, Hash, PartialEq, Eq)] +pub struct VariableLengthOnionPacket { + /// BOLT 4 version number. + pub version: u8, + /// In order to ensure we always return an error on onion decode in compliance with [BOLT + /// #4](https://github.com/lightning/bolts/blob/master/04-onion-routing.md), we have to + /// deserialize `OnionPacket`s contained in [`UpdateAddHTLC`] messages even if the ephemeral + /// public key (here) is bogus, so we hold a [`Result`] instead of a [`PublicKey`] as we'd + /// like. + pub public_key: Result, + /// Variable number of bytes encrypted payload for the next hop; 650 by default for Trampoline + pub(crate) hop_data: Vec, + /// HMAC to verify the integrity of hop_data. + pub hmac: [u8; 32], +} + +impl fmt::Debug for VariableLengthOnionPacket { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("VariableLengthOnionPacket version {} with hmac {:?}", self.version, &self.hmac[..])) + } +} + #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub(crate) struct OnionErrorPacket { // This really should be a constant size slice, but the spec lets these things be up to 128KB? @@ -2217,6 +2240,22 @@ impl Readable for OnionPacket { } } +// This will be written as the value of a TLV, so when reading, the length of the hop data will be +// inferred from a ReadableArg containing the total length. The standard hop data for Trampoline +// onions will prospectively be 650 bytes. +impl Writeable for VariableLengthOnionPacket { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.version.write(w)?; + match self.public_key { + Ok(pubkey) => pubkey.write(w)?, + Err(_) => [0u8;33].write(w)?, + } + &self.hop_data.write(w)?; + &self.hmac.write(w)?; + Ok(()) + } +} + impl_writeable_msg!(UpdateAddHTLC, { channel_id, htlc_id, From 6870049cc6f289e05143ef1550c6da0c0a960493 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Tue, 5 Dec 2023 10:47:58 -0800 Subject: [PATCH 4/6] Add Trampoline packet encoding test. --- lightning/src/ln/msgs.rs | 43 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index feb76828845..010dfc441d2 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -2878,10 +2878,10 @@ mod tests { use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret}; use crate::ln::ChannelId; use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; - use crate::ln::msgs::{self, FinalOnionHopData, OnionErrorPacket}; + use crate::ln::msgs::{self, FinalOnionHopData, OnionErrorPacket, VariableLengthOnionPacket}; use crate::ln::msgs::SocketAddress; use crate::routing::gossip::{NodeAlias, NodeId}; - use crate::util::ser::{Writeable, Readable, ReadableArgs, Hostname, TransactionU16LenLimited}; + use crate::util::ser::{Writeable, Readable, ReadableArgs, Hostname, TransactionU16LenLimited, BigSize}; use crate::util::test_utils; use bitcoin::hashes::hex::FromHex; @@ -4171,6 +4171,45 @@ mod tests { } else { panic!(); } } + #[test] + fn encoding_final_onion_hop_data_with_trampoline_packet() { + let secp_ctx = Secp256k1::new(); + let (private_key, public_key) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx); + + let compressed_public_key = public_key.serialize(); + assert_eq!(compressed_public_key.len(), 33); + + let trampoline_packet = VariableLengthOnionPacket { + version: 0, + public_key: Ok(public_key), + hop_data: vec![1; 650], // this should be the standard encoded length + hmac: [2; 32], + }; + let encoded_trampoline_packet = trampoline_packet.encode(); + assert_eq!(encoded_trampoline_packet.len(), 718); + + let msg = msgs::OutboundOnionPayload::Receive { + payment_data: None, + payment_metadata: None, + keysend_preimage: None, + custom_tlvs: Vec::new(), + amt_msat: 0x0badf00d01020304, + outgoing_cltv_value: 0xffffffff, + trampoline_packet: Some(trampoline_packet) + }; + let encoded_payload = msg.encode(); + + let trampoline_type_bytes = &encoded_payload[19..=23]; + let mut trampoline_type_cursor = Cursor::new(trampoline_type_bytes); + let trampoline_type_big_size: BigSize = Readable::read(&mut trampoline_type_cursor).unwrap(); + assert_eq!(trampoline_type_big_size.0, 66100); + + let trampoline_length_bytes = &encoded_payload[24..=26]; + let mut trampoline_length_cursor = Cursor::new(trampoline_length_bytes); + let trampoline_length_big_size: BigSize = Readable::read(&mut trampoline_length_cursor).unwrap(); + assert_eq!(trampoline_length_big_size.0, encoded_trampoline_packet.len() as u64); + } + #[test] fn query_channel_range_end_blocknum() { let tests: Vec<(u32, u32, u32)> = vec![ From 5c2aea5898b505153e695514e84b50bafb2a4900 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Tue, 5 Dec 2023 16:57:04 -0800 Subject: [PATCH 5/6] Serialize hop_data without length in VariableLengthOnionPacket. --- lightning/src/ln/msgs.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 010dfc441d2..0408f941757 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -2250,7 +2250,8 @@ impl Writeable for VariableLengthOnionPacket { Ok(pubkey) => pubkey.write(w)?, Err(_) => [0u8;33].write(w)?, } - &self.hop_data.write(w)?; + // don't encode the length of hop_data + w.write_all(&self.hop_data)?; &self.hmac.write(w)?; Ok(()) } @@ -4186,7 +4187,7 @@ mod tests { hmac: [2; 32], }; let encoded_trampoline_packet = trampoline_packet.encode(); - assert_eq!(encoded_trampoline_packet.len(), 718); + assert_eq!(encoded_trampoline_packet.len(), 716); let msg = msgs::OutboundOnionPayload::Receive { payment_data: None, @@ -4210,6 +4211,28 @@ mod tests { assert_eq!(trampoline_length_big_size.0, encoded_trampoline_packet.len() as u64); } + #[test] + fn encoding_final_onion_hop_data_with_eclair_trampoline_packet() { + let public_key = PublicKey::from_slice(&>::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()).unwrap(); + let hop_data = >::from_hex("cff34152f3a36e52ca94e74927203a560392b9cc7ce3c45809c6be52166c24a595716880f95f178bf5b30ca63141f74db6e92795c6130877cfdac3d4bd3087ee73c65d627ddd709112a848cc99e303f3706509aa43ba7c8a88cba175fccf9a8f5016ef06d3b935dbb15196d7ce16dc1a7157845566901d7b2197e52cab4ce487014b14816e5805f9fcacb4f8f88b8ff176f1b94f6ce6b00bc43221130c17d20ef629db7c5f7eafaa166578c720619561dd14b3277db557ec7dcdb793771aef0f2f667cfdbeae3ac8d331c5994779dffb31e5fc0dbdedc0c592ca6d21c18e47fe3528d6975c19517d7e2ea8c5391cf17d0fe30c80913ed887234ccb48808f7ef9425bcd815c3b586210979e3bb286ef2851bf9ce04e28c40a203df98fd648d2f1936fd2f1def0e77eecb277229b4b682322371c0a1dbfcd723a991993df8cc1f2696b84b055b40a1792a29f710295a18fbd351b0f3ff34cd13941131b8278ba79303c89117120eea691738a9954908195143b039dbeed98f26a92585f3d15cf742c953799d3272e0545e9b744be9d3b4c").unwrap(); + let hmac_vector = >::from_hex("bb079bfc4b35190eee9f59a1d7b41ba2f773179f322dafb4b1af900c289ebd6c").unwrap(); + let mut hmac = [0; 32]; + hmac.copy_from_slice(&hmac_vector); + + let compressed_public_key = public_key.serialize(); + assert_eq!(compressed_public_key.len(), 33); + + let trampoline_packet = VariableLengthOnionPacket { + version: 0, + public_key: Ok(public_key), + hop_data, + hmac + }; + let encoded_trampoline_packet = trampoline_packet.encode(); + let expected_eclair_trampoline_packet = >::from_hex("0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619cff34152f3a36e52ca94e74927203a560392b9cc7ce3c45809c6be52166c24a595716880f95f178bf5b30ca63141f74db6e92795c6130877cfdac3d4bd3087ee73c65d627ddd709112a848cc99e303f3706509aa43ba7c8a88cba175fccf9a8f5016ef06d3b935dbb15196d7ce16dc1a7157845566901d7b2197e52cab4ce487014b14816e5805f9fcacb4f8f88b8ff176f1b94f6ce6b00bc43221130c17d20ef629db7c5f7eafaa166578c720619561dd14b3277db557ec7dcdb793771aef0f2f667cfdbeae3ac8d331c5994779dffb31e5fc0dbdedc0c592ca6d21c18e47fe3528d6975c19517d7e2ea8c5391cf17d0fe30c80913ed887234ccb48808f7ef9425bcd815c3b586210979e3bb286ef2851bf9ce04e28c40a203df98fd648d2f1936fd2f1def0e77eecb277229b4b682322371c0a1dbfcd723a991993df8cc1f2696b84b055b40a1792a29f710295a18fbd351b0f3ff34cd13941131b8278ba79303c89117120eea691738a9954908195143b039dbeed98f26a92585f3d15cf742c953799d3272e0545e9b744be9d3b4cbb079bfc4b35190eee9f59a1d7b41ba2f773179f322dafb4b1af900c289ebd6c").unwrap(); + assert_eq!(encoded_trampoline_packet, expected_eclair_trampoline_packet); + } + #[test] fn query_channel_range_end_blocknum() { let tests: Vec<(u32, u32, u32)> = vec![ From 3184ad405f2090231e22241e6d129ca8ea5ff3dc Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Dec 2023 18:58:56 +0530 Subject: [PATCH 6/6] Introduce Trampoline Packet deserialization for Inbound::Receive - This PR introduces Trampoline Packets to Inbound::Receive - Implement Readable for VariableLengthOnionPacket struct to allow deserialization for Trampoline Packets. - Make appropriate changes in rest of code, to allow proper compiling with the new change. --- lightning/src/ln/channelmanager.rs | 3 +++ lightning/src/ln/msgs.rs | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index a3842285263..7d3ef3f0fd6 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -11867,6 +11867,7 @@ mod tests { payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat, }), custom_tlvs: Vec::new(), + trampoline_packet: None }; // Check that if the amount we received + the penultimate hop extra fee is less than the sender // intended amount, we fail the payment. @@ -11889,6 +11890,7 @@ mod tests { payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat, }), custom_tlvs: Vec::new(), + trampoline_packet: None }; let current_height: u32 = node[0].node.best_block.read().unwrap().height(); assert!(create_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]), @@ -11913,6 +11915,7 @@ mod tests { payment_secret: PaymentSecret([0; 32]), total_msat: 100, }), custom_tlvs: Vec::new(), + trampoline_packet: None }, [0; 32], PaymentHash([0; 32]), 100, 23, None, true, None, current_height, node[0].node.default_configuration.accept_mpp_keysend); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 0408f941757..75afdc6d367 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1696,6 +1696,7 @@ mod fuzzy_internal_msgs { custom_tlvs: Vec<(u64, Vec)>, amt_msat: u64, outgoing_cltv_value: u32, + trampoline_packet: Option }, BlindedForward { short_channel_id: u64, @@ -1806,6 +1807,21 @@ pub struct VariableLengthOnionPacket { pub hmac: [u8; 32], } +impl Readable for VariableLengthOnionPacket { + fn read(r: &mut R) -> Result { + Ok(VariableLengthOnionPacket { + version: Readable::read(r)?, + public_key: { + let mut buf = [0u8;33]; + r.read_exact(&mut buf)?; + PublicKey::from_slice(&buf) + }, + hop_data: Readable::read(r)?, + hmac: Readable::read(r)?, + }) + } +} + impl fmt::Debug for VariableLengthOnionPacket { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_fmt(format_args!("VariableLengthOnionPacket version {} with hmac {:?}", self.version, &self.hmac[..])) @@ -2373,6 +2389,7 @@ impl ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node let mut total_msat = None; let mut keysend_preimage: Option = None; let mut custom_tlvs = Vec::new(); + let mut trampoline_packet: Option = None; let tlv_len = BigSize::read(r)?; let rd = FixedLengthReader::new(r, tlv_len.0); @@ -2385,6 +2402,7 @@ impl ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node (12, intro_node_blinding_point, option), (16, payment_metadata, option), (18, total_msat, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), + (66100, trampoline_packet, option), // See https://github.com/lightning/blips/blob/master/blip-0003.md (5482373484, keysend_preimage, option) }, |msg_type: u64, msg_reader: &mut FixedLengthReader<_>| -> Result { @@ -2461,6 +2479,7 @@ impl ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node amt_msat: amt.ok_or(DecodeError::InvalidValue)?, outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?, custom_tlvs, + trampoline_packet }) } } @@ -4094,6 +4113,7 @@ mod tests { payment_metadata: None, keysend_preimage: None, custom_tlvs, + .. } = inbound_msg { assert_eq!(payment_secret, expected_payment_secret); assert_eq!(amt_msat, 0x0badf00d01020304);