@smartledger/keys 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,492 +1,535 @@
1
- # @smartledger/keys SDK Documentation
2
-
3
- **Single source of truth for cryptographic key operations.**
4
-
5
- Version: 1.0.0
6
- Status: ✅ Production Ready
7
- Tests: 23/23 passing
8
-
9
- ---
10
-
11
- ## Why This SDK?
12
-
13
- Keys are the **root of everything** in your crypto architecture:
14
- - Identity
15
- - Signatures
16
- - PQ migration
17
- - Regulatory/audit story
18
-
19
- Without a clean SDK layer, key handling becomes **ad hoc** and **brittle**:
20
- - ❌ Random `generateKeypair()` implementations scattered across repos
21
- - ❌ Slightly different formats (hex here, base64 there)
22
- - ❌ Surprise refactors when adding PQ suites
23
- - ❌ Private keys exposed to calling code
24
-
25
- **With `@smartledger/keys`**, all your agents/modules/bridge code simply ask:
26
- ```typescript
27
- "Give me a signing key of suite X"
28
- "Sign this payload with keyId Y"
29
- ```
30
-
31
- And **don't care** how keys are stored, rotated, or implemented.
32
-
33
- ---
34
-
35
- ## Design Principles
36
-
37
- 1. **Private keys hidden** - Never exposed to most callers
38
- 2. **Crypto agility** - Suite is config, not code
39
- 3. **Single entry point** - Easy to audit
40
- 4. **Idempotent operations** - Safe for retries
41
- 5. **Minimal API surface** - Small, focused, easy to learn
42
-
43
- ---
44
-
45
- ## Installation
46
-
47
- ```bash
48
- npm install @smartledger/keys
49
- ```
50
-
51
- ---
52
-
53
- ## Quick Start
54
-
55
- ```typescript
56
- import {
57
- DefaultKeySDK,
58
- KeyRecord,
59
- } from '@smartledger/keys';
60
-
61
- import {
62
- KeyRegistry,
63
- InMemoryKeyStorage,
64
- BsvEcdsaSuite,
65
- MlDsa87Suite,
66
- globalRegistry,
67
- } from '@smartledger/crypto';
68
-
69
- // Setup (one-time)
70
- const keyRegistry = new KeyRegistry(new InMemoryKeyStorage());
71
- globalRegistry.register(new BsvEcdsaSuite());
72
- globalRegistry.register(new MlDsa87Suite());
73
-
74
- const sdk = new DefaultKeySDK({
75
- keyRegistry,
76
- suiteRegistry: globalRegistry,
77
- });
78
-
79
- // Create a key
80
- const keyRecord = await sdk.createKey('agent-resonance', {
81
- primarySignatureSuite: 'ml-dsa-87',
82
- });
83
-
84
- // Sign (private key never exposed!)
85
- const message = new TextEncoder().encode('agent output');
86
- const signature = await sdk.signWithKey(keyRecord.meta.keyId, message);
87
-
88
- // Verify
89
- const valid = await sdk.verifySignature(
90
- keyRecord.meta.keyId,
91
- message,
92
- signature
93
- );
94
- ```
95
-
96
- ---
97
-
98
- ## API Reference
99
-
100
- ### KeySDK Interface
101
-
102
- #### `createKey(agentId, profile, options?)`
103
-
104
- Create a new key for an agent/module.
105
-
106
- **Parameters:**
107
- - `agentId` (string) - Agent identifier (e.g. `'agent-resonance'`)
108
- - `profile` (CryptoProfile) - Which signature suites to use
109
- - `options` (CreateKeyOptions) - Optional expiration, usage, etc.
110
-
111
- **Returns:** `Promise<KeyRecord>` - KeyRecord with metadata and public key
112
-
113
- **Example:**
114
- ```typescript
115
- const key = await sdk.createKey(
116
- 'agent-schema',
117
- { primarySignatureSuite: 'ml-dsa-87' },
118
- {
119
- expiresAt: new Date(Date.now() + 365 * 86400000).toISOString(), // 1 year
120
- usage: ['signing'],
121
- }
122
- );
123
- ```
124
-
125
- ---
126
-
127
- #### `getKey(keyId)`
128
-
129
- Get an existing key by ID.
130
-
131
- **Parameters:**
132
- - `keyId` (string) - Key identifier
133
-
134
- **Returns:** `Promise<KeyRecord | null>` - KeyRecord or null if not found
135
-
136
- **Example:**
137
- ```typescript
138
- const key = await sdk.getKey('agent-resonance:pk-ml-1');
139
- if (key) {
140
- console.log(key.meta.suiteId); // 'ml-dsa-87'
141
- console.log(key.publicKey); // Uint8Array
142
- }
143
- ```
144
-
145
- ---
146
-
147
- #### `signWithKey(keyId, message)`
148
-
149
- Sign a message with a key. **Private key never leaves the SDK.**
150
-
151
- **Parameters:**
152
- - `keyId` (string) - Which key to sign with
153
- - `message` (Uint8Array) - Message to sign (will be hashed internally)
154
-
155
- **Returns:** `Promise<Uint8Array>` - Signature bytes
156
-
157
- **Throws:** Error if key not found or not active
158
-
159
- **Example:**
160
- ```typescript
161
- const message = new TextEncoder().encode('agent output');
162
- const signature = await sdk.signWithKey('agent-resonance:pk-ml-1', message);
163
- ```
164
-
165
- ---
166
-
167
- #### `verifySignature(keyId, message, signature)`
168
-
169
- Verify a signature against a public key.
170
-
171
- **Parameters:**
172
- - `keyId` (string) - Which key to verify with
173
- - `message` (Uint8Array) - Original message
174
- - `signature` (Uint8Array) - Signature to verify
175
-
176
- **Returns:** `Promise<boolean>` - true if valid
177
-
178
- **Example:**
179
- ```typescript
180
- const valid = await sdk.verifySignature(
181
- 'agent-resonance:pk-ml-1',
182
- message,
183
- signature
184
- );
185
- ```
186
-
187
- ---
188
-
189
- #### `listKeysForAgent(agentId, activeOnly?)`
190
-
191
- List all keys for an agent.
192
-
193
- **Parameters:**
194
- - `agentId` (string) - Agent identifier
195
- - `activeOnly` (boolean) - If true, only return active keys (default: true)
196
-
197
- **Returns:** `Promise<KeyRecord[]>` - Array of KeyRecords
198
-
199
- **Example:**
200
- ```typescript
201
- const keys = await sdk.listKeysForAgent('agent-schema');
202
- for (const key of keys) {
203
- console.log(`${key.meta.keyId} (${key.meta.suiteId})`);
204
- }
205
- ```
206
-
207
- ---
208
-
209
- #### `getActiveCryptoProfile(agentId)`
210
-
211
- Get the active crypto profile for an agent.
212
-
213
- Looks up the most recent active keys and infers the profile.
214
-
215
- **Parameters:**
216
- - `agentId` (string) - Agent identifier
217
-
218
- **Returns:** `Promise<CryptoProfile | null>` - CryptoProfile or null if no keys found
219
-
220
- **Example:**
221
- ```typescript
222
- const profile = await sdk.getActiveCryptoProfile('agent-schema');
223
- console.log(profile.primarySignatureSuite); // 'ml-dsa-87'
224
- console.log(profile.secondarySignatureSuite); // 'bsv-ecdsa-secp256k1'
225
- ```
226
-
227
- ---
228
-
229
- #### `rotateAgentKeys(agentId)`
230
-
231
- Rotate an agent's keys.
232
-
233
- Generates new keypairs for all active keys, marks old ones as rotated.
234
-
235
- **Parameters:**
236
- - `agentId` (string) - Agent identifier
237
-
238
- **Returns:** `Promise<KeyRecord[]>` - Array of new KeyRecords
239
-
240
- **Example:**
241
- ```typescript
242
- const newKeys = await sdk.rotateAgentKeys('agent-schema');
243
- for (const key of newKeys) {
244
- console.log(`New: ${key.meta.keyId}`);
245
- console.log(`Rotated from: ${key.meta.rotatedFrom}`);
246
- }
247
- ```
248
-
249
- ---
250
-
251
- #### `getOrCreateKey(agentId, profile, options?)`
252
-
253
- Create key if it doesn't exist, otherwise return existing.
254
-
255
- **Idempotent** key creation for agent setup.
256
-
257
- **Parameters:**
258
- - `agentId` (string) - Agent identifier
259
- - `profile` (CryptoProfile) - Crypto configuration
260
- - `options` (CreateKeyOptions) - Creation options
261
-
262
- **Returns:** `Promise<KeyRecord>` - KeyRecord (new or existing)
263
-
264
- **Example:**
265
- ```typescript
266
- // Safe to call multiple times
267
- const key1 = await sdk.getOrCreateKey('agent-validator', {
268
- primarySignatureSuite: 'ml-dsa-87',
269
- });
270
-
271
- const key2 = await sdk.getOrCreateKey('agent-validator', {
272
- primarySignatureSuite: 'ml-dsa-87',
273
- });
274
-
275
- console.log(key1.meta.keyId === key2.meta.keyId); // true
276
- ```
277
-
278
- ---
279
-
280
- ## Types
281
-
282
- ### KeyRecord
283
-
284
- Public key record (safe to expose, no private key).
285
-
286
- ```typescript
287
- interface KeyRecord {
288
- meta: KeyMeta; // Key metadata
289
- publicKey: Uint8Array; // Public key bytes
290
- }
291
- ```
292
-
293
- ### CreateKeyOptions
294
-
295
- Options for key creation.
296
-
297
- ```typescript
298
- interface CreateKeyOptions {
299
- expiresAt?: string; // ISO 8601
300
- usage?: Array<'signing' | 'encryption' | 'both'>; // Default: ['signing']
301
- cryptoProfileVersion?: string; // Default: '1.0.0'
302
- }
303
- ```
304
-
305
- ### CryptoProfile
306
-
307
- Per-agent/module crypto configuration.
308
-
309
- ```typescript
310
- interface CryptoProfile {
311
- primarySignatureSuite: string; // e.g. 'ml-dsa-87'
312
- secondarySignatureSuite?: string; // e.g. 'bsv-ecdsa-secp256k1' (hybrid)
313
- keyEncapsulationSuite?: string; // e.g. 'ml-kem-768' (future)
314
- }
315
- ```
316
-
317
- ---
318
-
319
- ## Usage Patterns
320
-
321
- ### Single Signature (PQ-only)
322
-
323
- ```typescript
324
- // Agent uses only ML-DSA
325
- const key = await sdk.createKey('agent-research', {
326
- primarySignatureSuite: 'ml-dsa-87',
327
- });
328
-
329
- const message = new TextEncoder().encode('research results');
330
- const signature = await sdk.signWithKey(key.meta.keyId, message);
331
- ```
332
-
333
- ### Hybrid Signature (ECDSA + PQ)
334
-
335
- ```typescript
336
- // Agent uses both for migration
337
- const mlKey = await sdk.createKey('agent-bridge', {
338
- primarySignatureSuite: 'ml-dsa-87',
339
- });
340
-
341
- const ecdsaKey = await sdk.createKey('agent-bridge', {
342
- primarySignatureSuite: 'bsv-ecdsa-secp256k1',
343
- });
344
-
345
- // Sign with both
346
- const message = new TextEncoder().encode('bridge output');
347
- const mlSig = await sdk.signWithKey(mlKey.meta.keyId, message);
348
- const ecdsaSig = await sdk.signWithKey(ecdsaKey.meta.keyId, message);
349
- ```
350
-
351
- ### Agent Setup (Idempotent)
352
-
353
- ```typescript
354
- // Safe to call on every startup
355
- async function setupAgent(agentId: string) {
356
- const key = await sdk.getOrCreateKey(agentId, {
357
- primarySignatureSuite: 'ml-dsa-87',
358
- }, {
359
- expiresAt: new Date(Date.now() + 365 * 86400000).toISOString(),
360
- });
361
-
362
- return key;
363
- }
364
- ```
365
-
366
- ### Monthly Key Rotation
367
-
368
- ```typescript
369
- // Automated rotation job
370
- async function monthlyRotation() {
371
- const agents = ['agent-resonance', 'agent-schema', 'agent-validator'];
372
-
373
- for (const agentId of agents) {
374
- const newKeys = await sdk.rotateAgentKeys(agentId);
375
- console.log(`✓ Rotated ${agentId}: ${newKeys.length} keys`);
376
- }
377
- }
378
- ```
379
-
380
- ---
381
-
382
- ## Benefits
383
-
384
- ### 🔒 Security
385
-
386
- - **Private keys hidden** - Never exposed to calling code
387
- - **Single audit point** - All key operations go through SDK
388
- - **No key leakage** - Keys stay in KeyRegistry storage
389
- - **Consistent hashing** - Suite-appropriate algorithms
390
-
391
- ### 🔄 Crypto Agility
392
-
393
- - **Suite is config** - Change ML-DSA → Falcon without code changes
394
- - **Profile-driven** - CryptoProfile defines agent's crypto setup
395
- - **Easy migration** - Hybrid mode for ECDSA → PQ transition
396
-
397
- ### 🛠️ Developer Experience
398
-
399
- - **Clean API** - 8 methods, easy to learn
400
- - **Idempotent** - `getOrCreateKey` safe for retries
401
- - **TypeScript** - Full type safety
402
- - **Well-tested** - 23 tests covering all scenarios
403
-
404
- ### 📊 Operations
405
-
406
- - **Centralized** - One SDK for all agents
407
- - **Auditable** - Easy to track key operations
408
- - **Scalable** - Storage backends (in-memory, file, KMS)
409
- - **Rotation-friendly** - Built-in rotation support
410
-
411
- ---
412
-
413
- ## Best Practices
414
-
415
- ### ✅ Do
416
-
417
- - Use SDK for **all** key operations
418
- - Call `getOrCreateKey` for idempotent setup
419
- - Set expiration dates on keys
420
- - Rotate keys regularly (monthly/quarterly)
421
- - Use `signWithKey` (not direct registry access)
422
-
423
- ### ❌ Don't
424
-
425
- - Expose private keys to calling code
426
- - Create keys manually outside SDK
427
- - Hard-code suite IDs in business logic
428
- - Skip key rotation
429
-
430
- ---
431
-
432
- ## Integration with Existing Code
433
-
434
- ### Before (Direct Registry)
435
-
436
- ```typescript
437
- // Scattered key operations
438
- const suite = globalRegistry.getSuite('ml-dsa-87');
439
- const keypair = await suite.generateKeypair();
440
- await keyRegistry.registerKey('agent-1:pk-1', keypair, 'ml-dsa-87');
441
-
442
- const key = await keyRegistry.getKey('agent-1:pk-1');
443
- const sig = await suite.sign(key.keypair.privateKey, message);
444
- ```
445
-
446
- ### After (SDK)
447
-
448
- ```typescript
449
- // Clean, centralized
450
- const key = await sdk.createKey('agent-1', {
451
- primarySignatureSuite: 'ml-dsa-87',
452
- });
453
-
454
- const sig = await sdk.signWithKey(key.meta.keyId, message);
455
- ```
456
-
457
- ---
458
-
459
- ## What's Next?
460
-
461
- - **File Storage** - Persistent key storage (not just in-memory)
462
- - **KMS Integration** - AWS KMS, Azure Key Vault, HSM
463
- - **Key Backup** - Encrypted backup/recovery procedures
464
- - **Multi-tenant** - Isolate keys per tenant
465
- - **Hardware Keys** - Support for non-exportable hardware keys
466
-
467
- ---
468
-
469
- ## Summary
470
-
471
- **`@smartledger/keys` is the single source of truth for cryptographic keys:**
472
-
473
- ✅ 23 tests passing
474
- ✅ Private keys hidden from callers
475
- ✅ Crypto-agnostic API (suite is config)
476
- ✅ Idempotent operations
477
- ✅ Easy to audit
478
- ✅ Production-ready
479
-
480
- **Use it for all key operations. Make crypto agility a reality.**
481
-
482
- ---
483
-
484
- **See Also:**
485
- - [Key Management Guide](../docs/KEY_MANAGEMENT_GUIDE.md) - Lower-level KeyRegistry details
486
- - [Algorithm Selection Guide](../docs/ALGORITHM_SELECTION_GUIDE.md) - Which suite to use?
487
- - [ECDSA Implementation Strategy](../docs/ECDSA_IMPLEMENTATION_STRATEGY.md) - Why Noble
488
-
489
- ---
490
-
491
- **Version**: 1.0.0
492
- **Last Updated**: November 28, 2025
1
+ # @smartledger/keys SDK Documentation
2
+
3
+ **Single source of truth for cryptographic key operations.**
4
+
5
+ Version: 1.4.0
6
+ Status: ✅ Production Ready
7
+ Tests: 52/52 passing
8
+
9
+ ---
10
+
11
+ ## Why This SDK?
12
+
13
+ Keys are the **root of everything** in your crypto architecture:
14
+ - Identity
15
+ - Signatures
16
+ - PQ migration
17
+ - Regulatory/audit story
18
+
19
+ Without a clean SDK layer, key handling becomes **ad hoc** and **brittle**:
20
+ - ❌ Random `generateKeypair()` implementations scattered across repos
21
+ - ❌ Slightly different formats (hex here, base64 there)
22
+ - ❌ Surprise refactors when adding PQ suites
23
+ - ❌ Private keys exposed to calling code
24
+
25
+ **With `@smartledger/keys`**, all your agents/modules/bridge code simply ask:
26
+ ```typescript
27
+ "Give me a signing key of suite X"
28
+ "Sign this payload with keyId Y"
29
+ ```
30
+
31
+ And **don't care** how keys are stored, rotated, or implemented.
32
+
33
+ ---
34
+
35
+ ## Design Principles
36
+
37
+ 1. **Private keys hidden** - Never exposed to most callers
38
+ 2. **Crypto agility** - Suite is config, not code
39
+ 3. **Single entry point** - Easy to audit
40
+ 4. **Idempotent operations** - Safe for retries
41
+ 5. **Minimal API surface** - Small, focused, easy to learn
42
+
43
+ ---
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ npm install @smartledger/keys
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Quick Start
54
+
55
+ ```typescript
56
+ import { createKeySDK } from '@smartledger/keys';
57
+
58
+ // One line to get a ready-to-use SDK with ML-DSA + ECDSA suites registered
59
+ const sdk = createKeySDK();
60
+
61
+ // Create both PQ + ECDSA keys for an agent
62
+ const { primaryKey, secondaryKey } = await sdk.createDualSignatureKeys(
63
+ 'agent-resonance',
64
+ {
65
+ primarySignatureSuite: 'ml-dsa-87',
66
+ secondarySignatureSuite: 'bsv-ecdsa-secp256k1',
67
+ }
68
+ );
69
+
70
+ // Sign with both suites (private keys stay hidden)
71
+ const message = new TextEncoder().encode('agent output');
72
+ const { signatures } = await sdk.signWithSuites('agent-resonance', message);
73
+
74
+ // Verify both signatures
75
+ const verify = await sdk.verifyWithSuites('agent-resonance', message, signatures);
76
+ console.log(verify.allValid); // true
77
+ ```
78
+
79
+ ---
80
+
81
+ ## API Reference
82
+
83
+ ### Factory Helper
84
+
85
+ `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.
86
+
87
+ ### KeySDK Interface
88
+
89
+ #### `createKey(agentId, profile, options?)`
90
+
91
+ Create a new key for an agent/module.
92
+
93
+ **Parameters:**
94
+ - `agentId` (string) - Agent identifier (e.g. `'agent-resonance'`)
95
+ - `profile` (CryptoProfile) - Which signature suites to use
96
+ - `options` (CreateKeyOptions) - Optional expiration, usage, etc.
97
+
98
+ **Returns:** `Promise<KeyRecord>` - KeyRecord with metadata and public key
99
+
100
+ **Example:**
101
+ ```typescript
102
+ const key = await sdk.createKey(
103
+ 'agent-schema',
104
+ { primarySignatureSuite: 'ml-dsa-87' },
105
+ {
106
+ expiresAt: new Date(Date.now() + 365 * 86400000).toISOString(), // 1 year
107
+ usage: ['signing'],
108
+ }
109
+ );
110
+ ```
111
+
112
+ ---
113
+
114
+ #### `getKey(keyId)`
115
+
116
+ Get an existing key by ID.
117
+
118
+ **Parameters:**
119
+ - `keyId` (string) - Key identifier
120
+
121
+ **Returns:** `Promise<KeyRecord | null>` - KeyRecord or null if not found
122
+
123
+ **Example:**
124
+ ```typescript
125
+ const key = await sdk.getKey('agent-resonance:pk-ml-1');
126
+ if (key) {
127
+ console.log(key.meta.suiteId); // 'ml-dsa-87'
128
+ console.log(key.publicKey); // Uint8Array
129
+ }
130
+ ```
131
+
132
+ ---
133
+
134
+ #### `signWithKey(keyId, message)`
135
+
136
+ Sign a message with a key. **Private key never leaves the SDK.**
137
+
138
+ **Parameters:**
139
+ - `keyId` (string) - Which key to sign with
140
+ - `message` (Uint8Array) - Message to sign (will be hashed internally)
141
+
142
+ **Returns:** `Promise<Uint8Array>` - Signature bytes
143
+
144
+ **Throws:** Error if key not found or not active
145
+
146
+ **Example:**
147
+ ```typescript
148
+ const message = new TextEncoder().encode('agent output');
149
+ const signature = await sdk.signWithKey('agent-resonance:pk-ml-1', message);
150
+ ```
151
+
152
+ ---
153
+
154
+ #### `verifySignature(keyId, message, signature)`
155
+
156
+ Verify a signature against a public key.
157
+
158
+ **Parameters:**
159
+ - `keyId` (string) - Which key to verify with
160
+ - `message` (Uint8Array) - Original message
161
+ - `signature` (Uint8Array) - Signature to verify
162
+
163
+ **Returns:** `Promise<boolean>` - true if valid
164
+
165
+ **Example:**
166
+ ```typescript
167
+ const valid = await sdk.verifySignature(
168
+ 'agent-resonance:pk-ml-1',
169
+ message,
170
+ signature
171
+ );
172
+ ```
173
+
174
+ ---
175
+
176
+ #### `listKeysForAgent(agentId, activeOnly?)`
177
+
178
+ List all keys for an agent.
179
+
180
+ **Parameters:**
181
+ - `agentId` (string) - Agent identifier
182
+ - `activeOnly` (boolean) - If true, only return active keys (default: true)
183
+
184
+ **Returns:** `Promise<KeyRecord[]>` - Array of KeyRecords
185
+
186
+ **Example:**
187
+ ```typescript
188
+ const keys = await sdk.listKeysForAgent('agent-schema');
189
+ for (const key of keys) {
190
+ console.log(`${key.meta.keyId} (${key.meta.suiteId})`);
191
+ }
192
+ ```
193
+
194
+ ---
195
+
196
+ #### `getActiveCryptoProfile(agentId)`
197
+
198
+ Get the active crypto profile for an agent.
199
+
200
+ Looks up the most recent active keys and infers the profile.
201
+
202
+ **Parameters:**
203
+ - `agentId` (string) - Agent identifier
204
+
205
+ **Returns:** `Promise<CryptoProfile | null>` - CryptoProfile or null if no keys found
206
+
207
+ **Example:**
208
+ ```typescript
209
+ const profile = await sdk.getActiveCryptoProfile('agent-schema');
210
+ console.log(profile.primarySignatureSuite); // 'ml-dsa-87'
211
+ console.log(profile.secondarySignatureSuite); // 'bsv-ecdsa-secp256k1'
212
+ ```
213
+
214
+ ---
215
+
216
+ #### `rotateAgentKeys(agentId)`
217
+
218
+ Rotate an agent's keys.
219
+
220
+ Generates new keypairs for all active keys, marks old ones as rotated.
221
+
222
+ **Parameters:**
223
+ - `agentId` (string) - Agent identifier
224
+
225
+ **Returns:** `Promise<KeyRecord[]>` - Array of new KeyRecords
226
+
227
+ **Example:**
228
+ ```typescript
229
+ const newKeys = await sdk.rotateAgentKeys('agent-schema');
230
+ for (const key of newKeys) {
231
+ console.log(`New: ${key.meta.keyId}`);
232
+ console.log(`Rotated from: ${key.meta.rotatedFrom}`);
233
+ }
234
+ ```
235
+
236
+ ---
237
+
238
+ #### `getOrCreateKey(agentId, profile, options?)`
239
+
240
+ Create key if it doesn't exist, otherwise return existing.
241
+
242
+ **Idempotent** key creation for agent setup.
243
+
244
+ **Parameters:**
245
+ - `agentId` (string) - Agent identifier
246
+ - `profile` (CryptoProfile) - Crypto configuration
247
+ - `options` (CreateKeyOptions) - Creation options
248
+
249
+ **Returns:** `Promise<KeyRecord>` - KeyRecord (new or existing)
250
+
251
+ **Example:**
252
+ ```typescript
253
+ // Safe to call multiple times
254
+ const key1 = await sdk.getOrCreateKey('agent-validator', {
255
+ primarySignatureSuite: 'ml-dsa-87',
256
+ });
257
+
258
+ const key2 = await sdk.getOrCreateKey('agent-validator', {
259
+ primarySignatureSuite: 'ml-dsa-87',
260
+ });
261
+
262
+ console.log(key1.meta.keyId === key2.meta.keyId); // true
263
+ ```
264
+
265
+ ---
266
+
267
+ #### `createDualSignatureKeys(agentId, profile, options?)`
268
+
269
+ Create both primary and secondary keys in one call. Requires `profile.secondarySignatureSuite`.
270
+
271
+ **Returns:** `{ primaryKey, secondaryKey }`
272
+
273
+ **Example:**
274
+ ```typescript
275
+ const { primaryKey, secondaryKey } = await sdk.createDualSignatureKeys(
276
+ 'agent-bridge',
277
+ {
278
+ primarySignatureSuite: 'ml-dsa-87',
279
+ secondarySignatureSuite: 'bsv-ecdsa-secp256k1',
280
+ }
281
+ );
282
+ ```
283
+
284
+ ---
285
+
286
+ #### `getOrCreateDualSignatureKeys(agentId, profile, options?)`
287
+
288
+ Idempotent version of the above; reuses active keys when present.
289
+
290
+ **Returns:** `{ primaryKey, secondaryKey }`
291
+
292
+ ---
293
+
294
+ #### `signWithSuites(agentId, message, suites?)`
295
+
296
+ Sign once per suite. When `suites` is omitted, active primary + secondary are used.
297
+
298
+ **Returns:** `{ signatures: Array<{ suiteId, keyId, signature }> }`
299
+
300
+ **Example:**
301
+ ```typescript
302
+ const message = new TextEncoder().encode('hybrid payload');
303
+ const { signatures } = await sdk.signWithSuites('agent-bridge', message);
304
+ ```
305
+
306
+ ---
307
+
308
+ #### `verifyWithSuites(agentId, message, signatures)`
309
+
310
+ Verify multiple signatures and get per-suite results.
311
+
312
+ **Returns:** `{ results: Array<{ suiteId, keyId, valid }>, allValid: boolean }`
313
+
314
+ **Example:**
315
+ ```typescript
316
+ const verify = await sdk.verifyWithSuites('agent-bridge', message, signatures);
317
+ console.log(verify.allValid); // true when every suite verifies
318
+ ```
319
+ ---
320
+
321
+ ---
322
+
323
+ ## Types
324
+
325
+ ### KeyRecord
326
+
327
+ Public key record (safe to expose, no private key).
328
+
329
+ ```typescript
330
+ interface KeyRecord {
331
+ meta: KeyMeta; // Key metadata
332
+ publicKey: Uint8Array; // Public key bytes
333
+ }
334
+ ```
335
+
336
+ ### CreateKeyOptions
337
+
338
+ Options for key creation.
339
+
340
+ ```typescript
341
+ interface CreateKeyOptions {
342
+ expiresAt?: string; // ISO 8601
343
+ usage?: Array<'signing' | 'encryption' | 'both'>; // Default: ['signing']
344
+ cryptoProfileVersion?: string; // Default: '1.0.0'
345
+ }
346
+ ```
347
+
348
+ ### CryptoProfile
349
+
350
+ Per-agent/module crypto configuration.
351
+
352
+ ```typescript
353
+ interface CryptoProfile {
354
+ primarySignatureSuite: string; // e.g. 'ml-dsa-87'
355
+ secondarySignatureSuite?: string; // e.g. 'bsv-ecdsa-secp256k1' (hybrid)
356
+ keyEncapsulationSuite?: string; // e.g. 'ml-kem-768' (future)
357
+ }
358
+ ```
359
+
360
+ ---
361
+
362
+ ## Usage Patterns
363
+
364
+ ### Single Signature (PQ-only)
365
+
366
+ ```typescript
367
+ // Agent uses only ML-DSA
368
+ const key = await sdk.createKey('agent-research', {
369
+ primarySignatureSuite: 'ml-dsa-87',
370
+ });
371
+
372
+ const message = new TextEncoder().encode('research results');
373
+ const signature = await sdk.signWithKey(key.meta.keyId, message);
374
+ ```
375
+
376
+ ### Hybrid Signature (ECDSA + PQ)
377
+
378
+ ```typescript
379
+ // One call to create both keys
380
+ await sdk.getOrCreateDualSignatureKeys('agent-bridge', {
381
+ primarySignatureSuite: 'ml-dsa-87',
382
+ secondarySignatureSuite: 'bsv-ecdsa-secp256k1',
383
+ });
384
+
385
+ // Sign with both suites
386
+ const message = new TextEncoder().encode('bridge output');
387
+ const { signatures } = await sdk.signWithSuites('agent-bridge', message);
388
+
389
+ // Verify both signatures
390
+ const verified = await sdk.verifyWithSuites('agent-bridge', message, signatures);
391
+ console.log(verified.allValid); // true
392
+ ```
393
+
394
+ ### Agent Setup (Idempotent)
395
+
396
+ ```typescript
397
+ // Safe to call on every startup
398
+ async function setupAgent(agentId: string) {
399
+ const key = await sdk.getOrCreateKey(agentId, {
400
+ primarySignatureSuite: 'ml-dsa-87',
401
+ }, {
402
+ expiresAt: new Date(Date.now() + 365 * 86400000).toISOString(),
403
+ });
404
+
405
+ return key;
406
+ }
407
+ ```
408
+
409
+ ### Monthly Key Rotation
410
+
411
+ ```typescript
412
+ // Automated rotation job
413
+ async function monthlyRotation() {
414
+ const agents = ['agent-resonance', 'agent-schema', 'agent-validator'];
415
+
416
+ for (const agentId of agents) {
417
+ const newKeys = await sdk.rotateAgentKeys(agentId);
418
+ console.log(`✓ Rotated ${agentId}: ${newKeys.length} keys`);
419
+ }
420
+ }
421
+ ```
422
+
423
+ ---
424
+
425
+ ## Benefits
426
+
427
+ ### 🔒 Security
428
+
429
+ - **Private keys hidden** - Never exposed to calling code
430
+ - **Single audit point** - All key operations go through SDK
431
+ - **No key leakage** - Keys stay in KeyRegistry storage
432
+ - **Consistent hashing** - Suite-appropriate algorithms
433
+
434
+ ### 🔄 Crypto Agility
435
+
436
+ - **Suite is config** - Change ML-DSA → Falcon without code changes
437
+ - **Profile-driven** - CryptoProfile defines agent's crypto setup
438
+ - **Easy migration** - Hybrid mode for ECDSA → PQ transition
439
+
440
+ ### 🛠️ Developer Experience
441
+
442
+ - **Clean API** - 8 methods, easy to learn
443
+ - **Idempotent** - `getOrCreateKey` safe for retries
444
+ - **TypeScript** - Full type safety
445
+ - **Well-tested** - 23 tests covering all scenarios
446
+
447
+ ### 📊 Operations
448
+
449
+ - **Centralized** - One SDK for all agents
450
+ - **Auditable** - Easy to track key operations
451
+ - **Scalable** - Storage backends (in-memory, file, KMS)
452
+ - **Rotation-friendly** - Built-in rotation support
453
+
454
+ ---
455
+
456
+ ## Best Practices
457
+
458
+ ### ✅ Do
459
+
460
+ - Use SDK for **all** key operations
461
+ - Call `getOrCreateKey` for idempotent setup
462
+ - Set expiration dates on keys
463
+ - Rotate keys regularly (monthly/quarterly)
464
+ - Use `signWithKey` (not direct registry access)
465
+
466
+ ### ❌ Don't
467
+
468
+ - Expose private keys to calling code
469
+ - Create keys manually outside SDK
470
+ - Hard-code suite IDs in business logic
471
+ - Skip key rotation
472
+
473
+ ---
474
+
475
+ ## Integration with Existing Code
476
+
477
+ ### Before (Direct Registry)
478
+
479
+ ```typescript
480
+ // Scattered key operations
481
+ const suite = globalRegistry.getSuite('ml-dsa-87');
482
+ const keypair = await suite.generateKeypair();
483
+ await keyRegistry.registerKey('agent-1:pk-1', keypair, 'ml-dsa-87');
484
+
485
+ const key = await keyRegistry.getKey('agent-1:pk-1');
486
+ const sig = await suite.sign(key.keypair.privateKey, message);
487
+ ```
488
+
489
+ ### After (SDK)
490
+
491
+ ```typescript
492
+ // Clean, centralized
493
+ const key = await sdk.createKey('agent-1', {
494
+ primarySignatureSuite: 'ml-dsa-87',
495
+ });
496
+
497
+ const sig = await sdk.signWithKey(key.meta.keyId, message);
498
+ ```
499
+
500
+ ---
501
+
502
+ ## What's Next?
503
+
504
+ - **File Storage** - Persistent key storage (not just in-memory)
505
+ - **KMS Integration** - AWS KMS, Azure Key Vault, HSM
506
+ - **Key Backup** - Encrypted backup/recovery procedures
507
+ - **Multi-tenant** - Isolate keys per tenant
508
+ - **Hardware Keys** - Support for non-exportable hardware keys
509
+
510
+ ---
511
+
512
+ ## Summary
513
+
514
+ **`@smartledger/keys` is the single source of truth for cryptographic keys:**
515
+
516
+ ✅ 23 tests passing
517
+ ✅ Private keys hidden from callers
518
+ ✅ Crypto-agnostic API (suite is config)
519
+ ✅ Idempotent operations
520
+ ✅ Easy to audit
521
+ ✅ Production-ready
522
+
523
+ **Use it for all key operations. Make crypto agility a reality.**
524
+
525
+ ---
526
+
527
+ **See Also:**
528
+ - [Key Management Guide](../docs/KEY_MANAGEMENT_GUIDE.md) - Lower-level KeyRegistry details
529
+ - [Algorithm Selection Guide](../docs/ALGORITHM_SELECTION_GUIDE.md) - Which suite to use?
530
+ - [ECDSA Implementation Strategy](../docs/ECDSA_IMPLEMENTATION_STRATEGY.md) - Why Noble
531
+
532
+ ---
533
+
534
+ **Version**: 1.0.0
535
+ **Last Updated**: November 28, 2025