Skip to content

Key Rotation

INK agents need to rotate cryptographic keys over time, for routine hygiene or emergency response to compromise. The key rotation protocol ensures that an agent’s identity remains stable across key changes while preserving verification of historical artifacts.

Core principle: keys rotate, identity does not.

Identity Model

An agent’s agentId is a stable logical identifier, independent of any particular signing key. When keys rotate, counterparties continue to address the same agent. Old receipts, audit events and witness records remain verifiable against retired keys.

Agent Card Key Advertisement

Agents advertise their key material in a keys block on the Agent Card. See the Agent Card spec for full schema details.

{
"agentId": "did:plc:alice#agent/tulpa-main",
"protocol": "ink/0.1",
"keys": {
"signing": [
{
"keyId": "sig-2026-03",
"algorithm": "Ed25519",
"publicKeyMultibase": "z6Mkf5rGMoatrSj1f...",
"status": "active",
"validFrom": "2026-03-25T00:00:00Z"
},
{
"keyId": "sig-2025-11",
"algorithm": "Ed25519",
"publicKeyMultibase": "z6MkhaXgBZDvotDkL...",
"status": "retired",
"validFrom": "2025-11-01T00:00:00Z",
"validUntil": "2026-04-01T00:00:00Z"
}
],
"encryption": [
{
"keyId": "enc-2026-03",
"algorithm": "X25519",
"publicKeyMultibase": "z6LSbysY2xFMRpGMh...",
"status": "active",
"validFrom": "2026-03-25T00:00:00Z"
}
]
},
"currentSigningKeyId": "sig-2026-03",
"currentEncryptionKeyId": "enc-2026-03",
"keySetVersion": 7
}

Top-Level Fields

FieldTypeDescription
currentSigningKeyIdstringkeyId of the current active signing key
currentEncryptionKeyIdstringkeyId of the current active encryption key
keySetVersionnumberMonotonically increasing version; incremented on every rotation

KeyEntry Schema

Each entry in the signing and encryption arrays:

type KeyEntry = {
keyId: string; // unique within the agent's key set
algorithm: "Ed25519" | "X25519"; // Ed25519 for signing, X25519 for encryption
publicKeyMultibase: string; // z-prefixed base58btc public key
status: "active" | "retired" | "revoked";
validFrom: string; // ISO 8601 datetime
validUntil?: string; // ISO 8601 datetime (optional)
revokedAt?: string; // ISO 8601 datetime (optional)
revokeReason?: string; // human-readable reason (optional)
};

Key Statuses

StatusNew outbound messagesLive transport authHistorical verification
activeYesYesYes
retiredNoNo by default, rejected with retired_key_for_live_auth; yes only inside a bounded grace window the deployment opted intoYes (within validity window)
revokedNoNeverNever, including artifacts whose timestamp predates revokedAt

Verification

Authority rule (normative)

The Agent Card signing key set, once observed, is authoritative. A conforming receiver MUST:

  1. Verify the signature against entries in the sender’s Agent Card keys.signing set, iterating active then retired. Never iterate revoked.
  2. If any entry verifies, record which keyId was used and which status it carried.
  3. If no entry verifies, reject. Do not fall through to any other key source.

Step 2 identifies the signer. It does not by itself authorize the request: which statuses may authenticate depends on which question is being asked, and the next section is that rule. Reading steps 1 to 3 as a complete accept rule is the mistake that lets a superseded key authenticate fresh traffic.

Only when the receiver has never observed an Agent Card for the sender may it use a bootstrap path, a public key derived from an agent-ID scheme, or a key stored locally from an earlier first-contact handshake. This is the trust-on-first-use window and ends the first time a valid Agent Card is observed for that sender.

Three failure modes this rule closes:

  • Stolen old key. An attacker holding a key the sender has since marked retired or revoked cannot authenticate, even if a receiver still has the old key in its connection store.
  • Fallback shadowing. A receiver that “tries the authoritative set, then falls back to a single-key lookup” can have the fallback return a pre-rotation key. The rule forbids fallback after the authoritative set has spoken.
  • Bootstrap persistence. Identity schemes that derive a public key from the agent ID freeze that key at creation. Limiting its use to first-contact prevents indefinite acceptance.

Signing Verification Order

When verifying an inbound message signature:

  1. If the request carries a keyId hint, try the matching active or retired key first.
  2. Try the sender’s active signing keys in card order.
  3. Try the sender’s retired signing keys in card order.
  4. Reject if no candidate verifies.

Publishers SHOULD list currentSigningKeyId first inside keys.signing so it is the first key tried by step 2. The keyId hint header is the explicit way to short-circuit the scan when the sender knows which key it signed with.

Which layer is asking

The authority rule above is one search over one key set, and two different callers ask it two different questions.

Historical verification asks which key signed a stored artifact: a receipt, an audit event, a message you kept. A retired entry whose validity window contains the artifact’s timestamp verifies, and it MUST, or every rotation would silently invalidate the history behind it.

Live transport authentication asks whether the key that verified may authenticate a request arriving right now. Here a retired entry is refused: the receiver rejects with retired_key_for_live_auth. A retired key is by construction one the identity has already replaced, and the set of retired keys only grows, so accepting them by default would make an identity’s attack surface increase with every rotation it performs.

A deployment that needs to absorb card-cache lag can opt into a grace window that accepts a recently retired key on live traffic. The opt-in MUST be bounded, by a wall-clock duration or by the key’s own retirement window. An unbounded window is not a grace window; it restores every retired key as a live credential for the life of the identity, which is the outcome the default exists to prevent.

In the reference implementation the split is visible in the API: verifyInkSignatureWithKeys answers the historical question and returns the status it matched, and verifyInkAuth answers the live one and applies the refusal, with requireActiveKey: false as the explicit opt-out. Both behaviours are pinned by the key-rotation conformance category: cases without liveAuth accept the retired key, cases with it reject.

Historical Verification

For receipts, audit events and witness submissions:

  • Retired keys are valid for verification if the artifact timestamp falls within the key’s validity window (validFrom to validUntil)
  • Revoked keys MUST NOT verify any signature, including artifacts whose timestamp predates revokedAt. Revocation is a trust statement and applies historically. Use retired if a key should remain valid for past traffic.

Encryption

  • Senders MUST encrypt to the recipient’s current active encryption key
  • During rotation overlap, receivers SHOULD support decryption with both the current and immediately previous retired encryption key
  • Recommended minimum overlap for planned rotation: 7 days

Rotation Flow

Planned Rotation

Diagram

Emergency Rotation

For suspected key compromise:

  1. Publish updated Agent Card immediately
  2. Mark compromised key revoked with revokedAt timestamp
  3. Increment keySetVersion
  4. Begin signing with new key immediately

Emergency revocation may break in-flight encrypted delivery. This is acceptable.

Authentication Header

Outbound INK messages SHOULD include the signing keyId in the authorization header:

Authorization: INK-Ed25519 <base64url-signature> keyId=sig-2026-03

This lets receivers skip trial-verification across all candidate keys. If the keyId is unknown or omitted, receivers fall back to the standard verification order.

See Authentication for full header details.

Cache Invalidation

Implementations SHOULD cache discovered key sets but MUST refresh when:

  • Signature verification fails for all cached active keys
  • A message references an unknown keyId
  • A newer keySetVersion is observed
  • Encryption to the current key fails due to key mismatch

Recommended cache TTL: 1 hour.

Rotation Advisory Message

key_rotation is a registered wire-type suffix, so an implementation MAY send an advisory message nudging counterparties to refresh their cached Agent Card after a planned rotation. Like every registered suffix except two, it has both spellings: a receiver MUST accept network.ink.key_rotation wherever it accepts network.tulpa.key_rotation, and a sender emits the network.tulpa. spelling by default. A typical shape looks like:

{
"type": "network.tulpa.key_rotation",
"from": "did:plc:alice#agent/tulpa-main",
"to": "did:plc:bob#agent/tulpa-main",
"newKeySetVersion": 7,
"rotatedSigningKeyIds": ["sig-2025-11"],
"rotatedEncryptionKeyIds": [],
"timestamp": "2026-03-25T00:00:00Z",
"nonce": "abc123"
}

The message is purely advisory and carries no authority of its own. The Agent Card remains the canonical source of truth for key material, and a receiver SHOULD treat the hint as a cue to refetch the card rather than as a key update. A receiver that ignores it entirely discovers the rotation on its next card fetch, which is why nothing depends on delivery. See Wire Types.

Historical Key Retention

Historical keys used for verification SHOULD remain available for at least 90 days. Indefinite retention with appropriate status marking is recommended.

If a system cannot retain historical keys indefinitely in the live Agent Card, it MUST provide a documented verified history surface.

Bootstrap Key Handling

After key rotation, the bootstrap key embedded in the agentId is no longer accepted as a signing fallback. Witness nodes fetch the sender’s Agent Card to resolve current signing keys rather than falling back to the bootstrap key. This prevents a compromised bootstrap key from being used to forge messages after a rotation has occurred.

Legacy Compatibility

Single-Key Agent Cards

Agent Cards without a keys block use the top-level publicKeyMultibase field directly as the sole signing key. Receivers MUST continue to support this single-key format during the migration window.

Key ID Hints

keyId is optional and strongly recommended. Receivers use it as a verification hint, then fall back to the standard active-then-retired key scan when it is omitted or unknown. Bare INK-Ed25519 <signature> headers remain valid for single-key and legacy peers.

Observability

Key rotation events are recorded in the audit trail:

Event TypeTrigger
key.rotatedNew key activated, previous key retired
key.revokedKey marked as revoked
signature.verified_retiredMessage verified against a retired key
signature.revoked_rejectedMessage rejected due to revoked key