Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce custom TLV support for OnionMessage #2830

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash;
use bitcoin::hashes::Hash as TraitImport;
use bitcoin::WPubkeyHash;

use lightning::blinded_path::message::{BlindedMessagePath, MessageContext};
use lightning::blinded_path::message::{self, BlindedMessagePath};
use lightning::blinded_path::payment::{BlindedPaymentPath, ReceiveTlvs};
use lightning::chain;
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
Expand Down Expand Up @@ -141,7 +141,7 @@ impl MessageRouter for FuzzRouter {
}

fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
&self, _recipient: PublicKey, _context: MessageContext, _peers: Vec<PublicKey>,
&self, _recipient: PublicKey, _receive_tlvs: message::ReceiveTlvs, _peers: Vec<PublicKey>,
_secp_ctx: &Secp256k1<T>,
) -> Result<Vec<BlindedMessagePath>, ()> {
unreachable!()
Expand Down
4 changes: 2 additions & 2 deletions fuzz/src/full_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use bitcoin::hashes::Hash as _;
use bitcoin::hex::FromHex;
use bitcoin::WPubkeyHash;

use lightning::blinded_path::message::{BlindedMessagePath, MessageContext};
use lightning::blinded_path::message::{self, BlindedMessagePath};
use lightning::blinded_path::payment::{BlindedPaymentPath, ReceiveTlvs};
use lightning::chain;
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
Expand Down Expand Up @@ -176,7 +176,7 @@ impl MessageRouter for FuzzRouter {
}

fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
&self, _recipient: PublicKey, _context: MessageContext, _peers: Vec<PublicKey>,
&self, _recipient: PublicKey, _receive_tlvs: message::ReceiveTlvs, _peers: Vec<PublicKey>,
_secp_ctx: &Secp256k1<T>,
) -> Result<Vec<BlindedMessagePath>, ()> {
unreachable!()
Expand Down
1 change: 1 addition & 0 deletions fuzz/src/invoice_request_deser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
htlc_minimum_msat: 1,
},
payment_context,
custom_data: Vec::new(),
};
let intermediate_nodes = [PaymentForwardNode {
tlvs: ForwardTlvs {
Expand Down
6 changes: 3 additions & 3 deletions fuzz/src/onion_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bitcoin::secp256k1::schnorr;
use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey};

use lightning::blinded_path::message::{
AsyncPaymentsContext, BlindedMessagePath, MessageContext, OffersContext,
AsyncPaymentsContext, BlindedMessagePath, OffersContext, ReceiveTlvs,
};
use lightning::blinded_path::EmptyNodeIdLookUp;
use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
Expand Down Expand Up @@ -99,7 +99,7 @@ impl MessageRouter for TestMessageRouter {
}

fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
&self, _recipient: PublicKey, _context: MessageContext, _peers: Vec<PublicKey>,
&self, _recipient: PublicKey, _receive_tlvs: ReceiveTlvs, _peers: Vec<PublicKey>,
_secp_ctx: &Secp256k1<T>,
) -> Result<Vec<BlindedMessagePath>, ()> {
unreachable!()
Expand All @@ -111,7 +111,7 @@ struct TestOffersMessageHandler {}
impl OffersMessageHandler for TestOffersMessageHandler {
fn handle_message(
&self, _message: OffersMessage, _context: Option<OffersContext>,
_responder: Option<Responder>,
_custom_data: Option<Vec<u8>>, _responder: Option<Responder>,
) -> Option<(OffersMessage, ResponseInstruction)> {
None
}
Expand Down
1 change: 1 addition & 0 deletions fuzz/src/refund_deser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
htlc_minimum_msat: 1,
},
payment_context,
custom_data: Vec::new(),
};
let intermediate_nodes = [PaymentForwardNode {
tlvs: ForwardTlvs {
Expand Down
9 changes: 5 additions & 4 deletions lightning-dns-resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ mod test {
use bitcoin::secp256k1::{self, PublicKey, Secp256k1};
use bitcoin::Block;

use lightning::blinded_path::message::{BlindedMessagePath, MessageContext};
use lightning::blinded_path::message::{BlindedMessagePath, MessageContext, ReceiveTlvs};
use lightning::blinded_path::NodeIdLookUp;
use lightning::events::{Event, PaymentPurpose};
use lightning::ln::channelmanager::{PaymentId, Retry};
Expand Down Expand Up @@ -225,11 +225,11 @@ mod test {
}

fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
&self, recipient: PublicKey, context: MessageContext, _peers: Vec<PublicKey>,
&self, recipient: PublicKey, receive_tlvs: ReceiveTlvs, _peers: Vec<PublicKey>,
secp_ctx: &Secp256k1<T>,
) -> Result<Vec<BlindedMessagePath>, ()> {
let keys = KeysManager::new(&[0; 32], 42, 43);
Ok(vec![BlindedMessagePath::one_hop(recipient, context, &keys, secp_ctx).unwrap()])
Ok(vec![BlindedMessagePath::one_hop(recipient, receive_tlvs, &keys, secp_ctx).unwrap()])
}
}
impl Deref for DirectlyConnectedRouter {
Expand Down Expand Up @@ -331,8 +331,9 @@ mod test {
let (msg, context) =
payer.resolver.resolve_name(payment_id, name.clone(), &*payer_keys).unwrap();
let query_context = MessageContext::DNSResolver(context);
let receive_tlvs = ReceiveTlvs { context: Some(query_context), custom_data: None };
let reply_path =
BlindedMessagePath::one_hop(payer_id, query_context, &*payer_keys, &secp_ctx).unwrap();
BlindedMessagePath::one_hop(payer_id, receive_tlvs, &*payer_keys, &secp_ctx).unwrap();
payer.pending_messages.lock().unwrap().push((
DNSResolverMessage::DNSSECQuery(msg),
MessageSendInstructions::WithSpecifiedReplyPath {
Expand Down
33 changes: 24 additions & 9 deletions lightning/src/blinded_path/message.rs
shaavan marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ impl Readable for BlindedMessagePath {
impl BlindedMessagePath {
/// Create a one-hop blinded path for a message.
pub fn one_hop<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
recipient_node_id: PublicKey, context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1<T>
recipient_node_id: PublicKey, receive_tlvs: ReceiveTlvs, entropy_source: ES, secp_ctx: &Secp256k1<T>
) -> Result<Self, ()> where ES::Target: EntropySource {
Self::new(&[], recipient_node_id, context, entropy_source, secp_ctx)
Self::new(&[], recipient_node_id, receive_tlvs, entropy_source, secp_ctx)
}

/// Create a path for an onion message, to be forwarded along `node_pks`. The last node
Expand All @@ -67,7 +67,7 @@ impl BlindedMessagePath {
// TODO: make all payloads the same size with padding + add dummy hops
pub fn new<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
intermediate_nodes: &[MessageForwardNode], recipient_node_id: PublicKey,
context: MessageContext, entropy_source: ES, secp_ctx: &Secp256k1<T>,
receive_tlvs: ReceiveTlvs, entropy_source: ES, secp_ctx: &Secp256k1<T>,
) -> Result<Self, ()> where ES::Target: EntropySource {
let introduction_node = IntroductionNode::NodeId(
intermediate_nodes.first().map_or(recipient_node_id, |n| n.node_id)
Expand All @@ -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,
receive_tlvs, &blinding_secret,
).map_err(|_| ())?,
}))
}
Expand Down Expand Up @@ -236,12 +236,26 @@ pub(crate) struct ForwardTlvs {
pub(crate) next_blinding_override: Option<PublicKey>,
}

/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
pub(crate) struct ReceiveTlvs {
#[derive(Clone)]
/// Similar to Forward Tlvs, but these TLVs are for the final node.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not mention "Forward TLVs" if they are pub(crate). Instead, use similar wording from the ForwardTlvs docs.

pub 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<MessageContext>
pub context: Option<MessageContext>,

/// Custom data set by the user. If `custom_data` is `Some`, it will be returned when the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/returned/provided to the message recipient

/// blinded path is used.
///
/// This field allows encoding custom data intended to be retrieved when the blinded path is used.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/retrieved/provided back

///
/// ## Note on Forward Compatibility:
/// Users can encode any kind of data into the `Vec<u8>` bytes here. However, they should ensure
/// that the data is structured in a forward-compatible manner. This is especially important as
/// `ReceiveTlvs` created in one version of the software may still appear in messages received
/// shortly after a software upgrade. Proper forward compatibility helps prevent data loss or
/// misinterpretation in future versions.
pub custom_data: Option<Vec<u8>>,
}

impl Writeable for ForwardTlvs {
Expand All @@ -265,6 +279,7 @@ impl Writeable for ReceiveTlvs {
// TODO: write padding
encode_tlv_stream!(writer, {
(65537, self.context, option),
(65539, self.custom_data, option),
});
Ok(())
}
Expand Down Expand Up @@ -456,7 +471,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<T: secp256k1::Signing + secp256k1::Verification>(
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[MessageForwardNode],
recipient_node_id: PublicKey, context: MessageContext, session_priv: &SecretKey,
recipient_node_id: PublicKey, receive_tlvs: ReceiveTlvs, session_priv: &SecretKey,
) -> Result<Vec<BlindedHop>, secp256k1::Error> {
let pks = intermediate_nodes.iter().map(|node| node.node_id)
.chain(core::iter::once(recipient_node_id));
Expand All @@ -468,7 +483,7 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
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(receive_tlvs)));

let path = pks.zip(tlvs);

Expand Down
19 changes: 18 additions & 1 deletion lightning/src/blinded_path/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,15 @@ pub struct ReceiveTlvs {
pub payment_constraints: PaymentConstraints,
/// Context for the receiver of this payment.
pub payment_context: PaymentContext,
/// Custom data set by the user. And is returned back when the blinded path is used.
///
/// ## Note on Forward Compatibility:
/// Users can encode any kind of data into the `Vec<u8>` bytes here. However, they should ensure
/// that the data is structured in a forward-compatible manner. This is especially important as
/// `ReceiveTlvs` created in one version of the software may still appear in payments received
/// shortly after a software upgrade. Proper forward compatibility helps prevent data loss or
/// misinterpretation in future versions.
pub custom_data: Vec<u8>,
}

/// Data to construct a [`BlindedHop`] for sending a payment over.
Expand Down Expand Up @@ -404,7 +413,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_data, (default_value, Vec::new())),
});
Ok(())
}
Expand Down Expand Up @@ -432,6 +442,7 @@ impl Readable for BlindedPaymentTlvs {
(14, features, (option, encoding: (BlindedHopFeatures, WithoutLength))),
(65536, payment_secret, option),
(65537, payment_context, (default_value, PaymentContext::unknown())),
(65539, custom_data, (default_value, Vec::new()))
});
let _padding: Option<utils::Padding> = _padding;

Expand All @@ -452,6 +463,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_data: custom_data.0.unwrap(),
}))
}
}
Expand Down Expand Up @@ -683,6 +695,7 @@ mod tests {
htlc_minimum_msat: 1,
},
payment_context: PaymentContext::unknown(),
custom_data: Vec::new(),
};
let htlc_maximum_msat = 100_000;
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, 12).unwrap();
Expand All @@ -702,6 +715,7 @@ mod tests {
htlc_minimum_msat: 1,
},
payment_context: PaymentContext::unknown(),
custom_data: 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);
Expand Down Expand Up @@ -758,6 +772,7 @@ mod tests {
htlc_minimum_msat: 3,
},
payment_context: PaymentContext::unknown(),
custom_data: 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();
Expand Down Expand Up @@ -811,6 +826,7 @@ mod tests {
htlc_minimum_msat: 1,
},
payment_context: PaymentContext::unknown(),
custom_data: 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());
Expand Down Expand Up @@ -868,6 +884,7 @@ mod tests {
htlc_minimum_msat: 1,
},
payment_context: PaymentContext::unknown(),
custom_data: Vec::new()
};

let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000, TEST_FINAL_CLTV as u16).unwrap();
Expand Down
13 changes: 10 additions & 3 deletions lightning/src/ln/blinded_payment_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,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_data: Vec::new(),
};
let mut secp_ctx = Secp256k1::new();
BlindedPaymentPath::new(
Expand Down Expand Up @@ -123,6 +124,7 @@ fn do_one_hop_blinded_path(success: bool) {
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
},
payment_context: PaymentContext::unknown(),
custom_data: Vec::new(),
};
let mut secp_ctx = Secp256k1::new();
let blinded_path = BlindedPaymentPath::new(
Expand Down Expand Up @@ -167,6 +169,7 @@ fn mpp_to_one_hop_blinded_path() {
htlc_minimum_msat: chan_upd_1_3.htlc_minimum_msat,
},
payment_context: PaymentContext::unknown(),
custom_data: Vec::new(),
};
let blinded_path = BlindedPaymentPath::new(
&[], nodes[3].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16,
Expand Down Expand Up @@ -1376,6 +1379,7 @@ fn custom_tlvs_to_blinded_path() {
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
},
payment_context: PaymentContext::unknown(),
custom_data: vec![43, 43]
};
let mut secp_ctx = Secp256k1::new();
let blinded_path = BlindedPaymentPath::new(
Expand All @@ -1389,7 +1393,8 @@ fn custom_tlvs_to_blinded_path() {
);

let recipient_onion_fields = RecipientOnionFields::spontaneous_empty()
.with_custom_tlvs(vec![((1 << 16) + 1, vec![42, 42])])
.with_user_custom_data(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(),
PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
Expand All @@ -1402,11 +1407,13 @@ 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_user_custom_data(recipient_onion_fields.user_custom_data.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_user_custom_data(recipient_onion_fields.user_custom_data.clone())
.with_sender_custom_tlvs(recipient_onion_fields.sender_custom_tlvs.clone())
);
}

Expand Down
Loading
Loading