Skip to content

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.

bash
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."

bash
atp revoke --identity identity.json \
  --reason "Agent retired — successor: <new-fingerprint>"

Scenario: A court order requires you to terminate an identity immediately.

bash
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
json
{
  "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:

FieldRequiredTypePurpose
vYesStringProtocol version (1.0)
tYesStringDocument type (revoke)
targetYesObjectIdentity being revoked (f + ref)
reasonYesString"key-compromised" or "defunct"
tsNoIntegerRevocation timestamp (Unix seconds, informational)
sYesObjectSignature: { f, sig } — can be from ANY non-expired key in the supersession chain
vnbNoIntegerValid-not-before (scheduled revocation, Unix seconds)

Creating a Revocation (CLI)

bash
atp revoke --identity identity.json \
  --reason "Private key compromised on 2026-02-13"

This:

  1. Reads the identity from identity.json
  2. Computes the fingerprint and TXID
  3. Builds a revocation document
  4. Signs it with the identity's private key
  5. Outputs revocation.json

Inscribe it immediately:

bash
atp identity inscribe revocation.json

Creating a Revocation (Code)

javascript
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

AspectSupersessionRevocation
PurposeKey rotation, algorithm upgrade, metadata updateEmergency shutdown, permanent retirement
EffectNew identity continues the old oneIdentity chain dies permanently
AttestationsTransferred 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 useRoutine key hygiene, upgradesCatastrophic 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
json
{
  "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:

bash
atp verify <revocation-txid>

The CLI will:

  1. Fetch the revocation document from Bitcoin
  2. Resolve the target identity (via target.tx)
  3. Verify the signature matches the target's public key
  4. 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.io

GitHub

Create an issue or commit to a well-known repo:

markdown
# 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:

html
<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:

bash
atp identity create --name MyAgent \
  --meta previous_identity:xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s \
  --meta revocation_txid:abc123...def456

In your new identity's metadata:

json
{
  "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

SituationAction
Routine key rotationSupersession with reason "key-rotation"
Key compromised, you still control itSupersession with reason "key-compromised" (preserves continuity)
Key compromised, attacker actively using itRevocation with reason "key-compromised"
Identity retirementRevocation with reason "defunct"
Legal requirement to terminateRevocation with appropriate reason

Remember: Revocation is a nuclear option. Use it only when there's no other choice.


← Previous: Rotating Keys | Next: CLI Reference →

Released under the MIT License.