@interop/did-method-webvh 3.2.0 → 3.3.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 +186 -0
- package/README.md +2 -11
- package/dist/assertions.js +19 -2
- package/dist/constants.d.ts +21 -0
- package/dist/constants.js +28 -0
- package/dist/cryptography.d.ts +5 -3
- package/dist/cryptography.js +4 -2
- package/dist/interfaces.d.ts +8 -7
- package/dist/method.d.ts +7 -5
- package/dist/method.js +3 -3
- package/dist/method_versions/method.v1.0.d.ts +6 -5
- package/dist/method_versions/method.v1.0.js +296 -133
- package/dist/utils/iso8601-datetime.d.ts +55 -0
- package/dist/utils/iso8601-datetime.js +116 -0
- package/dist/utils/multiformats.d.ts +2 -0
- package/dist/utils/multiformats.js +4 -0
- package/dist/utils.d.ts +15 -2
- package/dist/utils.js +182 -95
- package/dist/witness.js +12 -5
- package/package.json +2 -6
- package/dist/cli.d.ts +0 -21
- package/dist/cli.js +0 -533
package/dist/witness.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { concatBuffers } from './utils/buffer.js';
|
|
2
2
|
import { canonicalizeStrict } from './utils/canonicalize.js';
|
|
3
3
|
import { createHash } from './utils/crypto.js';
|
|
4
|
-
import { multibaseDecode } from './utils/multiformats.js';
|
|
4
|
+
import { isEd25519Multikey, multibaseDecode } from './utils/multiformats.js';
|
|
5
5
|
import { fetchWitnessProofs, parseDidKeyDid, parseDidKeyVerificationMethod, resolveVM } from './utils.js';
|
|
6
6
|
function createWitnessProofSigner(signer) {
|
|
7
7
|
return async (document, proofTemplate) => {
|
|
@@ -44,19 +44,21 @@ export async function createWitnessProof(signer, versionId, verificationMethod,
|
|
|
44
44
|
};
|
|
45
45
|
// Strip undefined fields to keep the proof JSON-compatible.
|
|
46
46
|
const sanitizedProof = JSON.parse(JSON.stringify(mergedProof));
|
|
47
|
-
|
|
47
|
+
const verificationMethodValue = mergedProof.verificationMethod;
|
|
48
|
+
if (!verificationMethodValue) {
|
|
48
49
|
throw new Error('Witness proof is missing verificationMethod');
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
+
const proofValue = mergedProof.proofValue;
|
|
52
|
+
if (!proofValue) {
|
|
51
53
|
throw new Error('Witness proof is missing proofValue');
|
|
52
54
|
}
|
|
53
55
|
return {
|
|
54
56
|
id: sanitizedProof.id,
|
|
55
57
|
type: sanitizedProof.type ?? proofTemplate.type,
|
|
56
58
|
cryptosuite: sanitizedProof.cryptosuite ?? proofTemplate.cryptosuite,
|
|
57
|
-
verificationMethod:
|
|
59
|
+
verificationMethod: verificationMethodValue,
|
|
58
60
|
created: sanitizedProof.created ?? proofTemplate.created,
|
|
59
|
-
proofValue
|
|
61
|
+
proofValue,
|
|
60
62
|
proofPurpose: sanitizedProof.proofPurpose ?? proofTemplate.proofPurpose,
|
|
61
63
|
};
|
|
62
64
|
}
|
|
@@ -130,6 +132,11 @@ export function validateWitnessParameter(witness) {
|
|
|
130
132
|
throw new Error('Witness DIDs must be did:key format');
|
|
131
133
|
}
|
|
132
134
|
})();
|
|
135
|
+
// did:webvh v1.0 requires witness keys to be Ed25519 multikeys.
|
|
136
|
+
const keyBytes = multibaseDecode(parsedDid.keyMultibase).bytes;
|
|
137
|
+
if (!isEd25519Multikey(keyBytes)) {
|
|
138
|
+
throw new Error(`Witness DID key type must be Ed25519 (multicodec 0xed01): ${w.id}`);
|
|
139
|
+
}
|
|
133
140
|
if (ids.has(parsedDid.did)) {
|
|
134
141
|
throw new Error(`Duplicate witness id: ${w.id}`);
|
|
135
142
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interop/did-method-webvh",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.3.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
"test:bail": "rm -rf ./test/logs && NODE_ENV=test vitest --bail=1",
|
|
30
30
|
"test:coverage": "rm -rf ./test/logs && NODE_ENV=test vitest run --coverage",
|
|
31
31
|
"test:log": "mkdir -p ./test/logs && rm -rf ./test/logs/* && LOG_RESOLVES=true NODE_ENV=test vitest run > ./test/logs/test-run.txt 2>&1",
|
|
32
|
-
"cli": "tsx src/cli.ts",
|
|
33
32
|
"build": "npm run build:clean && tsc",
|
|
34
33
|
"build:clean": "rm -rf dist",
|
|
35
34
|
"check": "tsc --noEmit --project tsconfig.dev.json",
|
|
@@ -38,6 +37,7 @@
|
|
|
38
37
|
},
|
|
39
38
|
"devDependencies": {
|
|
40
39
|
"@biomejs/biome": "^2.3.6",
|
|
40
|
+
"@stablelib/ed25519": "^2.1.0",
|
|
41
41
|
"@types/node": "^22.10.0",
|
|
42
42
|
"@vitest/coverage-v8": "^4.1.8",
|
|
43
43
|
"tsx": "^4.19.0",
|
|
@@ -46,12 +46,8 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@noble/hashes": "^2.2.0",
|
|
49
|
-
"@stablelib/ed25519": "^2.1.0",
|
|
50
49
|
"json-canonicalize": "^2.0.0"
|
|
51
50
|
},
|
|
52
|
-
"bin": {
|
|
53
|
-
"didwebvh": "./dist/cli.js"
|
|
54
|
-
},
|
|
55
51
|
"publishConfig": {
|
|
56
52
|
"access": "public",
|
|
57
53
|
"provenance": true
|
package/dist/cli.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import type { DIDLog } from './interfaces.js';
|
|
3
|
-
export declare function handleCreate(args: string[]): Promise<{
|
|
4
|
-
did: string;
|
|
5
|
-
doc: import("./interfaces.js").DIDDoc;
|
|
6
|
-
meta: import("./interfaces.js").DIDResolutionMeta;
|
|
7
|
-
log: DIDLog;
|
|
8
|
-
}>;
|
|
9
|
-
export declare function handleResolve(args: string[]): Promise<{
|
|
10
|
-
did: string;
|
|
11
|
-
doc: any;
|
|
12
|
-
meta: import("./interfaces.js").DIDResolutionMeta;
|
|
13
|
-
}>;
|
|
14
|
-
export declare function handleUpdate(args: string[]): Promise<import("./interfaces.js").UpdateDIDResult>;
|
|
15
|
-
export declare function handleDeactivate(args: string[]): Promise<{
|
|
16
|
-
did: string;
|
|
17
|
-
doc: any;
|
|
18
|
-
meta: import("./interfaces.js").DIDResolutionMeta;
|
|
19
|
-
log: DIDLog;
|
|
20
|
-
}>;
|
|
21
|
-
export declare function main(): Promise<void>;
|
package/dist/cli.js
DELETED
|
@@ -1,533 +0,0 @@
|
|
|
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
|
-
}
|