Skip to content

Commit

Permalink
Add Custom TLVs for payment::ReceiveTlvs
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
shaavan committed Jan 17, 2025
1 parent 2170c9c commit d37bb5f
Show file tree
Hide file tree
Showing 10 changed files with 44 additions and 12 deletions.
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
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
2 changes: 1 addition & 1 deletion lightning/src/blinded_path/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ impl Writeable for ReceiveTlvs {
// TODO: write padding
encode_tlv_stream!(writer, {
(65537, self.context, option),
(65539, self.custom_data, option)
(65541, self.custom_data, option)
});
Ok(())
}
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
8 changes: 8 additions & 0 deletions lightning/src/ln/blinded_payment_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pub fn blinded_payment_path(
intro_node_min_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_minimum_msat),
},
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
custom_data: Vec::new(),
};

let nonce = Nonce([42u8; 16]);
Expand Down Expand Up @@ -127,6 +128,7 @@ fn do_one_hop_blinded_path(success: bool) {
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
},
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
custom_data: Vec::new(),
};
let nonce = Nonce([42u8; 16]);
let expanded_key = chanmon_cfgs[1].keys_manager.get_inbound_payment_key();
Expand Down Expand Up @@ -175,6 +177,7 @@ fn mpp_to_one_hop_blinded_path() {
htlc_minimum_msat: chan_upd_1_3.htlc_minimum_msat,
},
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
custom_data: Vec::new(),
};
let nonce = Nonce([42u8; 16]);
let expanded_key = chanmon_cfgs[3].keys_manager.get_inbound_payment_key();
Expand Down Expand Up @@ -836,6 +839,8 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) {
let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000,
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2],
&chanmon_cfgs[2].keys_manager);

route_params.payment_params.max_path_length = 18;

let route = if check == ReceiveCheckFail::ProcessPendingHTLCsCheck {
let mut route = get_route(&nodes[0], &route_params).unwrap();
Expand Down Expand Up @@ -1240,6 +1245,7 @@ fn sender_custom_tlvs_to_blinded_path() {
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
},
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
custom_data: Vec::new(),
};
let nonce = Nonce([42u8; 16]);
let expanded_key = chanmon_cfgs[1].keys_manager.get_inbound_payment_key();
Expand Down Expand Up @@ -1294,6 +1300,7 @@ fn fails_receive_tlvs_authentication() {
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
},
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
custom_data: Vec::new(),
};
let nonce = Nonce([42u8; 16]);
let expanded_key = chanmon_cfgs[1].keys_manager.get_inbound_payment_key();
Expand Down Expand Up @@ -1325,6 +1332,7 @@ fn fails_receive_tlvs_authentication() {
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
},
payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}),
custom_data: Vec::new(),
};
let nonce = Nonce([43u8; 16]);
let mut payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key);
Expand Down
7 changes: 4 additions & 3 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10243,7 +10243,7 @@ where
Ok((payment_hash, payment_secret)) => {
let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {});
let payment_paths = self.create_blinded_payment_paths(
Some(amount_msats), payment_secret, payment_context, relative_expiry,
Some(amount_msats), payment_secret, payment_context, None, relative_expiry,
)
.map_err(|_| Bolt12SemanticError::MissingPaths)?;

Expand Down Expand Up @@ -10562,7 +10562,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: Option<u64>, payment_secret: PaymentSecret, payment_context: PaymentContext,
&self, amount_msats: Option<u64>, payment_secret: PaymentSecret, payment_context: PaymentContext, custom_data: Option<Vec<u8>>,
relative_expiry_seconds: u32
) -> Result<Vec<BlindedPaymentPath>, ()> {
let expanded_key = &self.inbound_payment_key;
Expand All @@ -10586,6 +10586,7 @@ where
htlc_minimum_msat: 1,
},
payment_context,
custom_data: custom_data.unwrap_or_default()
};
let nonce = Nonce::from_entropy_source(entropy);
let payee_tlvs = payee_tlvs.authenticate(nonce, expanded_key);
Expand Down Expand Up @@ -12123,7 +12124,7 @@ where
invoice_request: invoice_request.fields(),
});
let payment_paths = match self.create_blinded_payment_paths(
Some(amount_msats), payment_secret, payment_context, relative_expiry
Some(amount_msats), payment_secret, payment_context, None, relative_expiry
) {
Ok(payment_paths) => payment_paths,
Err(()) => {
Expand Down
1 change: 1 addition & 0 deletions lightning/src/ln/max_payment_path_len_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::Bolt12Refund(Bolt12RefundContext {}),
custom_data: Vec::new(),
};
let nonce = Nonce([42u8; 16]);
let expanded_key = chanmon_cfgs[2].keys_manager.get_inbound_payment_key();
Expand Down
12 changes: 7 additions & 5 deletions lightning/src/ln/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2750,7 +2750,7 @@ impl<'a> Writeable for OutboundOnionPayload<'a> {
// We need to update [`ln::outbound_payment::RecipientOnionFields::with_sender_custom_tlvs`]
// to reject any reserved types in the experimental range if new ones are ever
// standardized.
let user_custom_data = (!user_custom_data.is_empty()).then(|| (65539, user_custom_data.to_vec()));
let user_custom_data = (!user_custom_data.is_empty()).then(|| (65541, user_custom_data.to_vec()));
let keysend_tlv = keysend_preimage.map(|preimage| (5482373484, preimage.encode()));
let mut custom_tlvs: Vec<&(u64, Vec<u8>)> = sender_custom_tlvs.iter().chain(keysend_tlv.iter()).chain(user_custom_data.iter()).collect();
custom_tlvs.sort_unstable_by_key(|(typ, _)| *typ);
Expand All @@ -2774,7 +2774,7 @@ impl<'a> Writeable for OutboundOnionPayload<'a> {
// We need to update [`ln::outbound_payment::RecipientOnionFields::with_sender_custom_tlvs`]
// to reject any reserved types in the experimental range if new ones are ever
// standardized.
let user_custom_data = (!user_custom_data.is_empty()).then(|| (65539, user_custom_data.to_vec()));
let user_custom_data = (!user_custom_data.is_empty()).then(|| (65541, user_custom_data.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<u8>)> = sender_custom_tlvs.iter()
Expand Down Expand Up @@ -2832,7 +2832,7 @@ impl<'a> Writeable for OutboundTrampolinePayload<'a> {
});
},
Self::BlindedReceive { sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, encrypted_tlvs, intro_node_blinding_point, keysend_preimage, sender_custom_tlvs, user_custom_data} => {
let user_custom_data = (!user_custom_data.is_empty()).then(|| (65539, user_custom_data.to_vec()));
let user_custom_data = (!user_custom_data.is_empty()).then(|| (65541, user_custom_data.to_vec()));
let custom_tlvs: Vec<&(u64, Vec<u8>)> = sender_custom_tlvs.iter()
.chain(user_custom_data.iter())
.collect();
Expand Down Expand Up @@ -2889,7 +2889,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh

let (user_custom_data, sender_custom_tlvs): (Vec<(u64, Vec<u8>)>, Vec<(u64, Vec<u8>)>) = custom_tlvs
.into_iter()
.partition(|(tlv_type, _)| *tlv_type == 65539);
.partition(|(tlv_type, _)| *tlv_type == 65541);

let user_custom_data = user_custom_data.into_iter().next().map(|(_, data)| data).unwrap_or_else(Vec::new);

Expand Down Expand Up @@ -2926,6 +2926,7 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
next_blinding_override,
})
},
// Note: The custom data in the receive tlvs is not used here.
ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(receive_tlvs) } => {
let ReceiveTlvs { tlvs, authentication: (hmac, nonce) } = receive_tlvs;
let expanded_key = node_signer.get_inbound_payment_key();
Expand All @@ -2934,8 +2935,9 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
}

let UnauthenticatedReceiveTlvs {
payment_secret, payment_constraints, payment_context,
payment_secret, payment_constraints, payment_context, custom_data
} = tlvs;
debug_assert_eq!(custom_data, user_custom_data, "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)?,
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/payment_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3834,8 +3834,8 @@ fn test_sender_custom_tlvs_consistency() {
let even_type_1 = 1 << 16;
let odd_type_1 = (1 << 16)+ 1;
let even_type_2 = (1 << 16) + 2;
// (1<<16) + 3 is reserved for user_custom_data.
let odd_type_2 = (1 << 16) + 5;
let odd_type_2 = (1 << 16) + 3;
// (1<<16) + 5 is reserved for user_custom_data.
let value_1 = || vec![1, 2, 3, 4];
let differing_value_1 = || vec![1, 2, 3, 5];
let value_2 = || vec![42u8; 16];
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/onion_message/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ impl Readable for ControlTlvs {
(4, next_node_id, option),
(8, next_blinding_override, option),
(65537, context, option),
(65539, custom_data, option)
(65541, custom_data, option)
});
let _padding: Option<Padding> = _padding;

Expand Down

0 comments on commit d37bb5f

Please sign in to comment.