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 Data support for ReceiveTlvs #2830

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
6 changes: 3 additions & 3 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 @@ -147,8 +147,8 @@ impl MessageRouter for FuzzRouter {
}

fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
&self, _recipient: PublicKey, _context: MessageContext, _peers: Vec<PublicKey>,
_secp_ctx: &Secp256k1<T>,
&self, _recipient: PublicKey, _recipient_tlvs: message::ReceiveTlvs,
_peers: Vec<PublicKey>, _secp_ctx: &Secp256k1<T>,
) -> Result<Vec<BlindedMessagePath>, ()> {
unreachable!()
}
Expand Down
6 changes: 3 additions & 3 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 @@ -174,8 +174,8 @@ impl MessageRouter for FuzzRouter {
}

fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
&self, _recipient: PublicKey, _context: MessageContext, _peers: Vec<PublicKey>,
_secp_ctx: &Secp256k1<T>,
&self, _recipient: PublicKey, _recipient_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 @@ -103,6 +103,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
htlc_minimum_msat: 1,
},
payment_context,
custom_data: Vec::new(),
};
let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key);
let intermediate_nodes = [PaymentForwardNode {
Expand Down
14 changes: 9 additions & 5 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::inbound_payment::ExpandedKey;
Expand Down Expand Up @@ -100,7 +100,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, _recipient_tlvs: ReceiveTlvs, _peers: Vec<PublicKey>,
_secp_ctx: &Secp256k1<T>,
) -> Result<Vec<BlindedMessagePath>, ()> {
unreachable!()
Expand All @@ -112,7 +112,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 All @@ -130,7 +130,11 @@ impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler {
};
Some((ReleaseHeldHtlc {}, responder.respond()))
}
fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {}
fn handle_release_held_htlc(
&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext,
_custom_data: Option<Vec<u8>>,
) {
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -160,7 +164,7 @@ impl CustomOnionMessageHandler for TestCustomMessageHandler {
type CustomMessage = TestCustomMessage;
fn handle_custom_message(
&self, message: Self::CustomMessage, _context: Option<Vec<u8>>,
responder: Option<Responder>,
_custom_data: Option<Vec<u8>>, responder: Option<Responder>,
) -> Option<(Self::CustomMessage, ResponseInstruction)> {
match responder {
Some(responder) => Some((message, responder.respond())),
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 @@ -80,6 +80,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
htlc_minimum_msat: 1,
},
payment_context,
custom_data: Vec::new(),
};
let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key);
let intermediate_nodes = [PaymentForwardNode {
Expand Down
30 changes: 20 additions & 10 deletions lightning-dns-resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,11 @@ impl<PH: Deref> DNSResolverMessageHandler for OMDomainResolver<PH>
where
PH::Target: DNSResolverMessageHandler,
{
fn handle_dnssec_proof(&self, proof: DNSSECProof, context: DNSResolverContext) {
fn handle_dnssec_proof(
&self, proof: DNSSECProof, context: DNSResolverContext, custom_data: Option<Vec<u8>>,
) {
if let Some(proof_handler) = &self.proof_handler {
proof_handler.handle_dnssec_proof(proof, context);
proof_handler.handle_dnssec_proof(proof, context, custom_data);
}
}

Expand Down Expand Up @@ -159,7 +161,7 @@ mod test {
use bitcoin::secp256k1::{self, PublicKey, Secp256k1};
use bitcoin::Block;

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

fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
&self, recipient: PublicKey, context: MessageContext, _peers: Vec<PublicKey>,
secp_ctx: &Secp256k1<T>,
&self, recipient: PublicKey, recipient_tlvs: message::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, recipient_tlvs, &keys, secp_ctx).unwrap()
])
}
}
impl Deref for DirectlyConnectedRouter {
Expand All @@ -251,8 +255,11 @@ mod test {
panic!();
}

fn handle_dnssec_proof(&self, msg: DNSSECProof, context: DNSResolverContext) {
let mut proof = self.resolver.handle_dnssec_proof_for_uri(msg, context).unwrap();
fn handle_dnssec_proof(
&self, msg: DNSSECProof, context: DNSResolverContext, custom_data: Option<Vec<u8>>,
) {
let mut proof =
self.resolver.handle_dnssec_proof_for_uri(msg, context, custom_data).unwrap();
assert_eq!(proof.0.len(), 1);
let payment = proof.0.pop().unwrap();
let mut result = Some((payment.0, payment.1, proof.1));
Expand Down Expand Up @@ -330,9 +337,12 @@ mod test {

let (msg, context) =
payer.resolver.resolve_name(payment_id, name.clone(), &*payer_keys).unwrap();
let query_context = MessageContext::DNSResolver(context);
let recipient_tlvs = message::ReceiveTlvs {
context: Some(MessageContext::DNSResolver(context)),
custom_data: None,
};
let reply_path =
BlindedMessagePath::one_hop(payer_id, query_context, &*payer_keys, &secp_ctx).unwrap();
BlindedMessagePath::one_hop(payer_id, recipient_tlvs, &*payer_keys, &secp_ctx).unwrap();
payer.pending_messages.lock().unwrap().push((
DNSResolverMessage::DNSSECQuery(msg),
MessageSendInstructions::WithSpecifiedReplyPath {
Expand Down
35 changes: 26 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, recipient_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, recipient_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>,
recipient_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,
recipient_tlvs, &blinding_secret,
).map_err(|_| ())?,
}))
}
Expand Down Expand Up @@ -236,12 +236,28 @@ 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 {
/// TLVs to encode in the final onion message packet's hop data. These TLVs are specific to the
/// recipient node and provide information necessary for final processing of the message.
/// When provided in a blinded route, they are encoded into [`BlindedHop::encrypted_payload`].
#[derive(Clone)]
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 provided to the message
/// recipient when the blinded path is used.
///
/// This field allows encoding custom data intended to be provided 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 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 +281,7 @@ impl Writeable for ReceiveTlvs {
// TODO: write padding
encode_tlv_stream!(writer, {
(65537, self.context, option),
(65541, self.custom_data, option)
});
Ok(())
}
Expand Down Expand Up @@ -478,7 +495,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, recipient_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 @@ -490,7 +507,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(recipient_tlvs)));

let path = pks.zip(tlvs);

Expand Down
18 changes: 18 additions & 0 deletions lightning/src/blinded_path/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,15 @@ pub struct UnauthenticatedReceiveTlvs {
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>,
}

impl UnauthenticatedReceiveTlvs {
Expand Down Expand Up @@ -444,6 +453,7 @@ impl Writeable for ReceiveTlvs {
(65536, self.tlvs.payment_secret, required),
(65537, self.tlvs.payment_context, required),
(65539, self.authentication, required),
(65541, self.tlvs.custom_data, required)
});
Ok(())
}
Expand All @@ -455,6 +465,7 @@ impl Writeable for UnauthenticatedReceiveTlvs {
(12, self.payment_constraints, required),
(65536, self.payment_secret, required),
(65537, self.payment_context, required),
(65541, self.custom_data, (default_value, Vec::new())),
});
Ok(())
}
Expand Down Expand Up @@ -483,6 +494,7 @@ impl Readable for BlindedPaymentTlvs {
(65536, payment_secret, option),
(65537, payment_context, option),
(65539, authentication, option),
(65541, custom_data, (default_value, Vec::new()))
});
let _padding: Option<utils::Padding> = _padding;

Expand All @@ -504,6 +516,7 @@ impl Readable for BlindedPaymentTlvs {
payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?,
payment_constraints: payment_constraints.0.unwrap(),
payment_context: payment_context.ok_or(DecodeError::InvalidValue)?,
custom_data: custom_data.0.unwrap(),
},
authentication: authentication.ok_or(DecodeError::InvalidValue)?,
}))
Expand Down Expand Up @@ -730,6 +743,7 @@ mod tests {
htlc_minimum_msat: 1,
},
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
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 @@ -749,6 +763,7 @@ mod tests {
htlc_minimum_msat: 1,
},
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
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 @@ -805,6 +820,7 @@ mod tests {
htlc_minimum_msat: 3,
},
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
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 @@ -858,6 +874,7 @@ mod tests {
htlc_minimum_msat: 1,
},
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
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 @@ -915,6 +932,7 @@ mod tests {
htlc_minimum_msat: 1,
},
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
custom_data: Vec::new(),
};

let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000, TEST_FINAL_CLTV as u16).unwrap();
Expand Down
12 changes: 6 additions & 6 deletions lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -703,15 +703,15 @@ pub enum Event {
/// [`ChannelManager::fail_htlc_backwards`] or [`ChannelManager::fail_htlc_backwards_with_reason`]
/// to free up resources for this HTLC and avoid network congestion.
///
/// If [`Event::PaymentClaimable::onion_fields`] is `Some`, and includes custom TLVs with even type
/// If [`Event::PaymentClaimable::onion_fields`] is `Some`, and includes sender custom TLVs with even type
/// numbers, you should use [`ChannelManager::fail_htlc_backwards_with_reason`] with
/// [`FailureCode::InvalidOnionPayload`] if you fail to understand and handle the contents, or
/// [`ChannelManager::claim_funds_with_known_custom_tlvs`] upon successful handling.
/// If you don't intend to check for custom TLVs, you can simply use
/// [`ChannelManager::claim_funds`], which will automatically fail back even custom TLVs.
/// [`ChannelManager::claim_funds_with_known_sender_custom_tlvs`] upon successful handling.
/// If you don't intend to check for sender custom TLVs, you can simply use
/// [`ChannelManager::claim_funds`], which will automatically fail back even sender custom TLVs.
///
/// If you fail to call [`ChannelManager::claim_funds`],
/// [`ChannelManager::claim_funds_with_known_custom_tlvs`],
/// [`ChannelManager::claim_funds_with_known_sender_custom_tlvs`],
/// [`ChannelManager::fail_htlc_backwards`], or
/// [`ChannelManager::fail_htlc_backwards_with_reason`] within the HTLC's timeout, the HTLC will
/// be automatically failed.
Expand All @@ -730,7 +730,7 @@ pub enum Event {
/// returning `Err(ReplayEvent ())`) and will be persisted across restarts.
///
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
/// [`ChannelManager::claim_funds_with_known_custom_tlvs`]: crate::ln::channelmanager::ChannelManager::claim_funds_with_known_custom_tlvs
/// [`ChannelManager::claim_funds_with_known_sender_custom_tlvs`]: crate::ln::channelmanager::ChannelManager::claim_funds_with_known_sender_custom_tlvs
/// [`FailureCode::InvalidOnionPayload`]: crate::ln::channelmanager::FailureCode::InvalidOnionPayload
/// [`ChannelManager::fail_htlc_backwards`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards
/// [`ChannelManager::fail_htlc_backwards_with_reason`]: crate::ln::channelmanager::ChannelManager::fail_htlc_backwards_with_reason
Expand Down
Loading
Loading