NIP-2F | Serverless Two-Factor Key Login via NIP-05 and Blossom
NIP-2F
Serverless Two-Factor Key Login via NIP-05 and Blossom
draft optional
This NIP defines a serverless, self-custodial login flow with an email-and-password style user experience. A user's [NIP-49](49.md) encrypted secret key (ncryptsec) is stored as a public blob on one or more Blossom servers and discovered via the user's [NIP-05](05.md) identifier. The blob is encrypted under a passphrase derived from two factors: a user-chosen password and a high-entropy shared secret held in an RFC 6238 authenticator app or backup QR code. No server participates in authentication, no party ever custodies the key, and all decryption happens client-side.
Discovery
The /.well-known/nostr.json response defined in [NIP-05](05.md) is extended with an OPTIONAL ncryptsec field mapping public keys to blob locations:
{
"names": {
"alice": "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9"
},
"relays": {
"b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9": [ "wss://relay.example.com" ]
},
"ncryptsec": {
"b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9": {
"sha256": "5d2899290e0e69bcd809949ee516a4a1597205390878f780c098707a7f18e3df",
"servers": [
"https://blossom.example.com",
"https://cdn.mirror.example.net"
]
}
}
}sha256is the hash of the blob content, used as the Blossom blob identifier.serversis an ordered list of Blossom servers hosting the blob.
The blob content MUST be the UTF-8 encoded bech32 ncryptsec1... string as defined in [NIP-49](49.md). Because the blob content is deterministic, every mirror serves a byte-identical blob with the same hash, and clients can verify integrity by hashing what they receive.
Key Derivation
Two factors are combined into the NIP-49 passphrase:
password— the user's chosen password (any Unicode string, normalized per NIP-49).S— a randomly generated shared secret of at least 20 bytes (160 bits), encoded in RFC 4648 base32 without padding. This is the same format used by RFC 6238 authenticator apps.
passphrase = password || ":" || base32(S)The resulting passphrase is passed to NIP-49, which applies its own scrypt key derivation. Clients MUST use a NIP-49 log_n of at least 16.
The secret S serves double duty:
- It is a true second factor with enough entropy (≥160 bits) to make offline brute force of the public blob computationally infeasible regardless of password strength.
- It can be enrolled into any RFC 6238 authenticator app via a standard
otpauth://QR code, letting the same secret also generate ordinary TOTP codes for the user's other services or for future companion protocols. Apps that support secret export (e.g. Aegis) can reproduceSon demand; clients SHOULD additionally offer a printable backup QR code containingSat setup time.
Setup Flow
- Client generates a Nostr keypair (
nsec/npub). - Client generates the shared secret
Sand displays it as anotpauth://QR code and as a printable backup QR/text string. The user MUST confirm they have stored it before proceeding. - User chooses a password.
- Client derives the passphrase as above and encrypts the
nsecinto anncryptsecper NIP-49. - Client uploads the blob to one or more Blossom servers.
- Client (or the user's NIP-05 provider) publishes the
ncryptsecentry in the domain'snostr.json.
The user retains three things: their NIP-05 address, their password, and their secret S (in an authenticator app and/or backup QR).
Login Flow
- User enters their NIP-05 identifier (e.g.
[email protected]). - Client fetches
/.well-known/nostr.json?name=alice, obtaining the pubkey, blob hash, and server list. - Client fetches the blob from each listed Blossom server in order until one responds, then verifies the content hashes to the published
sha256. - User enters their password and provides
S(by scanning their backup QR, pasting the base32 string, or exporting from their authenticator app). - Client derives the passphrase and decrypts the
ncryptseclocally per NIP-49. - The
nsecis available in memory; clients SHOULD immediately re-secure it (e.g. device keychain, NIP-46 bunker) rather than persisting it in plain form.
No server receives the password, the secret, or the derived passphrase at any point.
Redundancy
Because the blob is deterministic and content-addressed, any number of Blossom servers can mirror it with zero additional trust assumptions. Clients SHOULD attempt every listed server before failing. Users SHOULD keep an offline copy of their ncryptsec string as a final fallback, since it is fully public data.
Security Considerations
- Offline brute force. The public blob is protected by the combined entropy of the password and
S. SinceSalone contributes ≥160 bits, offline attacks against the blob are infeasible even with a weak password. The password's role is to protect against theft ofS(e.g. a compromised authenticator backup), preserving true two-factor security. - Why not per-login TOTP codes. A static ciphertext cannot be bound to a rotating code: whatever code was used at encryption time is fixed in the ciphertext forever, so a rotating window neither enables later decryption (the user's app shows a different current code) nor prevents offline cracking (the attacker targets the one baked-in code, which adds only ~20 bits). This NIP therefore uses the shared secret itself as the second factor. Serverless designs cannot achieve online-only verification; they can only maximize offline cost, which this construction does.
- Server compromise. Blossom servers and NIP-05 hosts hold only public data. A malicious NIP-05 host could serve a wrong hash or server list, causing denial of service but never key disclosure. Clients MAY pin the blob hash after first login.
- Loss of factors. Losing both the password and
Smakes the key unrecoverable. Losing either one alone is also unrecoverable by design; clients MUST make the backup step in setup explicit and mandatory. - Phishing. A malicious client can capture both factors at login. This matches the threat model of every existing password-based login and is out of scope.
Compatibility
No changes are required to relay software, Blossom server implementations, or the NIP-49 encryption format. NIP-05 servers need only serve one additional JSON field. All new behavior is client-side.