Skip to content

Commit

Permalink
Allow blinded path diversification by expanding create_blinded_paths
Browse files Browse the repository at this point in the history
- The current usage of `blinded_paths` is limited because `create_blinded_path`
  only returns a single `BlindedPath`.
- This commit expands the functionality of `create_blinded_path` by allowing
  it to return multiple `BlindedPaths`, as determined by the new `count`
  parameter.
- Additionally, this commit integrates this new capability throughout the codebase by:
    - Allowing multiple paths in offers and refund builders.
    - Sending Offers Response messages, such as `InvoiceRequest` (in `pay_for_offer`)
      and `Invoice` (in `request_refund_payment`), using multiple reply paths.
- As a proof-of-concept, this commit increases the maximum count of
  `create_blinded_paths` to 10, enabling the generation of more reply
paths. It also increases the number of `blinded_paths` used in offer and
refund builders and responders to 5, demonstrating the usage of multiple
reply paths.
  • Loading branch information
shaavan committed Jun 3, 2024
1 parent c57b94a commit f32a93a
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 82 deletions.
2 changes: 1 addition & 1 deletion fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ impl MessageRouter for FuzzRouter {
}

fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
&self, _recipient: PublicKey, _peers: Vec<ForwardNode>, _secp_ctx: &Secp256k1<T>,
&self, _recipient: PublicKey, _peers: Vec<ForwardNode>, _secp_ctx: &Secp256k1<T>, count: usize,
) -> Result<Vec<BlindedPath>, ()> {
unreachable!()
}
Expand Down
2 changes: 1 addition & 1 deletion fuzz/src/full_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ impl MessageRouter for FuzzRouter {
}

fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
&self, _recipient: PublicKey, _peers: Vec<ForwardNode>, _secp_ctx: &Secp256k1<T>,
&self, _recipient: PublicKey, _peers: Vec<ForwardNode>, _secp_ctx: &Secp256k1<T>, count: usize,
) -> Result<Vec<BlindedPath>, ()> {
unreachable!()
}
Expand Down
2 changes: 1 addition & 1 deletion fuzz/src/onion_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ impl MessageRouter for TestMessageRouter {
}

fn create_blinded_paths<T: secp256k1::Signing + secp256k1::Verification>(
&self, _recipient: PublicKey, _peers: Vec<ForwardNode>, _secp_ctx: &Secp256k1<T>,
&self, _recipient: PublicKey, _peers: Vec<ForwardNode>, _secp_ctx: &Secp256k1<T>, count: usize
) -> Result<Vec<BlindedPath>, ()> {
unreachable!()
}
Expand Down
81 changes: 49 additions & 32 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8584,12 +8584,15 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
let entropy = &*$self.entropy_source;
let secp_ctx = &$self.secp_ctx;

let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
// TODO: Introduce a parameter to allow adding more paths to the created Offer.
let paths = $self.create_blinded_path(1)
.map_err(|_| Bolt12SemanticError::MissingPaths)?;

let builder = OfferBuilder::deriving_signing_pubkey(
node_id, expanded_key, entropy, secp_ctx
)
.chain_hash($self.chain_hash)
.path(path);
.path(paths);

Ok(builder.into())
}
Expand Down Expand Up @@ -8651,13 +8654,16 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
let entropy = &*$self.entropy_source;
let secp_ctx = &$self.secp_ctx;

let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
// TODO: Introduce a parameter to allow adding more paths to the created Refund.
let paths = $self.create_blinded_path(1)
.map_err(|_| Bolt12SemanticError::MissingPaths)?;

let builder = RefundBuilder::deriving_payer_id(
node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
)?
.chain_hash($self.chain_hash)
.absolute_expiry(absolute_expiry)
.path(path);
.path(paths);

let _persistence_guard = PersistenceNotifierGuard::notify_on_drop($self);

Expand Down Expand Up @@ -8774,7 +8780,8 @@ where
Some(payer_note) => builder.payer_note(payer_note),
};
let invoice_request = builder.build_and_sign()?;
let reply_path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
let reply_paths = self.create_blinded_path(5)
.map_err(|_| Bolt12SemanticError::MissingPaths)?;

let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);

Expand All @@ -8787,25 +8794,32 @@ where

let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
if !offer.paths().is_empty() {
// Send as many invoice requests as there are paths in the offer (with an upper bound).
// Using only one path could result in a failure if the path no longer exists. But only
// one invoice for a given payment id will be paid, even if more than one is received.
// Send as many invoice requests as there are paths in the offer using as many different
// reply_paths possible (with an upper bound).
// Using only one path could result in a failure if the path no longer exists.
// But only one invoice for a given payment ID will be paid, even if more than one is received.
const REQUEST_LIMIT: usize = 10;
for path in offer.paths().into_iter().take(REQUEST_LIMIT) {
reply_paths
.iter()
.flat_map(|reply_path| offer.paths().iter().map(|path| (path.clone(), reply_path.clone())))
.take(REQUEST_LIMIT)
.for_each(|(path, reply_path)| {
let message = new_pending_onion_message(
OffersMessage::InvoiceRequest(invoice_request.clone()),
Destination::BlindedPath(path.clone()),
Some(reply_path.clone()),
);
pending_offers_messages.push(message);
});
} else if let Some(signing_pubkey) = offer.signing_pubkey() {
for reply_path in reply_paths {
let message = new_pending_onion_message(
OffersMessage::InvoiceRequest(invoice_request.clone()),
Destination::BlindedPath(path.clone()),
Some(reply_path.clone()),
Destination::Node(signing_pubkey),
Some(reply_path),
);
pending_offers_messages.push(message);
}
} else if let Some(signing_pubkey) = offer.signing_pubkey() {
let message = new_pending_onion_message(
OffersMessage::InvoiceRequest(invoice_request),
Destination::Node(signing_pubkey),
Some(reply_path),
);
pending_offers_messages.push(message);
} else {
debug_assert!(false);
return Err(Bolt12SemanticError::MissingSigningPubkey);
Expand Down Expand Up @@ -8874,26 +8888,30 @@ where
)?;
let builder: InvoiceBuilder<DerivedSigningPubkey> = builder.into();
let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?;
let reply_path = self.create_blinded_path()
let reply_paths = self.create_blinded_path(5)
.map_err(|_| Bolt12SemanticError::MissingPaths)?;

let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
if refund.paths().is_empty() {
let message = new_pending_onion_message(
OffersMessage::Invoice(invoice.clone()),
Destination::Node(refund.payer_id()),
Some(reply_path),
);
pending_offers_messages.push(message);
} else {
for path in refund.paths() {
for reply_path in reply_paths {
let message = new_pending_onion_message(
OffersMessage::Invoice(invoice.clone()),
Destination::BlindedPath(path.clone()),
Some(reply_path.clone()),
Destination::Node(refund.payer_id()),
Some(reply_path),
);
pending_offers_messages.push(message);
}
} else {
for path in refund.paths() {
for reply_path in reply_paths.clone() {
let message = new_pending_onion_message(
OffersMessage::Invoice(invoice.clone()),
Destination::BlindedPath(path.clone()),
Some(reply_path.clone()),
);
pending_offers_messages.push(message);
}
}
}

Ok(invoice)
Expand Down Expand Up @@ -9003,7 +9021,7 @@ where
/// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`].
///
/// Errors if the `MessageRouter` errors or returns an empty `Vec`.
fn create_blinded_path(&self) -> Result<BlindedPath, ()> {
fn create_blinded_path(&self, count: usize) -> Result<Vec<BlindedPath>, ()> {
let recipient = self.get_our_node_id();
let secp_ctx = &self.secp_ctx;

Expand All @@ -9022,8 +9040,7 @@ where
.collect::<Vec<_>>();

self.router
.create_blinded_paths(recipient, peers, secp_ctx)
.and_then(|paths| paths.into_iter().next().ok_or(()))
.create_blinded_paths(recipient, peers, secp_ctx, count)
}

/// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to
Expand Down
1 change: 1 addition & 0 deletions lightning/src/ln/offers_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ fn prefers_more_connected_nodes_in_blinded_paths() {
.amount_msats(10_000_000)
.build().unwrap();
assert_ne!(offer.signing_pubkey(), Some(bob_id));

assert!(!offer.paths().is_empty());
for path in offer.paths() {
let introduction_node_id = resolve_introduction_node(david, &path);
Expand Down
10 changes: 4 additions & 6 deletions lightning/src/offers/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1833,14 +1833,14 @@ mod tests {
let entropy = FixedEntropy {};
let secp_ctx = Secp256k1::new();

let blinded_path = BlindedPath {
let blinded_path = vec![BlindedPath {
introduction_node: IntroductionNode::NodeId(pubkey(40)),
blinding_point: pubkey(41),
blinded_hops: vec![
BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![0; 44] },
],
};
}];

#[cfg(c_bindings)]
use crate::offers::offer::OfferWithDerivedMetadataBuilder as OfferBuilder;
Expand Down Expand Up @@ -2409,8 +2409,7 @@ mod tests {
let invoice = OfferBuilder::new(recipient_pubkey())
.clear_signing_pubkey()
.amount_msats(1000)
.path(paths[0].clone())
.path(paths[1].clone())
.path(paths.clone())
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
Expand All @@ -2431,8 +2430,7 @@ mod tests {
let invoice = OfferBuilder::new(recipient_pubkey())
.clear_signing_pubkey()
.amount_msats(1000)
.path(paths[0].clone())
.path(paths[1].clone())
.path(paths.clone())
.build().unwrap()
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
Expand Down
31 changes: 15 additions & 16 deletions lightning/src/offers/offer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
//! # #[cfg(feature = "std")]
//! # use std::time::SystemTime;
//! #
//! # fn create_blinded_path() -> BlindedPath { unimplemented!() }
//! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
//! # fn create_blinded_path(count: usize) -> Vec<BlindedPath> { unimplemented!() }
//! # fn create_another_blinded_path(count: usize) -> Vec<BlindedPath> { unimplemented!() }
//! #
//! # #[cfg(feature = "std")]
//! # fn build() -> Result<(), Bolt12ParseError> {
Expand All @@ -49,8 +49,8 @@
//! .supported_quantity(Quantity::Unbounded)
//! .absolute_expiry(expiration.duration_since(SystemTime::UNIX_EPOCH).unwrap())
//! .issuer("Foo Bar".to_string())
//! .path(create_blinded_path())
//! .path(create_another_blinded_path())
//! .path(create_blinded_path(1))
//! .path(create_another_blinded_path(1))
//! .build()?;
//!
//! // Encode as a bech32 string for use in a QR code.
Expand Down Expand Up @@ -344,8 +344,9 @@ macro_rules! offer_builder_methods { (
///
/// Successive calls to this method will add another blinded path. Caller is responsible for not
/// adding duplicate paths.
pub fn path($($self_mut)* $self: $self_type, path: BlindedPath) -> $return_type {
$self.offer.paths.get_or_insert_with(Vec::new).push(path);
pub fn path($($self_mut)* $self: $self_type, paths: Vec<BlindedPath>) -> $return_type {
let entry = $self.offer.paths.get_or_insert_with(Vec::new);
entry.extend(paths);
$return_value
}

Expand Down Expand Up @@ -1316,14 +1317,14 @@ mod tests {
let entropy = FixedEntropy {};
let secp_ctx = Secp256k1::new();

let blinded_path = BlindedPath {
let blinded_path = vec![BlindedPath {
introduction_node: IntroductionNode::NodeId(pubkey(40)),
blinding_point: pubkey(41),
blinded_hops: vec![
BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![0; 44] },
],
};
}];

#[cfg(c_bindings)]
use super::OfferWithDerivedMetadataBuilder as OfferBuilder;
Expand Down Expand Up @@ -1509,8 +1510,7 @@ mod tests {
];

let offer = OfferBuilder::new(pubkey(42))
.path(paths[0].clone())
.path(paths[1].clone())
.path(paths.clone())
.build()
.unwrap();
let tlv_stream = offer.as_tlv_stream();
Expand Down Expand Up @@ -1693,37 +1693,36 @@ mod tests {
#[test]
fn parses_offer_with_paths() {
let offer = OfferBuilder::new(pubkey(42))
.path(BlindedPath {
.path(vec![BlindedPath {
introduction_node: IntroductionNode::NodeId(pubkey(40)),
blinding_point: pubkey(41),
blinded_hops: vec![
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
],
})
.path(BlindedPath {
}, BlindedPath {
introduction_node: IntroductionNode::NodeId(pubkey(40)),
blinding_point: pubkey(41),
blinded_hops: vec![
BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
],
})
}])
.build()
.unwrap();
if let Err(e) = offer.to_string().parse::<Offer>() {
panic!("error parsing offer: {:?}", e);
}

let offer = OfferBuilder::new(pubkey(42))
.path(BlindedPath {
.path(vec![BlindedPath {
introduction_node: IntroductionNode::NodeId(pubkey(40)),
blinding_point: pubkey(41),
blinded_hops: vec![
BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
],
})
}])
.clear_signing_pubkey()
.build()
.unwrap();
Expand Down
Loading

0 comments on commit f32a93a

Please sign in to comment.