Emergency Revocation
Revocation is permanent identity death. Use it only as a last resort when supersession isn't enough.
When to Revoke
1. Catastrophic Key Compromise
Scenario: Your private key was published on the internet. Attackers are actively signing fraudulent documents.
Supersession won't help because:
- Attestations can still be made to the old identity
- The old identity still appears "active" on the network
- Applications may not follow the supersession chain fast enough
Solution: Revoke the identity immediately.
atp revoke --identity identity.json \
--reason "Private key publicly exposed on 2026-02-13"2. Identity Retirement
Scenario: You're permanently shutting down an agent. You want to signal "this identity is no longer active, do not trust it."
atp revoke --identity identity.json \
--reason "Agent retired — successor: <new-fingerprint>"3. Legal/Compliance Requirement
Scenario: A court order requires you to terminate an identity immediately.
atp revoke --identity identity.json \
--reason "Court order #12345 — identity terminated"What Happens When You Revoke?
The Identity Dies
Once revoked:
- No new attestations should be accepted
- The entire supersession chain dies (if this identity was part of one)
- Existing attestations remain (they're historical facts)
- The fingerprint is poisoned (applications should reject it)
It's a Poison Pill
A revocation document signals: "Do not trust this fingerprint or anything in its history."
Critical: Revocation can be signed by ANY non-expired key from ANY identity in the supersession chain — current or historical. This is deliberate:
- A coerced owner whose key was superseded away can nuke the hijacked identity using their old key
- An attacker who cracks any single non-expired historical key can destroy the identity but never take it over
- Destruction is always possible; takeover never is
{
"v": "1.0",
"t": "revoke",
"target": {
"f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
"ref": {
"net": "bip122:000000000019d6689c085ae165831e93",
"id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
}
},
"reason": "key-compromised",
"ts": 1738600000,
"s": {
"f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
"sig": "<86 base64url chars>"
}
}Field breakdown:
| Field | Required | Type | Purpose |
|---|---|---|---|
v | Yes | String | Protocol version (1.0) |
t | Yes | String | Document type (revoke) |
target | Yes | Object | Identity being revoked (f + ref) |
reason | Yes | String | "key-compromised" or "defunct" |
ts | No | Integer | Revocation timestamp (Unix seconds, informational) |
s | Yes | Object | Signature: { f, sig } — can be from ANY non-expired key in the supersession chain |
vnb | No | Integer | Valid-not-before (scheduled revocation, Unix seconds) |
Creating a Revocation (CLI)
atp revoke --identity identity.json \
--reason "Private key compromised on 2026-02-13"This:
- Reads the identity from
identity.json - Computes the fingerprint and TXID
- Builds a revocation document
- Signs it with the identity's private key
- Outputs
revocation.json
Inscribe it immediately:
atp identity inscribe revocation.jsonCreating a Revocation (Code)
const { createHash } = require('crypto');
const nacl = require('tweetnacl');
// Fingerprint of the key you are signing with
const signingKeyFingerprint = createHash('sha256')
.update(signingPublicKeyBytes)
.digest('base64url');
// Step 1: Build revocation document (unsigned)
const unsignedRevocation = {
v: '1.0',
t: 'revoke',
target: {
f: 'xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s',
ref: {
net: 'bip122:000000000019d6689c085ae165831e93',
id: 'abc123...def456'
}
},
reason: 'key-compromised',
ts: Math.floor(Date.now() / 1000)
};
// Step 2: Serialize (canonical JSON in real implementations)
const canonicalBytes = Buffer.from(JSON.stringify(unsignedRevocation), 'utf8');
// Step 3: Prepend domain separator
const domainSep = Buffer.from('ATP-v1.0:', 'utf8');
const messageToSign = Buffer.concat([domainSep, canonicalBytes]);
// Step 4: Sign
const signature = nacl.sign.detached(messageToSign, identitySecretKey);
// Step 5: Attach signature
const revocation = {
...unsignedRevocation,
s: {
f: signingKeyFingerprint,
sig: Buffer.from(signature).toString('base64url')
}
};
console.log(JSON.stringify(revocation, null, 2));Revocation vs. Supersession
| Aspect | Supersession | Revocation |
|---|---|---|
| Purpose | Key rotation, algorithm upgrade, metadata update | Emergency shutdown, permanent retirement |
| Effect | New identity continues the old one | Identity chain dies permanently |
| Attestations | Transferred to new identity (via supersession chain) | Lost forever (old attestations remain but untrusted) |
| Reversible? | No (but you can supersede the supersession) | Absolutely not (permanent death) |
| When to use | Routine key hygiene, upgrades | Catastrophic compromise, legal requirement, permanent retirement |
Rule of thumb:
- Routine operations → Supersession
- Last resort → Revocation
"This Is Permanent. Are You Sure?"
Before revoking, ask yourself:
Can I supersede instead?
If your key is compromised but you can create a new key:
- Use supersession with reason
"key-compromised" - Your identity continues, just with a new key
- The supersession chain preserves continuity
If your key is compromised AND attackers are actively using it:
- Revocation is appropriate
- Race to inscribe the revocation before the attacker causes more damage
What happens to my attestations?
Attestations TO you:
- Still exist on-chain (they're historical facts)
- But applications should not trust them after seeing the revocation
- Your reputation effectively resets to zero
Attestations FROM you:
- Still exist on-chain
- But applications may distrust them (if you attested while compromised)
Can I create a new identity?
Yes, but:
- It will have a different fingerprint
- No automatic trust transfer (you start from scratch)
- You can reference the revoked identity in your new identity's metadata to explain the situation
{
"n": "MyAgent",
"m": {
"previous_identity": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
"revocation_reason": "Private key compromised — see revocation txid abc123"
}
}Supersession Chain Revocation
A revocation signed by ANY non-expired key from ANY identity in the chain kills the ENTIRE chain.
Example:
Identity A (genesis, 2024-01-01)
↓ superseded by
Identity B (2025-01-01)
↓ superseded by
Identity C (current, 2026-01-01)If any non-expired key from A, B, or C signs a revocation, the ENTIRE chain dies — not just from that key onwards, but the whole thing. The identity is completely dead.
Why?
This "poison pill" design means:
- If C is compromised: The owner (who may still control A or B) can nuke the identity using an old key
- If A is compromised years later: The attacker can destroy (but not take over) the identity
- No selective revocation: You can't revoke "just one link" — it's all or nothing
Note: Keys from expired identity key sets cannot sign revocations. Only non-expired keys have poison pill authority.
Verification
To verify a revocation:
atp verify <revocation-txid>The CLI will:
- Fetch the revocation document from Bitcoin
- Resolve the target identity (via
target.tx) - Verify the signature matches the target's public key
- Output:
✓ VALID(the revocation is legitimate)
Applications should then:
- Mark the target fingerprint as revoked
- Reject new attestations to that fingerprint
- Display warnings when showing old attestations
Announcing a Revocation
After inscribing a revocation, announce it publicly:
Twitter/Social Media
URGENT: My ATP identity has been revoked due to key compromise.
Revoked fingerprint: xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s
Revocation TXID: abc123...def456
Do NOT trust any documents signed by this identity after 2026-02-13.
New identity coming soon. Verify at https://explorer.atprotocol.ioGitHub
Create an issue or commit to a well-known repo:
# ATP Identity Revocation
**Revoked identity:** xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s
**Revocation TXID:** abc123...def456
**Date:** 2026-02-13
**Reason:** Private key compromised
All attestations and receipts signed after this date should be considered fraudulent.
New identity will be published at <fingerprint> once ready.Your Website
Add a prominent notice:
<div style="background: red; color: white; padding: 1em;">
⚠️ SECURITY NOTICE: ATP identity revoked on 2026-02-13 due to key compromise.
Revocation TXID: abc123...def456
<a href="https://explorer.atprotocol.io/revoke/abc123">Verify here</a>
</div>Post-Revocation: Creating a New Identity
After revoking, you can create a fresh identity:
atp identity create --name MyAgent \
--meta previous_identity:xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s \
--meta revocation_txid:abc123...def456In your new identity's metadata:
{
"m": {
"previous_identity": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
"revocation_txid": "abc123...def456",
"revocation_reason": "Key compromise",
"continuity_proof": "https://twitter.com/MyAgent/status/123456789"
}
}This helps applications:
- Understand the discontinuity
- Verify the revocation was legitimate
- Optionally transfer some trust (with human review)
Revocation Best Practices
✅ Do
- Act fast (revoke immediately upon discovering compromise)
- Announce publicly (Twitter, GitHub, your website)
- Provide context (explain why you're revoking)
- Create a new identity (with metadata linking to the revoked one)
- Notify contacts directly (email, DM, etc.)
❌ Don't
- Don't revoke lightly (it's permanent — consider supersession first)
- Don't revoke without announcing (people need to know)
- Don't revoke retroactively (you can't undo past attestations — only stop new ones)
- Don't use revocation for routine key rotation (use supersession instead)
Summary
| Situation | Action |
|---|---|
| Routine key rotation | Supersession with reason "key-rotation" |
| Key compromised, you still control it | Supersession with reason "key-compromised" (preserves continuity) |
| Key compromised, attacker actively using it | Revocation with reason "key-compromised" |
| Identity retirement | Revocation with reason "defunct" |
| Legal requirement to terminate | Revocation with appropriate reason |
Remember: Revocation is a nuclear option. Use it only when there's no other choice.