@interop/did-cli 0.9.0 → 0.10.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/CHANGELOG.md CHANGED
@@ -1,5 +1,96 @@
1
1
  # History
2
2
 
3
+ ## 0.10.0 - 2026-06-25
4
+
5
+ ### Added
6
+
7
+ - `did show` now resolves a `did:webvh` DID from its stored history log
8
+ (`<did>.jsonl`) -- the source of truth -- rather than printing the saved
9
+ document snapshot, so the output reflects every appended log entry. With
10
+ `--meta` it also reports the parameters resolved from the log: version id,
11
+ last-updated time, portability, pre-rotation, deactivation status, and the
12
+ count of update keys, witnesses, and watchers (also included in
13
+ `--meta --json`). A `did:webvh` DID with no stored log still falls back to the
14
+ saved snapshot.
15
+ - Add `did add-service <did>` and `did remove-service <did>`, which add or
16
+ remove a [service](https://www.w3.org/TR/did-core/#services) entry on a stored
17
+ `did:web` or `did:webvh` DID (the DID may be given as a metadata handle).
18
+ `--id` accepts a bare fragment (expanded to `<did>#fragment`) or a full id;
19
+ the type comes from `--type` (repeatable) and the endpoint from either
20
+ `--endpoint` (repeatable) or `--endpoint-json` (a raw JSON value). For
21
+ `did:web` this edits the stored document in place; for `did:webvh` it appends
22
+ a sparse log entry that carries the update keys and document verification
23
+ methods forward unchanged -- except that, when pre-rotation is armed, the
24
+ update-key ratchet is advanced as part of the change (the staged key signs the
25
+ entry), with `--keep-old-key` and `-y` / `--yes` honored as in
26
+ `webvh rotate-keys`.
27
+ - Add richer create-time options to `did create webvh`, each declared in the
28
+ signed `did.jsonl` log `parameters`: `--portable` / `--no-portable` (portable
29
+ is the default -- a portable DID can later be moved to another domain),
30
+ `--witness <did...>` (repeatable; declare witness `did:key` DIDs authorized to
31
+ co-sign log entries) with `--witness-threshold <n>` (defaults to the number of
32
+ witnesses; requires `--witness`), and `--watcher <url...>` (repeatable;
33
+ `https://` or `http://localhost`). Witnesses are **declaration only** for now
34
+ -- generating witness proofs / a `did-witness.json` sidecar remains out of
35
+ scope.
36
+ - Add key **pre-rotation** to `did:webvh`. `did create webvh` now arms
37
+ pre-rotation by default: the DID commits, in advance, to the hash of the key
38
+ allowed to perform the next update, so a compromise of the active update key
39
+ cannot seize the DID. Update keys are now **decoupled** from the document's
40
+ verification key -- `create` generates an active update key A, a staged next
41
+ update key B (whose hash is committed as `nextKeyHashes`), and a separate
42
+ document key V (wired into the verification relationships as before). On
43
+ `--save`, V is stored in `<did>.keys.json` (keyed by its document
44
+ verification-method id, so it can be selected for signing) and A+B in a new
45
+ `<did>.update-keys.json` sidecar. Pass `--no-prerotation` to create without a
46
+ staged key. Requires `@interop/did-method-webvh` >= 3.2.0 (for the exported
47
+ `deriveNextKeyHash`).
48
+ - Add `did webvh rotate-keys <did>`, which rotates a stored `did:webvh` DID's
49
+ update (authorization) key and never touches the document's verification
50
+ methods. With no flags it advances the pre-rotation ratchet in one step --
51
+ revealing and activating the staged key, then staging a fresh one -- and
52
+ deletes the retired key's secret by default. Flags: `--stop-prerotation` (turn
53
+ pre-rotation off), `--enable-prerotation` (turn it on for a DID without it;
54
+ alone, stage only), `--update-key <multibase...>` (rotate to specific key(s)
55
+ in ordinary mode; rejected while pre-rotation is armed), `--keep-old-key`
56
+ (retain the retired secret), and `-y, --yes` (skip the confirmation prompt).
57
+ - Add `was space meta <space>`, which updates a registered space's local
58
+ registry metadata (`--handle` and/or `--description`) only, leaving the
59
+ server-side space untouched. At least one of the two flags is required;
60
+ passing an empty string (e.g. `--handle ''`) clears that field.
61
+
62
+ - Implement `did create webvh`, which creates a real did:webvh DID via the
63
+ `@interop/did-method-webvh` library (Ed25519 / `eddsa-jcs-2022` only for now;
64
+ `--type ecdsa` is rejected). Requires `--url` (passed as the DID's `address`)
65
+ and supports the same `--with-seed` / `--save` / `--handle` / `--description`
66
+ flags as the other methods. did:webvh is a two-part DID: on `--save`, the
67
+ resolved DID document, keys, and metadata are written as usual under
68
+ `dids/webvh/`, plus the signed `did.jsonl` history log as a raw
69
+ newline-delimited `<did>.jsonl` file alongside them; `did remove` deletes the
70
+ log too. The generated DID is portable, and its single Ed25519 key is wired
71
+ into the same verification relationships as did:web (`authentication`,
72
+ `assertionMethod`, `capabilityDelegation`, `capabilityInvocation`; not
73
+ `keyAgreement`). Requires `@interop/did-method-webvh` >= 3.1.0. Resolving a
74
+ stored webvh DID, witnesses, and the parallel did:web alias are deferred.
75
+ (Update-key rotation and pre-rotation are implemented separately below.)
76
+ - Support `did get`/`did resolve` for `did:webvh` DIDs. A `did:webvh` driver
77
+ (`src/keys/webvh-driver.ts`) wraps `resolveDID` from
78
+ `@interop/did-method-webvh` -- fetching and verifying the DID's history log --
79
+ and is registered onto the security document loader's resolver via the new
80
+ injectable `securityLoader({ didResolver })` seam, so the `did:webvh`
81
+ dependency stays out of the shared loader. A bare DID resolves to its DID
82
+ document; a `did#fragment` URL is dereferenced to its verification method.
83
+ Requires `@interop/security-document-loader` >= 9.4.0.
84
+
85
+ ### Changed
86
+
87
+ - Update to `@interop/did-method-webvh` >= 3.3.0. `updateDID` now performs a
88
+ sparse update -- it carries the prior DID document state forward and overlays
89
+ only the fields an update actually supplies -- so `did webvh rotate-keys` no
90
+ longer reconstructs and re-supplies the document's verification methods to
91
+ keep them unchanged; the key-only rotation simply omits all document
92
+ directives.
93
+
3
94
  ## 0.9.0 - 2026-06-14
4
95
 
5
96
  ### Added
package/README.md CHANGED
@@ -453,6 +453,164 @@ seed-derivable, so `--with-seed` is not supported with `--type ecdsa` or
453
453
  ./di did add-key did:web:example.com --with-seed
454
454
  ```
455
455
 
456
+ #### Create a did:webvh DID
457
+
458
+ Generate a `did:webvh` DID. Like `did:web` it is tied to a domain, so `--url`
459
+ (the HTTPS url that will host the DID's history log) is required:
460
+
461
+ ```
462
+ ./di did create webvh --url https://example.com
463
+ {
464
+ "id": "did:webvh:Qm...:example.com",
465
+ "didDocument": { ... }
466
+ }
467
+ ```
468
+
469
+ `did:webvh` separates two key roles: an **update (authorization) key** that
470
+ signs entries in the DID's append-only history log, and the **document
471
+ verification key** wired into the `authentication`, `assertionMethod`,
472
+ `capabilityDelegation`, and `capabilityInvocation` relationships. They are
473
+ distinct keys, so the update key can be rotated without ever disturbing the
474
+ document.
475
+
476
+ By default `did:webvh` arms **key pre-rotation**: the DID commits, in advance,
477
+ to the *hash* of the key allowed to perform the next update. A compromise of the
478
+ currently active update key cannot be used to seize the DID, because the
479
+ attacker still does not hold the pre-committed next key. So `create` generates
480
+ three keys: the active update key, a staged next update key (whose hash is
481
+ committed as `nextKeyHashes`), and the document key. Pass `--no-prerotation` to
482
+ create the DID without pre-rotation (no next key is staged):
483
+
484
+ ```
485
+ ./di did create webvh --url https://example.com --no-prerotation
486
+ ```
487
+
488
+ Save the DID document, history log, and key material to local storage with
489
+ `--save` (written to `~/.config/did-cli-wallet/dids/webvh/` by default, or
490
+ `$DIDS_DIR` if set). The document key is stored in `<did>.keys.json`; the update
491
+ keys (active, and the staged next key when pre-rotation is on) are stored in a
492
+ separate `<did>.update-keys.json` sidecar, and the signed history log in
493
+ `<did>.jsonl`:
494
+
495
+ ```
496
+ ./di did create webvh --url https://example.com --save
497
+ DID saved to /home/user/.config/did-cli-wallet/dids/webvh/did:webvh:Qm...:example.com.json
498
+ DID history log saved to /home/user/.config/did-cli-wallet/dids/webvh/did:webvh:Qm...:example.com.jsonl
499
+ Update keys saved to /home/user/.config/did-cli-wallet/dids/webvh/did:webvh:Qm...:example.com.update-keys.json
500
+ {
501
+ "id": "did:webvh:Qm...:example.com",
502
+ "didDocument": { ... }
503
+ }
504
+ ```
505
+
506
+ Only Ed25519 update keys are supported (the `eddsa-jcs-2022` cryptosuite the
507
+ method uses requires them), so `--type ecdsa` is rejected.
508
+
509
+ A few more create-time options are recorded in the signed history-log
510
+ `parameters`:
511
+
512
+ - `--portable` / `--no-portable` -- a **portable** DID (the default) can later
513
+ be moved to a different domain; `--no-portable` pins it to its origin.
514
+ - `--witness <did...>` -- declare one or more **witness** `did:key` DIDs
515
+ authorized to co-sign the DID's log entries (repeatable). `--witness-threshold
516
+ <n>` sets how many witness approvals are required (defaults to the number of
517
+ witnesses; it requires `--witness`). This only *declares* the witnesses;
518
+ actually generating witness proofs is not yet supported.
519
+ - `--watcher <url...>` -- declare one or more **watcher** URLs that monitor the
520
+ DID's log (repeatable; `https://`, or `http://localhost` for local testing).
521
+
522
+ ```
523
+ ./di did create webvh --url https://example.com \
524
+ --witness did:key:z6Mk... --witness did:key:z6Mk... --witness-threshold 1 \
525
+ --watcher https://watcher.example.com --save
526
+ ```
527
+
528
+ #### Rotate a did:webvh update key
529
+
530
+ Rotate the update (authorization) key of a locally stored `did:webvh` DID with
531
+ `did webvh rotate-keys`. This appends a new entry to the DID's history log and
532
+ **never touches the document's verification methods** (those are separate keys).
533
+
534
+ With no flags it advances the pre-rotation ratchet in one step: it reveals and
535
+ activates the previously staged next key (signing the new entry with it), and
536
+ stages a fresh next key for the following rotation. The retired update key's
537
+ secret is deleted by default -- a retired key is only ever needed to *verify*
538
+ historic log entries, which uses the public key from the log, not the secret:
539
+
540
+ ```
541
+ ./di did webvh rotate-keys did:webvh:Qm...:example.com
542
+ DID document saved to /home/user/.config/did-cli-wallet/dids/webvh/did:webvh:Qm...:example.com.json
543
+ DID history log saved to /home/user/.config/did-cli-wallet/dids/webvh/did:webvh:Qm...:example.com.jsonl
544
+ Update keys saved to /home/user/.config/did-cli-wallet/dids/webvh/did:webvh:Qm...:example.com.update-keys.json
545
+ Pre-rotation is armed: a next update key is staged.
546
+ {
547
+ "id": "did:webvh:Qm...:example.com",
548
+ "didDocument": { ... }
549
+ }
550
+ ```
551
+
552
+ Pre-rotation requires the staged key to sign its own activation, so the staged
553
+ secret in `<did>.update-keys.json` is what makes the next rotation possible --
554
+ losing it means the DID can never be updated again. Keep that sidecar backed up.
555
+
556
+ Flags:
557
+
558
+ - `--stop-prerotation` -- rotate but commit no next-key hash; pre-rotation turns
559
+ **off** after this entry.
560
+ - `--enable-prerotation` -- for a DID with pre-rotation currently off, turn it
561
+ **on** by staging a next key. Alone it stages only (the active key is
562
+ unchanged, signed by the current key).
563
+ - `--update-key <multibase...>` -- in ordinary (non-pre-rotation) mode, rotate
564
+ to specific update key(s) by public key instead of generating a fresh one.
565
+ Rejected while pre-rotation is armed, where the next keys are fixed by the
566
+ prior commitment.
567
+ - `--keep-old-key` -- retain the retired update key's secret in the sidecar
568
+ instead of dropping it.
569
+ - `-y`, `--yes` -- skip the confirmation prompt (rotation is hard to undo).
570
+
571
+ #### Add or remove a service entry
572
+
573
+ Add or remove a [service](https://www.w3.org/TR/did-core/#services) entry on a
574
+ locally stored `did:web` or `did:webvh` DID. The DID may be given as a full DID
575
+ or a metadata handle. For `did:web` this edits the stored document in place; for
576
+ `did:webvh` it appends a new entry to the history log.
577
+
578
+ ```
579
+ ./di did add-service did:web:example.com \
580
+ --id files --type LinkedDomains --endpoint https://example.com
581
+ {
582
+ "id": "did:web:example.com",
583
+ "didDocument": { ..., "service": [ ... ] }
584
+ }
585
+ ```
586
+
587
+ `--id` accepts a bare fragment (`files`), which is expanded to `<did>#files`, or
588
+ a full service id. The service type comes from `--type` (repeat for multiple
589
+ types). The endpoint comes from either `--endpoint` (repeat for multiple values;
590
+ a single value stays a string, several become an array) or `--endpoint-json` (a
591
+ raw JSON value, e.g. an object); exactly one of the two is required.
592
+
593
+ ```
594
+ ./di did add-service did:web:example.com \
595
+ --id dwn --type DecentralizedWebNode \
596
+ --endpoint-json '{"nodes":["https://dwn.example"]}'
597
+ ```
598
+
599
+ Remove a service by its id (a bare fragment is expanded the same way):
600
+
601
+ ```
602
+ ./di did remove-service did:web:example.com --id files
603
+ ```
604
+
605
+ For `did:webvh`, both commands sign and append a log entry (and so accept `-y` /
606
+ `--yes` to skip the confirmation prompt). The update keys and document
607
+ verification methods are carried forward unchanged -- with one exception: when
608
+ pre-rotation is armed the library requires the staged key to sign the entry, so
609
+ the update-key ratchet is **advanced** as part of the change (the staged key is
610
+ revealed and activated, and a fresh next key is staged), exactly as in
611
+ `webvh rotate-keys`. Pass `--keep-old-key` to retain the retired update key's
612
+ secret in that case.
613
+
456
614
  #### List DIDs
457
615
 
458
616
  List the DIDs saved in local storage (via `did create --save`) as a table of
@@ -492,7 +650,8 @@ did:key:z6Mks...
492
650
 
493
651
  Resolve a DID to its DID document through the security document loader. Unlike
494
652
  `did show` (which reads local storage), `did get` resolves live: did:key is
495
- resolved offline, did:web is fetched over HTTPS. Pass a DID URL (a
653
+ resolved offline, did:web is fetched over HTTPS, and did:webvh is resolved by
654
+ fetching and verifying its history log over HTTPS. Pass a DID URL (a
496
655
  `did#fragment` key id) to dereference straight to its verification method:
497
656
 
498
657
  ```
@@ -534,6 +693,11 @@ printed as-is:
534
693
 
535
694
  Aliases: `view`, `cat`.
536
695
 
696
+ For a `did:webvh` DID the document is **resolved from its stored history log**
697
+ (`<did>.jsonl`) -- the source of truth -- rather than the saved snapshot, so the
698
+ output reflects every appended log entry. If no log is stored the saved snapshot
699
+ is shown instead.
700
+
537
701
  Pass `--meta` to show the DID's metadata instead of the DID document:
538
702
 
539
703
  ```
@@ -548,7 +712,29 @@ Description
548
712
  Keys 1
549
713
  ```
550
714
 
551
- `--meta --json` prints the same metadata as a JSON object.
715
+ For a `did:webvh` DID, `--meta` also reports the parameters resolved from the
716
+ log -- the current version id, last-updated time, portability, pre-rotation,
717
+ deactivation status, and the number of update keys, witnesses, and watchers:
718
+
719
+ ```
720
+ ./di did show my-webvh --meta
721
+ FIELD VALUE
722
+ ----------- ----------------------------------------------
723
+ DID did:webvh:Qm...:example.com
724
+ Method webvh
725
+ ...
726
+ Version 1-Qm...
727
+ Updated 2026-06-10T17:22:31Z
728
+ Portable yes
729
+ Prerotation yes
730
+ Deactivated no
731
+ Update keys 1
732
+ Witnesses 0
733
+ Watchers 0
734
+ ```
735
+
736
+ `--meta --json` prints the same metadata as a JSON object (the `did:webvh`
737
+ fields are included there too).
552
738
 
553
739
  #### Edit DID metadata
554
740
 
@@ -1146,8 +1332,10 @@ the server, or the local registry record with `--meta`:
1146
1332
  `was space update` (alias: `configure`) upserts description fields
1147
1333
  (`--name`), also refreshing the registry entry. `was space add` registers an
1148
1334
  *existing* remote space (a full space URL, or a bare id plus `--server`) in
1149
- the local registry, verifying it with a describe first. The local/remote
1150
- delete pair:
1335
+ the local registry, verifying it with a describe first. `was space meta
1336
+ <space>` updates only a registered space's local metadata (`--handle` and/or
1337
+ `--description`); the server-side space is untouched, and passing an empty
1338
+ string (`--handle ''`) clears that field. The local/remote delete pair:
1151
1339
 
1152
1340
  - `was space delete <space>` (alias: `rm`) deletes the space **on the
1153
1341
  server** (idempotent) and removes the registry entry;
@@ -1,3 +1,12 @@
1
1
  import { Command } from 'commander';
2
+ import { type DIDLog } from '@interop/did-method-webvh';
3
+ /**
4
+ * Parse a raw did:webvh history log (newline-delimited JSON) into the entry
5
+ * array the library's resolver/updater expect, ignoring blank lines.
6
+ *
7
+ * @param logText {string}
8
+ * @returns {DIDLog}
9
+ */
10
+ export declare function parseDidLog(logText: string): DIDLog;
2
11
  export declare function makeDidCommand(): Command;
3
12
  //# sourceMappingURL=did.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"did.d.ts","sourceRoot":"","sources":["../../src/commands/did.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AA0FnC,wBAAgB,cAAc,IAAI,OAAO,CA0uBxC"}
1
+ {"version":3,"file":"did.d.ts","sourceRoot":"","sources":["../../src/commands/did.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAOnC,OAAO,EAIL,KAAK,MAAM,EAEZ,MAAM,2BAA2B,CAAA;AA2FlC;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKnD;AA6qBD,wBAAgB,cAAc,IAAI,OAAO,CA41CxC"}