bitcoin_payment_instructions/
hrn_resolution.rs

1//! When encountering human-readable names of the form alice@domain, we should attempt to resolve
2//! them into concrete payment instructions which wallets can handle.
3//!
4//! Currently, this generally is done either using BIP 353 and the DNS or using LNURL-Pay and
5//! LN-Address. Because these could be resolved using different methods (and, for privacy reasons
6//! some wallets may wish to avoid LNURL), we abstract the resolution process using the trait and
7//! associated types in this module.
8
9use crate::amount::Amount;
10
11use lightning_invoice::Bolt11Invoice;
12
13pub use lightning::onion_message::dns_resolution::HumanReadableName;
14
15use core::future::Future;
16use core::pin::Pin;
17
18use alloc::boxed::Box;
19use alloc::string::String;
20use alloc::vec::Vec;
21
22/// The first-step resolution of a Human Readable Name.
23///
24/// It can either represent a resolution using BIP 353 and the DNS or the first step resolution of
25/// an LNURL-Pay. The second step, resolving a callback URI to a [`Bolt11Invoice`] occurs via
26/// [`HrnResolver::resolve_lnurl`].
27pub enum HrnResolution {
28	/// The HRN was resolved using BIP 353 and the DNS. The result should contain a BIP 321
29	/// bitcoin: URI as well as a DNSSEC proof which allows later verification of the payment
30	/// instructions.
31	DNSSEC {
32		/// A DNSSEC proof as used in BIP 353.
33		///
34		/// If the HRN was resolved using BIP 353, this should be set to a full proof which can later
35		/// be copied to PSBTs for hardware wallet verification or stored as a part of proof of
36		/// payment.
37		proof: Option<Vec<u8>>,
38		/// The result of the resolution.
39		///
40		/// This should contain a string which can be parsed as further payment instructions. For a BIP
41		/// 353 resolution, this will contain a full BIP 321 bitcoin: URI, for a LN-Address resolution
42		/// this will contain a lightning BOLT 11 invoice.
43		result: String,
44	},
45	/// The HRN was resolved using LNURL-Pay as an LN-Address. The result contains a callback URI
46	/// which will be used once we pick an amount to fetch a final [`Bolt11Invoice`].
47	LNURLPay {
48		/// The minimum amount which can be sent to the recipient, as specified in the LNURL-Pay
49		/// initial response.
50		min_value: Amount,
51		/// The maximum amount which can be sent to the recipient, as specified in the LNURL-Pay
52		/// initial response.
53		max_value: Amount,
54		/// The description hash which must appear in the final [`Bolt11Invoice`], committing to
55		/// the full recipient metadata.
56		///
57		/// While we could store the full recipient metadata and use it as a committed value in our
58		/// proof-of-payment, there is no way to ensure it is actually provable against the
59		/// server/initial payment instructions string because the server can return a
60		/// [`Bolt11Invoice`] signed by any arbitrary public key.
61		expected_description_hash: [u8; 32],
62		/// The text/plain description provided in the LNURL-Pay initial response.
63		///
64		/// This is generally human-readable and can be displayed to the user as with any other
65		/// recipient description in payment instructions.
66		recipient_description: Option<String>,
67		/// The callback URI which can be used, with a concrete amount, to fetch a final
68		/// [`Bolt11Invoice`] which can be paid.
69		callback: String,
70	},
71}
72
73/// A future which resolves to a [`HrnResolution`].
74pub type HrnResolutionFuture<'a> =
75	Pin<Box<dyn Future<Output = Result<HrnResolution, &'static str>> + Send + 'a>>;
76
77/// A future which resolves to a [`Bolt11Invoice`].
78pub type LNURLResolutionFuture<'a> =
79	Pin<Box<dyn Future<Output = Result<Bolt11Invoice, &'static str>> + Send + 'a>>;
80
81/// An arbitrary resolver for a Human Readable Name.
82///
83/// In general, such a resolver should first attempt to resolve using DNSSEC as defined in BIP 353.
84///
85/// For clients that also support LN-Address, if the BIP 353 resolution fails they should then fall
86/// back to LN-Address to resolve to a Lightning BOLT 11 using HTTP.
87///
88/// A resolver which uses any (DNSSEC-enabled) recursive DNS resolver to resolve BIP 353 HRNs is
89/// provided in
90#[cfg_attr(feature = "std", doc = "[`dns_resolver::DNSHrnResolver`]")]
91#[cfg_attr(not(feature = "std"), doc = "`dns_resolver::DNSHrnResolver`")]
92/// if the crate is built with the `std` feature. Note that using this reveals who we are paying to
93/// the recursive DNS resolver.
94///
95/// A resolver which uses HTTPS to `dns.google` and HTTPS to arbitrary servers for LN-Address is
96/// provided in
97#[cfg_attr(feature = "http", doc = "[`http_resolver::HTTPHrnResolver`]")]
98#[cfg_attr(not(feature = "http"), doc = "`http_resolver::HTTPHrnResolver`")]
99/// if this crate is built with the `http` feature. Note that using this generally reveals our IP
100/// address to recipients,  as well as potentially who we are paying to Google.
101///
102#[cfg_attr(
103	feature = "std",
104	doc = "[`dns_resolver::DNSHrnResolver`]: crate::dns_resolver::DNSHrnResolver"
105)]
106#[cfg_attr(
107	feature = "http",
108	doc = "[`http_resolver::HTTPHrnResolver`]: crate::http_resolver::HTTPHrnResolver"
109)]
110pub trait HrnResolver {
111	/// Resolves the given Human Readable Name into a [`HrnResolution`] containing a result which
112	/// can be further parsed as payment instructions.
113	fn resolve_hrn<'a>(&'a self, hrn: &'a HumanReadableName) -> HrnResolutionFuture<'a>;
114
115	/// Resolves the given Lnurl into a [`HrnResolution`] containing a result which
116	/// can be further parsed as payment instructions.
117	fn resolve_lnurl<'a>(&'a self, url: &'a str) -> HrnResolutionFuture<'a>;
118
119	/// Resolves the LNURL callback (from a [`HrnResolution::LNURLPay`]) into a [`Bolt11Invoice`].
120	///
121	/// This shall only be called if [`Self::resolve_hrn`] returns an [`HrnResolution::LNURLPay`].
122	fn resolve_lnurl_to_invoice<'a>(
123		&'a self, callback_url: String, amount: Amount, expected_description_hash: [u8; 32],
124	) -> LNURLResolutionFuture<'a>;
125}
126
127/// An HRN "resolver" that never succeeds at resolving.
128#[derive(Clone, Copy)]
129pub struct DummyHrnResolver;
130
131impl HrnResolver for DummyHrnResolver {
132	fn resolve_hrn<'a>(&'a self, _hrn: &'a HumanReadableName) -> HrnResolutionFuture<'a> {
133		Box::pin(async { Err("Human Readable Name resolution not supported") })
134	}
135
136	fn resolve_lnurl<'a>(&'a self, _lnurl: &'a str) -> HrnResolutionFuture<'a> {
137		Box::pin(async { Err("LNURL resolution not supported") })
138	}
139
140	fn resolve_lnurl_to_invoice<'a>(
141		&'a self, _: String, _: Amount, _: [u8; 32],
142	) -> LNURLResolutionFuture<'a> {
143		Box::pin(async { Err("LNURL resolution not supported") })
144	}
145}