@interop/did-cli 0.9.0 → 0.10.1

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 (117) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +192 -4
  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 +625 -0
  10. package/dist/commands/did/create.js.map +1 -0
  11. package/dist/commands/did/manage.d.ts +68 -0
  12. package/dist/commands/did/manage.d.ts.map +1 -0
  13. package/dist/commands/did/manage.js +287 -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 +324 -0
  18. package/dist/commands/did/service.js.map +1 -0
  19. package/dist/commands/did/webvh-update.d.ts +170 -0
  20. package/dist/commands/did/webvh-update.d.ts.map +1 -0
  21. package/dist/commands/did/webvh-update.js +402 -0
  22. package/dist/commands/did/webvh-update.js.map +1 -0
  23. package/dist/commands/did.d.ts +9 -0
  24. package/dist/commands/did.d.ts.map +1 -1
  25. package/dist/commands/did.js +107 -597
  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 +78 -0
  49. package/dist/commands/was/shared.d.ts.map +1 -1
  50. package/dist/commands/was/shared.js +84 -0
  51. package/dist/commands/was/shared.js.map +1 -1
  52. package/dist/commands/was/space.d.ts +17 -0
  53. package/dist/commands/was/space.d.ts.map +1 -1
  54. package/dist/commands/was/space.js +47 -18
  55. package/dist/commands/was/space.js.map +1 -1
  56. package/dist/commands/was/tree.d.ts.map +1 -1
  57. package/dist/commands/was/tree.js +4 -6
  58. package/dist/commands/was/tree.js.map +1 -1
  59. package/dist/commands/was.d.ts +1 -1
  60. package/dist/commands/was.d.ts.map +1 -1
  61. package/dist/commands/was.js +71 -76
  62. package/dist/commands/was.js.map +1 -1
  63. package/dist/commands/zcap.d.ts +0 -18
  64. package/dist/commands/zcap.d.ts.map +1 -1
  65. package/dist/commands/zcap.js +63 -155
  66. package/dist/commands/zcap.js.map +1 -1
  67. package/dist/documentLoader.d.ts +5 -0
  68. package/dist/documentLoader.d.ts.map +1 -0
  69. package/dist/documentLoader.js +29 -0
  70. package/dist/documentLoader.js.map +1 -0
  71. package/dist/edv/hmac.d.ts.map +1 -1
  72. package/dist/edv/hmac.js +12 -12
  73. package/dist/edv/hmac.js.map +1 -1
  74. package/dist/edv/recipients.d.ts +2 -0
  75. package/dist/edv/recipients.d.ts.map +1 -1
  76. package/dist/edv/recipients.js +12 -15
  77. package/dist/edv/recipients.js.map +1 -1
  78. package/dist/keys/webvh-signer.d.ts +29 -0
  79. package/dist/keys/webvh-signer.d.ts.map +1 -0
  80. package/dist/keys/webvh-signer.js +44 -0
  81. package/dist/keys/webvh-signer.js.map +1 -0
  82. package/dist/keys/webvh-update.d.ts +33 -0
  83. package/dist/keys/webvh-update.d.ts.map +1 -0
  84. package/dist/keys/webvh-update.js +62 -0
  85. package/dist/keys/webvh-update.js.map +1 -0
  86. package/dist/meta.d.ts +36 -11
  87. package/dist/meta.d.ts.map +1 -1
  88. package/dist/meta.js +95 -73
  89. package/dist/meta.js.map +1 -1
  90. package/dist/storage.d.ts +122 -13
  91. package/dist/storage.d.ts.map +1 -1
  92. package/dist/storage.js +105 -13
  93. package/dist/storage.js.map +1 -1
  94. package/dist/vc/issue.d.ts.map +1 -1
  95. package/dist/vc/issue.js +4 -7
  96. package/dist/vc/issue.js.map +1 -1
  97. package/dist/vc/verify.d.ts +10 -0
  98. package/dist/vc/verify.d.ts.map +1 -1
  99. package/dist/vc/verify.js +38 -1
  100. package/dist/vc/verify.js.map +1 -1
  101. package/dist/was/client.d.ts.map +1 -1
  102. package/dist/was/client.js +8 -2
  103. package/dist/was/client.js.map +1 -1
  104. package/dist/was/grant.d.ts +44 -0
  105. package/dist/was/grant.d.ts.map +1 -0
  106. package/dist/was/grant.js +78 -0
  107. package/dist/was/grant.js.map +1 -0
  108. package/dist/was/registry.d.ts +2 -1
  109. package/dist/was/registry.d.ts.map +1 -1
  110. package/dist/was/registry.js +25 -7
  111. package/dist/was/registry.js.map +1 -1
  112. package/dist/zcap/delegate.d.ts.map +1 -1
  113. package/dist/zcap/delegate.js +1 -6
  114. package/dist/zcap/delegate.js.map +1 -1
  115. package/dist/zcap/signer.d.ts.map +1 -1
  116. package/dist/zcap/signer.js.map +1 -1
  117. package/package.json +8 -6
@@ -1,56 +1,19 @@
1
- import { Command } from 'commander';
2
- import { decodeSecretKeySeed, generateSecretKeySeed } from '@digitalcredentials/bnid';
3
- import { driver } from '@interop/did-method-key';
4
- import * as didWeb from '@interop/did-web-resolver';
5
- import { securityLoader } from '@interop/security-document-loader';
6
- import { Ed25519VerificationKey } from '@interop/ed25519-verification-key';
7
- import * as EcdsaMultikey from '@interop/ecdsa-multikey';
8
- import { X25519KeyAgreementKey2020 } from '@interop/x25519-key-agreement-key';
9
- import { listDids, loadDidDocument, loadDidKeys, loadDidMeta, removeDidFiles, saveDidMeta, saveToDids } from '../storage.js';
10
- import { recordKeyDidAssociation, removeKeyDidAssociation, resolveDidRef } from '../meta.js';
11
- import { renderTable } from '../table.js';
12
- import { normalizeEcdsaCurve, SUPPORTED_ECDSA_CURVES, warnIfNotVcIssuanceCapable } from '../keys/ecdsa.js';
13
1
  /**
14
- * Save the artifacts of a newly created DID: the DID document, its keys file,
15
- * a metadata sidecar (creation timestamp plus the handle and description when
16
- * given), and the key-to-DID association cache of any matching wallet keys.
17
- *
18
- * @param options {object}
19
- * @param options.method {string}
20
- * @param options.didDocument {object}
21
- * @param options.exportedKeys {object}
22
- * @param options.fingerprints {(string | undefined)[]}
23
- * @param [options.handle] {string}
24
- * @param [options.description] {string}
25
- * @returns {Promise<void>}
2
+ * `did` command wiring. Defines the commander surface (create, add-key,
3
+ * add-service, remove-service, get, show, list, meta, remove, and the webvh
4
+ * subcommand) and delegates each action to a `run*` function in the `did/`
5
+ * modules: create/add-key in `./did/create.js`, service edits in
6
+ * `./did/service.js`, read/manage operations in `./did/manage.js`, and the
7
+ * did:webvh update-key plumbing plus rotate-keys in `./did/webvh-update.js`.
26
8
  */
27
- async function saveDidArtifacts({ method, didDocument, exportedKeys, fingerprints, handle, description }) {
28
- const did = didDocument.id;
29
- const docPath = await saveToDids({ method, did, data: didDocument });
30
- await saveToDids({ method, did, suffix: 'keys', data: exportedKeys });
31
- const meta = { created: new Date().toISOString() };
32
- if (handle) {
33
- meta.handle = handle;
34
- }
35
- if (description) {
36
- meta.description = description;
37
- }
38
- await saveDidMeta({ did, meta });
39
- for (const fingerprint of fingerprints) {
40
- if (fingerprint) {
41
- await recordKeyDidAssociation({ publicKeyMultibase: fingerprint, did });
42
- }
43
- }
44
- console.error(`DID saved to ${docPath}`);
45
- }
46
- /**
47
- * Document loader for DID / DID-URL resolution. A bare DID resolves to its DID
48
- * document; a `did#fragment` URL is dereferenced straight to its
49
- * verification-method node. Works for did:key (offline) and did:web (fetched).
50
- * Built once and reused. (Per project convention, DID/JSON-LD resolution goes
51
- * through `@interop/security-document-loader`, never a hand-rolled loader.)
52
- */
53
- const documentLoader = securityLoader().build();
9
+ import { Command } from 'commander';
10
+ import { SUPPORTED_ECDSA_CURVES } from '../keys/ecdsa.js';
11
+ import { runAndExit } from './was/shared.js';
12
+ import { DEFAULT_VERIFICATION_PURPOSES, runAddKey, runCreate } from './did/create.js';
13
+ import { addServiceEntry, buildServiceEntry, dispatchServiceUpdate, normalizeServiceId, removeServiceEntry } from './did/service.js';
14
+ import { runGet, runList, runMeta, runRemove, runShow } from './did/manage.js';
15
+ import { runRotateKeys } from './did/webvh-update.js';
16
+ export { parseDidLog } from './did/webvh-update.js';
54
17
  export function makeDidCommand() {
55
18
  const did = new Command('did').description('Manage DIDs');
56
19
  did
@@ -59,227 +22,24 @@ export function makeDidCommand() {
59
22
  .option('-t, --type <type>', 'key type (supported: ed25519, ecdsa)', 'ed25519')
60
23
  .option('--curve <curve>', `ECDSA curve for --type ecdsa (supported: ${SUPPORTED_ECDSA_CURVES})`, 'p256')
61
24
  .option('--url <url>', 'HTTPS url of the DID document (required for did:web)')
25
+ .option('--prerotation', 'arm did:webvh key pre-rotation: stage a next update key and commit ' +
26
+ 'its hash (default)')
27
+ .option('--no-prerotation', 'create the did:webvh without key pre-rotation')
28
+ .option('--portable', 'create a portable did:webvh that can later be moved to a different ' +
29
+ 'domain (default)')
30
+ .option('--no-portable', 'create a non-portable did:webvh (pinned to its domain)')
31
+ .option('--witness <did...>', 'declare a witness did:key DID authorized to co-sign did:webvh log ' +
32
+ 'entries (repeatable; declaration only -- witness proof generation is ' +
33
+ 'out of scope)')
34
+ .option('--witness-threshold <n>', 'number of did:webvh witness approvals required ' +
35
+ '(default: number of witnesses; requires --witness)')
36
+ .option('--watcher <url...>', 'declare a did:webvh watcher URL that monitors the DID log ' +
37
+ '(repeatable; https:// or http://localhost)')
62
38
  .option('--with-seed', 'include the secret key seed in output (generated if SECRET_KEY_SEED is not set)')
63
39
  .option('--save', 'save the DID document to local storage (~/.config/did-cli-wallet/dids/)')
64
40
  .option('--handle <handle>', 'short tag for the saved DID (requires --save)')
65
41
  .option('--description <description>', 'longer description of the saved DID (requires --save)')
66
- .action(async (method = 'key', options) => {
67
- if ((options.handle !== undefined || options.description !== undefined) &&
68
- !options.save) {
69
- console.error('--handle and --description require --save');
70
- process.exit(1);
71
- return;
72
- }
73
- switch (method) {
74
- case 'key': {
75
- switch (options.type) {
76
- case 'ed25519': {
77
- const envSeed = process.env.SECRET_KEY_SEED;
78
- const secretKeySeed = options.withSeed
79
- ? (envSeed ?? (await generateSecretKeySeed()))
80
- : envSeed;
81
- const seedBytes = secretKeySeed
82
- ? decodeSecretKeySeed({ secretKeySeed })
83
- : undefined;
84
- const keyPair = await Ed25519VerificationKey.generate({
85
- seed: seedBytes
86
- });
87
- const didDriver = driver();
88
- didDriver.use({ keyPairClass: Ed25519VerificationKey });
89
- const { didDocument } = await didDriver.fromKeyPair({
90
- verificationKeyPair: keyPair
91
- });
92
- if (options.save) {
93
- const exported = await keyPair.export({
94
- publicKey: true,
95
- secretKey: true
96
- });
97
- await saveDidArtifacts({
98
- method: 'key',
99
- didDocument,
100
- exportedKeys: exported,
101
- fingerprints: [
102
- exported
103
- .publicKeyMultibase
104
- ],
105
- handle: options.handle,
106
- description: options.description
107
- });
108
- }
109
- const output = { id: didDocument.id };
110
- if (options.withSeed) {
111
- output.secretKeySeed = secretKeySeed;
112
- }
113
- output.didDocument = didDocument;
114
- console.log(JSON.stringify(output, null, 2));
115
- break;
116
- }
117
- case 'ecdsa': {
118
- if (options.withSeed) {
119
- console.error('--with-seed is not supported for ecdsa keys; ECDSA key ' +
120
- 'generation is non-deterministic and cannot be derived ' +
121
- 'from a seed.');
122
- process.exit(1);
123
- return;
124
- }
125
- const curve = normalizeEcdsaCurve({ curve: options.curve });
126
- if (!curve) {
127
- console.error(`Unknown ecdsa curve: ${options.curve}. ` +
128
- `Supported: ${SUPPORTED_ECDSA_CURVES}`);
129
- process.exit(1);
130
- return;
131
- }
132
- warnIfNotVcIssuanceCapable({ curve });
133
- const keyPair = await EcdsaMultikey.generate({ curve });
134
- const didDriver = driver();
135
- const { didDocument } = await didDriver.fromKeyPair({
136
- verificationKeyPair: keyPair
137
- });
138
- if (options.save) {
139
- const exported = await keyPair.export({
140
- publicKey: true,
141
- secretKey: true
142
- });
143
- await saveDidArtifacts({
144
- method: 'key',
145
- didDocument,
146
- exportedKeys: exported,
147
- fingerprints: [
148
- exported
149
- .publicKeyMultibase
150
- ],
151
- handle: options.handle,
152
- description: options.description
153
- });
154
- }
155
- const output = { id: didDocument.id };
156
- output.didDocument = didDocument;
157
- console.log(JSON.stringify(output, null, 2));
158
- break;
159
- }
160
- default:
161
- console.error(`Unknown key type: ${options.type}. Supported: ed25519, ecdsa`);
162
- process.exit(1);
163
- }
164
- break;
165
- }
166
- case 'web': {
167
- switch (options.type) {
168
- case 'ed25519': {
169
- if (!options.url) {
170
- console.error('did:web requires --url (e.g. --url https://example.com)');
171
- process.exit(1);
172
- return;
173
- }
174
- const envSeed = process.env.SECRET_KEY_SEED;
175
- const secretKeySeed = options.withSeed
176
- ? (envSeed ?? (await generateSecretKeySeed()))
177
- : envSeed;
178
- const seedBytes = secretKeySeed
179
- ? decodeSecretKeySeed({ secretKeySeed })
180
- : undefined;
181
- const didWebDriver = didWeb.driver();
182
- didWebDriver.use({ keyPairClass: Ed25519VerificationKey });
183
- const { didDocument, keyPairs } = await didWebDriver.generate({
184
- url: options.url,
185
- seed: seedBytes
186
- });
187
- if (options.save) {
188
- const exported = {};
189
- const fingerprints = [];
190
- for (const [methodId, keyPair] of keyPairs) {
191
- const exportedKey = (await keyPair.export({
192
- publicKey: true,
193
- secretKey: true
194
- }));
195
- exported[methodId] = exportedKey;
196
- fingerprints.push(exportedKey.publicKeyMultibase);
197
- }
198
- await saveDidArtifacts({
199
- method: 'web',
200
- didDocument,
201
- exportedKeys: exported,
202
- fingerprints,
203
- handle: options.handle,
204
- description: options.description
205
- });
206
- }
207
- const output = { id: didDocument.id };
208
- if (options.withSeed) {
209
- output.secretKeySeed = secretKeySeed;
210
- }
211
- output.didDocument = didDocument;
212
- console.log(JSON.stringify(output, null, 2));
213
- break;
214
- }
215
- case 'ecdsa': {
216
- if (!options.url) {
217
- console.error('did:web requires --url (e.g. --url https://example.com)');
218
- process.exit(1);
219
- return;
220
- }
221
- if (options.withSeed) {
222
- console.error('--with-seed is not supported for ecdsa keys; ECDSA key ' +
223
- 'generation is non-deterministic and cannot be derived ' +
224
- 'from a seed.');
225
- process.exit(1);
226
- return;
227
- }
228
- const curve = normalizeEcdsaCurve({ curve: options.curve });
229
- if (!curve) {
230
- console.error(`Unknown ecdsa curve: ${options.curve}. ` +
231
- `Supported: ${SUPPORTED_ECDSA_CURVES}`);
232
- process.exit(1);
233
- return;
234
- }
235
- warnIfNotVcIssuanceCapable({ curve });
236
- const keyPair = await EcdsaMultikey.generate({ curve });
237
- const didWebDriver = didWeb.driver();
238
- const { didDocument, keyPairs } = await didWebDriver.generate({
239
- url: options.url,
240
- verificationKeyPair: keyPair
241
- });
242
- if (options.save) {
243
- const exported = {};
244
- const fingerprints = [];
245
- for (const [methodId, savedKey] of keyPairs) {
246
- const exportedKey = (await savedKey.export({
247
- publicKey: true,
248
- secretKey: true
249
- }));
250
- exported[methodId] = exportedKey;
251
- fingerprints.push(exportedKey.publicKeyMultibase);
252
- }
253
- await saveDidArtifacts({
254
- method: 'web',
255
- didDocument,
256
- exportedKeys: exported,
257
- fingerprints,
258
- handle: options.handle,
259
- description: options.description
260
- });
261
- }
262
- const output = { id: didDocument.id };
263
- output.didDocument = didDocument;
264
- console.log(JSON.stringify(output, null, 2));
265
- break;
266
- }
267
- default:
268
- console.error(`Unknown key type: ${options.type}. ` +
269
- `Supported: ed25519, ecdsa`);
270
- process.exit(1);
271
- }
272
- break;
273
- }
274
- case 'webvh':
275
- console.log(`Creating did:${method}...`);
276
- // TODO: implement
277
- break;
278
- default:
279
- console.error(`Unknown method: ${method}. Supported: key, web, webvh`);
280
- process.exit(1);
281
- }
282
- });
42
+ .action((method = 'key', options) => runAndExit(runCreate({ method, ...options })));
283
43
  did
284
44
  .command('add-key <did>')
285
45
  .description('Add a verification key to an existing (locally stored) did:web')
@@ -287,367 +47,117 @@ export function makeDidCommand() {
287
47
  'into keyAgreement only', 'ed25519')
288
48
  .option('--curve <curve>', `ECDSA curve for --type ecdsa (supported: ${SUPPORTED_ECDSA_CURVES})`, 'p256')
289
49
  .option('--purpose <purpose...>', 'verification relationship(s) to wire the key into ' +
290
- '(default: authentication, assertionMethod, capabilityDelegation, capabilityInvocation)')
50
+ `(default: ${DEFAULT_VERIFICATION_PURPOSES.join(', ')})`)
291
51
  .option('--with-seed', 'include the new secret key seed in output (generated if SECRET_KEY_SEED is not set)')
52
+ .action((did, options) => runAndExit(runAddKey({ did, ...options })));
53
+ did
54
+ .command('add-service <did>')
55
+ .description('Add a service entry to a locally stored did:web or did:webvh DID. The ' +
56
+ 'DID may be given as a metadata handle. For did:webvh this appends a ' +
57
+ 'log entry; if pre-rotation is armed the update key is advanced as ' +
58
+ 'part of the change.')
59
+ .requiredOption('--id <id>', 'service id; a bare fragment (e.g. "files") is expanded to <did>#files')
60
+ .requiredOption('--type <type...>', 'service type(s), e.g. LinkedDomains (repeat for multiple)')
61
+ .option('--endpoint <endpoint...>', 'serviceEndpoint value(s); a single value stays a string, several ' +
62
+ 'become an array (mutually exclusive with --endpoint-json)')
63
+ .option('--endpoint-json <json>', 'serviceEndpoint as a raw JSON value (mutually exclusive with --endpoint)')
64
+ .option('--keep-old-key', 'did:webvh pre-rotation only: retain the retired update key secret in ' +
65
+ 'the sidecar (default: drop it)')
66
+ .option('-y, --yes', 'skip the did:webvh confirmation prompt')
292
67
  .action(async (did, options) => {
293
- if (!did.startsWith('did:web:')) {
294
- console.error('add-key is only supported for did:web DIDs');
295
- process.exit(1);
296
- }
297
- if (options.type !== 'ed25519' &&
298
- options.type !== 'ecdsa' &&
299
- options.type !== 'x25519') {
300
- console.error(`Unknown key type: ${options.type}. ` +
301
- 'Supported: ed25519, ecdsa, x25519');
302
- process.exit(1);
303
- }
304
- // x25519 keys are key agreement keys; they can only be wired into the
305
- // keyAgreement verification relationship.
306
- let purposes = options.purpose;
307
- if (options.type === 'x25519') {
308
- if (purposes &&
309
- purposes.some(purpose => purpose !== 'keyAgreement')) {
310
- console.error('x25519 keys can only be added to the keyAgreement ' +
311
- 'verification relationship');
312
- process.exit(1);
313
- return;
314
- }
315
- purposes = ['keyAgreement'];
316
- }
317
- let didDocument;
318
- let exportedKeys;
319
- try {
320
- didDocument = await loadDidDocument(did);
321
- exportedKeys = await loadDidKeys(did);
322
- }
323
- catch {
324
- console.error(`No locally stored did:web found for ${did}`);
325
- process.exit(1);
326
- }
327
- // ed25519 keys are derived from a seed; ecdsa and x25519 keys are
328
- // generated non-deterministically and have no seed.
329
- let secretKeySeed;
330
- let keyPair;
331
- const didWebDriver = didWeb.driver();
332
- if (options.type === 'ed25519') {
333
- const envSeed = process.env.SECRET_KEY_SEED;
334
- secretKeySeed = options.withSeed
335
- ? (envSeed ?? (await generateSecretKeySeed()))
336
- : envSeed;
337
- const seedBytes = secretKeySeed
338
- ? decodeSecretKeySeed({ secretKeySeed })
339
- : undefined;
340
- keyPair = await Ed25519VerificationKey.generate({ seed: seedBytes });
341
- didWebDriver.use({ keyPairClass: Ed25519VerificationKey });
342
- }
343
- else if (options.type === 'ecdsa') {
344
- if (options.withSeed) {
345
- console.error('--with-seed is not supported for ecdsa keys; ECDSA key ' +
346
- 'generation is non-deterministic and cannot be derived ' +
347
- 'from a seed.');
348
- process.exit(1);
349
- return;
350
- }
351
- const curve = normalizeEcdsaCurve({ curve: options.curve });
352
- if (!curve) {
353
- console.error(`Unknown ecdsa curve: ${options.curve}. ` +
354
- `Supported: ${SUPPORTED_ECDSA_CURVES}`);
355
- process.exit(1);
356
- return;
357
- }
358
- warnIfNotVcIssuanceCapable({ curve });
359
- keyPair = await EcdsaMultikey.generate({ curve });
360
- }
361
- else {
362
- if (options.withSeed) {
363
- console.error('--with-seed is not supported for x25519 keys; X25519 key ' +
364
- 'generation is non-deterministic and cannot be derived ' +
365
- 'from a seed.');
366
- process.exit(1);
367
- return;
368
- }
369
- keyPair = await X25519KeyAgreementKey2020.generate();
370
- }
371
- const keyPairs = new Map();
372
- try {
373
- await didWebDriver.addVerificationMethod({
374
- didDocument,
375
- keyPairs,
376
- keyPair,
377
- purposes
68
+ const transform = (current, resolvedDid) => {
69
+ const entry = buildServiceEntry({
70
+ did: resolvedDid,
71
+ id: options.id,
72
+ type: options.type,
73
+ endpoint: options.endpoint,
74
+ endpointJson: options.endpointJson
378
75
  });
379
- }
380
- catch (err) {
381
- console.error(err.message);
382
- process.exit(1);
383
- }
384
- // Merge the new key into the stored keys file (keyed by VM id). X25519
385
- // keys export their private half as `privateKeyMultibase`, the other
386
- // suites as `secretKeyMultibase`.
387
- const addedFingerprints = [];
388
- for (const [methodId, addedKey] of keyPairs) {
389
- const exportedKey = (await addedKey.export(options.type === 'x25519'
390
- ? { publicKey: true, privateKey: true }
391
- : { publicKey: true, secretKey: true }));
392
- exportedKeys[methodId] = exportedKey;
393
- addedFingerprints.push(exportedKey.publicKeyMultibase);
394
- }
395
- const docPath = await saveToDids({
396
- method: 'web',
397
- did,
398
- data: didDocument
399
- });
400
- await saveToDids({
401
- method: 'web',
402
- did,
403
- suffix: 'keys',
404
- data: exportedKeys
76
+ return addServiceEntry({ current, entry, did: resolvedDid });
77
+ };
78
+ return runAndExit(dispatchServiceUpdate({
79
+ ref: did,
80
+ transform,
81
+ yes: options.yes,
82
+ keepOldKey: options.keepOldKey
83
+ }));
84
+ });
85
+ did
86
+ .command('remove-service <did>')
87
+ .description('Remove a service entry (by id) from a locally stored did:web or ' +
88
+ 'did:webvh DID. The DID may be given as a metadata handle. For ' +
89
+ 'did:webvh this appends a log entry; if pre-rotation is armed the ' +
90
+ 'update key is advanced as part of the change.')
91
+ .requiredOption('--id <id>', 'id of the service to remove; a bare fragment (e.g. "files") is ' +
92
+ 'expanded to <did>#files')
93
+ .option('--keep-old-key', 'did:webvh pre-rotation only: retain the retired update key secret in ' +
94
+ 'the sidecar (default: drop it)')
95
+ .option('-y, --yes', 'skip the did:webvh confirmation prompt')
96
+ .action(async (did, options) => {
97
+ const transform = (current, resolvedDid) => removeServiceEntry({
98
+ current,
99
+ id: normalizeServiceId({ did: resolvedDid, id: options.id }),
100
+ did: resolvedDid
405
101
  });
406
- // Update the key-to-DID cache of any matching wallet keys; the DID's
407
- // own metadata sidecar is left untouched.
408
- for (const fingerprint of addedFingerprints) {
409
- if (fingerprint) {
410
- await recordKeyDidAssociation({
411
- publicKeyMultibase: fingerprint,
412
- did
413
- });
414
- }
415
- }
416
- console.error(`DID saved to ${docPath}`);
417
- const output = { id: didDocument.id };
418
- if (options.withSeed) {
419
- output.secretKeySeed = secretKeySeed;
420
- }
421
- output.didDocument = didDocument;
422
- console.log(JSON.stringify(output, null, 2));
102
+ return runAndExit(dispatchServiceUpdate({
103
+ ref: did,
104
+ transform,
105
+ yes: options.yes,
106
+ keepOldKey: options.keepOldKey
107
+ }));
423
108
  });
424
109
  did
425
110
  .command('get <did>')
426
111
  .aliases(['resolve'])
427
112
  .description('Resolve a DID to its DID document, or a DID URL (a did#fragment key ' +
428
113
  'id) to its verification method, via the security document loader')
429
- .action(async (didOrKeyId) => {
430
- let document;
431
- try {
432
- ;
433
- ({ document } = (await documentLoader(didOrKeyId)));
434
- }
435
- catch (err) {
436
- console.error(`Could not resolve "${didOrKeyId}": ${err.message}`);
437
- process.exit(1);
438
- return;
439
- }
440
- console.log(JSON.stringify(document, null, 2));
441
- });
114
+ .action((didOrKeyId) => runAndExit(runGet({ didOrKeyId })));
442
115
  did
443
116
  .command('show <did>')
444
117
  .aliases(['view', 'cat'])
445
118
  .description('Show a locally stored DID document (no secret key material) by DID ' +
446
- 'or handle')
119
+ 'or handle. For did:webvh the document is resolved from its history ' +
120
+ 'log -- the source of truth -- rather than the stored snapshot.')
447
121
  .option('--meta', 'show the DID metadata instead of the DID document')
448
122
  .option('--json', 'with --meta, output the metadata as JSON')
449
- .action(async (didRef, options) => {
450
- let did;
451
- try {
452
- did = await resolveDidRef({ ref: didRef });
453
- }
454
- catch (err) {
455
- console.error(err.message);
456
- process.exit(1);
457
- return;
458
- }
459
- let didDocument;
460
- try {
461
- didDocument = await loadDidDocument(did ?? didRef);
462
- }
463
- catch {
464
- console.error(`No locally stored DID found for ${didRef}`);
465
- process.exit(1);
466
- return;
467
- }
468
- if (options.meta) {
469
- const docDid = didDocument.id;
470
- const meta = await loadDidMeta({ did: docDid });
471
- const keyCount = Array.isArray(didDocument.verificationMethod)
472
- ? didDocument.verificationMethod.length
473
- : 0;
474
- if (options.json) {
475
- const output = {
476
- did: docDid,
477
- method: docDid.split(':')[1],
478
- ...(meta?.created && { created: meta.created }),
479
- ...(meta?.handle && { handle: meta.handle }),
480
- ...(meta?.description && { description: meta.description }),
481
- keys: keyCount
482
- };
483
- console.log(JSON.stringify(output, null, 2));
484
- return;
485
- }
486
- const rows = [
487
- ['DID', docDid],
488
- ['Method', docDid.split(':')[1]],
489
- ['Handle', meta?.handle ?? ''],
490
- ['Created', meta?.created ?? ''],
491
- ['Description', meta?.description ?? ''],
492
- ['Keys', String(keyCount)]
493
- ];
494
- console.log(renderTable({
495
- columns: [{ header: 'FIELD' }, { header: 'VALUE' }],
496
- rows
497
- }));
498
- return;
499
- }
500
- // The stored DID document holds no secret material -- signing keys live in
501
- // the separate `<did>.keys.json` file -- so it is safe to print as-is.
502
- console.log(JSON.stringify(didDocument, null, 2));
503
- });
123
+ .action((didRef, options) => runAndExit(runShow({ didRef, ...options })));
504
124
  did
505
125
  .command('list')
506
126
  .description('List locally stored DIDs with their metadata')
507
127
  .option('--json', 'output the list as a JSON array of objects with metadata')
508
128
  .option('--plain', 'output one DID per line, sorted (no metadata)')
509
- .action(async (options) => {
510
- const dids = await listDids();
511
- if (options.plain) {
512
- for (const did of dids) {
513
- console.log(did);
514
- }
515
- return;
516
- }
517
- const entries = [];
518
- for (const did of dids) {
519
- const meta = await loadDidMeta({ did });
520
- entries.push({ did, method: did.split(':')[1], ...meta });
521
- }
522
- if (options.json) {
523
- const output = entries.map(entry => ({
524
- did: entry.did,
525
- method: entry.method,
526
- ...(entry.created && { created: entry.created }),
527
- ...(entry.handle && { handle: entry.handle }),
528
- ...(entry.description && { description: entry.description })
529
- }));
530
- console.log(JSON.stringify(output, null, 2));
531
- return;
532
- }
533
- if (entries.length === 0) {
534
- return;
535
- }
536
- const rows = entries.map(entry => [
537
- entry.handle ?? '',
538
- entry.method,
539
- entry.created?.slice(0, 10) ?? '',
540
- entry.did,
541
- entry.description ?? ''
542
- ]);
543
- console.log(renderTable({
544
- columns: [
545
- { header: 'HANDLE', maxWidth: 16 },
546
- { header: 'METHOD' },
547
- { header: 'CREATED' },
548
- { header: 'DID', maxWidth: 44 },
549
- { header: 'DESCRIPTION', maxWidth: 40 }
550
- ],
551
- rows
552
- }));
553
- });
129
+ .action((options) => runAndExit(runList(options)));
554
130
  did
555
131
  .command('meta <did>')
556
132
  .description('Show or edit the metadata of a locally stored DID (by DID or handle); ' +
557
133
  'with no options, prints the current metadata')
558
134
  .option('--handle <handle>', 'set the handle (an empty string clears it)')
559
135
  .option('--description <description>', 'set the description (an empty string clears it)')
560
- .action(async (didRef, options) => {
561
- let did;
562
- try {
563
- did = await resolveDidRef({ ref: didRef });
564
- }
565
- catch (err) {
566
- console.error(err.message);
567
- process.exit(1);
568
- return;
569
- }
570
- if (did) {
571
- // Refuse to create metadata for DIDs that are not saved locally.
572
- try {
573
- await loadDidDocument(did);
574
- }
575
- catch {
576
- did = undefined;
577
- }
578
- }
579
- if (!did) {
580
- console.error(`No locally stored DID found for ${didRef}`);
581
- process.exit(1);
582
- return;
583
- }
584
- const existing = await loadDidMeta({ did });
585
- const hasEdits = options.handle !== undefined || options.description !== undefined;
586
- if (!hasEdits) {
587
- console.log(JSON.stringify(existing ?? {}, null, 2));
588
- return;
589
- }
590
- const meta = { ...(existing ?? {}) };
591
- if (options.handle !== undefined) {
592
- if (options.handle === '') {
593
- delete meta.handle;
594
- }
595
- else {
596
- meta.handle = options.handle;
597
- }
598
- }
599
- if (options.description !== undefined) {
600
- if (options.description === '') {
601
- delete meta.description;
602
- }
603
- else {
604
- meta.description = options.description;
605
- }
606
- }
607
- const filePath = await saveDidMeta({ did, meta });
608
- console.error(`Metadata saved to ${filePath}`);
609
- console.log(JSON.stringify(meta, null, 2));
610
- });
136
+ .action((didRef, options) => runAndExit(runMeta({ didRef, ...options })));
611
137
  did
612
138
  .command('remove <did>')
613
139
  .aliases(['delete', 'rm'])
614
140
  .description('Remove a locally stored DID document, its keys file, and its ' +
615
141
  'metadata sidecar (by DID or handle)')
616
- .action(async (didRef) => {
617
- let did;
618
- try {
619
- did = await resolveDidRef({ ref: didRef });
620
- }
621
- catch (err) {
622
- console.error(err.message);
623
- process.exit(1);
624
- return;
625
- }
626
- let didDocument;
627
- try {
628
- didDocument = await loadDidDocument(did ?? didRef);
629
- }
630
- catch {
631
- console.error(`No locally stored DID found for ${didRef}`);
632
- process.exit(1);
633
- return;
634
- }
635
- const docDid = did ?? didRef;
636
- // Scrub the cached key-to-DID associations of any matching wallet keys
637
- // before the DID document (the source of truth) is deleted.
638
- for (const method of didDocument.verificationMethod ?? []) {
639
- if (method.publicKeyMultibase) {
640
- await removeKeyDidAssociation({
641
- publicKeyMultibase: method.publicKeyMultibase,
642
- did: docDid
643
- });
644
- }
645
- }
646
- const removed = await removeDidFiles({ did: docDid });
647
- for (const filePath of removed) {
648
- console.error(`Removed ${filePath}`);
649
- }
650
- });
142
+ .action((didRef) => runAndExit(runRemove({ didRef })));
143
+ const webvh = new Command('webvh').description('Manage did:webvh DIDs: rotate update (authorization) keys');
144
+ webvh
145
+ .command('rotate-keys <did>')
146
+ .description('Rotate the update (authorization) key of a locally stored did:webvh ' +
147
+ 'DID. By default advances key pre-rotation -- reveals the staged next ' +
148
+ 'key and stages a fresh one -- and never touches the document ' +
149
+ 'verification methods.')
150
+ .option('--update-key <multibase...>', 'rotate to specific update key(s) by publicKeyMultibase instead of ' +
151
+ 'generating a fresh one (ordinary mode only; rejected while ' +
152
+ 'pre-rotation is armed, where the next keys are fixed by the prior ' +
153
+ 'commitment)')
154
+ .option('--enable-prerotation', 'for a DID without pre-rotation, turn it on by staging a next key this ' +
155
+ 'rotation (alone: stage only, leaving the active key unchanged)')
156
+ .option('--stop-prerotation', 'do not stage a next key; pre-rotation turns off after this rotation')
157
+ .option('--keep-old-key', 'retain the retired update key secret in the sidecar (default: drop it)')
158
+ .option('-y, --yes', 'skip the confirmation prompt')
159
+ .action((didRef, options) => runAndExit(runRotateKeys({ didRef, ...options })));
160
+ did.addCommand(webvh);
651
161
  return did;
652
162
  }
653
163
  //# sourceMappingURL=did.js.map