@interop/did-cli 0.6.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 +341 -0
- package/LICENSE.md +21 -0
- package/README.md +1307 -0
- package/dist/commands/did.d.ts +3 -0
- package/dist/commands/did.d.ts.map +1 -0
- package/dist/commands/did.js +605 -0
- package/dist/commands/did.js.map +1 -0
- package/dist/commands/key.d.ts +3 -0
- package/dist/commands/key.d.ts.map +1 -0
- package/dist/commands/key.js +430 -0
- package/dist/commands/key.js.map +1 -0
- package/dist/commands/vc.d.ts +79 -0
- package/dist/commands/vc.d.ts.map +1 -0
- package/dist/commands/vc.js +528 -0
- package/dist/commands/vc.js.map +1 -0
- package/dist/commands/wallet.d.ts +14 -0
- package/dist/commands/wallet.d.ts.map +1 -0
- package/dist/commands/wallet.js +48 -0
- package/dist/commands/wallet.js.map +1 -0
- package/dist/commands/was.d.ts +500 -0
- package/dist/commands/was.d.ts.map +1 -0
- package/dist/commands/was.js +1833 -0
- package/dist/commands/was.js.map +1 -0
- package/dist/commands/zcap.d.ts +85 -0
- package/dist/commands/zcap.d.ts.map +1 -0
- package/dist/commands/zcap.js +447 -0
- package/dist/commands/zcap.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/keys/ecdsa.d.ts +40 -0
- package/dist/keys/ecdsa.d.ts.map +1 -0
- package/dist/keys/ecdsa.js +73 -0
- package/dist/keys/ecdsa.js.map +1 -0
- package/dist/meta.d.ts +164 -0
- package/dist/meta.d.ts.map +1 -0
- package/dist/meta.js +286 -0
- package/dist/meta.js.map +1 -0
- package/dist/storage.d.ts +191 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +307 -0
- package/dist/storage.js.map +1 -0
- package/dist/table.d.ts +43 -0
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +61 -0
- package/dist/table.js.map +1 -0
- package/dist/vc/fixtures/welcomeCredential.d.ts +22 -0
- package/dist/vc/fixtures/welcomeCredential.d.ts.map +1 -0
- package/dist/vc/fixtures/welcomeCredential.js +25 -0
- package/dist/vc/fixtures/welcomeCredential.js.map +1 -0
- package/dist/vc/issue.d.ts +24 -0
- package/dist/vc/issue.d.ts.map +1 -0
- package/dist/vc/issue.js +211 -0
- package/dist/vc/issue.js.map +1 -0
- package/dist/vc/registries.d.ts +30 -0
- package/dist/vc/registries.d.ts.map +1 -0
- package/dist/vc/registries.js +53 -0
- package/dist/vc/registries.js.map +1 -0
- package/dist/vc/registryManager.d.ts +25 -0
- package/dist/vc/registryManager.d.ts.map +1 -0
- package/dist/vc/registryManager.js +29 -0
- package/dist/vc/registryManager.js.map +1 -0
- package/dist/vc/suites/expirationSuite.d.ts +23 -0
- package/dist/vc/suites/expirationSuite.d.ts.map +1 -0
- package/dist/vc/suites/expirationSuite.js +84 -0
- package/dist/vc/suites/expirationSuite.js.map +1 -0
- package/dist/vc/suites/issuerDetailsSuite.d.ts +22 -0
- package/dist/vc/suites/issuerDetailsSuite.d.ts.map +1 -0
- package/dist/vc/suites/issuerDetailsSuite.js +69 -0
- package/dist/vc/suites/issuerDetailsSuite.js.map +1 -0
- package/dist/vc/verify.d.ts +46 -0
- package/dist/vc/verify.d.ts.map +1 -0
- package/dist/vc/verify.js +147 -0
- package/dist/vc/verify.js.map +1 -0
- package/dist/was/address.d.ts +44 -0
- package/dist/was/address.d.ts.map +1 -0
- package/dist/was/address.js +98 -0
- package/dist/was/address.js.map +1 -0
- package/dist/was/capability.d.ts +65 -0
- package/dist/was/capability.d.ts.map +1 -0
- package/dist/was/capability.js +108 -0
- package/dist/was/capability.js.map +1 -0
- package/dist/was/client.d.ts +108 -0
- package/dist/was/client.d.ts.map +1 -0
- package/dist/was/client.js +142 -0
- package/dist/was/client.js.map +1 -0
- package/dist/was/io.d.ts +71 -0
- package/dist/was/io.d.ts.map +1 -0
- package/dist/was/io.js +146 -0
- package/dist/was/io.js.map +1 -0
- package/dist/was/registry.d.ts +79 -0
- package/dist/was/registry.d.ts.map +1 -0
- package/dist/was/registry.js +99 -0
- package/dist/was/registry.js.map +1 -0
- package/dist/zcap/create.d.ts +20 -0
- package/dist/zcap/create.d.ts.map +1 -0
- package/dist/zcap/create.js +29 -0
- package/dist/zcap/create.js.map +1 -0
- package/dist/zcap/delegate.d.ts +44 -0
- package/dist/zcap/delegate.d.ts.map +1 -0
- package/dist/zcap/delegate.js +77 -0
- package/dist/zcap/delegate.js.map +1 -0
- package/dist/zcap/encoding.d.ts +17 -0
- package/dist/zcap/encoding.d.ts.map +1 -0
- package/dist/zcap/encoding.js +37 -0
- package/dist/zcap/encoding.js.map +1 -0
- package/dist/zcap/signer.d.ts +20 -0
- package/dist/zcap/signer.d.ts.map +1 -0
- package/dist/zcap/signer.js +62 -0
- package/dist/zcap/signer.js.map +1 -0
- package/dist/zcap/ttl.d.ts +19 -0
- package/dist/zcap/ttl.d.ts.map +1 -0
- package/dist/zcap/ttl.js +40 -0
- package/dist/zcap/ttl.js.map +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,1307 @@
|
|
|
1
|
+
# DID CLI wallet _(@interop/did-cli)_
|
|
2
|
+
|
|
3
|
+
> A command line client for managing DIDs, VCs, zCaps, and corresponding cryptographic key pairs, written in Typescript.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Background](#background)
|
|
8
|
+
- [Install](#install)
|
|
9
|
+
- [Usage](#usage)
|
|
10
|
+
- [Contribute](#contribute)
|
|
11
|
+
- [License](#license)
|
|
12
|
+
|
|
13
|
+
## Background
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Help is available with the `--help/-h` command line option:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
./di -h
|
|
23
|
+
./di COMMAND -h
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Key Management
|
|
27
|
+
|
|
28
|
+
#### Create a key pair
|
|
29
|
+
|
|
30
|
+
Generate a random Ed25519 key pair (ed25519 is the default type):
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
./di key create
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If you'd like to also generate a secret key seed (to help deterministically
|
|
37
|
+
generate the same key pair in the future), pass in the `--with-seed` flag:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
./di key create --with-seed
|
|
41
|
+
{
|
|
42
|
+
"secretKeySeed": "z1AXVyT6G1Qk3E9cMPkDYY6wVRpZjVGWAZ3TfrAgFZkX6bv",
|
|
43
|
+
"keyPair": {
|
|
44
|
+
"@context": "https://w3id.org/security/multikey/v1",
|
|
45
|
+
"type": "Multikey",
|
|
46
|
+
"publicKeyMultibase": "z6MkrLBubwzwEvwmsyEKd2kJ6pt91E6MHdf3EeQMnCsdX2hM",
|
|
47
|
+
"secretKeyMultibase": "zruzykbtvWUgV8Tp1LKVEuTmywLEa75qHsvWRVarVhdgHiCgiMYTSDXTavJVh47Cwes4mKgdAY5PTizbRvHXcA7XcLF"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Generate a deterministic key pair by setting the `SECRET_KEY_SEED` environment
|
|
53
|
+
variable to a multibase-encoded seed (e.g. from `@digitalcredentials/bnid`):
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
SECRET_KEY_SEED=z1AXVyT6G1Qk3E9cMPkDYY6wVRpZjVGWAZ3TfrAgFZkX6bv ./di key create
|
|
57
|
+
{
|
|
58
|
+
"@context": "https://w3id.org/security/multikey/v1",
|
|
59
|
+
"type": "Multikey",
|
|
60
|
+
"publicKeyMultibase": "z6MkrLBubwzwEvwmsyEKd2kJ6pt91E6MHdf3EeQMnCsdX2hM",
|
|
61
|
+
"secretKeyMultibase": "zruzykbtvWUgV8Tp1LKVEuTmywLEa75qHsvWRVarVhdgHiCgiMYTSDXTavJVh47Cwes4mKgdAY5PTizbRvHXcA7XcLF"
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Specify an explicit key type with `--type` (defaults to `ed25519`; supported:
|
|
66
|
+
`ed25519`, `ecdsa`):
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
SECRET_KEY_SEED=z1Aaj5A4UCsd... ./di key create --type ed25519
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Output is a JSON-LD Multikey document with both the public and secret key in
|
|
73
|
+
multibase encoding:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"@context": "https://w3id.org/security/multikey/v1",
|
|
78
|
+
"type": "Multikey",
|
|
79
|
+
"publicKeyMultibase": "z6Mk...",
|
|
80
|
+
"secretKeyMultibase": "zrv..."
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Generate an ECDSA key with `--type ecdsa`. The curve is chosen with `--curve`
|
|
85
|
+
(defaults to `p256`; supported: `p256`, `p384`, `p521`, each also accepted in
|
|
86
|
+
hyphenated `p-256` and SECG `secp256r1` spellings, case-insensitively):
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
./di key create --type ecdsa --curve p384
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
ECDSA keys are serialized as Multikey, the same as Ed25519. Note that ECDSA key
|
|
93
|
+
generation is non-deterministic (it cannot be derived from a seed), so
|
|
94
|
+
`--with-seed` and `SECRET_KEY_SEED` are not supported with `--type ecdsa`.
|
|
95
|
+
|
|
96
|
+
Save the key to local wallet storage (`~/.config/did-cli-wallet/keys/` by default, or
|
|
97
|
+
`$WALLET_DIR/keys/` if set) with `--save`. A `.meta.json` metadata sidecar is
|
|
98
|
+
written next to the key, recording the creation timestamp; `--handle` (a short
|
|
99
|
+
tag for telling keys apart) and `--description` add user-defined metadata to
|
|
100
|
+
it (both require `--save`):
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
./di key create --save --handle issuer-signing --description 'Demo issuer signing key'
|
|
104
|
+
Key saved to /home/user/.config/did-cli-wallet/keys/2026-06-10-ed25519-z6Mkr....json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### List key pairs
|
|
108
|
+
|
|
109
|
+
List the key pairs saved in local wallet storage (via `key create --save`) as
|
|
110
|
+
a table of their metadata. The DIDS column shows the locally stored DIDs whose
|
|
111
|
+
documents reference the key, derived by scanning the saved DID documents:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
./di key list
|
|
115
|
+
HANDLE TYPE CREATED FINGERPRINT DIDS DESCRIPTION
|
|
116
|
+
-------------- ------- ---------- ---------------------------- ------------------- -----------------------
|
|
117
|
+
issuer-signing ed25519 2026-06-10 z6MkrLBubwzwEv...MnCsdX2hM did:key:z6MkrL... Demo issuer signing key
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
If no keys are stored, nothing is printed. Pass `--json` to output the list as
|
|
121
|
+
a JSON array of objects with metadata:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
./di key list --json
|
|
125
|
+
[
|
|
126
|
+
{
|
|
127
|
+
"fingerprint": "z6Mkr...",
|
|
128
|
+
"storageId": "2026-06-10-ed25519-z6Mkr...",
|
|
129
|
+
"type": "ed25519",
|
|
130
|
+
"created": "2026-06-10T17:22:31.123Z",
|
|
131
|
+
"handle": "issuer-signing",
|
|
132
|
+
"description": "Demo issuer signing key",
|
|
133
|
+
"dids": ["did:key:z6Mkr..."]
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or pass `--plain` to print just the fingerprints (multibase-encoded public
|
|
139
|
+
keys), one per line, sorted:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
./di key list --plain
|
|
143
|
+
z6Mkr...
|
|
144
|
+
z6Mks...
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Show a key pair
|
|
148
|
+
|
|
149
|
+
Display a key saved in local wallet storage, looked up by its fingerprint
|
|
150
|
+
(`publicKeyMultibase`, as printed by `key list`) or by its metadata handle.
|
|
151
|
+
Only the public key object is shown -- the stored secret key is never included
|
|
152
|
+
in the output:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
./di key show z6Mkr...
|
|
156
|
+
{
|
|
157
|
+
"@context": "https://w3id.org/security/multikey/v1",
|
|
158
|
+
"id": "...",
|
|
159
|
+
"type": "Multikey",
|
|
160
|
+
"controller": "...",
|
|
161
|
+
"publicKeyMultibase": "z6Mkr..."
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Aliases: `view`, `cat`.
|
|
166
|
+
|
|
167
|
+
Pass `--meta` to show the key's metadata instead of the public key object,
|
|
168
|
+
including the DIDs the key participates in (derived from the locally stored
|
|
169
|
+
DID documents):
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
./di key show issuer-signing --meta
|
|
173
|
+
FIELD VALUE
|
|
174
|
+
----------- ------------------------------------------------
|
|
175
|
+
Fingerprint z6Mkr...
|
|
176
|
+
Type ed25519
|
|
177
|
+
Created 2026-06-10T17:22:31.123Z
|
|
178
|
+
Handle issuer-signing
|
|
179
|
+
Description Demo issuer signing key
|
|
180
|
+
DIDs did:key:z6Mkr...
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`--meta --json` prints the same metadata as a JSON object.
|
|
184
|
+
|
|
185
|
+
#### Edit key metadata
|
|
186
|
+
|
|
187
|
+
Show or edit the metadata of a stored key with `key meta` (looked up by
|
|
188
|
+
fingerprint or handle). With no options it prints the current metadata; with
|
|
189
|
+
`--handle` / `--description` it updates the metadata sidecar (the key file
|
|
190
|
+
itself is never rewritten). Passing an empty string clears a field:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
./di key meta z6Mkr... --handle issuer-signing --description 'Demo issuer signing key'
|
|
194
|
+
Metadata saved to /home/user/.config/did-cli-wallet/keys/2026-06-10-ed25519-z6Mkr....meta.json
|
|
195
|
+
{
|
|
196
|
+
"created": "2026-06-10T17:22:31.123Z",
|
|
197
|
+
"handle": "issuer-signing",
|
|
198
|
+
"description": "Demo issuer signing key"
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
./di key meta issuer-signing --description ''
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Keys saved before metadata support get a sidecar created on first edit, with
|
|
205
|
+
`created` backfilled from the date prefix of the key's file name.
|
|
206
|
+
|
|
207
|
+
#### Remove a key pair
|
|
208
|
+
|
|
209
|
+
Remove a stored key with `key remove` (aliases: `delete`, `rm`), looked up by
|
|
210
|
+
fingerprint or handle. Both the key file and its `.meta.json` metadata sidecar
|
|
211
|
+
are deleted:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
./di key remove issuer-signing
|
|
215
|
+
Removed /home/user/.config/did-cli-wallet/keys/2026-06-10-ed25519-z6Mkr....json
|
|
216
|
+
Removed /home/user/.config/did-cli-wallet/keys/2026-06-10-ed25519-z6Mkr....meta.json
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### DID Management
|
|
220
|
+
|
|
221
|
+
#### Create a DID
|
|
222
|
+
|
|
223
|
+
Generate a random Ed25519 `did:key` DID (method defaults to `key`):
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
./di did create
|
|
227
|
+
{
|
|
228
|
+
"id": "did:key:z6Mkr...",
|
|
229
|
+
"didDocument": { ... }
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Or pass the method explicitly:
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
./di did create key
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
By default the DID's verification key is Ed25519. Pass `--type ecdsa` (with an
|
|
240
|
+
optional `--curve`, defaulting to `p256`) to mint a DID backed by an ECDSA key
|
|
241
|
+
instead. This works for both `did:key` and `did:web`:
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
./di did create key --type ecdsa --curve p384
|
|
245
|
+
./di did create web --type ecdsa --url https://example.com
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
ECDSA works for `did create web --type ecdsa` and `did add-key --type ecdsa`
|
|
249
|
+
too. Because ECDSA keys are not seed-derivable, `--with-seed` and
|
|
250
|
+
`SECRET_KEY_SEED` are not supported with `--type ecdsa`.
|
|
251
|
+
|
|
252
|
+
To also include the secret key seed in the output (useful for re-deriving the
|
|
253
|
+
same DID later), pass `--with-seed`:
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
./di did create --with-seed
|
|
257
|
+
{
|
|
258
|
+
"id": "did:key:z6MkrLBubwzwEvwmsyEKd2kJ6pt91E6MHdf3EeQMnCsdX2hM",
|
|
259
|
+
"secretKeySeed": "z1AXVyT6G1Qk3E9cMPkDYY6wVRpZjVGWAZ3TfrAgFZkX6bv",
|
|
260
|
+
"didDocument": {
|
|
261
|
+
"@context": [ ... ],
|
|
262
|
+
"id": "did:key:z6MkrLBubwzwEvwmsyEKd2kJ6pt91E6MHdf3EeQMnCsdX2hM",
|
|
263
|
+
"verificationMethod": [ ... ],
|
|
264
|
+
...
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Generate a deterministic DID by setting the `SECRET_KEY_SEED` environment
|
|
270
|
+
variable to a multibase-encoded seed (e.g. from `@digitalcredentials/bnid`):
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
SECRET_KEY_SEED=z1AXVyT6G1Qk3E9cMPkDYY6wVRpZjVGWAZ3TfrAgFZkX6bv ./di did create
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Save the DID document and key material to local storage with `--save`
|
|
277
|
+
(written to `~/.config/did-cli-wallet/dids/` by default, or `$DIDS_DIR` if set). A `.meta.json`
|
|
278
|
+
metadata sidecar is written next to the DID document, recording the creation
|
|
279
|
+
timestamp; `--handle` and `--description` add user-defined metadata to it
|
|
280
|
+
(both require `--save`):
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
./di did create --save --handle demo-issuer
|
|
284
|
+
DID saved to /home/user/.config/did-cli-wallet/dids/key/did:key:z6Mkr....json
|
|
285
|
+
{
|
|
286
|
+
"id": "did:key:z6Mkr...",
|
|
287
|
+
"didDocument": { ... }
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
If the DID's verification key also exists in the local wallet (e.g. both were
|
|
292
|
+
derived from the same seed), saving the DID records the association in that
|
|
293
|
+
key's metadata sidecar as well.
|
|
294
|
+
|
|
295
|
+
#### Create a did:web DID
|
|
296
|
+
|
|
297
|
+
Generate a `did:web` DID. Unlike `did:key`, a `did:web` DID is tied to a domain,
|
|
298
|
+
so `--url` (the HTTPS url of the DID document) is required:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
./di did create web --url https://example.com
|
|
302
|
+
{
|
|
303
|
+
"id": "did:web:example.com",
|
|
304
|
+
"didDocument": { ... }
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
This generates a single Ed25519 verification key, wired into the
|
|
309
|
+
`authentication`, `assertionMethod`, `capabilityDelegation`, and
|
|
310
|
+
`capabilityInvocation` relationships. Additional keys can be added later.
|
|
311
|
+
|
|
312
|
+
As with `did:key`, pass `--with-seed` to include the secret key seed in the
|
|
313
|
+
output (useful for re-deriving the same DID later):
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
./di did create web --url https://example.com --with-seed
|
|
317
|
+
{
|
|
318
|
+
"id": "did:web:example.com",
|
|
319
|
+
"secretKeySeed": "z1AXVyT6G1Qk3E9cMPkDYY6wVRpZjVGWAZ3TfrAgFZkX6bv",
|
|
320
|
+
"didDocument": { ... }
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Or set the `SECRET_KEY_SEED` environment variable to a multibase-encoded seed to
|
|
325
|
+
generate the DID deterministically:
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
SECRET_KEY_SEED=z1AXVyT6G1Qk3E9cMPkDYY6wVRpZjVGWAZ3TfrAgFZkX6bv \
|
|
329
|
+
./di did create web --url https://example.com
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Save the DID document and key material to local storage with `--save` (written
|
|
333
|
+
to `~/.config/did-cli-wallet/dids/web/` by default, or `$DIDS_DIR` if set). The key file is an
|
|
334
|
+
object keyed by verification method id, so further keys can be appended later:
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
./di did create web --url https://example.com --save
|
|
338
|
+
DID saved to /home/user/.config/did-cli-wallet/dids/web/did:web:example.com.json
|
|
339
|
+
{
|
|
340
|
+
"id": "did:web:example.com",
|
|
341
|
+
"didDocument": { ... }
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### Add a key to a did:web DID
|
|
346
|
+
|
|
347
|
+
Add another verification key to an existing, locally stored `did:web` DID (the
|
|
348
|
+
DID must have been saved with `did create web --save`). The new key is generated,
|
|
349
|
+
added to the DID document, and both the document and key file in storage are
|
|
350
|
+
updated in place:
|
|
351
|
+
|
|
352
|
+
```
|
|
353
|
+
./di did add-key did:web:example.com
|
|
354
|
+
DID saved to /home/user/.config/did-cli-wallet/dids/web/did:web:example.com.json
|
|
355
|
+
{
|
|
356
|
+
"id": "did:web:example.com",
|
|
357
|
+
"didDocument": { ... }
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
By default the new key is wired into the `authentication`, `assertionMethod`,
|
|
362
|
+
`capabilityDelegation`, and `capabilityInvocation` relationships. Pass
|
|
363
|
+
`--purpose` (repeatable) to choose specific relationships:
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
./di did add-key did:web:example.com --purpose authentication --purpose assertionMethod
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
By default the new key is Ed25519; pass `--type ecdsa` (with an optional
|
|
370
|
+
`--curve`, defaulting to `p256`) to add an ECDSA key instead:
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
./di did add-key did:web:example.com --type ecdsa --curve p384
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
For Ed25519 keys, the new key is derived from a seed (as with `did create`):
|
|
377
|
+
pass `--with-seed` to generate (and print) a fresh seed, or set `SECRET_KEY_SEED`
|
|
378
|
+
to derive the key deterministically. ECDSA keys are not seed-derivable, so
|
|
379
|
+
`--with-seed` is not supported with `--type ecdsa`:
|
|
380
|
+
|
|
381
|
+
```
|
|
382
|
+
./di did add-key did:web:example.com --with-seed
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
#### List DIDs
|
|
386
|
+
|
|
387
|
+
List the DIDs saved in local storage (via `did create --save`) as a table of
|
|
388
|
+
their metadata:
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
./di did list
|
|
392
|
+
HANDLE METHOD CREATED DID DESCRIPTION
|
|
393
|
+
----------- ------ ---------- -------------------------------------------- -----------
|
|
394
|
+
demo-issuer key 2026-06-10 did:key:z6MkrLBubwzwEvwms...6MHdf3EeQMnCsdX2hM
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
If no DIDs are stored, nothing is printed. Pass `--json` to output the list as
|
|
398
|
+
a JSON array of objects with metadata:
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
./di did list --json
|
|
402
|
+
[
|
|
403
|
+
{
|
|
404
|
+
"did": "did:key:z6Mkr...",
|
|
405
|
+
"method": "key",
|
|
406
|
+
"created": "2026-06-10T17:22:31.123Z",
|
|
407
|
+
"handle": "demo-issuer"
|
|
408
|
+
}
|
|
409
|
+
]
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Or pass `--plain` to print just the DIDs, one per line, sorted:
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
./di did list --plain
|
|
416
|
+
did:key:z6Mkr...
|
|
417
|
+
did:key:z6Mks...
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
#### Show a DID
|
|
421
|
+
|
|
422
|
+
Display the DID document saved in local storage (via `did create --save`),
|
|
423
|
+
looked up by DID or by its metadata handle. The stored DID document holds no
|
|
424
|
+
secret key material -- signing keys live in a separate key file -- so it is
|
|
425
|
+
printed as-is:
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
./di did show did:key:z6Mkr...
|
|
429
|
+
{
|
|
430
|
+
"@context": [ ... ],
|
|
431
|
+
"id": "did:key:z6Mkr...",
|
|
432
|
+
"verificationMethod": [ ... ],
|
|
433
|
+
...
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Aliases: `view`, `cat`.
|
|
438
|
+
|
|
439
|
+
Pass `--meta` to show the DID's metadata instead of the DID document:
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
./di did show demo-issuer --meta
|
|
443
|
+
FIELD VALUE
|
|
444
|
+
----------- ----------------------------------------------
|
|
445
|
+
DID did:key:z6Mkr...
|
|
446
|
+
Method key
|
|
447
|
+
Handle demo-issuer
|
|
448
|
+
Created 2026-06-10T17:22:31.123Z
|
|
449
|
+
Description
|
|
450
|
+
Keys 1
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
`--meta --json` prints the same metadata as a JSON object.
|
|
454
|
+
|
|
455
|
+
#### Edit DID metadata
|
|
456
|
+
|
|
457
|
+
Show or edit the metadata of a stored DID with `did meta` (looked up by DID or
|
|
458
|
+
handle). With no options it prints the current metadata; with `--handle` /
|
|
459
|
+
`--description` it updates the metadata sidecar (the DID document itself is
|
|
460
|
+
never rewritten). Passing an empty string clears a field:
|
|
461
|
+
|
|
462
|
+
```
|
|
463
|
+
./di did meta did:key:z6Mkr... --handle demo-issuer --description 'Issuer DID for the demo'
|
|
464
|
+
Metadata saved to /home/user/.config/did-cli-wallet/dids/key/did:key:z6Mkr....meta.json
|
|
465
|
+
{
|
|
466
|
+
"created": "2026-06-10T17:22:31.123Z",
|
|
467
|
+
"handle": "demo-issuer",
|
|
468
|
+
"description": "Issuer DID for the demo"
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
#### Remove a DID
|
|
473
|
+
|
|
474
|
+
Remove a stored DID with `did remove` (aliases: `delete`, `rm`), looked up by
|
|
475
|
+
DID or handle. The DID document, its `.keys.json` key file, and its
|
|
476
|
+
`.meta.json` metadata sidecar are all deleted, and the DID is scrubbed from
|
|
477
|
+
the cached `dids` associations of any matching wallet keys:
|
|
478
|
+
|
|
479
|
+
```
|
|
480
|
+
./di did remove demo-issuer
|
|
481
|
+
Removed /home/user/.config/did-cli-wallet/dids/key/did:key:z6Mkr....json
|
|
482
|
+
Removed /home/user/.config/did-cli-wallet/dids/key/did:key:z6Mkr....keys.json
|
|
483
|
+
Removed /home/user/.config/did-cli-wallet/dids/key/did:key:z6Mkr....meta.json
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### Verifiable Credentials
|
|
487
|
+
|
|
488
|
+
#### Verify a credential
|
|
489
|
+
|
|
490
|
+
Run full verification on a Verifiable Credential (JSON). Beyond the
|
|
491
|
+
cryptographic signature check, this also verifies expiration, revocation /
|
|
492
|
+
status, and whether the issuer DID is recognized in any trusted registry
|
|
493
|
+
(via `@interop/verifier-core` and `@digitalcredentials/issuer-registry-client`).
|
|
494
|
+
|
|
495
|
+
The credential is read from a file argument, an http(s) URL, or, if neither
|
|
496
|
+
is given, from stdin:
|
|
497
|
+
|
|
498
|
+
```
|
|
499
|
+
./di vc verify credential.json
|
|
500
|
+
./di vc verify https://example.com/credentials/123.json
|
|
501
|
+
cat credential.json | ./di vc verify
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
By default it prints the full `@interop/verifier-core` verification result
|
|
505
|
+
(top-level `verified`, a per-suite `summary`, and the flat `results` of every
|
|
506
|
+
check). Pass `--summary` for a compact, human-friendly object instead:
|
|
507
|
+
|
|
508
|
+
```
|
|
509
|
+
./di vc verify credential.json --summary
|
|
510
|
+
{
|
|
511
|
+
"verified": true,
|
|
512
|
+
"checks": {
|
|
513
|
+
"signature": true,
|
|
514
|
+
"revoked": false,
|
|
515
|
+
"issuerRecognized": true
|
|
516
|
+
},
|
|
517
|
+
"matchingIssuers": [ ... ]
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
A check is omitted from `checks` when it was skipped (for example `expired` is
|
|
522
|
+
absent when the credential has no expiration date).
|
|
523
|
+
|
|
524
|
+
The exit code is scriptable: `0` when the credential verified, `1` when it did
|
|
525
|
+
not, and `2` on a read/parse error or a structurally malformed credential.
|
|
526
|
+
|
|
527
|
+
The trusted registry list is fetched from the DCC
|
|
528
|
+
[known-did-registries](https://digitalcredentials.github.io/dcc-known-registries/known-did-registries.json)
|
|
529
|
+
at runtime, falling back to a bundled list of DCC registries when the network
|
|
530
|
+
is unavailable.
|
|
531
|
+
|
|
532
|
+
#### Issue a credential
|
|
533
|
+
|
|
534
|
+
Issue (sign) an unsigned Verifiable Credential with a locally-stored DID, acting
|
|
535
|
+
as a command-line wallet and issuer. The credential is read from a file
|
|
536
|
+
argument, an http(s) URL, or, if neither is given, from stdin, and the issued
|
|
537
|
+
credential is printed to stdout. If the input already carries a proof, issuing
|
|
538
|
+
appends an additional one.
|
|
539
|
+
|
|
540
|
+
The DID to issue with is required (`--did`); it must have been saved locally (see
|
|
541
|
+
`di did create --save`):
|
|
542
|
+
|
|
543
|
+
```
|
|
544
|
+
./di vc issue credential.json --did did:key:z6Mk...
|
|
545
|
+
cat credential.json | ./di vc issue --did did:key:z6Mk...
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
The credential's `issuer` is set to the signing DID when the input has none.
|
|
549
|
+
When the input already names an `issuer`, it must match the signing DID,
|
|
550
|
+
otherwise issuance is aborted -- a credential cannot be issued by a DID other
|
|
551
|
+
than the one named as its issuer.
|
|
552
|
+
|
|
553
|
+
By default the first key in the DID's `assertionMethod` relationship is used.
|
|
554
|
+
Pass `--key` to choose a specific verification method; it must be authorized by
|
|
555
|
+
the DID's `assertionMethod` array, otherwise issuance fails:
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
./di vc issue credential.json --did did:key:z6Mk... --key did:key:z6Mk...#z6Mk...
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
The signature suite defaults to the signing key's type. An Ed25519 DID signs
|
|
562
|
+
with `eddsa-rdfc-2022` (a W3C Data Integrity proof) by default; pass
|
|
563
|
+
`--suite Ed25519Signature2020` for the classic Ed25519Signature2020 proof:
|
|
564
|
+
|
|
565
|
+
```
|
|
566
|
+
./di vc issue credential.json --did did:key:z6Mk... --suite Ed25519Signature2020
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
An ECDSA DID (see `did create --type ecdsa`) signs with `ecdsa-rdfc-2019`. The
|
|
570
|
+
suite is selected automatically from the key, so no `--suite` flag is needed:
|
|
571
|
+
|
|
572
|
+
```
|
|
573
|
+
./di vc issue credential.json --did did:key:zDna...
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Only the P-256 and P-384 curves can issue credentials -- the `ecdsa-rdfc-2019`
|
|
577
|
+
cryptosuite does not support P-521 (key creation warns about this). A suite that
|
|
578
|
+
does not match the key type (e.g. `--suite eddsa-rdfc-2022` for an ECDSA key) is
|
|
579
|
+
rejected. ECDSA credentials round-trip through `vc verify` (below).
|
|
580
|
+
|
|
581
|
+
Pass `--save` to also store the issued credential in local wallet storage
|
|
582
|
+
(`~/.config/did-cli-wallet/credentials/` by default, or `$WALLET_DIR` if set); `--save`
|
|
583
|
+
records the creation timestamp in a `.meta.json` metadata sidecar, and
|
|
584
|
+
`--handle` / `--description` (which require `--save`) tag the saved credential
|
|
585
|
+
the same way `zcap create --save` does:
|
|
586
|
+
|
|
587
|
+
```
|
|
588
|
+
./di vc issue credential.json --did did:key:z6Mk... --save --handle alumni
|
|
589
|
+
Credential saved to /home/user/.config/did-cli-wallet/credentials/sha256-1f4a....json
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
The exit code is scriptable: `0` when the credential was issued, `1` on an
|
|
593
|
+
issuance error (an unauthorized key, an unknown suite, a missing DID / key
|
|
594
|
+
file, or an issuer that does not match the signing DID), and `2` on a
|
|
595
|
+
read/parse error.
|
|
596
|
+
|
|
597
|
+
#### Import a credential
|
|
598
|
+
|
|
599
|
+
Store an existing Verifiable Credential in local wallet storage with
|
|
600
|
+
`vc import`. The credential is read from a file argument, an http(s) URL, or,
|
|
601
|
+
if neither is given, from stdin. The input must structurally look like a
|
|
602
|
+
credential (its `type` must include `VerifiableCredential`); it is stored
|
|
603
|
+
as-is and is *not* verified on import (run `vc verify` for that). `--handle` /
|
|
604
|
+
`--description` tag the saved credential:
|
|
605
|
+
|
|
606
|
+
```
|
|
607
|
+
./di vc import credential.json --handle alumni --description 'Alumni credential'
|
|
608
|
+
./di vc import https://example.com/credentials/123.json
|
|
609
|
+
cat credential.json | ./di vc import
|
|
610
|
+
Credential saved to /home/user/.config/did-cli-wallet/credentials/urn_uuid_9b1deb4d....json
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
The credential file is named after the credential's `id`; a credential
|
|
614
|
+
without an `id` (the property is optional) is stored under a digest of its
|
|
615
|
+
content, so re-importing it overwrites rather than duplicates. Re-importing a
|
|
616
|
+
credential preserves the metadata its sidecar already carries.
|
|
617
|
+
|
|
618
|
+
The exit code is scriptable: `0` when the credential was imported, `1` when
|
|
619
|
+
the input is not a Verifiable Credential, and `2` on a fetch/read/parse error.
|
|
620
|
+
|
|
621
|
+
#### List credentials
|
|
622
|
+
|
|
623
|
+
List the credentials saved in local wallet storage (via `vc import` or
|
|
624
|
+
`vc issue --save`) as a table of their metadata. The TYPE column shows the
|
|
625
|
+
credential's most specific type (its first `type` entry other than the
|
|
626
|
+
generic `VerifiableCredential`):
|
|
627
|
+
|
|
628
|
+
```
|
|
629
|
+
./di vc list
|
|
630
|
+
HANDLE TYPE ISSUER CREATED ID DESCRIPTION
|
|
631
|
+
------ ------------------- ------------------------------ ---------- --------------------------- -----------------
|
|
632
|
+
alumni OpenBadgeCredential did:key:z6MkExa...ampleIssuer 2026-06-11 urn:uuid:9b1deb4d-3b7d-4ba8 Alumni credential
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
If no credentials are stored, nothing is printed. Pass `--json` to output the
|
|
636
|
+
list as a JSON array of objects with metadata:
|
|
637
|
+
|
|
638
|
+
```
|
|
639
|
+
./di vc list --json
|
|
640
|
+
[
|
|
641
|
+
{
|
|
642
|
+
"id": "urn:uuid:9b1deb4d-3b7d-4ba8",
|
|
643
|
+
"type": "OpenBadgeCredential",
|
|
644
|
+
"issuer": "did:key:z6MkExampleIssuer",
|
|
645
|
+
"created": "2026-06-11T17:22:31.123Z",
|
|
646
|
+
"handle": "alumni",
|
|
647
|
+
"description": "Alumni credential"
|
|
648
|
+
}
|
|
649
|
+
]
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Or pass `--plain` to print just the credential ids, one per line, sorted. A
|
|
653
|
+
credential without an `id` is listed by its storage id (the `sha256-...` file
|
|
654
|
+
name), which `show` / `meta` / `remove` accept in place of a credential id.
|
|
655
|
+
|
|
656
|
+
#### Show a credential
|
|
657
|
+
|
|
658
|
+
Display a credential saved in local wallet storage, looked up by its
|
|
659
|
+
credential id (as printed by `vc list`), its storage id, or its metadata
|
|
660
|
+
handle:
|
|
661
|
+
|
|
662
|
+
```
|
|
663
|
+
./di vc show alumni
|
|
664
|
+
{
|
|
665
|
+
"@context": ["https://www.w3.org/ns/credentials/v2"],
|
|
666
|
+
"id": "urn:uuid:9b1deb4d-3b7d-4ba8",
|
|
667
|
+
"type": ["VerifiableCredential", "OpenBadgeCredential"],
|
|
668
|
+
...
|
|
669
|
+
}
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
Aliases: `view`, `cat`.
|
|
673
|
+
|
|
674
|
+
Pass `--meta` to show the credential's metadata instead, along with its
|
|
675
|
+
issuer, validity start, and expiration:
|
|
676
|
+
|
|
677
|
+
```
|
|
678
|
+
./di vc show alumni --meta
|
|
679
|
+
FIELD VALUE
|
|
680
|
+
----------- ----------------------------
|
|
681
|
+
ID urn:uuid:9b1deb4d-3b7d-4ba8
|
|
682
|
+
Type OpenBadgeCredential
|
|
683
|
+
Handle alumni
|
|
684
|
+
Created 2026-06-11T17:22:31.123Z
|
|
685
|
+
Description Alumni credential
|
|
686
|
+
Issuer did:key:z6MkExampleIssuer
|
|
687
|
+
Valid From 2026-01-01T00:00:00Z
|
|
688
|
+
Expires
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
`--meta --json` prints the same metadata as a JSON object.
|
|
692
|
+
|
|
693
|
+
#### Edit credential metadata
|
|
694
|
+
|
|
695
|
+
Show or edit the metadata of a stored credential with `vc meta` (looked up by
|
|
696
|
+
credential id, storage id, or handle). With no options it prints the current
|
|
697
|
+
metadata; with `--handle` / `--description` it updates the metadata sidecar
|
|
698
|
+
(the stored credential itself is never rewritten). Passing an empty string
|
|
699
|
+
clears a field:
|
|
700
|
+
|
|
701
|
+
```
|
|
702
|
+
./di vc meta urn:uuid:9b1deb4d-3b7d-4ba8 \
|
|
703
|
+
--handle alumni --description 'Alumni credential'
|
|
704
|
+
Metadata saved to /home/user/.config/did-cli-wallet/credentials/urn_uuid_9b1deb4d-3b7d-4ba8.meta.json
|
|
705
|
+
{
|
|
706
|
+
"created": "2026-06-11T17:22:31.123Z",
|
|
707
|
+
"handle": "alumni",
|
|
708
|
+
"description": "Alumni credential"
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
./di vc meta alumni --description ''
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
#### Remove a credential
|
|
715
|
+
|
|
716
|
+
Remove a stored credential with `vc remove` (aliases: `delete`, `rm`), looked
|
|
717
|
+
up by credential id, storage id, or handle. Both the credential file and its
|
|
718
|
+
`.meta.json` metadata sidecar are deleted:
|
|
719
|
+
|
|
720
|
+
```
|
|
721
|
+
./di vc remove alumni
|
|
722
|
+
Removed /home/user/.config/did-cli-wallet/credentials/urn_uuid_9b1deb4d-3b7d-4ba8.json
|
|
723
|
+
Removed /home/user/.config/did-cli-wallet/credentials/urn_uuid_9b1deb4d-3b7d-4ba8.meta.json
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### Authorization Capabilities (zCaps)
|
|
727
|
+
|
|
728
|
+
An Authorization Capability (zCap) grants its
|
|
729
|
+
controller permission to invoke an action against a resource (the
|
|
730
|
+
`invocationTarget`). Authority starts at an unsigned _root_ capability and is
|
|
731
|
+
handed down a chain of signed _delegated_ capabilities, each one optionally
|
|
732
|
+
narrowing the allowed actions or the target.
|
|
733
|
+
|
|
734
|
+
Both commands print the capability as JSON together with an `encoded` field --
|
|
735
|
+
the capability serialized and `base58btc`-encoded with a multibase `z` prefix --
|
|
736
|
+
which is the compact form you pass to `zcap delegate --capability` to delegate it
|
|
737
|
+
further. Pass `--save` to also write the capability to local wallet storage
|
|
738
|
+
(`~/.config/did-cli-wallet/zcaps/` by default, or `$WALLET_DIR` if set); `--save` records the
|
|
739
|
+
creation timestamp in a `.meta.json` metadata sidecar, and `--handle` /
|
|
740
|
+
`--description` (which require `--save`) tag the saved capability the same way
|
|
741
|
+
`key create --save` and `did create --save` do. The exit code is `0` on
|
|
742
|
+
success and `1` on a creation / delegation or input error.
|
|
743
|
+
|
|
744
|
+
#### Create a root capability
|
|
745
|
+
|
|
746
|
+
Build the root capability for an invocation target. The `--controller` is the DID
|
|
747
|
+
that holds root authority over the target, and `--url` is the `invocationTarget`.
|
|
748
|
+
Root capabilities are unsigned, so no key is needed:
|
|
749
|
+
|
|
750
|
+
```
|
|
751
|
+
./di zcap create \
|
|
752
|
+
--controller did:key:z6Mkfeco2NSEPeFV3DkjNSabaCza1EoS3CmqLb1eJ5BriiaR \
|
|
753
|
+
--url https://example.com/api
|
|
754
|
+
{
|
|
755
|
+
"rootCapability": {
|
|
756
|
+
"@context": "https://w3id.org/zcap/v1",
|
|
757
|
+
"id": "urn:zcap:root:https%3A%2F%2Fexample.com%2Fapi",
|
|
758
|
+
"controller": "did:key:z6Mkfeco2NSEPeFV3DkjNSabaCza1EoS3CmqLb1eJ5BriiaR",
|
|
759
|
+
"invocationTarget": "https://example.com/api"
|
|
760
|
+
},
|
|
761
|
+
"encoded": "z3g9TJBrQTdKemE9BC43N9WsT8snKvQzwCpCWs8o..."
|
|
762
|
+
}
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
The root capability's `id` is always `urn:zcap:root:<url-encoded invocationTarget>`,
|
|
766
|
+
and a root capability grants all actions (it has no `allowedAction`).
|
|
767
|
+
|
|
768
|
+
#### Note about the `encoded` field
|
|
769
|
+
|
|
770
|
+
The multibase- (that's the `z` prefix) and base58btc-encoded JSON of the zcap
|
|
771
|
+
is returned, for convenience, in the `encoded` field.
|
|
772
|
+
|
|
773
|
+
This is done for easier "double-click to copy" and pasting into other tools,
|
|
774
|
+
such as password managers, server env secrets, etc.
|
|
775
|
+
|
|
776
|
+
#### Delegate a capability
|
|
777
|
+
|
|
778
|
+
Delegate authority to another DID (`--delegatee`, which becomes the delegated
|
|
779
|
+
capability's controller). The delegation is signed with the delegator's
|
|
780
|
+
`capabilityDelegation` key, sourced one of two ways:
|
|
781
|
+
|
|
782
|
+
- **A locally-stored DID (`--did`)** -- the DID must have been saved with
|
|
783
|
+
`di did create --save`; this is the preferred mode and mirrors `vc issue`.
|
|
784
|
+
- **A secret key seed (`ZCAP_CONTROLLER_KEY_SEED` + `--controller`)** -- the
|
|
785
|
+
`did:key` is re-derived from the seed and checked against `--controller`.
|
|
786
|
+
|
|
787
|
+
To delegate from the root capability for a target, pass `--url` (the same
|
|
788
|
+
`invocationTarget` the root was created for) and the action(s) to allow with
|
|
789
|
+
`--allow` (repeatable; if omitted the delegatee inherits the parent's actions):
|
|
790
|
+
|
|
791
|
+
```
|
|
792
|
+
./di zcap delegate \
|
|
793
|
+
--did did:key:z6Mkfeco2NSEPeFV3DkjNSabaCza1EoS3CmqLb1eJ5BriiaR \
|
|
794
|
+
--delegatee did:key:z6MknBxrctS4KsfiBsEaXsfnrnfNYTvDjVpLYYUAN6PX2EfG \
|
|
795
|
+
--url https://example.com/documents \
|
|
796
|
+
--allow read
|
|
797
|
+
{
|
|
798
|
+
"delegatedCapability": {
|
|
799
|
+
"@context": [
|
|
800
|
+
"https://w3id.org/zcap/v1",
|
|
801
|
+
"https://w3id.org/security/suites/ed25519-2020/v1"
|
|
802
|
+
],
|
|
803
|
+
"id": "urn:uuid:e03d4f97-2e70-42e8-ae5d-51e92e903afa",
|
|
804
|
+
"controller": "did:key:z6MknBxrctS4KsfiBsEaXsfnrnfNYTvDjVpLYYUAN6PX2EfG",
|
|
805
|
+
"parentCapability": "urn:zcap:root:https%3A%2F%2Fexample.com%2Fdocuments",
|
|
806
|
+
"invocationTarget": "https://example.com/documents",
|
|
807
|
+
"expires": "2027-06-07T17:30:00Z",
|
|
808
|
+
"allowedAction": ["read"],
|
|
809
|
+
"proof": {
|
|
810
|
+
"type": "Ed25519Signature2020",
|
|
811
|
+
"created": "2026-06-07T17:30:00Z",
|
|
812
|
+
"verificationMethod": "did:key:z6Mkfeco...#z6Mkfeco...",
|
|
813
|
+
"proofPurpose": "capabilityDelegation",
|
|
814
|
+
"capabilityChain": ["urn:zcap:root:https%3A%2F%2Fexample.com%2Fdocuments"],
|
|
815
|
+
"proofValue": "z5tuwwdJE6VXLhf1v8SNAquBmMcJCD7zJ4bXDi6rh1Fk..."
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
"encoded": "zkL8vet8M2mn7akSpHEVvgFUCTVq4VSGs1s8Zsq9bYba..."
|
|
819
|
+
}
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
The same delegation, signed via a secret key seed instead of a stored DID:
|
|
823
|
+
|
|
824
|
+
```
|
|
825
|
+
ZCAP_CONTROLLER_KEY_SEED=z1AZK4h5w5YZkKYEgqtcFfvSbWQ3tZ3ZFgmLsXMZsTVoeK7 \
|
|
826
|
+
./di zcap delegate \
|
|
827
|
+
--controller did:key:z6Mkfeco2NSEPeFV3DkjNSabaCza1EoS3CmqLb1eJ5BriiaR \
|
|
828
|
+
--delegatee did:key:z6MknBxrctS4KsfiBsEaXsfnrnfNYTvDjVpLYYUAN6PX2EfG \
|
|
829
|
+
--url https://example.com/documents \
|
|
830
|
+
--allow read
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
To delegate an _existing_ capability further down the chain, pass it as
|
|
834
|
+
`--capability` instead of `--url` -- either the `encoded` string from a previous
|
|
835
|
+
delegation or a path to a JSON file containing the capability. Use
|
|
836
|
+
`--invocation-target` to attenuate (narrow) the parent's target to a sub-path:
|
|
837
|
+
|
|
838
|
+
```
|
|
839
|
+
./di zcap delegate \
|
|
840
|
+
--did did:key:z6MknBxr... \
|
|
841
|
+
--delegatee did:key:z6Mks... \
|
|
842
|
+
--capability zkL8vet8M2mn7akSpHEVvgFUCTVq4VSGs1s8Zsq9bYba... \
|
|
843
|
+
--invocation-target https://example.com/documents/reports \
|
|
844
|
+
--allow read
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
The delegated capability expires after `--ttl` (a duration such as `1y`, `30d`,
|
|
848
|
+
`24h`, `15m`; default `1y`). Pass `--expires` with an explicit ISO 8601 date to
|
|
849
|
+
override it:
|
|
850
|
+
|
|
851
|
+
```
|
|
852
|
+
./di zcap delegate --did did:key:z6Mk... --delegatee did:key:z6Mkn... \
|
|
853
|
+
--url https://example.com/documents --allow read --ttl 30d
|
|
854
|
+
|
|
855
|
+
./di zcap delegate --did did:key:z6Mk... --delegatee did:key:z6Mkn... \
|
|
856
|
+
--url https://example.com/documents --allow read --expires 2027-01-01T00:00:00Z
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
#### List capabilities
|
|
860
|
+
|
|
861
|
+
List the capabilities saved in local wallet storage (via `zcap create --save`
|
|
862
|
+
or `zcap delegate --save`) as a table of their metadata. The TYPE column shows
|
|
863
|
+
whether the capability is a `root` or a `delegated` one:
|
|
864
|
+
|
|
865
|
+
```
|
|
866
|
+
./di zcap list
|
|
867
|
+
HANDLE TYPE CREATED ID DESCRIPTION
|
|
868
|
+
-------- ---- ---------- -------------------------------------------- -------------
|
|
869
|
+
api-root root 2026-06-11 urn:zcap:root:https%3...%2Fexample.com%2Fapi Demo API root
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
If no capabilities are stored, nothing is printed. Pass `--json` to output the
|
|
873
|
+
list as a JSON array of objects with metadata:
|
|
874
|
+
|
|
875
|
+
```
|
|
876
|
+
./di zcap list --json
|
|
877
|
+
[
|
|
878
|
+
{
|
|
879
|
+
"id": "urn:zcap:root:https%3A%2F%2Fexample.com%2Fapi",
|
|
880
|
+
"type": "root",
|
|
881
|
+
"created": "2026-06-11T17:22:31.123Z",
|
|
882
|
+
"handle": "api-root",
|
|
883
|
+
"description": "Demo API root"
|
|
884
|
+
}
|
|
885
|
+
]
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
Or pass `--plain` to print just the capability ids, one per line, sorted:
|
|
889
|
+
|
|
890
|
+
```
|
|
891
|
+
./di zcap list --plain
|
|
892
|
+
urn:zcap:root:https%3A%2F%2Fexample.com%2Fa
|
|
893
|
+
urn:zcap:root:https%3A%2F%2Fexample.com%2Fb
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
#### Show a capability
|
|
897
|
+
|
|
898
|
+
Display a capability saved in local wallet storage, looked up by its
|
|
899
|
+
capability id (as printed by `zcap list`) or by its metadata handle:
|
|
900
|
+
|
|
901
|
+
```
|
|
902
|
+
./di zcap show api-root
|
|
903
|
+
{
|
|
904
|
+
"@context": "https://w3id.org/zcap/v1",
|
|
905
|
+
"id": "urn:zcap:root:https%3A%2F%2Fexample.com%2Fapi",
|
|
906
|
+
"controller": "did:key:z6Mkfeco2NSEPeFV3DkjNSabaCza1EoS3CmqLb1eJ5BriiaR",
|
|
907
|
+
"invocationTarget": "https://example.com/api"
|
|
908
|
+
}
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
Aliases: `view`, `cat`.
|
|
912
|
+
|
|
913
|
+
Pass `--meta` to show the capability's metadata instead, along with its
|
|
914
|
+
controller, invocation target, and (for delegated capabilities) expiration:
|
|
915
|
+
|
|
916
|
+
```
|
|
917
|
+
./di zcap show api-root --meta
|
|
918
|
+
FIELD VALUE
|
|
919
|
+
----------- --------------------------------------------------------
|
|
920
|
+
ID urn:zcap:root:https%3A%2F%2Fexample.com%2Fapi
|
|
921
|
+
Type root
|
|
922
|
+
Handle api-root
|
|
923
|
+
Created 2026-06-11T17:22:31.123Z
|
|
924
|
+
Description Demo API root
|
|
925
|
+
Controller did:key:z6Mkfeco2NSEPeFV3DkjNSabaCza1EoS3CmqLb1eJ5BriiaR
|
|
926
|
+
Target https://example.com/api
|
|
927
|
+
Expires
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
`--meta --json` prints the same metadata as a JSON object.
|
|
931
|
+
|
|
932
|
+
#### Edit capability metadata
|
|
933
|
+
|
|
934
|
+
Show or edit the metadata of a stored capability with `zcap meta` (looked up
|
|
935
|
+
by capability id or handle). With no options it prints the current metadata;
|
|
936
|
+
with `--handle` / `--description` it updates the metadata sidecar (the stored
|
|
937
|
+
capability itself is never rewritten). Passing an empty string clears a field:
|
|
938
|
+
|
|
939
|
+
```
|
|
940
|
+
./di zcap meta urn:zcap:root:https%3A%2F%2Fexample.com%2Fapi \
|
|
941
|
+
--handle api-root --description 'Demo API root'
|
|
942
|
+
Metadata saved to /home/user/.config/did-cli-wallet/zcaps/urn_zcap_root_https_3A_2F_2Fexample.com_2Fapi.meta.json
|
|
943
|
+
{
|
|
944
|
+
"created": "2026-06-11T17:22:31.123Z",
|
|
945
|
+
"handle": "api-root",
|
|
946
|
+
"description": "Demo API root"
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
./di zcap meta api-root --description ''
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
#### Remove a capability
|
|
953
|
+
|
|
954
|
+
Remove a stored capability with `zcap remove` (aliases: `delete`, `rm`),
|
|
955
|
+
looked up by capability id or handle. Both the capability file and its
|
|
956
|
+
`.meta.json` metadata sidecar are deleted:
|
|
957
|
+
|
|
958
|
+
```
|
|
959
|
+
./di zcap remove api-root
|
|
960
|
+
Removed /home/user/.config/did-cli-wallet/zcaps/urn_zcap_root_https_3A_2F_2Fexample.com_2Fapi.json
|
|
961
|
+
Removed /home/user/.config/did-cli-wallet/zcaps/urn_zcap_root_https_3A_2F_2Fexample.com_2Fapi.meta.json
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
Note that removing a capability from local storage does not revoke it -- a
|
|
965
|
+
delegated capability that has already been handed to its delegatee remains
|
|
966
|
+
valid until it expires (see `--ttl` / `--expires`).
|
|
967
|
+
|
|
968
|
+
### Wallet Attached Storage (WAS)
|
|
969
|
+
|
|
970
|
+
The `was` command group is a client for
|
|
971
|
+
[Wallet Attached Storage](https://digitalcredentials.github.io/wallet-attached-storage-spec/)
|
|
972
|
+
servers, which organize content as `Space > Collection > Resource` behind
|
|
973
|
+
zcap-authorized HTTP. Every request is signed with a `did:key` DID stored in
|
|
974
|
+
the local wallet (saved with `did create --save`; Ed25519 keys only for now).
|
|
975
|
+
|
|
976
|
+
Commands address content with a single positional **WAS path**:
|
|
977
|
+
|
|
978
|
+
```
|
|
979
|
+
SPACE[/COLLECTION[/RESOURCE]]
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
where `SPACE` is one of:
|
|
983
|
+
|
|
984
|
+
- a **registry handle** (e.g. `home`) of a space registered in the local
|
|
985
|
+
wallet (`~/.config/did-cli-wallet/was-spaces/`), which also supplies the server URL and
|
|
986
|
+
signing DID defaults;
|
|
987
|
+
- a **bare space id** (a server-generated uuid or urn), combined with
|
|
988
|
+
`--server` / `WAS_SERVER_URL`;
|
|
989
|
+
- a **full space URL** (e.g. `https://was.example/space/8124...cf2e`), which
|
|
990
|
+
is self-contained -- the server URL is its origin. Collection and resource
|
|
991
|
+
segments can be appended to any of the three forms.
|
|
992
|
+
|
|
993
|
+
The signing DID resolves from `--did` (a DID or stored-DID handle), the
|
|
994
|
+
`WAS_DID` environment variable, or the controller recorded in the registry
|
|
995
|
+
entry. Exit codes: `0` success, `1` operation error (a typed WAS error or a
|
|
996
|
+
not-found/not-visible read -- the spec returns 404 for both), `2` input error
|
|
997
|
+
(bad path, unknown handle/DID, missing server URL).
|
|
998
|
+
|
|
999
|
+
#### Create a space
|
|
1000
|
+
|
|
1001
|
+
Create a space on a WAS server (`--name`, a display name, is optional). Pass
|
|
1002
|
+
`--save` to register it in the local wallet, with the usual `--handle` /
|
|
1003
|
+
`--description` metadata; the handle is what makes every later command short:
|
|
1004
|
+
|
|
1005
|
+
```
|
|
1006
|
+
./di was space create --name 'Home space' \
|
|
1007
|
+
--server http://localhost:3002 --did did:key:z6Mkfeco... \
|
|
1008
|
+
--save --handle home
|
|
1009
|
+
Space registered in /home/user/.config/did-cli-wallet/was-spaces/81246131-69a4-45ab-9bff-9c946b59cf2e.json
|
|
1010
|
+
{
|
|
1011
|
+
"id": "81246131-69a4-45ab-9bff-9c946b59cf2e",
|
|
1012
|
+
"url": "http://localhost:3002/space/81246131-69a4-45ab-9bff-9c946b59cf2e",
|
|
1013
|
+
"name": "Home space",
|
|
1014
|
+
"controller": "did:key:z6Mkfeco..."
|
|
1015
|
+
}
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
Without `--save`, address the space later by its full URL (or register it
|
|
1019
|
+
afterwards with `was space add`).
|
|
1020
|
+
|
|
1021
|
+
#### List, show, and manage spaces
|
|
1022
|
+
|
|
1023
|
+
`was space list` lists the locally registered spaces (WAS servers do not
|
|
1024
|
+
implement server-side space listing yet; `--remote` asks anyway and surfaces
|
|
1025
|
+
the 501). `--json` and `--plain` work as in the other list commands:
|
|
1026
|
+
|
|
1027
|
+
```
|
|
1028
|
+
./di was space list
|
|
1029
|
+
HANDLE NAME SPACE ID SERVER CREATED
|
|
1030
|
+
------ ---------- ------------------------------------ --------------------- ----------
|
|
1031
|
+
home Home space 81246131-69a4-45ab-9bff-9c946b59cf2e http://localhost:3002 2026-06-11
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
`was space show` (aliases: `view`, `cat`) prints the Space Description from
|
|
1035
|
+
the server, or the local registry record with `--meta`:
|
|
1036
|
+
|
|
1037
|
+
```
|
|
1038
|
+
./di was space show home
|
|
1039
|
+
{
|
|
1040
|
+
"id": "81246131-69a4-45ab-9bff-9c946b59cf2e",
|
|
1041
|
+
"type": ["Space"],
|
|
1042
|
+
"name": "Home space",
|
|
1043
|
+
"controller": "did:key:z6Mkfeco..."
|
|
1044
|
+
}
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
`was space update` (alias: `configure`) upserts description fields
|
|
1048
|
+
(`--name`), also refreshing the registry entry. `was space add` registers an
|
|
1049
|
+
*existing* remote space (a full space URL, or a bare id plus `--server`) in
|
|
1050
|
+
the local registry, verifying it with a describe first. The local/remote
|
|
1051
|
+
delete pair:
|
|
1052
|
+
|
|
1053
|
+
- `was space delete <space>` (alias: `rm`) deletes the space **on the
|
|
1054
|
+
server** (idempotent) and removes the registry entry;
|
|
1055
|
+
- `was space forget <space>` removes only the local registry entry.
|
|
1056
|
+
|
|
1057
|
+
#### Manage collections
|
|
1058
|
+
|
|
1059
|
+
The `collection` group (alias: `coll`) manages collections within a space.
|
|
1060
|
+
`create` takes a space address plus an optional `--name` and `--id` (the id
|
|
1061
|
+
is server-generated otherwise); `show`/`update`/`delete` take a
|
|
1062
|
+
`SPACE/COLLECTION` path:
|
|
1063
|
+
|
|
1064
|
+
```
|
|
1065
|
+
./di was collection create home --name Credentials --id credentials
|
|
1066
|
+
{
|
|
1067
|
+
"id": "credentials",
|
|
1068
|
+
"url": "http://localhost:3002/space/8124...cf2e/credentials",
|
|
1069
|
+
"name": "Credentials"
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
./di was collection list home
|
|
1073
|
+
ID NAME URL
|
|
1074
|
+
----------- ----------- ---------------------------------------------------
|
|
1075
|
+
credentials Credentials http://localhost:3002/space/8124...cf2e/credentials
|
|
1076
|
+
|
|
1077
|
+
./di was collection delete home/credentials
|
|
1078
|
+
Deleted http://localhost:3002/space/8124...cf2e/credentials on the server.
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
#### Add and read resources
|
|
1082
|
+
|
|
1083
|
+
The `resource` group (alias: `res`) manages the content itself. Payloads come
|
|
1084
|
+
from a file argument or stdin: `*.json` files (and any input that parses to a
|
|
1085
|
+
JSON object or array) are sent as JSON, anything else as binary
|
|
1086
|
+
`application/octet-stream`, and an explicit `--content-type` sends the bytes
|
|
1087
|
+
as-is with that type (useful for e.g. `application/ld+json` or images).
|
|
1088
|
+
|
|
1089
|
+
`add` posts to a collection and lets the server pick the resource id; `put`
|
|
1090
|
+
creates or replaces at a known id:
|
|
1091
|
+
|
|
1092
|
+
```
|
|
1093
|
+
./di was resource add home/credentials vc.json
|
|
1094
|
+
{
|
|
1095
|
+
"id": "d3c9...",
|
|
1096
|
+
"url": "http://localhost:3002/space/8124...cf2e/credentials/d3c9...",
|
|
1097
|
+
"contentType": "application/json"
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
./di was resource put home/credentials/vc-1 vc.json
|
|
1101
|
+
cat vc.json | ./di was resource put home/credentials/vc-1
|
|
1102
|
+
./di was resource put home/photos/pic-1 photo.png --content-type image/png
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
`get` pretty-prints JSON to stdout and writes binary raw (use `--output` for
|
|
1106
|
+
files); a missing or not-visible resource prints
|
|
1107
|
+
`Not found (or not visible to you): <url>` and exits `1`:
|
|
1108
|
+
|
|
1109
|
+
```
|
|
1110
|
+
./di was resource get home/credentials/vc-1
|
|
1111
|
+
{
|
|
1112
|
+
"name": "Alice"
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
./di was resource get home/photos/pic-1 --output photo.png
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
`list` renders the resources of a collection (`ID | CONTENT TYPE | URL`), and
|
|
1119
|
+
`delete` (alias: `rm`) removes one (idempotent).
|
|
1120
|
+
|
|
1121
|
+
#### Shorthand verbs
|
|
1122
|
+
|
|
1123
|
+
For day-to-day use, the top-level verbs dispatch on the path depth:
|
|
1124
|
+
|
|
1125
|
+
```
|
|
1126
|
+
./di was ls home # collections of a space
|
|
1127
|
+
./di was ls home/credentials # resources of a collection
|
|
1128
|
+
./di was get home/credentials/vc-1 # = resource get
|
|
1129
|
+
./di was put home/credentials/vc-1 vc.json # = resource put
|
|
1130
|
+
./di was rm home/credentials/vc-1 # delete whatever the path points at
|
|
1131
|
+
./di was rm home # ... including a whole space
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
#### Delegate access (grant)
|
|
1135
|
+
|
|
1136
|
+
`was grant` delegates access to a space, collection, or resource. Actions are
|
|
1137
|
+
HTTP verbs (`GET`, `PUT`, `POST`, `DELETE`; lowercase accepted), expiration
|
|
1138
|
+
comes from `--ttl` (default `1y`) or an explicit `--expires`, and the output
|
|
1139
|
+
is the signed capability plus its `encoded` multibase form -- the same shape
|
|
1140
|
+
as `zcap delegate`. `--save` (with `--handle` / `--description`) stores it in
|
|
1141
|
+
the zcap store (`~/.config/did-cli-wallet/zcaps/`):
|
|
1142
|
+
|
|
1143
|
+
```
|
|
1144
|
+
./di was grant home/credentials --to did:key:z6MkBob... --action GET PUT
|
|
1145
|
+
{
|
|
1146
|
+
"delegatedCapability": {
|
|
1147
|
+
"@context": [...],
|
|
1148
|
+
"id": "urn:uuid:e03d4f97-...",
|
|
1149
|
+
"controller": "did:key:z6MkBob...",
|
|
1150
|
+
"invocationTarget": "http://localhost:3002/space/8124...cf2e/credentials",
|
|
1151
|
+
"allowedAction": ["GET", "PUT"],
|
|
1152
|
+
"expires": "2027-06-11T17:30:00Z",
|
|
1153
|
+
"proof": { ... }
|
|
1154
|
+
},
|
|
1155
|
+
"encoded": "zkL8vet8M2mn..."
|
|
1156
|
+
}
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
Hand the `encoded` string (or the JSON) to the delegatee out-of-band.
|
|
1160
|
+
|
|
1161
|
+
#### Use a received capability
|
|
1162
|
+
|
|
1163
|
+
On the receiving side, `ls` / `get` / `put` / `rm` (and `resource
|
|
1164
|
+
add/get/put`) accept `--capability` instead of a path. The reference is one
|
|
1165
|
+
of:
|
|
1166
|
+
|
|
1167
|
+
- the `encoded` multibase string from the grant output (`zkL8vet...`),
|
|
1168
|
+
- a path to a JSON file holding the capability, or
|
|
1169
|
+
- the capability id or metadata **handle of a zcap** stored in
|
|
1170
|
+
`~/.config/did-cli-wallet/zcaps/`.
|
|
1171
|
+
|
|
1172
|
+
Note that a `--capability` reference is *not* a WAS path -- no space,
|
|
1173
|
+
collection, or resource address is given (or needed). The capability itself
|
|
1174
|
+
records what it grants access to in its `invocationTarget`, and that is what
|
|
1175
|
+
the command operates on:
|
|
1176
|
+
|
|
1177
|
+
- a capability granted on a **resource** drives `get` / `put` / `rm`;
|
|
1178
|
+
- one granted on a **collection** drives `ls`, `resource add`, and `rm`;
|
|
1179
|
+
- one granted on a whole **space** drives `ls` and `rm`.
|
|
1180
|
+
|
|
1181
|
+
A depth mismatch (e.g. `get` with a collection-scoped capability) is an
|
|
1182
|
+
input error. The server URL is taken from the invocation target's origin,
|
|
1183
|
+
and the signing DID defaults to the capability's controller (the delegatee)
|
|
1184
|
+
when that DID is stored locally -- so usually no flags are needed at all.
|
|
1185
|
+
|
|
1186
|
+
In the examples below, `bob-share` is the metadata handle of a *stored zcap*
|
|
1187
|
+
(not a space or collection handle): say Alice granted Bob `GET`/`PUT` on the
|
|
1188
|
+
single resource `home/credentials/vc-1`, and the capability was saved with
|
|
1189
|
+
`--save --handle bob-share` (on Alice's machine via `was grant --save`; on
|
|
1190
|
+
Bob's machine he can pass the encoded string or a JSON file directly):
|
|
1191
|
+
|
|
1192
|
+
```
|
|
1193
|
+
# Alice delegates one resource to Bob, keeping a tagged copy:
|
|
1194
|
+
./di was grant home/credentials/vc-1 --to did:key:z6MkBob... \
|
|
1195
|
+
--action GET PUT --save --handle bob-share
|
|
1196
|
+
|
|
1197
|
+
# Bob, with the encoded string he received out-of-band -- this reads the
|
|
1198
|
+
# resource the capability targets (home/credentials/vc-1):
|
|
1199
|
+
./di was get --capability zkL8vet8M2mn...
|
|
1200
|
+
{
|
|
1201
|
+
"name": "Alice"
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
# The same via a capability JSON file, or a stored zcap's handle:
|
|
1205
|
+
./di was get --capability ./bob-share.json
|
|
1206
|
+
./di was get --capability bob-share
|
|
1207
|
+
|
|
1208
|
+
# A resource-scoped capability also allows writing (it grants PUT):
|
|
1209
|
+
./di was put data.json --capability bob-share
|
|
1210
|
+
|
|
1211
|
+
# Had the grant been on the whole collection (home/credentials), ls would
|
|
1212
|
+
# list it and `resource add` could post new resources into it:
|
|
1213
|
+
./di was ls --capability zkL8vet8M2mn...
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1216
|
+
#### Policies and public sharing
|
|
1217
|
+
|
|
1218
|
+
By default all operations on a space requires a capability invocation. A
|
|
1219
|
+
**policy** can override this per space, collection, or resource; the common
|
|
1220
|
+
case is `PublicCanRead` -- the "share via public link" model. `was publish`
|
|
1221
|
+
is the sugar for it and prints the public URL; `was unpublish` reverts to
|
|
1222
|
+
capability-only access:
|
|
1223
|
+
|
|
1224
|
+
```
|
|
1225
|
+
./di was publish home/credentials/vc-1
|
|
1226
|
+
Published (world-readable): http://localhost:3002/space/8124...cf2e/credentials/vc-1
|
|
1227
|
+
http://localhost:3002/space/8124...cf2e/credentials/vc-1
|
|
1228
|
+
|
|
1229
|
+
curl http://localhost:3002/space/8124...cf2e/credentials/vc-1 # no auth needed
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
The generic primitives work at any depth: `was policy show <path>` prints the
|
|
1233
|
+
policy document (or `No policy set (or not visible to you)` with exit `1`),
|
|
1234
|
+
`was policy set <path> --type PublicCanRead` (or a policy JSON file for
|
|
1235
|
+
richer, server-defined policies), and `was policy clear <path>`.
|
|
1236
|
+
|
|
1237
|
+
#### Export and import a space
|
|
1238
|
+
|
|
1239
|
+
`was space export` downloads a whole space as a tar archive (to `--output`
|
|
1240
|
+
or raw to stdout); `was space import` merges a tar into a space (from a file
|
|
1241
|
+
or stdin) and prints the import stats:
|
|
1242
|
+
|
|
1243
|
+
```
|
|
1244
|
+
./di was space export home --output home.tar
|
|
1245
|
+
Wrote 10240 bytes to home.tar
|
|
1246
|
+
|
|
1247
|
+
./di was space import other-space home.tar
|
|
1248
|
+
{
|
|
1249
|
+
"collectionsCreated": 1,
|
|
1250
|
+
"collectionsSkipped": 0,
|
|
1251
|
+
"resourcesCreated": 2,
|
|
1252
|
+
"resourcesSkipped": 0,
|
|
1253
|
+
"policiesCreated": 0,
|
|
1254
|
+
"policiesSkipped": 0
|
|
1255
|
+
}
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
#### End-to-end smoke test
|
|
1259
|
+
|
|
1260
|
+
To exercise the whole flow against a local server, start the reference
|
|
1261
|
+
[was-teaching-server](https://github.com/digitalcredentials/was-teaching-server)
|
|
1262
|
+
(the server URL must match byte-for-byte, including the port, since zcap
|
|
1263
|
+
invocation targets embed it):
|
|
1264
|
+
|
|
1265
|
+
```
|
|
1266
|
+
SERVER_URL='http://localhost:3002' PORT=3002 pnpm dev
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
Then, in another shell:
|
|
1270
|
+
|
|
1271
|
+
```
|
|
1272
|
+
export WAS_SERVER_URL=http://localhost:3002
|
|
1273
|
+
|
|
1274
|
+
./di did create --save --handle alice
|
|
1275
|
+
./di was space create --name Demo --did alice --save --handle demo
|
|
1276
|
+
./di was collection create demo --name Docs --id docs
|
|
1277
|
+
echo '{"hello": "world"}' | ./di was put demo/docs/doc-1
|
|
1278
|
+
./di was get demo/docs/doc-1
|
|
1279
|
+
|
|
1280
|
+
./di did create --save --handle bob # the delegatee
|
|
1281
|
+
./di was grant demo/docs/doc-1 --to <bob's did> --action GET --did alice \
|
|
1282
|
+
--save --handle bob-share
|
|
1283
|
+
./di was get --capability bob-share --did bob
|
|
1284
|
+
|
|
1285
|
+
./di was publish demo/docs/doc-1 # prints the public URL
|
|
1286
|
+
curl <public url> # readable without auth
|
|
1287
|
+
|
|
1288
|
+
./di was rm demo # clean up (deletes the space)
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
The same flow runs as an env-gated integration test: `npm test` skips it
|
|
1292
|
+
unless `WAS_TEST_SERVER_URL` points at a running server:
|
|
1293
|
+
|
|
1294
|
+
```
|
|
1295
|
+
WAS_TEST_SERVER_URL=http://localhost:3002 npm run test:node
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
## Contribute
|
|
1299
|
+
|
|
1300
|
+
PRs accepted.
|
|
1301
|
+
|
|
1302
|
+
If editing the Readme, please conform to the
|
|
1303
|
+
[standard-readme](https://github.com/RichardLitt/standard-readme) specification.
|
|
1304
|
+
|
|
1305
|
+
## License
|
|
1306
|
+
|
|
1307
|
+
[MIT](LICENSE.md) © 2026 Interop Alliance
|