diff --git a/Cargo.toml b/Cargo.toml index d34710a6e..bd66b3cd5 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,7 @@ async-trait = { version = "0.1", default-features = false } vss-client = { package = "vss-client-ng", version = "0.5" } prost = { version = "0.11.6", default-features = false} #bitcoin-payment-instructions = { version = "0.6" } -bitcoin-payment-instructions = { git = "https://github.com/jkczyz/bitcoin-payment-instructions", rev = "679dac50cc0d81ec4d31da94b93d467e5308f16a" } +bitcoin-payment-instructions = { git = "https://github.com/jkczyz/bitcoin-payment-instructions", rev = "679dac50cc0d81ec4d31da94b93d467e5308f16a", features = ["http"] } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["winbase"] } diff --git a/src/builder.rs b/src/builder.rs index 04ee39244..ed9888fd6 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -21,6 +21,7 @@ use bitcoin::key::Secp256k1; use bitcoin::secp256k1::PublicKey; use bitcoin::Network; use bitcoin_payment_instructions::dns_resolver::DNSHrnResolver; +use bitcoin_payment_instructions::http_resolver::HTTPHrnResolver; use bitcoin_payment_instructions::onion_message_resolver::LDKOnionMessageDNSSECHrnResolver; use lightning::chain::{chainmonitor, BestBlock as BlockLocator}; use lightning::ln::channelmanager::{self, ChainParameters, ChannelManagerReadArgs}; @@ -80,8 +81,8 @@ use crate::runtime::{Runtime, RuntimeSpawner}; use crate::tx_broadcaster::TransactionBroadcaster; use crate::types::{ AsyncPersister, ChainMonitor, ChannelManager, DynStore, DynStoreRef, DynStoreWrapper, - GossipSync, Graph, HRNResolver, KeysManager, MessageRouter, OnionMessenger, PaymentStore, - PeerManager, PendingPaymentStore, SyncAndAsyncKVStore, + GossipSync, Graph, HRNResolver, HRNResolverInner, KeysManager, MessageRouter, OnionMessenger, + PaymentStore, PeerManager, PendingPaymentStore, SyncAndAsyncKVStore, }; use crate::wallet::persist::KVStoreWalletPersister; use crate::wallet::Wallet; @@ -1739,7 +1740,7 @@ fn build_with_store_internal( })?; } - let hrn_resolver; + let hrn_resolver_inner; let mut blip32_resolver = None; let runtime_handle = runtime.handle(); @@ -1751,7 +1752,7 @@ fn build_with_store_internal( HRNResolverConfig::Blip32 => { let hrn_res = Arc::new(LDKOnionMessageDNSSECHrnResolver::new(Arc::clone(&network_graph))); - hrn_resolver = HRNResolver::Onion(Arc::clone(&hrn_res)); + hrn_resolver_inner = HRNResolverInner::Onion(Arc::clone(&hrn_res)); blip32_resolver = Some(Arc::clone(&hrn_res)); hrn_res as Arc @@ -1766,7 +1767,7 @@ fn build_with_store_internal( BuildError::DNSResolverSetupFailed })?; let hrn_res = Arc::new(DNSHrnResolver(addr)); - hrn_resolver = HRNResolver::Local(hrn_res); + hrn_resolver_inner = HRNResolverInner::Local(hrn_res); if *enable_hrn_resolution_service { if let Err(_) = may_announce_channel(&config) { @@ -1790,6 +1791,13 @@ fn build_with_store_internal( }, }; + let http_hrn_resolver = if config.hrn_config.enable_lnurl_resolution { + Some(Arc::new(HTTPHrnResolver::new())) + } else { + None + }; + let hrn_resolver = HRNResolver::new(hrn_resolver_inner, http_hrn_resolver); + // Initialize the PeerManager let onion_messenger: Arc = if let Some(AsyncPaymentsRole::Server) = async_payments_role { diff --git a/src/config.rs b/src/config.rs index 014d6216a..0950ebc06 100644 --- a/src/config.rs +++ b/src/config.rs @@ -266,6 +266,18 @@ pub struct HumanReadableNamesConfig { /// * **DNS Server**: `8.8.8.8:53` (Google Public DNS) /// * **Resolution Service**: Disabled (`false`) pub resolution_config: HRNResolverConfig, + /// If set to true, enables resolving [LNURL-pay] links and the LN-Address LNURL fallback + /// via HTTP, in addition to the BIP 353 / bLIP-32 resolution method configured in + /// [`resolution_config`]. + /// + /// **Default:** `false` + /// + /// **Note:** Enabling this may reveal our IP address to the recipient and information about + /// who we're paying to the LNURL endpoint we're querying. + /// + /// [LNURL-pay]: https://github.com/lnurl/luds/blob/luds/06.md + /// [`resolution_config`]: Self::resolution_config + pub enable_lnurl_resolution: bool, } impl Default for HumanReadableNamesConfig { @@ -276,6 +288,7 @@ impl Default for HumanReadableNamesConfig { .expect("Socket address conversion failed."), enable_hrn_resolution_service: false, }, + enable_lnurl_resolution: false, } } } diff --git a/src/types.rs b/src/types.rs index 06e65fbd0..fdab5d1ac 100644 --- a/src/types.rs +++ b/src/types.rs @@ -17,6 +17,7 @@ use bitcoin_payment_instructions::dns_resolver::DNSHrnResolver; use bitcoin_payment_instructions::hrn_resolution::{ HrnResolutionFuture, HrnResolver, HumanReadableName, LNURLResolutionFuture, }; +use bitcoin_payment_instructions::http_resolver::HTTPHrnResolver; use bitcoin_payment_instructions::onion_message_resolver::LDKOnionMessageDNSSECHrnResolver; use lightning::chain::chainmonitor; use lightning::impl_writeable_tlv_based; @@ -329,37 +330,85 @@ pub(crate) type OnionMessenger = lightning::onion_message::messenger::OnionMesse >; #[derive(Clone)] -pub enum HRNResolver { +pub enum HRNResolverInner { Onion(Arc, Arc>>), Local(Arc), } +#[derive(Clone)] +pub struct HRNResolver { + inner: HRNResolverInner, + http: Option>, +} + +impl HRNResolver { + pub(crate) fn new(inner: HRNResolverInner, http: Option>) -> Self { + Self { inner, http } + } +} + impl HrnResolver for HRNResolver { fn resolve_hrn<'a>(&'a self, hrn: &'a HumanReadableName) -> HrnResolutionFuture<'a> { - match self { - HRNResolver::Onion(inner) => inner.resolve_hrn(hrn), - HRNResolver::Local(inner) => inner.resolve_hrn(hrn), - } + Box::pin(async move { + let primary = match &self.inner { + HRNResolverInner::Onion(inner) => inner.resolve_hrn(hrn).await, + HRNResolverInner::Local(inner) => inner.resolve_hrn(hrn).await, + }; + match (primary, &self.http) { + (Ok(r), _) => Ok(r), + (Err(_), Some(http)) => http.resolve_hrn(hrn).await, + (Err(e), None) => Err(e), + } + }) } fn resolve_lnurl<'a>(&'a self, url: &'a str) -> HrnResolutionFuture<'a> { - match self { - HRNResolver::Onion(inner) => inner.resolve_lnurl(url), - HRNResolver::Local(inner) => inner.resolve_lnurl(url), - } + Box::pin(async move { + let primary = match &self.inner { + HRNResolverInner::Onion(inner) => inner.resolve_lnurl(url).await, + HRNResolverInner::Local(inner) => inner.resolve_lnurl(url).await, + }; + match (primary, &self.http) { + (Ok(r), _) => Ok(r), + (Err(_), Some(http)) => http.resolve_lnurl(url).await, + (Err(e), None) => Err(e), + } + }) } fn resolve_lnurl_to_invoice<'a>( &'a self, callback_url: String, amount: BPIAmount, expected_description_hash: [u8; 32], ) -> LNURLResolutionFuture<'a> { - match self { - HRNResolver::Onion(inner) => { - inner.resolve_lnurl_to_invoice(callback_url, amount, expected_description_hash) - }, - HRNResolver::Local(inner) => { - inner.resolve_lnurl_to_invoice(callback_url, amount, expected_description_hash) - }, - } + Box::pin(async move { + let primary = match &self.inner { + HRNResolverInner::Onion(inner) => { + inner + .resolve_lnurl_to_invoice( + callback_url.clone(), + amount, + expected_description_hash, + ) + .await + }, + HRNResolverInner::Local(inner) => { + inner + .resolve_lnurl_to_invoice( + callback_url.clone(), + amount, + expected_description_hash, + ) + .await + }, + }; + match (primary, &self.http) { + (Ok(r), _) => Ok(r), + (Err(_), Some(http)) => { + http.resolve_lnurl_to_invoice(callback_url, amount, expected_description_hash) + .await + }, + (Err(e), None) => Err(e), + } + }) } }