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/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)] diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 2d871b354a2..75afdc6d367 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::VariableLengthOnionPacket; // These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize // them from untrusted input): @@ -1695,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, @@ -1727,6 +1729,7 @@ mod fuzzy_internal_msgs { custom_tlvs: Vec<(u64, Vec)>, amt_msat: u64, outgoing_cltv_value: u32, + trampoline_packet: Option }, BlindedForward { encrypted_tlvs: Vec, @@ -1787,6 +1790,44 @@ 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 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[..])) + } +} + #[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? @@ -2215,6 +2256,23 @@ 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)?, + } + // don't encode the length of hop_data + w.write_all(&self.hop_data)?; + &self.hmac.write(w)?; + Ok(()) + } +} + impl_writeable_msg!(UpdateAddHTLC, { channel_id, htlc_id, @@ -2277,13 +2335,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), @@ -2327,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); @@ -2339,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 { @@ -2415,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 }) } } @@ -2833,10 +2898,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; @@ -4002,6 +4067,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 +4096,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(); @@ -4046,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); @@ -4069,6 +4137,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 +4170,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(); @@ -4122,6 +4192,67 @@ 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(), 716); + + 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 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![ 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 {