Repository navigation
Key Management and Rotation
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
This document explains how SigvaraIdentity manages cryptographic keys and rotates them safely, focusing on Ed25519 public key storage, challenge-response authentication, operator-controlled rotation, and agent control transfers. It also provides security guidance, best practices, and operational procedures for key management.
The system uses:
- An Ethereum address as the identity anchor.
- A deterministic DID derived from chain ID and agent address.
- A raw 32-byte Ed25519 public key stored on-chain for off-chain challenge-response verification.
- Operator-controlled functions to rotate keys and transfer control between addresses.
- SDK utilities for generating, encoding, signing, and verifying Ed25519 challenges.
Key files involved in this documentation:
- Solidity registry contract that stores identities and enforces authorization.
- TypeScript SDK modules for Ed25519 key handling and challenge-response flows.
- Test suite validating rotation, status transitions, and operator transfer logic.
graph TB
subgraph "On-chain"
Identity["SigvaraIdentity.sol"]
end
subgraph "Off-chain SDK"
Keys["keys.ts"]
Challenge["challenge.ts"]
Agent["agent.ts"]
end
subgraph "Tests"
Tests["SigvaraIdentity.t.sol"]
end
Agent --> Challenge
Agent --> Keys
Identity --> Tests
Challenge --> Keys
Diagram sources
- SigvaraIdentity.sol:1-618
- keys.ts:1-126
- challenge.ts:1-168
- agent.ts:1-83
- SigvaraIdentity.t.sol:1-848
Section sources
- SigvaraIdentity.sol:1-618
- keys.ts:1-126
- challenge.ts:1-168
- agent.ts:1-83
- SigvaraIdentity.t.sol:1-848
- AgentIdentity: Stores operator address, agent address, Ed25519 public key, status, and registration timestamp.
- Operator roles: Only the recorded operator can rotate keys or manage status; slashing is controlled by a staking core role.
- DID derivation: Deterministic hash from chain ID and agent address, enabling trustless off-chain computation.
- Challenge-response: Off-chain Ed25519 signatures over structured payloads with nonce, timestamp, prover DID, and audience binding.
Key responsibilities:
- Registration requires an EIP-191 signed digest bound to chain, registry, agent, operator, and Ed25519 key.
- Rotation updates only the Ed25519 public key and emits an event.
- Operator transfer is two-phase: offer then accept, with collateral checks and slash-pending guards.
Section sources
- SigvaraIdentity.sol:56-62
- SigvaraIdentity.sol:229-242
- SigvaraIdentity.sol:284-309
- SigvaraIdentity.sol:316-324
- SigvaraIdentity.sol:398-456
The architecture separates on-chain identity anchoring from off-chain cryptographic operations:
sequenceDiagram
participant Client as "Client App"
participant SDK as "SigvaraAgent (agent.ts)"
participant Challenge as "challenge.ts"
participant Keys as "keys.ts"
participant Registry as "SigvaraIdentity.sol"
Client->>SDK : Generate agent + private key
SDK->>Keys : seedToKeyPair()
SDK->>Registry : registerAgent(agentAddress, ed25519PubKey, signature)
Registry->>Registry : verifyRegistration()
Registry-->>Client : didHash emitted
Client->>SDK : issueChallenge(peerDid)
SDK->>Challenge : generateChallenge(proverDid, audience, ttl)
Note over SDK,Challenge : Payload includes nonce, timestamp, DID, audience
Client->>SDK : signChallenge(payload)
SDK->>Challenge : signChallenge(payload, secretKey)
Challenge->>Keys : base58Encode(signature)
Challenge-->>Client : base58 signature
Verifier->>Registry : getIdentity(didHash)
Verifier->>Challenge : verifyChallenge(payload, signature, publicKey)
Diagram sources
- agent.ts:13-81
- challenge.ts:43-87
- keys.ts:89-125
- SigvaraIdentity.sol:247-260
- SigvaraIdentity.sol:284-309
- The registry stores a raw 32-byte Ed25519 public key per agent identity.
- Registration binds the public key into the EIP-191 digest, preventing substitution attacks.
- The SDK validates input formats and encodes keys for both on-chain bytes32 and W3C multibase DID documents.
Validation rules:
- Zero public keys are rejected during registration and rotation.
- Public keys must be exactly 32 bytes.
- Multibase encoding adds a multicodec header required by standard resolvers.
Security implications:
- Binding the public key in the registration digest prevents an attacker from intercepting registration and swapping the key.
- Raw 32-byte storage avoids ambiguity; clients must ensure correct encoding when interacting with DIDs.
Section sources
Purpose:
- Allows the operator to replace the stored Ed25519 public key for an agent.
Authorization and validation:
- Caller must be the current operator.
- Slashed agents cannot rotate keys.
- New public key must not be zero.
- No additional signature requirement is enforced by the contract; the operator’s Ethereum signature proves control.
Behavior:
- Updates the stored public key and emits PublicKeyRotated.
- Rotation is allowed even while Suspended, enabling recovery after compromise.
flowchart TD
Start([rotatePublicKey Entry]) --> RequireRegistered["Require registered didHash"]
RequireRegistered --> RequireOperator["Require caller is operator"]
RequireOperator --> CheckSlashed{"Status == Slashed?"}
CheckSlashed --> |Yes| RevertSlashed["Revert SlashedAgentImmutable"]
CheckSlashed --> |No| ValidateNewKey["Validate newEd25519PubKey != 0"]
ValidateNewKey --> UpdateKey["Update stored public key"]
UpdateKey --> EmitEvent["Emit PublicKeyRotated"]
EmitEvent --> End([Exit])
RevertSlashed --> End
Diagram sources
Section sources
These functions implement a two-phase operator transfer mechanism:
-
offerOperatorTransfer:
- Requires operator authorization.
- Prevents transfers if slashed or slash-suspended.
- Validates new operator address is non-zero and different from current operator.
- Emits OperatorTransferOffered without changing ownership yet.
-
cancelOperatorTransfer:
- Either the current operator or the offered recipient can cancel.
- Clears pendingOperator mapping.
-
acceptOperatorTransfer:
- Only the offered recipient can accept.
- Prevents acceptance if slashed or slash-suspended.
- Requires minimum stake via stakeView oracle.
- Transfers operatorship, updates indexes, records change timestamp and transfer count.
sequenceDiagram
participant OldOp as "Old Operator"
participant NewOp as "New Operator"
participant Registry as "SigvaraIdentity.sol"
OldOp->>Registry : offerOperatorTransfer(didHash, newOperator)
Registry->>Registry : requireOperator(), checkSlashState()
Registry-->>OldOp : emit OperatorTransferOffered
NewOp->>Registry : acceptOperatorTransfer(didHash)
Registry->>Registry : requireOfferedRecipient(), checkSlashState()
Registry->>Registry : stakeView.hasMinimumStake(didHash)
Registry->>Registry : update operator, indexes, timestamps
Registry-->>NewOp : emit OperatorTransferred
Diagram sources
Section sources
The SDK implements a secure challenge-response protocol:
-
generateChallenge:
- Produces a structured payload including version header, prover DID, audience, nonce, and timestamp.
- Audience binding prevents replay across verifiers.
- TTL controls expiration.
-
signChallenge:
- Signs the UTF-8 payload with Ed25519 using tweetnacl.
- Returns base58-encoded signature.
-
verifyChallenge:
- Decodes base58 signature and verifies against the payload and public key.
- Rejects invalid lengths and mismatches.
-
isChallengeExpired:
- Parses payload and compares timestamp against current time and max age.
flowchart TD
Start([Generate Challenge]) --> ValidateAudience["Validate audience and prover DID"]
ValidateAudience --> CreatePayload["Create newline-delimited payload"]
CreatePayload --> ReturnChallenge["Return payload, nonce, timestamp, audience, expiresAt"]
SignStart([Sign Challenge]) --> EncodeMessage["Encode payload to UTF-8 bytes"]
EncodeMessage --> SignWithNaCl["nacl.sign.detached(message, secretKey)"]
SignWithNaCl --> Base58Encode["base58Encode(signature)"]
Base58Encode --> ReturnSignature["Return base58 signature"]
VerifyStart([Verify Challenge]) --> DecodeSig["base58Decode(signatureBase58)"]
DecodeSig --> CheckLength{"Signature length == 64?"}
CheckLength --> |No| ReturnFalse["Return false"]
CheckLength --> |Yes| NaClVerify["nacl.sign.detached.verify(message, sig, publicKey)"]
NaClVerify --> ReturnResult["Return verification result"]
Diagram sources
Section sources
- Registration signature binds chain ID, registry address, agent address, operator, and Ed25519 public key, preventing replay and key substitution.
- Operator control is enforced by storing the operator address and checking msg.sender.
- Slash state is terminal for mutations like key rotation and operator transfer.
- Slash suspension lock prevents operators from reactivating mid-slash.
- Collateral checks prevent under-collateralized activations and transfers.
- Challenge-response binds audience to prevent cross-verifier impersonation.
Best practices:
- Store private keys securely offline or in hardware wallets.
- Rotate keys promptly upon suspected compromise.
- Use short TTLs for challenges to limit replay windows.
- Ensure stake remains above minimum before reactivation or transfer.
- Monitor slash events and suspend agents proactively if needed.
Section sources
- SigvaraIdentity.sol:229-242
- SigvaraIdentity.sol:316-324
- SigvaraIdentity.sol:398-456
- challenge.ts:21-41
The components interact as follows:
graph LR
AgentTS["agent.ts"] --> ChallengeTS["challenge.ts"]
AgentTS --> KeysTS["keys.ts"]
ChallengeTS --> KeysTS
Registry["SigvaraIdentity.sol"] --> StakeView["IStakeView interface"]
Tests["SigvaraIdentity.t.sol"] --> Registry
Diagram sources
- agent.ts:1-83
- challenge.ts:1-168
- keys.ts:1-126
- SigvaraIdentity.sol:10-18
- SigvaraIdentity.t.sol:1-848
Section sources
- agent.ts:1-83
- challenge.ts:1-168
- keys.ts:1-126
- SigvaraIdentity.sol:10-18
- SigvaraIdentity.t.sol:1-848
- Operator index removal uses swap-and-pop with recorded positions for constant-time removal where possible, falling back to linear scan for legacy entries.
- Paged queries allow efficient retrieval of large operator agent lists.
- Challenge parsing rejects malformed payloads early to avoid unnecessary computation.
- Base58 encoding/decoding is implemented efficiently but should be used judiciously for large payloads.
Common issues and resolutions:
-
Registration fails due to bad signature:
- Ensure the agent address signs the exact registrationDigest returned by the contract.
- Verify operator and public key match the intended values.
-
Rotation fails:
- Confirm caller is the current operator.
- Check that the agent is not slashed.
- Ensure new public key is non-zero and exactly 32 bytes.
-
Operator transfer fails:
- Verify slash state is not suspended or slashed.
- Ensure stake view reports sufficient collateral.
- Confirm the recipient is the offered address.
-
Challenge verification fails:
- Check payload format and audience binding.
- Ensure signature is base58-encoded and decodes to 64 bytes.
- Validate timestamp is within TTL.
Section sources
- SigvaraIdentity.sol:247-260
- SigvaraIdentity.sol:316-324
- SigvaraIdentity.sol:398-456
- challenge.ts:78-87
SigvaraIdentity provides a robust framework for managing AI agent identities with strong cryptographic guarantees. Ed25519 public keys are securely stored and validated through operator-controlled rotation and challenge-response authentication. The two-phase operator transfer mechanism ensures safe handover of control with collateral and slash-state checks. Following the documented best practices and troubleshooting steps will help maintain secure and reliable key management operations.
- Agent Registration and Lifecycle
- Contract Overview and Architecture
- Economic Security Model and Incentives
- Evidence Verification and Merkle Proofs
- Key Management and Rotation
- Maturity Mechanism and Score Release
- Proposal Workflow and Challenge System
- Score Storage and Data Model
- SigvaraIdentity Contract
- SigvaraOracleBond Contract
- SigvaraReputation Contract
- SigvaraStaking Contract
- Slashing Lifecycle and Dispute Resolution
- Stake Management Functions
- Status Management and Control
- Token and Fee Contracts
- Unbonding Period and Withdrawal Security
- Anti-Manipulation and Security Measures
- Community Flag Processing
- Evidence Sources and Verification
- External Trust Integration
- Merkle Proof System
- Oracle Score Computation
- Payment-Backed Attestations
- Scoring Model and Formulas
- Agent Creation and Key Management
- Agent Operations
- Agent Registration Workflow
- Challenge-Response Authentication
- Gate Access Control
- Integration Patterns and Examples
- SDK Overview and Setup
- Verifier Functions