-
Notifications
You must be signed in to change notification settings - Fork 421
Introduce Dummy Hop support for Blinded Payment Path #4152
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
Open
shaavan
wants to merge
7
commits into
lightningdevkit:main
Choose a base branch
from
shaavan:pay-dummy
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+469
−77
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
8cecad3
Introduce Dummy BlindedPaymentTlv
shaavan 63f9f57
Introduce Dummy Hop support in Blinded Path Constructor
shaavan a24efed
Refactor: Introduce ForwardInfo
shaavan 96368a0
Introduce Payment Dummy Hop parsing mechanism
shaavan 3d50170
Update PaymentPath, and ClaimAlongRoute arguments
shaavan d8d01ff
Introduce payment dummy hops in DefaultRouter
shaavan 923982c
Introduce Blinded Payment Dummy Path test
shaavan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| use bitcoin::secp256k1::ecdh::SharedSecret; | ||
| use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; | ||
|
|
||
| use crate::blinded_path::message::MAX_DUMMY_HOPS_COUNT; | ||
| use crate::blinded_path::utils::{self, BlindedPathWithPadding}; | ||
| use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NodeIdLookUp}; | ||
| use crate::crypto::streams::ChaChaDualPolyReadAdapter; | ||
|
|
@@ -121,6 +122,32 @@ impl BlindedPaymentPath { | |
| local_node_receive_key: ReceiveAuthKey, payee_tlvs: ReceiveTlvs, htlc_maximum_msat: u64, | ||
| min_final_cltv_expiry_delta: u16, entropy_source: ES, secp_ctx: &Secp256k1<T>, | ||
| ) -> Result<Self, ()> | ||
| where | ||
| ES::Target: EntropySource, | ||
| { | ||
| BlindedPaymentPath::new_with_dummy_hops( | ||
| intermediate_nodes, | ||
| payee_node_id, | ||
| 0, | ||
| local_node_receive_key, | ||
| payee_tlvs, | ||
| htlc_maximum_msat, | ||
| min_final_cltv_expiry_delta, | ||
| entropy_source, | ||
| secp_ctx, | ||
| ) | ||
| } | ||
|
|
||
| /// Same as [`BlindedPaymentPath::new`], but allows specifying a number of dummy hops. | ||
| /// | ||
| /// Note: | ||
| /// At most [`MAX_DUMMY_HOPS_COUNT`] dummy hops can be added to the blinded path. | ||
| pub fn new_with_dummy_hops<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>( | ||
| intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey, | ||
| dummy_hop_count: usize, local_node_receive_key: ReceiveAuthKey, payee_tlvs: ReceiveTlvs, | ||
| htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16, entropy_source: ES, | ||
| secp_ctx: &Secp256k1<T>, | ||
| ) -> Result<Self, ()> | ||
| where | ||
| ES::Target: EntropySource, | ||
| { | ||
|
|
@@ -145,6 +172,7 @@ impl BlindedPaymentPath { | |
| secp_ctx, | ||
| intermediate_nodes, | ||
| payee_node_id, | ||
| dummy_hop_count, | ||
| payee_tlvs, | ||
| &blinding_secret, | ||
| local_node_receive_key, | ||
|
|
@@ -211,6 +239,19 @@ impl BlindedPaymentPath { | |
| self.inner_path.blinded_hops.remove(0); | ||
| Ok(()) | ||
| }, | ||
| Ok((BlindedPaymentTlvs::Dummy, control_tlvs_ss)) => { | ||
| let next_node_id = node_signer.get_node_id(Recipient::Node)?; | ||
| let mut new_blinding_point = onion_utils::next_hop_pubkey( | ||
| secp_ctx, | ||
| self.inner_path.blinding_point, | ||
| control_tlvs_ss.as_ref(), | ||
| ) | ||
| .map_err(|_| ())?; | ||
| mem::swap(&mut self.inner_path.blinding_point, &mut new_blinding_point); | ||
| self.inner_path.introduction_node = IntroductionNode::NodeId(next_node_id); | ||
| self.inner_path.blinded_hops.remove(0); | ||
| Ok(()) | ||
| }, | ||
| _ => Err(()), | ||
| } | ||
| } | ||
|
|
@@ -234,9 +275,9 @@ impl BlindedPaymentPath { | |
| .map_err(|_| ())?; | ||
|
|
||
| match (&readable, used_aad) { | ||
| (BlindedPaymentTlvs::Forward(_), false) | (BlindedPaymentTlvs::Receive(_), true) => { | ||
| Ok((readable, control_tlvs_ss)) | ||
| }, | ||
| (BlindedPaymentTlvs::Forward(_), false) | ||
| | (BlindedPaymentTlvs::Dummy, true) | ||
| | (BlindedPaymentTlvs::Receive(_), true) => Ok((readable, control_tlvs_ss)), | ||
| _ => Err(()), | ||
| } | ||
| } | ||
|
|
@@ -346,6 +387,8 @@ pub struct ReceiveTlvs { | |
| pub(crate) enum BlindedPaymentTlvs { | ||
| /// This blinded payment data is for a forwarding node. | ||
| Forward(ForwardTlvs), | ||
| /// This blinded payment data is dummy and is to be peeled by receiving node. | ||
| Dummy, | ||
| /// This blinded payment data is for the receiving node. | ||
| Receive(ReceiveTlvs), | ||
| } | ||
|
|
@@ -363,6 +406,7 @@ pub(crate) enum BlindedTrampolineTlvs { | |
| // Used to include forward and receive TLVs in the same iterator for encoding. | ||
| enum BlindedPaymentTlvsRef<'a> { | ||
| Forward(&'a ForwardTlvs), | ||
| Dummy, | ||
| Receive(&'a ReceiveTlvs), | ||
| } | ||
|
|
||
|
|
@@ -532,6 +576,11 @@ impl<'a> Writeable for BlindedPaymentTlvsRef<'a> { | |
| fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> { | ||
| match self { | ||
| Self::Forward(tlvs) => tlvs.write(w)?, | ||
| Self::Dummy => { | ||
| encode_tlv_stream!(w, { | ||
| (65539, (), required), | ||
| }) | ||
| }, | ||
| Self::Receive(tlvs) => tlvs.write(w)?, | ||
| } | ||
| Ok(()) | ||
|
|
@@ -548,32 +597,48 @@ impl Readable for BlindedPaymentTlvs { | |
| (2, scid, option), | ||
| (8, next_blinding_override, option), | ||
| (10, payment_relay, option), | ||
| (12, payment_constraints, required), | ||
| (12, payment_constraints, option), | ||
| (14, features, (option, encoding: (BlindedHopFeatures, WithoutLength))), | ||
| (65536, payment_secret, option), | ||
| (65537, payment_context, option), | ||
| (65539, is_dummy, option) | ||
| }); | ||
|
|
||
| if let Some(short_channel_id) = scid { | ||
| if payment_secret.is_some() { | ||
| return Err(DecodeError::InvalidValue); | ||
| } | ||
| Ok(BlindedPaymentTlvs::Forward(ForwardTlvs { | ||
| match ( | ||
| scid, | ||
| next_blinding_override, | ||
| payment_relay, | ||
| payment_constraints, | ||
| features, | ||
| payment_secret, | ||
| payment_context, | ||
| is_dummy, | ||
| ) { | ||
| ( | ||
| Some(short_channel_id), | ||
| next_override, | ||
| Some(relay), | ||
| Some(constraints), | ||
| features, | ||
| None, | ||
| None, | ||
| None, | ||
| ) => Ok(BlindedPaymentTlvs::Forward(ForwardTlvs { | ||
| short_channel_id, | ||
| payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?, | ||
| payment_constraints: payment_constraints.0.unwrap(), | ||
| next_blinding_override, | ||
| payment_relay: relay, | ||
| payment_constraints: constraints, | ||
| next_blinding_override: next_override, | ||
| features: features.unwrap_or_else(BlindedHopFeatures::empty), | ||
| })) | ||
| } else { | ||
| if payment_relay.is_some() || features.is_some() { | ||
| return Err(DecodeError::InvalidValue); | ||
| } | ||
| Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs { | ||
| payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?, | ||
| payment_constraints: payment_constraints.0.unwrap(), | ||
| payment_context: payment_context.ok_or(DecodeError::InvalidValue)?, | ||
| })) | ||
| })), | ||
| (None, None, None, Some(constraints), None, Some(secret), Some(context), None) => { | ||
| Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs { | ||
| payment_secret: secret, | ||
| payment_constraints: constraints, | ||
| payment_context: context, | ||
| })) | ||
| }, | ||
| (None, None, None, None, None, None, None, Some(())) => Ok(BlindedPaymentTlvs::Dummy), | ||
| _ => return Err(DecodeError::InvalidValue), | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -620,22 +685,43 @@ pub(crate) const PAYMENT_PADDING_ROUND_OFF: usize = 30; | |
| /// Construct blinded payment hops for the given `intermediate_nodes` and payee info. | ||
| pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>( | ||
| secp_ctx: &Secp256k1<T>, intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey, | ||
| payee_tlvs: ReceiveTlvs, session_priv: &SecretKey, local_node_receive_key: ReceiveAuthKey, | ||
| dummy_hop_count: usize, payee_tlvs: ReceiveTlvs, session_priv: &SecretKey, | ||
| local_node_receive_key: ReceiveAuthKey, | ||
| ) -> Vec<BlindedHop> { | ||
| let dummy_count = core::cmp::min(dummy_hop_count, MAX_DUMMY_HOPS_COUNT); | ||
| let pks = intermediate_nodes | ||
| .iter() | ||
| .map(|node| (node.node_id, None)) | ||
| .chain(core::iter::repeat((payee_node_id, Some(local_node_receive_key))).take(dummy_count)) | ||
| .chain(core::iter::once((payee_node_id, Some(local_node_receive_key)))); | ||
| let tlvs = intermediate_nodes | ||
| .iter() | ||
| .map(|node| BlindedPaymentTlvsRef::Forward(&node.tlvs)) | ||
| .chain((0..dummy_count).map(|_| BlindedPaymentTlvsRef::Dummy)) | ||
| .chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs))); | ||
|
|
||
| let path = pks.zip( | ||
| tlvs.map(|tlv| BlindedPathWithPadding { tlvs: tlv, round_off: PAYMENT_PADDING_ROUND_OFF }), | ||
| ); | ||
| let path: Vec<_> = pks | ||
| .zip( | ||
| tlvs.map(|tlv| BlindedPathWithPadding { | ||
| tlvs: tlv, | ||
| round_off: PAYMENT_PADDING_ROUND_OFF, | ||
| }), | ||
| ) | ||
| .collect(); | ||
|
|
||
| // Debug invariant: all non-final hops must have identical serialized size. | ||
| #[cfg(debug_assertions)] | ||
| if let Some((_, first)) = path.first() { | ||
| let expected = first.serialized_length(); | ||
| for (_, hop) in &path[..path.len() - 1] { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: can skip the first element |
||
| debug_assert!( | ||
| hop.serialized_length() == expected, | ||
| "All intermediate blinded hops must have identical serialized size" | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| utils::construct_blinded_hops(secp_ctx, path, session_priv) | ||
| utils::construct_blinded_hops(secp_ctx, path.into_iter(), session_priv) | ||
| } | ||
|
|
||
| /// `None` if underflow occurs. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -190,6 +190,56 @@ fn do_one_hop_blinded_path(success: bool) { | |
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn one_hop_blinded_path_with_dummy_hops() { | ||
| let chanmon_cfgs = create_chanmon_cfgs(2); | ||
| let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); | ||
| let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); | ||
| let nodes = create_network(2, &node_cfgs, &node_chanmgrs); | ||
| let chan_upd = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0).0.contents; | ||
|
|
||
| let amt_msat = 5000; | ||
| let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None); | ||
| let payee_tlvs = ReceiveTlvs { | ||
| payment_secret, | ||
| payment_constraints: PaymentConstraints { | ||
| max_cltv_expiry: u32::max_value(), | ||
| htlc_minimum_msat: chan_upd.htlc_minimum_msat, | ||
| }, | ||
| payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), | ||
| }; | ||
| let receive_auth_key = chanmon_cfgs[1].keys_manager.get_receive_auth_key(); | ||
| let dummy_hops = 1; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's do at least two. |
||
|
|
||
| let mut secp_ctx = Secp256k1::new(); | ||
| let blinded_path = BlindedPaymentPath::new_with_dummy_hops( | ||
| &[], nodes[1].node.get_our_node_id(), dummy_hops, receive_auth_key, | ||
| payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, | ||
| &chanmon_cfgs[1].keys_manager, &secp_ctx | ||
| ).unwrap(); | ||
|
|
||
| let route_params = RouteParameters::from_payment_params_and_value( | ||
| PaymentParameters::blinded(vec![blinded_path]), | ||
| amt_msat, | ||
| ); | ||
| nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), | ||
| PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); | ||
| check_added_monitors(&nodes[0], 1); | ||
|
|
||
| let mut events = nodes[0].node.get_and_clear_pending_msg_events(); | ||
| assert_eq!(events.len(), 1); | ||
| let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); | ||
|
|
||
| let path = &[&nodes[1]]; | ||
| let args = | ||
| PassAlongPathArgs::new(&nodes[0], path, amt_msat, payment_hash, ev) | ||
| .with_dummy_override(dummy_hops) | ||
| .with_payment_secret(payment_secret); | ||
|
|
||
| do_pass_along_path(args); | ||
| claim_payment(&nodes[0], &[&nodes[1]], payment_preimage); | ||
| } | ||
|
|
||
| #[test] | ||
| fn mpp_to_one_hop_blinded_path() { | ||
| let chanmon_cfgs = create_chanmon_cfgs(4); | ||
|
|
@@ -1663,8 +1713,9 @@ fn route_blinding_spec_test_vector() { | |
| hop_data: carol_packet_bytes, | ||
| hmac: carol_hmac, | ||
| }; | ||
| let carol_forward_info = carol_packet_details.forward_info.unwrap(); | ||
| let carol_update_add = update_add_msg( | ||
| carol_packet_details.outgoing_amt_msat, carol_packet_details.outgoing_cltv_value, | ||
| carol_forward_info.outgoing_amt_msat, carol_forward_info.outgoing_cltv_value, | ||
| Some(pubkey_from_hex("034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0")), | ||
| carol_onion | ||
| ); | ||
|
|
@@ -1697,8 +1748,9 @@ fn route_blinding_spec_test_vector() { | |
| hop_data: dave_packet_bytes, | ||
| hmac: dave_hmac, | ||
| }; | ||
| let dave_forward_info = dave_packet_details.forward_info.unwrap(); | ||
| let dave_update_add = update_add_msg( | ||
| dave_packet_details.outgoing_amt_msat, dave_packet_details.outgoing_cltv_value, | ||
| dave_forward_info.outgoing_amt_msat, dave_forward_info.outgoing_cltv_value, | ||
| Some(pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")), | ||
| dave_onion | ||
| ); | ||
|
|
@@ -1731,8 +1783,9 @@ fn route_blinding_spec_test_vector() { | |
| hop_data: eve_packet_bytes, | ||
| hmac: eve_hmac, | ||
| }; | ||
| let eve_forward_info = eve_packet_details.forward_info.unwrap(); | ||
| let eve_update_add = update_add_msg( | ||
| eve_packet_details.outgoing_amt_msat, eve_packet_details.outgoing_cltv_value, | ||
| eve_forward_info.outgoing_amt_msat, eve_forward_info.outgoing_cltv_value, | ||
| Some(pubkey_from_hex("03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")), | ||
| eve_onion | ||
| ); | ||
|
|
@@ -1963,7 +2016,8 @@ fn test_trampoline_inbound_payment_decoding() { | |
| hop_data: carol_packet_bytes, | ||
| hmac: carol_hmac, | ||
| }; | ||
| let carol_update_add = update_add_msg(carol_packet_details.outgoing_amt_msat, carol_packet_details.outgoing_cltv_value, None, carol_onion); | ||
| let carol_forward_info = carol_packet_details.forward_info.unwrap(); | ||
| let carol_update_add = update_add_msg(carol_forward_info.outgoing_amt_msat, carol_forward_info.outgoing_cltv_value, None, carol_onion); | ||
|
|
||
| let carol_node_signer = TestEcdhSigner { node_secret: carol_secret }; | ||
| let (carol_peeled_onion, _) = onion_payment::decode_incoming_update_add_htlc_onion( | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can DRY this up with the previous arm by having an inner
matchon theBlindedPaymentTlvsevaluating to thenext_node_id.