Skip to main content

Signer Backend Options

Agentic Wallet supports five signer backends for different security and operational requirements. All backends implement the same KeyProvider interface, allowing seamless switching without code changes.
WALLET_SIGNER_BACKEND=encrypted-file|memory|kms|hsm|mpc

Encrypted-File

Local development and prototyping

Memory

Ephemeral testing (keys not persisted)

KMS

Managed key governance and rotation

HSM

Hardware-rooted custody for compliance

MPC

Distributed threshold custody

Decision Matrix

BackendUse CasePersistenceRecoveryProduction Ready
encrypted-fileLocal development, demosEncrypted files on diskManual backupNo
memoryIntegration tests, CIIn-memory onlyNone (ephemeral)No
kmsManaged cloud environmentsExternal KMSKMS backup/recoveryYes
hsmHigh-compliance deploymentsHardware security moduleHSM failoverYes
mpcDistributed custodyThreshold secret shares2-of-3 reconstructionYes

Encrypted-File Backend

Overview

Keys stored as encrypted files on local disk using AES-256-GCM. Best for:
  • Local development and prototyping
  • Single-machine deployments
  • Quick setup and iteration
Not suitable for:
  • Production environments
  • Multi-node deployments
  • High-security requirements

Configuration

WALLET_SIGNER_BACKEND=encrypted-file
WALLET_KEY_ENCRYPTION_SECRET=your-secret-here  # Required
WALLET_ENGINE_DATA_DIR=/path/to/data           # Optional
The WALLET_KEY_ENCRYPTION_SECRET must be kept secure. If compromised, all encrypted keys are at risk.

How It Works

// From services/wallet-engine/src/key-provider/encrypted-file-key-provider.ts

export class EncryptedFileKeyProvider implements KeyProvider {
  async save(walletId: string, keypair: Keypair): Promise<void> {
    const keyFile = path.join(this.keysDir, `${walletId}.json`);
    const secretBytes = JSON.stringify(Array.from(keypair.secretKey));
    const encrypted = encryptText(secretBytes, this.encryptionSecret);
    await fs.writeFile(keyFile, encrypted, 'utf8');
  }

  async load(walletId: string): Promise<Keypair> {
    const keyFile = path.join(this.keysDir, `${walletId}.json`);
    const encrypted = await fs.readFile(keyFile, 'utf8');
    const secretBytes = JSON.parse(decryptText(encrypted, this.encryptionSecret));
    return Keypair.fromSecretKey(Uint8Array.from(secretBytes));
  }
}

File Format

Keys stored in JSON format:
{
  "iv": "base64-encoded-iv",
  "encrypted": "base64-encoded-ciphertext",
  "authTag": "base64-encoded-auth-tag"
}
  • Algorithm: AES-256-GCM (authenticated encryption)
  • Key derivation: SHA-256 hash of WALLET_KEY_ENCRYPTION_SECRET
  • IV: Random 16 bytes per encryption
  • Authentication: GCM provides integrity verification

File Locations

$WALLET_ENGINE_DATA_DIR/keys/{walletId}.json
Example:
services/wallet-engine/data/keys/
├── f47ac10b-58cc-4372-a567-0e02b2c3d479.json
├── 9d2c8f6e-4b1a-4c3d-8e5f-6a7b8c9d0e1f.json
└── ...

Security Considerations

1

Strong Secret

Generate secret with openssl rand -base64 32
2

File Permissions

Set restrictive permissions:
chmod 700 $WALLET_ENGINE_DATA_DIR/keys
chmod 600 $WALLET_ENGINE_DATA_DIR/keys/*
3

Backup Strategy

Implement encrypted backups of key directory
4

Secret Management

Store WALLET_KEY_ENCRYPTION_SECRET in environment, not in code

Memory Backend

Overview

Keys held in process memory only, never persisted to disk. Best for:
  • Integration tests
  • CI/CD pipelines
  • Ephemeral testing environments
Not suitable for:
  • Any production use
  • Development with persistent wallets
  • Scenarios requiring key recovery

Configuration

WALLET_SIGNER_BACKEND=memory
# No additional configuration required

How It Works

// From services/wallet-engine/src/key-provider/memory-key-provider.ts

export class MemoryKeyProvider implements KeyProvider {
  private readonly keys = new Map<string, Keypair>();

  async save(walletId: string, keypair: Keypair): Promise<void> {
    this.keys.set(walletId, keypair);
  }

  async load(walletId: string): Promise<Keypair> {
    const keypair = this.keys.get(walletId);
    if (!keypair) {
      throw new Error(`Memory key provider: wallet ${walletId} not found`);
    }
    return keypair;
  }
}

Characteristics

  • Lifetime: Keys lost when process terminates
  • Performance: Fastest backend (no disk I/O)
  • Recovery: Impossible once process exits
  • Isolation: Keys isolated per process
Memory backend provides no persistence. Wallets created with this backend cannot be recovered after restart.

KMS Backend

Overview

Keys encrypted using envelope encryption with a master secret managed by a Key Management Service. Best for:
  • Managed cloud environments (AWS, GCP, Azure)
  • Centralized key governance
  • Audit logging requirements
  • Key rotation policies

Configuration

WALLET_SIGNER_BACKEND=kms
WALLET_KMS_MASTER_SECRET=your-master-secret  # Required
WALLET_KMS_KEY_ID=wallet-engine-kms-key      # Optional (default shown)
The current implementation is a software simulation of KMS envelope encryption. For production, integrate with AWS KMS, Google Cloud KMS, or Azure Key Vault.

How It Works

Envelope encryption pattern:
1

Generate Data Key

For each wallet, generate a random 32-byte data key
2

Wrap Data Key

Encrypt the data key with the KMS master secret
3

Encrypt Private Key

Encrypt the wallet’s private key with the data key
4

Store Envelope

Save both wrapped data key and encrypted private key
// From services/wallet-engine/src/key-provider/kms-key-provider.ts

async save(walletId: string, keypair: Keypair): Promise<void> {
  const dataKey = randomBytes(32).toString('base64');
  const wrappedDataKey = encryptText(dataKey, this.secretForWrap());
  const encryptedSecret = encryptText(
    Buffer.from(keypair.secretKey).toString('base64'),
    dataKey
  );
  
  const payload: KmsEnvelope = {
    v: 1,
    keyId: this.keyId,
    wrappedDataKey,
    encryptedSecret,
    createdAt: new Date().toISOString(),
  };
  
  await fs.writeFile(this.keyFile(walletId), JSON.stringify(payload), 'utf8');
}

File Format

{
  "v": 1,
  "keyId": "wallet-engine-kms-key",
  "wrappedDataKey": "encrypted-data-key",
  "encryptedSecret": "encrypted-private-key",
  "createdAt": "2026-03-08T10:00:00.000Z"
}

Benefits

  • Centralized key management: Master secret can be rotated independently
  • Audit trail: KMS operations can be logged
  • Access control: KMS policies control who can decrypt
  • Compliance: Meets regulatory requirements for key governance

Production Integration

For production AWS KMS integration:
import { KMSClient, DecryptCommand, GenerateDataKeyCommand } from '@aws-sdk/client-kms';

const kms = new KMSClient({ region: 'us-east-1' });

// Generate data key using KMS
const { Plaintext, CiphertextBlob } = await kms.send(
  new GenerateDataKeyCommand({
    KeyId: process.env.WALLET_KMS_KEY_ID,
    KeySpec: 'AES_256',
  })
);

// Use Plaintext as data key, store CiphertextBlob as wrappedDataKey

HSM Backend

Overview

Keys protected by hardware security module (HSM) with hardware-rooted custody. Best for:
  • High-compliance environments (PCI-DSS, FIPS 140-2)
  • Financial services
  • Regulatory requirements for hardware key storage
  • Scenarios requiring physical tamper protection

Configuration

WALLET_SIGNER_BACKEND=hsm
WALLET_HSM_PIN=your-hsm-pin                # Required
WALLET_HSM_MODULE_SECRET=your-module-secret  # Required
WALLET_HSM_SLOT=slot-0                       # Optional (default shown)
The current implementation is a software simulation. For production, integrate with AWS CloudHSM, Thales Luna HSM, YubiHSM, or similar hardware.

How It Works

// From services/wallet-engine/src/key-provider/hsm-key-provider.ts

export class HsmKeyProvider implements KeyProvider {
  private unwrapSecret(): string {
    return `${this.moduleSecret}:${this.slotId}:${this.pin}`;
  }

  async save(walletId: string, keypair: Keypair): Promise<void> {
    const payload: HsmEnvelope = {
      v: 1,
      slotId: this.slotId,
      wrappedSecret: encryptText(
        Buffer.from(keypair.secretKey).toString('base64'),
        this.unwrapSecret()
      ),
      createdAt: new Date().toISOString(),
    };
    
    await fs.writeFile(this.keyFile(walletId), JSON.stringify(payload), 'utf8');
  }
}

File Format

{
  "v": 1,
  "slotId": "slot-0",
  "wrappedSecret": "encrypted-with-hsm-secret",
  "createdAt": "2026-03-08T10:00:00.000Z"
}

HSM Concepts

  • Slot: Logical partition within HSM hardware
  • PIN: Authentication credential for accessing slot
  • Module Secret: Shared secret for wrapping keys at rest
  • FIPS 140-2: U.S. government security standard for cryptographic modules

Production Integration

For production AWS CloudHSM integration:
import { CloudHSMv2Client } from '@aws-sdk/client-cloudhsmv2';
import * as pkcs11 from 'pkcs11js';

// Initialize PKCS#11 module
const pkcs11Module = new pkcs11.PKCS11();
pkcs11Module.load('/opt/cloudhsm/lib/libcloudhsm_pkcs11.so');

// Open session and login
const session = pkcs11Module.C_OpenSession(slotId, flags);
pkcs11Module.C_Login(session, userType, pin);

// Generate key in HSM
const keyHandle = pkcs11Module.C_GenerateKeyPair(
  session,
  mechanism,
  publicKeyTemplate,
  privateKeyTemplate
);

MPC Backend

Overview

Keys split into multiple shares using threshold cryptography, providing distributed custody. Best for:
  • Distributed operational control
  • Reducing single-point-of-failure risk
  • Multi-party governance
  • Geographic distribution requirements

Configuration

WALLET_SIGNER_BACKEND=mpc

# Option 1: CSV format
WALLET_MPC_NODE_SECRETS=secret1,secret2,secret3

# Option 2: Individual environment variables
WALLET_MPC_NODE1_SECRET=secret1
WALLET_MPC_NODE2_SECRET=secret2
WALLET_MPC_NODE3_SECRET=secret3
All 3 node secrets are required. The system uses a 2-of-3 threshold: any 2 shares can reconstruct the key.

How It Works

Secret Sharing Scheme:
1

Split Secret

Wallet private key divided into 3 shares using Galois field arithmetic (GF(256))
2

Encrypt Shares

Each share encrypted with its corresponding node secret
3

Store All Shares

All 3 encrypted shares stored in single envelope file
4

Reconstruct on Load

Any 2 shares can reconstruct the original key using Lagrange interpolation
// From services/wallet-engine/src/key-provider/mpc-key-provider.ts

const splitSecret = (secret: Uint8Array): Array<{ id: number; bytes: Uint8Array }> => {
  const coefficient = randomBytes(secret.length);
  const s1 = new Uint8Array(secret.length);
  const s2 = new Uint8Array(secret.length);
  const s3 = new Uint8Array(secret.length);
  
  for (let i = 0; i < secret.length; i += 1) {
    const secretByte = secret[i] ?? 0;
    const a = coefficient[i] ?? 0;
    s1[i] = (secretByte ^ gfMul(a, 1)) & 0xff;
    s2[i] = (secretByte ^ gfMul(a, 2)) & 0xff;
    s3[i] = (secretByte ^ gfMul(a, 3)) & 0xff;
  }
  
  return [
    { id: 1, bytes: s1 },
    { id: 2, bytes: s2 },
    { id: 3, bytes: s3 },
  ];
};

File Format

{
  "v": 1,
  "threshold": 2,
  "shares": [
    {"id": 1, "wrapped": "encrypted-with-node1-secret"},
    {"id": 2, "wrapped": "encrypted-with-node2-secret"},
    {"id": 3, "wrapped": "encrypted-with-node3-secret"}
  ],
  "createdAt": "2026-03-08T10:00:00.000Z"
}

Threshold Reconstruction

Key reconstruction requires any 2 of the 3 shares:
// From services/wallet-engine/src/key-provider/mpc-key-provider.ts

const reconstructFromTwoShares = (
  first: { id: number; bytes: Uint8Array },
  second: { id: number; bytes: Uint8Array }
): Uint8Array => {
  const denominator = (first.id ^ second.id) & 0xff;
  const l1 = gfDiv(second.id & 0xff, denominator);
  const l2 = gfDiv(first.id & 0xff, denominator);
  
  const secret = new Uint8Array(first.bytes.length);
  for (let i = 0; i < secret.length; i += 1) {
    const y1 = first.bytes[i] ?? 0;
    const y2 = second.bytes[i] ?? 0;
    secret[i] = (gfMul(y1, l1) ^ gfMul(y2, l2)) & 0xff;
  }
  
  return secret;
};

Benefits

  • No single point of failure: Loss of one secret doesn’t compromise keys
  • Distributed trust: No single party holds complete key
  • Geographic distribution: Secrets can be stored in different regions
  • Operational flexibility: Any 2 of 3 nodes can sign

Production Deployment

For production MPC deployments:
1

Generate Node Secrets

Create 3 strong secrets with openssl rand -base64 32
2

Distribute Secrets

Store each secret in a different infrastructure zone:
  • Node 1: Region A, AWS Secrets Manager
  • Node 2: Region B, HashiCorp Vault
  • Node 3: Region C, Google Secret Manager
3

Access Control

Configure IAM policies so no single role can access all 3 secrets
4

Backup Strategy

Back up share files to different storage systems
5

Recovery Testing

Regularly test 2-of-3 reconstruction in staging

Switching Backends

Backends can be changed, but keys must be migrated:
Changing WALLET_SIGNER_BACKEND without migrating keys will make existing wallets inaccessible.

Migration Process

1

Export Keys

Read all wallet IDs and load keypairs from current backend
2

Switch Backend

Update WALLET_SIGNER_BACKEND and related environment variables
3

Re-save Keys

Save all keypairs using new backend
4

Verify

Test signing operations with migrated keys
5

Backup Old Keys

Keep old key files as backup until verified
A migration utility script is planned for a future release. For now, contact support for assistance with production migrations.

Performance Comparison

BackendSave LatencyLoad LatencyThroughputNotes
memory< 1 ms< 1 msVery highNo disk I/O
encrypted-file5-10 ms5-10 msHighDisk I/O bound
kms10-20 ms10-20 msMediumDouble encryption
hsm10-30 ms10-30 msMediumHardware latency
mpc15-30 ms20-40 msLower3 share operations
Latencies are approximate and depend on hardware, storage, and network conditions.

Security Comparison

BackendKey Exposure RiskComplianceKey RecoveryOperational Complexity
memoryHigh (memory dumps)NoNoneLow
encrypted-fileMedium (host compromise)NoManual backupLow
kmsLowYesKMS backupMedium
hsmVery lowYes (FIPS 140-2)HSM failoverHigh
mpcVery lowYes2-of-3 sharesHigh

Recommendations

Development

Use encrypted-file for local development:
WALLET_SIGNER_BACKEND=encrypted-file
WALLET_KEY_ENCRYPTION_SECRET=$(openssl rand -base64 32)

Testing

Use memory for CI/CD and integration tests:
WALLET_SIGNER_BACKEND=memory

Production (Cloud)

Use kms with cloud-native key management:
WALLET_SIGNER_BACKEND=kms
WALLET_KMS_MASTER_SECRET=arn:aws:kms:region:account:key/key-id

Production (High Compliance)

Use hsm for regulated industries:
WALLET_SIGNER_BACKEND=hsm
WALLET_HSM_PIN=***
WALLET_HSM_MODULE_SECRET=***
WALLET_HSM_SLOT=slot-production

Production (Distributed)

Use mpc for multi-party custody:
WALLET_SIGNER_BACKEND=mpc
WALLET_MPC_NODE1_SECRET=$(aws secretsmanager get-secret-value --region us-east-1 ...)
WALLET_MPC_NODE2_SECRET=$(gcloud secrets versions access latest --secret=...)
WALLET_MPC_NODE3_SECRET=$(vault kv get -field=value secret/mpc/node3)

Key Management

Key isolation and lifecycle management

Security Overview

Trust boundaries and protection layers

Build docs developers (and LLMs) love