Supported library reference. Rust, Node.js, Python and Go remain supported in a separate workspace. This chapter can contain historical version or installation text; use the current package guide. Extended CLI examples use
jacs-compatin 0.15. See the current MCP guide for actual runtime profiles and the release inventory for published versions.
Key Rotation
Key rotation is the process of replacing an agent's cryptographic keys while preserving the ability to verify documents signed with previous keys. JACS implements version-aware key management to support secure key lifecycle operations.
Why Key Rotation Matters
Key Compromise Recovery
When a private key is compromised, the agent must be able to:
- Generate new keys and continue operating
- Revoke trust in the compromised key
- Maintain verifiability of documents signed before the compromise
Cryptographic Agility
Cryptographic algorithms evolve. Key rotation enables:
- Migration from older algorithms to newer ones
- Transition to post-quantum cryptography when needed
- Algorithm upgrades without breaking existing signatures
Migrating Ed25519 Agents to pq2025
Rotation always resolves to pq2025 — it is the designated migration path
for Ed25519 agents:
jacs agent rotate-keys
- An Ed25519 agent that rotates (with or without an
--algorithmargument) gets apq2025root; the config is stampedpq2025; the transition proof is signed by the old Ed25519 key, so the migration is cryptographically authorized by the previous root. - Rotating to Ed25519 (or any non-PQ algorithm) is a typed error.
- Until it rotates, an Ed25519 agent keeps signing normally with Ed25519.
- Documents signed by the old Ed25519 key remain verifiable after migration (see Cross-Algorithm Verification in the Algorithm Selection Guide).
- Rotation supersedes the ES256 compatibility key binding: the binding
is signed by the native root, so after rotation ecosystem exports fail
with a "re-issue" error until
jacs agent issue-compat-bindingruns under the new native root. - Rotation applies to the native root only — rotating the ES256 compatibility key itself is out of scope in P2.
Compliance Requirements
Many security standards require periodic key rotation:
- PCI-DSS mandates regular key changes
- SOC 2 requires key management policies
- NIST guidelines recommend rotation schedules
Agent Versioning
JACS uses a versioned identity model where each key rotation creates a new agent version.
Version Format
Agent identifiers follow the format: {agent_id}:{version_uuid}
- jacsId: The stable agent identity (UUID v4) - never changes
- jacsVersion: Current version UUID - changes on each update
- jacsPreviousVersion: Links to the prior version
- jacsOriginalVersion: The first version ever created
{
"jacsId": "550e8400-e29b-41d4-a716-446655440000",
"jacsVersion": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"jacsPreviousVersion": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"jacsOriginalVersion": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
Version Chain
Each version forms a linked chain back to the original:
Original (v1) <-- Previous (v2) <-- Current (v3)
| | |
key-A key-B key-C
This chain provides an audit trail of all key changes and allows verification of any version.
Version-Aware Verification
The critical insight enabling key rotation is that signatures contain both the agent ID and the version that created them.
Signature Structure
{
"jacsSignature": {
"agentID": "550e8400-e29b-41d4-a716-446655440000",
"agentVersion": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"publicKeyHash": "sha256-of-public-key-A",
"signingAlgorithm": "ring-Ed25519",
"signature": "base64-encoded-signature",
"date": "2024-01-15T10:00:00Z"
}
}
Key Resolution Process
When verifying a signature:
- Extract
agentVersionandpublicKeyHashfrom the signature - Look up the public key that was active for that version
- Verify the signature using that historical key
#![allow(unused)] fn main() { // Pseudocode for version-aware verification fn verify_signature(doc: &Document) -> Result<()> { let sig = &doc.jacs_signature; // Find the key that was active for this version let public_key = resolve_key_for_version( &sig.agent_id, &sig.agent_version, &sig.public_key_hash, )?; // Verify with the historical key verify_with_key(&doc, &sig, &public_key) } }
Key Lookup Priority
The verification system tries multiple sources:
- Local cache by hash - Fastest, key already stored locally
- Trust store by version - Most accurate for known agents
- Trust store by hash - Fallback for legacy entries
- DNS lookup - External verification, authoritative
- Fail - Key not found, verification impossible
Key Rotation Process
Step-by-Step Rotation
- Generate new key pair with the desired algorithm
- Create new agent version with updated key information
- Sign new version with old key (transition signature)
- Update DNS records to include new key fingerprint
- Store old public key for future verifications
Transition Signature
The transition signature proves the key rotation was authorized by the holder of the old key:
JACS_KEY_ROTATION:{agent_id}:{old_key_hash}:{new_key_hash}:{timestamp}
This signed message:
- Proves continuity of ownership
- Provides an audit trail
- Binds old and new keys together cryptographically
CLI Commands
# Rotate keys with default algorithm
jacs agent rotate-keys
# Rotate to post-quantum algorithm
jacs agent rotate-keys --algorithm pq2025
# List key history (active and archived keys)
jacs agent keys-list
# Repair config after a crash during rotation
jacs agent repair
# Revoke a compromised key (Coming Soon)
jacs agent keys revoke <key-hash>
Transition Signature
During key rotation, JACS produces a cryptographic transition proof that binds the old key to the new key. This proof is embedded in the agent document as jacsKeyRotationProof:
{
"jacsKeyRotationProof": {
"transitionMessage": "JACS_KEY_ROTATION:{agent_id}:{old_key_hash}:{new_key_hash}:{timestamp}",
"signature": "base64-encoded-signature-with-old-key",
"signingAlgorithm": "ring-Ed25519",
"oldPublicKeyHash": "sha256-of-old-key",
"newPublicKeyHash": "sha256-of-new-key",
"timestamp": "2026-04-07T10:00:00Z"
}
}
The transition message is signed with the old private key before it is archived. This proves:
- The rotation was authorized by the holder of the previous key
- The old and new keys are cryptographically linked
- An attacker cannot forge a rotation without the old private key
Agent::verify_transition_proof() validates the detached old-key signature.
Registries and directories must use
Agent::verify_transition_proof_for_rotation(proof, expected_agent_id, old_key, new_key)
instead. That stricter API also binds the proof to the expected stable identity
and exact candidate new key, preventing proof transplantation.
Crash Recovery
JACS uses a write-ahead journal to recover from crashes during key rotation. Before rotation begins, a journal file is written to {key_directory}/.jacs_rotation_journal.json. The journal tracks the rotation stage:
started- Rotation initiatedkeys_rotated- New keys generated, old keys archivedagent_saved- New agent version saved to diskconfig_signed- Config re-signed with new key (journal deleted on success)
If the process crashes mid-rotation, the next agent load detects the journal and automatically repairs the config by re-signing it with the current key. No manual intervention is required for the common case.
For manual recovery: jacs agent repair
Cross-Algorithm Rotation
Rotation is a one-way migration to pq2025, not an arbitrary algorithm
selector. An Ed25519 root can migrate to post-quantum signing:
# Rotate from Ed25519 to post-quantum
jacs agent rotate-keys --algorithm pq2025
#![allow(unused)] fn main() { // Rust API let result = advanced::rotate(&agent, Some("pq2025"))?; }
# Python
result = client.rotate_keys(algorithm="pq2025")
// Node.js
const result = await client.rotateKeys({ algorithm: 'pq2025' });
After cross-algorithm rotation, the config file's jacs_agent_key_algorithm field is updated atomically. Documents signed before the rotation remain verifiable using the archived old key.
Example Rotation Flow
Time T0: Agent created
- jacsId: "abc-123"
- jacsVersion: "v1-uuid"
- jacsCurrentKeyHash: "hash-A"
Time T1: Agent signs document D1
- D1.jacsSignature.agentVersion: "v1-uuid"
- D1.jacsSignature.publicKeyHash: "hash-A"
Time T2: Key rotation
- New keys generated with hash-B
- jacsVersion: "v2-uuid"
- jacsKeyHistory: [{ hash: "hash-A", status: "rotated" }]
- jacsCurrentKeyHash: "hash-B"
Time T3: Verify D1
- Extract agentVersion "v1-uuid" and hash "hash-A"
- Look up key: find "hash-A" with status "rotated"
- Verification succeeds (old key still valid for old docs)
Time T4: Agent signs document D2
- D2.jacsSignature.agentVersion: "v2-uuid"
- D2.jacsSignature.publicKeyHash: "hash-B"
Trust Store with Version History
The trust store maintains a history of all public keys for each trusted agent.
TrustedAgent Structure
{
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Example Agent",
"trusted_at": "2024-01-15T10:00:00Z",
"current_key_hash": "abc123...",
"domain": "agent.example.com",
"key_history": [
{
"public_key_hash": "xyz789...",
"public_key_pem": "-----BEGIN PUBLIC KEY-----\n...",
"signing_algorithm": "ring-Ed25519",
"trusted_at": "2024-01-01T00:00:00Z",
"first_version": "11111111-1111-1111-1111-111111111111",
"last_version": "22222222-2222-2222-2222-222222222222",
"status": "rotated"
},
{
"public_key_hash": "abc123...",
"public_key_pem": "-----BEGIN PUBLIC KEY-----\n...",
"signing_algorithm": "ring-Ed25519",
"trusted_at": "2024-01-15T10:00:00Z",
"first_version": "33333333-3333-3333-3333-333333333333",
"last_version": null,
"status": "active"
}
]
}
Key Status Values
| Status | Description |
|---|---|
active | Currently in use for signing |
rotated | Superseded by newer key, still valid for old signatures |
revoked | Compromised, signatures should not be trusted |
expired | Past validity period |
Looking Up Keys
#![allow(unused)] fn main() { impl TrustedAgent { /// Get the public key that was active for a specific agent version fn get_key_for_version(&self, version: &str) -> Option<&KeyEntry> { self.key_history.iter().find(|entry| { match (&entry.first_version, &entry.last_version) { (Some(first), Some(last)) => { version >= first && version <= last } (Some(first), None) => { version >= first // Current key } _ => false } }) } /// Get the public key by its hash fn get_key_by_hash(&self, hash: &str) -> Option<&KeyEntry> { self.key_history.iter().find(|e| e.public_key_hash == hash) } } }
DNS Support for Key Versions
DNS records can advertise multiple key versions for an agent.
Multi-Version DNS Records
Each key version gets its own TXT record:
; Current key
_v1.agent.jacs.example.com. 3600 IN TXT "v=hai.ai; jacs_agent_id={id}; ver=current; alg=SHA-256; hash={hash1}"
; Previous key (still valid for old signatures)
_v1.agent.jacs.example.com. 3600 IN TXT "v=hai.ai; jacs_agent_id={id}; ver=rotated; valid_until=2025-01-15; hash={hash2}"
DNS Record Generation
# Generate DNS records for all active keys
jacs agent dns --all-keys
Security Considerations
Key Revocation
When a key is compromised:
- Mark as revoked in the agent's key history
- Update DNS to include revocation status
- Signatures fail verification when made with revoked keys
- Notify trusted peers if possible
Overlap Period
During rotation, both old and new keys may be valid:
- New documents should be signed with the new key
- Old documents remain verifiable with the old key
- DNS may advertise both keys during transition
Secure Deletion
After rotation:
- Old private keys should be securely deleted
- Only public keys are retained for verification
- Key metadata must be protected from modification
Best Practices
Rotation Schedule
- Regular rotation: Quarterly or annually for compliance
- Algorithm upgrade: When transitioning to stronger cryptography
- Incident response: Immediately after suspected compromise
Pre-Rotation Checklist
- Backup current agent state
- Verify all systems can handle new key format
- Plan DNS propagation time
- Notify dependent systems of upcoming change
Post-Rotation Checklist
- Verify new key is active
- Confirm old documents still verify
- Update DNS records
- Securely delete old private key
- Test signing with new key
-
Re-issue the compatibility key binding (
jacs agent issue-compat-binding) if the agent has an ES256 compatibility key — ecosystem exports fail until the new native root signs a fresh binding
See Also
- Security Model - Overall security architecture
- Cryptographic Algorithms - Algorithm details
- DNS Verification - DNS-based identity verification