Key management SDK for Lumen cryptographic operations - Now with proper ML-DSA-44/65/87 implementations
npm install @smartledger/keysgenerateKeypair() implementations scattered across repos
@smartledger/keys, all your agents/modules/bridge code simply ask:
typescript
"Give me a signing key of suite X"
"Sign this payload with keyId Y"
`
And don't care how keys are stored, rotated, or implemented.
---
Design Principles
1. Private keys hidden - Never exposed to most callers
2. Crypto agility - Suite is config, not code
3. Single entry point - Easy to audit
4. Idempotent operations - Safe for retries
5. Minimal API surface - Small, focused, easy to learn
---
Installation
`bash
npm install @smartledger/keys
`
$3
Choose the right security level for your use case:
| Variant | NIST Level | Equivalent | Use Case |
|---------|------------|------------|----------|
| ml-dsa-44 | Level 2 | AES-128 | IoT devices, high-throughput systems |
| ml-dsa-65 | Level 3 | AES-192 | General purpose (recommended) |
| ml-dsa-87 | Level 5 | AES-256 | High-security, long-term confidential data |
---
Quick Start
`typescript
import { createKeySDK } from '@smartledger/keys';
// One line to get a ready-to-use SDK with ML-DSA + ECDSA suites registered
const sdk = createKeySDK();
// Create both PQ + ECDSA keys for an agent
// Using ml-dsa-65 (Level 3) - recommended for most use cases
const { primaryKey, secondaryKey } = await sdk.createDualSignatureKeys(
'agent-resonance',
{
primarySignatureSuite: 'ml-dsa-65', // or 'ml-dsa-44', 'ml-dsa-87'
secondarySignatureSuite: 'bsv-ecdsa-secp256k1',
}
);
// Sign with both suites (private keys stay hidden)
const message = new TextEncoder().encode('agent output');
const { signatures } = await sdk.signWithSuites('agent-resonance', message);
// Verify both signatures
const verify = await sdk.verifyWithSuites('agent-resonance', message, signatures);
console.log(verify.allValid); // true
`
---
API Reference
$3
createKeySDK(config?) returns a ready-to-use SDK wired with in-memory storage and the default ML-DSA + ECDSA suites. Pass your own keyRegistry or suiteRegistry to override defaults.
$3
#### createKey(agentId, profile, options?)
Create a new key for an agent/module.
Parameters:
- agentId (string) - Agent identifier (e.g. 'agent-resonance')
- profile (CryptoProfile) - Which signature suites to use
- options (CreateKeyOptions) - Optional expiration, usage, etc.
Returns: Promise - KeyRecord with metadata and public key
Example:
`typescript
const key = await sdk.createKey(
'agent-schema',
{ primarySignatureSuite: 'ml-dsa-65' }, // Recommended: Level 3
{
expiresAt: new Date(Date.now() + 365 * 86400000).toISOString(), // 1 year
usage: ['signing'],
}
);
`
---
#### getKey(keyId)
Get an existing key by ID.
Parameters:
- keyId (string) - Key identifier
Returns: Promise - KeyRecord or null if not found
Example:
`typescript
const key = await sdk.getKey('agent-resonance:pk-ml-1');
if (key) {
console.log(key.meta.suiteId); // 'ml-dsa-65' or 'ml-dsa-44', 'ml-dsa-87'
console.log(key.publicKey); // Uint8Array
}
`
---
#### signWithKey(keyId, message)
Sign a message with a key. Private key never leaves the SDK.
Parameters:
- keyId (string) - Which key to sign with
- message (Uint8Array) - Message to sign (will be hashed internally)
Returns: Promise - Signature bytes
Throws: Error if key not found or not active
Example:
`typescript
const message = new TextEncoder().encode('agent output');
const signature = await sdk.signWithKey('agent-resonance:pk-ml-1', message);
`
---
#### verifySignature(keyId, message, signature)
Verify a signature against a public key.
Parameters:
- keyId (string) - Which key to verify with
- message (Uint8Array) - Original message
- signature (Uint8Array) - Signature to verify
Returns: Promise - true if valid
Example:
`typescript
const valid = await sdk.verifySignature(
'agent-resonance:pk-ml-1',
message,
signature
);
`
---
#### listKeysForAgent(agentId, activeOnly?)
List all keys for an agent.
Parameters:
- agentId (string) - Agent identifier
- activeOnly (boolean) - If true, only return active keys (default: true)
Returns: Promise - Array of KeyRecords
Example:
`typescript
const keys = await sdk.listKeysForAgent('agent-schema');
for (const key of keys) {
console.log(${key.meta.keyId} (${key.meta.suiteId}));
}
`
---
#### getActiveCryptoProfile(agentId)
Get the active crypto profile for an agent.
Looks up the most recent active keys and infers the profile.
Parameters:
- agentId (string) - Agent identifier
Returns: Promise - CryptoProfile or null if no keys found
Example:
`typescript
const profile = await sdk.getActiveCryptoProfile('agent-schema');
console.log(profile.primarySignatureSuite); // 'ml-dsa-65'
console.log(profile.secondarySignatureSuite); // 'bsv-ecdsa-secp256k1'
`
---
#### rotateAgentKeys(agentId)
Rotate an agent's keys.
Generates new keypairs for all active keys, marks old ones as rotated.
Parameters:
- agentId (string) - Agent identifier
Returns: Promise - Array of new KeyRecords
Example:
`typescript
const newKeys = await sdk.rotateAgentKeys('agent-schema');
for (const key of newKeys) {
console.log(New: ${key.meta.keyId});
console.log(Rotated from: ${key.meta.rotatedFrom});
}
`
---
#### getOrCreateKey(agentId, profile, options?)
Create key if it doesn't exist, otherwise return existing.
Idempotent key creation for agent setup.
Parameters:
- agentId (string) - Agent identifier
- profile (CryptoProfile) - Crypto configuration
- options (CreateKeyOptions) - Creation options
Returns: Promise - KeyRecord (new or existing)
Example:
`typescript
// Safe to call multiple times
const key1 = await sdk.getOrCreateKey('agent-validator', {
primarySignatureSuite: 'ml-dsa-65',
});
const key2 = await sdk.getOrCreateKey('agent-validator', {
primarySignatureSuite: 'ml-dsa-65',
});
console.log(key1.meta.keyId === key2.meta.keyId); // true
`
---
#### createDualSignatureKeys(agentId, profile, options?)
Create both primary and secondary keys in one call. Requires profile.secondarySignatureSuite.
Returns: { primaryKey, secondaryKey }
Example:
`typescript
const { primaryKey, secondaryKey } = await sdk.createDualSignatureKeys(
'agent-bridge',
{
primarySignatureSuite: 'ml-dsa-65', // Recommended
secondarySignatureSuite: 'bsv-ecdsa-secp256k1',
}
);
`
---
#### getOrCreateDualSignatureKeys(agentId, profile, options?)
Idempotent version of the above; reuses active keys when present.
Returns: { primaryKey, secondaryKey }
---
#### signWithSuites(agentId, message, suites?)
Sign once per suite. When suites is omitted, active primary + secondary are used.
Returns: { signatures: Array<{ suiteId, keyId, signature }> }
Example:
`typescript
const message = new TextEncoder().encode('hybrid payload');
const { signatures } = await sdk.signWithSuites('agent-bridge', message);
`
---
#### verifyWithSuites(agentId, message, signatures)
Verify multiple signatures and get per-suite results.
Returns: { results: Array<{ suiteId, keyId, valid }>, allValid: boolean }
Example:
`typescript
const verify = await sdk.verifyWithSuites('agent-bridge', message, signatures);
console.log(verify.allValid); // true when every suite verifies
`
---
---
Types
$3
Public key record (safe to expose, no private key).
`typescript
interface KeyRecord {
meta: KeyMeta; // Key metadata
publicKey: Uint8Array; // Public key bytes
}
`
$3
Options for key creation.
`typescript
interface CreateKeyOptions {
expiresAt?: string; // ISO 8601
usage?: Array<'signing' | 'encryption' | 'both'>; // Default: ['signing']
cryptoProfileVersion?: string; // Default: '1.0.0'
}
`
$3
Per-agent/module crypto configuration.
`typescript
interface CryptoProfile {
primarySignatureSuite: string; // e.g. 'ml-dsa-65' (recommended), 'ml-dsa-44', 'ml-dsa-87'
secondarySignatureSuite?: string; // e.g. 'bsv-ecdsa-secp256k1' (hybrid)
keyEncapsulationSuite?: string; // e.g. 'ml-kem-768' (future)
}
`
---
Usage Patterns
$3
`typescript
// Agent uses only ML-DSA (recommended: ml-dsa-65)
const key = await sdk.createKey('agent-research', {
primarySignatureSuite: 'ml-dsa-65', // or 'ml-dsa-44' for IoT, 'ml-dsa-87' for max security
});
const message = new TextEncoder().encode('research results');
const signature = await sdk.signWithKey(key.meta.keyId, message);
`
$3
`typescript
// One call to create both keys
await sdk.getOrCreateDualSignatureKeys('agent-bridge', {
primarySignatureSuite: 'ml-dsa-65', // Balanced security (recommended)
secondarySignatureSuite: 'bsv-ecdsa-secp256k1',
});
// Sign with both suites
const message = new TextEncoder().encode('bridge output');
const { signatures } = await sdk.signWithSuites('agent-bridge', message);
// Verify both signatures
const verified = await sdk.verifyWithSuites('agent-bridge', message, signatures);
console.log(verified.allValid); // true
`
$3
`typescript
// Safe to call on every startup
async function setupAgent(agentId: string) {
const key = await sdk.getOrCreateKey(agentId, {
primarySignatureSuite: 'ml-dsa-65', // Recommended default
}, {
expiresAt: new Date(Date.now() + 365 * 86400000).toISOString(),
});
return key;
}
`
$3
`typescript
// Automated rotation job
async function monthlyRotation() {
const agents = ['agent-resonance', 'agent-schema', 'agent-validator'];
for (const agentId of agents) {
const newKeys = await sdk.rotateAgentKeys(agentId);
console.log(✓ Rotated ${agentId}: ${newKeys.length} keys);
}
}
`
---
Benefits
$3
- Private keys hidden - Never exposed to calling code
- Single audit point - All key operations go through SDK
- No key leakage - Keys stay in KeyRegistry storage
- Consistent hashing - Suite-appropriate algorithms
$3
- Suite is config - Change ML-DSA → Falcon without code changes
- Profile-driven - CryptoProfile defines agent's crypto setup
- Easy migration - Hybrid mode for ECDSA → PQ transition
$3
- Clean API - 8 methods, easy to learn
- Idempotent - getOrCreateKey safe for retries
- TypeScript - Full type safety
- Well-tested - 23 tests covering all scenarios
$3
- Centralized - One SDK for all agents
- Auditable - Easy to track key operations
- Scalable - Storage backends (in-memory, file, KMS)
- Rotation-friendly - Built-in rotation support
---
Best Practices
$3
- Use SDK for all key operations
- Call getOrCreateKey for idempotent setup
- Set expiration dates on keys
- Rotate keys regularly (monthly/quarterly)
- Use signWithKey (not direct registry access)
$3
- Expose private keys to calling code
- Create keys manually outside SDK
- Hard-code suite IDs in business logic
- Skip key rotation
---
Integration with Existing Code
$3
`typescript
// Scattered key operations
const suite = globalRegistry.getSuite('ml-dsa-87');
const keypair = await suite.generateKeypair();
await keyRegistry.registerKey('agent-1:pk-1', keypair, 'ml-dsa-87');
const key = await keyRegistry.getKey('agent-1:pk-1');
const sig = await suite.sign(key.keypair.privateKey, message);
`
$3
`typescript
// Clean, centralized
const key = await sdk.createKey('agent-1', {
primarySignatureSuite: 'ml-dsa-65', // Choose: 'ml-dsa-44', '65', or '87'
});
const sig = await sdk.signWithKey(key.meta.keyId, message);
`
---
What's Next?
- File Storage - Persistent key storage (not just in-memory)
- KMS Integration - AWS KMS, Azure Key Vault, HSM
- Key Backup - Encrypted backup/recovery procedures
- Multi-tenant - Isolate keys per tenant
- Hardware Keys - Support for non-exportable hardware keys
---
Summary
@smartledger/keys` is the single source of truth for cryptographic keys: