Skip to content

Commit 9af5847

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 ed7e617 commit 9af5847

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
@@ -74,6 +74,7 @@ use crate::offers::signer;
7474
#[cfg(async_payments)]
7575
use crate::offers::static_invoice::StaticInvoice;
7676
use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
77+
use crate::onion_message::dns_resolution::HumanReadableName;
7778
use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions};
7879
use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
7980
use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider};
@@ -86,6 +87,11 @@ use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, Maybe
8687
use crate::util::logger::{Level, Logger, WithContext};
8788
use crate::util::errors::APIError;
8889

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

2437+
#[cfg(feature = "dnssec")]
2438+
dns_resolver: OMNameResolver,
2439+
#[cfg(feature = "dnssec")]
2440+
pending_dns_onion_messages: Mutex<Vec<(DNSResolverMessage, MessageSendInstructions)>>,
2441+
24312442
entropy_source: ES,
24322443
node_signer: NS,
24332444
signer_provider: SP,
@@ -3251,6 +3262,11 @@ where
32513262
signer_provider,
32523263

32533264
logger,
3265+
3266+
#[cfg(feature = "dnssec")]
3267+
dns_resolver: OMNameResolver::new(current_timestamp, params.best_block.height),
3268+
#[cfg(feature = "dnssec")]
3269+
pending_dns_onion_messages: Mutex::new(Vec::new()),
32543270
}
32553271
}
32563272

@@ -9304,6 +9320,26 @@ where
93049320
&self, offer: &Offer, quantity: Option<u64>, amount_msats: Option<u64>,
93059321
payer_note: Option<String>, payment_id: PaymentId, retry_strategy: Retry,
93069322
max_total_routing_fee_msat: Option<u64>
9323+
) -> Result<(), Bolt12SemanticError> {
9324+
self.pay_for_offer_intern(offer, quantity, amount_msats, payer_note, payment_id, None, |invoice_request, nonce| {
9325+
let expiration = StaleExpiration::TimerTicks(1);
9326+
let retryable_invoice_request = RetryableInvoiceRequest {
9327+
invoice_request: invoice_request.clone(),
9328+
nonce,
9329+
};
9330+
self.pending_outbound_payments
9331+
.add_new_awaiting_invoice(
9332+
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
9333+
Some(retryable_invoice_request)
9334+
)
9335+
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)
9336+
})
9337+
}
9338+
9339+
fn pay_for_offer_intern<CPP: FnOnce(&InvoiceRequest, Nonce) -> Result<(), Bolt12SemanticError>>(
9340+
&self, offer: &Offer, quantity: Option<u64>, amount_msats: Option<u64>,
9341+
payer_note: Option<String>, payment_id: PaymentId,
9342+
human_readable_name: Option<HumanReadableName>, create_pending_payment: CPP,
93079343
) -> Result<(), Bolt12SemanticError> {
93089344
let expanded_key = &self.inbound_payment_key;
93099345
let entropy = &*self.entropy_source;
@@ -9327,6 +9363,10 @@ where
93279363
None => builder,
93289364
Some(payer_note) => builder.payer_note(payer_note),
93299365
};
9366+
let builder = match human_readable_name {
9367+
None => builder,
9368+
Some(hrn) => builder.sourced_from_human_readable_name(hrn),
9369+
};
93309370
let invoice_request = builder.build_and_sign()?;
93319371

93329372
let hmac = payment_id.hmac_for_offer_payment(nonce, expanded_key);
@@ -9338,17 +9378,7 @@ where
93389378

93399379
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
93409380

9341-
let expiration = StaleExpiration::TimerTicks(1);
9342-
let retryable_invoice_request = RetryableInvoiceRequest {
9343-
invoice_request: invoice_request.clone(),
9344-
nonce,
9345-
};
9346-
self.pending_outbound_payments
9347-
.add_new_awaiting_invoice(
9348-
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
9349-
Some(retryable_invoice_request)
9350-
)
9351-
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
9381+
create_pending_payment(&invoice_request, nonce)?;
93529382

93539383
self.enqueue_invoice_request(invoice_request, reply_paths)
93549384
}
@@ -9489,6 +9519,73 @@ where
94899519
}
94909520
}
94919521

9522+
/// Pays for an [`Offer`] looked up using [BIP 353] Human Readable Names resolved by the DNS
9523+
/// resolver which resolves names according to bLIP 32 at the `dns_resolver` [`Destination`].
9524+
///
9525+
/// If the wallet supports paying on-chain schemes, you should instead use
9526+
/// [`OMNameResolver::resolve_name`] and [`OMNameResolver::handle_dnssec_proof_for_uri`] (by
9527+
/// implementing [`DNSResolverMessageHandler`]) directly to look up a URI and then delegate to
9528+
/// your normal URI handling.
9529+
///
9530+
/// If `max_total_routing_fee_msat` is not specified, The default from
9531+
/// [`RouteParameters::from_payment_params_and_value`] is applied.
9532+
///
9533+
/// # Payment
9534+
///
9535+
/// The provided `payment_id` is used to ensure that only one invoice is paid for the request
9536+
/// when received. See [Avoiding Duplicate Payments] for other requirements once the payment has
9537+
/// been sent.
9538+
///
9539+
/// To revoke the request, use [`ChannelManager::abandon_payment`] prior to receiving the
9540+
/// invoice. If abandoned, or an invoice isn't received in a reasonable amount of time, the
9541+
/// payment will fail with an [`Event::InvoiceRequestFailed`].
9542+
///
9543+
/// # Privacy
9544+
///
9545+
/// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`]
9546+
/// to construct a [`BlindedPath`] for the reply path. For further privacy implications, see the
9547+
/// docs of the parameterized [`Router`], which implements [`MessageRouter`].
9548+
///
9549+
/// # Limitations
9550+
///
9551+
/// Requires a direct connection to the given [`Destination`] as well as an introduction node in
9552+
/// [`Offer::paths`] or to [`Offer::signing_pubkey`], if empty. A similar restriction applies to
9553+
/// the responding [`Bolt12Invoice::payment_paths`].
9554+
///
9555+
/// # Errors
9556+
///
9557+
/// Errors if:
9558+
/// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
9559+
///
9560+
/// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths
9561+
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
9562+
#[cfg(feature = "dnssec")]
9563+
pub fn pay_for_offer_from_human_readable_name(
9564+
&self, name: HumanReadableName, amount_msats: u64, payment_id: PaymentId,
9565+
retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>,
9566+
dns_resolvers: Vec<Destination>,
9567+
) -> Result<(), ()> {
9568+
let (onion_message, context) =
9569+
self.dns_resolver.resolve_name(payment_id, name, &*self.entropy_source)?;
9570+
let reply_paths = self.create_blinded_paths(MessageContext::DNSResolver(context))?;
9571+
let expiration = StaleExpiration::TimerTicks(2);
9572+
self.pending_outbound_payments.add_new_awaiting_offer(payment_id, expiration, retry_strategy, max_total_routing_fee_msat, amount_msats)?;
9573+
let message_params = dns_resolvers
9574+
.iter()
9575+
.flat_map(|destination| reply_paths.iter().map(move |path| (path, destination)))
9576+
.take(OFFERS_MESSAGE_REQUEST_LIMIT);
9577+
for (reply_path, destination) in message_params {
9578+
self.pending_dns_onion_messages.lock().unwrap().push((
9579+
DNSResolverMessage::DNSSECQuery(onion_message.clone()),
9580+
MessageSendInstructions::WithSpecifiedReplyPath {
9581+
destination: destination.clone(),
9582+
reply_path: reply_path.clone(),
9583+
},
9584+
));
9585+
}
9586+
Ok(())
9587+
}
9588+
94929589
/// Gets a payment secret and payment hash for use in an invoice given to a third party wishing
94939590
/// to pay us.
94949591
///
@@ -10116,6 +10213,10 @@ where
1011610213
payment_secrets.retain(|_, inbound_payment| {
1011710214
inbound_payment.expiry_time > header.time as u64
1011810215
});
10216+
#[cfg(feature = "dnssec")] {
10217+
let timestamp = self.highest_seen_timestamp.load(Ordering::Relaxed) as u32;
10218+
self.dns_resolver.new_best_block(height, timestamp);
10219+
}
1011910220
}
1012010221

1012110222
fn get_relevant_txids(&self) -> Vec<(Txid, u32, Option<BlockHash>)> {
@@ -11341,6 +11442,62 @@ where
1134111442
}
1134211443
}
1134311444

11445+
#[cfg(feature = "dnssec")]
11446+
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref>
11447+
DNSResolverMessageHandler for ChannelManager<M, T, ES, NS, SP, F, R, MR, L>
11448+
where
11449+
M::Target: chain::Watch<<SP::Target as SignerProvider>::EcdsaSigner>,
11450+
T::Target: BroadcasterInterface,
11451+
ES::Target: EntropySource,
11452+
NS::Target: NodeSigner,
11453+
SP::Target: SignerProvider,
11454+
F::Target: FeeEstimator,
11455+
R::Target: Router,
11456+
MR::Target: MessageRouter,
11457+
L::Target: Logger,
11458+
{
11459+
fn handle_dnssec_query(
11460+
&self, _message: DNSSECQuery, _responder: Option<Responder>,
11461+
) -> Option<(DNSResolverMessage, ResponseInstruction)> {
11462+
None
11463+
}
11464+
11465+
fn handle_dnssec_proof(&self, message: DNSSECProof, context: DNSResolverContext) {
11466+
let offer_opt = self.dns_resolver.handle_dnssec_proof_for_offer(message, context);
11467+
if let Some((completed_requests, offer)) = offer_opt {
11468+
for (name, payment_id) in completed_requests {
11469+
if let Ok(amt_msats) = self.pending_outbound_payments.amt_msats_for_payment_awaiting_offer(payment_id) {
11470+
let offer_pay_res =
11471+
self.pay_for_offer_intern(&offer, None, Some(amt_msats), None, payment_id, Some(name),
11472+
|invoice_request, nonce| {
11473+
let retryable_invoice_request = RetryableInvoiceRequest {
11474+
invoice_request: invoice_request.clone(),
11475+
nonce,
11476+
};
11477+
self.pending_outbound_payments
11478+
.received_offer(payment_id, Some(retryable_invoice_request))
11479+
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)
11480+
});
11481+
if offer_pay_res.is_err() {
11482+
// The offer we tried to pay is the canonical current offer for the name we
11483+
// wanted to pay. If we can't pay it, there's no way to recover so fail the
11484+
// payment.
11485+
// Note that the PaymentFailureReason should be ignored for an
11486+
// AwaitingInvoice payment.
11487+
self.pending_outbound_payments.abandon_payment(
11488+
payment_id, PaymentFailureReason::RouteNotFound, &self.pending_events,
11489+
);
11490+
}
11491+
}
11492+
}
11493+
}
11494+
}
11495+
11496+
fn release_pending_messages(&self) -> Vec<(DNSResolverMessage, MessageSendInstructions)> {
11497+
core::mem::take(&mut self.pending_dns_onion_messages.lock().unwrap())
11498+
}
11499+
}
11500+
1134411501
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref>
1134511502
NodeIdLookUp for ChannelManager<M, T, ES, NS, SP, F, R, MR, L>
1134611503
where
@@ -13079,6 +13236,11 @@ where
1307913236

1308013237
logger: args.logger,
1308113238
default_configuration: args.default_config,
13239+
13240+
#[cfg(feature = "dnssec")]
13241+
dns_resolver: OMNameResolver::new(highest_seen_timestamp, best_block_height),
13242+
#[cfg(feature = "dnssec")]
13243+
pending_dns_onion_messages: Mutex::new(Vec::new()),
1308213244
};
1308313245

1308413246
for htlc_source in failed_htlcs.drain(..) {

lightning/src/ln/outbound_payment.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,6 +1611,62 @@ impl OutboundPayments {
16111611
(payment, onion_session_privs)
16121612
}
16131613

1614+
#[cfg(feature = "dnssec")]
1615+
pub(super) fn add_new_awaiting_offer(
1616+
&self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry,
1617+
max_total_routing_fee_msat: Option<u64>, amount_msats: u64,
1618+
) -> Result<(), ()> {
1619+
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
1620+
match pending_outbounds.entry(payment_id) {
1621+
hash_map::Entry::Occupied(_) => Err(()),
1622+
hash_map::Entry::Vacant(entry) => {
1623+
entry.insert(PendingOutboundPayment::AwaitingOffer {
1624+
expiration,
1625+
retry_strategy,
1626+
max_total_routing_fee_msat,
1627+
amount_msats,
1628+
});
1629+
1630+
Ok(())
1631+
},
1632+
}
1633+
}
1634+
1635+
#[cfg(feature = "dnssec")]
1636+
pub(super) fn amt_msats_for_payment_awaiting_offer(&self, payment_id: PaymentId) -> Result<u64, ()> {
1637+
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
1638+
hash_map::Entry::Occupied(entry) => match entry.get() {
1639+
PendingOutboundPayment::AwaitingOffer { amount_msats, .. } => Ok(*amount_msats),
1640+
_ => Err(()),
1641+
},
1642+
_ => Err(()),
1643+
}
1644+
}
1645+
1646+
#[cfg(feature = "dnssec")]
1647+
pub(super) fn received_offer(
1648+
&self, payment_id: PaymentId, retryable_invoice_request: Option<RetryableInvoiceRequest>,
1649+
) -> Result<(), ()> {
1650+
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
1651+
hash_map::Entry::Occupied(entry) => match entry.get() {
1652+
PendingOutboundPayment::AwaitingOffer {
1653+
expiration, retry_strategy, max_total_routing_fee_msat, ..
1654+
} => {
1655+
let mut new_val = PendingOutboundPayment::AwaitingInvoice {
1656+
expiration: *expiration,
1657+
retry_strategy: *retry_strategy,
1658+
max_total_routing_fee_msat: *max_total_routing_fee_msat,
1659+
retryable_invoice_request,
1660+
};
1661+
core::mem::swap(&mut new_val, entry.into_mut());
1662+
Ok(())
1663+
},
1664+
_ => Err(()),
1665+
},
1666+
hash_map::Entry::Vacant(_) => Err(()),
1667+
}
1668+
}
1669+
16141670
pub(super) fn add_new_awaiting_invoice(
16151671
&self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry,
16161672
max_total_routing_fee_msat: Option<u64>, retryable_invoice_request: Option<RetryableInvoiceRequest>

0 commit comments

Comments
 (0)