1
0
forked from GRIN/grim

Build 47: username lookup over clearnet, off Tor

The read-only NIP-05 username lookups (resolve + check_availability) carry no
secret and need no anonymity — routing them over Tor is what made choosing a
username slow. They now use a fast clearnet HTTPS GET (reqwest + rustls/ring,
so it still cross-compiles to Android), falling back to Tor only if clearnet is
blocked. The anonymity-critical paths (slatepack delivery, NIP-98-signed ops)
are untouched.
This commit is contained in:
2ro
2026-06-13 12:40:36 -04:00
parent 1ac1186319
commit dda07dee0a
3 changed files with 196 additions and 5 deletions
Generated
+164 -2
View File
@@ -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"
+4
View File
@@ -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"] }
+28 -3
View File
@@ -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<Nip05Resolution> {
let url = format!(
@@ -75,7 +91,11 @@ pub async fn resolve(name: &str, domain: &str) -> Option<Nip05Resolution> {
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::<Value>(&body) else {
return Availability::Unknown;