Skip to content
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
6 changes: 6 additions & 0 deletions lightning-dns-resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ mod test {
use lightning::onion_message::messenger::{
AOnionMessenger, Destination, MessageRouter, OnionMessagePath, OnionMessenger,
};
use lightning::routing::router::DEFAULT_PAYMENT_DUMMY_HOPS;
use lightning::sign::{KeysManager, NodeSigner, ReceiveAuthKey, Recipient};
use lightning::types::features::InitFeatures;
use lightning::types::payment::PaymentHash;
Expand Down Expand Up @@ -419,6 +420,11 @@ mod test {
let updates = get_htlc_update_msgs(&nodes[0], &payee_id);
nodes[1].node.handle_update_add_htlc(payer_id, &updates.update_add_htlcs[0]);
do_commitment_signed_dance(&nodes[1], &nodes[0], &updates.commitment_signed, false, false);

for _ in 0..DEFAULT_PAYMENT_DUMMY_HOPS {
nodes[1].node.process_pending_htlc_forwards();
}

expect_and_process_pending_htlcs(&nodes[1], false);

let claimable_events = nodes[1].node.get_and_clear_pending_events();
Expand Down
140 changes: 113 additions & 27 deletions lightning/src/blinded_path/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
{
Expand All @@ -145,6 +172,7 @@ impl BlindedPaymentPath {
secp_ctx,
intermediate_nodes,
payee_node_id,
dummy_hop_count,
payee_tlvs,
&blinding_secret,
local_node_receive_key,
Expand Down Expand Up @@ -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(()),
}
}
Expand All @@ -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(()),
}
}
Expand Down Expand Up @@ -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),
}
Expand All @@ -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),
}

Expand Down Expand Up @@ -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(())
Expand All @@ -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),
}
}
}
Expand Down Expand Up @@ -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] {
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.
Expand Down
9 changes: 8 additions & 1 deletion lightning/src/ln/async_payments_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ use crate::onion_message::messenger::{
use crate::onion_message::offers::OffersMessage;
use crate::onion_message::packet::ParsedOnionMessageContents;
use crate::prelude::*;
use crate::routing::router::{Payee, PaymentParameters};
use crate::routing::router::{Payee, PaymentParameters, DEFAULT_PAYMENT_DUMMY_HOPS};
use crate::sign::NodeSigner;
use crate::sync::Mutex;
use crate::types::features::Bolt12InvoiceFeatures;
Expand Down Expand Up @@ -1858,6 +1858,13 @@ fn expired_static_invoice_payment_path() {
blinded_path
.advance_path_by_one(&nodes[1].keys_manager, &nodes[1].node, &secp_ctx)
.unwrap();

for _ in 0..DEFAULT_PAYMENT_DUMMY_HOPS {
blinded_path
.advance_path_by_one(&nodes[2].keys_manager, &nodes[2].node, &secp_ctx)
.unwrap();
}

match blinded_path.decrypt_intro_payload(&nodes[2].keys_manager).unwrap().0 {
BlindedPaymentTlvs::Receive(tlvs) => tlvs.payment_constraints.max_cltv_expiry,
_ => panic!(),
Expand Down
62 changes: 58 additions & 4 deletions lightning/src/ln/blinded_payment_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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);
Expand Down Expand Up @@ -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
);
Expand Down Expand Up @@ -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
);
Expand Down Expand Up @@ -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
);
Expand Down Expand Up @@ -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(
Expand Down
Loading