diff --git a/Cargo.lock b/Cargo.lock index d6e68b6..0245759 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1176,7 +1176,7 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ - "bitcoin_hashes 0.12.0", + "bitcoin_hashes 0.14.100", "serde", "unicode-normalization", ] @@ -4087,6 +4087,7 @@ dependencies = [ "qrcodegen", "rand 0.9.2", "regex", + "reqwest 0.12.28", "rfd", "ring 0.16.20", "rkv", @@ -4456,7 +4457,7 @@ dependencies = [ "log", "rand 0.6.5", "regex", - "reqwest", + "reqwest 0.10.10", "ring 0.16.20", "serde", "serde_derive", @@ -5034,6 +5035,22 @@ dependencies = [ "tokio-rustls 0.23.4", ] +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "rustls 0.23.40", + "tokio 1.49.0", + "tokio-rustls 0.26.4", + "tower-service", + "webpki-roots 1.0.7", +] + [[package]] name = "hyper-socks2" version = "0.9.1" @@ -5097,13 +5114,16 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64 0.22.1", "bytes 1.11.1", "futures-channel", "futures-util", "http 1.4.0", "http-body 1.0.1", "hyper 1.8.1", + "ipnet", "libc", + "percent-encoding", "pin-project-lite 0.2.16", "socket2 0.6.2", "tokio 1.49.0", @@ -6004,6 +6024,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "malloc_buf" version = "0.0.6" @@ -7974,6 +8000,61 @@ dependencies = [ "serde", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes 1.11.1", + "cfg_aliases", + "pin-project-lite 0.2.16", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls 0.23.40", + "socket2 0.6.2", + "thiserror 2.0.18", + "tokio 1.49.0", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes 1.11.1", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring 0.17.14", + "rustc-hash 2.1.1", + "rustls 0.23.40", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "0.6.13" @@ -8505,6 +8586,44 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes 1.11.1", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.9", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite 0.2.16", + "quinn", + "rustls 0.23.40", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio 1.49.0", + "tokio-rustls 0.26.4", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.7", +] + [[package]] name = "resvg" version = "0.45.1" @@ -8904,6 +9023,7 @@ version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ + "web-time", "zeroize", ] @@ -9851,6 +9971,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synchronoise" @@ -11628,6 +11751,45 @@ dependencies = [ "tor-memquota", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite 0.2.16", + "sync_wrapper", + "tokio 1.49.0", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.10.0", + "bytes 1.11.1", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite 0.2.16", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index 16828b5..ec09125 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,10 @@ tokio-tungstenite = { version = "0.26", features = ["rustls-tls-webpki-roots"] } regex = "1" base64 = "0.22" hex = "0.4" +## clearnet HTTP for the public NIP-05 username lookup (rustls, no native TLS +## so it cross-compiles to Android) — read-only name→key, kept off the mixnet +## on purpose because it's a "simple API lookup" and speed matters there. +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } ## tor arti-client = { version = "0.42.0", features = ["static", "pt-client", "onion-service-service", "onion-service-client"] } diff --git a/src/nostr/nip05.rs b/src/nostr/nip05.rs index f069152..73c14e7 100644 --- a/src/nostr/nip05.rs +++ b/src/nostr/nip05.rs @@ -68,6 +68,22 @@ fn is_valid_hostname(d: &str) -> bool { }) } +/// A plain clearnet HTTPS GET (rustls). Used only for the public, read-only +/// NIP-05 username lookups: those carry no secret and don't need anonymity, +/// and routing them over Tor/the mixnet is exactly what made choosing a name +/// feel slow. Returns (status, body). +async fn clearnet_get(url: &str) -> Option<(u16, String)> { + let client = reqwest::Client::builder() + .user_agent("goblin-wallet") + .timeout(std::time::Duration::from_secs(8)) + .build() + .ok()?; + let resp = client.get(url).send().await.ok()?; + let status = resp.status().as_u16(); + let body = resp.text().await.ok()?; + Some((status, body)) +} + /// Resolve a NIP-05 identifier (user@domain) to a pubkey + relay hints. pub async fn resolve(name: &str, domain: &str) -> Option { let url = format!( @@ -75,7 +91,11 @@ pub async fn resolve(name: &str, domain: &str) -> Option { domain, urlencode(name) ); - let body = Tor::http_request("GET", url, None, vec![]).await?; + // Fast clearnet lookup first; fall back to Tor if clearnet is blocked. + let body = match clearnet_get(&url).await { + Some((200, b)) => b, + _ => Tor::http_request("GET", url, None, vec![]).await?, + }; parse_well_known(&body, name) } @@ -123,8 +143,13 @@ pub async fn check_availability(server: &str, name: &str) -> Availability { server.trim_end_matches('/'), urlencode(name) ); - let Some(body) = Tor::http_request("GET", url, None, vec![]).await else { - return Availability::Unknown; + // Fast clearnet lookup first; fall back to Tor if clearnet is blocked. + let body = match clearnet_get(&url).await { + Some((200, b)) => b, + _ => match Tor::http_request("GET", url, None, vec![]).await { + Some(b) => b, + None => return Availability::Unknown, + }, }; let Ok(doc) = serde_json::from_str::(&body) else { return Availability::Unknown;