@interop/did-cli 0.8.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/README.md +386 -4
  3. package/dist/commands/did.d.ts +9 -0
  4. package/dist/commands/did.d.ts.map +1 -1
  5. package/dist/commands/did.js +1041 -20
  6. package/dist/commands/did.js.map +1 -1
  7. package/dist/commands/edv.d.ts +63 -0
  8. package/dist/commands/edv.d.ts.map +1 -0
  9. package/dist/commands/edv.js +627 -0
  10. package/dist/commands/edv.js.map +1 -0
  11. package/dist/commands/key.d.ts.map +1 -1
  12. package/dist/commands/key.js +44 -8
  13. package/dist/commands/key.js.map +1 -1
  14. package/dist/commands/was/space.d.ts +17 -0
  15. package/dist/commands/was/space.d.ts.map +1 -1
  16. package/dist/commands/was/space.js +37 -2
  17. package/dist/commands/was/space.js.map +1 -1
  18. package/dist/commands/was.d.ts +1 -1
  19. package/dist/commands/was.d.ts.map +1 -1
  20. package/dist/commands/was.js +11 -2
  21. package/dist/commands/was.js.map +1 -1
  22. package/dist/edv/core.d.ts +60 -0
  23. package/dist/edv/core.d.ts.map +1 -0
  24. package/dist/edv/core.js +75 -0
  25. package/dist/edv/core.js.map +1 -0
  26. package/dist/edv/document.d.ts +25 -0
  27. package/dist/edv/document.d.ts.map +1 -0
  28. package/dist/edv/document.js +19 -0
  29. package/dist/edv/document.js.map +1 -0
  30. package/dist/edv/hmac.d.ts +26 -0
  31. package/dist/edv/hmac.d.ts.map +1 -0
  32. package/dist/edv/hmac.js +67 -0
  33. package/dist/edv/hmac.js.map +1 -0
  34. package/dist/edv/recipients.d.ts +65 -0
  35. package/dist/edv/recipients.d.ts.map +1 -0
  36. package/dist/edv/recipients.js +253 -0
  37. package/dist/edv/recipients.js.map +1 -0
  38. package/dist/edv/stream.d.ts +69 -0
  39. package/dist/edv/stream.d.ts.map +1 -0
  40. package/dist/edv/stream.js +169 -0
  41. package/dist/edv/stream.js.map +1 -0
  42. package/dist/index.js +2 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/keys/webvh-driver.d.ts +24 -0
  45. package/dist/keys/webvh-driver.d.ts.map +1 -0
  46. package/dist/keys/webvh-driver.js +73 -0
  47. package/dist/keys/webvh-driver.js.map +1 -0
  48. package/dist/keys/webvh-signer.d.ts +29 -0
  49. package/dist/keys/webvh-signer.d.ts.map +1 -0
  50. package/dist/keys/webvh-signer.js +44 -0
  51. package/dist/keys/webvh-signer.js.map +1 -0
  52. package/dist/keys/webvh-update.d.ts +33 -0
  53. package/dist/keys/webvh-update.d.ts.map +1 -0
  54. package/dist/keys/webvh-update.js +62 -0
  55. package/dist/keys/webvh-update.js.map +1 -0
  56. package/dist/meta.d.ts +1 -0
  57. package/dist/meta.d.ts.map +1 -1
  58. package/dist/meta.js +1 -1
  59. package/dist/meta.js.map +1 -1
  60. package/dist/storage.d.ts +80 -4
  61. package/dist/storage.d.ts.map +1 -1
  62. package/dist/storage.js +75 -6
  63. package/dist/storage.js.map +1 -1
  64. package/dist/was/io.d.ts +14 -0
  65. package/dist/was/io.d.ts.map +1 -1
  66. package/dist/was/io.js +20 -7
  67. package/dist/was/io.js.map +1 -1
  68. package/dist/was/registry.d.ts +2 -1
  69. package/dist/was/registry.d.ts.map +1 -1
  70. package/dist/was/registry.js +16 -5
  71. package/dist/was/registry.js.map +1 -1
  72. package/package.json +9 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,163 @@
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
+
94
+ ## 0.9.0 - 2026-06-14
95
+
96
+ ### Added
97
+
98
+ - Implement `did get <did>` (alias: `resolve`), which resolves a DID to its DID
99
+ document through `@interop/security-document-loader`'s document loader
100
+ (did:key offline, did:web fetched). A DID URL (a `did#fragment` key id)
101
+ dereferences straight to its verification method. Unlike `did show`, which
102
+ reads local storage, `did get` resolves live.
103
+ - Add an `edv` command group with `encrypt` and `decrypt` subcommands
104
+ (Layer 1: raw JWE). `edv encrypt [file]` encrypts stdin or a file to one or
105
+ more X25519 recipients and emits a single flattened JWE (the `jwe` field of
106
+ an EDV Document) to stdout or an `-o` file (convention `*.jwe.json`);
107
+ `edv decrypt [file]` reverses it. Encryption is public-key (key-agreement)
108
+ only, via `@interop/minimal-cipher` with its default algorithm
109
+ (`ECDH-ES+A256KW` key wrap, `XC20P` content encryption). A recipient
110
+ (`-r/--recipient`, repeatable) is a raw X25519 `publicKeyMultibase`, a wallet
111
+ key fingerprint/handle, or a DID / DID URL (its `keyAgreement` key, typed
112
+ either `X25519KeyAgreementKey2020` or `Multikey`); `--recipient-file` reads a
113
+ key-document JSON. `--json` switches both commands
114
+ to object semantics. On decrypt, the secret key is given with `-k/--key` or
115
+ auto-selected from the wallet by matching a recipient `kid`; a non-recipient
116
+ key exits non-zero with a clear error. The full EDV Document envelope,
117
+ chunked streams, and HMAC-blinded indexing are deferred to Layer 2.
118
+ - Add EDV Document support to `edv` (Layer 2, Phase 1). `edv encrypt --document`
119
+ wraps the JWE in a full EDV Document envelope `{ id, sequence, indexed, jwe }`
120
+ (convention `*.edvdoc.json`), encrypting the input as the document `content`
121
+ with an optional `--meta <json>` object alongside it (both encrypted inside the
122
+ `jwe`); `id` is a fresh identity-multihash multibase value and `sequence`
123
+ starts at `0`, byte-faithful to `@interop/edv-client`'s `EdvClientCore` without
124
+ taking that dependency. `--update <file>` versions an existing document
125
+ (reusing its `id`, incrementing `sequence`, and merging its recipients).
126
+ `edv decrypt` detects an envelope automatically and emits its decrypted
127
+ `content` (reporting `meta`/`stream` on stderr); `--document` requires one.
128
+ Chunked streams and HMAC-blinded indexing remain deferred to later phases.
129
+ - Add chunked-stream support to `edv` (Layer 2, Phase 2). `edv encrypt --stream`
130
+ encrypts the input as a sequence of fixed-size chunks (`--chunk-size <bytes>`,
131
+ default 1 MiB) and writes a bundle directory (convention `*.edvdoc/`, so `-o`
132
+ is required): a `document.json` EDV Document carrying a cleartext
133
+ `stream: { sequence, chunks }` descriptor, plus one `chunks/<index>.jwe.json`
134
+ per chunk (`{ sequence, index, offset, jwe }`), mirroring how an EDV / WAS
135
+ server stores stream bytes separately from the document. `--meta` and
136
+ `--update` (a file or bundle) work as for `--document`. `edv decrypt`
137
+ recognizes a bundle directory, reassembles the chunks in order, and writes the
138
+ original bytes to `-o`/stdout (reporting `content`/`meta`/`stream` on stderr).
139
+ HMAC-blinded indexing remains deferred to Layer 2, Phase 3.
140
+ - Add HMAC-blinded indexing to `edv` (Layer 2, Phase 3). In
141
+ `--document`/`--stream` mode, `--index <attribute>` (repeatable; a dotted path
142
+ into `content`/`meta`) populates the envelope's `indexed` array with entries
143
+ whose attribute names and values are HMAC-blinded, so a document is searchable
144
+ the way an EDV / WAS server indexes it without the server learning the
145
+ cleartext. `--unique` marks every `--index` attribute unique; `--hmac <ref>`
146
+ selects the blinding key (auto-selected when the wallet holds exactly one). The
147
+ same key over the same value yields a stable blinded entry, matchable across
148
+ documents. The EDV Document envelope and its blinded `indexed` array are now
149
+ assembled and unwrapped by `@interop/edv-client`'s `EdvClientCore` (a new
150
+ dependency), converging on the reference implementation rather than the
151
+ hand-rolled envelope used in Phases 1-2.
152
+ - Add `key create --type hmac`, generating a `Sha256HmacKey2019` HMAC key (a
153
+ 32-byte symmetric secret used to blind EDV index attributes) via
154
+ `@interop/data-integrity-core`'s `SHA256HMACKey`. The key has no public half;
155
+ it is serialized with its secret as an `oct` JWK and identified by a random
156
+ `urn:uuid:` id. HMAC keys list with an `hmac` type label, and `key show`
157
+ prints only their `id`/`type` (never the secret). Like ecdsa/x25519, HMAC
158
+ generation is non-deterministic, so `--with-seed` / `SECRET_KEY_SEED` are not
159
+ supported.
160
+
3
161
  ## 0.8.0 - 2026-06-13
4
162
 
5
163
  ### Added
package/README.md CHANGED
@@ -81,7 +81,7 @@ SECRET_KEY_SEED=z1AXVyT6G1Qk3E9cMPkDYY6wVRpZjVGWAZ3TfrAgFZkX6bv ./di key create
81
81
  ```
82
82
 
83
83
  Specify an explicit key type with `--type` (defaults to `ed25519`; supported:
84
- `ed25519`, `ecdsa`, `x25519`):
84
+ `ed25519`, `ecdsa`, `x25519`, `hmac`):
85
85
 
86
86
  ```
87
87
  SECRET_KEY_SEED=z1Aaj5A4UCsd... ./di key create --type ed25519
@@ -132,6 +132,28 @@ private key in multibase encoding:
132
132
  Like ECDSA, X25519 key generation is non-deterministic, so `--with-seed` and
133
133
  `SECRET_KEY_SEED` are not supported with `--type x25519`.
134
134
 
135
+ Generate a `Sha256HmacKey2019` HMAC key -- a 32-byte symmetric secret used to
136
+ HMAC-blind EDV index attributes (see [Blinded indexing](#blinded-indexing---index))
137
+ -- with `--type hmac`:
138
+
139
+ ```
140
+ ./di key create --type hmac
141
+ ```
142
+
143
+ It is serialized with the secret carried as an `oct` JWK (it has no public
144
+ half), identified by a random `urn:uuid:` id:
145
+
146
+ ```json
147
+ {
148
+ "id": "urn:uuid:...",
149
+ "type": "Sha256HmacKey2019",
150
+ "secretKeyJwk": { "kty": "oct", "alg": "HS256", "k": "..." }
151
+ }
152
+ ```
153
+
154
+ HMAC key generation is non-deterministic, so `--with-seed` and `SECRET_KEY_SEED`
155
+ are not supported with `--type hmac`.
156
+
135
157
  Save the key to local wallet storage (`~/.config/did-cli-wallet/keys/` by default, or
136
158
  `$WALLET_DIR/keys/` if set) with `--save`. A `.meta.json` metadata sidecar is
137
159
  written next to the key, recording the creation timestamp; `--handle` (a short
@@ -431,6 +453,164 @@ seed-derivable, so `--with-seed` is not supported with `--type ecdsa` or
431
453
  ./di did add-key did:web:example.com --with-seed
432
454
  ```
433
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
+
434
614
  #### List DIDs
435
615
 
436
616
  List the DIDs saved in local storage (via `did create --save`) as a table of
@@ -466,6 +646,34 @@ did:key:z6Mkr...
466
646
  did:key:z6Mks...
467
647
  ```
468
648
 
649
+ #### Resolve a DID
650
+
651
+ Resolve a DID to its DID document through the security document loader. Unlike
652
+ `did show` (which reads local storage), `did get` resolves live: did:key is
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
655
+ `did#fragment` key id) to dereference straight to its verification method:
656
+
657
+ ```
658
+ ./di did get did:key:z6Mkr...
659
+ {
660
+ "@context": [ ... ],
661
+ "id": "did:key:z6Mkr...",
662
+ "verificationMethod": [ ... ],
663
+ ...
664
+ }
665
+
666
+ ./di did get did:key:z6Mkr...#z6Mkr...
667
+ {
668
+ "id": "did:key:z6Mkr...#z6Mkr...",
669
+ "type": "Ed25519VerificationKey2020",
670
+ "controller": "did:key:z6Mkr...",
671
+ "publicKeyMultibase": "z6Mkr..."
672
+ }
673
+ ```
674
+
675
+ Alias: `resolve`.
676
+
469
677
  #### Show a DID
470
678
 
471
679
  Display the DID document saved in local storage (via `did create --save`),
@@ -485,6 +693,11 @@ printed as-is:
485
693
 
486
694
  Aliases: `view`, `cat`.
487
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
+
488
701
  Pass `--meta` to show the DID's metadata instead of the DID document:
489
702
 
490
703
  ```
@@ -499,7 +712,29 @@ Description
499
712
  Keys 1
500
713
  ```
501
714
 
502
- `--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).
503
738
 
504
739
  #### Edit DID metadata
505
740
 
@@ -1097,8 +1332,10 @@ the server, or the local registry record with `--meta`:
1097
1332
  `was space update` (alias: `configure`) upserts description fields
1098
1333
  (`--name`), also refreshing the registry entry. `was space add` registers an
1099
1334
  *existing* remote space (a full space URL, or a bare id plus `--server`) in
1100
- the local registry, verifying it with a describe first. The local/remote
1101
- 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:
1102
1339
 
1103
1340
  - `was space delete <space>` (alias: `rm`) deletes the space **on the
1104
1341
  server** (idempotent) and removes the registry entry;
@@ -1428,6 +1665,151 @@ unless `WAS_TEST_SERVER_URL` points at a running server:
1428
1665
  WAS_TEST_SERVER_URL=http://localhost:3002 npm run test:node
1429
1666
  ```
1430
1667
 
1668
+ ### Encrypted Data (EDV)
1669
+
1670
+ The `edv` commands encrypt an object or file to one or more X25519 recipients
1671
+ and decrypt the result, using the EDV / [minimal-cipher](https://www.npmjs.com/package/@interop/minimal-cipher)
1672
+ serialization. The output is a single raw **JWE** (the `jwe` field of an EDV
1673
+ Document), written to stdout or an `-o` file -- by convention `*.jwe.json`.
1674
+ Encryption is public-key (key-agreement) only: there is no password mode. The
1675
+ algorithm is the library default, `ECDH-ES+A256KW` key wrap with `XC20P`
1676
+ (XChaCha20Poly1305) content encryption.
1677
+
1678
+ #### Encrypt
1679
+
1680
+ A recipient (`-r/--recipient`, repeatable, at least one required) is an X25519
1681
+ public key given as a raw `publicKeyMultibase` (starts `z6LS`), a wallet key
1682
+ fingerprint or handle, or a DID / DID URL (the DID's `keyAgreement` key; a DID
1683
+ with several keyAgreement keys needs the `did#fragment` form). A
1684
+ `--recipient-file <path>` is a key-document JSON file holding an X25519 public
1685
+ key.
1686
+
1687
+ Encrypt a JSON object (with `--json`, the input is parsed and encrypted as an
1688
+ object) to a stored x25519 key:
1689
+
1690
+ ```
1691
+ ./di key create --type x25519 --save --handle alice-kak
1692
+ echo '{"hello": "world"}' | ./di edv encrypt --json -r alice-kak -o secret.jwe.json
1693
+ ```
1694
+
1695
+ Without `--json` the raw input bytes are encrypted. Encrypt to several
1696
+ recipients by repeating `-r`:
1697
+
1698
+ ```
1699
+ ./di edv encrypt photo.png -r alice-kak -r z6LSr... -o photo.jwe.json
1700
+ ```
1701
+
1702
+ #### Decrypt
1703
+
1704
+ `-k/--key` is the X25519 secret key (fingerprint or handle) to decrypt with;
1705
+ when omitted, the matching stored key is auto-selected from the wallet. Use
1706
+ `--json` to pretty-print the decrypted object, and `-o` to write to a file.
1707
+ Only plaintext is ever written; secret key material stays in the key store.
1708
+
1709
+ ```
1710
+ ./di edv decrypt secret.jwe.json --json
1711
+ {
1712
+ "hello": "world"
1713
+ }
1714
+
1715
+ ./di edv decrypt photo.jwe.json -k alice-kak -o photo.png
1716
+ ```
1717
+
1718
+ Decryption with a key that is not a recipient exits non-zero with a clear
1719
+ error rather than emitting garbage.
1720
+
1721
+ #### EDV Documents (`--document`)
1722
+
1723
+ By default `edv encrypt` emits a bare JWE. With `-d/--document` it wraps that JWE
1724
+ in a full **EDV Document** envelope -- `{ id, sequence, indexed, jwe }`, the
1725
+ shape an EDV / WAS server stores -- written by convention to `*.edvdoc.json`. The
1726
+ input is encrypted as the document's `content`; an optional `--meta <json>`
1727
+ object is encrypted alongside it (both live inside the `jwe`, so only `id`,
1728
+ `sequence`, and `indexed` stay in cleartext). The `id` is a fresh
1729
+ identity-multihash multibase value and `sequence` starts at `0`. Index entries
1730
+ (`indexed`) are always `[]` for now; HMAC-blinded indexing and chunked streams
1731
+ are later phases.
1732
+
1733
+ ```
1734
+ echo '{"name": "alice"}' | \
1735
+ ./di edv encrypt --document -r alice-kak --meta '{"tag":"demo"}' -o doc.edvdoc.json
1736
+ ```
1737
+
1738
+ `--update <file>` versions an existing document: it reuses that document's `id`,
1739
+ increments its `sequence`, and merges its recipients (so you can add a recipient
1740
+ without re-listing the existing ones) before re-encrypting the new content.
1741
+
1742
+ ```
1743
+ echo '{"name": "alice", "v": 2}' | \
1744
+ ./di edv encrypt --document -r bob-kak --update doc.edvdoc.json -o doc.v2.edvdoc.json
1745
+ ```
1746
+
1747
+ `edv decrypt` detects an EDV Document automatically and prints its decrypted
1748
+ `content` (any `meta`/`stream` is reported on stderr); pass `-d/--document` to
1749
+ require an envelope and reject a bare JWE.
1750
+
1751
+ ```
1752
+ ./di edv decrypt doc.edvdoc.json
1753
+ {
1754
+ "name": "alice"
1755
+ }
1756
+ ```
1757
+
1758
+ #### Chunked streams (`--stream`)
1759
+
1760
+ For large inputs, `-s/--stream` encrypts the bytes as a sequence of fixed-size
1761
+ chunks rather than one JWE. The output is a **bundle directory** (convention
1762
+ `*.edvdoc/`, so `-o` is required) holding `document.json` -- an EDV Document whose
1763
+ cleartext `stream: { sequence, chunks }` descriptor records the chunk count -- and
1764
+ one `chunks/<index>.jwe.json` per chunk. This mirrors how an EDV / WAS server
1765
+ stores stream bytes as resources separate from the document. `--chunk-size
1766
+ <bytes>` sets the chunk size (default 1 MiB); `--meta` and `--update` work as for
1767
+ `--document`.
1768
+
1769
+ ```
1770
+ ./di edv encrypt photo.png --stream -r alice-kak --chunk-size 1048576 -o photo.edvdoc/
1771
+ ```
1772
+
1773
+ `edv decrypt` recognizes a bundle directory, reassembles the chunks in order, and
1774
+ writes the original bytes to `-o`/stdout (the document's `content`/`meta`/`stream`
1775
+ are reported on stderr).
1776
+
1777
+ ```
1778
+ ./di edv decrypt photo.edvdoc/ -o photo.png
1779
+ ```
1780
+
1781
+ #### Blinded indexing (`--index`)
1782
+
1783
+ In `--document`/`--stream` mode, `--index <attribute>` populates the envelope's
1784
+ `indexed` array so a document is searchable the way an EDV / WAS server indexes
1785
+ it -- without the server learning the cleartext. The attribute name and value
1786
+ are **HMAC-blinded**: a `Sha256HmacKey2019` key signs them, so the same key over
1787
+ the same value always yields the same opaque entry (matchable across documents),
1788
+ but the cleartext never leaves the client.
1789
+
1790
+ First create an HMAC key in the wallet (a 32-byte secret, no public half):
1791
+
1792
+ ```
1793
+ ./di key create --type hmac --save --handle vault-index
1794
+ ```
1795
+
1796
+ Then declare one or more indexable attribute paths (dotted paths into `content`
1797
+ or `meta`). The HMAC key is auto-selected when the wallet holds exactly one;
1798
+ otherwise pass `--hmac <id|handle>`. `--unique` marks every `--index` attribute
1799
+ as unique.
1800
+
1801
+ ```
1802
+ echo '{"type":"Person","name":"alice"}' | \
1803
+ ./di edv encrypt --document --json -r alice-kak \
1804
+ --index content.type --index content.name --hmac vault-index -o doc.edvdoc.json
1805
+ ```
1806
+
1807
+ The resulting envelope carries `indexed: [{ hmac: { id, type }, sequence,
1808
+ attributes: [{ name, value, unique? }] }]`, where each `name`/`value` is the
1809
+ blinded (opaque) form. The envelope and its blinded entries are assembled by
1810
+ [`@interop/edv-client`](https://www.npmjs.com/package/@interop/edv-client)'s
1811
+ `EdvClientCore`, so they match what an EDV server expects byte-for-byte.
1812
+
1431
1813
  ## Contribute
1432
1814
 
1433
1815
  PRs accepted. Please follow the code-style and contribution conventions in
@@ -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;AAgFnC,wBAAgB,cAAc,IAAI,OAAO,CA4tBxC"}
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"}