Encrypt your nsec with a passkey. Log in to any Nostr client with your fingerprint. No seed phrases. No clipboard. No extensions. Works with password managers too.
Copy-pasting nsec between devices leaks your identity to every app that reads the clipboard.
Browser extensions don't follow you across devices. Hardware signers add complexity.
Leak your nsec once and your Nostr identity is compromised permanently. There's no rotation.
Six steps from nsec chaos to passkey-protected login.
Create a new nsec or bring your existing one into any keytr-compatible client.
Your device creates a WebAuthn credential bound to a gateway (e.g. keytr.org) or the client's own domain.
keytr tries the passkey's PRF extension first. If the authenticator doesn't support PRF (e.g. 1Password, Bitwarden), it falls back to Key-in-Handle (KiH) mode, embedding a random encryption key in the passkey itself. Either way, the key goes through HKDF-SHA256 to derive an AES-256 encryption key.
The encrypted blob is published as a kind:31777 parameterized replaceable event to your Nostr relays.
On a new device, open any keytr-compatible client and tap "Login with Passkey".
Your passkey syncs via iCloud / Google / Windows — authenticate with your fingerprint or face to decrypt your nsec instantly.
No servers to trust. No passwords to remember. Just math and hardware.
Designed to make the right thing easy and the wrong thing impossible.
In PRF mode, the encryption key is hardware-bound and never leaves the authenticator. In KiH mode, a random 256-bit key is stored in the passkey's user.id field, protected by biometric/PIN. Either way, HKDF-SHA256 with a random salt produces a unique AES key per encryption. Even if the encrypted event is public on relays, it is useless without your passkey.
Your passkey-encrypted keys live on Nostr relays. The gateways just provide the WebAuthn rpId — and now there are two.
kind:31777 event on your relays.
One passkey works across every Nostr client. No single domain controls the protocol.
WebAuthn binds passkey outputs to the domain (rpId) they were created on. To enable cross-client login, keytr uses a federated gateway model built on the W3C Related Origin Requests spec.
Any domain can become a passkey gateway by hosting a /.well-known/webauthn file listing authorized client origins. Register passkeys against multiple gateways, producing a separate kind:31777 event for each. keytr.org runs on Cloudflare, nostkey.org runs on Hostinger — different providers, different infrastructure, zero single points of failure.
import { setup, discover, publishKeytrEvent } from '@sovit.xyz/keytr'
// Setup: tries PRF, falls back to KiH for password managers
const { eventTemplate, nsecBytes, mode } = await setup({
userName: 'alice', userDisplayName: 'Alice',
rpId: "keytr.org"
})
// mode: 'prf' (hardware-bound) or 'kih' (1Password, Bitwarden, etc.)
// Sign & publish
const signed = finalizeEvent(eventTemplate, nsecBytes)
await publishKeytrEvent(signed, relays)
// Discoverable login — auto-detects mode, 1 biometric prompt
const { nsecBytes, pubkey } = await discover(relays)
Decentralize the protocol further. All you need is a domain and one file.
{
"origins": [
"https://your-gateway.example",
"https://client-a.com",
"https://client-b.com"
]
}
Clients listed in your origins can register passkeys under your domain's rpId. The browser verifies authorization automatically via the Related Origin Requests spec.
Encrypted keys are stored as kind:31777 parameterized replaceable events.
{
"kind": 31777,
"content": "<base64 encrypted 93-byte blob>",
"tags": [
["d", "<credential-id-base64url>"],
["rp", "keytr.org"],
["algo", "aes-256-gcm"],
["kdf", "hkdf-sha256"],
["v", "1"],
["transports", "internal", "hybrid"],
["client", "<client-name>"]
]
}
Multiple passkeys can be registered — each produces a separate event with a different d tag. The v tag indicates the mode: 1 for PRF, 3 for KiH. transports and client tags are optional. Lose one passkey, your others still work.
Integrate keytr into your Nostr client in minutes.
npm install @sovit.xyz/keytr
import { setup, discover, publishKeytrEvent } from '@sovit.xyz/keytr'
// Setup: PRF-first with KiH fallback for password managers
const { eventTemplate, nsecBytes, npub, mode }
= await setup({ userName: 'alice', userDisplayName: 'Alice', rpId: 'keytr.org' })
// Sign & publish the kind:31777 event to relays
// Discoverable login: auto-detects PRF vs KiH, one biometric tap
const { nsecBytes, npub, pubkey } = await discover(relays)
To have your origin authorized for cross-client login, add your domain to the origins list via PR.
Open protocol. Open source. Open to everyone.