Skip to content

Commit 6a01594

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 e9c3401 commit 6a01594

File tree

2 files changed

+229
-11
lines changed

2 files changed

+229
-11
lines changed

lightning/src/ln/channelmanager.rs

+173-11
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,
@@ -2563,6 +2569,11 @@ where
25632569
/// [`ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee`] estimate.
25642570
last_days_feerates: Mutex<VecDeque<(u32, u32)>>,
25652571

2572+
#[cfg(feature = "dnssec")]
2573+
hrn_resolver: OMNameResolver,
2574+
#[cfg(feature = "dnssec")]
2575+
pending_dns_onion_messages: Mutex<Vec<(DNSResolverMessage, MessageSendInstructions)>>,
2576+
25662577
entropy_source: ES,
25672578
node_signer: NS,
25682579
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

@@ -9460,6 +9476,26 @@ where
94609476
&self, offer: &Offer, quantity: Option<u64>, amount_msats: Option<u64>,
94619477
payer_note: Option<String>, payment_id: PaymentId, retry_strategy: Retry,
94629478
max_total_routing_fee_msat: Option<u64>
9479+
) -> Result<(), Bolt12SemanticError> {
9480+
self.pay_for_offer_intern(offer, quantity, amount_msats, payer_note, payment_id, None, |invoice_request, nonce| {
9481+
let expiration = StaleExpiration::TimerTicks(1);
9482+
let retryable_invoice_request = RetryableInvoiceRequest {
9483+
invoice_request: invoice_request.clone(),
9484+
nonce,
9485+
};
9486+
self.pending_outbound_payments
9487+
.add_new_awaiting_invoice(
9488+
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
9489+
Some(retryable_invoice_request)
9490+
)
9491+
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)
9492+
})
9493+
}
9494+
9495+
fn pay_for_offer_intern<CPP: FnOnce(&InvoiceRequest, Nonce) -> Result<(), Bolt12SemanticError>>(
9496+
&self, offer: &Offer, quantity: Option<u64>, amount_msats: Option<u64>,
9497+
payer_note: Option<String>, payment_id: PaymentId,
9498+
human_readable_name: Option<HumanReadableName>, create_pending_payment: CPP,
94639499
) -> Result<(), Bolt12SemanticError> {
94649500
let expanded_key = &self.inbound_payment_key;
94659501
let entropy = &*self.entropy_source;
@@ -9483,6 +9519,10 @@ where
94839519
None => builder,
94849520
Some(payer_note) => builder.payer_note(payer_note),
94859521
};
9522+
let builder = match human_readable_name {
9523+
None => builder,
9524+
Some(hrn) => builder.sourced_from_human_readable_name(hrn),
9525+
};
94869526
let invoice_request = builder.build_and_sign()?;
94879527

94889528
let hmac = payment_id.hmac_for_offer_payment(nonce, expanded_key);
@@ -9494,17 +9534,7 @@ where
94949534

94959535
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
94969536

9497-
let expiration = StaleExpiration::TimerTicks(1);
9498-
let retryable_invoice_request = RetryableInvoiceRequest {
9499-
invoice_request: invoice_request.clone(),
9500-
nonce,
9501-
};
9502-
self.pending_outbound_payments
9503-
.add_new_awaiting_invoice(
9504-
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
9505-
Some(retryable_invoice_request)
9506-
)
9507-
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
9537+
create_pending_payment(&invoice_request, nonce)?;
95089538

95099539
self.enqueue_invoice_request(invoice_request, reply_paths)
95109540
}
@@ -9645,6 +9675,73 @@ where
96459675
}
96469676
}
96479677

9678+
/// Pays for an [`Offer`] looked up using [BIP 353] Human Readable Names resolved by the DNS
9679+
/// resolver(s) at `dns_resolvers` which resolve names according to bLIP 32.
9680+
///
9681+
/// If the wallet supports paying on-chain schemes, you should instead use
9682+
/// [`OMNameResolver::resolve_name`] and [`OMNameResolver::handle_dnssec_proof_for_uri`] (by
9683+
/// implementing [`DNSResolverMessageHandler`]) directly to look up a URI and then delegate to
9684+
/// your normal URI handling.
9685+
///
9686+
/// If `max_total_routing_fee_msat` is not specified, the default from
9687+
/// [`RouteParameters::from_payment_params_and_value`] is applied.
9688+
///
9689+
/// # Payment
9690+
///
9691+
/// The provided `payment_id` is used to ensure that only one invoice is paid for the request
9692+
/// when received. See [Avoiding Duplicate Payments] for other requirements once the payment has
9693+
/// been sent.
9694+
///
9695+
/// To revoke the request, use [`ChannelManager::abandon_payment`] prior to receiving the
9696+
/// invoice. If abandoned, or an invoice isn't received in a reasonable amount of time, the
9697+
/// payment will fail with an [`Event::InvoiceRequestFailed`].
9698+
///
9699+
/// # Privacy
9700+
///
9701+
/// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`]
9702+
/// to construct a [`BlindedPath`] for the reply path. For further privacy implications, see the
9703+
/// docs of the parameterized [`Router`], which implements [`MessageRouter`].
9704+
///
9705+
/// # Limitations
9706+
///
9707+
/// Requires a direct connection to the given [`Destination`] as well as an introduction node in
9708+
/// [`Offer::paths`] or to [`Offer::signing_pubkey`], if empty. A similar restriction applies to
9709+
/// the responding [`Bolt12Invoice::payment_paths`].
9710+
///
9711+
/// # Errors
9712+
///
9713+
/// Errors if:
9714+
/// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
9715+
///
9716+
/// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths
9717+
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
9718+
#[cfg(feature = "dnssec")]
9719+
pub fn pay_for_offer_from_human_readable_name(
9720+
&self, name: HumanReadableName, amount_msats: u64, payment_id: PaymentId,
9721+
retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>,
9722+
dns_resolvers: Vec<Destination>,
9723+
) -> Result<(), ()> {
9724+
let (onion_message, context) =
9725+
self.hrn_resolver.resolve_name(payment_id, name, &*self.entropy_source)?;
9726+
let reply_paths = self.create_blinded_paths(MessageContext::DNSResolver(context))?;
9727+
let expiration = StaleExpiration::TimerTicks(1);
9728+
self.pending_outbound_payments.add_new_awaiting_offer(payment_id, expiration, retry_strategy, max_total_routing_fee_msat, amount_msats)?;
9729+
let message_params = dns_resolvers
9730+
.iter()
9731+
.flat_map(|destination| reply_paths.iter().map(move |path| (path, destination)))
9732+
.take(OFFERS_MESSAGE_REQUEST_LIMIT);
9733+
for (reply_path, destination) in message_params {
9734+
self.pending_dns_onion_messages.lock().unwrap().push((
9735+
DNSResolverMessage::DNSSECQuery(onion_message.clone()),
9736+
MessageSendInstructions::WithSpecifiedReplyPath {
9737+
destination: destination.clone(),
9738+
reply_path: reply_path.clone(),
9739+
},
9740+
));
9741+
}
9742+
Ok(())
9743+
}
9744+
96489745
/// Gets a payment secret and payment hash for use in an invoice given to a third party wishing
96499746
/// to pay us.
96509747
///
@@ -10272,6 +10369,10 @@ where
1027210369
payment_secrets.retain(|_, inbound_payment| {
1027310370
inbound_payment.expiry_time > header.time as u64
1027410371
});
10372+
#[cfg(feature = "dnssec")] {
10373+
let timestamp = self.highest_seen_timestamp.load(Ordering::Relaxed) as u32;
10374+
self.hrn_resolver.new_best_block(height, timestamp);
10375+
}
1027510376
}
1027610377

1027710378
fn get_relevant_txids(&self) -> Vec<(Txid, u32, Option<BlockHash>)> {
@@ -11522,6 +11623,62 @@ where
1152211623
}
1152311624
}
1152411625

11626+
#[cfg(feature = "dnssec")]
11627+
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref>
11628+
DNSResolverMessageHandler for ChannelManager<M, T, ES, NS, SP, F, R, MR, L>
11629+
where
11630+
M::Target: chain::Watch<<SP::Target as SignerProvider>::EcdsaSigner>,
11631+
T::Target: BroadcasterInterface,
11632+
ES::Target: EntropySource,
11633+
NS::Target: NodeSigner,
11634+
SP::Target: SignerProvider,
11635+
F::Target: FeeEstimator,
11636+
R::Target: Router,
11637+
MR::Target: MessageRouter,
11638+
L::Target: Logger,
11639+
{
11640+
fn handle_dnssec_query(
11641+
&self, _message: DNSSECQuery, _responder: Option<Responder>,
11642+
) -> Option<(DNSResolverMessage, ResponseInstruction)> {
11643+
None
11644+
}
11645+
11646+
fn handle_dnssec_proof(&self, message: DNSSECProof, context: DNSResolverContext) {
11647+
let offer_opt = self.hrn_resolver.handle_dnssec_proof_for_offer(message, context);
11648+
if let Some((completed_requests, offer)) = offer_opt {
11649+
for (name, payment_id) in completed_requests {
11650+
if let Ok(amt_msats) = self.pending_outbound_payments.amt_msats_for_payment_awaiting_offer(payment_id) {
11651+
let offer_pay_res =
11652+
self.pay_for_offer_intern(&offer, None, Some(amt_msats), None, payment_id, Some(name),
11653+
|invoice_request, nonce| {
11654+
let retryable_invoice_request = RetryableInvoiceRequest {
11655+
invoice_request: invoice_request.clone(),
11656+
nonce,
11657+
};
11658+
self.pending_outbound_payments
11659+
.received_offer(payment_id, Some(retryable_invoice_request))
11660+
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)
11661+
});
11662+
if offer_pay_res.is_err() {
11663+
// The offer we tried to pay is the canonical current offer for the name we
11664+
// wanted to pay. If we can't pay it, there's no way to recover so fail the
11665+
// payment.
11666+
// Note that the PaymentFailureReason should be ignored for an
11667+
// AwaitingInvoice payment.
11668+
self.pending_outbound_payments.abandon_payment(
11669+
payment_id, PaymentFailureReason::RouteNotFound, &self.pending_events,
11670+
);
11671+
}
11672+
}
11673+
}
11674+
}
11675+
}
11676+
11677+
fn release_pending_messages(&self) -> Vec<(DNSResolverMessage, MessageSendInstructions)> {
11678+
core::mem::take(&mut self.pending_dns_onion_messages.lock().unwrap())
11679+
}
11680+
}
11681+
1152511682
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref>
1152611683
NodeIdLookUp for ChannelManager<M, T, ES, NS, SP, F, R, MR, L>
1152711684
where
@@ -13207,6 +13364,11 @@ where
1320713364

1320813365
logger: args.logger,
1320913366
default_configuration: args.default_config,
13367+
13368+
#[cfg(feature = "dnssec")]
13369+
hrn_resolver: OMNameResolver::new(highest_seen_timestamp, best_block_height),
13370+
#[cfg(feature = "dnssec")]
13371+
pending_dns_onion_messages: Mutex::new(Vec::new()),
1321013372
};
1321113373

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

lightning/src/ln/outbound_payment.rs

+56
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)