Skip to content

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

ModeVerificationBest for
softNone — name-based identityDevelopment, single-agent setups
cryptographicEd25519 signature verificationProduction, multi-agent
hybridOptional — accepts bothMigration, mixed environments
import { IdentityMode } from '@stoneforge/quarry';
IdentityMode.SOFT // 'soft' (default)
IdentityMode.CRYPTOGRAPHIC // 'cryptographic'
IdentityMode.HYBRID // 'hybrid'

Set the mode via CLI or config:

Terminal window
sf identity mode cryptographic
# or
sf config set identity.mode cryptographic

Configuration

import {
createIdentityConfig,
DEFAULT_IDENTITY_SYSTEM_CONFIG,
IdentityMode,
} from '@stoneforge/quarry';
// Defaults
DEFAULT_IDENTITY_SYSTEM_CONFIG;
// {
// mode: 'soft',
// timeTolerance: 300000, // 5 minutes
// allowUnregisteredActors: true,
// }
// Custom
const 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

Terminal window
sf identity keygen

Key formats

KeyFormatLength
Public keyBase6444 characters (32 bytes)
Private keyPKCS8 Base64Variable
SignatureBase6488 characters (64 bytes)

Register an agent with a public key

Terminal window
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 body
const requestHash = await hashRequestBody({ action: 'create', data: { /* ... */ } });
// 2. Create signed request
const signedRequest = await createSignedRequest(
{ actor: 'agent-name', requestHash },
privateKey,
);
// Result: { signature, signedAt, actor }

Sign with the CLI

Terminal window
# Sign data
sf identity sign --data "hello world" --sign-key <key> --actor alice
# Sign from file
sf identity sign --file request.json --sign-key-file ~/.stoneforge/private.key
# Sign a pre-computed hash
sf identity sign --hash abc123... --actor alice

Private key resolution order:

  1. --sign-key <key> flag
  2. --sign-key-file <path> flag
  3. STONEFORGE_SIGN_KEY env var
  4. STONEFORGE_SIGN_KEY_FILE env 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

StatusDescription
validSignature is valid
invalidSignature doesn’t match
expiredOutside time tolerance
actor_not_foundEntity not found
no_public_keyEntity has no public key
not_signedNo 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 tolerance
const result = checkTimeTolerance(signedAt, 60000); // 1 minute

Verify with the CLI

Terminal window
sf identity verify \
--signature <sig> \
--public-key <key> \
--signed-at 2024-01-01T00:00:00Z \
--data "hello" \
--actor alice

Actor resolution

When an operation is performed, the actor is resolved from multiple sources in priority order:

PrioritySourceDescription
1 (highest)explicitProvided in operation
2cli_flagFrom --actor flag
3configFrom configuration file
4elementFrom element’s createdBy
systemSystem-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

Terminal window
# Show current identity
sf identity whoami
# Generate keypair
sf identity keygen
# Show/set mode
sf identity mode
sf identity mode cryptographic
# Compute hash
sf identity hash --data "data to hash"

Full setup example

  1. Generate a keypair

    Terminal window
    sf identity keygen
    # Save the public key and private key
  2. Register the agent with the public key

    Terminal window
    sf entity register secure-agent --type agent --public-key "<public-key>"
  3. Enable cryptographic mode

    Terminal window
    sf identity mode cryptographic
  4. 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,
    );
  5. 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 },
    });