Skip to content

Commit 99d0093

Browse files
committed
Support paying Human Readable Names directly from ChannelManager
Now that we have the ability to resolve BIP 353 Human Readable Names directly and have tracking for outbound payments waiting on an offer resolution, we can implement full BIP 353 support in `ChannelManager`. Users will need one or more known nodes which offer DNS resolution service over onion messages using bLIP 32, which they pass to `ChannelManager::pay_for_offer_from_human_readable_name`, as well as the `HumanReadableName` itself. From there, `ChannelManager` asks the DNS resolver to provide a DNSSEC proof, which it verifies, parses into an `Offer`, and then pays. For those who wish to support on-chain fallbacks, sadly, this will not work, and they'll still have to use `OMNameResolver` directly in order to use their existing `bitcoin:` URI parsing.
1 parent 8d8416b commit 99d0093

File tree

2 files changed

+229
-11
lines changed

2 files changed

+229
-11
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 173 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ use crate::offers::signer;
7575
#[cfg(async_payments)]
7676
use crate::offers::static_invoice::StaticInvoice;
7777
use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
78+
use crate::onion_message::dns_resolution::HumanReadableName;
7879
use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions};
7980
use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
8081
use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider};
@@ -87,6 +88,11 @@ use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, Maybe
8788
use crate::util::logger::{Level, Logger, WithContext};
8889
use crate::util::errors::APIError;
8990

91+
#[cfg(feature = "dnssec")]
92+
use crate::blinded_path::message::DNSResolverContext;
93+
#[cfg(feature = "dnssec")]
94+
use crate::onion_message::dns_resolution::{DNSResolverMessage, DNSResolverMessageHandler, DNSSECQuery, DNSSECProof, OMNameResolver};
95+
9096
#[cfg(not(c_bindings))]
9197
use {
9298
crate::offers::offer::DerivedMetadata,
@@ -2564,6 +2570,11 @@ where
25642570
/// [`ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee`] estimate.
25652571
last_days_feerates: Mutex<VecDeque<(u32, u32)>>,
25662572

2573+
#[cfg(feature = "dnssec")]
2574+
hrn_resolver: OMNameResolver,
2575+
#[cfg(feature = "dnssec")]
2576+
pending_dns_onion_messages: Mutex<Vec<(DNSResolverMessage, MessageSendInstructions)>>,
2577+
25672578
entropy_source: ES,
25682579
node_signer: NS,
25692580
signer_provider: SP,
@@ -3386,6 +3397,11 @@ where
33863397
signer_provider,
33873398

33883399
logger,
3400+
3401+
#[cfg(feature = "dnssec")]
3402+
hrn_resolver: OMNameResolver::new(current_timestamp, params.best_block.height),
3403+
#[cfg(feature = "dnssec")]
3404+
pending_dns_onion_messages: Mutex::new(Vec::new()),
33893405
}
33903406
}
33913407

@@ -9579,6 +9595,26 @@ where
95799595
&self, offer: &Offer, quantity: Option<u64>, amount_msats: Option<u64>,
95809596
payer_note: Option<String>, payment_id: PaymentId, retry_strategy: Retry,
95819597
max_total_routing_fee_msat: Option<u64>
9598+
) -> Result<(), Bolt12SemanticError> {
9599+
self.pay_for_offer_intern(offer, quantity, amount_msats, payer_note, payment_id, None, |invoice_request, nonce| {
9600+
let expiration = StaleExpiration::TimerTicks(1);
9601+
let retryable_invoice_request = RetryableInvoiceRequest {
9602+
invoice_request: invoice_request.clone(),
9603+
nonce,
9604+
};
9605+
self.pending_outbound_payments
9606+
.add_new_awaiting_invoice(
9607+
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
9608+
Some(retryable_invoice_request)
9609+
)
9610+
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)
9611+
})
9612+
}
9613+
9614+
fn pay_for_offer_intern<CPP: FnOnce(&InvoiceRequest, Nonce) -> Result<(), Bolt12SemanticError>>(
9615+
&self, offer: &Offer, quantity: Option<u64>, amount_msats: Option<u64>,
9616+
payer_note: Option<String>, payment_id: PaymentId,
9617+
human_readable_name: Option<HumanReadableName>, create_pending_payment: CPP,
95829618
) -> Result<(), Bolt12SemanticError> {
95839619
let expanded_key = &self.inbound_payment_key;
95849620
let entropy = &*self.entropy_source;
@@ -9602,6 +9638,10 @@ where
96029638
None => builder,
96039639
Some(payer_note) => builder.payer_note(payer_note),
96049640
};
9641+
let builder = match human_readable_name {
9642+
None => builder,
9643+
Some(hrn) => builder.sourced_from_human_readable_name(hrn),
9644+
};
96059645
let invoice_request = builder.build_and_sign()?;
96069646

96079647
let hmac = payment_id.hmac_for_offer_payment(nonce, expanded_key);
@@ -9613,17 +9653,7 @@ where
96139653

96149654
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
96159655

9616-
let expiration = StaleExpiration::TimerTicks(1);
9617-
let retryable_invoice_request = RetryableInvoiceRequest {
9618-
invoice_request: invoice_request.clone(),
9619-
nonce,
9620-
};
9621-
self.pending_outbound_payments
9622-
.add_new_awaiting_invoice(
9623-
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
9624-
Some(retryable_invoice_request)
9625-
)
9626-
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
9656+
create_pending_payment(&invoice_request, nonce)?;
96279657

96289658
self.enqueue_invoice_request(invoice_request, reply_paths)
96299659
}
@@ -9764,6 +9794,73 @@ where
97649794
}
97659795
}
97669796

9797+
/// Pays for an [`Offer`] looked up using [BIP 353] Human Readable Names resolved by the DNS
9798+
/// resolver(s) at `dns_resolvers` which resolve names according to bLIP 32.
9799+
///
9800+
/// If the wallet supports paying on-chain schemes, you should instead use
9801+
/// [`OMNameResolver::resolve_name`] and [`OMNameResolver::handle_dnssec_proof_for_uri`] (by
9802+
/// implementing [`DNSResolverMessageHandler`]) directly to look up a URI and then delegate to
9803+
/// your normal URI handling.
9804+
///
9805+
/// If `max_total_routing_fee_msat` is not specified, the default from
9806+
/// [`RouteParameters::from_payment_params_and_value`] is applied.
9807+
///
9808+
/// # Payment
9809+
///
9810+
/// The provided `payment_id` is used to ensure that only one invoice is paid for the request
9811+
/// when received. See [Avoiding Duplicate Payments] for other requirements once the payment has
9812+
/// been sent.
9813+
///
9814+
/// To revoke the request, use [`ChannelManager::abandon_payment`] prior to receiving the
9815+
/// invoice. If abandoned, or an invoice isn't received in a reasonable amount of time, the
9816+
/// payment will fail with an [`Event::InvoiceRequestFailed`].
9817+
///
9818+
/// # Privacy
9819+
///
9820+
/// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`]
9821+
/// to construct a [`BlindedPath`] for the reply path. For further privacy implications, see the
9822+
/// docs of the parameterized [`Router`], which implements [`MessageRouter`].
9823+
///
9824+
/// # Limitations
9825+
///
9826+
/// Requires a direct connection to the given [`Destination`] as well as an introduction node in
9827+
/// [`Offer::paths`] or to [`Offer::signing_pubkey`], if empty. A similar restriction applies to
9828+
/// the responding [`Bolt12Invoice::payment_paths`].
9829+
///
9830+
/// # Errors
9831+
///
9832+
/// Errors if:
9833+
/// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
9834+
///
9835+
/// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths
9836+
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
9837+
#[cfg(feature = "dnssec")]
9838+
pub fn pay_for_offer_from_human_readable_name(
9839+
&self, name: HumanReadableName, amount_msats: u64, payment_id: PaymentId,
9840+
retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>,
9841+
dns_resolvers: Vec<Destination>,
9842+
) -> Result<(), ()> {
9843+
let (onion_message, context) =
9844+
self.hrn_resolver.resolve_name(payment_id, name, &*self.entropy_source)?;
9845+
let reply_paths = self.create_blinded_paths(MessageContext::DNSResolver(context))?;
9846+
let expiration = StaleExpiration::TimerTicks(1);
9847+
self.pending_outbound_payments.add_new_awaiting_offer(payment_id, expiration, retry_strategy, max_total_routing_fee_msat, amount_msats)?;
9848+
let message_params = dns_resolvers
9849+
.iter()
9850+
.flat_map(|destination| reply_paths.iter().map(move |path| (path, destination)))
9851+
.take(OFFERS_MESSAGE_REQUEST_LIMIT);
9852+
for (reply_path, destination) in message_params {
9853+
self.pending_dns_onion_messages.lock().unwrap().push((
9854+
DNSResolverMessage::DNSSECQuery(onion_message.clone()),
9855+
MessageSendInstructions::WithSpecifiedReplyPath {
9856+
destination: destination.clone(),
9857+
reply_path: reply_path.clone(),
9858+
},
9859+
));
9860+
}
9861+
Ok(())
9862+
}
9863+
97679864
/// Gets a payment secret and payment hash for use in an invoice given to a third party wishing
97689865
/// to pay us.
97699866
///
@@ -10387,6 +10484,10 @@ where
1038710484
}
1038810485
}
1038910486
max_time!(self.highest_seen_timestamp);
10487+
#[cfg(feature = "dnssec")] {
10488+
let timestamp = self.highest_seen_timestamp.load(Ordering::Relaxed) as u32;
10489+
self.hrn_resolver.new_best_block(height, timestamp);
10490+
}
1039010491
}
1039110492

1039210493
fn get_relevant_txids(&self) -> Vec<(Txid, u32, Option<BlockHash>)> {
@@ -11637,6 +11738,62 @@ where
1163711738
}
1163811739
}
1163911740

11741+
#[cfg(feature = "dnssec")]
11742+
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref>
11743+
DNSResolverMessageHandler for ChannelManager<M, T, ES, NS, SP, F, R, MR, L>
11744+
where
11745+
M::Target: chain::Watch<<SP::Target as SignerProvider>::EcdsaSigner>,
11746+
T::Target: BroadcasterInterface,
11747+
ES::Target: EntropySource,
11748+
NS::Target: NodeSigner,
11749+
SP::Target: SignerProvider,
11750+
F::Target: FeeEstimator,
11751+
R::Target: Router,
11752+
MR::Target: MessageRouter,
11753+
L::Target: Logger,
11754+
{
11755+
fn handle_dnssec_query(
11756+
&self, _message: DNSSECQuery, _responder: Option<Responder>,
11757+
) -> Option<(DNSResolverMessage, ResponseInstruction)> {
11758+
None
11759+
}
11760+
11761+
fn handle_dnssec_proof(&self, message: DNSSECProof, context: DNSResolverContext) {
11762+
let offer_opt = self.hrn_resolver.handle_dnssec_proof_for_offer(message, context);
11763+
if let Some((completed_requests, offer)) = offer_opt {
11764+
for (name, payment_id) in completed_requests {
11765+
if let Ok(amt_msats) = self.pending_outbound_payments.amt_msats_for_payment_awaiting_offer(payment_id) {
11766+
let offer_pay_res =
11767+
self.pay_for_offer_intern(&offer, None, Some(amt_msats), None, payment_id, Some(name),
11768+
|invoice_request, nonce| {
11769+
let retryable_invoice_request = RetryableInvoiceRequest {
11770+
invoice_request: invoice_request.clone(),
11771+
nonce,
11772+
};
11773+
self.pending_outbound_payments
11774+
.received_offer(payment_id, Some(retryable_invoice_request))
11775+
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)
11776+
});
11777+
if offer_pay_res.is_err() {
11778+
// The offer we tried to pay is the canonical current offer for the name we
11779+
// wanted to pay. If we can't pay it, there's no way to recover so fail the
11780+
// payment.
11781+
// Note that the PaymentFailureReason should be ignored for an
11782+
// AwaitingInvoice payment.
11783+
self.pending_outbound_payments.abandon_payment(
11784+
payment_id, PaymentFailureReason::RouteNotFound, &self.pending_events,
11785+
);
11786+
}
11787+
}
11788+
}
11789+
}
11790+
}
11791+
11792+
fn release_pending_messages(&self) -> Vec<(DNSResolverMessage, MessageSendInstructions)> {
11793+
core::mem::take(&mut self.pending_dns_onion_messages.lock().unwrap())
11794+
}
11795+
}
11796+
1164011797
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref>
1164111798
NodeIdLookUp for ChannelManager<M, T, ES, NS, SP, F, R, MR, L>
1164211799
where
@@ -13321,6 +13478,11 @@ where
1332113478

1332213479
logger: args.logger,
1332313480
default_configuration: args.default_config,
13481+
13482+
#[cfg(feature = "dnssec")]
13483+
hrn_resolver: OMNameResolver::new(highest_seen_timestamp, best_block_height),
13484+
#[cfg(feature = "dnssec")]
13485+
pending_dns_onion_messages: Mutex::new(Vec::new()),
1332413486
};
1332513487

1332613488
for (_, monitor) in args.channel_monitors.iter() {

lightning/src/ln/outbound_payment.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,6 +1639,62 @@ impl OutboundPayments {
16391639
(payment, onion_session_privs)
16401640
}
16411641

1642+
#[cfg(feature = "dnssec")]
1643+
pub(super) fn add_new_awaiting_offer(
1644+
&self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry,
1645+
max_total_routing_fee_msat: Option<u64>, amount_msats: u64,
1646+
) -> Result<(), ()> {
1647+
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
1648+
match pending_outbounds.entry(payment_id) {
1649+
hash_map::Entry::Occupied(_) => Err(()),
1650+
hash_map::Entry::Vacant(entry) => {
1651+
entry.insert(PendingOutboundPayment::AwaitingOffer {
1652+
expiration,
1653+
retry_strategy,
1654+
max_total_routing_fee_msat,
1655+
amount_msats,
1656+
});
1657+
1658+
Ok(())
1659+
},
1660+
}
1661+
}
1662+
1663+
#[cfg(feature = "dnssec")]
1664+
pub(super) fn amt_msats_for_payment_awaiting_offer(&self, payment_id: PaymentId) -> Result<u64, ()> {
1665+
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
1666+
hash_map::Entry::Occupied(entry) => match entry.get() {
1667+
PendingOutboundPayment::AwaitingOffer { amount_msats, .. } => Ok(*amount_msats),
1668+
_ => Err(()),
1669+
},
1670+
_ => Err(()),
1671+
}
1672+
}
1673+
1674+
#[cfg(feature = "dnssec")]
1675+
pub(super) fn received_offer(
1676+
&self, payment_id: PaymentId, retryable_invoice_request: Option<RetryableInvoiceRequest>,
1677+
) -> Result<(), ()> {
1678+
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
1679+
hash_map::Entry::Occupied(entry) => match entry.get() {
1680+
PendingOutboundPayment::AwaitingOffer {
1681+
expiration, retry_strategy, max_total_routing_fee_msat, ..
1682+
} => {
1683+
let mut new_val = PendingOutboundPayment::AwaitingInvoice {
1684+
expiration: *expiration,
1685+
retry_strategy: *retry_strategy,
1686+
max_total_routing_fee_msat: *max_total_routing_fee_msat,
1687+
retryable_invoice_request,
1688+
};
1689+
core::mem::swap(&mut new_val, entry.into_mut());
1690+
Ok(())
1691+
},
1692+
_ => Err(()),
1693+
},
1694+
hash_map::Entry::Vacant(_) => Err(()),
1695+
}
1696+
}
1697+
16421698
pub(super) fn add_new_awaiting_invoice(
16431699
&self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry,
16441700
max_total_routing_fee_msat: Option<u64>, retryable_invoice_request: Option<RetryableInvoiceRequest>

0 commit comments

Comments
 (0)