@interop/did-method-webvh 3.0.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 +345 -0
- package/LICENSE +201 -0
- package/README.md +294 -0
- package/dist/assertions.d.ts +5 -0
- package/dist/assertions.js +82 -0
- package/dist/cli.d.ts +21 -0
- package/dist/cli.js +533 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +35 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +3 -0
- package/dist/cryptography.d.ts +52 -0
- package/dist/cryptography.js +95 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/interfaces.d.ts +231 -0
- package/dist/interfaces.js +17 -0
- package/dist/method.d.ts +73 -0
- package/dist/method.js +95 -0
- package/dist/method_versions/method.v1.0.d.ts +23 -0
- package/dist/method_versions/method.v1.0.js +554 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.js +5 -0
- package/dist/utils/buffer.d.ts +3 -0
- package/dist/utils/buffer.js +62 -0
- package/dist/utils/canonicalize.d.ts +3 -0
- package/dist/utils/canonicalize.js +80 -0
- package/dist/utils/crypto.d.ts +2 -0
- package/dist/utils/crypto.js +17 -0
- package/dist/utils/multiformats.d.ts +100 -0
- package/dist/utils/multiformats.js +283 -0
- package/dist/utils.d.ts +49 -0
- package/dist/utils.js +659 -0
- package/dist/witness.d.ts +39 -0
- package/dist/witness.js +216 -0
- package/package.json +70 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { parseEnv } from 'node:util';
|
|
6
|
+
import { sign as ed25519Sign, verify as ed25519Verify, generateKeyPair } from '@stablelib/ed25519';
|
|
7
|
+
import { createDID, deactivateDID, resolveDIDFromLog, updateDID } from './method.js';
|
|
8
|
+
import { bufferToString, concatBuffers, createBuffer } from './utils/buffer.js';
|
|
9
|
+
import { canonicalizeStrict } from './utils/canonicalize.js';
|
|
10
|
+
import { createHash } from './utils/crypto.js';
|
|
11
|
+
import { MultibaseEncoding, multibaseDecode, multibaseEncode } from './utils/multiformats.js';
|
|
12
|
+
import { fetchLogFromIdentifier, parseDidKeyDid, readLogFromDisk, writeLogToDisk, writeVerificationMethodToEnv, } from './utils.js';
|
|
13
|
+
import { signWitnessProofEntries } from './witness.js';
|
|
14
|
+
const usage = `
|
|
15
|
+
Usage: npm run cli -- [command] [options]
|
|
16
|
+
|
|
17
|
+
Commands:
|
|
18
|
+
create Create a new DID
|
|
19
|
+
resolve Resolve a DID
|
|
20
|
+
update Update an existing DID
|
|
21
|
+
deactivate Deactivate an existing DID
|
|
22
|
+
generate-witness-proof Generate witness proofs for a DID version
|
|
23
|
+
generate-vm Generate a new verification method keypair
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
--address [address] Address for the DID (host, host:port, http://localhost, https://url, or did:webvh form) (required for create)
|
|
27
|
+
--domain [domain] DEPRECATED: Use --address instead. Domain for the DID (backwards compatibility).
|
|
28
|
+
--log [file] Path to the DID log file (required for resolve, update, deactivate)
|
|
29
|
+
--output [file] Path to save the updated DID log (optional for create, update, deactivate)
|
|
30
|
+
--portable Make the DID portable (optional for create)
|
|
31
|
+
--witness [witness] Add a witness (can be used multiple times)
|
|
32
|
+
--witness-threshold [n] Set witness threshold (optional, defaults to number of witnesses)
|
|
33
|
+
--watcher [url] Add a watcher URL (can be used multiple times)
|
|
34
|
+
--service [service] Add a service (format: type,endpoint) (can be used multiple times)
|
|
35
|
+
--add-vm [type] Add a verification method (type can be authentication, assertionMethod, keyAgreement, capabilityInvocation, capabilityDelegation)
|
|
36
|
+
--also-known-as [alias] Add an alsoKnownAs alias (can be used multiple times)
|
|
37
|
+
--next-key-hash [hash] Add a nextKeyHash (can be used multiple times)
|
|
38
|
+
--witness-file [file] Path to witness proofs file (optional for resolve)
|
|
39
|
+
|
|
40
|
+
# Options for generate-witness-proof:
|
|
41
|
+
--version-id [id] The version ID to generate proofs for (required, can be used multiple times)
|
|
42
|
+
--witness-did [did] Witness DID (did:key) (can be used multiple times)
|
|
43
|
+
--witness-secret [secret] Witness secret key multibase (matches witness-did order)
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
npm run cli -- create --address example.com --portable --witness did:key:z6Mk... --witness did:key:z6Mk...
|
|
47
|
+
npm run cli -- create --address https://example.com --portable
|
|
48
|
+
npm run cli -- create --address "example.com:3000" --portable
|
|
49
|
+
npm run cli -- create --address "did:webvh:example.com:3000" --portable
|
|
50
|
+
npm run cli -- create --domain example.com --portable # DEPRECATED: use --address
|
|
51
|
+
npm run cli -- resolve --did did:webvh:123456:example.com
|
|
52
|
+
npm run cli -- resolve --log ./did.jsonl --witness-file ./did-witness.json
|
|
53
|
+
npm run cli -- update --log ./did.jsonl --output ./updated-did.jsonl --add-vm keyAgreement --service LinkedDomains,https://example.com
|
|
54
|
+
npm run cli -- deactivate --log ./did.jsonl --output ./deactivated-did.jsonl
|
|
55
|
+
npm run cli -- generate-witness-proof --version-id 1-abc123 --witness-did did:key:z6Mk... --witness-secret z1A... --output did-witness.json
|
|
56
|
+
npm run cli -- generate-witness-proof --version-id 1-abc123 --version-id 2-def456 --witness-did did:key:z6Mk... --witness-secret z1A... --output did-witness.json
|
|
57
|
+
npm run cli -- generate-vm
|
|
58
|
+
`;
|
|
59
|
+
// Add this function at the top with the other constants
|
|
60
|
+
function showHelp() {
|
|
61
|
+
console.log(usage);
|
|
62
|
+
}
|
|
63
|
+
async function generateVerificationMethod(purpose = 'authentication') {
|
|
64
|
+
const keyPair = generateKeyPair();
|
|
65
|
+
const publicKeyBytes = new Uint8Array([0xed, 0x01, ...keyPair.publicKey]);
|
|
66
|
+
const secretKeyBytes = new Uint8Array([0x80, 0x26, ...keyPair.secretKey]);
|
|
67
|
+
return {
|
|
68
|
+
type: 'Multikey',
|
|
69
|
+
publicKeyMultibase: multibaseEncode(publicKeyBytes, MultibaseEncoding.BASE58_BTC),
|
|
70
|
+
secretKeyMultibase: multibaseEncode(secretKeyBytes, MultibaseEncoding.BASE58_BTC),
|
|
71
|
+
purpose,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
class CustomCryptoImplementation {
|
|
75
|
+
verificationMethod;
|
|
76
|
+
constructor(verificationMethod) {
|
|
77
|
+
this.verificationMethod = verificationMethod;
|
|
78
|
+
}
|
|
79
|
+
getVerificationMethodId() {
|
|
80
|
+
if (!this.verificationMethod) {
|
|
81
|
+
throw new Error('Verification method not set');
|
|
82
|
+
}
|
|
83
|
+
return `did:key:${this.verificationMethod.publicKeyMultibase}#${this.verificationMethod.publicKeyMultibase}`;
|
|
84
|
+
}
|
|
85
|
+
async sign(input) {
|
|
86
|
+
if (!this.verificationMethod) {
|
|
87
|
+
throw new Error('Verification method not set');
|
|
88
|
+
}
|
|
89
|
+
if (!this.verificationMethod.secretKeyMultibase) {
|
|
90
|
+
throw new Error('Secret key not set on verification method');
|
|
91
|
+
}
|
|
92
|
+
const { document, proof } = input;
|
|
93
|
+
const dataHash = await createHash(canonicalizeStrict(document));
|
|
94
|
+
const proofHash = await createHash(canonicalizeStrict(proof));
|
|
95
|
+
const message = concatBuffers(proofHash, dataHash);
|
|
96
|
+
const secretKey = multibaseDecode(this.verificationMethod.secretKeyMultibase).bytes.slice(2);
|
|
97
|
+
const signature = ed25519Sign(secretKey, message);
|
|
98
|
+
return {
|
|
99
|
+
proofValue: multibaseEncode(signature, MultibaseEncoding.BASE58_BTC),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async verify(signature, message, publicKey) {
|
|
103
|
+
return ed25519Verify(publicKey, message, signature);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function createCustomCrypto(verificationMethod) {
|
|
107
|
+
return new CustomCryptoImplementation(verificationMethod);
|
|
108
|
+
}
|
|
109
|
+
export async function handleCreate(args) {
|
|
110
|
+
const options = parseOptions(args);
|
|
111
|
+
// Support both --address (new) and --domain (deprecated) options
|
|
112
|
+
const addressInput = (options.address || options.domain);
|
|
113
|
+
// Extract optional explicit paths (colon-delimited) from CLI args
|
|
114
|
+
// If provided, these override any paths parsed from address input
|
|
115
|
+
const explicitPaths = options.paths;
|
|
116
|
+
const output = options.output;
|
|
117
|
+
const portable = options.portable !== undefined;
|
|
118
|
+
const nextKeyHashes = options['next-key-hash'];
|
|
119
|
+
const witnesses = options.witness;
|
|
120
|
+
const watchers = options.watcher;
|
|
121
|
+
const witnessThreshold = options['witness-threshold']
|
|
122
|
+
? parseInt(options['witness-threshold'], 10)
|
|
123
|
+
: (witnesses?.length ?? 0);
|
|
124
|
+
if (!addressInput) {
|
|
125
|
+
console.error('Address is required for create command (use --address or deprecated --domain)');
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const authKey = await generateVerificationMethod();
|
|
130
|
+
if (!authKey.publicKeyMultibase) {
|
|
131
|
+
throw new Error('Generated verification method is missing publicKeyMultibase');
|
|
132
|
+
}
|
|
133
|
+
const crypto = createCustomCrypto(authKey);
|
|
134
|
+
// Strip secret key from verification method for DID document (security)
|
|
135
|
+
const publicAuthKey = {
|
|
136
|
+
id: authKey.id,
|
|
137
|
+
type: authKey.type,
|
|
138
|
+
controller: authKey.controller,
|
|
139
|
+
publicKeyMultibase: authKey.publicKeyMultibase,
|
|
140
|
+
purpose: authKey.purpose,
|
|
141
|
+
};
|
|
142
|
+
// Use new address parameter for strict parsing and encoding
|
|
143
|
+
const { did, doc, meta, log } = await createDID({
|
|
144
|
+
address: addressInput,
|
|
145
|
+
paths: explicitPaths,
|
|
146
|
+
signer: crypto,
|
|
147
|
+
verifier: crypto,
|
|
148
|
+
updateKeys: [authKey.publicKeyMultibase],
|
|
149
|
+
verificationMethods: [publicAuthKey],
|
|
150
|
+
portable,
|
|
151
|
+
witness: witnesses?.length
|
|
152
|
+
? {
|
|
153
|
+
witnesses: witnesses.map((witness) => ({ id: witness })),
|
|
154
|
+
threshold: witnessThreshold,
|
|
155
|
+
}
|
|
156
|
+
: undefined,
|
|
157
|
+
watchers: watchers ?? undefined,
|
|
158
|
+
nextKeyHashes,
|
|
159
|
+
});
|
|
160
|
+
console.log('Created DID:', did);
|
|
161
|
+
if (output) {
|
|
162
|
+
// Ensure output directory exists
|
|
163
|
+
const outputDir = dirname(output);
|
|
164
|
+
if (!fs.existsSync(outputDir)) {
|
|
165
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
// Write log to file
|
|
168
|
+
await writeLogToDisk(output, log);
|
|
169
|
+
console.log(`DID log written to ${output}`);
|
|
170
|
+
// Save verification method to env
|
|
171
|
+
await writeVerificationMethodToEnv({
|
|
172
|
+
...authKey,
|
|
173
|
+
controller: did,
|
|
174
|
+
id: `${did}#${authKey.publicKeyMultibase?.slice(-8)}`,
|
|
175
|
+
});
|
|
176
|
+
console.log(`DID verification method saved to env`);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// If no output specified, print to console
|
|
180
|
+
console.log('DID Document:', JSON.stringify(doc, null, 2));
|
|
181
|
+
console.log('DID Log:', JSON.stringify(log, null, 2));
|
|
182
|
+
}
|
|
183
|
+
return { did, doc, meta, log };
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error('Error creating DID:', error);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export async function handleResolve(args) {
|
|
191
|
+
const options = parseOptions(args);
|
|
192
|
+
const didIdentifier = options.did;
|
|
193
|
+
const logFile = options.log;
|
|
194
|
+
const witnessFile = options['witness-file'];
|
|
195
|
+
if (!didIdentifier && !logFile) {
|
|
196
|
+
console.error('Either --did or --log is required for resolve command');
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
let log;
|
|
201
|
+
if (logFile) {
|
|
202
|
+
log = await readLogFromDisk(logFile);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
log = await fetchLogFromIdentifier(didIdentifier);
|
|
206
|
+
}
|
|
207
|
+
const resolutionOptions = {};
|
|
208
|
+
if (witnessFile) {
|
|
209
|
+
const witnessProofs = JSON.parse(fs.readFileSync(witnessFile, 'utf8'));
|
|
210
|
+
resolutionOptions.witnessProofs = witnessProofs;
|
|
211
|
+
}
|
|
212
|
+
const crypto = createCustomCrypto();
|
|
213
|
+
resolutionOptions.verifier = crypto;
|
|
214
|
+
console.time('Resolution time');
|
|
215
|
+
const { did, doc, meta } = await resolveDIDFromLog(log, resolutionOptions);
|
|
216
|
+
console.timeEnd('Resolution time');
|
|
217
|
+
console.log('Resolved DID:', did);
|
|
218
|
+
console.log('DID Document:', JSON.stringify(doc, null, 2));
|
|
219
|
+
console.log('Metadata:', JSON.stringify(meta, null, 2));
|
|
220
|
+
return { did, doc, meta };
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
console.error('Error resolving DID:', error);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
export async function handleUpdate(args) {
|
|
228
|
+
const options = parseOptions(args);
|
|
229
|
+
const logFile = options.log;
|
|
230
|
+
const output = options.output;
|
|
231
|
+
const witnesses = options.witness;
|
|
232
|
+
const witnessThreshold = options['witness-threshold']
|
|
233
|
+
? parseInt(options['witness-threshold'], 10)
|
|
234
|
+
: undefined;
|
|
235
|
+
const services = options.service ? parseServices(options.service) : undefined;
|
|
236
|
+
const addVm = options['add-vm'];
|
|
237
|
+
const alsoKnownAs = options['also-known-as'];
|
|
238
|
+
const updateKey = options['update-key'];
|
|
239
|
+
const watchers = options.watcher;
|
|
240
|
+
if (!logFile) {
|
|
241
|
+
console.error('Log file is required for update command');
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const log = await readLogFromDisk(logFile);
|
|
246
|
+
const { did, meta } = await resolveDIDFromLog(log, { verifier: createCustomCrypto() });
|
|
247
|
+
// console.log('\nCurrent DID:', did);
|
|
248
|
+
// console.log('Current meta:', meta);
|
|
249
|
+
// Get the verification method from environment
|
|
250
|
+
const envVMs = JSON.parse(bufferToString(createBuffer(process.env.DID_VERIFICATION_METHODS || 'W10=', 'base64')));
|
|
251
|
+
let vm = envVMs.find((vm) => vm.controller === did);
|
|
252
|
+
if (!vm) {
|
|
253
|
+
// Try to find VM by matching public key with current update keys
|
|
254
|
+
vm = envVMs.find((vm) => meta.updateKeys.includes(vm.publicKeyMultibase));
|
|
255
|
+
}
|
|
256
|
+
if (!vm && envVMs.length > 0) {
|
|
257
|
+
// Fall back to first available VM with warning
|
|
258
|
+
console.warn('Warning: No matching verification method found for DID or update keys. Using first available VM.');
|
|
259
|
+
vm = envVMs[0];
|
|
260
|
+
}
|
|
261
|
+
// console.log('\nFound VM:', vm);
|
|
262
|
+
if (!vm) {
|
|
263
|
+
throw new Error('No verification method found in environment');
|
|
264
|
+
}
|
|
265
|
+
if (!vm.publicKeyMultibase) {
|
|
266
|
+
throw new Error('Verification method missing publicKeyMultibase');
|
|
267
|
+
}
|
|
268
|
+
// Create verification methods array
|
|
269
|
+
const verificationMethods = [];
|
|
270
|
+
// If we're adding VMs, create a VM for each type
|
|
271
|
+
if (addVm && addVm.length > 0) {
|
|
272
|
+
const vmId = `${did}#${vm.publicKeyMultibase?.slice(-8)}`;
|
|
273
|
+
// Add a verification method for each type
|
|
274
|
+
for (const vmType of addVm) {
|
|
275
|
+
const newVM = {
|
|
276
|
+
id: vmId,
|
|
277
|
+
type: 'Multikey',
|
|
278
|
+
controller: did,
|
|
279
|
+
publicKeyMultibase: vm.publicKeyMultibase,
|
|
280
|
+
purpose: vmType,
|
|
281
|
+
};
|
|
282
|
+
verificationMethods.push(newVM);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// For non-VM updates (services, alsoKnownAs), still need a VM with purpose
|
|
287
|
+
verificationMethods.push({
|
|
288
|
+
id: `${did}#${vm.publicKeyMultibase?.slice(-8)}`,
|
|
289
|
+
type: 'Multikey',
|
|
290
|
+
controller: did,
|
|
291
|
+
publicKeyMultibase: vm.publicKeyMultibase,
|
|
292
|
+
purpose: 'assertionMethod',
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const crypto = createCustomCrypto(vm);
|
|
296
|
+
const result = await updateDID({
|
|
297
|
+
log,
|
|
298
|
+
signer: crypto,
|
|
299
|
+
verifier: crypto,
|
|
300
|
+
updateKeys: [vm.publicKeyMultibase],
|
|
301
|
+
verificationMethods,
|
|
302
|
+
witness: witnesses?.length
|
|
303
|
+
? {
|
|
304
|
+
witnesses: witnesses.map((witness) => ({ id: witness })),
|
|
305
|
+
threshold: witnessThreshold ?? witnesses.length,
|
|
306
|
+
}
|
|
307
|
+
: undefined,
|
|
308
|
+
watchers: watchers ?? undefined,
|
|
309
|
+
services,
|
|
310
|
+
alsoKnownAs,
|
|
311
|
+
});
|
|
312
|
+
if (output) {
|
|
313
|
+
await writeLogToDisk(output, result.log);
|
|
314
|
+
console.log(`Updated DID log written to ${output}`);
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
console.error('Error updating DID:', error);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
export async function handleDeactivate(args) {
|
|
324
|
+
const options = parseOptions(args);
|
|
325
|
+
const logFile = options.log;
|
|
326
|
+
const output = options.output;
|
|
327
|
+
if (!logFile) {
|
|
328
|
+
console.error('Log file is required for deactivate command');
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
// Read the current log to get the latest state
|
|
333
|
+
const log = await readLogFromDisk(logFile);
|
|
334
|
+
const { did, meta } = await resolveDIDFromLog(log, { verifier: createCustomCrypto() });
|
|
335
|
+
// Get the verification method from environment
|
|
336
|
+
const envContent = fs.readFileSync('.env', 'utf8');
|
|
337
|
+
const vmMatch = envContent.match(/DID_VERIFICATION_METHODS=(.+)/);
|
|
338
|
+
if (!vmMatch) {
|
|
339
|
+
throw new Error('No verification method found in .env file');
|
|
340
|
+
}
|
|
341
|
+
// Parse the VM from env
|
|
342
|
+
const vms = JSON.parse(bufferToString(createBuffer(vmMatch[1], 'base64')));
|
|
343
|
+
if (!vms || vms.length === 0) {
|
|
344
|
+
throw new Error('No verification method found in environment');
|
|
345
|
+
}
|
|
346
|
+
// Find VM that matches the current update key
|
|
347
|
+
let vm = vms.find((v) => v.publicKeyMultibase === meta.updateKeys[0]);
|
|
348
|
+
if (!vm) {
|
|
349
|
+
// If no matching VM found, use the first one and warn
|
|
350
|
+
console.warn('Warning: No matching verification method found for current update key. Using first available VM.');
|
|
351
|
+
vm = vms[0];
|
|
352
|
+
}
|
|
353
|
+
// Don't modify the publicKeyMultibase - it should match the secretKeyMultibase
|
|
354
|
+
const crypto = createCustomCrypto(vm);
|
|
355
|
+
const result = await deactivateDID({
|
|
356
|
+
log,
|
|
357
|
+
signer: crypto,
|
|
358
|
+
verifier: crypto,
|
|
359
|
+
});
|
|
360
|
+
if (output) {
|
|
361
|
+
await writeLogToDisk(output, result.log);
|
|
362
|
+
console.log(`Deactivated DID log written to ${output}`);
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
console.error('Error deactivating DID:', error);
|
|
368
|
+
process.exit(1);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
async function handleGenerateWitnessProof(args) {
|
|
372
|
+
const options = parseOptions(args);
|
|
373
|
+
const rawVersionIds = options['version-id'];
|
|
374
|
+
const versionIds = Array.isArray(rawVersionIds) ? rawVersionIds : rawVersionIds ? [rawVersionIds] : [];
|
|
375
|
+
const witnessDids = options['witness-did'];
|
|
376
|
+
const witnessSecrets = options['witness-secret'];
|
|
377
|
+
const output = options.output;
|
|
378
|
+
if (versionIds.length === 0) {
|
|
379
|
+
console.error('At least one --version-id is required');
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
if (!output) {
|
|
383
|
+
console.error('Output file is required');
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
if (!witnessDids || !witnessSecrets || witnessDids.length !== witnessSecrets.length) {
|
|
387
|
+
console.error('Must provide matching number of witness DIDs and secrets');
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
const witnessSignersByDid = {};
|
|
391
|
+
const witnesses = [];
|
|
392
|
+
for (let i = 0; i < witnessDids.length; i++) {
|
|
393
|
+
const did = witnessDids[i];
|
|
394
|
+
const secret = witnessSecrets[i];
|
|
395
|
+
const { did: normalizedDid, keyMultibase: publicKeyMultibase } = parseDidKeyDid(did);
|
|
396
|
+
const vm = {
|
|
397
|
+
type: 'Multikey',
|
|
398
|
+
publicKeyMultibase,
|
|
399
|
+
secretKeyMultibase: secret,
|
|
400
|
+
purpose: 'authentication',
|
|
401
|
+
};
|
|
402
|
+
witnessSignersByDid[normalizedDid] = createCustomCrypto(vm);
|
|
403
|
+
witnesses.push({ id: normalizedDid });
|
|
404
|
+
}
|
|
405
|
+
const witnessEntries = await signWitnessProofEntries(versionIds, witnesses, witnessSignersByDid);
|
|
406
|
+
const witnessFileContent = witnessEntries.map((entry) => ({
|
|
407
|
+
versionId: entry.versionId,
|
|
408
|
+
proof: entry.proof,
|
|
409
|
+
}));
|
|
410
|
+
fs.writeFileSync(output, JSON.stringify(witnessFileContent, null, 2));
|
|
411
|
+
console.log(`Witness proof file generated at ${output}`);
|
|
412
|
+
}
|
|
413
|
+
function parseOptions(args) {
|
|
414
|
+
const options = {};
|
|
415
|
+
for (let i = 0; i < args.length; i++) {
|
|
416
|
+
if (args[i].startsWith('--')) {
|
|
417
|
+
const key = args[i].slice(2);
|
|
418
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
419
|
+
if (key === 'witness' ||
|
|
420
|
+
key === 'service' ||
|
|
421
|
+
key === 'also-known-as' ||
|
|
422
|
+
key === 'next-key-hash' ||
|
|
423
|
+
key === 'watcher' ||
|
|
424
|
+
key === 'witness-did' ||
|
|
425
|
+
key === 'witness-secret' ||
|
|
426
|
+
key === 'version-id') {
|
|
427
|
+
options[key] = options[key] || [];
|
|
428
|
+
options[key].push(args[++i]);
|
|
429
|
+
}
|
|
430
|
+
else if (key === 'add-vm') {
|
|
431
|
+
options[key] = options[key] || [];
|
|
432
|
+
const value = args[++i];
|
|
433
|
+
if (isValidVerificationMethodType(value)) {
|
|
434
|
+
options[key].push(value);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
console.error(`Invalid verification method type: ${value}`);
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
options[key] = args[++i];
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
options[key] = '';
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return options;
|
|
451
|
+
}
|
|
452
|
+
// Add this function to validate VerificationMethodType
|
|
453
|
+
function isValidVerificationMethodType(type) {
|
|
454
|
+
return ['authentication', 'assertionMethod', 'keyAgreement', 'capabilityInvocation', 'capabilityDelegation'].includes(type);
|
|
455
|
+
}
|
|
456
|
+
function parseServices(services) {
|
|
457
|
+
return services.map((service) => {
|
|
458
|
+
const [type, serviceEndpoint] = service.split(',');
|
|
459
|
+
return { type, serviceEndpoint };
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
// Load .env from the working directory, matching Bun's behavior:
|
|
463
|
+
// values already present in process.env take precedence over .env values.
|
|
464
|
+
function loadEnvFile() {
|
|
465
|
+
try {
|
|
466
|
+
const parsed = parseEnv(fs.readFileSync('.env', 'utf8'));
|
|
467
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
468
|
+
if (!(key in process.env)) {
|
|
469
|
+
process.env[key] = value;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
// No .env file is fine
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Update the main function to be exported
|
|
478
|
+
export async function main() {
|
|
479
|
+
loadEnvFile();
|
|
480
|
+
const [command, ...args] = process.argv.slice(2);
|
|
481
|
+
// console.log('Command:', command);
|
|
482
|
+
// console.log('Args:', args);
|
|
483
|
+
try {
|
|
484
|
+
switch (command) {
|
|
485
|
+
case 'create':
|
|
486
|
+
console.log('Handling create command...');
|
|
487
|
+
await handleCreate(args);
|
|
488
|
+
break;
|
|
489
|
+
case 'resolve':
|
|
490
|
+
await handleResolve(args);
|
|
491
|
+
break;
|
|
492
|
+
case 'update':
|
|
493
|
+
await handleUpdate(args);
|
|
494
|
+
break;
|
|
495
|
+
case 'deactivate':
|
|
496
|
+
await handleDeactivate(args);
|
|
497
|
+
break;
|
|
498
|
+
case 'generate-witness-proof':
|
|
499
|
+
await handleGenerateWitnessProof(args);
|
|
500
|
+
break;
|
|
501
|
+
case 'generate-vm': {
|
|
502
|
+
const vm = await generateVerificationMethod('authentication');
|
|
503
|
+
const publicKeyMultibase = vm.publicKeyMultibase;
|
|
504
|
+
const did = `did:key:${publicKeyMultibase}`;
|
|
505
|
+
console.log(JSON.stringify({
|
|
506
|
+
did,
|
|
507
|
+
publicKeyMultibase,
|
|
508
|
+
secretKeyMultibase: vm.secretKeyMultibase,
|
|
509
|
+
}, null, 2));
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
case 'help':
|
|
513
|
+
showHelp();
|
|
514
|
+
break;
|
|
515
|
+
default:
|
|
516
|
+
console.error('Unknown command:', command);
|
|
517
|
+
showHelp();
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
522
|
+
console.error('Error:', error);
|
|
523
|
+
process.exit(1);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// Only run main if this file is being executed directly
|
|
527
|
+
const isMain = process.argv[1] && import.meta.url === pathToFileURL(fs.realpathSync(process.argv[1])).href;
|
|
528
|
+
if (isMain) {
|
|
529
|
+
main().catch((error) => {
|
|
530
|
+
console.error('Fatal error:', error);
|
|
531
|
+
process.exit(1);
|
|
532
|
+
});
|
|
533
|
+
}
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { bufferToString, createBuffer } from './utils/buffer.js';
|
|
2
|
+
// Helper to safely access environment variables
|
|
3
|
+
const isBrowser = typeof window !== 'undefined';
|
|
4
|
+
const getEnvValue = (key) => {
|
|
5
|
+
if (isBrowser)
|
|
6
|
+
return undefined;
|
|
7
|
+
try {
|
|
8
|
+
return process?.env?.[key];
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export const config = {
|
|
15
|
+
// Helper functions
|
|
16
|
+
getEnvValue,
|
|
17
|
+
isBrowser,
|
|
18
|
+
// Environment checks
|
|
19
|
+
isTestEnvironment: getEnvValue('NODE_ENV') === 'test',
|
|
20
|
+
// Feature flags
|
|
21
|
+
logResolves: getEnvValue('LOG_RESOLVES') === 'true',
|
|
22
|
+
// Get verification methods from env
|
|
23
|
+
getVerificationMethods: () => {
|
|
24
|
+
const encoded = getEnvValue('DID_VERIFICATION_METHODS');
|
|
25
|
+
if (!encoded)
|
|
26
|
+
return [];
|
|
27
|
+
try {
|
|
28
|
+
const decoded = createBuffer(encoded, 'base64');
|
|
29
|
+
return JSON.parse(bufferToString(decoded));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { DataIntegrityProofTemplate, Signer, SignerOptions, SigningInput, SigningOutput, VerificationMethod, Verifier } from './interfaces.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a proof object for a document
|
|
4
|
+
* @param verificationMethodId - The verification method ID to use in the proof
|
|
5
|
+
* @returns A proof object with type, cryptosuite, verificationMethod, created, and proofPurpose
|
|
6
|
+
*/
|
|
7
|
+
export declare const createProof: (verificationMethodId: string) => DataIntegrityProofTemplate;
|
|
8
|
+
/**
|
|
9
|
+
* Prepares data for signing by hashing and concatenating the document and proof
|
|
10
|
+
* @param document - The document to sign
|
|
11
|
+
* @param proof - The proof object
|
|
12
|
+
* @returns The prepared data for signing as a Uint8Array
|
|
13
|
+
*/
|
|
14
|
+
export declare const prepareDataForSigning: (document: unknown, proof: DataIntegrityProofTemplate) => Promise<Uint8Array>;
|
|
15
|
+
/**
|
|
16
|
+
* Abstract base class for signers
|
|
17
|
+
* Users should extend this class to implement their own signing logic
|
|
18
|
+
*/
|
|
19
|
+
export declare abstract class AbstractCrypto implements Signer, Verifier {
|
|
20
|
+
protected verificationMethod?: VerificationMethod | null;
|
|
21
|
+
protected useStaticId: boolean;
|
|
22
|
+
constructor(options: SignerOptions);
|
|
23
|
+
/**
|
|
24
|
+
* Sign the input data
|
|
25
|
+
* @param input - The signing input containing the document and proof
|
|
26
|
+
* @returns The signing output containing the proof value
|
|
27
|
+
*/
|
|
28
|
+
abstract sign(input: SigningInput): Promise<SigningOutput>;
|
|
29
|
+
/**
|
|
30
|
+
* Verify a signature
|
|
31
|
+
* @param signature - The signature to verify
|
|
32
|
+
* @param message - The message to verify
|
|
33
|
+
* @param publicKey - The public key to verify the signature with
|
|
34
|
+
*/
|
|
35
|
+
abstract verify(signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array): Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Get the verification method ID
|
|
38
|
+
* @returns The verification method ID
|
|
39
|
+
*/
|
|
40
|
+
getVerificationMethodId(): string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates a document signer from any Signer implementation
|
|
44
|
+
* @param signer - The signer to use
|
|
45
|
+
* @param verificationMethodId - The verification method ID to use in proofs
|
|
46
|
+
* @returns A function that signs a document and returns the document with proof
|
|
47
|
+
*/
|
|
48
|
+
export declare const createDocumentSigner: (signer: Signer, verificationMethodId: string) => (doc: any) => Promise<any>;
|
|
49
|
+
/**
|
|
50
|
+
* @deprecated Use createDocumentSigner with your own Signer implementation instead
|
|
51
|
+
*/
|
|
52
|
+
export declare const createSigner: (vm: VerificationMethod, useStatic?: boolean) => (doc: any) => Promise<never>;
|