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}