Repository navigation
Agent Operations
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
- Appendices
This document explains agent operations in the Sigvara SDK, focusing on:
- Creating agents with Ed25519 key pairs and managing private keys securely
- Deriving public keys and DID identifiers for on-chain registration
- Registering agents on-chain, including digest computation and transaction submission
- Challenge-response authentication between agents and verifiers
- Key rotation procedures to update credentials while maintaining continuity
- Practical lifecycle patterns: creation, registration, updates, and deactivation
- Security best practices for key storage, signing, and error handling
- Integration patterns with wallet providers and transaction signing libraries
The SDK exposes a small, focused API surface that keeps cryptographic details internal while providing clear integration points for wallets, signers, and on-chain contracts.
The SDK is organized by capability:
- Identity and keys: agent identity, DID formatting, and Ed25519 utilities
- Authentication: challenge generation, signing, parsing, and verification
- On-chain interaction: verifier reads, reputation checks, and registration transactions
- Gatekeeping: admission control with replay protection and threshold checks
- Stake management: operator deposit flows for bonding agents
- Public exports: a single entry point re-exporting core types and functions
graph TB
subgraph "SDK Core"
A["SigvaraAgent<br/>agent.ts"]
K["Key Utilities<br/>keys.ts"]
D["DID Helpers<br/>did.ts"]
C["Challenge Engine<br/>challenge.ts"]
V["SigvaraVerifier<br/>verifier.ts"]
G["SigvaraGate<br/>gate.ts"]
S["Staking Helper<br/>stake.ts"]
T["Types & Exports<br/>types.ts / index.ts"]
end
A --> K
A --> D
A --> C
V --> D
V --> C
V --> K
G --> V
G --> C
S --> V
T --> A
T --> V
T --> G
T --> S
T --> C
T --> D
T --> K
Diagram sources
- agent.ts
- keys.ts:1-125
- challenge.ts
- did.ts:1-28
- verifier.ts
- gate.ts
- stake.ts:1-39
- types.ts
- index.ts:1-67
Section sources
- SigvaraAgent: encapsulates an Ed25519 keypair, derives DID and bytes32 public key, issues and signs challenges. It keeps the secret key in a private field, and
signChallengeFor(payload, audience)signs only a challenge addressed to the audience the caller passes (both are new in 1.0.0-alpha.10). - SigvaraVerifier: reads on-chain identity and reputation, verifies signatures against the registered public key, builds DID documents, and registers agents.
- SigvaraGate: provides admission control with challenge issuance, replay protection, status checks, and score thresholds.
- Stake helpers: simplify ERC-20 approvals and staking deposits for operators.
- Key and DID utilities: safe hex/base58 conversions, Ed25519 seed/keypair derivation, multibase encoding for W3C DID Documents.
Key responsibilities:
- Agent lifecycle: create, derive identity, register, rotate keys, deactivate via stake or status changes.
- Authentication: secure challenge-response binding prover, audience, nonce, and timestamp.
- On-chain integration: EIP-191 signed registration digests, event parsing, and contract interactions.
Section sources
The agent architecture separates client-side cryptography from on-chain state and gate policies:
sequenceDiagram
participant Client as "Client App"
participant Agent as "SigvaraAgent"
participant Verifier as "SigvaraVerifier"
participant Chain as "Identity/Staking Contracts"
participant Gate as "SigvaraGate"
Client->>Agent : generate({ agentAddress, chainId })
Agent-->>Client : { agent, privateKey }
Client->>Verifier : registerAgent(signer, agentAddress, publicKeyBytes32, identityAddress, proof)
Verifier->>Chain : compute registrationDigest(...)
Verifier->>Chain : registerAgent(agentAddress, ed25519PubKeyBytes32, signature)
Chain-->>Verifier : emit AgentRegistered(didHash)
Verifier-->>Client : { didHash, txHash }
Client->>Gate : challenge(did)
Gate-->>Client : Challenge(payload, nonce, expiresAt)
Client->>Agent : signChallengeFor(payload, audience)
Agent-->>Client : base58(signature)
Client->>Gate : admit(did, challenge, signature)
Gate->>Verifier : verifySignature(did, payload, signature, ttl, audience)
Verifier->>Chain : getIdentity(did), getTotalScore(did)
Chain-->>Verifier : identity, score
Verifier-->>Gate : verified?
Gate-->>Client : AdmitResult(ok, reason, score)
Diagram sources
- Ed25519 keypair generation uses a cryptographically random 32-byte seed. The SDK returns both the agent instance and the raw private key (hex) for secure storage.
- Private key validation enforces exact length and valid hex characters; invalid input throws descriptive errors rather than silently producing incorrect keys.
- Public key derivation produces:
- bytes32 hex string for on-chain registration
- multibase representation for W3C DID Documents
- DID formatting and hashing mirror on-chain logic, enabling off-chain derivation of the same identifier hash used by contracts.
flowchart TD
Start(["Create Agent"]) --> GenSeed["Generate 32-byte seed"]
GenSeed --> BuildAgent["Construct SigvaraAgent(seed, agentAddress, chainId)"]
BuildAgent --> PubKey["Derive Ed25519 public key"]
PubKey --> Bytes32["Convert to bytes32 hex"]
Bytes32 --> Did["Format DID and compute DID hash"]
Did --> Ready(["Agent ready for registration"])
Diagram sources
Section sources
Registration binds three entities: the agent address, the operator address, and the Ed25519 public key. The SDK computes the registration digest from the contract, signs it with the agent’s EOA signer, and submits the transaction.
Key steps:
- Compute registration digest using the contract function
- Sign the raw 32-byte digest (EIP-191 prefix applied by the signer)
- Submit registerAgent with agent address, bytes32 public key, and signature
- Parse logs to extract didHash and txHash
sequenceDiagram
participant Operator as "Operator Wallet"
participant Verifier as "registerAgent()"
participant Contract as "Identity Contract"
Operator->>Verifier : call with (agentAddress, pubKeyBytes32, identityAddress, proof)
Verifier->>Contract : registrationDigest(agentAddress, operator, pubKeyBytes32)
Contract-->>Verifier : digest (unprefixed)
Verifier->>Verifier : signMessage(raw digest)
Verifier->>Contract : registerAgent(agentAddress, pubKeyBytes32, signature)
Contract-->>Verifier : emit AgentRegistered(didHash)
Verifier-->>Operator : { didHash, txHash }
Diagram sources
Section sources
The SDK implements a robust challenge-response protocol:
- v2 payloads bind prover DID, audience, nonce, and timestamp
- Audience binding prevents relay attacks across verifiers
- Nonce ensures single-use within TTL window
- Signature is base58-encoded Ed25519 over UTF-8 payload
- The signer can check the audience before signing with
signChallengeFor(payload, audience)(1.0.0-alpha.10 and later). The agent'ssignChallenge, which signed whatever it was handed, is removed in 1.0.0-alpha.12
Verification flow:
- Parse and validate payload structure
- Check prover DID matches expected agent
- Require the expected audience, which a call cannot leave out, and enforce audience binding
- Reject expired challenges, and ones dated more than 60 seconds ahead of the verifier’s clock (
MAX_FUTURE_SKEW_SECONDS) - Validate signature width and decode safely
- Fetch on-chain public key and verify signature
flowchart TD
Start(["Verify Signature"]) --> Parse["Parse challenge payload"]
Parse --> CheckDid{"Prover DID matches?"}
CheckDid --> |No| FailBadProof["Return bad_proof"]
CheckDid --> |Yes| CheckAudience{"Audience bound and correct?"}
CheckAudience --> |No| FailBadProof
CheckAudience --> |Yes| CheckExpiry{"Not expired?"}
CheckExpiry --> |No| FailBadProof
CheckExpiry --> |Yes| WidthCheck["Validate signature length"]
WidthCheck --> Decode["Decode base58 signature"]
Decode --> FetchKey["Fetch on-chain public key"]
FetchKey --> Verify["Verify Ed25519 signature"]
Verify --> Result(["true/false"])
Diagram sources
Section sources
SigvaraGate orchestrates admission decisions:
- Issues challenges bound to a stable audience
- Verifies signatures and enforces replay protection via nonces
- Checks agent activity status and reputation threshold
- Returns explicit reasons for refusal to aid debugging
- Reads status, the bond held now and the score in parallel (standing is still named before score in the answer), and refuses to be built with a threshold that is not a finite number of 0 or more, or a
ttlSecondsormaxScoreAgeSecondsthat is not positive - Compares the spendable score and checks the stored status and the bond held now against the current minimum stake, as
SigvaraReader.isTrusteddoes for an on-chain consumer (alpha.9 does not check the bond). WithmaxScoreAgeSecondsit also refuses a score nobody has refreshed
Nonce store:
- In-memory default for single-process deployments
- Optional distributed store with atomic consume for fleet safety.
atomicNonceStorebuilds one from a RedisSET NXor a database insert, andinstances: 'multiple'makes the gate refuse to start without one (1.0.0-alpha.13) -
profile: 'production'(1.0.0-alpha.14) makes the gate refuse to start untilinstancesandmaxScoreAgeSecondsare both set - Pruning strategy bounds memory usage by TTL
classDiagram
class SigvaraGate {
+challenge(did) Challenge
+admit(did, challenge, signature) AdmitResult
-consumeNonce(nonce, expiresAt, nowSeconds) boolean
-verifier : SigvaraVerifier
-nonces : NonceStore
-ttl : number
-threshold : number
-audience : string
}
class MemoryNonceStore {
+seen(nonce) boolean
+add(nonce, expiresAt) void
+prune(nowSeconds) void
+consume(nonce, expiresAt) boolean
}
class SigvaraVerifier {
+verifySignature(did, payload, signature, maxAge, expectedAudience) boolean
+isActive(did) boolean
+getTotalScore(did) number
}
SigvaraGate --> SigvaraVerifier : "uses"
SigvaraGate --> MemoryNonceStore : "owns"
Diagram sources
Section sources
Operators deposit stake to activate agents:
- Approve token allowance if needed
- Deposit stake for the agent’s didHash
- Return transaction hashes for approval and deposit
flowchart TD
Start(["Deposit Stake"]) --> CheckAllowance["Check token allowance"]
CheckAllowance --> NeedApprove{"Allowance < amount?"}
NeedApprove --> |Yes| Approve["token.approve(staking, amount)"]
NeedApprove --> |No| Deposit["staking.depositStake(didHash, amount)"]
Approve --> Deposit
Deposit --> Done(["txHash, approveTxHash?"])
Diagram sources
Section sources
To rotate agent credentials:
- Generate a new Ed25519 keypair and retain the new private key securely
- Derive the new bytes32 public key
- Call
rotatePublicKey(didHash, newEd25519PubKey)onSigvaraIdentityfrom the operator wallet. The SDK carries the ABI entry but no helper, andregisterAgentcannot change a key: adidHashregisters once, so a second registration reverts withAlreadyRegistered - Keep using the old key until the rotation transaction is confirmed
- Update any stored references to the agent’s public key and DID
Operational notes:
- The DID hash is immutable per agent address and chain; only the public key changes
- Keep the operator signer consistent unless also rotating operator control
- After rotation, verify on-chain identity reflects the new public key before relying on it
- Suspended agents may rotate, so after a suspected compromise the operator can suspend the agent first (
updateStatus(didHash, Suspended)), rotate, and then reinstate it. Verifiers that check status (isActive,SigvaraGate) refuse the agent meanwhile, andSigvaraVerifier.verifySignaturealone does not check status. Slashed agents cannot rotate
Section sources
Agents can become inactive through:
- Suspension or slashing by governance mechanisms
- Insufficient stake causing PendingBond or non-active status
- Explicit deactivation flows defined by on-chain contracts
Integration guidance:
- Always check isActive before admitting work. It reads the stored status; the gate also checks the bond held now, and an on-chain consumer that wants that plus the score age uses
SigvaraReader(isTrusted,isTrustedFresh) - Use gate admission results to distinguish bad proofs, replays, not active, stale scores (if you set
maxScoreAgeSeconds) and below threshold - Monitor stake levels and reputation scores to proactively maintain Active status
Section sources
The SDK exhibits low coupling and high cohesion:
- SigvaraAgent depends on keys, did, and challenge modules
- SigvaraVerifier depends on did, challenge, keys, and contract ABIs
- SigvaraGate composes SigvaraVerifier and a pluggable NonceStore
- Stake helper depends on contract ABIs and ethers
graph LR
Agent["agent.ts"] --> Keys["keys.ts"]
Agent --> Did["did.ts"]
Agent --> Challenge["challenge.ts"]
Verifier["verifier.ts"] --> Did
Verifier --> Challenge
Verifier --> Keys
Gate["gate.ts"] --> Verifier
Gate --> Challenge
Stake["stake.ts"] --> Verifier
Diagram sources
Section sources
- Lazy contract instantiation in SigvaraVerifier avoids repeated ABI parsing overhead
- Signature width checks prevent expensive decoding of malformed inputs
- Reputation total score read is optimized to avoid unnecessary tuple decoding
- Nonce pruning in MemoryNonceStore runs proportional to expired entries, not live set size
- Challenge TTLs should be tuned to balance security and latency
- Chain reads give up after 30 seconds by default (
timeoutMsonSigvaraVerifierandSigvaraGate, where ethers alone waits five minutes), andSigvaraGate.admitreads status, bond and score together, so an admit costs two round trips rather than three
Common issues and resolutions:
- Invalid hex private key: ensure exactly 64 hex characters; the SDK throws precise errors indicating the offending character and position
- Double-prefixed registration digest: sign the raw 32-byte digest returned by the contract; do not pre-hash or re-prefix
- Replay detection: implement nonce tracking at the gate; use a distributed NonceStore for multi-instance deployments
- Audience mismatch: always include a stable audience in challenges; verifiers will reject payloads without matching audience
- Expired challenges: enforce TTL checks and prompt clients to request fresh challenges. A challenge dated more than 60 seconds ahead of the verifier’s clock is refused as not fresh too
- Base58 signature errors: catch decode exceptions and treat them as failed proofs rather than server errors
Section sources
The Sigvara SDK provides a secure, well-structured foundation for agent operations:
- Clear separation of cryptographic primitives, identity management, and on-chain interactions
- Robust challenge-response authentication with audience binding and replay protection
- Straightforward registration and stake workflows compatible with standard wallet providers
- Extensible gatekeeping with explicit admission reasons and scalable nonce stores
Adopt these patterns to build reliable agent systems with strong security guarantees and clear operational visibility.
- Create agent and derive identity:
- Register agent on-chain:
- Issue and sign challenge:
- Verify signature and admit via gate:
- Deposit stake:
Section sources
- 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