We added NIP-05 resolution to Sparrow Wallet so you can send bitcoin to a Nostr identity like _@bushbashjapan.fyi — and it resolves to a Silent Payment address from the recipient's Nostr profile. No more copying and pasting 118-character sp1... addresses.
We also added Silent Payment address support to Jumble, a Nostr client, so users can publish their SP address in their kind 0 profile and share it via QR code.
Silent Payments (BIP 352) give Bitcoin users reusable, private addresses — but the addresses are long and unwieldy. BIP 353 solves this with DNS-based human-readable names (user@domain), but it requires domain owners to configure DNSSEC-signed TXT records containing bitcoin: URIs. That's a non-trivial DNS setup.
NIP-05 also uses the user@domain format and also requires a domain — but the barrier is lower. You only need to serve a static nostr.json file, and many Nostr services provide NIP-05 identities for free. More importantly, NIP-05 separates identity verification (the domain) from payment data (Nostr relays). Your SP address lives in your Nostr profile, not in DNS records — so you can update it anytime without touching DNS configuration.
Millions of Nostr users already have NIP-05 identities and publish profile metadata (kind 0 events) that can carry arbitrary fields — including a Silent Payment address.
The missing piece: no wallet connects these two systems.
When a user types user@domain into Sparrow's send field, the resolution chain is:
user@domain
│
▼
┌─────────────────────┐
│ BIP 353 (DNS) │──── Found? ──→ Use it (existing Sparrow behavior)
│ DNS TXT record │
└─────────────────────┘
│ Not found
▼
┌─────────────────────┐
│ NIP-05 (Nostr) │
│ 1. GET nostr.json │
│ 2. Extract pubkey │
│ 3. Query relays │
│ 4. Read kind 0 │
│ 5. Extract "sp" │
└─────────────────────┘
│
▼
Silent Payment Address (sp1...)
│
▼
Existing Sparrow SP send flow
BIP 353 is checked first. NIP-05 only fires as a fallback if DNS doesn't resolve. This respects the existing standard while extending reach to every Nostr user with an SP address in their profile.
- HTTP lookup:
GET https://domain/.well-known/nostr.json?name=user→ returns the user's hex pubkey - Relay discovery: Check
nostr.jsonfor relay hints; fall back to popular relays (purplepag.es,relay.damus.io,nos.lol,relay.nostr.band) if none provided - Profile fetch: Open a WebSocket to relays, send a Nostr
REQfor kind 0 events filtered by the pubkey - Signature verification: Verify the event's BIP-340 Schnorr signature against the pubkey (see Security section)
- SP extraction: Parse the
"sp"field from the profile metadata JSON - Address parsing: Validate and parse as a
SilentPaymentAddress, feed into Sparrow's existing SP send flow
Sparrow (wallet side) — Fork: BoltTouring/sparrow
Modified PaymentController.java to add NIP-05 as a fallback in the address resolution chain. When the DNS Service succeeds with an empty result or fails, tryNip05Resolution() is called. It checks a Caffeine cache first, then launches an async Nip05PaymentService that runs the full resolution on a background thread.
The resolved SP address is fed into the existing setSilentPaymentAddress() method — so the entire downstream flow (UTXO selection, transaction building, signing) works unchanged with zero modifications.
Drongo (core library) — Fork: BoltTouring/drongo
Added a nip05 package with four classes:
Nip05Resolver— Core resolution logic using Java's built-inHttpClientandWebSocket(no external Nostr library needed). JSON parsing uses regex patterns to avoid adding a JSON dependency to Drongo. Includes full Schnorr signature verification of Nostr events.Nip05Payment— Data record:(hrn, spAddress, nostrPubkey)Nip05PaymentCache— Caffeine cache with 1-hour TTLNip05Exception— Custom exception type Jumble (Nostr client) — Fork: BoltTouring/jumble Addedspfield support to kind 0 profiles:- Display SP address on profiles with truncation (matches npub display format)
- Copy button for the full SP address
- QR code dialog (responsive: Dialog on desktop, Drawer on mobile)
- Profile editor field with
sp1prefix validation
- BIP 353 first, NIP-05 fallback — Respects the DNS standard. NIP-05 only activates when DNS comes back empty.
- Fallback relays — Most
nostr.jsonfiles don't include relay hints, so we maintain a list of well-known relays to query. - No external Nostr library — Used Java's built-in HTTP and WebSocket APIs to avoid adding dependencies to Drongo's module system.
- Minimal scope — Only touches the send flow. No Nostr contacts integration, no deep protocol coupling.
- Pattern matching existing code — The
Nip05PaymentServicemirrors the existingDnsPaymentResolverpattern (async JavaFX Service, Caffeine cache, property binding, context menu).
Relays are untrusted intermediaries. A malicious relay could serve a fake kind 0 event with a substituted SP address, redirecting funds to an attacker. To prevent this, the resolver cryptographically verifies every Nostr event before trusting the sp field:
- Pubkey match — The event's
pubkeymust match the pubkey returned by the NIP-05 lookup - Event ID verification — The event ID is recomputed from the canonical NIP-01 serialization (
[0, pubkey, created_at, kind, tags, content]) and compared to the claimed ID. Unicode escapes are normalized to ensure consistent hashing across different relay implementations. - Schnorr signature verification — The event's
sigis verified as a valid BIP-340 Schnorr signature over the event ID using Sparrow's existing libsecp256k1 bindings If any check fails, the event is rejected and the resolution aborts. A relay cannot forge a valid signature without the user's Nostr private key. Trust model: The NIP-05 identity lookup (nostr.json) relies on HTTPS/TLS. If an attacker compromises the domain, they could map a username to their own pubkey — which would then resolve to their own (legitimately signed) profile with their SP address. This is the same trust model as email and the web: you trust the domain to tell the truth about who owns a username. BIP 353 is stronger here because DNSSEC provides a cryptographic proof chain, which is why it's checked first.
Any Nostr user can now receive bitcoin privately by:
- Generating a Silent Payment address in any SP-capable wallet
- Adding it to their Nostr profile's
spfield (via Jumble or any client that supports it) - Sharing their NIP-05 identity (
user@domain) Senders using Sparrow just type the NIP-05 address — no need to handle raw SP addresses, no DNS configuration required on the recipient's side.
Unit tests cover all parsing logic (pubkey extraction, relay extraction, SP address extraction, event content parsing with unicode). A live integration test resolves _@bushbashjapan.fyi end-to-end — from NIP-05 lookup through relay query to SP address extraction, with full Schnorr signature verification.
# Run all NIP-05 tests
./gradlew :drongo:test --tests "com.sparrowwallet.drongo.nip05.Nip05ResolverTest"
# Run live integration test
./gradlew :drongo:test --tests "com.sparrowwallet.drongo.nip05.Nip05ResolverTest.liveResolveTest"
# Launch Sparrow with isolated data directory
./gradlew run --args="--dir /tmp/sparrow-test"- Sparrow fork: BoltTouring/sparrow
- Drongo fork: BoltTouring/drongo
- Jumble fork: BoltTouring/jumble (PR submitted to upstream)
- Needs wallets like Sparrow to implement this lookup
- Encourage other Nostr clients to support the
spfield in kind 0 profiles - Once Silent Payments proliferates, never cut and paste an on-chain address again.