Merge pull request #2756 from arik-so/arik/trampoline/2023-11-outbound
authorvalentinewallace <valentinewallace@users.noreply.github.com>
Fri, 22 Mar 2024 14:41:19 +0000 (10:41 -0400)
committerGitHub <noreply@github.com>
Fri, 22 Mar 2024 14:41:19 +0000 (10:41 -0400)
Serialize Trampoline payloads in outbound onions.

1  2 
lightning/src/ln/msgs.rs

diff --combined lightning/src/ln/msgs.rs
index 034b2451693975f04da3a310b432be6b2ea4de85,bb6d025606047fecb65be7341294e52229fe0f4b..2efc3c1c051798eebcad6fc800502d85ab683659
@@@ -396,10 -396,6 +396,10 @@@ pub struct ChannelReady 
        pub short_channel_id_alias: Option<u64>,
  }
  
 +/// A randomly chosen number that is used to identify inputs within an interactive transaction
 +/// construction.
 +pub type SerialId = u64;
 +
  /// An stfu (quiescence) message to be sent by or received from the stfu initiator.
  // TODO(splicing): Add spec link for `stfu`; still in draft, using from https://github.com/lightning/bolts/pull/863
  #[derive(Clone, Debug, PartialEq, Eq)]
@@@ -463,7 -459,7 +463,7 @@@ pub struct TxAddInput 
        pub channel_id: ChannelId,
        /// A randomly chosen unique identifier for this input, which is even for initiators and odd for
        /// non-initiators.
 -      pub serial_id: u64,
 +      pub serial_id: SerialId,
        /// Serialized transaction that contains the output this input spends to verify that it is non
        /// malleable.
        pub prevtx: TransactionU16LenLimited,
@@@ -482,7 -478,7 +482,7 @@@ pub struct TxAddOutput 
        pub channel_id: ChannelId,
        /// A randomly chosen unique identifier for this output, which is even for initiators and odd for
        /// non-initiators.
 -      pub serial_id: u64,
 +      pub serial_id: SerialId,
        /// The satoshi value of the output
        pub sats: u64,
        /// The scriptPubKey for the output
@@@ -497,7 -493,7 +497,7 @@@ pub struct TxRemoveInput 
        /// The channel ID
        pub channel_id: ChannelId,
        /// The serial ID of the input to be removed
 -      pub serial_id: u64,
 +      pub serial_id: SerialId,
  }
  
  /// A tx_remove_output message for removing an output during interactive transaction construction.
@@@ -508,7 -504,7 +508,7 @@@ pub struct TxRemoveOutput 
        /// The channel ID
        pub channel_id: ChannelId,
        /// The serial ID of the output to be removed
 -      pub serial_id: u64,
 +      pub serial_id: SerialId,
  }
  
  /// A tx_complete message signalling the conclusion of a peer's transaction contributions during
@@@ -1140,16 -1136,8 +1140,16 @@@ pub struct UnsignedNodeAnnouncement 
        pub alias: NodeAlias,
        /// List of addresses on which this node is reachable
        pub addresses: Vec<SocketAddress>,
 -      pub(crate) excess_address_data: Vec<u8>,
 -      pub(crate) excess_data: Vec<u8>,
 +      /// Excess address data which was signed as a part of the message which we do not (yet) understand how
 +      /// to decode.
 +      ///
 +      /// This is stored to ensure forward-compatibility as new address types are added to the lightning gossip protocol.
 +      pub excess_address_data: Vec<u8>,
 +      /// Excess data which was signed as a part of the message which we do not (yet) understand how
 +      /// to decode.
 +      ///
 +      /// This is stored to ensure forward-compatibility as new fields are added to the lightning gossip protocol.
 +      pub excess_data: Vec<u8>,
  }
  #[derive(Clone, Debug, Hash, PartialEq, Eq)]
  /// A [`node_announcement`] message to be sent to or received from a peer.
@@@ -1666,7 -1654,7 +1666,7 @@@ mod fuzzy_internal_msgs 
        use crate::prelude::*;
        use crate::ln::{PaymentPreimage, PaymentSecret};
        use crate::ln::features::BlindedHopFeatures;
-       use super::FinalOnionHopData;
+       use super::{FinalOnionHopData, TrampolineOnionPacket};
  
        // These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize
        // them from untrusted input):
                        payment_secret: PaymentSecret,
                        payment_constraints: PaymentConstraints,
                        intro_node_blinding_point: Option<PublicKey>,
 +                      keysend_preimage: Option<PaymentPreimage>,
                }
        }
  
                        amt_to_forward: u64,
                        outgoing_cltv_value: u32,
                },
+               #[allow(unused)]
+               TrampolineEntrypoint {
+                       amt_to_forward: u64,
+                       outgoing_cltv_value: u32,
+                       multipath_trampoline_data: Option<FinalOnionHopData>,
+                       trampoline_packet: TrampolineOnionPacket,
+               },
                Receive {
                        payment_data: Option<FinalOnionHopData>,
                        payment_metadata: Option<Vec<u8>>,
                        cltv_expiry_height: u32,
                        encrypted_tlvs: Vec<u8>,
                        intro_node_blinding_point: Option<PublicKey>, // Set if the introduction node of the blinded path is the final node
 +                      keysend_preimage: Option<PaymentPreimage>,
                }
        }
  
@@@ -1779,6 -1772,52 +1786,52 @@@ impl fmt::Debug for OnionPacket 
        }
  }
  
+ /// BOLT 4 onion packet including hop data for the next peer.
+ #[derive(Clone, Hash, PartialEq, Eq)]
+ pub struct TrampolineOnionPacket {
+       /// Bolt 04 version number
+       pub version: u8,
+       /// A random sepc256k1 point, used to build the ECDH shared secret to decrypt hop_data
+       pub public_key: PublicKey,
+       /// Encrypted payload for the next hop
+       //
+       // Unlike the onion packets used for payments, Trampoline onion packets have to be shorter than
+       // 1300 bytes. The expected default is 650 bytes.
+       // TODO: if 650 ends up being the most common size, optimize this to be:
+       // enum { ThirteenHundred([u8; 650]), VarLen(Vec<u8>) }
+       pub hop_data: Vec<u8>,
+       /// HMAC to verify the integrity of hop_data
+       pub hmac: [u8; 32],
+ }
+ impl onion_utils::Packet for TrampolineOnionPacket {
+       type Data = Vec<u8>;
+       fn new(public_key: PublicKey, hop_data: Vec<u8>, hmac: [u8; 32]) -> Self {
+               Self {
+                       version: 0,
+                       public_key,
+                       hop_data,
+                       hmac,
+               }
+       }
+ }
+ impl Writeable for TrampolineOnionPacket {
+       fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+               self.version.write(w)?;
+               self.public_key.write(w)?;
+               w.write_all(&self.hop_data)?;
+               self.hmac.write(w)?;
+               Ok(())
+       }
+ }
+ impl Debug for TrampolineOnionPacket {
+       fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+               f.write_fmt(format_args!("TrampolineOnionPacket 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?
@@@ -2492,6 -2531,17 +2545,17 @@@ impl Writeable for OutboundOnionPayloa
                                        (6, short_channel_id, required)
                                });
                        },
+                       Self::TrampolineEntrypoint {
+                               amt_to_forward, outgoing_cltv_value, ref multipath_trampoline_data,
+                               ref trampoline_packet
+                       } => {
+                               _encode_varint_length_prefixed_tlv!(w, {
+                                       (2, HighZeroBytesDroppedBigSize(*amt_to_forward), required),
+                                       (4, HighZeroBytesDroppedBigSize(*outgoing_cltv_value), required),
+                                       (8, multipath_trampoline_data, option),
+                                       (20, trampoline_packet, required)
+                               });
+                       },
                        Self::Receive {
                                ref payment_data, ref payment_metadata, ref keysend_preimage, sender_intended_htlc_amt_msat,
                                cltv_expiry_height, ref custom_tlvs,
                        },
                        Self::BlindedReceive {
                                sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, encrypted_tlvs,
 -                              intro_node_blinding_point,
 +                              intro_node_blinding_point, keysend_preimage,
                        } => {
                                _encode_varint_length_prefixed_tlv!(w, {
                                        (2, HighZeroBytesDroppedBigSize(*sender_intended_htlc_amt_msat), required),
                                        (4, HighZeroBytesDroppedBigSize(*cltv_expiry_height), required),
                                        (10, *encrypted_tlvs, required_vec),
                                        (12, intro_node_blinding_point, option),
 -                                      (18, HighZeroBytesDroppedBigSize(*total_msat), required)
 +                                      (18, HighZeroBytesDroppedBigSize(*total_msat), required),
 +                                      (5482373484, keysend_preimage, option)
                                });
                        },
                }
@@@ -2575,7 -2624,9 +2639,7 @@@ impl<NS: Deref> ReadableArgs<(Option<Pu
                }
  
                if let Some(blinding_point) = intro_node_blinding_point.or(update_add_blinding_point) {
 -                      if short_id.is_some() || payment_data.is_some() || payment_metadata.is_some() ||
 -                              keysend_preimage.is_some()
 -                      {
 +                      if short_id.is_some() || payment_data.is_some() || payment_metadata.is_some() {
                                return Err(DecodeError::InvalidValue)
                        }
                        let enc_tlvs = encrypted_tlvs_opt.ok_or(DecodeError::InvalidValue)?.0;
                                ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Forward(ForwardTlvs {
                                        short_channel_id, payment_relay, payment_constraints, features
                                })} => {
 -                                      if amt.is_some() || cltv_value.is_some() || total_msat.is_some() {
 +                                      if amt.is_some() || cltv_value.is_some() || total_msat.is_some() ||
 +                                              keysend_preimage.is_some()
 +                                      {
                                                return Err(DecodeError::InvalidValue)
                                        }
                                        Ok(Self::BlindedForward {
                                                payment_secret,
                                                payment_constraints,
                                                intro_node_blinding_point,
 +                                              keysend_preimage,
                                        })
                                },
                        }
@@@ -3059,10 -3107,10 +3123,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, CommonOpenChannelFields, CommonAcceptChannelFields};
+       use crate::ln::msgs::{self, FinalOnionHopData, OnionErrorPacket, CommonOpenChannelFields, CommonAcceptChannelFields, TrampolineOnionPacket};
        use crate::ln::msgs::SocketAddress;
        use crate::routing::gossip::{NodeAlias, NodeId};
-       use crate::util::ser::{Writeable, Readable, ReadableArgs, Hostname, TransactionU16LenLimited};
+       use crate::util::ser::{BigSize, Hostname, Readable, ReadableArgs, TransactionU16LenLimited, Writeable};
        use crate::util::test_utils;
  
        use bitcoin::hashes::hex::FromHex;
                } 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 = TrampolineOnionPacket {
+                       version: 0,
+                       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::TrampolineEntrypoint {
+                       multipath_trampoline_data: None,
+                       amt_to_forward: 0x0badf00d01020304,
+                       outgoing_cltv_value: 0xffffffff,
+                       trampoline_packet,
+               };
+               let encoded_payload = msg.encode();
+               let trampoline_type_bytes = &encoded_payload[19..=19];
+               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, 20);
+               let trampoline_length_bytes = &encoded_payload[20..=22];
+               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(&<Vec<u8>>::from_hex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()).unwrap();
+               let hop_data = <Vec<u8>>::from_hex("cff34152f3a36e52ca94e74927203a560392b9cc7ce3c45809c6be52166c24a595716880f95f178bf5b30ca63141f74db6e92795c6130877cfdac3d4bd3087ee73c65d627ddd709112a848cc99e303f3706509aa43ba7c8a88cba175fccf9a8f5016ef06d3b935dbb15196d7ce16dc1a7157845566901d7b2197e52cab4ce487014b14816e5805f9fcacb4f8f88b8ff176f1b94f6ce6b00bc43221130c17d20ef629db7c5f7eafaa166578c720619561dd14b3277db557ec7dcdb793771aef0f2f667cfdbeae3ac8d331c5994779dffb31e5fc0dbdedc0c592ca6d21c18e47fe3528d6975c19517d7e2ea8c5391cf17d0fe30c80913ed887234ccb48808f7ef9425bcd815c3b586210979e3bb286ef2851bf9ce04e28c40a203df98fd648d2f1936fd2f1def0e77eecb277229b4b682322371c0a1dbfcd723a991993df8cc1f2696b84b055b40a1792a29f710295a18fbd351b0f3ff34cd13941131b8278ba79303c89117120eea691738a9954908195143b039dbeed98f26a92585f3d15cf742c953799d3272e0545e9b744be9d3b4c").unwrap();
+               let hmac_vector = <Vec<u8>>::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 = TrampolineOnionPacket {
+                       version: 0,
+                       public_key,
+                       hop_data,
+                       hmac,
+               };
+               let encoded_trampoline_packet = trampoline_packet.encode();
+               let expected_eclair_trampoline_packet = <Vec<u8>>::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![