Skip to content

Rotation Chain

Immutable audit trail for key lifecycle events.

The Rotation Chain is an append-only log that records every key rotation, revocation, and recovery event. It provides:

  • Auditability — Complete history of key changes
  • Verifiability — Third parties can validate the chain
  • Continuity — Proves identity through key transitions
  • Non-repudiation — Actions are cryptographically signed
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Entry 1 │───▶│ Entry 2 │───▶│ Entry 3 │───▶│ Entry 4 │
│ │ │ │ │ │ │ │
│ OK₁ gen │ │ OK₁→OK₂ │ │ OK₂ rev │ │ OK₃ gen │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │
└──────────────┴──────────────┴──────────────┘
RIK signatures

Records the creation of a new key.

{
"sequence": 1,
"type": "key_generation",
"timestamp": "2026-01-15T00:00:00Z",
"keyId": "ok-001",
"keyType": "Ed25519",
"publicKey": "z6Nk...",
"purposes": ["authentication", "signing"],
"validFrom": "2026-01-15T00:00:00Z",
"validUntil": "2026-02-15T00:00:00Z",
"rikSignature": "..."
}

Records the transition from one key to another.

{
"sequence": 2,
"type": "key_rotation",
"timestamp": "2026-02-01T00:00:00Z",
"oldKeyId": "ok-001",
"newKeyId": "ok-002",
"reason": "scheduled",
"previousEntryHash": "sha256:...",
"rikSignature": "..."
}

Rotation Reasons:

ReasonDescription
scheduledRegular rotation per policy
compromise_suspectedPrecautionary rotation
compromise_confirmedEmergency rotation
upgradeAlgorithm/protocol upgrade
manualOperator-initiated

Records the invalidation of a key before expiry.

{
"sequence": 3,
"type": "key_revocation",
"timestamp": "2026-02-03T12:00:00Z",
"keyId": "ok-002",
"reason": "compromise_confirmed",
"effectiveImmediately": true,
"previousEntryHash": "sha256:...",
"rikSignature": "..."
}

Records the rare event of rotating the Root Identity Key.

{
"sequence": 10,
"type": "rik_rotation",
"timestamp": "2026-06-01T00:00:00Z",
"oldRikId": "rik-001",
"oldRikDid": "did:key:z6MkOLD...",
"newRikId": "rik-002",
"newRikDid": "did:key:z6MkNEW...",
"reason": "scheduled",
"continuityProof": {
"type": "dual_signature",
"oldRikSignature": "...",
"newRikSignature": "..."
},
"previousEntryHash": "sha256:..."
}

Records identity recovery using the Recovery Key.

{
"sequence": 11,
"type": "recovery",
"timestamp": "2026-07-15T00:00:00Z",
"recoveryType": "rik_restoration",
"newRikId": "rik-003",
"newRikDid": "did:key:z6MkREC...",
"authorizingShards": 2,
"totalShards": 3,
"previousEntryHash": "sha256:...",
"rkSignature": "..."
}

Each entry includes a hash of the previous entry, forming an immutable chain:

Entry 1
├── data: {...}
├── hash: H1 = sha256(data)
└── rikSignature: sign(RIK, H1)
Entry 2
├── data: {..., previousEntryHash: H1}
├── hash: H2 = sha256(data)
└── rikSignature: sign(RIK, H2)
Entry 3
├── data: {..., previousEntryHash: H2}
├── hash: H3 = sha256(data)
└── rikSignature: sign(RIK, H3)
{
"format": "SAP/RotationChain",
"version": 1,
"chainId": "sha256:...",
"rootRik": "did:key:z6Mk...",
"created": "2026-01-15T00:00:00Z",
"entries": [
{ "sequence": 1, "type": "key_generation", ... },
{ "sequence": 2, "type": "key_rotation", ... },
{ "sequence": 3, "type": "key_revocation", ... }
],
"tip": {
"sequence": 3,
"hash": "sha256:...",
"timestamp": "2026-02-03T12:00:00Z"
}
}
1. Parse chain structure
2. Verify genesis entry (sequence 1)
3. For each subsequent entry:
├── Verify sequence is sequential
├── Verify previousEntryHash matches
├── Verify timestamp is monotonic
└── Verify RIK signature
4. Verify tip matches last entry
5. Return validation result

When you have a previously verified chain tip:

1. Compare stored tip hash with received
├── Match → Chain unchanged
└── Mismatch → Verify new entries only
2. Verify entries from (stored_tip + 1) to new_tip
3. Update stored tip

When RIK rotates, continuity is proven through dual signatures:

┌─────────────────────────────────────────────────────────┐
│ RIK Rotation Continuity Proof │
├─────────────────────────────────────────────────────────┤
│ │
│ Old Identity: did:key:z6MkOLD... │
│ New Identity: did:key:z6MkNEW... │
│ │
│ Proof Statement: │
│ "I (old) am transitioning to (new)" │
│ │
│ Signatures: │
│ ├── oldRikSignature: sign(OLD_RIK, statement) │
│ └── newRikSignature: sign(NEW_RIK, statement) │
│ │
│ Verifiers: │
│ 1. Verify old signature with old RIK ✓ │
│ 2. Verify new signature with new RIK ✓ │
│ 3. Accept continuity claim ✓ │
│ │
└─────────────────────────────────────────────────────────┘
~/.sap/
├── rotation_chain.json # Full chain
├── rotation_chain.sealed # Encrypted backup
└── chain_tip.json # Quick tip reference

The PID includes the chain tip for quick verification:

{
"sentinel": {
"rotationTip": "sha256:a1b2c3d4...",
"rotationCount": 15,
"policyHash": "sha256:e5f6g7h8..."
}
}

Options for sharing the complete chain:

  • On-demand fetch: API endpoint returns full chain
  • Content-addressed: IPFS/Arweave for immutability
  • Gossip protocol: P2P distribution among agents

Any modification to a historical entry invalidates all subsequent hashes:

Original: E1 → E2 → E3 → E4
Modified: E1 → E2' → ??? (hash mismatch)

Each entry is signed by RIK, proving:

  • The identity holder authorized the change
  • The change occurred after the previous entry
  • No entries have been removed or reordered

Compromise of an OK doesn’t expose:

  • RIK (used only for signing)
  • Historical chain entries
  • Future rotation plans
function addRotationEntry(chain, entry, rik) {
// 1. Set sequence
entry.sequence = chain.entries.length + 1;
// 2. Link to previous
if (chain.entries.length > 0) {
const lastEntry = chain.entries[chain.entries.length - 1];
entry.previousEntryHash = hashEntry(lastEntry);
}
// 3. Set timestamp
entry.timestamp = new Date().toISOString();
// 4. Sign with RIK
entry.rikSignature = sign(rik, entry);
// 5. Append to chain
chain.entries.push(entry);
// 6. Update tip
chain.tip = {
sequence: entry.sequence,
hash: hashEntry(entry),
timestamp: entry.timestamp
};
return chain;
}
function verifyChain(chain, trustedRik) {
let previousHash = null;
for (const entry of chain.entries) {
// Verify sequence
if (entry.sequence !== chain.entries.indexOf(entry) + 1) {
return { valid: false, error: "Invalid sequence" };
}
// Verify hash link
if (previousHash && entry.previousEntryHash !== previousHash) {
return { valid: false, error: "Hash chain broken" };
}
// Verify signature
if (!verify(trustedRik, entry, entry.rikSignature)) {
return { valid: false, error: "Invalid signature" };
}
previousHash = hashEntry(entry);
}
return { valid: true };
}