Owned by No One: Solving Nostr's Key Rotation Problem

npub1klkk3vrzme455yh9rl2jshq7rc8dpegj3ndf82c3ks2sk40dxt7qulx3vt
hex
4e824e4a67a9661566c269de2ffbdffa7887c2a03c9b88a44efb743f7b874e62nevent
nevent1qqsyaqjwffn6jes4vmpxnh30l00l57y8c2srexug5380kapl0wr5ucsprpmhxue69uhhyetvv9ujuem4d36kwatvw5hx6mm9qgst0mtgkp3du662ztj3l4fgts0purksu5fgek5n4vgmg9gt2hkn9lq9373c7naddr
naddr1qqgrydtrvyekywfev56nvdfjxsux2qgcwaehxw309aex2mrp0yhxwatvw4nh2mr49ekk7egzyzm7669svt0xkjsju50a22zurc0qa589z2xd4yatzx6p2z64a5e0cqcyqqq823clqdv8pKind-30023 (Article)
In Nostr, you are your key. Your identity belongs to you alone: any device holding your private key grants full access, and any relay holding your events stores your permanent record. The radical ownership comes with radical responsibility: lose your private key and you lose everything; get it compromised and an attacker becomes you.
The protocol needs key rotation, but previous proposals have struggled with complexity. They require pre-generated key chains, cryptographic commitments, or trusted hardware that most users postpone configuring until disaster strikes.
The social layer offers a more tractable solution than the cryptographic one.
The problem space
There are two distinct scenarios we need to handle:
- Key Compromise with Access: You discover your key is compromised, but you still have it
- Lost Keys: Your key is gone - device destroyed, forgotten, or otherwise inaccessible
Traditional cryptographic solutions fail the second case entirely. With no pre-committed recovery key, access is gone. But Nostr already has a powerful social graph. What if we used it?
Solution 1: the compromise declaration
When you discover your key is compromised but still have access, you can publish a special "compromise declaration" event. This is a loud, permanent, irreversible signal that your key should no longer be trusted.
The brilliance is in what happens next: you update your profile name to [COMPROMISED] YourName. If an attacker tries to remove it, you add it back. This creates a spam war that works in the victim's favor: the chaos of competing updates signals to everyone that something is wrong with this account.
Even if the attacker "wins" and controls the account, they're controlling an account that's been permanently marked as compromised. They've vandalized their own prize.
Solution 2: social key rotation
The second solution is more radical: anyone can propose that a key should rotate to a new key, and the community decides if it's legitimate through attestations.
Here's how it works:
- Proposal: Anyone (including you with a different key) publishes a rotation proposal
- Attestation: Your friends verify out-of-band (Signal, in-person, video call) and publish attestations
- Confidence: Clients calculate confidence based on who's attesting and how many
- Manual Action: Users see the attestations and manually decide whether to follow the new key
The key insight: if both the old AND new keys sign the proposal, that's strong evidence. But even without the old key (lost scenario), sufficient attestations from your social graph can establish legitimacy.
Why this works
These solutions embrace what Nostr does well: social signaling and community verification, applied as security primitives. They're:
- Simple: No pre-planning or complex setup required
- Social: Leverages existing trust relationships
- Permanent: Creates undeletable audit trails
- Backward Compatible: The profile name trick works on every client today
- Antifragile: Attacks make the compromise more visible, not less
The non-deletable event range
These proposals introduce events in kinds 65533-65535, which we're proposing as permanently non-deletable. Once you declare a key compromised or propose a rotation, that record stays forever. This prevents attackers from hiding their tracks and gives the community a permanent audit trail.
Moving forward
Perfect security is unattainable, and pursuing it often crowds out good-enough solutions. These proposals are practical and implementable, designed to work with human behavior as it exists. They turn Nostr's social layer into a security feature.
Key rotation doesn't need to be complex. Sometimes the best solution is to admit that humans are social creatures and build systems that embrace that reality.
NIP-101: Key compromise declaration
draft optional
This NIP defines a method for users to permanently declare that their private key has been compromised.
Abstract
When a Nostr user's private key (nsec) is compromised but they still have access to it, they can publish a special event to warn others that the key should no longer be trusted.
Specification
Compromise declaration event
A compromise declaration uses event kind 65535:
{
"id": "<32-bytes lowercase hex of the id>",
"pubkey": "<32-bytes lowercase hex of the compromised public key>",
"created_at": <unix timestamp>,
"kind": 65535,
"tags": [
["nonce", "<unique random value>"]
],
"content": "KEY COMPROMISED - DO NOT TRUST SIGNATURES AFTER THIS TIMESTAMP",
"sig": "<64-bytes lowercase hex of the signature>"
}
Users MAY include a Bitcoin block reference for proof-of-absence:
["block", "<bitcoin block hash>", "<block height>"]
Requirements
- Non-replaceable: Kind 65535 is a regular event (not replaceable)
- Non-deletable: Relays SHOULD ignore kind 5 deletion requests for kind 65535 events
- Unique nonce: Prevents replay attacks
OpenTimestamps Attestation
Users MAY publish a NIP-03 OpenTimestamps attestation (kind 1040) referencing the compromise declaration for cryptographic time proof.
Profile update convention
After publishing a compromise declaration, users SHOULD update their kind 0 metadata event to prepend [COMPROMISED] to their display name:
{
"id": "<32-bytes lowercase hex of the id>",
"pubkey": "<32-bytes lowercase hex of the public key>",
"created_at": <unix timestamp>,
"kind": 0,
"tags": [],
"content": "{\"name\":\"[COMPROMISED] Alice\",\"about\":\"Key compromised at block 850000\",\"picture\":\" https://example.com/pic.jpg\"}",
"sig": "<64-bytes lowercase hex of the signature>"
}
This creates a visible warning even on clients that don't support this NIP.
Client Implementation
Clients supporting this NIP:
- MUST check for kind 65535 events when displaying any user profile or content
- MUST show a prominent warning if a compromise declaration exists
- SHOULD display the compromise timestamp
- SHOULD NOT automatically unfollow compromised keys
Security Considerations
- Attackers self-sabotage by publishing compromise declarations
- The spam war over kind 0 updates alerts all users
- Services may run bots to maintain [COMPROMISED] profile names
NIP-102: Social key rotation
draft optional
This NIP defines a method for rotating Nostr keys through social attestation, enabling recovery from both compromised and lost keys.
Abstract
Users can rotate from an old key to a new key through community verification. Anyone can propose a rotation, and the community provides attestations to validate it. This works even when the original private key is lost.
Specification
Event Kinds
65534: Key rotation proposal (non-deletable)65533: Key rotation attestation (non-deletable)
Rotation proposal event
Anyone can publish a rotation proposal:
{
"id": "<32-bytes lowercase hex of the id>",
"pubkey": "<32-bytes lowercase hex of the proposer>",
"created_at": <unix timestamp>,
"kind": 65534,
"tags": [
["p", "<old pubkey>"],
["p", "<new pubkey>"],
["old", "<old pubkey>"],
["new", "<new pubkey>"]
],
"content": "Proposing key rotation for [reason]",
"sig": "<64-bytes lowercase hex of the signature>"
}
The proposer can be the old key, new key, or any third party.
Attestation Event
Community members publish attestations:
{
"id": "<32-bytes lowercase hex of the id>",
"pubkey": "<32-bytes lowercase hex of the attester>",
"created_at": <unix timestamp>,
"kind": 65533,
"tags": [
["e", "<rotation proposal event id>"],
["attestation", "confirm"] // or "reject"
],
"content": "Verified via [details]",
"sig": "<64-bytes lowercase hex of the signature>"
}
MAY include: ["method", "in-person|video-call|signal|telegram|other"]
The old and new keys can also publish attestations to strengthen the signal.
Client Implementation
Clients query for kind 65534 events and fetch associated kind 65533 attestations. Calculate weight based on:
- Both old+new keys attesting = highest confidence
- Attestations from follows > follows-of-follows > unknown
- More attestations = higher confidence
Never auto-follow rotated keys. Require explicit user confirmation.
Example Flow
Lost Key: Alice loses her key → creates proposal with any key → shares event ID via Signal → friends attest → others manually update follows
Compromised Key: Alice publishes proposal from old key → signs from new key → dual signature creates high confidence
Security Considerations
- No automatic actions prevent false rotations
- Social verification leverages existing trust
- Sybil resistance through web-of-trust weighting
- Non-deletable events create permanent audit trail
Relays SHOULD store kinds 65533-65535 permanently and ignore deletion requests.
Raw JSON
{
"kind": 30023,
"id": "4e824e4a67a9661566c269de2ffbdffa7887c2a03c9b88a44efb743f7b874e62",
"pubkey": "b7ed68b062de6b4a12e51fd5285c1e1e0ed0e5128cda93ab11b4150b55ed32fc",
"created_at": 1777543606,
"tags": [
[
"d",
"25ca3b99e565248e"
],
[
"image",
"https://image.nostr.build/8b714dc3fb77e0589f71e31c1df89b25711f82dc73024bb12ad4f3c5b8c6d218.jpg"
],
[
"title",
"Owned by No One: Solving Nostr's Key Rotation Problem"
],
[
"summary",
"Nostr's greatest strength - that you own your identity through cryptographic keys - becomes its greatest weakness when keys are lost or stolen. This post introduces two simple, social solutions that let users mark compromised keys and rotate to new ones through community verification, without complex cryptography or pre-planning."
],
[
"published_at",
"1763653902"
],
[
"t",
"nostr"
],
[
"t",
"nip"
],
[
"t",
"self-sovereignty"
],
[
"t",
"wot"
],
[
"t",
"cryptography"
],
[
"t",
"decentralization"
],
[
"t",
"austrian-economics"
],
[
"t",
"freedom-tech"
],
[
"t",
"key-rotation"
],
[
"t",
"key-compromise"
],
[
"t",
"rfc"
]
],
"content": "In Nostr, you are your key. Your identity belongs to you alone: any device holding your private key grants full access, and any relay holding your events stores your permanent record. The radical ownership comes with radical responsibility: lose your private key and you lose everything; get it compromised and an attacker becomes you.\n\nThe protocol needs key rotation, but previous proposals have struggled with complexity. They require pre-generated key chains, cryptographic commitments, or trusted hardware that most users postpone configuring until disaster strikes.\n\nThe social layer offers a more tractable solution than the cryptographic one.\n\n## The problem space\n\nThere are two distinct scenarios we need to handle:\n\n1. **Key Compromise with Access**: You discover your key is compromised, but you still have it\n2. **Lost Keys**: Your key is gone - device destroyed, forgotten, or otherwise inaccessible\n\nTraditional cryptographic solutions fail the second case entirely. With no pre-committed recovery key, access is gone. But Nostr already has a powerful social graph. What if we used it?\n\n## Solution 1: the compromise declaration\n\nWhen you discover your key is compromised but still have access, you can publish a special \"compromise declaration\" event. This is a loud, permanent, irreversible signal that your key should no longer be trusted.\n\nThe brilliance is in what happens next: you update your profile name to `[COMPROMISED] YourName`. If an attacker tries to remove it, you add it back. This creates a spam war that works in the victim's favor: the chaos of competing updates signals to everyone that something is wrong with this account.\n\nEven if the attacker \"wins\" and controls the account, they're controlling an account that's been permanently marked as compromised. They've vandalized their own prize.\n\n## Solution 2: social key rotation\n\nThe second solution is more radical: anyone can propose that a key should rotate to a new key, and the community decides if it's legitimate through attestations.\n\nHere's how it works:\n\n1. **Proposal**: Anyone (including you with a different key) publishes a rotation proposal\n2. **Attestation**: Your friends verify out-of-band (Signal, in-person, video call) and publish attestations\n3. **Confidence**: Clients calculate confidence based on who's attesting and how many\n4. **Manual Action**: Users see the attestations and manually decide whether to follow the new key\n\nThe key insight: if both the old AND new keys sign the proposal, that's strong evidence. But even without the old key (lost scenario), sufficient attestations from your social graph can establish legitimacy.\n\n## Why this works\n\nThese solutions embrace what Nostr does well: social signaling and community verification, applied as security primitives. They're:\n\n- **Simple**: No pre-planning or complex setup required\n- **Social**: Leverages existing trust relationships\n- **Permanent**: Creates undeletable audit trails\n- **Backward Compatible**: The profile name trick works on every client today\n- **Antifragile**: Attacks make the compromise more visible, not less\n\n## The non-deletable event range\n\nThese proposals introduce events in kinds 65533-65535, which we're proposing as permanently non-deletable. Once you declare a key compromised or propose a rotation, that record stays forever. This prevents attackers from hiding their tracks and gives the community a permanent audit trail.\n\n## Moving forward\n\nPerfect security is unattainable, and pursuing it often crowds out good-enough solutions. These proposals are practical and implementable, designed to work with human behavior as it exists. They turn Nostr's social layer into a security feature.\n\nKey rotation doesn't need to be complex. Sometimes the best solution is to admit that humans are social creatures and build systems that embrace that reality.\n\n---\n\n# NIP-101: Key compromise declaration\n\n`draft` `optional`\n\nThis NIP defines a method for users to permanently declare that their private key has been compromised.\n\n## Abstract\n\nWhen a Nostr user's private key (nsec) is compromised but they still have access to it, they can publish a special event to warn others that the key should no longer be trusted.\n\n## Specification\n\n### Compromise declaration event\n\nA compromise declaration uses event kind `65535`:\n\n```json\n{\n \"id\": \"\u003c32-bytes lowercase hex of the id\u003e\",\n \"pubkey\": \"\u003c32-bytes lowercase hex of the compromised public key\u003e\",\n \"created_at\": \u003cunix timestamp\u003e,\n \"kind\": 65535,\n \"tags\": [\n [\"nonce\", \"\u003cunique random value\u003e\"]\n ],\n \"content\": \"KEY COMPROMISED - DO NOT TRUST SIGNATURES AFTER THIS TIMESTAMP\",\n \"sig\": \"\u003c64-bytes lowercase hex of the signature\u003e\"\n}\n```\n\nUsers MAY include a Bitcoin block reference for proof-of-absence:\n```json\n[\"block\", \"\u003cbitcoin block hash\u003e\", \"\u003cblock height\u003e\"]\n```\n\n### Requirements\n\n1. **Non-replaceable**: Kind 65535 is a regular event (not replaceable)\n2. **Non-deletable**: Relays SHOULD ignore kind 5 deletion requests for kind 65535 events\n3. **Unique nonce**: Prevents replay attacks\n\n### OpenTimestamps Attestation\n\nUsers MAY publish a NIP-03 OpenTimestamps attestation (kind 1040) referencing the compromise declaration for cryptographic time proof.\n\n### Profile update convention\n\nAfter publishing a compromise declaration, users SHOULD update their kind 0 metadata event to prepend `[COMPROMISED]` to their display name:\n\n```json\n{\n \"id\": \"\u003c32-bytes lowercase hex of the id\u003e\",\n \"pubkey\": \"\u003c32-bytes lowercase hex of the public key\u003e\",\n \"created_at\": \u003cunix timestamp\u003e,\n \"kind\": 0,\n \"tags\": [],\n \"content\": \"{\\\"name\\\":\\\"[COMPROMISED] Alice\\\",\\\"about\\\":\\\"Key compromised at block 850000\\\",\\\"picture\\\":\\\" https://example.com/pic.jpg\\\"}\",\n \"sig\": \"\u003c64-bytes lowercase hex of the signature\u003e\"\n}\n```\n\nThis creates a visible warning even on clients that don't support this NIP.\n\n## Client Implementation\n\nClients supporting this NIP:\n- MUST check for kind 65535 events when displaying any user profile or content\n- MUST show a prominent warning if a compromise declaration exists\n- SHOULD display the compromise timestamp\n- SHOULD NOT automatically unfollow compromised keys\n\n## Security Considerations\n\n1. Attackers self-sabotage by publishing compromise declarations\n2. The spam war over kind 0 updates alerts all users\n3. Services may run bots to maintain [COMPROMISED] profile names\n\n---\n\n# NIP-102: Social key rotation\n\n`draft` `optional`\n\nThis NIP defines a method for rotating Nostr keys through social attestation, enabling recovery from both compromised and lost keys.\n\n## Abstract\n\nUsers can rotate from an old key to a new key through community verification. Anyone can propose a rotation, and the community provides attestations to validate it. This works even when the original private key is lost.\n\n## Specification\n\n### Event Kinds\n\n- `65534`: Key rotation proposal (non-deletable)\n- `65533`: Key rotation attestation (non-deletable)\n\n### Rotation proposal event\n\nAnyone can publish a rotation proposal:\n\n```json\n{\n \"id\": \"\u003c32-bytes lowercase hex of the id\u003e\",\n \"pubkey\": \"\u003c32-bytes lowercase hex of the proposer\u003e\",\n \"created_at\": \u003cunix timestamp\u003e,\n \"kind\": 65534,\n \"tags\": [\n [\"p\", \"\u003cold pubkey\u003e\"],\n [\"p\", \"\u003cnew pubkey\u003e\"],\n [\"old\", \"\u003cold pubkey\u003e\"],\n [\"new\", \"\u003cnew pubkey\u003e\"]\n ],\n \"content\": \"Proposing key rotation for [reason]\",\n \"sig\": \"\u003c64-bytes lowercase hex of the signature\u003e\"\n}\n```\n\nThe proposer can be the old key, new key, or any third party.\n\n### Attestation Event\n\nCommunity members publish attestations:\n\n```json\n{\n \"id\": \"\u003c32-bytes lowercase hex of the id\u003e\",\n \"pubkey\": \"\u003c32-bytes lowercase hex of the attester\u003e\",\n \"created_at\": \u003cunix timestamp\u003e,\n \"kind\": 65533,\n \"tags\": [\n [\"e\", \"\u003crotation proposal event id\u003e\"],\n [\"attestation\", \"confirm\"] // or \"reject\"\n ],\n \"content\": \"Verified via [details]\",\n \"sig\": \"\u003c64-bytes lowercase hex of the signature\u003e\"\n}\n```\n\nMAY include: `[\"method\", \"in-person|video-call|signal|telegram|other\"]`\n\nThe old and new keys can also publish attestations to strengthen the signal.\n\n## Client Implementation\n\nClients query for kind 65534 events and fetch associated kind 65533 attestations. Calculate weight based on:\n- Both old+new keys attesting = highest confidence \n- Attestations from follows \u003e follows-of-follows \u003e unknown\n- More attestations = higher confidence\n\nNever auto-follow rotated keys. Require explicit user confirmation.\n\n## Example Flow\n\n**Lost Key**: Alice loses her key → creates proposal with any key → shares event ID via Signal → friends attest → others manually update follows\n\n**Compromised Key**: Alice publishes proposal from old key → signs from new key → dual signature creates high confidence\n\n## Security Considerations\n\n- No automatic actions prevent false rotations\n- Social verification leverages existing trust\n- Sybil resistance through web-of-trust weighting\n- Non-deletable events create permanent audit trail\n\nRelays SHOULD store kinds 65533-65535 permanently and ignore deletion requests.",
"sig": "7fecb0bc76af09798c64232f019e9aa0e7bd144a59b9a7c6c6dd05d7ba44db4254f158c1ed98a0db42d3ce199ab5cb292e3dc4ae04ac83847684fa49d47d6d90"
}