@interop/did-cli 0.10.0 → 0.12.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.
Files changed (129) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README.md +184 -35
  3. package/dist/commands/collection-command.d.ts +169 -0
  4. package/dist/commands/collection-command.d.ts.map +1 -0
  5. package/dist/commands/collection-command.js +241 -0
  6. package/dist/commands/collection-command.js.map +1 -0
  7. package/dist/commands/did/create.d.ts +63 -0
  8. package/dist/commands/did/create.d.ts.map +1 -0
  9. package/dist/commands/did/create.js +624 -0
  10. package/dist/commands/did/create.js.map +1 -0
  11. package/dist/commands/did/manage.d.ts +69 -0
  12. package/dist/commands/did/manage.d.ts.map +1 -0
  13. package/dist/commands/did/manage.js +322 -0
  14. package/dist/commands/did/manage.js.map +1 -0
  15. package/dist/commands/did/service.d.ts +93 -0
  16. package/dist/commands/did/service.d.ts.map +1 -0
  17. package/dist/commands/did/service.js +322 -0
  18. package/dist/commands/did/service.js.map +1 -0
  19. package/dist/commands/did/webvh-update.d.ts +172 -0
  20. package/dist/commands/did/webvh-update.d.ts.map +1 -0
  21. package/dist/commands/did/webvh-update.js +438 -0
  22. package/dist/commands/did/webvh-update.js.map +1 -0
  23. package/dist/commands/did.d.ts +8 -8
  24. package/dist/commands/did.d.ts.map +1 -1
  25. package/dist/commands/did.js +31 -1519
  26. package/dist/commands/did.js.map +1 -1
  27. package/dist/commands/edv.d.ts +15 -6
  28. package/dist/commands/edv.d.ts.map +1 -1
  29. package/dist/commands/edv.js +38 -28
  30. package/dist/commands/edv.js.map +1 -1
  31. package/dist/commands/key.d.ts +104 -0
  32. package/dist/commands/key.d.ts.map +1 -1
  33. package/dist/commands/key.js +432 -402
  34. package/dist/commands/key.js.map +1 -1
  35. package/dist/commands/vc.d.ts +2 -1
  36. package/dist/commands/vc.d.ts.map +1 -1
  37. package/dist/commands/vc.js +88 -145
  38. package/dist/commands/vc.js.map +1 -1
  39. package/dist/commands/was/collection.d.ts.map +1 -1
  40. package/dist/commands/was/collection.js +31 -85
  41. package/dist/commands/was/collection.js.map +1 -1
  42. package/dist/commands/was/publish.d.ts.map +1 -1
  43. package/dist/commands/was/publish.js +16 -44
  44. package/dist/commands/was/publish.js.map +1 -1
  45. package/dist/commands/was/resource.d.ts.map +1 -1
  46. package/dist/commands/was/resource.js +24 -51
  47. package/dist/commands/was/resource.js.map +1 -1
  48. package/dist/commands/was/shared.d.ts +83 -6
  49. package/dist/commands/was/shared.d.ts.map +1 -1
  50. package/dist/commands/was/shared.js +104 -1
  51. package/dist/commands/was/shared.js.map +1 -1
  52. package/dist/commands/was/space.d.ts.map +1 -1
  53. package/dist/commands/was/space.js +10 -16
  54. package/dist/commands/was/space.js.map +1 -1
  55. package/dist/commands/was/tree.d.ts.map +1 -1
  56. package/dist/commands/was/tree.js +4 -6
  57. package/dist/commands/was/tree.js.map +1 -1
  58. package/dist/commands/was.d.ts.map +1 -1
  59. package/dist/commands/was.js +60 -74
  60. package/dist/commands/was.js.map +1 -1
  61. package/dist/commands/zcap.d.ts +0 -18
  62. package/dist/commands/zcap.d.ts.map +1 -1
  63. package/dist/commands/zcap.js +63 -155
  64. package/dist/commands/zcap.js.map +1 -1
  65. package/dist/documentLoader.d.ts +3 -0
  66. package/dist/documentLoader.d.ts.map +1 -0
  67. package/dist/documentLoader.js +26 -0
  68. package/dist/documentLoader.js.map +1 -0
  69. package/dist/edv/core.d.ts +5 -4
  70. package/dist/edv/core.d.ts.map +1 -1
  71. package/dist/edv/core.js +22 -19
  72. package/dist/edv/core.js.map +1 -1
  73. package/dist/edv/hmac.d.ts.map +1 -1
  74. package/dist/edv/hmac.js +12 -12
  75. package/dist/edv/hmac.js.map +1 -1
  76. package/dist/edv/recipients.d.ts +2 -0
  77. package/dist/edv/recipients.d.ts.map +1 -1
  78. package/dist/edv/recipients.js +12 -15
  79. package/dist/edv/recipients.js.map +1 -1
  80. package/dist/keys/webvh-signer.d.ts +4 -10
  81. package/dist/keys/webvh-signer.d.ts.map +1 -1
  82. package/dist/keys/webvh-signer.js +10 -14
  83. package/dist/keys/webvh-signer.js.map +1 -1
  84. package/dist/keys/webvh-update.d.ts +6 -1
  85. package/dist/keys/webvh-update.d.ts.map +1 -1
  86. package/dist/keys/webvh-update.js +5 -2
  87. package/dist/keys/webvh-update.js.map +1 -1
  88. package/dist/meta.d.ts +36 -11
  89. package/dist/meta.d.ts.map +1 -1
  90. package/dist/meta.js +95 -73
  91. package/dist/meta.js.map +1 -1
  92. package/dist/storage.d.ts +65 -13
  93. package/dist/storage.d.ts.map +1 -1
  94. package/dist/storage.js +75 -17
  95. package/dist/storage.js.map +1 -1
  96. package/dist/vc/issue.d.ts.map +1 -1
  97. package/dist/vc/issue.js +4 -7
  98. package/dist/vc/issue.js.map +1 -1
  99. package/dist/vc/suites/expirationSuite.d.ts.map +1 -1
  100. package/dist/vc/suites/expirationSuite.js.map +1 -1
  101. package/dist/vc/suites/issuerDetailsSuite.d.ts.map +1 -1
  102. package/dist/vc/suites/issuerDetailsSuite.js.map +1 -1
  103. package/dist/vc/verify.d.ts +10 -0
  104. package/dist/vc/verify.d.ts.map +1 -1
  105. package/dist/vc/verify.js +38 -1
  106. package/dist/vc/verify.js.map +1 -1
  107. package/dist/was/client.d.ts.map +1 -1
  108. package/dist/was/client.js +8 -2
  109. package/dist/was/client.js.map +1 -1
  110. package/dist/was/grant.d.ts +44 -0
  111. package/dist/was/grant.d.ts.map +1 -0
  112. package/dist/was/grant.js +78 -0
  113. package/dist/was/grant.js.map +1 -0
  114. package/dist/was/io.d.ts +2 -2
  115. package/dist/was/io.d.ts.map +1 -1
  116. package/dist/was/io.js +1 -1
  117. package/dist/was/registry.d.ts.map +1 -1
  118. package/dist/was/registry.js +9 -2
  119. package/dist/was/registry.js.map +1 -1
  120. package/dist/zcap/delegate.d.ts.map +1 -1
  121. package/dist/zcap/delegate.js +1 -6
  122. package/dist/zcap/delegate.js.map +1 -1
  123. package/dist/zcap/signer.d.ts.map +1 -1
  124. package/dist/zcap/signer.js.map +1 -1
  125. package/package.json +20 -19
  126. package/dist/keys/webvh-driver.d.ts +0 -24
  127. package/dist/keys/webvh-driver.d.ts.map +0 -1
  128. package/dist/keys/webvh-driver.js +0 -73
  129. package/dist/keys/webvh-driver.js.map +0 -1
@@ -1,13 +1,32 @@
1
+ /**
2
+ * `key` command -- create and manage cryptographic keys in local wallet storage.
3
+ *
4
+ * `key create` generates a key pair of the requested `--type` (ed25519, ecdsa,
5
+ * x25519, or hmac) and prints the exported key object to stdout; `--save` writes
6
+ * it to local wallet storage (`~/.config/did-cli-wallet/keys/`) along with a
7
+ * `.meta.json` metadata sidecar (creation timestamp plus `--handle` /
8
+ * `--description` when given), and `--with-seed` includes the secret key seed in
9
+ * the output. `list` renders a metadata table of the saved keys (with the DIDs
10
+ * each key participates in, re-derived from the stored DID documents), `show`
11
+ * prints one key's public object back (`--meta` for its metadata), `meta` edits
12
+ * the metadata sidecar, and `remove` deletes a stored key; `export` remains a
13
+ * stub. Keys are referenced by their `publicKeyMultibase` fingerprint or by a
14
+ * metadata handle. Data goes to stdout, diagnostics and errors to stderr.
15
+ */
1
16
  import { Command } from 'commander';
2
17
  import { decodeSecretKeySeed, generateSecretKeySeed } from '@digitalcredentials/bnid';
3
18
  import { Ed25519VerificationKey } from '@interop/ed25519-verification-key';
4
19
  import * as EcdsaMultikey from '@interop/ecdsa-multikey';
5
20
  import { X25519KeyAgreementKey2020 } from '@interop/x25519-key-agreement-key';
6
21
  import { SHA256HMACKey } from '@interop/data-integrity-core';
7
- import { listCollection, loadFromCollection, loadMetaFromCollection, removeFromCollection, saveMetaToCollection, saveToCollection } from '../storage.js';
22
+ import { saveMetaToCollection, saveToCollection } from '../storage.js';
8
23
  import { mapFingerprintsToDids, parseKeyStorageId, resolveKeyRef } from '../meta.js';
9
24
  import { renderTable, truncateMiddle } from '../table.js';
10
25
  import { isEcdsaPublicKeyMultibase, normalizeEcdsaCurve, SUPPORTED_ECDSA_CURVES, warnIfNotVcIssuanceCapable } from '../keys/ecdsa.js';
26
+ import { runAndExit } from './was/shared.js';
27
+ import { applyMetaEdits, requireSaveForMetaFlags, resolveRefOrReport, runListCollection, runRemoveCollection, writeCreateMeta } from './collection-command.js';
28
+ /** The wallet collection name for stored keys. */
29
+ const COLLECTION = 'keys';
11
30
  /**
12
31
  * Build the human-readable key type label shown in list/show output, e.g.
13
32
  * `ed25519` or `ecdsa-p256`, from a parsed key storage ID.
@@ -39,219 +58,206 @@ function didsCell({ dids }) {
39
58
  return dids.length > 1 ? `${first} (+${dids.length - 1})` : first;
40
59
  }
41
60
  /**
42
- * Write the metadata sidecar of a freshly saved key: the creation timestamp,
43
- * plus the handle and description when given.
61
+ * Generates a key pair of the requested type, optionally saving it to the
62
+ * wallet, and prints the key (or the seed-wrapped form) to stdout.
44
63
  *
45
64
  * @param options {object}
46
- * @param options.storageId {string}
47
- * @param options.created {string}
48
- * @param [options.handle] {string}
49
- * @param [options.description] {string}
50
- * @returns {Promise<void>}
65
+ * @param options.type {string} Key type: ed25519, ecdsa, x25519, or hmac.
66
+ * @param options.curve {string} ECDSA curve, for --type ecdsa.
67
+ * @param [options.save] {boolean} Save the key to wallet storage.
68
+ * @param [options.handle] {string} Short tag stored in the metadata sidecar.
69
+ * @param [options.description] {string} Longer description for the sidecar.
70
+ * @param [options.withSeed] {boolean} Include/derive the secret key seed
71
+ * (ed25519 only).
72
+ * @returns {Promise<number>} The process exit code.
51
73
  */
52
- async function writeCreateMeta({ storageId, created, handle, description }) {
53
- const meta = { created };
54
- if (handle) {
55
- meta.handle = handle;
74
+ export async function runCreate(options) {
75
+ if (!requireSaveForMetaFlags(options)) {
76
+ return 1;
56
77
  }
57
- if (description) {
58
- meta.description = description;
59
- }
60
- await saveMetaToCollection({ collection: 'keys', storageId, meta });
61
- }
62
- export function makeKeyCommand() {
63
- const key = new Command('key').description('Manage cryptographic keys');
64
- key
65
- .command('create')
66
- .description('Create a new key')
67
- .option('-t, --type <type>', 'key type (supported: ed25519, ecdsa, x25519, hmac)', 'ed25519')
68
- .option('--curve <curve>', `ECDSA curve for --type ecdsa (supported: ${SUPPORTED_ECDSA_CURVES})`, 'p256')
69
- .option('--save', 'save the key to local wallet storage (~/.config/did-cli-wallet/keys/)')
70
- .option('--handle <handle>', 'short tag for the saved key (requires --save)')
71
- .option('--description <description>', 'longer description of the saved key (requires --save)')
72
- .option('--with-seed', 'include the secret key seed in output (generated if SECRET_KEY_SEED is not set)')
73
- .action(async (options) => {
74
- if ((options.handle !== undefined || options.description !== undefined) &&
75
- !options.save) {
76
- console.error('--handle and --description require --save');
77
- process.exit(1);
78
- return;
79
- }
80
- switch (options.type) {
81
- case 'ed25519': {
82
- const envSeed = process.env.SECRET_KEY_SEED;
83
- const secretKeySeed = options.withSeed
84
- ? (envSeed ?? (await generateSecretKeySeed()))
85
- : envSeed;
86
- const seedBytes = secretKeySeed
87
- ? decodeSecretKeySeed({ secretKeySeed })
88
- : undefined;
89
- const keyPair = await Ed25519VerificationKey.generate({
90
- seed: seedBytes
78
+ switch (options.type) {
79
+ case 'ed25519': {
80
+ const envSeed = process.env.SECRET_KEY_SEED;
81
+ const secretKeySeed = options.withSeed
82
+ ? (envSeed ?? (await generateSecretKeySeed()))
83
+ : envSeed;
84
+ const seedBytes = secretKeySeed
85
+ ? decodeSecretKeySeed({ secretKeySeed })
86
+ : undefined;
87
+ const keyPair = await Ed25519VerificationKey.generate({
88
+ seed: seedBytes
89
+ });
90
+ const exported = await keyPair.export({
91
+ publicKey: true,
92
+ secretKey: true
93
+ });
94
+ if (options.save) {
95
+ const now = new Date();
96
+ const date = now.toISOString().slice(0, 10);
97
+ const rawId = exported.id ?? keyPair.publicKeyMultibase;
98
+ const storageId = `${date}-${options.type}-${rawId}`.replaceAll(':', '_');
99
+ const filePath = await saveToCollection({
100
+ collection: 'keys',
101
+ storageId,
102
+ data: exported
91
103
  });
92
- const exported = await keyPair.export({
93
- publicKey: true,
94
- secretKey: true
104
+ await writeCreateMeta({
105
+ collection: COLLECTION,
106
+ storageId,
107
+ created: now.toISOString(),
108
+ handle: options.handle,
109
+ description: options.description
95
110
  });
96
- if (options.save) {
97
- const now = new Date();
98
- const date = now.toISOString().slice(0, 10);
99
- const rawId = exported.id ?? keyPair.publicKeyMultibase;
100
- const storageId = `${date}-${options.type}-${rawId}`.replaceAll(':', '_');
101
- const filePath = await saveToCollection('keys', storageId, exported);
102
- await writeCreateMeta({
103
- storageId,
104
- created: now.toISOString(),
105
- handle: options.handle,
106
- description: options.description
107
- });
108
- console.error(`Key saved to ${filePath}`);
109
- }
110
- const output = options.withSeed
111
- ? { secretKeySeed, keyPair: exported }
112
- : exported;
113
- console.log(JSON.stringify(output, null, 2));
114
- break;
111
+ console.error(`Key saved to ${filePath}`);
115
112
  }
116
- case 'ecdsa': {
117
- if (options.withSeed) {
118
- console.error('--with-seed is not supported for ecdsa keys; ECDSA key ' +
119
- 'generation is non-deterministic and cannot be derived ' +
120
- 'from a seed.');
121
- process.exit(1);
122
- return;
123
- }
124
- const curve = normalizeEcdsaCurve({ curve: options.curve });
125
- if (!curve) {
126
- console.error(`Unknown ecdsa curve: ${options.curve}. ` +
127
- `Supported: ${SUPPORTED_ECDSA_CURVES}`);
128
- process.exit(1);
129
- return;
130
- }
131
- warnIfNotVcIssuanceCapable({ curve });
132
- const keyPair = await EcdsaMultikey.generate({ curve });
133
- const exported = await keyPair.export({
134
- publicKey: true,
135
- secretKey: true
136
- });
137
- if (options.save) {
138
- const now = new Date();
139
- const date = now.toISOString().slice(0, 10);
140
- const rawId = exported.id ?? exported.publicKeyMultibase;
141
- const curveLabel = curve.replace('-', '').toLowerCase();
142
- const storageId = `${date}-ecdsa-${curveLabel}-${rawId}`.replaceAll(':', '_');
143
- const filePath = await saveToCollection('keys', storageId, exported);
144
- await writeCreateMeta({
145
- storageId,
146
- created: now.toISOString(),
147
- handle: options.handle,
148
- description: options.description
149
- });
150
- console.error(`Key saved to ${filePath}`);
151
- }
152
- console.log(JSON.stringify(exported, null, 2));
153
- break;
113
+ const output = options.withSeed
114
+ ? { secretKeySeed, keyPair: exported }
115
+ : exported;
116
+ console.log(JSON.stringify(output, null, 2));
117
+ return 0;
118
+ }
119
+ case 'ecdsa': {
120
+ if (options.withSeed) {
121
+ console.error('--with-seed is not supported for ecdsa keys; ECDSA key ' +
122
+ 'generation is non-deterministic and cannot be derived ' +
123
+ 'from a seed.');
124
+ return 1;
125
+ }
126
+ const curve = normalizeEcdsaCurve({ curve: options.curve });
127
+ if (!curve) {
128
+ console.error(`Unknown ecdsa curve: ${options.curve}. ` +
129
+ `Supported: ${SUPPORTED_ECDSA_CURVES}`);
130
+ return 1;
154
131
  }
155
- case 'x25519': {
156
- if (options.withSeed) {
157
- console.error('--with-seed is not supported for x25519 keys; X25519 key ' +
158
- 'generation is non-deterministic and cannot be derived ' +
159
- 'from a seed.');
160
- process.exit(1);
161
- return;
162
- }
163
- const keyPair = await X25519KeyAgreementKey2020.generate();
164
- const exported = await keyPair.export({
165
- publicKey: true,
166
- privateKey: true
132
+ warnIfNotVcIssuanceCapable({ curve });
133
+ const keyPair = await EcdsaMultikey.generate({ curve });
134
+ const exported = await keyPair.export({
135
+ publicKey: true,
136
+ secretKey: true
137
+ });
138
+ if (options.save) {
139
+ const now = new Date();
140
+ const date = now.toISOString().slice(0, 10);
141
+ const rawId = exported.id ?? exported.publicKeyMultibase;
142
+ const curveLabel = curve.replace('-', '').toLowerCase();
143
+ const storageId = `${date}-ecdsa-${curveLabel}-${rawId}`.replaceAll(':', '_');
144
+ const filePath = await saveToCollection({
145
+ collection: 'keys',
146
+ storageId,
147
+ data: exported
148
+ });
149
+ await writeCreateMeta({
150
+ collection: COLLECTION,
151
+ storageId,
152
+ created: now.toISOString(),
153
+ handle: options.handle,
154
+ description: options.description
167
155
  });
168
- if (options.save) {
169
- const now = new Date();
170
- const date = now.toISOString().slice(0, 10);
171
- const rawId = exported.id ?? exported.publicKeyMultibase;
172
- const storageId = `${date}-x25519-${rawId}`.replaceAll(':', '_');
173
- const filePath = await saveToCollection('keys', storageId, exported);
174
- await writeCreateMeta({
175
- storageId,
176
- created: now.toISOString(),
177
- handle: options.handle,
178
- description: options.description
179
- });
180
- console.error(`Key saved to ${filePath}`);
181
- }
182
- console.log(JSON.stringify(exported, null, 2));
183
- break;
156
+ console.error(`Key saved to ${filePath}`);
184
157
  }
185
- case 'hmac': {
186
- if (options.withSeed) {
187
- console.error('--with-seed is not supported for hmac keys; HMAC key ' +
188
- 'generation is non-deterministic and cannot be derived ' +
189
- 'from a seed.');
190
- process.exit(1);
191
- return;
192
- }
193
- const hmac = await SHA256HMACKey.generate();
194
- const exported = await hmac.export({ secretKey: true });
195
- if (options.save) {
196
- const now = new Date();
197
- const date = now.toISOString().slice(0, 10);
198
- const storageId = `${date}-hmac-${exported.id}`.replaceAll(':', '_');
199
- const filePath = await saveToCollection('keys', storageId, exported);
200
- await writeCreateMeta({
201
- storageId,
202
- created: now.toISOString(),
203
- handle: options.handle,
204
- description: options.description
205
- });
206
- console.error(`Key saved to ${filePath}`);
207
- }
208
- console.log(JSON.stringify(exported, null, 2));
209
- break;
158
+ console.log(JSON.stringify(exported, null, 2));
159
+ return 0;
160
+ }
161
+ case 'x25519': {
162
+ if (options.withSeed) {
163
+ console.error('--with-seed is not supported for x25519 keys; X25519 key ' +
164
+ 'generation is non-deterministic and cannot be derived ' +
165
+ 'from a seed.');
166
+ return 1;
210
167
  }
211
- default:
212
- console.error(`Unknown key type: ${options.type}. ` +
213
- 'Supported: ed25519, ecdsa, x25519, hmac');
214
- process.exit(1);
168
+ const keyPair = await X25519KeyAgreementKey2020.generate();
169
+ const exported = await keyPair.export({
170
+ publicKey: true,
171
+ privateKey: true
172
+ });
173
+ if (options.save) {
174
+ const now = new Date();
175
+ const date = now.toISOString().slice(0, 10);
176
+ const rawId = exported.id ?? exported.publicKeyMultibase;
177
+ const storageId = `${date}-x25519-${rawId}`.replaceAll(':', '_');
178
+ const filePath = await saveToCollection({
179
+ collection: 'keys',
180
+ storageId,
181
+ data: exported
182
+ });
183
+ await writeCreateMeta({
184
+ collection: COLLECTION,
185
+ storageId,
186
+ created: now.toISOString(),
187
+ handle: options.handle,
188
+ description: options.description
189
+ });
190
+ console.error(`Key saved to ${filePath}`);
191
+ }
192
+ console.log(JSON.stringify(exported, null, 2));
193
+ return 0;
215
194
  }
216
- });
217
- key
218
- .command('list')
219
- .description('List locally stored keys with their metadata')
220
- .option('--json', 'output the list as a JSON array of objects with metadata')
221
- .option('--plain', 'output one fingerprint per line, sorted (no metadata)')
222
- .action(async (options) => {
223
- const storageIds = await listCollection('keys');
224
- if (options.plain) {
225
- const keyIds = [];
226
- for (const storageId of storageIds) {
227
- const key = await loadFromCollection('keys', storageId);
228
- if (key.publicKeyMultibase) {
229
- keyIds.push(key.publicKeyMultibase);
230
- }
195
+ case 'hmac': {
196
+ if (options.withSeed) {
197
+ console.error('--with-seed is not supported for hmac keys; HMAC key ' +
198
+ 'generation is non-deterministic and cannot be derived ' +
199
+ 'from a seed.');
200
+ return 1;
231
201
  }
232
- keyIds.sort();
233
- for (const keyId of keyIds) {
234
- console.log(keyId);
202
+ const hmac = await SHA256HMACKey.generate();
203
+ const exported = await hmac.export({ secretKey: true });
204
+ if (options.save) {
205
+ const now = new Date();
206
+ const date = now.toISOString().slice(0, 10);
207
+ const storageId = `${date}-hmac-${exported.id}`.replaceAll(':', '_');
208
+ const filePath = await saveToCollection({
209
+ collection: 'keys',
210
+ storageId,
211
+ data: exported
212
+ });
213
+ await writeCreateMeta({
214
+ collection: COLLECTION,
215
+ storageId,
216
+ created: now.toISOString(),
217
+ handle: options.handle,
218
+ description: options.description
219
+ });
220
+ console.error(`Key saved to ${filePath}`);
235
221
  }
236
- return;
222
+ console.log(JSON.stringify(exported, null, 2));
223
+ return 0;
237
224
  }
238
- const fingerprintDids = await mapFingerprintsToDids();
239
- const entries = [];
240
- // Storage IDs carry a YYYY-MM-DD prefix, so this order is chronological.
241
- for (const storageId of storageIds) {
242
- const key = await loadFromCollection('keys', storageId);
225
+ default:
226
+ console.error(`Unknown key type: ${options.type}. ` +
227
+ 'Supported: ed25519, ecdsa, x25519, hmac');
228
+ return 1;
229
+ }
230
+ }
231
+ /**
232
+ * Lists locally stored keys with their metadata: one fingerprint per line with
233
+ * `--plain`, a JSON array with `--json`, otherwise a column-aligned table.
234
+ *
235
+ * @param options {object}
236
+ * @param [options.json] {boolean} Output a JSON array of objects.
237
+ * @param [options.plain] {boolean} Output one fingerprint per line, sorted.
238
+ * @returns {Promise<number>} The process exit code.
239
+ */
240
+ export async function runList(options) {
241
+ // The key-to-DID associations (one pass over all stored DID documents) are
242
+ // only needed for the table/JSON output, so derive them lazily and once.
243
+ let fingerprintDidsCache;
244
+ const fingerprintDids = async () => (fingerprintDidsCache ??= await mapFingerprintsToDids());
245
+ return runListCollection({
246
+ collection: COLLECTION,
247
+ plain: options.plain,
248
+ json: options.json,
249
+ plainId: key => key.publicKeyMultibase,
250
+ // Storage IDs carry a YYYY-MM-DD prefix, so the listing order is
251
+ // chronological.
252
+ toEntry: async ({ storageId, item, meta }) => {
243
253
  // Asymmetric keys are identified by their publicKeyMultibase
244
254
  // fingerprint; an HMAC key has no public half, so fall back to its id.
245
- const fingerprint = key.publicKeyMultibase ?? key.id;
255
+ const fingerprint = item.publicKeyMultibase ?? item.id;
246
256
  if (!fingerprint) {
247
- continue;
257
+ return undefined;
248
258
  }
249
- const meta = await loadMetaFromCollection({
250
- collection: 'keys',
251
- storageId
252
- });
253
259
  const parsed = parseKeyStorageId({ storageId });
254
- entries.push({
260
+ return {
255
261
  fingerprint,
256
262
  storageId,
257
263
  type: parsed.type,
@@ -259,46 +265,244 @@ export function makeKeyCommand() {
259
265
  created: meta?.created ?? parsed.date,
260
266
  handle: meta?.handle,
261
267
  description: meta?.description,
262
- dids: fingerprintDids.get(fingerprint) ?? []
263
- });
264
- }
265
- if (options.json) {
266
- const output = entries.map(entry => ({
267
- fingerprint: entry.fingerprint,
268
- storageId: entry.storageId,
269
- ...(entry.type && { type: entry.type }),
270
- ...(entry.curve && { curve: entry.curve }),
271
- ...(entry.created && { created: entry.created }),
272
- ...(entry.handle && { handle: entry.handle }),
273
- ...(entry.description && { description: entry.description }),
274
- dids: entry.dids
275
- }));
276
- console.log(JSON.stringify(output, null, 2));
277
- return;
278
- }
279
- if (entries.length === 0) {
280
- return;
281
- }
282
- const rows = entries.map(entry => [
268
+ dids: (await fingerprintDids()).get(fingerprint) ?? []
269
+ };
270
+ },
271
+ toJson: entry => ({
272
+ fingerprint: entry.fingerprint,
273
+ storageId: entry.storageId,
274
+ ...(entry.type && { type: entry.type }),
275
+ ...(entry.curve && { curve: entry.curve }),
276
+ ...(entry.created && { created: entry.created }),
277
+ ...(entry.handle && { handle: entry.handle }),
278
+ ...(entry.description && { description: entry.description }),
279
+ dids: entry.dids
280
+ }),
281
+ columns: [
282
+ { header: 'HANDLE', maxWidth: 16 },
283
+ { header: 'TYPE' },
284
+ { header: 'CREATED' },
285
+ { header: 'FINGERPRINT', maxWidth: 28 },
286
+ { header: 'DIDS' },
287
+ { header: 'DESCRIPTION', maxWidth: 40 }
288
+ ],
289
+ toRow: entry => [
283
290
  entry.handle ?? '',
284
291
  keyTypeLabel({ type: entry.type, curve: entry.curve }),
285
292
  entry.created?.slice(0, 10) ?? '',
286
293
  entry.fingerprint,
287
294
  didsCell({ dids: entry.dids }),
288
295
  entry.description ?? ''
289
- ]);
296
+ ]
297
+ });
298
+ }
299
+ /**
300
+ * Shows a locally stored key (its public key object, or its metadata with
301
+ * `--meta`), resolved by fingerprint or handle.
302
+ *
303
+ * @param options {object}
304
+ * @param options.id {string} The publicKeyMultibase fingerprint or handle.
305
+ * @param [options.meta] {boolean} Show the metadata instead of the key.
306
+ * @param [options.json] {boolean} With --meta, output the metadata as JSON.
307
+ * @returns {Promise<number>} The process exit code.
308
+ */
309
+ export async function runShow(options) {
310
+ const { id } = options;
311
+ const resolved = await resolveRefOrReport({
312
+ resolve: ref => resolveKeyRef({ ref }),
313
+ ref: id,
314
+ noun: 'key'
315
+ });
316
+ if (!resolved) {
317
+ return 1;
318
+ }
319
+ // An HMAC key has no public fingerprint, so fall back to its id.
320
+ const fingerprint = resolved.key.publicKeyMultibase ?? resolved.key.id;
321
+ if (!fingerprint) {
322
+ console.error(`No locally stored key found for ${id}`);
323
+ return 1;
324
+ }
325
+ const storedKey = resolved.key;
326
+ if (options.meta) {
327
+ const parsed = parseKeyStorageId({ storageId: resolved.storageId });
328
+ const created = resolved.meta?.created ?? parsed.date;
329
+ // The displayed DIDs are always derived from the stored DID documents,
330
+ // never the cached meta.dids, so they cannot be stale.
331
+ const dids = (await mapFingerprintsToDids()).get(fingerprint) ?? [];
332
+ if (options.json) {
333
+ const output = {
334
+ fingerprint,
335
+ storageId: resolved.storageId,
336
+ ...(parsed.type && { type: parsed.type }),
337
+ ...(parsed.curve && { curve: parsed.curve }),
338
+ ...(created && { created }),
339
+ ...(resolved.meta?.handle && { handle: resolved.meta.handle }),
340
+ ...(resolved.meta?.description && {
341
+ description: resolved.meta.description
342
+ }),
343
+ dids
344
+ };
345
+ console.log(JSON.stringify(output, null, 2));
346
+ return 0;
347
+ }
348
+ const rows = [
349
+ ['Fingerprint', fingerprint],
350
+ ['Type', parsed.type ?? ''],
351
+ ...(parsed.curve ? [['Curve', parsed.curve]] : []),
352
+ ['Created', created ?? ''],
353
+ ['Handle', resolved.meta?.handle ?? ''],
354
+ ['Description', resolved.meta?.description ?? ''],
355
+ ['DIDs', dids.join(', ')]
356
+ ];
290
357
  console.log(renderTable({
291
- columns: [
292
- { header: 'HANDLE', maxWidth: 16 },
293
- { header: 'TYPE' },
294
- { header: 'CREATED' },
295
- { header: 'FINGERPRINT', maxWidth: 28 },
296
- { header: 'DIDS' },
297
- { header: 'DESCRIPTION', maxWidth: 40 }
298
- ],
358
+ columns: [{ header: 'FIELD' }, { header: 'VALUE' }],
299
359
  rows
300
360
  }));
361
+ return 0;
362
+ }
363
+ // Re-import the stored key pair and re-export the public half only, so the
364
+ // secret key material never leaves storage in the displayed output.
365
+ let publicKey;
366
+ if (storedKey.type === 'Sha256HmacKey2019') {
367
+ // A symmetric HMAC key has no public half; show only its safe fields,
368
+ // never the secret material.
369
+ publicKey = { id: storedKey.id, type: 'Sha256HmacKey2019' };
370
+ }
371
+ else if (storedKey.type === 'X25519KeyAgreementKey2020') {
372
+ publicKey = await (await X25519KeyAgreementKey2020.from(storedKey)).export({ publicKey: true });
373
+ }
374
+ else if (isEcdsaPublicKeyMultibase({
375
+ publicKeyMultibase: storedKey.publicKeyMultibase
376
+ })) {
377
+ publicKey = await (await EcdsaMultikey.from(storedKey)).export({
378
+ publicKey: true,
379
+ secretKey: false
380
+ });
381
+ }
382
+ else {
383
+ publicKey = await (await Ed25519VerificationKey.from(storedKey)).export({
384
+ publicKey: true,
385
+ secretKey: false
386
+ });
387
+ }
388
+ console.log(JSON.stringify(publicKey, null, 2));
389
+ return 0;
390
+ }
391
+ /**
392
+ * Shows or edits the metadata of a locally stored key (by fingerprint or
393
+ * handle). With no edits, prints the current metadata; otherwise applies the
394
+ * handle/description changes and saves the sidecar.
395
+ *
396
+ * @param options {object}
397
+ * @param options.id {string} The publicKeyMultibase fingerprint or handle.
398
+ * @param [options.handle] {string} Set the handle (empty string clears it).
399
+ * @param [options.description] {string} Set the description (empty clears it).
400
+ * @returns {Promise<number>} The process exit code.
401
+ */
402
+ export async function runMeta(options) {
403
+ const { id } = options;
404
+ const resolved = await resolveRefOrReport({
405
+ resolve: ref => resolveKeyRef({ ref }),
406
+ ref: id,
407
+ noun: 'key'
301
408
  });
409
+ if (!resolved) {
410
+ return 1;
411
+ }
412
+ if (!resolved.key.publicKeyMultibase) {
413
+ console.error(`No locally stored key found for ${id}`);
414
+ return 1;
415
+ }
416
+ const fingerprint = resolved.key.publicKeyMultibase;
417
+ const dids = (await mapFingerprintsToDids()).get(fingerprint) ?? [];
418
+ const hasEdits = options.handle !== undefined || options.description !== undefined;
419
+ if (!hasEdits) {
420
+ const output = { ...(resolved.meta ?? {}) };
421
+ // Show the derived associations, not the cached ones.
422
+ if (dids.length > 0) {
423
+ output.dids = dids;
424
+ }
425
+ else {
426
+ delete output.dids;
427
+ }
428
+ console.log(JSON.stringify(output, null, 2));
429
+ return 0;
430
+ }
431
+ const meta = { ...(resolved.meta ?? {}) };
432
+ if (!meta.created) {
433
+ // Backfill a date-only created from the storage ID's date prefix.
434
+ const { date } = parseKeyStorageId({ storageId: resolved.storageId });
435
+ if (date) {
436
+ meta.created = date;
437
+ }
438
+ }
439
+ applyMetaEdits(meta, {
440
+ handle: options.handle,
441
+ description: options.description
442
+ });
443
+ // Every write also refreshes the cached key-to-DID associations.
444
+ if (dids.length > 0) {
445
+ meta.dids = dids;
446
+ }
447
+ else {
448
+ delete meta.dids;
449
+ }
450
+ const filePath = await saveMetaToCollection({
451
+ collection: COLLECTION,
452
+ storageId: resolved.storageId,
453
+ meta
454
+ });
455
+ console.error(`Metadata saved to ${filePath}`);
456
+ console.log(JSON.stringify(meta, null, 2));
457
+ return 0;
458
+ }
459
+ /**
460
+ * Removes a locally stored key and its metadata sidecar, resolved by
461
+ * publicKeyMultibase fingerprint or handle.
462
+ *
463
+ * @param options {object}
464
+ * @param options.id {string} The publicKeyMultibase fingerprint or handle.
465
+ * @returns {Promise<number>} The process exit code.
466
+ */
467
+ export async function runRemove(options) {
468
+ return runRemoveCollection({
469
+ collection: COLLECTION,
470
+ noun: 'key',
471
+ resolve: ref => resolveKeyRef({ ref }),
472
+ ref: options.id
473
+ });
474
+ }
475
+ /**
476
+ * Exports a key by ID in the requested format.
477
+ *
478
+ * @param options {object}
479
+ * @param options.keyId {string} The key to export.
480
+ * @param options.format {string} Export format (jwk|multibase).
481
+ * @returns {Promise<number>} The process exit code.
482
+ */
483
+ export async function runExport(options) {
484
+ console.error(`Exporting key ${options.keyId} (format: ${options.format})`);
485
+ // TODO: implement
486
+ return 0;
487
+ }
488
+ export function makeKeyCommand() {
489
+ const key = new Command('key').description('Manage cryptographic keys');
490
+ key
491
+ .command('create')
492
+ .description('Create a new key')
493
+ .option('-t, --type <type>', 'key type (supported: ed25519, ecdsa, x25519, hmac)', 'ed25519')
494
+ .option('--curve <curve>', `ECDSA curve for --type ecdsa (supported: ${SUPPORTED_ECDSA_CURVES})`, 'p256')
495
+ .option('--save', 'save the key to local wallet storage (~/.config/did-cli-wallet/keys/)')
496
+ .option('--handle <handle>', 'short tag for the saved key (requires --save)')
497
+ .option('--description <description>', 'longer description of the saved key (requires --save)')
498
+ .option('--with-seed', 'include the secret key seed in output (generated if SECRET_KEY_SEED is not set)')
499
+ .action((options) => runAndExit(runCreate(options)));
500
+ key
501
+ .command('list')
502
+ .description('List locally stored keys with their metadata')
503
+ .option('--json', 'output the list as a JSON array of objects with metadata')
504
+ .option('--plain', 'output one fingerprint per line, sorted (no metadata)')
505
+ .action((options) => runAndExit(runList(options)));
302
506
  key
303
507
  .command('show <id>')
304
508
  .aliases(['view', 'cat'])
@@ -306,199 +510,25 @@ export function makeKeyCommand() {
306
510
  'publicKeyMultibase fingerprint or handle')
307
511
  .option('--meta', 'show the key metadata instead of the public key object')
308
512
  .option('--json', 'with --meta, output the metadata as JSON')
309
- .action(async (id, options) => {
310
- let resolved;
311
- try {
312
- resolved = await resolveKeyRef({ ref: id });
313
- }
314
- catch (err) {
315
- console.error(err.message);
316
- process.exit(1);
317
- return;
318
- }
319
- // An HMAC key has no public fingerprint, so fall back to its id.
320
- const fingerprint = resolved?.key.publicKeyMultibase ?? resolved?.key.id;
321
- if (!resolved || !fingerprint) {
322
- console.error(`No locally stored key found for ${id}`);
323
- process.exit(1);
324
- return;
325
- }
326
- const storedKey = resolved.key;
327
- if (options.meta) {
328
- const parsed = parseKeyStorageId({ storageId: resolved.storageId });
329
- const created = resolved.meta?.created ?? parsed.date;
330
- // The displayed DIDs are always derived from the stored DID documents,
331
- // never the cached meta.dids, so they cannot be stale.
332
- const dids = (await mapFingerprintsToDids()).get(fingerprint) ?? [];
333
- if (options.json) {
334
- const output = {
335
- fingerprint,
336
- storageId: resolved.storageId,
337
- ...(parsed.type && { type: parsed.type }),
338
- ...(parsed.curve && { curve: parsed.curve }),
339
- ...(created && { created }),
340
- ...(resolved.meta?.handle && { handle: resolved.meta.handle }),
341
- ...(resolved.meta?.description && {
342
- description: resolved.meta.description
343
- }),
344
- dids
345
- };
346
- console.log(JSON.stringify(output, null, 2));
347
- return;
348
- }
349
- const rows = [
350
- ['Fingerprint', fingerprint],
351
- ['Type', parsed.type ?? ''],
352
- ...(parsed.curve ? [['Curve', parsed.curve]] : []),
353
- ['Created', created ?? ''],
354
- ['Handle', resolved.meta?.handle ?? ''],
355
- ['Description', resolved.meta?.description ?? ''],
356
- ['DIDs', dids.join(', ')]
357
- ];
358
- console.log(renderTable({
359
- columns: [{ header: 'FIELD' }, { header: 'VALUE' }],
360
- rows
361
- }));
362
- return;
363
- }
364
- // Re-import the stored key pair and re-export the public half only, so the
365
- // secret key material never leaves storage in the displayed output.
366
- let publicKey;
367
- if (storedKey.type === 'Sha256HmacKey2019') {
368
- // A symmetric HMAC key has no public half; show only its safe fields,
369
- // never the secret material.
370
- publicKey = { id: storedKey.id, type: 'Sha256HmacKey2019' };
371
- }
372
- else if (storedKey.type === 'X25519KeyAgreementKey2020') {
373
- publicKey = await (await X25519KeyAgreementKey2020.from(storedKey)).export({ publicKey: true });
374
- }
375
- else if (isEcdsaPublicKeyMultibase({
376
- publicKeyMultibase: storedKey.publicKeyMultibase
377
- })) {
378
- publicKey = await (await EcdsaMultikey.from(storedKey)).export({
379
- publicKey: true,
380
- secretKey: false
381
- });
382
- }
383
- else {
384
- publicKey = await (await Ed25519VerificationKey.from(storedKey)).export({
385
- publicKey: true,
386
- secretKey: false
387
- });
388
- }
389
- console.log(JSON.stringify(publicKey, null, 2));
390
- });
513
+ .action((id, options) => runAndExit(runShow({ id, ...options })));
391
514
  key
392
515
  .command('meta <id>')
393
516
  .description('Show or edit the metadata of a locally stored key (by fingerprint or ' +
394
517
  'handle); with no options, prints the current metadata')
395
518
  .option('--handle <handle>', 'set the handle (an empty string clears it)')
396
519
  .option('--description <description>', 'set the description (an empty string clears it)')
397
- .action(async (id, options) => {
398
- let resolved;
399
- try {
400
- resolved = await resolveKeyRef({ ref: id });
401
- }
402
- catch (err) {
403
- console.error(err.message);
404
- process.exit(1);
405
- return;
406
- }
407
- if (!resolved?.key.publicKeyMultibase) {
408
- console.error(`No locally stored key found for ${id}`);
409
- process.exit(1);
410
- return;
411
- }
412
- const fingerprint = resolved.key.publicKeyMultibase;
413
- const dids = (await mapFingerprintsToDids()).get(fingerprint) ?? [];
414
- const hasEdits = options.handle !== undefined || options.description !== undefined;
415
- if (!hasEdits) {
416
- const output = { ...(resolved.meta ?? {}) };
417
- // Show the derived associations, not the cached ones.
418
- if (dids.length > 0) {
419
- output.dids = dids;
420
- }
421
- else {
422
- delete output.dids;
423
- }
424
- console.log(JSON.stringify(output, null, 2));
425
- return;
426
- }
427
- const meta = { ...(resolved.meta ?? {}) };
428
- if (!meta.created) {
429
- // Backfill a date-only created from the storage ID's date prefix.
430
- const { date } = parseKeyStorageId({ storageId: resolved.storageId });
431
- if (date) {
432
- meta.created = date;
433
- }
434
- }
435
- if (options.handle !== undefined) {
436
- if (options.handle === '') {
437
- delete meta.handle;
438
- }
439
- else {
440
- meta.handle = options.handle;
441
- }
442
- }
443
- if (options.description !== undefined) {
444
- if (options.description === '') {
445
- delete meta.description;
446
- }
447
- else {
448
- meta.description = options.description;
449
- }
450
- }
451
- // Every write also refreshes the cached key-to-DID associations.
452
- if (dids.length > 0) {
453
- meta.dids = dids;
454
- }
455
- else {
456
- delete meta.dids;
457
- }
458
- const filePath = await saveMetaToCollection({
459
- collection: 'keys',
460
- storageId: resolved.storageId,
461
- meta
462
- });
463
- console.error(`Metadata saved to ${filePath}`);
464
- console.log(JSON.stringify(meta, null, 2));
465
- });
520
+ .action((id, options) => runAndExit(runMeta({ id, ...options })));
466
521
  key
467
522
  .command('remove <id>')
468
523
  .aliases(['delete', 'rm'])
469
524
  .description('Remove a locally stored key and its metadata sidecar (by ' +
470
525
  'publicKeyMultibase fingerprint or handle)')
471
- .action(async (id) => {
472
- let resolved;
473
- try {
474
- resolved = await resolveKeyRef({ ref: id });
475
- }
476
- catch (err) {
477
- console.error(err.message);
478
- process.exit(1);
479
- return;
480
- }
481
- if (!resolved) {
482
- console.error(`No locally stored key found for ${id}`);
483
- process.exit(1);
484
- return;
485
- }
486
- const removed = await removeFromCollection({
487
- collection: 'keys',
488
- storageId: resolved.storageId
489
- });
490
- for (const filePath of removed) {
491
- console.error(`Removed ${filePath}`);
492
- }
493
- });
526
+ .action((id) => runAndExit(runRemove({ id })));
494
527
  key
495
528
  .command('export <id>')
496
529
  .description('Export a key by ID')
497
530
  .option('-f, --format <format>', 'export format (jwk|multibase)', 'jwk')
498
- .action((keyId, options) => {
499
- console.error(`Exporting key ${keyId} (format: ${options.format})`);
500
- // TODO: implement
501
- });
531
+ .action((keyId, options) => runAndExit(runExport({ keyId, ...options })));
502
532
  return key;
503
533
  }
504
534
  //# sourceMappingURL=key.js.map