NIP-K1 Compatible Gateway

Passkey login
for Nostr

An alternative passkey gateway for Nostr. Decentralize your key management across multiple providers. No single point of failure.

Two gateways, zero single points of failure

Your passkey-encrypted keys live on Nostr relays. The gateways just provide the WebAuthn rpId — and now there are two.

Cloudflare
keytr.org
Primary gateway operated by sovIT. Hosted on Cloudflare for global edge performance.
  • Hosting Cloudflare Pages
  • Operator sovIT
  • Status Active
GitHub Pages
nostkey.org
Alternative gateway hosted on GitHub Pages. Same protocol, different infrastructure. True decentralization.
  • Hosting GitHub Pages
  • Operator sovIT
  • Status Active
Why does this matter? WebAuthn passkeys are bound to the domain (rpId) they were created on. If keytr.org goes down or Cloudflare has an outage, passkeys registered against it can't authenticate. By registering passkeys against both gateways, you maintain access even if one provider fails. Each gateway produces a separate kind:30079 event on your relays.

How it works

nostkey.org is a fully compatible keytr gateway. Same protocol, same encryption, different domain.

1

Register passkeys on multiple gateways

Create passkeys on both keytr.org and nostkey.org for redundancy. Each produces an independent credential.

2

Encrypt & vault the key

Each passkey stores a unique random encryption key in its user.id field. Your nsec is encrypted separately for each credential.

3

Publish to relays

Each encrypted blob is published as a separate kind:30079 event. One tags rp: keytr.org, the other tags rp: nostkey.org.

4

Login from any gateway

On a new device, authenticate with whichever gateway is available. Cloudflare down? Use nostkey.org. GitHub down? Use keytr.org.

Architecture

Same encryption. Different infrastructure. True redundancy.

Your device
Passkey + biometric
Gateways (rpId)
keytr.org + nostkey.org
AES-256-GCM
Per-gateway encryption
Nostr relays
Multiple kind:30079 events
All cryptography happens client-side. Gateways are static files — they serve the /.well-known/webauthn endpoint and nothing else.

Security

Identical cryptography to keytr. Decentralization adds resilience, not complexity.

Encryption scheme

  • Key source Random 256-bit export key in WebAuthn user.id
  • Split knowledge Relay has ciphertext, passkey has key
  • Cipher AES-256-GCM with 12-byte random IV
  • AAD Credential ID (prevents substitution)

Multi-gateway benefits

  • No SPOF Two providers, two infrastructure stacks
  • Domain diversity Different registrars, DNS, hosting
  • Independent credentials Compromise of one gateway doesn't affect the other
  • Relay redundancy Separate events per gateway on your relays

Federated cross-client login

Both gateways support the same Related Origin Requests spec. Any compatible client works with either.

keytr.org
Cloudflare — primary gateway
nostkey.org
GitHub Pages — alternative gateway
your-domain.com
Run your own — anyone can
Register on both gateways for redundancy
// Register passkey against keytr.org (Cloudflare)
const result1 = await setupKeytr({
  userName: npub, rpId: "keytr.org"
})

// Register passkey against nostkey.org (GitHub Pages)
const result2 = await setupKeytr({
  userName: npub, rpId: "nostkey.org",
  nsecBytes: existingNsec  // reuse same identity
})

// Each credential produces a separate kind:30079 event
// with its own export key vaulted in user.id
// Lose access to one gateway? The other still works.

Run your own gateway

Three gateways are better than two. All you need is a domain and one file.

/.well-known/webauthn
{
  "origins": [
    "https://nostkey.org",
    "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.

The event — NIP-K1

Same event format as keytr. Fully compatible kind:30079 parameterized replaceable events.

kind:30079 (nostkey.org gateway)
{
  "kind": 30079,
  "content": "<base64 encrypted nsec blob>",
  "tags": [
    ["d", "<credential-id>"],
    ["rp", "nostkey.org"],
    ["algo", "aes-256-gcm"],
    ["scheme", "passkey-vault"],
    ["v", "1"]
  ]
}

The only difference from a keytr.org event is the rp tag. Each gateway's passkey holds its own export key — clients query for events matching the gateway they're authenticating against.

For client developers

Use nostkey.org as an rpId in your Nostr client. Same keytr library, different gateway.

Install
npm install keytr
Usage with nostkey.org gateway
import { setupKeytr, loginWithKeytr, fetchKeytrEvents } from 'keytr'

// Setup with nostkey.org gateway
const { credential, encryptedBlob, eventTemplate, nsecBytes, npub }
  = await setupKeytr({ userName: 'alice', rpId: 'nostkey.org' })

// Sign & publish the kind:30079 event to relays

// Login on new device: one biometric tap
const events = await fetchKeytrEvents(pubkey, relays)
const { nsecBytes, npub } = await loginWithKeytr(events[0])

To have your origin authorized for cross-client login via nostkey.org, add your domain to the origins list via PR.

Decentralize your Nostr keys

Register passkeys on both gateways. No single point of failure. No single provider to trust.