Repository navigation
Agent Creation and Key Management
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
This document explains how to create a Sigvara agent and manage its cryptographic keys. It focuses on:
- Ed25519 key pair generation using tweetnacl
- Seed derivation and secure storage practices
- The recommended entry point
SigvaraAgent.generate() - Parameters such as
agentAddress,chainId, andchallengeTtlSeconds - The relationship between private keys (seeds), public keys, and agent addresses
- Converting public keys to the
bytes32format used for on-chain registration - Security best practices for key handling, memory management, and protection against leakage
The SDK is a TypeScript library that provides agent identity, challenge-response authentication, and integration with on-chain state. Its runtime depends on ethers v6 and tweetnacl.
Section sources
The relevant code for agent creation and key management lives under the SDK package:
-
packages/sdk/src/agent.ts: DefinesSigvaraAgent, including the recommendedgenerate()factory and challenge signing methods. -
packages/sdk/src/keys.ts: Implements Ed25519 seed-to-keypair conversion, hex/base58 utilities, andbytes32public key encoding. -
packages/sdk/src/types.ts: Declares shared types, includingChallengeand DID-related structures. -
packages/sdk/test/keys.test.ts: Validates key derivation, encoding, and error behavior.
graph TB
A["agent.ts<br/>SigvaraAgent"] --> B["keys.ts<br/>seedToKeyPair, pubKeyToBytes32"]
A --> C["types.ts<br/>Challenge"]
D["keys.test.ts<br/>Tests"] --> B
E["README.md<br/>SDK overview"] --> A
Diagram sources
Section sources
-
SigvaraAgent: Encapsulates an Ed25519 key pair, agent address, chain ID, and challenge TTL. It exposes:-
generate(): Factory that creates a new random seed, builds an agent instance, and returns both the agent and the raw private key (seed) for secure storage. -
publicKeyBytes32: Returns the 32-byte public key as a0x-prefixed hex string suitable for on-chain registration. -
issueChallenge(): Challenge-response helper for agent-to-agent authentication. The agent'ssignChallenge(), which signed whatever it was handed, is removed in 1.0.0-alpha.12: a call does not compile in TypeScript, and in JavaScript it throws an error that namessignChallengeFor. -
signChallengeFor(payload, audience): Signs only a v2 challenge that names this agent as the prover and names exactly the audience the caller passes; anything else throws and nothing is signed. New in 1.0.0-alpha.10, and the only way for an agent to sign a challenge from 1.0.0-alpha.12.
-
-
keys.ts: Provides:-
seedToKeyPair(seed): Derives an Ed25519 key pair from a 32-byte seed or a 64-hex-character string. -
pubKeyToBytes32(pubKey)/bytes32ToPubKey(bytes32): Converts between raw public key bytes and thebytes32hex representation. - Hex and base58 utilities for safe encoding and decoding.
-
Section sources
At a high level, agent creation and key management follow this flow:
- Generate a cryptographically secure 32-byte seed using tweetnacl.
- Derive an Ed25519 key pair from the seed.
- Construct a
SigvaraAgentwith the derived key pair, the providedagentAddress,chainId, and optionalchallengeTtlSeconds. - Use
publicKeyBytes32to obtain the on-chain registration value. - Store the raw private key (seed) securely; it cannot be recovered from the agent instance.
sequenceDiagram
participant App as "Your Application"
participant Agent as "SigvaraAgent"
participant NaCl as "tweetnacl"
participant Keys as "keys.ts"
App->>Agent : generate({ agentAddress, chainId, challengeTtlSeconds? })
Agent->>NaCl : randomBytes(32)
NaCl-->>Agent : seed
Agent->>Keys : seedToKeyPair(seed)
Keys-->>Agent : { publicKey, secretKey }
Agent-->>App : { agent, privateKey : "0x" + hex(seed) }
App->>Agent : agent.publicKeyBytes32
Agent->>Keys : pubKeyToBytes32(publicKey)
Keys-->>Agent : "0x" + hex(publicKey)
Agent-->>App : bytes32 public key
Diagram sources
SigvaraAgent holds:
- An internal Ed25519 key pair (
publicKey,secretKey), held in a JavaScript private field (#keyPair).JSON.stringify(agent), a logged agent and an error reporter’s snapshot do not carry it. On alpha.9 it is an ordinary property that all of them include, so the key of an alpha.9 or earlier agent that was ever logged or serialized should be treated as exposed and rotated. - The canonical
agentAddress - The
chainIdused for DID computation - The default challenge TTL in seconds
Recommended workflow:
- Call
SigvaraAgent.generate()to create a new agent and receive the raw private key (seed). - Immediately store the returned private key securely.
- Use
agent.publicKeyBytes32for on-chain registration. - Use
agent.issueChallenge()andagent.signChallengeFor(payload, audience)for peer authentication flows.
classDiagram
class SigvaraAgent {
-keyPair : KeyPair
-_agentAddress : string
-_chainId : number
-_challengeTtl : number
+constructor(options)
+static generate(options)
+agentAddress : string
+did : string
+didHash : string
+publicKeyBytes32 : string
+issueChallenge(peerDid, ttlSeconds?)
+signChallengeFor(payload, audience)
}
Diagram sources
Section sources
The SDK uses tweetnacl’s Ed25519 implementation:
-
seedToKeyPair()accepts either a 32-byteUint8Arrayor a 64-hex-character string (with or without0x). - It validates length and content before calling
nacl.sign.keyPair.fromSeed(). - Deterministic derivation ensures the same seed always produces the same key pair.
flowchart TD
Start(["seedToKeyPair(seed)"]) --> CheckType{"seed is string?"}
CheckType --> |Yes| NormalizeHex["Normalize '0x' prefix"]
NormalizeHex --> ValidateLength{"hex length == 64?"}
ValidateLength --> |No| ThrowLen["Throw length error"]
ValidateLength --> |Yes| ParseHex["Parse hex to bytes"]
CheckType --> |No| ValidateBytes{"length == 32?"}
ValidateBytes --> |No| ThrowBytes["Throw length error"]
ValidateBytes --> |Yes| UseBytes["Use seed bytes"]
ParseHex --> Derive["nacl.sign.keyPair.fromSeed(seedBytes)"]
UseBytes --> Derive
Derive --> Return(["{ publicKey, secretKey }"])
Diagram sources
Section sources
Public keys are represented as bytes32 hex strings prefixed with 0x:
-
pubKeyToBytes32()converts a 32-byte public key to a0x-prefixed hex string. -
bytes32ToPubKey()performs the reverse conversion. - Tests assert the expected format and round-trip behavior.
flowchart TD
Pub(["Ed25519 public key bytes"]) --> CheckLen{"length == 32?"}
CheckLen --> |No| ErrPub["Throw length error"]
CheckLen --> |Yes| ToHex["Convert to hex"]
ToHex --> Prefix["Prepend '0x'"]
Prefix --> Bytes32["bytes32 hex string"]
Diagram sources
Section sources
- Private key (seed): A 32-byte secret used to derive the Ed25519 key pair. It must be kept secret and stored securely.
- Public key: Derived from the seed; used for verification and on-chain registration as
bytes32. - Agent address: Provided by the caller during construction; it identifies the agent on-chain and is used to compute the DID and DID hash.
- The agent address does not derive the key pair; instead, the seed derives the key pair, and the agent address is bound to the agent instance for DID and registration purposes.
graph LR
Seed["Private key (seed)<br/>32 bytes"] --> KP["Ed25519 key pair<br/>(publicKey, secretKey)"]
KP --> Pub["Public key<br/>32 bytes"]
Pub --> Bytes32["bytes32 hex<br/>for on-chain registration"]
Addr["Agent address<br/>provided at construction"] --> DID["DID and DID hash"]
Diagram sources
Section sources
- Call
SigvaraAgent.generate()withagentAddressandchainId. Optionally passchallengeTtlSeconds. - The method returns
{ agent, privateKey }. - Store
privateKeyimmediately and securely.
Reference example usage is documented in the SDK README.
Section sources
- Treat the returned
privateKeyas the master secret. - Do not log it, print it, or transmit it unencrypted.
- Persist it using a secure secrets manager or hardware-backed storage solution appropriate for your environment.
- Use
agent.publicKeyBytes32to obtain thebytes32hex string for on-chain registration. - For DID documents, use multibase encoding helpers when required by external resolvers.
Section sources
- Memory management:
- Avoid keeping the seed in long-lived global variables.
- Clear sensitive buffers after use where possible.
- Minimize logging and telemetry around private keys.
- Secure storage:
- Use platform-native secure storage (e.g., OS keychain, HSM, cloud KMS) rather than plain files or environment variables.
- Encrypt secrets at rest and restrict access via least-privilege IAM policies.
- Protection against leakage:
- Never embed seeds in source control, logs, stack traces, or client-side bundles.
- Validate inputs strictly to avoid silent misdecoding that could produce unexpected keys.
- Challenge-response security:
- Always bind challenges to an audience you control to prevent relay attacks.
- Check the audience before you sign:
signChallengeFor(payload, audience)does. The agent'ssignChallenge(), which did not, is removed in 1.0.0-alpha.12.signChallengeForis in 1.0.0-alpha.10 and later. - Implement nonce tracking to prevent replay attacks across instances.
Section sources
The following diagram shows the primary dependencies among SDK modules involved in agent creation and key management.
graph TB
Agent["agent.ts<br/>SigvaraAgent"] --> Keys["keys.ts<br/>seedToKeyPair, pubKeyToBytes32"]
Agent --> Types["types.ts<br/>Challenge"]
NaCl["tweetnacl"] --> Keys
NaCl --> Agent
Tests["keys.test.ts"] --> Keys
Diagram sources
Section sources
- Key derivation is lightweight and deterministic; performance impact is negligible compared to network calls.
- Avoid unnecessary conversions between byte arrays and hex strings in hot paths.
- Reuse agent instances where appropriate to minimize repeated key material handling.
Common issues and resolutions:
- Invalid seed length:
- Ensure the seed is exactly 32 bytes or a 64-hex-character string.
- Errors will be thrown if the length is incorrect.
- Non-hex characters in input:
- The SDK now rejects non-hex characters explicitly, reporting the offending character and position.
- Wrong public key length:
- Public key conversion functions require exactly 32 bytes; otherwise, they throw.
- Replay and relay risks:
- Always include an audience in challenges and verify signatures with the same audience.
- On alpha.9,
signChallenge()is the only signer and it signs blind, so check the payload’s audience yourself before you sign. - Track nonces to prevent replay attacks, especially in multi-instance deployments.
Section sources
The Sigvara SDK provides a clear, secure path to create agents and manage their cryptographic keys:
- Use
SigvaraAgent.generate()as the single entry point for new agent creation. - Store the returned private key (seed) securely and never expose it.
- Convert public keys to
bytes32for on-chain registration. - Follow strict input validation and challenge-binding practices to protect against misuse and relay attacks.
- 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