Identity
The identity system controls how Stoneforge verifies who performed each operation. It supports three modes — from zero-friction development to production-grade cryptographic verification.
Modes
| Mode | Verification | Best for |
|---|---|---|
soft | None — name-based identity | Development, single-agent setups |
cryptographic | Ed25519 signature verification | Production, multi-agent |
hybrid | Optional — accepts both | Migration, mixed environments |
import { IdentityMode } from '@stoneforge/quarry';
IdentityMode.SOFT // 'soft' (default)IdentityMode.CRYPTOGRAPHIC // 'cryptographic'IdentityMode.HYBRID // 'hybrid'Set the mode via CLI or config:
sf identity mode cryptographic# orsf config set identity.mode cryptographicConfiguration
import { createIdentityConfig, DEFAULT_IDENTITY_SYSTEM_CONFIG, IdentityMode,} from '@stoneforge/quarry';
// DefaultsDEFAULT_IDENTITY_SYSTEM_CONFIG;// {// mode: 'soft',// timeTolerance: 300000, // 5 minutes// allowUnregisteredActors: true,// }
// Customconst config = createIdentityConfig({ mode: IdentityMode.CRYPTOGRAPHIC, timeTolerance: 60000, // 1 minute allowUnregisteredActors: false,});| Parameter | Type | Default | Description |
|---|---|---|---|
mode | IdentityMode | soft | Identity verification mode. |
timeTolerance | number | 300000 | Maximum age in ms for valid signatures (5 minutes). |
allowUnregisteredActors | boolean | true | Whether to accept operations from actors without registered entities. |
Key management
Generate a keypair
sf identity keygenimport { generateEd25519Keypair } from '@stoneforge/quarry';
const { publicKey, privateKey } = await generateEd25519Keypair();// publicKey: 44-character base64 (32 bytes)// privateKey: PKCS8 base64 (for signing)Key formats
| Key | Format | Length |
|---|---|---|
| Public key | Base64 | 44 characters (32 bytes) |
| Private key | PKCS8 Base64 | Variable |
| Signature | Base64 | 88 characters (64 bytes) |
Register an agent with a public key
sf entity register secure-agent --type agent --public-key "base64key..."Or programmatically:
const agent = await api.create({ type: 'entity', createdBy: adminId, name: 'secure-agent', entityType: 'agent', publicKey: publicKey,});Signing requests
Signature format
The signed data format is: actor|signedAt|requestHash
import { constructSignedData, parseSignedData } from '@stoneforge/quarry';
const signedData = constructSignedData({ actor: 'agent-name', signedAt: '2024-01-15T10:30:00.000Z', requestHash: 'a1b2c3...', // SHA256 hex (64 chars)});// 'agent-name|2024-01-15T10:30:00.000Z|a1b2c3...'Create a signed request
import { createSignedRequest, hashRequestBody } from '@stoneforge/quarry';
// 1. Hash the request bodyconst requestHash = await hashRequestBody({ action: 'create', data: { /* ... */ } });
// 2. Create signed requestconst signedRequest = await createSignedRequest( { actor: 'agent-name', requestHash }, privateKey,);
// Result: { signature, signedAt, actor }Sign with the CLI
# Sign datasf identity sign --data "hello world" --sign-key <key> --actor alice
# Sign from filesf identity sign --file request.json --sign-key-file ~/.stoneforge/private.key
# Sign a pre-computed hashsf identity sign --hash abc123... --actor alicePrivate key resolution order:
--sign-key <key>flag--sign-key-file <path>flagSTONEFORGE_SIGN_KEYenv varSTONEFORGE_SIGN_KEY_FILEenv var
Verification
Full verification pipeline
import { verifySignature, type EntityLookup } from '@stoneforge/quarry';
const lookupEntity: EntityLookup = async (actor) => { const entity = await api.lookupEntityByName(actor); return entity ? { publicKey: entity.publicKey } : null;};
const result = await verifySignature({ signedRequest: { signature, signedAt, actor }, requestHash, lookupEntity, config: { mode: IdentityMode.CRYPTOGRAPHIC },});
if (result.allowed) { console.log(`Verified: ${result.actor}`);} else { console.error(`Failed: ${result.error}`);}Verification statuses
| Status | Description |
|---|---|
valid | Signature is valid |
invalid | Signature doesn’t match |
expired | Outside time tolerance |
actor_not_found | Entity not found |
no_public_key | Entity has no public key |
not_signed | No signature provided |
Time tolerance
import { checkTimeTolerance, DEFAULT_TIME_TOLERANCE } from '@stoneforge/quarry';
// Default: 5 minutes (300000ms)const result = checkTimeTolerance(signedAt, DEFAULT_TIME_TOLERANCE);// { valid: boolean, ageMs: number, expiredBy?: number }
// Custom toleranceconst result = checkTimeTolerance(signedAt, 60000); // 1 minuteVerify with the CLI
sf identity verify \ --signature <sig> \ --public-key <key> \ --signed-at 2024-01-01T00:00:00Z \ --data "hello" \ --actor aliceActor resolution
When an operation is performed, the actor is resolved from multiple sources in priority order:
| Priority | Source | Description |
|---|---|---|
| 1 (highest) | explicit | Provided in operation |
| 2 | cli_flag | From --actor flag |
| 3 | config | From configuration file |
| 4 | element | From element’s createdBy |
| — | system | System-generated |
Validation functions
import { isValidPublicKey, // 44-char base64 isValidSignature, // 88-char base64 isValidRequestHash, // 64-char hex isValidIdentityMode, validatePublicKey, // Throws on invalid validateSignature, validateRequestHash, validateIdentityMode,} from '@stoneforge/quarry';CLI commands
# Show current identitysf identity whoami
# Generate keypairsf identity keygen
# Show/set modesf identity modesf identity mode cryptographic
# Compute hashsf identity hash --data "data to hash"Full setup example
-
Generate a keypair
Terminal window sf identity keygen# Save the public key and private key -
Register the agent with the public key
Terminal window sf entity register secure-agent --type agent --public-key "<public-key>" -
Enable cryptographic mode
Terminal window sf identity mode cryptographic -
Sign requests with the private key
import { createSignedRequest, hashRequestBody } from '@stoneforge/quarry';const requestHash = await hashRequestBody(requestBody);const signedRequest = await createSignedRequest({ actor: 'secure-agent', requestHash },privateKey,); -
Verify on the receiving end
import { verifySignature } from '@stoneforge/quarry';const result = await verifySignature({signedRequest,requestHash,lookupEntity: (actor) => api.lookupEntityByName(actor),config: { mode: IdentityMode.CRYPTOGRAPHIC },});