Skip to content

feat: NIP-PB — Password-Bound Key Backup#385

Open
tlongwell-block wants to merge 2 commits intomainfrom
feat/nip-pb-password-bound-key-backup
Open

feat: NIP-PB — Password-Bound Key Backup#385
tlongwell-block wants to merge 2 commits intomainfrom
feat/nip-pb-password-bound-key-backup

Conversation

@tlongwell-block
Copy link
Copy Markdown
Collaborator

@tlongwell-block tlongwell-block commented Apr 22, 2026

NIP-PB: Password-Bound Key Backup

Minimal protocol for backing up a Nostr private key to any relay using just a password. One blob, one scrypt call, no sharding.

The problem

NIP-49 ncryptsec1 is safe for local storage but dangerous on relays. An attacker who dumps a relay can identify every encrypted backup by its ncryptsec1 prefix, giving them a list of targets. For each password guess, the attacker gets N chances — the probability of cracking at least one user grows with N, and the cost per successful crack drops to |passwords| × scrypt_cost / N. NIP-49 itself warns against relay publishing for exactly this reason.

The solution

Mix the user's public key into the scrypt input. Now each password guess is bound to one target pubkey. To test a password, the attacker must already know which user they're targeting. One guess, one user. To attack all users: |users| × |passwords| × scrypt.

The backup blob is published as a kind:30078 event signed by a throwaway key derived from the password. No field references the user's real pubkey. Recovery requires the password, the public key, and a relay URL.

What's in this PR

  • crates/sprout-core/src/backup/NIP-PB.md — complete protocol spec (~450 lines)

Spec only. Implementation and demo script are follow-ups.

Key properties

  • Accumulation resistance — each password guess bound to one pubkey via KDF input
  • Unlinkability — throwaway signing key, no reference to real identity
  • Cross-relay unlinkability — relay URL mixed into HKDF info strings
  • Post-quantum confidentiality — entire confidentiality chain is symmetric/hash-based (scrypt → HKDF-SHA256 → XChaCha20-Poly1305). 128-bit entropy floor provides ~64-bit quantum security under Grover's algorithm
  • No relay trust required — worst-case adversary (relay operator with full logs, known pubkey, all blobs) pays 2^(entropy−1) scrypt calls to recover nsec
  • One scrypt call for both backup and recovery
  • Stronger than Bitcoin wallet encryption — scrypt at N=2^20 (1 GiB) is 64× more memory per guess than BIP-38 (N=2^14, 16 MiB), and orders of magnitude stronger than Bitcoin Core wallet.dat or Electrum encryption

Design decisions

  • Single blob, not sharded. Accumulation resistance comes entirely from pubkey ∈ scrypt_input, not from sharding. NIP-PB keeps the core security property and drops the complexity.
  • scrypt at N=2^20 (1 GiB). Aggressive but appropriate for a publicly-available encrypted blob facing offline attack forever. Works on modern phones (≥4 GB RAM native) and all desktops. Web/WASM clients on constrained devices should use NIP-AB for key transfer instead.
  • 128-bit entropy floor. 10-word EFF passphrase (129 bits) costs a nation-state with 10,000 H100 GPUs ~$10^32 and ~10^25 years to crack. Even 7 words (90 bits) would take 68 trillion years at that scale. The 128-bit floor adds quantum insurance (~65-bit Grover security).
  • Exact-address recovery queries (authors + #d). Eliminates d-tag squatting without pagination complexity.
  • kind:30078 (NIP-78 application-specific data). Sharing the kind with other app data provides ambient cover.
  • Verify-before-delete on rotation. New blob must be confirmed retrievable before old blob deletion events are published.

Adversarial review

Developed through iterative crossfire review across 4 model families:

  • Codex (GPT-5.4): 6.9 → 8.5/10 after fixes
  • Claude Opus: 8/10, APPROVE_WITH_NOTES
  • Gemini 3.1 Pro: Crypto 9/10, accumulation resistance 10/10

All three confirm: cryptographic construction is sound, accumulation resistance is mathematically valid, novel in the NIP ecosystem. Remaining reviewer requests are scope/product decisions (version indicator, entropy estimation precision, URL conformance suite) that were consciously declined.

Relationship to NIP-SB (#373)

NIP-PB is an independent, simpler alternative to NIP-SB. Both provide accumulation resistance via the same mechanism (pubkey-bound KDF). NIP-SB additionally provides fault tolerance (RS P=2) and steganographic cover at the cost of significantly more complexity (900+ lines). NIP-PB is the distillation: same crypto, honest threat model, ~200 lines instead of 906.

Test vector

Full KDF-chain interop test vector with normative hex values at log_n=20: password → base → H → enc_key → d_tag → sign_key → sign_pk → nonce → ciphertext → base64 content.

Add NIP-PB, a minimal protocol for backing up a Nostr private key to
any relay using just a password. One blob, one scrypt call, no sharding.

The core security property: each password guess is bound to one pubkey,
eliminating the accumulation attack that makes relay-published encrypted
keys dangerous. Confidentiality chain is entirely symmetric/hash-based
(scrypt → HKDF-SHA256 → XChaCha20-Poly1305), providing post-quantum
confidentiality with sufficient password entropy.

Includes full KDF-chain interop test vector with normative hex values.

Spec only — implementation, Tamarin proof, and demo are follow-ups.
Six wording fixes from adversarial crossfire review (Codex, Opus, Gemini).
Zero construction changes — crypto, parameters, and event format untouched.

- Fix NIP-49 accumulation attack description (Motivation + Security Analysis)
- Clarify AAD convention rationale
- State three-factor recovery requirement, recommend storing relay URLs
- Add verify-before-delete safety for password rotation
- Clarify tag validation: extra tags ignored
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant