Skip to content

Agent Creation and Key Management

RunTimeAdmin edited this page Nov 3, 2026 · 22 revisions

Agent Creation & Key Management

**Referenced Files in This Document** - [agent.ts](https://github.com/RunTimeAdmin/Sigvara/blob/main/packages/sdk/src/agent.ts) - [keys.ts](https://github.com/RunTimeAdmin/Sigvara/blob/main/packages/sdk/src/keys.ts) - [types.ts](https://github.com/RunTimeAdmin/Sigvara/blob/main/packages/sdk/src/types.ts) - [README.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/packages/sdk/README.md) - [keys.test.ts](https://github.com/RunTimeAdmin/Sigvara/blob/main/packages/sdk/test/keys.test.ts) - [CHANGELOG.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/packages/sdk/CHANGELOG.md)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion

Introduction

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, and challengeTtlSeconds
  • The relationship between private keys (seeds), public keys, and agent addresses
  • Converting public keys to the bytes32 format 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

Project Structure

The relevant code for agent creation and key management lives under the SDK package:

  • packages/sdk/src/agent.ts: Defines SigvaraAgent, including the recommended generate() factory and challenge signing methods.
  • packages/sdk/src/keys.ts: Implements Ed25519 seed-to-keypair conversion, hex/base58 utilities, and bytes32 public key encoding.
  • packages/sdk/src/types.ts: Declares shared types, including Challenge and 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
Loading

Diagram sources

Section sources

Core Components

  • 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 a 0x-prefixed hex string suitable for on-chain registration.
    • issueChallenge(): Challenge-response helper for agent-to-agent authentication. The agent's signChallenge(), 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 names signChallengeFor.
    • 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 the bytes32 hex representation.
    • Hex and base58 utilities for safe encoding and decoding.

Section sources

Architecture Overview

At a high level, agent creation and key management follow this flow:

  1. Generate a cryptographically secure 32-byte seed using tweetnacl.
  2. Derive an Ed25519 key pair from the seed.
  3. Construct a SigvaraAgent with the derived key pair, the provided agentAddress, chainId, and optional challengeTtlSeconds.
  4. Use publicKeyBytes32 to obtain the on-chain registration value.
  5. 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
Loading

Diagram sources

Detailed Component Analysis

SigvaraAgent and Key Lifecycle

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 chainId used 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.publicKeyBytes32 for on-chain registration.
  • Use agent.issueChallenge() and agent.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)
}
Loading

Diagram sources

Section sources

Ed25519 Seed Derivation and Key Pair Generation

The SDK uses tweetnacl’s Ed25519 implementation:

  • seedToKeyPair() accepts either a 32-byte Uint8Array or a 64-hex-character string (with or without 0x).
  • 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 }"])
Loading

Diagram sources

Section sources

bytes32 Public Key Format for On-Chain Registration

Public keys are represented as bytes32 hex strings prefixed with 0x:

  • pubKeyToBytes32() converts a 32-byte public key to a 0x-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"]
Loading

Diagram sources

Section sources

Relationship Between Private Keys, Public Keys, and Agent Addresses

  • 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"]
Loading

Diagram sources

Section sources

Practical Examples

Generate a New Agent

  • Call SigvaraAgent.generate() with agentAddress and chainId. Optionally pass challengeTtlSeconds.
  • The method returns { agent, privateKey }.
  • Store privateKey immediately and securely.

Reference example usage is documented in the SDK README.

Section sources

Securely Store the Private Key

  • Treat the returned privateKey as 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.

Derive Public Key Representations

  • Use agent.publicKeyBytes32 to obtain the bytes32 hex string for on-chain registration.
  • For DID documents, use multibase encoding helpers when required by external resolvers.

Section sources

Security Best Practices for Key Handling

  • 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's signChallenge(), which did not, is removed in 1.0.0-alpha.12. signChallengeFor is in 1.0.0-alpha.10 and later.
    • Implement nonce tracking to prevent replay attacks across instances.

Section sources

Dependency Analysis

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
Loading

Diagram sources

Section sources

Performance Considerations

  • 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.

Troubleshooting Guide

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

Conclusion

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 bytes32 for on-chain registration.
  • Follow strict input validation and challenge-binding practices to protect against misuse and relay attacks.

Overview

Clone this wiki locally