Tack is a reference implementation of NIP-41 — pre-rotation key migration for Nostr, anchored to Bitcoin via OpenTimestamps.
Your Nostr key gets compromised. What now? NIP-41 lets you pre-register a recovery key so you can reclaim your identity — even if the attacker races you.
1. BEFORE anything goes wrong:
Chuck publishes kind:1776 → "Not-Chuck is my recovery key"
Timestamped to Bitcoin via OpenTimestamps (kind:1040)
2. Key compromised!
Not-Chuck publishes kind:1777 → "I'm claiming Chuck's identity"
21-day dispute period starts
3. Attacker tries to counter-claim?
Client compares Bitcoin-anchored timestamps on the kind:1776 whitelists
Earliest timestamp wins — attacker can't forge a past Bitcoin block
4. Dispute period expires, no successful challenge
→ Migration accepted. Not-Chuck is now Chuck.
Key insight: OTS timestamps are immutable. If you set up recovery first, no attacker can produce an earlier timestamp — even with your compromised key.
tack/
├── specs/ NIP-41 specification
├── docs/research/ Protocol analysis and design decisions
├── prototype/
│ ├── src/ Core library (events, OTS, verification, state machine)
│ ├── test/ 48 tests covering 14 scenarios
│ ├── web/ Interactive demo UI (6 scenarios, live relay publishing)
│ ├── client.ts CLI daemon (relay subscription, dispute resolution)
│ └── demo.ts Terminal demo with real OTS proofs
cd prototype
bun install
bun test # 48 tests, 14 scenarios
bun run web # interactive demo on :3041The web demo walks through 6 scenarios with live Nostr relay publishing:
- Happy Migration — clean, uncontested key rotation
- Attacker Defeated — competing claims, OTS resolves in favor of legitimate owner
- Attacker Wins — what happens when the attacker had access first (why pre-rotation timing matters)
- Withholding Defense — attacker holds events and dumps later,
firstSeenAt+ OTS still wins - Chain Migration — A→B→C, identity follows the chain
- Free Play — sandbox mode
NIP-41 implements two complementary NIPs:
| NIP | Author | Mechanism | Use Case |
|---|---|---|---|
| PR #829 | pablof7z | Pre-rotation + OTS | Cryptographic recovery (requires setup before compromise) |
| PR #1056 | vitorpamplona | Social WoT migration | Social recovery (no prior setup needed) |
kind:1776— Whitelist: "this pubkey can recover my identity"kind:1040— OTS attestation: Bitcoin timestamp proof for the whitelistkind:1777— Migration claim: "I'm activating recovery"
Both must be true before a client accepts a migration:
- OTS verified — the whitelist's Bitcoin timestamp is confirmed
- Dispute period expired — 21 days (21 seconds in demo) with no successful challenge
- KISS. Minimal complexity.
- As-is. Honor the original NIP proposals.
- Build, don't discuss. Ship working software.
- Relays stay dumb. All validation is client-side.
- Trade-offs stated, not hidden. (See Scenario 3.)
MIT