From 53996cc8aba4112fda9342ccea4e571f443932df Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 25 Oct 2024 20:32:16 +0530 Subject: [PATCH 1/4] Add Custom TLVs for `message::ReceiveTlvs` - This update lets users add their own custom TLVs to the reply path of a sent onion message. - By including these custom TLVs, users can send custom data with the message and get it back in the response. --- fuzz/src/chanmon_consistency.rs | 4 +- fuzz/src/full_stack.rs | 4 +- fuzz/src/onion_message.rs | 6 +- lightning/src/blinded_path/message.rs | 18 +++-- lightning/src/ln/channelmanager.rs | 8 +- lightning/src/ln/offers_tests.rs | 10 +-- lightning/src/ln/peer_handler.rs | 2 +- .../src/onion_message/functional_tests.rs | 34 ++++---- lightning/src/onion_message/messenger.rs | 77 ++++++++++--------- lightning/src/onion_message/offers.rs | 2 +- lightning/src/onion_message/packet.rs | 2 + lightning/src/util/test_utils.rs | 8 +- 12 files changed, 92 insertions(+), 83 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 821acfc5301..3e00cdebc4f 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -142,8 +142,8 @@ impl MessageRouter for FuzzRouter { } fn create_blinded_paths( - &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, - _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _context: MessageContext, _custom_tlvs: Vec, + _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 59f1275a600..15f4d1486ae 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -177,8 +177,8 @@ impl MessageRouter for FuzzRouter { } fn create_blinded_paths( - &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, - _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _context: MessageContext, _custom_tlvs: Vec, + _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index edf304b5467..1b4c5556bec 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -100,8 +100,8 @@ impl MessageRouter for TestMessageRouter { } fn create_blinded_paths( - &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, - _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _context: MessageContext, _custom_tlvs: Vec, + _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() } @@ -112,7 +112,7 @@ struct TestOffersMessageHandler {} impl OffersMessageHandler for TestOffersMessageHandler { fn handle_message( &self, _message: OffersMessage, _context: Option, - _responder: Option, + _custom_tlvs: Option>, _responder: Option, ) -> Option<(OffersMessage, ResponseInstruction)> { None } diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 4d96434dd63..9a1357110d4 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -55,9 +55,9 @@ impl Readable for BlindedMessagePath { impl BlindedMessagePath { /// Create a one-hop blinded path for a message. pub fn one_hop( - recipient_node_id: PublicKey, context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1 + recipient_node_id: PublicKey, context: MessageContext, custom_tlvs: Vec, entropy_source: ES, secp_ctx: &Secp256k1 ) -> Result where ES::Target: EntropySource { - Self::new(&[], recipient_node_id, context, entropy_source, secp_ctx) + Self::new(&[], recipient_node_id, context, custom_tlvs, entropy_source, secp_ctx) } /// Create a path for an onion message, to be forwarded along `node_pks`. The last node @@ -67,7 +67,7 @@ impl BlindedMessagePath { // TODO: make all payloads the same size with padding + add dummy hops pub fn new( intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey, - context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1, + context: MessageContext, custom_tlvs: Vec, entropy_source: ES, secp_ctx: &Secp256k1, ) -> Result where ES::Target: EntropySource { let introduction_node = IntroductionNode::NodeId( intermediate_nodes.first().map_or(recipient_node_id, |n| n.node_id) @@ -80,7 +80,7 @@ impl BlindedMessagePath { blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), blinded_hops: blinded_hops( secp_ctx, intermediate_nodes, recipient_node_id, - context, &blinding_secret, + context, custom_tlvs, &blinding_secret, ).map_err(|_| ())?, })) } @@ -241,7 +241,10 @@ pub(crate) struct ReceiveTlvs { /// If `context` is `Some`, it is used to identify the blinded path that this onion message is /// sending to. This is useful for receivers to check that said blinded path is being used in /// the right context. - pub context: Option + pub context: Option, + + /// Custom Tlvs. A user can use this to send custom tlvs information back to themself. + pub custom_tlvs: Option>, } impl Writeable for ForwardTlvs { @@ -265,6 +268,7 @@ impl Writeable for ReceiveTlvs { // TODO: write padding encode_tlv_stream!(writer, { (65537, self.context, option), + (65539, self.custom_tlvs, option), }); Ok(()) } @@ -456,7 +460,7 @@ impl_writeable_tlv_based!(DNSResolverContext, { /// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`. pub(super) fn blinded_hops( secp_ctx: &Secp256k1, intermediate_nodes: &[MessageForwardNode], - recipient_node_id: PublicKey, context: MessageContext, session_priv: &SecretKey, + recipient_node_id: PublicKey, context: MessageContext, custom_tlvs: Vec, session_priv: &SecretKey, ) -> Result, secp256k1::Error> { let pks = intermediate_nodes.iter().map(|node| node.node_id) .chain(core::iter::once(recipient_node_id)); @@ -468,7 +472,7 @@ pub(super) fn blinded_hops( None => NextMessageHop::NodeId(pubkey), }) .map(|next_hop| ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None })) - .chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs{ context: Some(context) }))); + .chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs{ context: Some(context), custom_tlvs: Some(custom_tlvs) }))); let path = pks.zip(tlvs); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index d575ae4a9b6..3ac9804a45b 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9792,7 +9792,7 @@ where .collect::>(); self.message_router - .create_blinded_paths(recipient, context, peers, secp_ctx) + .create_blinded_paths(recipient, context, Vec::new(), peers, secp_ctx) .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) } @@ -9820,7 +9820,7 @@ where .collect::>(); self.message_router - .create_compact_blinded_paths(recipient, MessageContext::Offers(context), peers, secp_ctx) + .create_compact_blinded_paths(recipient, MessageContext::Offers(context), Vec::new(), peers, secp_ctx) .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) } @@ -11268,7 +11268,7 @@ where L::Target: Logger, { fn handle_message( - &self, message: OffersMessage, context: Option, responder: Option, + &self, message: OffersMessage, context: Option, custom_tlvs: Option>, responder: Option, ) -> Option<(OffersMessage, ResponseInstruction)> { let secp_ctx = &self.secp_ctx; let expanded_key = &self.inbound_payment_key; @@ -11411,7 +11411,7 @@ where let nonce = Nonce::from_entropy_source(&*self.entropy_source); let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); let context = MessageContext::Offers(OffersContext::InboundPayment { payment_hash, nonce, hmac }); - Some((OffersMessage::Invoice(invoice), responder.respond_with_reply_path(context))) + Some((OffersMessage::Invoice(invoice), responder.respond_with_reply_path(context, custom_tlvs))) }, Err(error) => Some((OffersMessage::InvoiceError(error.into()), responder.respond())), } diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index b667ce2c05d..44f718b57ba 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -193,8 +193,8 @@ fn claim_bolt12_payment<'a, 'b, 'c>( fn extract_offer_nonce<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) -> Nonce { match node.onion_messenger.peel_onion_message(message) { - Ok(PeeledOnion::Receive(_, Some(MessageContext::Offers(OffersContext::InvoiceRequest { nonce })), _)) => nonce, - Ok(PeeledOnion::Receive(_, context, _)) => panic!("Unexpected onion message context: {:?}", context), + Ok(PeeledOnion::Receive(_, Some(MessageContext::Offers(OffersContext::InvoiceRequest { nonce })), _, _)) => nonce, + Ok(PeeledOnion::Receive(_, context, _, _)) => panic!("Unexpected onion message context: {:?}", context), Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"), Err(e) => panic!("Failed to process onion message {:?}", e), } @@ -204,7 +204,7 @@ fn extract_invoice_request<'a, 'b, 'c>( node: &Node<'a, 'b, 'c>, message: &OnionMessage ) -> (InvoiceRequest, BlindedMessagePath) { match node.onion_messenger.peel_onion_message(message) { - Ok(PeeledOnion::Receive(message, _, reply_path)) => match message { + Ok(PeeledOnion::Receive(message, _, _, reply_path)) => match message { ParsedOnionMessageContents::Offers(offers_message) => match offers_message { OffersMessage::InvoiceRequest(invoice_request) => (invoice_request, reply_path.unwrap()), OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), @@ -221,7 +221,7 @@ fn extract_invoice_request<'a, 'b, 'c>( fn extract_invoice<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) -> (Bolt12Invoice, BlindedMessagePath) { match node.onion_messenger.peel_onion_message(message) { - Ok(PeeledOnion::Receive(message, _, reply_path)) => match message { + Ok(PeeledOnion::Receive(message, _, _, reply_path)) => match message { ParsedOnionMessageContents::Offers(offers_message) => match offers_message { OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request), OffersMessage::Invoice(invoice) => (invoice, reply_path.unwrap()), @@ -240,7 +240,7 @@ fn extract_invoice_error<'a, 'b, 'c>( node: &Node<'a, 'b, 'c>, message: &OnionMessage ) -> InvoiceError { match node.onion_messenger.peel_onion_message(message) { - Ok(PeeledOnion::Receive(message, _, _)) => match message { + Ok(PeeledOnion::Receive(message, _, _, _)) => match message { ParsedOnionMessageContents::Offers(offers_message) => match offers_message { OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request), OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index 9470346f852..5658ccf066e 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -143,7 +143,7 @@ impl OnionMessageHandler for IgnoringMessageHandler { } impl OffersMessageHandler for IgnoringMessageHandler { - fn handle_message(&self, _message: OffersMessage, _context: Option, _responder: Option) -> Option<(OffersMessage, ResponseInstruction)> { + fn handle_message(&self, _message: OffersMessage, _context: Option, _custom_tlvs: Option>, _responder: Option) -> Option<(OffersMessage, ResponseInstruction)> { None } } diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index f9d73f05ff3..7f4d949c7d1 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -76,7 +76,7 @@ impl Drop for MessengerNode { struct TestOffersMessageHandler {} impl OffersMessageHandler for TestOffersMessageHandler { - fn handle_message(&self, _message: OffersMessage, _context: Option, _responder: Option) -> Option<(OffersMessage, ResponseInstruction)> { + fn handle_message(&self, _message: OffersMessage, _context: Option, _custom_tlvs: Option>, _responder: Option) -> Option<(OffersMessage, ResponseInstruction)> { None } } @@ -204,7 +204,7 @@ impl CustomOnionMessageHandler for TestCustomMessageHandler { match responder { Some(responder) if expectation.include_reply_path => { - Some((response, responder.respond_with_reply_path(MessageContext::Custom(context.unwrap_or_else(Vec::new))))) + Some((response, responder.respond_with_reply_path(MessageContext::Custom(context.unwrap_or_else(Vec::new)), None))) }, Some(responder) => Some((response, responder.respond())), None => None @@ -391,7 +391,7 @@ fn one_blinded_hop() { let secp_ctx = Secp256k1::new(); let context = MessageContext::Custom(Vec::new()); - let blinded_path = BlindedMessagePath::new(&[], nodes[1].node_id, context, &*nodes[1].entropy_source, &secp_ctx).unwrap(); + let blinded_path = BlindedMessagePath::new(&[], nodes[1].node_id, context, Vec::new(), &*nodes[1].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; nodes[0].messenger.send_onion_message(test_msg, instructions).unwrap(); @@ -407,7 +407,7 @@ fn two_unblinded_two_blinded() { let secp_ctx = Secp256k1::new(); let intermediate_nodes = [MessageForwardNode { node_id: nodes[3].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); - let blinded_path = BlindedMessagePath::new(&intermediate_nodes, nodes[4].node_id, context, &*nodes[4].entropy_source, &secp_ctx).unwrap(); + let blinded_path = BlindedMessagePath::new(&intermediate_nodes, nodes[4].node_id, context, Vec::new(), &*nodes[4].entropy_source, &secp_ctx).unwrap(); let path = OnionMessagePath { intermediate_nodes: vec![nodes[1].node_id, nodes[2].node_id], destination: Destination::BlindedPath(blinded_path), @@ -430,7 +430,7 @@ fn three_blinded_hops() { MessageForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, ]; let context = MessageContext::Custom(Vec::new()); - let blinded_path = BlindedMessagePath::new(&intermediate_nodes, nodes[3].node_id, context, &*nodes[3].entropy_source, &secp_ctx).unwrap(); + let blinded_path = BlindedMessagePath::new(&intermediate_nodes, nodes[3].node_id, context, Vec::new(), &*nodes[3].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -454,7 +454,7 @@ fn async_response_over_one_blinded_hop() { // 3. Simulate the creation of a Blinded Reply path provided by Bob. let secp_ctx = Secp256k1::new(); let context = MessageContext::Custom(Vec::new()); - let reply_path = BlindedMessagePath::new(&[], nodes[1].node_id, context, &*nodes[1].entropy_source, &secp_ctx).unwrap(); + let reply_path = BlindedMessagePath::new(&[], nodes[1].node_id, context, Vec::new(), &*nodes[1].entropy_source, &secp_ctx).unwrap(); // 4. Create a responder using the reply path for Alice. let responder = Some(Responder::new(reply_path)); @@ -491,7 +491,7 @@ fn async_response_with_reply_path_succeeds() { // Alice receives a message from Bob with an added reply_path for responding back. let message = TestCustomMessage::Ping; let context = MessageContext::Custom(Vec::new()); - let reply_path = BlindedMessagePath::new(&[], bob.node_id, context, &*bob.entropy_source, &secp_ctx).unwrap(); + let reply_path = BlindedMessagePath::new(&[], bob.node_id, context, Vec::new(), &*bob.entropy_source, &secp_ctx).unwrap(); // Alice asynchronously responds to Bob, expecting a response back from him. let responder = Responder::new(reply_path); @@ -529,7 +529,7 @@ fn async_response_with_reply_path_fails() { // Alice receives a message from Bob with an added reply_path for responding back. let message = TestCustomMessage::Ping; let context = MessageContext::Custom(Vec::new()); - let reply_path = BlindedMessagePath::new(&[], bob.node_id, context, &*bob.entropy_source, &secp_ctx).unwrap(); + let reply_path = BlindedMessagePath::new(&[], bob.node_id, context, Vec::new(), &*bob.entropy_source, &secp_ctx).unwrap(); // Alice tries to asynchronously respond to Bob, but fails because the nodes are unannounced and // disconnected. Thus, a reply path could no be created for the response. @@ -575,7 +575,7 @@ fn we_are_intro_node() { MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, ]; let context = MessageContext::Custom(Vec::new()); - let blinded_path = BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, &*nodes[2].entropy_source, &secp_ctx).unwrap(); + let blinded_path = BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, Vec::new(), &*nodes[2].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -586,7 +586,7 @@ fn we_are_intro_node() { // Try with a two-hop blinded path where we are the introduction node. let intermediate_nodes = [MessageForwardNode { node_id: nodes[0].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); - let blinded_path = BlindedMessagePath::new(&intermediate_nodes, nodes[1].node_id, context, &*nodes[1].entropy_source, &secp_ctx).unwrap(); + let blinded_path = BlindedMessagePath::new(&intermediate_nodes, nodes[1].node_id, context, Vec::new(), &*nodes[1].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -605,7 +605,7 @@ fn invalid_blinded_path_error() { let secp_ctx = Secp256k1::new(); let intermediate_nodes = [MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); - let mut blinded_path = BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, &*nodes[2].entropy_source, &secp_ctx).unwrap(); + let mut blinded_path = BlindedMessagePath::new(&intermediate_nodes, nodes[2].node_id, context, Vec::new(), &*nodes[2].entropy_source, &secp_ctx).unwrap(); blinded_path.clear_blinded_hops(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -631,7 +631,7 @@ fn reply_path() { MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, ]; let context = MessageContext::Custom(Vec::new()); - let reply_path = BlindedMessagePath::new(&intermediate_nodes, nodes[0].node_id, context, &*nodes[0].entropy_source, &secp_ctx).unwrap(); + let reply_path = BlindedMessagePath::new(&intermediate_nodes, nodes[0].node_id, context, Vec::new(), &*nodes[0].entropy_source, &secp_ctx).unwrap(); nodes[0].messenger.send_onion_message_using_path(path, test_msg.clone(), Some(reply_path)).unwrap(); nodes[3].custom_message_handler.expect_message(TestCustomMessage::Ping); pass_along_path(&nodes); @@ -646,14 +646,14 @@ fn reply_path() { MessageForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, ]; let context = MessageContext::Custom(Vec::new()); - let blinded_path = BlindedMessagePath::new(&intermediate_nodes, nodes[3].node_id, context, &*nodes[3].entropy_source, &secp_ctx).unwrap(); + let blinded_path = BlindedMessagePath::new(&intermediate_nodes, nodes[3].node_id, context, Vec::new(), &*nodes[3].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); let intermediate_nodes = [ MessageForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, ]; let context = MessageContext::Custom(Vec::new()); - let reply_path = BlindedMessagePath::new(&intermediate_nodes, nodes[0].node_id, context, &*nodes[0].entropy_source, &secp_ctx).unwrap(); + let reply_path = BlindedMessagePath::new(&intermediate_nodes, nodes[0].node_id, context, Vec::new(), &*nodes[0].entropy_source, &secp_ctx).unwrap(); let instructions = MessageSendInstructions::WithSpecifiedReplyPath { destination, reply_path }; nodes[0].messenger.send_onion_message(test_msg, instructions).unwrap(); @@ -746,7 +746,7 @@ fn requests_peer_connection_for_buffered_messages() { let intermediate_nodes = [MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let blinded_path = BlindedMessagePath::new( - &intermediate_nodes, nodes[2].node_id, context, &*nodes[0].entropy_source, &secp_ctx + &intermediate_nodes, nodes[2].node_id, context, Vec::new(), &*nodes[0].entropy_source, &secp_ctx ).unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -786,7 +786,7 @@ fn drops_buffered_messages_waiting_for_peer_connection() { let intermediate_nodes = [MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let blinded_path = BlindedMessagePath::new( - &intermediate_nodes, nodes[2].node_id, context, &*nodes[0].entropy_source, &secp_ctx + &intermediate_nodes, nodes[2].node_id, context, Vec::new(), &*nodes[0].entropy_source, &secp_ctx ).unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; @@ -838,7 +838,7 @@ fn intercept_offline_peer_oms() { let intermediate_nodes = [MessageForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let context = MessageContext::Custom(Vec::new()); let blinded_path = BlindedMessagePath::new( - &intermediate_nodes, nodes[2].node_id, context, &*nodes[2].entropy_source, &secp_ctx + &intermediate_nodes, nodes[2].node_id, context, Vec::new(), &*nodes[2].entropy_source, &secp_ctx ).unwrap(); let destination = Destination::BlindedPath(blinded_path); let instructions = MessageSendInstructions::WithoutReplyPath { destination }; diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 0331a1060b8..6cde87fd663 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -183,7 +183,7 @@ for OnionMessenger where /// # }) /// # } /// # fn create_blinded_paths( -/// # &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1 +/// # &self, _recipient: PublicKey, _context: MessageContext, _custom_tlvs: Vec, _peers: Vec, _secp_ctx: &Secp256k1 /// # ) -> Result, ()> { /// # unreachable!() /// # } @@ -239,7 +239,8 @@ for OnionMessenger where /// MessageForwardNode { node_id: hop_node_id4, short_channel_id: None }, /// ]; /// let context = MessageContext::Custom(Vec::new()); -/// let blinded_path = BlindedMessagePath::new(&hops, your_node_id, context, &keys_manager, &secp_ctx).unwrap(); +/// let custom_tlvs = Vec::new(); +/// let blinded_path = BlindedMessagePath::new(&hops, your_node_id, context, custom_tlvs, &keys_manager, &secp_ctx).unwrap(); /// /// // Send a custom onion message to a blinded path. /// let destination = Destination::BlindedPath(blinded_path); @@ -380,17 +381,17 @@ impl Responder { pub fn respond(self) -> ResponseInstruction { ResponseInstruction { destination: Destination::BlindedPath(self.reply_path), - context: None, + reply_data: None, } } /// Creates a [`ResponseInstruction`] for responding including a reply path. /// /// Use when the recipient needs to send back a reply to us. - pub fn respond_with_reply_path(self, context: MessageContext) -> ResponseInstruction { + pub fn respond_with_reply_path(self, context: MessageContext, custom_tlvs: Option>) -> ResponseInstruction { ResponseInstruction { destination: Destination::BlindedPath(self.reply_path), - context: Some(context), + reply_data: Some((context, custom_tlvs.unwrap_or_default())), } } } @@ -402,7 +403,7 @@ pub struct ResponseInstruction { /// [`Destination`] rather than an explicit [`BlindedMessagePath`] simplifies the logic in /// [`OnionMessenger::send_onion_message_internal`] somewhat. destination: Destination, - context: Option, + reply_data: Option<(MessageContext, Vec)>, } impl ResponseInstruction { @@ -430,6 +431,8 @@ pub enum MessageSendInstructions { /// The context to include in the reply path we'll give the recipient so they can respond /// to us. context: MessageContext, + /// Custom Tlvs included by user as they want + custom_tlvs: Vec, }, /// Indicates that a message should be sent without including a reply path, preventing the /// recipient from responding. @@ -456,7 +459,7 @@ pub trait MessageRouter { fn create_blinded_paths< T: secp256k1::Signing + secp256k1::Verification >( - &self, recipient: PublicKey, context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, + &self, recipient: PublicKey, context: MessageContext, custom_tlvs: Vec, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()>; /// Creates compact [`BlindedMessagePath`]s to the `recipient` node. The nodes in `peers` are @@ -475,14 +478,14 @@ pub trait MessageRouter { fn create_compact_blinded_paths< T: secp256k1::Signing + secp256k1::Verification >( - &self, recipient: PublicKey, context: MessageContext, + &self, recipient: PublicKey, context: MessageContext, custom_tlvs: Vec, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { let peers = peers .into_iter() .map(|MessageForwardNode { node_id, short_channel_id: _ }| node_id) .collect(); - self.create_blinded_paths(recipient, context, peers, secp_ctx) + self.create_blinded_paths(recipient, context, custom_tlvs, peers, secp_ctx) } } @@ -517,8 +520,8 @@ where I: ExactSizeIterator, T: secp256k1::Signing + secp256k1::Verification >( - network_graph: &G, recipient: PublicKey, context: MessageContext, peers: I, - entropy_source: &ES, secp_ctx: &Secp256k1, compact_paths: bool, + network_graph: &G, recipient: PublicKey, context: MessageContext, custom_tlvs: Vec, + peers: I, entropy_source: &ES, secp_ctx: &Secp256k1, compact_paths: bool, ) -> Result, ()> { // Limit the number of blinded paths that are computed. const MAX_PATHS: usize = 3; @@ -557,7 +560,7 @@ where let paths = peer_info.into_iter() .map(|(peer, _, _)| { - BlindedMessagePath::new(&[peer], recipient, context.clone(), &**entropy_source, secp_ctx) + BlindedMessagePath::new(&[peer], recipient, context.clone(), custom_tlvs.clone(), &**entropy_source, secp_ctx) }) .take(MAX_PATHS) .collect::, _>>(); @@ -566,7 +569,7 @@ where Ok(paths) if !paths.is_empty() => Ok(paths), _ => { if is_recipient_announced { - BlindedMessagePath::new(&[], recipient, context, &**entropy_source, secp_ctx) + BlindedMessagePath::new(&[], recipient, context, Vec::new(), &**entropy_source, secp_ctx) .map(|path| vec![path]) } else { Err(()) @@ -619,22 +622,22 @@ where pub(crate) fn create_blinded_paths< T: secp256k1::Signing + secp256k1::Verification >( - network_graph: &G, recipient: PublicKey, context: MessageContext, + network_graph: &G, recipient: PublicKey, context: MessageContext, custom_tlvs: Vec, peers: Vec, entropy_source: &ES, secp_ctx: &Secp256k1, ) -> Result, ()> { let peers = peers .into_iter() .map(|node_id| MessageForwardNode { node_id, short_channel_id: None }); - Self::create_blinded_paths_from_iter(network_graph, recipient, context, peers.into_iter(), entropy_source, secp_ctx, false) + Self::create_blinded_paths_from_iter(network_graph, recipient, context, custom_tlvs, peers.into_iter(), entropy_source, secp_ctx, false) } pub(crate) fn create_compact_blinded_paths< T: secp256k1::Signing + secp256k1::Verification >( - network_graph: &G, recipient: PublicKey, context: MessageContext, + network_graph: &G, recipient: PublicKey, context: MessageContext, custom_tlvs: Vec, peers: Vec, entropy_source: &ES, secp_ctx: &Secp256k1, ) -> Result, ()> { - Self::create_blinded_paths_from_iter(network_graph, recipient, context, peers.into_iter(), entropy_source, secp_ctx, true) + Self::create_blinded_paths_from_iter(network_graph, recipient, context, custom_tlvs, peers.into_iter(), entropy_source, secp_ctx, true) } } @@ -652,17 +655,17 @@ where fn create_blinded_paths< T: secp256k1::Signing + secp256k1::Verification >( - &self, recipient: PublicKey, context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, + &self, recipient: PublicKey, context: MessageContext, custom_tlvs: Vec, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { - Self::create_blinded_paths(&self.network_graph, recipient, context, peers, &self.entropy_source, secp_ctx) + Self::create_blinded_paths(&self.network_graph, recipient, context, custom_tlvs, peers, &self.entropy_source, secp_ctx) } fn create_compact_blinded_paths< T: secp256k1::Signing + secp256k1::Verification >( - &self, recipient: PublicKey, context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, + &self, recipient: PublicKey, context: MessageContext, custom_tlvs: Vec, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { - Self::create_compact_blinded_paths(&self.network_graph, recipient, context, peers, &self.entropy_source, secp_ctx) + Self::create_compact_blinded_paths(&self.network_graph, recipient, context, custom_tlvs, peers, &self.entropy_source, secp_ctx) } } @@ -832,7 +835,7 @@ pub enum PeeledOnion { /// Forwarded onion, with the next node id and a new onion Forward(NextMessageHop, OnionMessage), /// Received onion message, with decrypted contents, context, and reply path - Receive(ParsedOnionMessageContents, Option, Option) + Receive(ParsedOnionMessageContents, Option, Option>, Option) } @@ -982,24 +985,24 @@ where (control_tlvs_ss, custom_handler.deref(), logger.deref()) ) { Ok((Payload::Receive::::Target as CustomOnionMessageHandler>::CustomMessage>> { - message, control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { context }), reply_path, + message, control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { context, custom_tlvs }), reply_path, }, None)) => { match (&message, &context) { (_, None) => { - Ok(PeeledOnion::Receive(message, None, reply_path)) + Ok(PeeledOnion::Receive(message, None, custom_tlvs, reply_path)) } (ParsedOnionMessageContents::Offers(_), Some(MessageContext::Offers(_))) => { - Ok(PeeledOnion::Receive(message, context, reply_path)) + Ok(PeeledOnion::Receive(message, context, custom_tlvs, reply_path)) } #[cfg(async_payments)] (ParsedOnionMessageContents::AsyncPayments(_), Some(MessageContext::AsyncPayments(_))) => { - Ok(PeeledOnion::Receive(message, context, reply_path)) + Ok(PeeledOnion::Receive(message, context, custom_tlvs, reply_path)) } (ParsedOnionMessageContents::Custom(_), Some(MessageContext::Custom(_))) => { - Ok(PeeledOnion::Receive(message, context, reply_path)) + Ok(PeeledOnion::Receive(message, context, custom_tlvs, reply_path)) } (ParsedOnionMessageContents::DNSResolver(_), Some(MessageContext::DNSResolver(_))) => { - Ok(PeeledOnion::Receive(message, context, reply_path)) + Ok(PeeledOnion::Receive(message, context, custom_tlvs, reply_path)) } _ => { log_trace!(logger, "Received message was sent on a blinded path with the wrong context."); @@ -1188,10 +1191,10 @@ where let (destination, reply_path) = match instructions { MessageSendInstructions::WithSpecifiedReplyPath { destination, reply_path } => (destination, Some(reply_path)), - MessageSendInstructions::WithReplyPath { destination, context } - |MessageSendInstructions::ForReply { instructions: ResponseInstruction { destination, context: Some(context) } } => + MessageSendInstructions::WithReplyPath { destination, context, custom_tlvs } + |MessageSendInstructions::ForReply { instructions: ResponseInstruction { destination, reply_data: Some((context, custom_tlvs)) } } => { - match self.create_blinded_path(context) { + match self.create_blinded_path(context, custom_tlvs) { Ok(reply_path) => (destination, Some(reply_path)), Err(err) => { log_trace!( @@ -1204,7 +1207,7 @@ where } }, MessageSendInstructions::WithoutReplyPath { destination } - |MessageSendInstructions::ForReply { instructions: ResponseInstruction { destination, context: None } } => + |MessageSendInstructions::ForReply { instructions: ResponseInstruction { destination, reply_data: None } } => (destination, None), }; @@ -1256,7 +1259,7 @@ where .map_err(|_| SendError::PathNotFound) } - fn create_blinded_path(&self, context: MessageContext) -> Result { + fn create_blinded_path(&self, context: MessageContext, custom_tlvs: Vec) -> Result { let recipient = self.node_signer .get_node_id(Recipient::Node) .map_err(|_| SendError::GetNodeIdFailed)?; @@ -1269,7 +1272,7 @@ where .collect::>(); self.message_router - .create_blinded_paths(recipient, context, peers, secp_ctx) + .create_blinded_paths(recipient, context, custom_tlvs, peers, secp_ctx) .and_then(|paths| paths.into_iter().next().ok_or(())) .map_err(|_| SendError::PathNotFound) } @@ -1599,7 +1602,7 @@ where fn handle_onion_message(&self, peer_node_id: PublicKey, msg: &OnionMessage) { let logger = WithContext::from(&self.logger, Some(peer_node_id), None, None); match self.peel_onion_message(msg) { - Ok(PeeledOnion::Receive(message, context, reply_path)) => { + Ok(PeeledOnion::Receive(message, context, custom_tlvs, reply_path)) => { log_trace!( logger, "Received an onion message with {} reply_path: {:?}", @@ -1616,7 +1619,7 @@ where return } }; - let response_instructions = self.offers_handler.handle_message(msg, context, responder); + let response_instructions = self.offers_handler.handle_message(msg, context, custom_tlvs, responder); if let Some((msg, instructions)) = response_instructions { let _ = self.handle_onion_message_response(msg, instructions); } @@ -1950,7 +1953,7 @@ fn packet_payloads_and_keys, responder: Option, + &self, message: OffersMessage, context: Option, custom_tlvs: Option>, responder: Option, ) -> Option<(OffersMessage, ResponseInstruction)>; /// Releases any [`OffersMessage`]s that need to be sent. diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 8ec85a6bed7..8006f27bd0b 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -347,6 +347,7 @@ impl Readable for ControlTlvs { (4, next_node_id, option), (8, next_blinding_override, option), (65537, context, option), + (65539, custom_tlvs, option), }); let _padding: Option = _padding; @@ -368,6 +369,7 @@ impl Readable for ControlTlvs { } else if valid_recv_fmt { ControlTlvs::Receive(ReceiveTlvs { context, + custom_tlvs, }) } else { return Err(DecodeError::InvalidValue) diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 122d7f0af48..3ea61ac9010 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -292,17 +292,17 @@ impl<'a> MessageRouter for TestMessageRouter<'a> { } fn create_blinded_paths( - &self, recipient: PublicKey, context: MessageContext, + &self, recipient: PublicKey, context: MessageContext, custom_tlvs: Vec, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { - self.inner.create_blinded_paths(recipient, context, peers, secp_ctx) + self.inner.create_blinded_paths(recipient, context, custom_tlvs, peers, secp_ctx) } fn create_compact_blinded_paths( - &self, recipient: PublicKey, context: MessageContext, + &self, recipient: PublicKey, context: MessageContext, custom_tlvs: Vec, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { - self.inner.create_compact_blinded_paths(recipient, context, peers, secp_ctx) + self.inner.create_compact_blinded_paths(recipient, context, custom_tlvs, peers, secp_ctx) } } From aa902290122c98bdfc333648d3e4f3477d4f25cd Mon Sep 17 00:00:00 2001 From: shaavan Date: Tue, 5 Nov 2024 17:33:42 +0530 Subject: [PATCH 2/4] Distinguish Between Sender and User Custom TLVs - This update introduces a clear separation between two types of custom TLVs: those sent by the sender for the user, and those added by the user to the reply path, which are expected to return with the response. - This commit establishes this distinction in the codebase at relevant points. - The next commit will build on this by providing an interface for users to add their own custom TLVs to the reply path, allowing them to receive specific data back in the response. Note: 1. Similar to keysend, for serialization purposes, user_custom_tlv are assigned a specific TLV number. 2. For uniformity, user_custom_tlv are assigned the lowest possible odd number after (1 << 16), which is 65537. --- lightning/src/ln/blinded_payment_tests.rs | 6 +- lightning/src/ln/channelmanager.rs | 38 ++++++---- lightning/src/ln/functional_test_utils.rs | 36 ++++++---- .../src/ln/max_payment_path_len_tests.rs | 44 ++++++------ lightning/src/ln/msgs.rs | 72 +++++++++++++------ lightning/src/ln/onion_payment.rs | 18 ++--- lightning/src/ln/onion_utils.rs | 6 +- lightning/src/ln/outbound_payment.rs | 58 ++++++++------- lightning/src/ln/payment_tests.rs | 42 ++++++----- 9 files changed, 196 insertions(+), 124 deletions(-) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index aa399b9d4d6..ee6d8cf3983 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -1323,7 +1323,7 @@ fn custom_tlvs_to_blinded_path() { ); let recipient_onion_fields = RecipientOnionFields::spontaneous_empty() - .with_custom_tlvs(vec![((1 << 16) + 1, vec![42, 42])]) + .with_sender_custom_tlvs(vec![((1 << 16) + 3, vec![42, 42])]) .unwrap(); nodes[0].node.send_payment(payment_hash, recipient_onion_fields.clone(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); @@ -1336,11 +1336,11 @@ fn custom_tlvs_to_blinded_path() { let path = &[&nodes[1]]; let args = PassAlongPathArgs::new(&nodes[0], path, amt_msat, payment_hash, ev) .with_payment_secret(payment_secret) - .with_custom_tlvs(recipient_onion_fields.custom_tlvs.clone()); + .with_sender_custom_tlvs(recipient_onion_fields.sender_custom_tlvs.clone()); do_pass_along_path(args); claim_payment_along_route( ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1]]], payment_preimage) - .with_custom_tlvs(recipient_onion_fields.custom_tlvs.clone()) + .with_sender_custom_tlvs(recipient_onion_fields.sender_custom_tlvs.clone()) ); } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 3ac9804a45b..2693d381295 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -187,7 +187,9 @@ pub enum PendingHTLCRouting { /// For HTLCs received by LDK, this will ultimately be exposed in /// [`Event::PaymentClaimable::onion_fields`] as /// [`RecipientOnionFields::custom_tlvs`]. - custom_tlvs: Vec<(u64, Vec)>, + sender_custom_tlvs: Vec<(u64, Vec)>, + /// Custom TLVs which were set by us through the reply path + user_custom_tlvs: Vec, /// Set if this HTLC is the final hop in a multi-hop blinded path. requires_blinded_error: bool, }, @@ -217,7 +219,9 @@ pub enum PendingHTLCRouting { /// /// For HTLCs received by LDK, these will ultimately bubble back up as /// [`RecipientOnionFields::custom_tlvs`]. - custom_tlvs: Vec<(u64, Vec)>, + sender_custom_tlvs: Vec<(u64, Vec)>, + /// Custom TLVs which were set by us through the reply path + user_custom_tlvs: Vec, /// Set if this HTLC is the final hop in a multi-hop blinded path. requires_blinded_error: bool, }, @@ -929,10 +933,10 @@ impl ClaimablePayments { } } - if let Some(RecipientOnionFields { custom_tlvs, .. }) = &payment.onion_fields { - if !custom_tlvs_known && custom_tlvs.iter().any(|(typ, _)| typ % 2 == 0) { + if let Some(RecipientOnionFields { sender_custom_tlvs, .. }) = &payment.onion_fields { + if !custom_tlvs_known && sender_custom_tlvs.iter().any(|(typ, _)| typ % 2 == 0) { log_info!(logger, "Rejecting payment with payment hash {} as we cannot accept payment with unknown even TLVs: {}", - &payment_hash, log_iter!(custom_tlvs.iter().map(|(typ, _)| typ).filter(|typ| *typ % 2 == 0))); + &payment_hash, log_iter!(sender_custom_tlvs.iter().map(|(typ, _)| typ).filter(|typ| *typ % 2 == 0))); return Err(payment.htlcs); } } @@ -5839,23 +5843,24 @@ where let (cltv_expiry, onion_payload, payment_data, payment_context, phantom_shared_secret, mut onion_fields) = match routing { PendingHTLCRouting::Receive { payment_data, payment_metadata, payment_context, - incoming_cltv_expiry, phantom_shared_secret, custom_tlvs, + incoming_cltv_expiry, phantom_shared_secret, sender_custom_tlvs, user_custom_tlvs, requires_blinded_error: _ } => { let _legacy_hop_data = Some(payment_data.clone()); let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret), - payment_metadata, custom_tlvs }; + payment_metadata, sender_custom_tlvs, user_custom_tlvs }; (incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data }, Some(payment_data), payment_context, phantom_shared_secret, onion_fields) }, PendingHTLCRouting::ReceiveKeysend { payment_data, payment_preimage, payment_metadata, - incoming_cltv_expiry, custom_tlvs, requires_blinded_error: _ + incoming_cltv_expiry, sender_custom_tlvs, user_custom_tlvs, requires_blinded_error: _ } => { let onion_fields = RecipientOnionFields { payment_secret: payment_data.as_ref().map(|data| data.payment_secret), payment_metadata, - custom_tlvs, + sender_custom_tlvs, + user_custom_tlvs, }; (incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage), payment_data, None, None, onion_fields) @@ -11627,9 +11632,10 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting, (1, phantom_shared_secret, option), (2, incoming_cltv_expiry, required), (3, payment_metadata, option), - (5, custom_tlvs, optional_vec), + (5, sender_custom_tlvs, optional_vec), (7, requires_blinded_error, (default_value, false)), (9, payment_context, option), + (11, user_custom_tlvs, optional_vec), }, (2, ReceiveKeysend) => { (0, payment_preimage, required), @@ -11637,7 +11643,8 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting, (2, incoming_cltv_expiry, required), (3, payment_metadata, option), (4, payment_data, option), // Added in 0.0.116 - (5, custom_tlvs, optional_vec), + (5, sender_custom_tlvs, optional_vec), + (7, user_custom_tlvs, optional_vec), }, ); @@ -14441,7 +14448,8 @@ mod tests { payment_data: Some(msgs::FinalOnionHopData { payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat, }), - custom_tlvs: Vec::new(), + sender_custom_tlvs: Vec::new(), + user_custom_tlvs: Vec::new(), }; // Check that if the amount we received + the penultimate hop extra fee is less than the sender // intended amount, we fail the payment. @@ -14463,7 +14471,8 @@ mod tests { payment_data: Some(msgs::FinalOnionHopData { payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat, }), - custom_tlvs: Vec::new(), + sender_custom_tlvs: Vec::new(), + user_custom_tlvs: Vec::new(), }; 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]), @@ -14487,7 +14496,8 @@ mod tests { payment_data: Some(msgs::FinalOnionHopData { payment_secret: PaymentSecret([0; 32]), total_msat: 100, }), - custom_tlvs: Vec::new(), + sender_custom_tlvs: Vec::new(), + user_custom_tlvs: Vec::new(), }, [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/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index de0b4c7d4bb..5a8b22d2543 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -2585,7 +2585,8 @@ pub struct PassAlongPathArgs<'a, 'b, 'c, 'd> { pub clear_recipient_events: bool, pub expected_preimage: Option, pub is_probe: bool, - pub custom_tlvs: Vec<(u64, Vec)>, + pub sender_custom_tlvs: Vec<(u64, Vec)>, + pub user_custom_tlvs: Vec, pub payment_metadata: Option>, } @@ -2597,7 +2598,7 @@ impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> { Self { origin_node, expected_path, recv_value, payment_hash, payment_secret: None, event, payment_claimable_expected: true, clear_recipient_events: true, expected_preimage: None, - is_probe: false, custom_tlvs: Vec::new(), payment_metadata: None, + is_probe: false, sender_custom_tlvs: Vec::new(), user_custom_tlvs: Vec::new(), payment_metadata: None, } } pub fn without_clearing_recipient_events(mut self) -> Self { @@ -2621,8 +2622,12 @@ impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> { self.expected_preimage = Some(payment_preimage); self } - pub fn with_custom_tlvs(mut self, custom_tlvs: Vec<(u64, Vec)>) -> Self { - self.custom_tlvs = custom_tlvs; + pub fn with_sender_custom_tlvs(mut self, custom_tlvs: Vec<(u64, Vec)>) -> Self { + self.sender_custom_tlvs = custom_tlvs; + self + } + pub fn with_user_custom_tlvs(mut self, custom_tlvs: Vec) -> Self { + self.user_custom_tlvs = custom_tlvs; self } pub fn with_payment_metadata(mut self, payment_metadata: Vec) -> Self { @@ -2635,7 +2640,8 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option let PassAlongPathArgs { origin_node, expected_path, recv_value, payment_hash: our_payment_hash, payment_secret: our_payment_secret, event: ev, payment_claimable_expected, - clear_recipient_events, expected_preimage, is_probe, custom_tlvs, payment_metadata, + clear_recipient_events, expected_preimage, is_probe, sender_custom_tlvs, user_custom_tlvs, + payment_metadata, } = args; let mut payment_event = SendEvent::from_event(ev); @@ -2668,7 +2674,8 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option assert_eq!(our_payment_hash, *payment_hash); assert_eq!(node.node.get_our_node_id(), receiver_node_id.unwrap()); assert!(onion_fields.is_some()); - assert_eq!(onion_fields.as_ref().unwrap().custom_tlvs, custom_tlvs); + assert_eq!(onion_fields.as_ref().unwrap().sender_custom_tlvs, sender_custom_tlvs); + assert_eq!(onion_fields.as_ref().unwrap().user_custom_tlvs, user_custom_tlvs); assert_eq!(onion_fields.as_ref().unwrap().payment_metadata, payment_metadata); match &purpose { PaymentPurpose::Bolt11InvoicePayment { payment_preimage, payment_secret, .. } => { @@ -2782,7 +2789,8 @@ pub struct ClaimAlongRouteArgs<'a, 'b, 'c, 'd> { pub expected_min_htlc_overpay: Vec, pub skip_last: bool, pub payment_preimage: PaymentPreimage, - pub custom_tlvs: Vec<(u64, Vec)>, + pub sender_custom_tlvs: Vec<(u64, Vec)>, + pub user_custom_tlvs: Vec, // Allow forwarding nodes to have taken 1 msat more fee than expected based on the downstream // fulfill amount. // @@ -2801,7 +2809,7 @@ impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> { Self { origin_node, expected_paths, expected_extra_fees: vec![0; expected_paths.len()], expected_min_htlc_overpay: vec![0; expected_paths.len()], skip_last: false, payment_preimage, - allow_1_msat_fee_overpay: false, custom_tlvs: vec![], + allow_1_msat_fee_overpay: false, sender_custom_tlvs: vec![], user_custom_tlvs: vec![], } } pub fn skip_last(mut self, skip_last: bool) -> Self { @@ -2820,8 +2828,8 @@ impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> { self.allow_1_msat_fee_overpay = true; self } - pub fn with_custom_tlvs(mut self, custom_tlvs: Vec<(u64, Vec)>) -> Self { - self.custom_tlvs = custom_tlvs; + pub fn with_sender_custom_tlvs(mut self, custom_tlvs: Vec<(u64, Vec)>) -> Self { + self.sender_custom_tlvs = custom_tlvs; self } } @@ -2829,7 +2837,7 @@ impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> { pub fn pass_claimed_payment_along_route(args: ClaimAlongRouteArgs) -> u64 { let ClaimAlongRouteArgs { origin_node, expected_paths, expected_extra_fees, expected_min_htlc_overpay, skip_last, - payment_preimage: our_payment_preimage, allow_1_msat_fee_overpay, custom_tlvs, + payment_preimage: our_payment_preimage, allow_1_msat_fee_overpay, sender_custom_tlvs, user_custom_tlvs, } = args; let claim_event = expected_paths[0].last().unwrap().node.get_and_clear_pending_events(); assert_eq!(claim_event.len(), 1); @@ -2849,7 +2857,8 @@ pub fn pass_claimed_payment_along_route(args: ClaimAlongRouteArgs) -> u64 { assert_eq!(preimage, our_payment_preimage); assert_eq!(htlcs.len(), expected_paths.len()); // One per path. assert_eq!(htlcs.iter().map(|h| h.value_msat).sum::(), amount_msat); - assert_eq!(onion_fields.as_ref().unwrap().custom_tlvs, custom_tlvs); + assert_eq!(onion_fields.as_ref().unwrap().sender_custom_tlvs, sender_custom_tlvs); + assert_eq!(onion_fields.as_ref().unwrap().user_custom_tlvs, user_custom_tlvs); check_claimed_htlcs_match_route(origin_node, expected_paths, htlcs); fwd_amt_msat = amount_msat; }, @@ -2866,7 +2875,8 @@ pub fn pass_claimed_payment_along_route(args: ClaimAlongRouteArgs) -> u64 { assert_eq!(&payment_hash.0, &Sha256::hash(&our_payment_preimage.0)[..]); assert_eq!(htlcs.len(), expected_paths.len()); // One per path. assert_eq!(htlcs.iter().map(|h| h.value_msat).sum::(), amount_msat); - assert_eq!(onion_fields.as_ref().unwrap().custom_tlvs, custom_tlvs); + assert_eq!(onion_fields.as_ref().unwrap().sender_custom_tlvs, sender_custom_tlvs); + assert_eq!(onion_fields.as_ref().unwrap().user_custom_tlvs, user_custom_tlvs); check_claimed_htlcs_match_route(origin_node, expected_paths, htlcs); fwd_amt_msat = amount_msat; } diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index af30003ee5d..aa18fce7ab4 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -58,7 +58,8 @@ fn large_payment_metadata() { }), payment_metadata: None, keysend_preimage: None, - custom_tlvs: &Vec::new(), + sender_custom_tlvs: &Vec::new(), + user_custom_tlvs: &Vec::new(), sender_intended_htlc_amt_msat: MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY, cltv_expiry_height: nodes[0].best_block_info().1 + DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, }.serialized_length(); @@ -75,7 +76,8 @@ fn large_payment_metadata() { let mut recipient_onion_max_md_size = RecipientOnionFields { payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata.clone()), - custom_tlvs: Vec::new(), + sender_custom_tlvs: Vec::new(), + user_custom_tlvs: Vec::new(), }; nodes[0].node.send_payment(payment_hash, recipient_onion_max_md_size.clone(), PaymentId(payment_hash.0), route_0_1.route_params.clone().unwrap(), Retry::Attempts(0)).unwrap(); check_added_monitors!(nodes[0], 1); @@ -123,7 +125,8 @@ fn large_payment_metadata() { let mut recipient_onion_allows_2_hops = RecipientOnionFields { payment_secret: Some(payment_secret_2), payment_metadata: Some(vec![42; max_metadata_len - INTERMED_PAYLOAD_LEN_ESTIMATE]), - custom_tlvs: Vec::new(), + sender_custom_tlvs: Vec::new(), + user_custom_tlvs: Vec::new(), }; let mut route_params_0_2 = route_0_2.route_params.clone().unwrap(); route_params_0_2.payment_params.max_path_length = 2; @@ -176,7 +179,7 @@ fn one_hop_blinded_path_with_custom_tlv() { ); // Calculate the maximum custom TLV value size where a valid onion packet is still possible. - const CUSTOM_TLV_TYPE: u64 = 65537; + const CUSTOM_TLV_TYPE: u64 = 65539; let final_payload_len_without_custom_tlv = msgs::OutboundOnionPayload::BlindedReceive { sender_intended_htlc_amt_msat: MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY, total_msat: MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY, @@ -185,7 +188,8 @@ fn one_hop_blinded_path_with_custom_tlv() { intro_node_blinding_point: Some(blinded_path.blinding_point()), keysend_preimage: None, invoice_request: None, - custom_tlvs: &Vec::new() + sender_custom_tlvs: &Vec::new(), + user_custom_tlvs: &Vec::new() }.serialized_length(); let max_custom_tlv_len = 1300 - crate::util::ser::BigSize(CUSTOM_TLV_TYPE).serialized_length() // custom TLV type @@ -196,7 +200,7 @@ fn one_hop_blinded_path_with_custom_tlv() { // Check that we can send the maximum custom TLV with 1 blinded hop. let recipient_onion_max_custom_tlv_size = RecipientOnionFields::spontaneous_empty() - .with_custom_tlvs(vec![(CUSTOM_TLV_TYPE, vec![42; max_custom_tlv_len])]) + .with_sender_custom_tlvs(vec![(CUSTOM_TLV_TYPE, vec![42; max_custom_tlv_len])]) .unwrap(); nodes[1].node.send_payment(payment_hash, recipient_onion_max_custom_tlv_size.clone(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap(); check_added_monitors(&nodes[1], 1); @@ -206,16 +210,16 @@ fn one_hop_blinded_path_with_custom_tlv() { let path = &[&nodes[2]]; let args = PassAlongPathArgs::new(&nodes[1], path, amt_msat, payment_hash, events.pop().unwrap()) .with_payment_secret(payment_secret) - .with_custom_tlvs(recipient_onion_max_custom_tlv_size.custom_tlvs.clone()); + .with_sender_custom_tlvs(recipient_onion_max_custom_tlv_size.sender_custom_tlvs.clone()); do_pass_along_path(args); claim_payment_along_route( ClaimAlongRouteArgs::new(&nodes[1], &[&[&nodes[2]]], payment_preimage) - .with_custom_tlvs(recipient_onion_max_custom_tlv_size.custom_tlvs.clone()) + .with_sender_custom_tlvs(recipient_onion_max_custom_tlv_size.sender_custom_tlvs.clone()) ); // If 1 byte is added to the custom TLV value, we'll fail to send prior to pathfinding. let mut recipient_onion_too_large_custom_tlv = recipient_onion_max_custom_tlv_size.clone(); - recipient_onion_too_large_custom_tlv.custom_tlvs[0].1.push(42); + recipient_onion_too_large_custom_tlv.sender_custom_tlvs[0].1.push(42); let err = nodes[1].node.send_payment(payment_hash, recipient_onion_too_large_custom_tlv, PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap_err(); assert_eq!(err, RetryableSendFailure::OnionPacketSizeExceeded); @@ -227,7 +231,7 @@ fn one_hop_blinded_path_with_custom_tlv() { // If we remove enough custom TLV bytes to allow for 1 intermediate unblinded hop, we're now able // to send nodes[0] -> nodes[2]. let mut recipient_onion_allows_2_hops = recipient_onion_max_custom_tlv_size.clone(); - recipient_onion_allows_2_hops.custom_tlvs[0].1.resize(max_custom_tlv_len - INTERMED_PAYLOAD_LEN_ESTIMATE, 0); + recipient_onion_allows_2_hops.sender_custom_tlvs[0].1.resize(max_custom_tlv_len - INTERMED_PAYLOAD_LEN_ESTIMATE, 0); nodes[0].node.send_payment(payment_hash, recipient_onion_allows_2_hops.clone(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap(); check_added_monitors(&nodes[0], 1); @@ -236,11 +240,11 @@ fn one_hop_blinded_path_with_custom_tlv() { let path = &[&nodes[1], &nodes[2]]; let args = PassAlongPathArgs::new(&nodes[0], path, amt_msat, payment_hash, events.pop().unwrap()) .with_payment_secret(payment_secret) - .with_custom_tlvs(recipient_onion_allows_2_hops.custom_tlvs.clone()); + .with_sender_custom_tlvs(recipient_onion_allows_2_hops.sender_custom_tlvs.clone()); do_pass_along_path(args); claim_payment_along_route( ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1], &nodes[2]]], payment_preimage) - .with_custom_tlvs(recipient_onion_allows_2_hops.custom_tlvs) + .with_sender_custom_tlvs(recipient_onion_allows_2_hops.sender_custom_tlvs) ); } @@ -271,7 +275,7 @@ fn blinded_path_with_custom_tlv() { &chanmon_cfgs[3].keys_manager); // Calculate the maximum custom TLV value size where a valid onion packet is still possible. - const CUSTOM_TLV_TYPE: u64 = 65537; + const CUSTOM_TLV_TYPE: u64 = 65539; let mut route = get_route(&nodes[1], &route_params).unwrap(); let reserved_packet_bytes_without_custom_tlv: usize = onion_utils::build_onion_payloads( &route.paths[0], MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY, @@ -291,7 +295,7 @@ fn blinded_path_with_custom_tlv() { // Check that we can send the maximum custom TLV size with 0 intermediate unblinded hops. let recipient_onion_max_custom_tlv_size = RecipientOnionFields::spontaneous_empty() - .with_custom_tlvs(vec![(CUSTOM_TLV_TYPE, vec![42; max_custom_tlv_len])]) + .with_sender_custom_tlvs(vec![(CUSTOM_TLV_TYPE, vec![42; max_custom_tlv_len])]) .unwrap(); nodes[1].node.send_payment(payment_hash, recipient_onion_max_custom_tlv_size.clone(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap(); check_added_monitors(&nodes[1], 1); @@ -301,16 +305,16 @@ fn blinded_path_with_custom_tlv() { let path = &[&nodes[2], &nodes[3]]; let args = PassAlongPathArgs::new(&nodes[1], path, amt_msat, payment_hash, events.pop().unwrap()) .with_payment_secret(payment_secret) - .with_custom_tlvs(recipient_onion_max_custom_tlv_size.custom_tlvs.clone()); + .with_sender_custom_tlvs(recipient_onion_max_custom_tlv_size.sender_custom_tlvs.clone()); do_pass_along_path(args); claim_payment_along_route( ClaimAlongRouteArgs::new(&nodes[1], &[&[&nodes[2], &nodes[3]]], payment_preimage) - .with_custom_tlvs(recipient_onion_max_custom_tlv_size.custom_tlvs.clone()) + .with_sender_custom_tlvs(recipient_onion_max_custom_tlv_size.sender_custom_tlvs.clone()) ); // If 1 byte is added to the custom TLV value, we'll fail to send prior to pathfinding. let mut recipient_onion_too_large_custom_tlv = recipient_onion_max_custom_tlv_size.clone(); - recipient_onion_too_large_custom_tlv.custom_tlvs[0].1.push(42); + recipient_onion_too_large_custom_tlv.sender_custom_tlvs[0].1.push(42); let err = nodes[1].node.send_payment(payment_hash, recipient_onion_too_large_custom_tlv.clone(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap_err(); assert_eq!(err, RetryableSendFailure::OnionPacketSizeExceeded); @@ -334,7 +338,7 @@ fn blinded_path_with_custom_tlv() { // If we remove enough custom TLV bytes to allow for 1 intermediate unblinded hop, we're now able // to send nodes[0] -> nodes[3]. let mut recipient_onion_allows_2_hops = recipient_onion_max_custom_tlv_size.clone(); - recipient_onion_allows_2_hops.custom_tlvs[0].1.resize(max_custom_tlv_len - INTERMED_PAYLOAD_LEN_ESTIMATE, 0); + recipient_onion_allows_2_hops.sender_custom_tlvs[0].1.resize(max_custom_tlv_len - INTERMED_PAYLOAD_LEN_ESTIMATE, 0); nodes[0].node.send_payment(payment_hash, recipient_onion_allows_2_hops.clone(), PaymentId(payment_hash.0), route_params.clone(), Retry::Attempts(0)).unwrap(); check_added_monitors(&nodes[0], 1); @@ -343,11 +347,11 @@ fn blinded_path_with_custom_tlv() { let path = &[&nodes[1], &nodes[2], &nodes[3]]; let args = PassAlongPathArgs::new(&nodes[0], path, amt_msat, payment_hash, events.pop().unwrap()) .with_payment_secret(payment_secret) - .with_custom_tlvs(recipient_onion_allows_2_hops.custom_tlvs.clone()); + .with_sender_custom_tlvs(recipient_onion_allows_2_hops.sender_custom_tlvs.clone()); do_pass_along_path(args); claim_payment_along_route( ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1], &nodes[2], &nodes[3]]], payment_preimage) - .with_custom_tlvs(recipient_onion_allows_2_hops.custom_tlvs) + .with_sender_custom_tlvs(recipient_onion_allows_2_hops.sender_custom_tlvs) ); } diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 6f508efa636..b312a2bf8c2 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1769,7 +1769,8 @@ mod fuzzy_internal_msgs { payment_data: Option, payment_metadata: Option>, keysend_preimage: Option, - custom_tlvs: Vec<(u64, Vec)>, + sender_custom_tlvs: Vec<(u64, Vec)>, + user_custom_tlvs: Vec, sender_intended_htlc_amt_msat: u64, cltv_expiry_height: u32, }, @@ -1790,7 +1791,8 @@ mod fuzzy_internal_msgs { payment_context: PaymentContext, intro_node_blinding_point: Option, keysend_preimage: Option, - custom_tlvs: Vec<(u64, Vec)>, + sender_custom_tlvs: Vec<(u64, Vec)>, + user_custom_tlvs: Vec, } } @@ -1812,7 +1814,8 @@ mod fuzzy_internal_msgs { payment_data: Option, payment_metadata: Option<&'a Vec>, keysend_preimage: Option, - custom_tlvs: &'a Vec<(u64, Vec)>, + sender_custom_tlvs: &'a Vec<(u64, Vec)>, + user_custom_tlvs: &'a Vec, sender_intended_htlc_amt_msat: u64, cltv_expiry_height: u32, }, @@ -1827,8 +1830,9 @@ mod fuzzy_internal_msgs { encrypted_tlvs: &'a Vec, intro_node_blinding_point: Option, // Set if the introduction node of the blinded path is the final node keysend_preimage: Option, - custom_tlvs: &'a Vec<(u64, Vec)>, invoice_request: Option<&'a InvoiceRequest>, + sender_custom_tlvs: &'a Vec<(u64, Vec)>, + user_custom_tlvs: &'a Vec, } } @@ -2739,13 +2743,18 @@ impl<'a> Writeable for OutboundOnionPayload<'a> { }, Self::Receive { ref payment_data, ref payment_metadata, ref keysend_preimage, sender_intended_htlc_amt_msat, - cltv_expiry_height, ref custom_tlvs, + cltv_expiry_height, ref sender_custom_tlvs, user_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 user_custom_tlv = (!user_custom_tlvs.is_empty()).then(|| (65537, user_custom_tlvs.to_vec())); 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 mut custom_tlvs: Vec<&(u64, Vec)> = sender_custom_tlvs + .iter() + .chain(keysend_tlv.iter()) + .chain(user_custom_tlv.iter()) + .collect(); custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ); _encode_varint_length_prefixed_tlv!(w, { (2, HighZeroBytesDroppedBigSize(*sender_intended_htlc_amt_msat), required), @@ -2762,14 +2771,17 @@ impl<'a> Writeable for OutboundOnionPayload<'a> { }, Self::BlindedReceive { sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, encrypted_tlvs, - intro_node_blinding_point, keysend_preimage, ref invoice_request, ref custom_tlvs, + intro_node_blinding_point, keysend_preimage, ref sender_custom_tlvs, user_custom_tlvs, ref invoice_request, } => { // 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 user_custom_tlv = (!user_custom_tlvs.is_empty()).then(|| (65537, user_custom_tlvs.to_vec())); let invoice_request_tlv = invoice_request.map(|invreq| (77_777, invreq.encode())); // TODO: update TLV type once the async payments spec is merged let keysend_tlv = keysend_preimage.map(|preimage| (5482373484, preimage.encode())); - let mut custom_tlvs: Vec<&(u64, Vec)> = custom_tlvs.iter() + let mut custom_tlvs: Vec<&(u64, Vec)> = sender_custom_tlvs + .iter() + .chain(user_custom_tlv.iter()) .chain(invoice_request_tlv.iter()) .chain(keysend_tlv.iter()) .collect(); @@ -2874,6 +2886,12 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh Ok(true) }); + let (user_custom_tlvs, sender_custom_tlvs): (Vec<(u64, Vec)>, Vec<(u64, Vec)>) = custom_tlvs + .into_iter() + .partition(|(tlv_type, _)| *tlv_type == 65537); + + let user_custom_tlvs = user_custom_tlvs.into_iter().next().map(|(_, data)| data).unwrap_or_else(Vec::new); + if amt.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } if intro_node_blinding_point.is_some() && update_add_blinding_point.is_some() { return Err(DecodeError::InvalidValue) @@ -2920,7 +2938,8 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh payment_context, intro_node_blinding_point, keysend_preimage, - custom_tlvs, + sender_custom_tlvs, + user_custom_tlvs, }) }, } @@ -2948,7 +2967,8 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh keysend_preimage, sender_intended_htlc_amt_msat: amt.ok_or(DecodeError::InvalidValue)?, cltv_expiry_height: cltv_value.ok_or(DecodeError::InvalidValue)?, - custom_tlvs, + sender_custom_tlvs, + user_custom_tlvs, }) } } @@ -4559,7 +4579,8 @@ mod tests { keysend_preimage: None, sender_intended_htlc_amt_msat: 0x0badf00d01020304, cltv_expiry_height: 0xffffffff, - custom_tlvs: &vec![], + sender_custom_tlvs: &vec![], + user_custom_tlvs: &vec![], }; let encoded_value = outbound_msg.encode(); let target_value = >::from_hex("1002080badf00d010203040404ffffffff").unwrap(); @@ -4587,7 +4608,8 @@ mod tests { keysend_preimage: None, sender_intended_htlc_amt_msat: 0x0badf00d01020304, cltv_expiry_height: 0xffffffff, - custom_tlvs: &vec![], + sender_custom_tlvs: &vec![], + user_custom_tlvs: &vec![], }; let encoded_value = outbound_msg.encode(); let target_value = >::from_hex("3602080badf00d010203040404ffffffff082442424242424242424242424242424242424242424242424242424242424242421badca1f").unwrap(); @@ -4603,12 +4625,14 @@ mod tests { sender_intended_htlc_amt_msat, cltv_expiry_height, payment_metadata: None, keysend_preimage: None, - custom_tlvs, + sender_custom_tlvs, + user_custom_tlvs, } = inbound_msg { assert_eq!(payment_secret, expected_payment_secret); assert_eq!(sender_intended_htlc_amt_msat, 0x0badf00d01020304); assert_eq!(cltv_expiry_height, 0xffffffff); - assert_eq!(custom_tlvs, vec![]); + assert_eq!(sender_custom_tlvs, vec![]); + assert_eq!(user_custom_tlvs, vec![]) } else { panic!(); } } @@ -4624,7 +4648,8 @@ mod tests { payment_data: None, payment_metadata: None, keysend_preimage: None, - custom_tlvs: &bad_type_range_tlvs, + sender_custom_tlvs: &bad_type_range_tlvs, + user_custom_tlvs: &vec![], sender_intended_htlc_amt_msat: 0x0badf00d01020304, cltv_expiry_height: 0xffffffff, }; @@ -4635,13 +4660,15 @@ mod tests { ((1 << 16) - 3, vec![42]), ((1 << 16) - 1, vec![42; 32]), ]; - if let msgs::OutboundOnionPayload::Receive { ref mut custom_tlvs, .. } = msg { - *custom_tlvs = &good_type_range_tlvs; + if let msgs::OutboundOnionPayload::Receive { ref mut sender_custom_tlvs, .. } = msg { + *sender_custom_tlvs = &good_type_range_tlvs; } let encoded_value = msg.encode(); let inbound_msg = ReadableArgs::read(&mut Cursor::new(&encoded_value[..]), (None, &node_signer)).unwrap(); match inbound_msg { - msgs::InboundOnionPayload::Receive { custom_tlvs, .. } => assert!(custom_tlvs.is_empty()), + msgs::InboundOnionPayload::Receive { sender_custom_tlvs, .. } => { + assert!(sender_custom_tlvs.is_empty()); + } _ => panic!(), } } @@ -4656,7 +4683,8 @@ mod tests { payment_data: None, payment_metadata: None, keysend_preimage: None, - custom_tlvs: &expected_custom_tlvs, + sender_custom_tlvs: &expected_custom_tlvs, + user_custom_tlvs: &vec![], sender_intended_htlc_amt_msat: 0x0badf00d01020304, cltv_expiry_height: 0xffffffff, }; @@ -4669,12 +4697,14 @@ mod tests { payment_data: None, payment_metadata: None, keysend_preimage: None, - custom_tlvs, + sender_custom_tlvs, + user_custom_tlvs, sender_intended_htlc_amt_msat, cltv_expiry_height: outgoing_cltv_value, .. } = inbound_msg { - assert_eq!(custom_tlvs, expected_custom_tlvs); + assert_eq!(sender_custom_tlvs, expected_custom_tlvs); + assert_eq!(user_custom_tlvs, Vec::new()); assert_eq!(sender_intended_htlc_amt_msat, 0x0badf00d01020304); assert_eq!(outgoing_cltv_value, 0xffffffff); } else { panic!(); } diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index a62846f75cd..81a8c27b32a 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -134,19 +134,19 @@ pub(super) fn create_recv_pending_htlc_info( counterparty_skimmed_fee_msat: Option, current_height: u32, accept_mpp_keysend: bool, ) -> Result { let ( - payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, onion_cltv_expiry, + payment_data, keysend_preimage, sender_custom_tlvs, user_custom_tlvs, onion_amt_msat, onion_cltv_expiry, payment_metadata, payment_context, requires_blinded_error ) = match hop_data { msgs::InboundOnionPayload::Receive { - payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, + payment_data, keysend_preimage, sender_custom_tlvs, user_custom_tlvs, sender_intended_htlc_amt_msat, cltv_expiry_height, payment_metadata, .. } => - (payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, + (payment_data, keysend_preimage, sender_custom_tlvs, user_custom_tlvs, sender_intended_htlc_amt_msat, cltv_expiry_height, payment_metadata, None, false), msgs::InboundOnionPayload::BlindedReceive { sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret, intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage, - custom_tlvs + sender_custom_tlvs, user_custom_tlvs } => { check_blinded_payment_constraints( sender_intended_htlc_amt_msat, cltv_expiry, &payment_constraints @@ -159,7 +159,7 @@ pub(super) fn create_recv_pending_htlc_info( } })?; let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat }; - (Some(payment_data), keysend_preimage, custom_tlvs, + (Some(payment_data), keysend_preimage, sender_custom_tlvs, user_custom_tlvs, sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context), intro_node_blinding_point.is_none()) } @@ -239,7 +239,8 @@ pub(super) fn create_recv_pending_htlc_info( payment_preimage, payment_metadata, incoming_cltv_expiry: onion_cltv_expiry, - custom_tlvs, + sender_custom_tlvs, + user_custom_tlvs, requires_blinded_error, } } else if let Some(data) = payment_data { @@ -249,7 +250,8 @@ pub(super) fn create_recv_pending_htlc_info( payment_context, incoming_cltv_expiry: onion_cltv_expiry, phantom_shared_secret, - custom_tlvs, + sender_custom_tlvs, + user_custom_tlvs, requires_blinded_error, } } else { @@ -536,7 +538,7 @@ mod tests { ) = payment_onion_args(bob_pk, charlie_pk); // Ensure the onion will not fit all the payloads by adding a large custom TLV. - recipient_onion.custom_tlvs.push((13377331, vec![0; 1156])); + recipient_onion.sender_custom_tlvs.push((13377331, vec![0; 1156])); let path = Path { hops, blinded_tail: None, }; let onion_keys = super::onion_utils::construct_onion_keys(&secp_ctx, &path, &session_priv).unwrap(); diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 960209c0e0a..6b27ed48b2f 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -267,7 +267,8 @@ where intro_node_blinding_point: blinding_point.take(), keysend_preimage: *keysend_preimage, invoice_request, - custom_tlvs: &recipient_onion.custom_tlvs, + sender_custom_tlvs: &recipient_onion.sender_custom_tlvs, + user_custom_tlvs: &recipient_onion.user_custom_tlvs, }, ); } else { @@ -289,7 +290,8 @@ where }), payment_metadata: recipient_onion.payment_metadata.as_ref(), keysend_preimage: *keysend_preimage, - custom_tlvs: &recipient_onion.custom_tlvs, + sender_custom_tlvs: &recipient_onion.sender_custom_tlvs, + user_custom_tlvs: &recipient_onion.user_custom_tlvs, sender_intended_htlc_amt_msat: value_msat, cltv_expiry_height: cltv, }, diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index c3fde629a83..46a45356d55 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -95,7 +95,8 @@ pub(crate) enum PendingOutboundPayment { payment_metadata: Option>, keysend_preimage: Option, invoice_request: Option, - custom_tlvs: Vec<(u64, Vec)>, + sender_custom_tlvs: Vec<(u64, Vec)>, + user_custom_tlvs: Vec, pending_amt_msat: u64, /// Used to track the fee paid. Present iff the payment was serialized on 0.0.103+. pending_fee_msat: Option, @@ -599,13 +600,16 @@ pub struct RecipientOnionFields { /// may not be supported as universally. pub payment_metadata: Option>, /// See [`Self::custom_tlvs`] for more info. - pub(super) custom_tlvs: Vec<(u64, Vec)>, + pub(super) sender_custom_tlvs: Vec<(u64, Vec)>, + /// Custom Tlvs sent by user to themself. + pub(super) user_custom_tlvs: Vec } impl_writeable_tlv_based!(RecipientOnionFields, { (0, payment_secret, option), - (1, custom_tlvs, optional_vec), + (1, sender_custom_tlvs, optional_vec), (2, payment_metadata, option), + (3, user_custom_tlvs, optional_vec), }); impl RecipientOnionFields { @@ -613,7 +617,7 @@ impl RecipientOnionFields { /// set of onion fields for today's BOLT11 invoices - most nodes require a [`PaymentSecret`] /// but do not require or provide any further data. pub fn secret_only(payment_secret: PaymentSecret) -> Self { - Self { payment_secret: Some(payment_secret), payment_metadata: None, custom_tlvs: Vec::new() } + Self { payment_secret: Some(payment_secret), payment_metadata: None, sender_custom_tlvs: Vec::new(), user_custom_tlvs: Vec::new() } } /// Creates a new [`RecipientOnionFields`] with no fields. This generally does not create @@ -625,7 +629,7 @@ impl RecipientOnionFields { /// [`ChannelManager::send_spontaneous_payment`]: super::channelmanager::ChannelManager::send_spontaneous_payment /// [`RecipientOnionFields::secret_only`]: RecipientOnionFields::secret_only pub fn spontaneous_empty() -> Self { - Self { payment_secret: None, payment_metadata: None, custom_tlvs: Vec::new() } + Self { payment_secret: None, payment_metadata: None, sender_custom_tlvs: Vec::new(), user_custom_tlvs: Vec::new() } } /// Creates a new [`RecipientOnionFields`] from an existing one, adding custom TLVs. Each @@ -637,11 +641,12 @@ impl RecipientOnionFields { /// standardized within the protocol, which only includes 5482373484 (keysend) for now. /// /// See [`Self::custom_tlvs`] for more info. - pub fn with_custom_tlvs(mut self, mut custom_tlvs: Vec<(u64, Vec)>) -> Result { - custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ); + pub fn with_sender_custom_tlvs(mut self, mut sender_custom_tlvs: Vec<(u64, Vec)>) -> Result { + sender_custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ); let mut prev_type = None; - for (typ, _) in custom_tlvs.iter() { + for (typ, _) in sender_custom_tlvs.iter() { if *typ < 1 << 16 { return Err(()); } + if *typ == 1 << 16 + 1 { return Err(()); } // user_custom_tlv if *typ == 5482373484 { return Err(()); } // keysend if *typ == 77_777 { return Err(()); } // invoice requests for async payments match prev_type { @@ -650,7 +655,7 @@ impl RecipientOnionFields { } prev_type = Some(*typ); } - self.custom_tlvs = custom_tlvs; + self.sender_custom_tlvs = sender_custom_tlvs; Ok(self) } @@ -665,8 +670,8 @@ impl RecipientOnionFields { /// represented by a `(u64, Vec)` for its type number and serialized value respectively. /// This is validated when setting this field using [`Self::with_custom_tlvs`]. #[cfg(not(c_bindings))] - pub fn custom_tlvs(&self) -> &Vec<(u64, Vec)> { - &self.custom_tlvs + pub fn sender_custom_tlvs(&self) -> &Vec<(u64, Vec)> { + &self.sender_custom_tlvs } /// Gets the custom TLVs that will be sent or have been received. @@ -680,8 +685,8 @@ impl RecipientOnionFields { /// represented by a `(u64, Vec)` for its type number and serialized value respectively. /// This is validated when setting this field using [`Self::with_custom_tlvs`]. #[cfg(c_bindings)] - pub fn custom_tlvs(&self) -> Vec<(u64, Vec)> { - self.custom_tlvs.clone() + pub fn sender_custom_tlvs(&self) -> Vec<(u64, Vec)> { + self.sender_custom_tlvs.clone() } /// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we @@ -695,8 +700,8 @@ impl RecipientOnionFields { if self.payment_secret != further_htlc_fields.payment_secret { return Err(()); } if self.payment_metadata != further_htlc_fields.payment_metadata { return Err(()); } - let tlvs = &mut self.custom_tlvs; - let further_tlvs = &mut further_htlc_fields.custom_tlvs; + let tlvs = &mut self.sender_custom_tlvs; + let further_tlvs = &mut further_htlc_fields.sender_custom_tlvs; let even_tlvs = tlvs.iter().filter(|(typ, _)| *typ % 2 == 0); let further_even_tlvs = further_tlvs.iter().filter(|(typ, _)| *typ % 2 == 0); @@ -929,7 +934,8 @@ impl OutboundPayments { let recipient_onion = RecipientOnionFields { payment_secret: None, payment_metadata: None, - custom_tlvs: vec![], + sender_custom_tlvs: vec![], + user_custom_tlvs: vec![], }; let route = match self.find_initial_route( payment_id, payment_hash, &recipient_onion, keysend_preimage, invoice_request, @@ -1346,7 +1352,7 @@ impl OutboundPayments { match payment.get() { PendingOutboundPayment::Retryable { total_msat, keysend_preimage, payment_secret, payment_metadata, - custom_tlvs, pending_amt_msat, invoice_request, .. + invoice_request, sender_custom_tlvs, user_custom_tlvs, pending_amt_msat, .. } => { const RETRY_OVERFLOW_PERCENTAGE: u64 = 10; let retry_amt_msat = route.get_total_amount(); @@ -1366,7 +1372,8 @@ impl OutboundPayments { let recipient_onion = RecipientOnionFields { payment_secret: *payment_secret, payment_metadata: payment_metadata.clone(), - custom_tlvs: custom_tlvs.clone(), + sender_custom_tlvs: sender_custom_tlvs.clone(), + user_custom_tlvs: user_custom_tlvs.clone() }; let keysend_preimage = *keysend_preimage; let invoice_request = invoice_request.clone(); @@ -1608,7 +1615,8 @@ impl OutboundPayments { payment_metadata: recipient_onion.payment_metadata, keysend_preimage, invoice_request, - custom_tlvs: recipient_onion.custom_tlvs, + sender_custom_tlvs: recipient_onion.sender_custom_tlvs, + user_custom_tlvs: recipient_onion.user_custom_tlvs, starting_block_height: best_block_height, total_msat: route.get_total_amount(), remaining_max_total_routing_fee_msat: @@ -2170,7 +2178,8 @@ impl OutboundPayments { payment_metadata: None, // only used for retries, and we'll never retry on startup keysend_preimage: None, // only used for retries, and we'll never retry on startup invoice_request: None, // only used for retries, and we'll never retry on startup - custom_tlvs: Vec::new(), // only used for retries, and we'll never retry on startup + sender_custom_tlvs: Vec::new(), // only used for retries, and we'll never retry on startup + user_custom_tlvs: Vec::new(), // only used for retries, and we'll never retry on startup pending_amt_msat: path_amt, pending_fee_msat: Some(path_fee), total_msat: path_amt, @@ -2253,10 +2262,11 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, (6, total_msat, required), (7, payment_metadata, option), (8, pending_amt_msat, required), - (9, custom_tlvs, optional_vec), + (9, sender_custom_tlvs, optional_vec), (10, starting_block_height, required), (11, remaining_max_total_routing_fee_msat, option), (13, invoice_request, option), + (15, user_custom_tlvs, optional_vec), (not_written, retry_strategy, (static_value, None)), (not_written, attempts, (static_value, PaymentAttempts::new())), }, @@ -2326,18 +2336,18 @@ mod tests { (0, vec![42]), (1, vec![42; 32]), ]; - assert!(onion_fields.clone().with_custom_tlvs(bad_type_range_tlvs).is_err()); + assert!(onion_fields.clone().with_sender_custom_tlvs(bad_type_range_tlvs).is_err()); let keysend_tlv = vec![ (5482373484, vec![42; 32]), ]; - assert!(onion_fields.clone().with_custom_tlvs(keysend_tlv).is_err()); + assert!(onion_fields.clone().with_sender_custom_tlvs(keysend_tlv).is_err()); let good_tlvs = vec![ ((1 << 16) + 1, vec![42]), ((1 << 16) + 3, vec![42; 32]), ]; - assert!(onion_fields.with_custom_tlvs(good_tlvs).is_ok()); + assert!(onion_fields.with_sender_custom_tlvs(good_tlvs).is_ok()); } #[test] diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index ca386682fa0..0f60a0fbd4c 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -3705,14 +3705,15 @@ fn do_test_custom_tlvs(spontaneous: bool, even_tlvs: bool, known_tlvs: bool) { let amt_msat = 100_000; let (mut route, our_payment_hash, our_payment_preimage, our_payment_secret) = get_route_and_payment_hash!(&nodes[0], &nodes[1], amt_msat); let payment_id = PaymentId(our_payment_hash.0); - let custom_tlvs = vec![ + let sender_custom_tlvs = vec![ (if even_tlvs { 5482373482 } else { 5482373483 }, vec![1, 2, 3, 4]), (5482373487, vec![0x42u8; 16]), ]; let onion_fields = RecipientOnionFields { payment_secret: if spontaneous { None } else { Some(our_payment_secret) }, payment_metadata: None, - custom_tlvs: custom_tlvs.clone() + sender_custom_tlvs: sender_custom_tlvs.clone(), + user_custom_tlvs: vec![], }; if spontaneous { nodes[0].node.send_spontaneous_payment(&route, Some(our_payment_preimage), onion_fields, payment_id).unwrap(); @@ -3734,7 +3735,7 @@ fn do_test_custom_tlvs(spontaneous: bool, even_tlvs: bool, known_tlvs: bool) { assert_eq!(events.len(), 1); match events[0] { Event::PaymentClaimable { ref onion_fields, .. } => { - assert_eq!(onion_fields.clone().unwrap().custom_tlvs().clone(), custom_tlvs); + assert_eq!(onion_fields.clone().unwrap().sender_custom_tlvs().clone(), sender_custom_tlvs); }, _ => panic!("Unexpected event"), } @@ -3744,14 +3745,14 @@ fn do_test_custom_tlvs(spontaneous: bool, even_tlvs: bool, known_tlvs: bool) { nodes[1].node.claim_funds_with_known_custom_tlvs(our_payment_preimage); let expected_total_fee_msat = pass_claimed_payment_along_route( ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1]]], our_payment_preimage) - .with_custom_tlvs(custom_tlvs) + .with_sender_custom_tlvs(sender_custom_tlvs) ); expect_payment_sent!(&nodes[0], our_payment_preimage, Some(expected_total_fee_msat)); }, (false, false) => { claim_payment_along_route( ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1]]], our_payment_preimage) - .with_custom_tlvs(custom_tlvs) + .with_sender_custom_tlvs(sender_custom_tlvs) ); }, (false, true) => { @@ -3785,9 +3786,9 @@ fn test_retry_custom_tlvs() { let payment_id = PaymentId(payment_hash.0); let mut route_params = route.route_params.clone().unwrap(); - let custom_tlvs = vec![((1 << 16) + 1, vec![0x42u8; 16])]; + let sender_custom_tlvs = vec![((1 << 16) + 3, vec![0x42u8; 16])]; let onion_fields = RecipientOnionFields::secret_only(payment_secret); - let onion_fields = onion_fields.with_custom_tlvs(custom_tlvs.clone()).unwrap(); + let onion_fields = onion_fields.with_sender_custom_tlvs(sender_custom_tlvs.clone()).unwrap(); nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone())); nodes[0].node.send_payment(payment_hash, onion_fields, @@ -3839,20 +3840,21 @@ fn test_retry_custom_tlvs() { let path = &[&nodes[1], &nodes[2]]; let args = PassAlongPathArgs::new(&nodes[0], path, 1_000_000, payment_hash, events.pop().unwrap()) .with_payment_secret(payment_secret) - .with_custom_tlvs(custom_tlvs.clone()); + .with_sender_custom_tlvs(sender_custom_tlvs.clone()); do_pass_along_path(args); claim_payment_along_route( ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1], &nodes[2]]], payment_preimage) - .with_custom_tlvs(custom_tlvs) + .with_sender_custom_tlvs(sender_custom_tlvs) ); } #[test] fn test_custom_tlvs_consistency() { let even_type_1 = 1 << 16; - let odd_type_1 = (1 << 16)+ 1; + // (1 << 16) + 1 = 66537 is used by user_custom_tlvs. + let odd_type_1 = (1 << 16)+ 3; let even_type_2 = (1 << 16) + 2; - let odd_type_2 = (1 << 16) + 3; + let odd_type_2 = (1 << 16) + 5; let value_1 = || vec![1, 2, 3, 4]; let differing_value_1 = || vec![1, 2, 3, 5]; let value_2 = || vec![42u8; 16]; @@ -3914,7 +3916,8 @@ fn do_test_custom_tlvs_consistency(first_tlvs: Vec<(u64, Vec)>, second_tlvs: let onion_fields = RecipientOnionFields { payment_secret: Some(our_payment_secret), payment_metadata: None, - custom_tlvs: first_tlvs + sender_custom_tlvs: first_tlvs, + user_custom_tlvs: vec![], }; let session_privs = nodes[0].node.test_add_new_pending_payment(our_payment_hash, onion_fields.clone(), payment_id, &route).unwrap(); @@ -3936,7 +3939,8 @@ fn do_test_custom_tlvs_consistency(first_tlvs: Vec<(u64, Vec)>, second_tlvs: let onion_fields = RecipientOnionFields { payment_secret: Some(our_payment_secret), payment_metadata: None, - custom_tlvs: second_tlvs + sender_custom_tlvs: second_tlvs, + user_custom_tlvs: vec![], }; nodes[0].node.test_send_payment_along_path(&route.paths[1], &our_payment_hash, onion_fields.clone(), amt_msat, cur_height, payment_id, &None, session_privs[1]).unwrap(); @@ -3970,14 +3974,14 @@ fn do_test_custom_tlvs_consistency(first_tlvs: Vec<(u64, Vec)>, second_tlvs: assert_eq!(events.len(), 1); match events[0] { Event::PaymentClaimable { ref onion_fields, .. } => { - assert_eq!(onion_fields.clone().unwrap().custom_tlvs, expected_tlvs); + assert_eq!(onion_fields.clone().unwrap().sender_custom_tlvs, expected_tlvs); }, _ => panic!("Unexpected event"), } do_claim_payment_along_route( ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], our_payment_preimage) - .with_custom_tlvs(expected_tlvs) + .with_sender_custom_tlvs(expected_tlvs) ); expect_payment_sent(&nodes[0], our_payment_preimage, Some(Some(2000)), true, true); } else { @@ -4042,7 +4046,7 @@ fn do_test_payment_metadata_consistency(do_reload: bool, do_modify: bool) { // Send the MPP payment, delivering the updated commitment state to nodes[1]. nodes[0].node.send_payment(payment_hash, RecipientOnionFields { - payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata), custom_tlvs: vec![], + payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata), sender_custom_tlvs: vec![], user_custom_tlvs: vec![], }, payment_id, route_params.clone(), Retry::Attempts(1)).unwrap(); check_added_monitors!(nodes[0], 2); @@ -4263,7 +4267,7 @@ fn peel_payment_onion_custom_tlvs() { let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); let route = functional_test_utils::get_route(&nodes[0], &route_params).unwrap(); let mut recipient_onion = RecipientOnionFields::spontaneous_empty() - .with_custom_tlvs(vec![(414141, vec![42; 1200])]).unwrap(); + .with_sender_custom_tlvs(vec![(414141, vec![42; 1200])]).unwrap(); let prng_seed = chanmon_cfgs[0].keys_manager.get_secure_random_bytes(); let session_priv = SecretKey::from_slice(&prng_seed[..]).expect("RNG is busted"); let keysend_preimage = PaymentPreimage([42; 32]); @@ -4291,10 +4295,10 @@ fn peel_payment_onion_custom_tlvs() { assert_eq!(peeled_onion.incoming_amt_msat, Some(amt_msat)); match peeled_onion.routing { PendingHTLCRouting::ReceiveKeysend { - payment_data, payment_metadata, custom_tlvs, .. + payment_data, payment_metadata, sender_custom_tlvs, .. } => { #[cfg(not(c_bindings))] - assert_eq!(&custom_tlvs, recipient_onion.custom_tlvs()); + assert_eq!(&sender_custom_tlvs, recipient_onion.sender_custom_tlvs()); #[cfg(c_bindings)] assert_eq!(custom_tlvs, recipient_onion.custom_tlvs()); assert!(payment_metadata.is_none()); From 0c33ae9a5bbc05d6310f0e52f0f80c9ee3f37efd Mon Sep 17 00:00:00 2001 From: shaavan Date: Sat, 26 Oct 2024 18:18:07 +0530 Subject: [PATCH 3/4] Add Custom TLVs for `payment::ReceiveTlvs` - Building on the previous commit, this update allows users to include their own custom TLVs within the reply path of a sent onion message. - With this, users can attach custom data to the message, which will be returned in the response, providing more flexibility for custom use cases. --- lightning/src/blinded_path/payment.rs | 12 +++++++++++- lightning/src/ln/blinded_payment_tests.rs | 4 ++++ lightning/src/ln/channelmanager.rs | 7 ++++--- lightning/src/ln/max_payment_path_len_tests.rs | 1 + lightning/src/ln/msgs.rs | 4 +++- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 62ce7dec186..7cadde1ab5e 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -260,6 +260,8 @@ pub struct ReceiveTlvs { pub payment_constraints: PaymentConstraints, /// Context for the receiver of this payment. pub payment_context: PaymentContext, + /// Custom Tlvs + pub custom_tlvs: Vec, } /// Data to construct a [`BlindedHop`] for sending a payment over. @@ -404,7 +406,8 @@ impl Writeable for ReceiveTlvs { encode_tlv_stream!(w, { (12, self.payment_constraints, required), (65536, self.payment_secret, required), - (65537, self.payment_context, required) + (65537, self.payment_context, required), + (65539, self.custom_tlvs, (default_value, Vec::new())), }); Ok(()) } @@ -432,6 +435,7 @@ impl Readable for BlindedPaymentTlvs { (14, features, (option, encoding: (BlindedHopFeatures, WithoutLength))), (65536, payment_secret, option), (65537, payment_context, (default_value, PaymentContext::unknown())), + (65539, custom_tlvs, (default_value, Vec::new())) }); let _padding: Option = _padding; @@ -452,6 +456,7 @@ impl Readable for BlindedPaymentTlvs { payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?, payment_constraints: payment_constraints.0.unwrap(), payment_context: payment_context.0.unwrap(), + custom_tlvs: custom_tlvs.0.unwrap(), })) } } @@ -683,6 +688,7 @@ mod tests { htlc_minimum_msat: 1, }, payment_context: PaymentContext::unknown(), + custom_tlvs: Vec::new(), }; let htlc_maximum_msat = 100_000; let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, 12).unwrap(); @@ -702,6 +708,7 @@ mod tests { htlc_minimum_msat: 1, }, payment_context: PaymentContext::unknown(), + custom_tlvs: Vec::new(), }; let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs, 4242, TEST_FINAL_CLTV as u16).unwrap(); assert_eq!(blinded_payinfo.fee_base_msat, 0); @@ -758,6 +765,7 @@ mod tests { htlc_minimum_msat: 3, }, payment_context: PaymentContext::unknown(), + custom_tlvs: Vec::new(), }; let htlc_maximum_msat = 100_000; let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, TEST_FINAL_CLTV as u16).unwrap(); @@ -811,6 +819,7 @@ mod tests { htlc_minimum_msat: 1, }, payment_context: PaymentContext::unknown(), + custom_tlvs: Vec::new(), }; let htlc_minimum_msat = 3798; assert!(super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_minimum_msat - 1, TEST_FINAL_CLTV as u16).is_err()); @@ -868,6 +877,7 @@ mod tests { htlc_minimum_msat: 1, }, payment_context: PaymentContext::unknown(), + custom_tlvs: Vec::new() }; let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000, TEST_FINAL_CLTV as u16).unwrap(); diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index ee6d8cf3983..2a1ae8f00c0 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -74,6 +74,7 @@ fn blinded_payment_path( intro_node_min_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_minimum_msat), }, payment_context: PaymentContext::unknown(), + custom_tlvs: Vec::new(), }; let mut secp_ctx = Secp256k1::new(); BlindedPaymentPath::new( @@ -120,6 +121,7 @@ fn do_one_hop_blinded_path(success: bool) { htlc_minimum_msat: chan_upd.htlc_minimum_msat, }, payment_context: PaymentContext::unknown(), + custom_tlvs: Vec::new(), }; let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( @@ -164,6 +166,7 @@ fn mpp_to_one_hop_blinded_path() { htlc_minimum_msat: chan_upd_1_3.htlc_minimum_msat, }, payment_context: PaymentContext::unknown(), + custom_tlvs: Vec::new(), }; let blinded_path = BlindedPaymentPath::new( &[], nodes[3].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, @@ -1310,6 +1313,7 @@ fn custom_tlvs_to_blinded_path() { htlc_minimum_msat: chan_upd.htlc_minimum_msat, }, payment_context: PaymentContext::unknown(), + custom_tlvs: Vec::new(), }; let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 2693d381295..f052887e41c 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9592,7 +9592,7 @@ where Ok((payment_hash, payment_secret)) => { let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); let payment_paths = self.create_blinded_payment_paths( - amount_msats, payment_secret, payment_context + amount_msats, payment_secret, payment_context, None ) .map_err(|_| Bolt12SemanticError::MissingPaths)?; @@ -9832,7 +9832,7 @@ where /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to /// [`Router::create_blinded_payment_paths`]. fn create_blinded_payment_paths( - &self, amount_msats: u64, payment_secret: PaymentSecret, payment_context: PaymentContext + &self, amount_msats: u64, payment_secret: PaymentSecret, payment_context: PaymentContext, custom_tlvs: Option> ) -> Result, ()> { let secp_ctx = &self.secp_ctx; @@ -9847,6 +9847,7 @@ where htlc_minimum_msat: 1, }, payment_context, + custom_tlvs: custom_tlvs.unwrap_or_default() }; self.router.create_blinded_payment_paths( payee_node_id, first_hops, payee_tlvs, amount_msats, secp_ctx @@ -11362,7 +11363,7 @@ where invoice_request: invoice_request.fields(), }); let payment_paths = match self.create_blinded_payment_paths( - amount_msats, payment_secret, payment_context + amount_msats, payment_secret, payment_context, custom_tlvs.clone() ) { Ok(payment_paths) => payment_paths, Err(()) => { diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index aa18fce7ab4..6b1f8881545 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -167,6 +167,7 @@ fn one_hop_blinded_path_with_custom_tlv() { htlc_minimum_msat: chan_upd_1_2.htlc_minimum_msat, }, payment_context: PaymentContext::unknown(), + custom_tlvs: Vec::new(), }; let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index b312a2bf8c2..3a5d6c0d892 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -2925,9 +2925,11 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh next_blinding_override, }) }, + // Note: The custom tlvs in the receive tlvs is not used here. ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs { - payment_secret, payment_constraints, payment_context + payment_secret, payment_constraints, payment_context, custom_tlvs: user_custom_tlv })} => { + debug_assert_eq!(user_custom_tlv, user_custom_tlvs, "The custom TLVs in ReceiveTlvs must match the ones read from serialization."); if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } Ok(Self::BlindedReceive { sender_intended_htlc_amt_msat: amt.ok_or(DecodeError::InvalidValue)?, From f9a8608a32e057ce6db923487178e705a582dddb Mon Sep 17 00:00:00 2001 From: shaavan Date: Thu, 7 Nov 2024 18:21:38 +0530 Subject: [PATCH 4/4] Test sending, and receiving of user_custom_tlvs --- lightning/src/ln/blinded_payment_tests.rs | 5 ++++- lightning/src/ln/functional_test_utils.rs | 4 ++++ lightning/src/ln/outbound_payment.rs | 5 +++++ lightning/src/ln/payment_tests.rs | 8 +++++++- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 2a1ae8f00c0..192d021201b 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -1313,7 +1313,7 @@ fn custom_tlvs_to_blinded_path() { htlc_minimum_msat: chan_upd.htlc_minimum_msat, }, payment_context: PaymentContext::unknown(), - custom_tlvs: Vec::new(), + custom_tlvs: vec![43, 43] }; let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( @@ -1327,6 +1327,7 @@ fn custom_tlvs_to_blinded_path() { ); let recipient_onion_fields = RecipientOnionFields::spontaneous_empty() + .with_user_custom_tlvs(vec![43, 43]) .with_sender_custom_tlvs(vec![((1 << 16) + 3, vec![42, 42])]) .unwrap(); nodes[0].node.send_payment(payment_hash, recipient_onion_fields.clone(), @@ -1340,10 +1341,12 @@ fn custom_tlvs_to_blinded_path() { let path = &[&nodes[1]]; let args = PassAlongPathArgs::new(&nodes[0], path, amt_msat, payment_hash, ev) .with_payment_secret(payment_secret) + .with_user_custom_tlvs(recipient_onion_fields.user_custom_tlvs.clone()) .with_sender_custom_tlvs(recipient_onion_fields.sender_custom_tlvs.clone()); do_pass_along_path(args); claim_payment_along_route( ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1]]], payment_preimage) + .with_user_custom_tlvs(recipient_onion_fields.user_custom_tlvs.clone()) .with_sender_custom_tlvs(recipient_onion_fields.sender_custom_tlvs.clone()) ); } diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 5a8b22d2543..23d90bb8448 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -2828,6 +2828,10 @@ impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> { self.allow_1_msat_fee_overpay = true; self } + pub fn with_user_custom_tlvs(mut self, custom_tlvs: Vec) -> Self { + self.user_custom_tlvs = custom_tlvs; + self + } pub fn with_sender_custom_tlvs(mut self, custom_tlvs: Vec<(u64, Vec)>) -> Self { self.sender_custom_tlvs = custom_tlvs; self diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 46a45356d55..265b0ca52f9 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -659,6 +659,11 @@ impl RecipientOnionFields { Ok(self) } + pub fn with_user_custom_tlvs(mut self, custom_tlvs: Vec) -> Self { + self.user_custom_tlvs = custom_tlvs; + self + } + /// Gets the custom TLVs that will be sent or have been received. /// /// Custom TLVs allow sending extra application-specific data with a payment. They provide diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 0f60a0fbd4c..60511edc852 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -3787,8 +3787,12 @@ fn test_retry_custom_tlvs() { let mut route_params = route.route_params.clone().unwrap(); let sender_custom_tlvs = vec![((1 << 16) + 3, vec![0x42u8; 16])]; + let user_custom_tlvs = vec![0x43u8; 16]; let onion_fields = RecipientOnionFields::secret_only(payment_secret); - let onion_fields = onion_fields.with_sender_custom_tlvs(sender_custom_tlvs.clone()).unwrap(); + let onion_fields = onion_fields + .with_user_custom_tlvs(user_custom_tlvs.clone()) + .with_sender_custom_tlvs(sender_custom_tlvs.clone()) + .unwrap(); nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone())); nodes[0].node.send_payment(payment_hash, onion_fields, @@ -3840,10 +3844,12 @@ fn test_retry_custom_tlvs() { let path = &[&nodes[1], &nodes[2]]; let args = PassAlongPathArgs::new(&nodes[0], path, 1_000_000, payment_hash, events.pop().unwrap()) .with_payment_secret(payment_secret) + .with_user_custom_tlvs(user_custom_tlvs.clone()) .with_sender_custom_tlvs(sender_custom_tlvs.clone()); do_pass_along_path(args); claim_payment_along_route( ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1], &nodes[2]]], payment_preimage) + .with_user_custom_tlvs(user_custom_tlvs) .with_sender_custom_tlvs(sender_custom_tlvs) ); }