Sfoglia il codice sorgente

Add `resolve_dns_txt` to HttpTransport and MintConnector

Fixes #1036
Cesar Rodas 2 mesi fa
parent
commit
378896e286

+ 4 - 0
crates/cdk-integration-tests/src/init_pure_tests.rs

@@ -55,6 +55,10 @@ impl Debug for DirectMintConnection {
 /// Convert the requests and responses between the [String] and [Uuid] variants as necessary.
 #[async_trait]
 impl MintConnector for DirectMintConnection {
+    async fn resolve_dns_txt(&self, _domain: &str) -> Result<Vec<String>, Error> {
+        panic!("Not implemented");
+    }
+
     async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error> {
         Ok(self.mint.pubkeys().keysets)
     }

+ 4 - 0
crates/cdk/examples/mint-token-bolt12-with-custom-http.rs

@@ -40,6 +40,10 @@ impl Default for CustomHttp {
 #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
 impl HttpTransport for CustomHttp {
+    async fn resolve_dns_txt(&self, _domain: &str) -> Result<Vec<String>, Error> {
+        panic!("Not implemented");
+    }
+
     fn with_proxy(
         &mut self,
         _proxy: Url,

+ 13 - 28
crates/cdk/src/bip353.rs

@@ -6,10 +6,11 @@
 
 use std::collections::HashMap;
 use std::str::FromStr;
+use std::sync::Arc;
 
 use anyhow::{bail, Result};
-use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
-use trust_dns_resolver::TokioAsyncResolver;
+
+use crate::wallet::MintConnector;
 
 /// BIP-353 human-readable Bitcoin address
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -37,35 +38,19 @@ impl Bip353Address {
     /// - No Bitcoin URI is found
     /// - Multiple Bitcoin URIs are found (BIP-353 requires exactly one)
     /// - The URI format is invalid
-    pub(crate) async fn resolve(self) -> Result<PaymentInstruction> {
+    pub(crate) async fn resolve(
+        self,
+        client: &Arc<dyn MintConnector + Send + Sync>,
+    ) -> Result<PaymentInstruction> {
         // Construct DNS name according to BIP-353
         let dns_name = format!("{}.user._bitcoin-payment.{}", self.user, self.domain);
 
-        // Create a new resolver with DNSSEC validation
-        let mut opts = ResolverOpts::default();
-        opts.validate = true; // Enable DNSSEC validation
-
-        let resolver = TokioAsyncResolver::tokio(ResolverConfig::default(), opts);
-
-        // Query TXT records - with opts.validate=true, this will fail if DNSSEC validation fails
-        let response = resolver.txt_lookup(&dns_name).await?;
-
-        // Extract and concatenate TXT record strings
-        let mut bitcoin_uris = Vec::new();
-
-        for txt in response.iter() {
-            let txt_data: Vec<String> = txt
-                .txt_data()
-                .iter()
-                .map(|bytes| String::from_utf8_lossy(bytes).into_owned())
-                .collect();
-
-            let concatenated = txt_data.join("");
-
-            if concatenated.to_lowercase().starts_with("bitcoin:") {
-                bitcoin_uris.push(concatenated);
-            }
-        }
+        let bitcoin_uris = client
+            .resolve_dns_txt(&dns_name)
+            .await?
+            .into_iter()
+            .filter(|txt_data| txt_data.to_lowercase().starts_with("bitcoin:"))
+            .collect::<Vec<_>>();
 
         // BIP-353 requires exactly one Bitcoin URI
         match bitcoin_uris.len() {

+ 1 - 1
crates/cdk/src/wallet/melt/melt_bip353.rs

@@ -66,7 +66,7 @@ impl Wallet {
         let address_string = address.to_string();
 
         // Resolve the address to get payment instructions
-        let payment_instructions = address.resolve().await.map_err(|e| {
+        let payment_instructions = address.resolve(&self.client).await.map_err(|e| {
             tracing::error!(
                 "Failed to resolve BIP353 address '{}': {}",
                 address_string,

+ 5 - 0
crates/cdk/src/wallet/mint_connector/http_client.rs

@@ -187,6 +187,11 @@ impl<T> MintConnector for HttpClient<T>
 where
     T: Transport + Send + Sync + 'static,
 {
+    #[instrument(skip(self), fields(mint_url = %self.mint_url))]
+    async fn resolve_dns_txt(&self, domain: &str) -> Result<Vec<String>, Error> {
+        self.transport.resolve_dns_txt(domain).await
+    }
+
     /// Get Active Mint Keys [NUT-01]
     #[instrument(skip(self), fields(mint_url = %self.mint_url))]
     async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error> {

+ 3 - 0
crates/cdk/src/wallet/mint_connector/mod.rs

@@ -28,6 +28,9 @@ pub type HttpClient = http_client::HttpClient<transport::Async>;
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
 pub trait MintConnector: Debug {
+    /// Resolve the DNS record getting the TXT value
+    async fn resolve_dns_txt(&self, domain: &str) -> Result<Vec<String>, Error>;
+
     /// Get Active Mint Keys [NUT-01]
     async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error>;
     /// Get Keyset Keys [NUT-01]

+ 34 - 0
crates/cdk/src/wallet/mint_connector/transport.rs

@@ -5,6 +5,10 @@ use cdk_common::AuthToken;
 use reqwest::Client;
 use serde::de::DeserializeOwned;
 use serde::Serialize;
+#[cfg(feature = "bip353")]
+use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
+#[cfg(feature = "bip353")]
+use trust_dns_resolver::TokioAsyncResolver;
 use url::Url;
 
 use super::Error;
@@ -14,6 +18,9 @@ use crate::error::ErrorResponse;
 #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
 pub trait Transport: Default + Send + Sync + Debug + Clone {
+    /// DNS resolver to get a TXT record from a domain name
+    async fn resolve_dns_txt(&self, domain: &str) -> Result<Vec<String>, Error>;
+
     /// Make the transport to use a given proxy
     fn with_proxy(
         &mut self,
@@ -102,6 +109,33 @@ impl Transport for Async {
         Ok(())
     }
 
+    /// DNS resolver to get a TXT record from a domain name
+    #[cfg(feature = "bip353")]
+    async fn resolve_dns_txt(&self, domain: &str) -> Result<Vec<String>, Error> {
+        // Create a new resolver with DNSSEC validation
+        let mut opts = ResolverOpts::default();
+        opts.validate = true; // Enable DNSSEC validation
+
+        Ok(TokioAsyncResolver::tokio(ResolverConfig::default(), opts)
+            .txt_lookup(domain)
+            .await
+            .map_err(|e| Error::Custom(e.to_string()))?
+            .into_iter()
+            .map(|txt| {
+                txt.txt_data()
+                    .iter()
+                    .map(|bytes| String::from_utf8_lossy(bytes).into_owned())
+                    .collect::<Vec<_>>()
+                    .join("")
+            })
+            .collect())
+    }
+
+    #[cfg(not(feature = "bip353"))]
+    async fn resolve_dns_txt(&self, _domain: &str) -> Result<Vec<String>, Error> {
+        Err(Error::Internal)
+    }
+
     async fn http_get<R>(&self, url: Url, auth: Option<AuthToken>) -> Result<R, Error>
     where
         R: DeserializeOwned,