@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.
@@ -0,0 +1,554 @@
1
+ import { documentStateIsValid, hashChainValid, newKeysAreInNextKeys, scidIsFromHash } from '../assertions.js';
2
+ import { METHOD, PLACEHOLDER } from '../constants.js';
3
+ import { DidResolutionError } from '../interfaces.js';
4
+ import { createDate, createDIDDoc, createSCID, deepClone, deriveHash, enrichAlsoKnownAs, findVerificationMethod, generateParallelDidWeb, getBaseUrl, parseCanonicalAddress, replaceCreateDidPlaceholders, replaceValueInObject, validateCreateDidDocument, } from '../utils.js';
5
+ import { countVerifiedWitnessApprovals, fetchWitnessProofs, validateWitnessParameter } from '../witness.js';
6
+ const VERSION = '1.0';
7
+ const PROTOCOL = `did:${METHOD}:${VERSION}`;
8
+ const requireDidId = (id) => {
9
+ if (!id) {
10
+ throw new Error('DID document id is missing');
11
+ }
12
+ return id;
13
+ };
14
+ export const createDID = async (options) => {
15
+ if (!options.updateKeys) {
16
+ throw new Error('Update keys not supplied');
17
+ }
18
+ if (options.witness?.witnesses && options.witness.witnesses.length > 0) {
19
+ validateWitnessParameter(options.witness);
20
+ }
21
+ // Parse address input with strict validation
22
+ const addressInput = options.address || options.domain;
23
+ if (!addressInput) {
24
+ throw new Error('Either address or domain must be provided');
25
+ }
26
+ const parsed = parseCanonicalAddress(addressInput);
27
+ const didDomainComponent = parsed.didDomainComponent;
28
+ const allPaths = [...(parsed.paths || []), ...(options.paths || [])];
29
+ const path = allPaths.length > 0 ? allPaths.join(':') : undefined;
30
+ const controller = `did:${METHOD}:${PLACEHOLDER}:${didDomainComponent}${path ? `:${path}` : ''}`;
31
+ const createdDate = createDate(options.created);
32
+ // Safety guard: Strip secret keys from verification methods before creating DID document
33
+ const safeVerificationMethods = options.verificationMethods?.map((vm) => {
34
+ if (vm.secretKeyMultibase) {
35
+ console.warn('Warning: Removing secretKeyMultibase from verification method - secret keys should not be stored in DID documents');
36
+ const { secretKeyMultibase, ...safeVm } = vm;
37
+ return safeVm;
38
+ }
39
+ return vm;
40
+ });
41
+ let doc;
42
+ if (options.didDocument) {
43
+ validateCreateDidDocument(options.didDocument);
44
+ doc = deepClone(options.didDocument);
45
+ }
46
+ else {
47
+ if (!safeVerificationMethods || safeVerificationMethods.length === 0) {
48
+ throw new Error('verificationMethods must be provided when didDocument is not supplied');
49
+ }
50
+ const didDocResult = await createDIDDoc({
51
+ ...options,
52
+ domain: addressInput,
53
+ paths: allPaths,
54
+ controller,
55
+ verificationMethods: safeVerificationMethods,
56
+ });
57
+ doc = didDocResult.doc;
58
+ }
59
+ doc = enrichAlsoKnownAs(doc, controller, {
60
+ alsoKnownAsWeb: options.alsoKnownAsWeb,
61
+ });
62
+ const params = {
63
+ scid: PLACEHOLDER,
64
+ updateKeys: options.updateKeys,
65
+ portable: options.portable ?? false,
66
+ nextKeyHashes: options.nextKeyHashes ?? [],
67
+ watchers: options.watchers ?? [],
68
+ witness: options.witness ?? {},
69
+ deactivated: false,
70
+ };
71
+ const initialLogEntry = {
72
+ versionId: PLACEHOLDER,
73
+ versionTime: createdDate,
74
+ parameters: {
75
+ method: PROTOCOL,
76
+ ...params,
77
+ },
78
+ state: doc,
79
+ };
80
+ const initialLogEntryHash = await deriveHash(initialLogEntry);
81
+ params.scid = await createSCID(initialLogEntryHash);
82
+ initialLogEntry.state = doc;
83
+ const didWithScid = controller.replaceAll(PLACEHOLDER, params.scid);
84
+ const prelimEntry = replaceCreateDidPlaceholders(initialLogEntry, params.scid, didWithScid);
85
+ prelimEntry.state = enrichAlsoKnownAs(prelimEntry.state, didWithScid, {
86
+ alsoKnownAsWeb: options.alsoKnownAsWeb,
87
+ });
88
+ const logEntryHash2 = await deriveHash(prelimEntry);
89
+ prelimEntry.versionId = `1-${logEntryHash2}`;
90
+ const proofTemplate = {
91
+ type: 'DataIntegrityProof',
92
+ cryptosuite: 'eddsa-jcs-2022',
93
+ verificationMethod: options.signer.getVerificationMethodId(),
94
+ created: createdDate,
95
+ proofPurpose: 'assertionMethod',
96
+ };
97
+ const signedProof = await options.signer.sign({ document: prelimEntry, proof: proofTemplate });
98
+ const allProofs = [{ ...proofTemplate, proofValue: signedProof.proofValue }];
99
+ prelimEntry.proof = allProofs;
100
+ const verified = await documentStateIsValid({ ...prelimEntry, versionId: `1-${logEntryHash2}` }, params.updateKeys, params.witness, true, // skipWitnessVerification
101
+ options.verifier);
102
+ if (!verified) {
103
+ throw new Error(`version ${prelimEntry.versionId} is invalid.`);
104
+ }
105
+ const didId = requireDidId(prelimEntry.state.id);
106
+ const webDoc = options.alsoKnownAsWeb ? generateParallelDidWeb(didId, prelimEntry.state) : undefined;
107
+ return {
108
+ did: didId,
109
+ doc: prelimEntry.state,
110
+ meta: {
111
+ versionId: prelimEntry.versionId,
112
+ created: prelimEntry.versionTime,
113
+ updated: prelimEntry.versionTime,
114
+ prerotation: (params.nextKeyHashes?.length ?? 0) > 0,
115
+ ...params,
116
+ },
117
+ log: [prelimEntry],
118
+ ...(webDoc ? { webDoc } : {}),
119
+ };
120
+ };
121
+ export const resolveDIDFromLog = async (log, options = {}) => {
122
+ if (options.verificationMethod && (options.versionNumber || options.versionId)) {
123
+ throw new Error('Cannot specify both verificationMethod and version number/id');
124
+ }
125
+ const resolutionLog = log.map((l) => deepClone(l));
126
+ const protocol = resolutionLog[0]?.parameters?.method;
127
+ if (protocol !== PROTOCOL) {
128
+ throw new Error(`'${protocol}' is not a supported method version.`);
129
+ }
130
+ let did = '';
131
+ let doc = null;
132
+ let resolvedDoc = null;
133
+ let lastValidDoc = null;
134
+ const meta = {
135
+ versionId: '',
136
+ created: '',
137
+ updated: '',
138
+ deactivated: false,
139
+ portable: false,
140
+ scid: '',
141
+ updateKeys: [],
142
+ nextKeyHashes: [],
143
+ prerotation: false,
144
+ witness: undefined,
145
+ watchers: null,
146
+ };
147
+ let resolvedMeta = null;
148
+ let lastValidMeta = null;
149
+ let i = 0;
150
+ let host = '';
151
+ const requiredWitnessChecks = [];
152
+ // Fast resolution is opt-in; full verification is the default conformant path.
153
+ const fastResolve = options.fastResolve ?? false;
154
+ const isFirstEntry = (idx) => idx === 0;
155
+ const isLastFewEntries = (idx) => idx >= resolutionLog.length - 10; // Verify last 10 entries
156
+ const shouldVerifyEntry = (idx) => !fastResolve || isFirstEntry(idx) || isLastFewEntries(idx);
157
+ try {
158
+ while (i < resolutionLog.length) {
159
+ const { versionId, versionTime, parameters, state, proof } = resolutionLog[i];
160
+ const [version, entryHash] = versionId.split('-');
161
+ const previousWitness = meta.witness ? deepClone(meta.witness) : undefined;
162
+ if (parseInt(version, 10) !== i + 1) {
163
+ throw new Error(`version '${version}' in log doesn't match expected '${i + 1}'.`);
164
+ }
165
+ meta.versionId = versionId;
166
+ if (versionTime) {
167
+ // TODO check timestamps make sense
168
+ }
169
+ meta.updated = versionTime;
170
+ let newDoc = state;
171
+ if (version === '1') {
172
+ meta.created = versionTime;
173
+ newDoc = state;
174
+ host = newDoc.id.split(':').at(-1);
175
+ meta.scid = parameters.scid;
176
+ if (options.scid && options.scid !== meta.scid) {
177
+ throw new Error(`SCID in DID '${options.scid}' does not match SCID in log '${meta.scid}'`);
178
+ }
179
+ meta.portable = parameters.portable ?? meta.portable;
180
+ meta.updateKeys = parameters.updateKeys;
181
+ meta.nextKeyHashes = parameters.nextKeyHashes || [];
182
+ meta.prerotation = meta.nextKeyHashes.length > 0;
183
+ meta.witness = parameters.witness || meta.witness;
184
+ meta.watchers = parameters.watchers ?? null;
185
+ if (shouldVerifyEntry(i)) {
186
+ // Optimized: Use efficient object manipulation instead of JSON stringify/parse
187
+ const logEntry = {
188
+ versionId: PLACEHOLDER,
189
+ versionTime: meta.created,
190
+ parameters: replaceValueInObject(parameters, meta.scid, PLACEHOLDER),
191
+ state: replaceValueInObject(newDoc, meta.scid, PLACEHOLDER),
192
+ };
193
+ const logEntryHash = await deriveHash(logEntry);
194
+ meta.previousLogEntryHash = logEntryHash;
195
+ if (!(await scidIsFromHash(meta.scid, logEntryHash))) {
196
+ throw new Error(`SCID '${meta.scid}' not derived from logEntryHash '${logEntryHash}'`);
197
+ }
198
+ // Optimized: Direct object manipulation instead of JSON stringify/parse
199
+ const prelimEntry = replaceValueInObject(logEntry, PLACEHOLDER, meta.scid);
200
+ const logEntryHash2 = await deriveHash(prelimEntry);
201
+ const verified = await documentStateIsValid({ ...prelimEntry, versionId: `1-${logEntryHash2}`, proof }, meta.updateKeys, meta.witness, false, options.verifier);
202
+ if (!verified) {
203
+ throw new Error(`version ${meta.versionId} failed verification of the proof.`);
204
+ }
205
+ }
206
+ }
207
+ else {
208
+ // version number > 1
209
+ const newHost = newDoc.id.split(':').at(-1);
210
+ if (!meta.portable && newHost !== host) {
211
+ throw new Error('Cannot move DID: portability is disabled');
212
+ }
213
+ else if (newHost !== host) {
214
+ host = newHost;
215
+ }
216
+ // Hash chain — ALWAYS runs (cheap), even in fast-resolve
217
+ const { proof: _proof, ...entryWithoutProof } = resolutionLog[i];
218
+ const recomputedHash = await deriveHash({ ...entryWithoutProof, versionId: resolutionLog[i - 1].versionId });
219
+ if (!hashChainValid(recomputedHash, entryHash)) {
220
+ throw new Error(`Hash chain broken at '${meta.versionId}'`);
221
+ }
222
+ if (shouldVerifyEntry(i)) {
223
+ // Signature verification — expensive, skipped for middle entries in fast-resolve
224
+ const keys = meta.prerotation ? parameters.updateKeys : meta.updateKeys;
225
+ const verified = await documentStateIsValid(resolutionLog[i], keys, meta.witness, false, options.verifier);
226
+ if (!verified) {
227
+ throw new Error(`version ${meta.versionId} failed verification of the proof.`);
228
+ }
229
+ if (meta.prerotation) {
230
+ await newKeysAreInNextKeys(parameters.updateKeys ?? [], meta.nextKeyHashes ?? []);
231
+ }
232
+ }
233
+ if (parameters.updateKeys) {
234
+ meta.updateKeys = parameters.updateKeys;
235
+ }
236
+ if (parameters.deactivated === true) {
237
+ meta.deactivated = true;
238
+ }
239
+ if (parameters.nextKeyHashes && parameters.nextKeyHashes.length > 0) {
240
+ meta.nextKeyHashes = parameters.nextKeyHashes;
241
+ meta.prerotation = true;
242
+ }
243
+ else {
244
+ meta.nextKeyHashes = [];
245
+ meta.prerotation = false;
246
+ }
247
+ if ('witness' in parameters) {
248
+ meta.witness = parameters.witness;
249
+ }
250
+ else if (parameters.witnesses) {
251
+ meta.witness = {
252
+ witnesses: parameters.witnesses,
253
+ threshold: parameters.witnessThreshold || parameters.witnesses.length,
254
+ };
255
+ }
256
+ if (meta.witness?.witnesses?.length) {
257
+ validateWitnessParameter(meta.witness);
258
+ }
259
+ if ('watchers' in parameters) {
260
+ meta.watchers = parameters.watchers ?? null;
261
+ }
262
+ }
263
+ const requiredWitness = getRequiredWitnessForEntry(previousWitness, parameters, meta.witness);
264
+ if (requiredWitness) {
265
+ requiredWitnessChecks.push({
266
+ targetVersionId: meta.versionId,
267
+ targetVersionNumber: parseInt(version, 10),
268
+ witness: requiredWitness,
269
+ });
270
+ }
271
+ // Optimized: Use efficient cloning instead of clone() function
272
+ doc = deepClone(newDoc);
273
+ did = doc.id;
274
+ // Only add default services for entries we need to process
275
+ if (shouldVerifyEntry(i) || i === resolutionLog.length - 1) {
276
+ // Add default services if they don't exist
277
+ doc.service = Array.isArray(doc.service) ? doc.service : [];
278
+ const baseUrl = getBaseUrl(did);
279
+ if (!doc.service.some((s) => s.id === '#files')) {
280
+ doc.service.push({
281
+ id: '#files',
282
+ type: 'relativeRef',
283
+ serviceEndpoint: baseUrl,
284
+ });
285
+ }
286
+ if (!doc.service.some((s) => s.id === '#whois')) {
287
+ doc.service.push({
288
+ '@context': 'https://identity.foundation/linked-vp/contexts/v1',
289
+ id: '#whois',
290
+ type: 'LinkedVerifiablePresentation',
291
+ serviceEndpoint: `${baseUrl}/whois.vp`,
292
+ });
293
+ }
294
+ }
295
+ if (options.verificationMethod && findVerificationMethod(doc, options.verificationMethod)) {
296
+ if (!resolvedDoc) {
297
+ resolvedDoc = deepClone(doc);
298
+ resolvedMeta = { ...meta };
299
+ }
300
+ }
301
+ if (options.versionNumber === parseInt(version, 10) || options.versionId === meta.versionId) {
302
+ if (!resolvedDoc) {
303
+ resolvedDoc = deepClone(doc);
304
+ resolvedMeta = { ...meta };
305
+ }
306
+ }
307
+ if (options.versionTime && options.versionTime > new Date(meta.updated)) {
308
+ if (resolutionLog[i + 1] && options.versionTime < new Date(resolutionLog[i + 1].versionTime)) {
309
+ if (!resolvedDoc) {
310
+ resolvedDoc = deepClone(doc);
311
+ resolvedMeta = { ...meta };
312
+ }
313
+ }
314
+ else if (!resolutionLog[i + 1]) {
315
+ if (!resolvedDoc) {
316
+ resolvedDoc = deepClone(doc);
317
+ resolvedMeta = { ...meta };
318
+ }
319
+ }
320
+ }
321
+ lastValidDoc = deepClone(doc);
322
+ lastValidMeta = { ...meta };
323
+ i++;
324
+ }
325
+ if (requiredWitnessChecks.length > 0) {
326
+ if (!options.witnessProofs) {
327
+ options.witnessProofs = await fetchWitnessProofs(did);
328
+ }
329
+ const publishedVersionNumbers = new Map(resolutionLog.map((entry, index) => [entry.versionId, index + 1]));
330
+ for (const check of requiredWitnessChecks) {
331
+ const candidateProofs = (options.witnessProofs ?? []).filter((witnessProof) => {
332
+ const proofVersionNumber = publishedVersionNumbers.get(witnessProof.versionId);
333
+ return proofVersionNumber !== undefined && proofVersionNumber >= check.targetVersionNumber;
334
+ });
335
+ const approvals = await countVerifiedWitnessApprovals(resolutionLog[check.targetVersionNumber - 1], candidateProofs, check.witness, options.verifier);
336
+ const threshold = parseInt((check.witness.threshold ?? 0).toString(), 10);
337
+ if (approvals < threshold) {
338
+ throw new Error(`Witness threshold not met for version ${check.targetVersionId}: got ${approvals}, need ${check.witness.threshold}`);
339
+ }
340
+ }
341
+ }
342
+ }
343
+ catch (e) {
344
+ if (!resolvedDoc) {
345
+ throw e;
346
+ }
347
+ if (resolvedMeta) {
348
+ const message = e instanceof Error ? e.message : String(e);
349
+ resolvedMeta.error = DidResolutionError.InvalidDid;
350
+ resolvedMeta.problemDetails = {
351
+ type: 'https://w3id.org/security#INVALID_CONTROLLED_IDENTIFIER_DOCUMENT_ID',
352
+ title: 'The resolved DID is invalid.',
353
+ detail: message,
354
+ };
355
+ }
356
+ }
357
+ if (!resolvedDoc) {
358
+ resolvedDoc = lastValidDoc;
359
+ resolvedMeta = lastValidMeta;
360
+ }
361
+ if (!resolvedMeta) {
362
+ throw new Error('DID resolution failed: No valid metadata found');
363
+ }
364
+ return {
365
+ did,
366
+ doc: resolvedDoc,
367
+ meta: resolvedMeta,
368
+ };
369
+ };
370
+ export const updateDID = async (options) => {
371
+ const log = options.log;
372
+ const lastEntry = log[log.length - 1];
373
+ const lastMeta = (await resolveDIDFromLog(log, { verifier: options.verifier, witnessProofs: options.witnessProofs }))
374
+ .meta;
375
+ if (lastMeta.deactivated) {
376
+ throw new Error('Cannot update deactivated DID');
377
+ }
378
+ const versionNumber = log.length + 1;
379
+ const createdDate = createDate(options.updated);
380
+ const watchersValue = options.watchers !== undefined ? options.watchers : lastMeta.watchers;
381
+ const witnessInput = options.witness;
382
+ const witness = witnessInput?.witnesses?.length
383
+ ? {
384
+ witnesses: witnessInput.witnesses,
385
+ threshold: witnessInput.threshold ?? 0,
386
+ }
387
+ : {};
388
+ const params = {
389
+ updateKeys: options.updateKeys ?? [],
390
+ nextKeyHashes: options.nextKeyHashes ?? [],
391
+ witness,
392
+ watchers: watchersValue ?? [],
393
+ };
394
+ if (params.witness?.witnesses?.length) {
395
+ validateWitnessParameter(params.witness);
396
+ }
397
+ // Safety guard: Strip secret keys from verification methods before creating DID document
398
+ const safeVerificationMethods = options.verificationMethods?.map((vm) => {
399
+ if (vm.secretKeyMultibase) {
400
+ console.warn('Warning: Removing secretKeyMultibase from verification method - secret keys should not be stored in DID documents');
401
+ const { secretKeyMultibase, ...safeVm } = vm;
402
+ return safeVm;
403
+ }
404
+ return vm;
405
+ });
406
+ const { doc } = await createDIDDoc({
407
+ ...options,
408
+ controller: options.controller || lastEntry.state.id || '',
409
+ context: options.context || lastEntry.state['@context'],
410
+ domain: options.domain ?? lastEntry.state.id?.split(':').at(-1) ?? '',
411
+ updateKeys: options.updateKeys ?? [],
412
+ verificationMethods: safeVerificationMethods ?? [],
413
+ });
414
+ // Add services if provided
415
+ if (options.services && options.services.length > 0) {
416
+ doc.service = options.services;
417
+ }
418
+ // Add assertionMethod if provided
419
+ if (options.assertionMethod) {
420
+ doc.assertionMethod = options.assertionMethod;
421
+ }
422
+ // Add keyAgreement if provided
423
+ if (options.keyAgreement) {
424
+ doc.keyAgreement = options.keyAgreement;
425
+ }
426
+ const logEntry = {
427
+ versionId: lastEntry.versionId,
428
+ versionTime: createdDate,
429
+ parameters: params,
430
+ state: doc,
431
+ };
432
+ const logEntryHash = await deriveHash(logEntry);
433
+ const versionId = `${versionNumber}-${logEntryHash}`;
434
+ const prelimEntry = { ...logEntry, versionId };
435
+ const proofTemplate = {
436
+ type: 'DataIntegrityProof',
437
+ cryptosuite: 'eddsa-jcs-2022',
438
+ verificationMethod: options.signer.getVerificationMethodId(),
439
+ created: createdDate,
440
+ proofPurpose: 'assertionMethod',
441
+ };
442
+ const signedProof = await options.signer.sign({ document: prelimEntry, proof: proofTemplate });
443
+ const allProofs = [{ ...proofTemplate, proofValue: signedProof.proofValue }];
444
+ prelimEntry.proof = allProofs;
445
+ const keysToVerify = lastMeta.prerotation ? params.updateKeys : lastMeta.updateKeys;
446
+ const verified = await documentStateIsValid(prelimEntry, keysToVerify, lastMeta.witness, true, options.verifier);
447
+ if (!verified) {
448
+ throw new Error(`version ${prelimEntry.versionId} is invalid.`);
449
+ }
450
+ const meta = {
451
+ ...lastMeta,
452
+ versionId: prelimEntry.versionId,
453
+ updated: prelimEntry.versionTime,
454
+ prerotation: (params.nextKeyHashes?.length ?? 0) > 0,
455
+ ...params,
456
+ };
457
+ const hasWebAlias = (prelimEntry.state.alsoKnownAs ?? []).some((alias) => alias.startsWith('did:web:'));
458
+ const didId = requireDidId(prelimEntry.state.id);
459
+ const webDoc = hasWebAlias ? generateParallelDidWeb(didId, prelimEntry.state) : undefined;
460
+ return {
461
+ did: didId,
462
+ doc: prelimEntry.state,
463
+ meta,
464
+ log: [...log, prelimEntry],
465
+ ...(webDoc ? { webDoc } : {}),
466
+ };
467
+ };
468
+ export const deactivateDID = async (options) => {
469
+ const log = options.log;
470
+ const lastEntry = log[log.length - 1];
471
+ const lastMeta = (await resolveDIDFromLog(log, { verifier: options.verifier })).meta;
472
+ if (lastMeta.deactivated) {
473
+ throw new Error('DID already deactivated');
474
+ }
475
+ const versionNumber = log.length + 1;
476
+ const createdDate = createDate();
477
+ const params = {
478
+ updateKeys: options.updateKeys ?? lastMeta.updateKeys,
479
+ deactivated: true,
480
+ };
481
+ const logEntry = {
482
+ versionId: lastEntry.versionId,
483
+ versionTime: createdDate,
484
+ parameters: params,
485
+ state: lastEntry.state,
486
+ };
487
+ const logEntryHash = await deriveHash(logEntry);
488
+ const versionId = `${versionNumber}-${logEntryHash}`;
489
+ const prelimEntry = { ...logEntry, versionId };
490
+ const proofTemplate = {
491
+ type: 'DataIntegrityProof',
492
+ cryptosuite: 'eddsa-jcs-2022',
493
+ verificationMethod: options.signer.getVerificationMethodId(),
494
+ created: createdDate,
495
+ proofPurpose: 'assertionMethod',
496
+ };
497
+ const signedProof = await options.signer.sign({ document: prelimEntry, proof: proofTemplate });
498
+ const allProofs = [{ ...proofTemplate, proofValue: signedProof.proofValue }];
499
+ prelimEntry.proof = allProofs;
500
+ const verified = await documentStateIsValid(prelimEntry, lastMeta.updateKeys, lastMeta.witness, true, // skipWitnessVerification
501
+ options.verifier);
502
+ if (!verified) {
503
+ throw new Error(`version ${prelimEntry.versionId} is invalid.`);
504
+ }
505
+ const meta = {
506
+ ...lastMeta,
507
+ versionId: prelimEntry.versionId,
508
+ updated: prelimEntry.versionTime,
509
+ deactivated: true,
510
+ updateKeys: params.updateKeys,
511
+ };
512
+ const didId = requireDidId(prelimEntry.state.id);
513
+ return {
514
+ did: didId,
515
+ doc: prelimEntry.state,
516
+ meta,
517
+ log: [...log, prelimEntry],
518
+ };
519
+ };
520
+ const getEntryWitnessParameter = (parameters) => {
521
+ if ('witness' in parameters) {
522
+ return parameters.witness ?? {};
523
+ }
524
+ if (parameters.witnesses) {
525
+ return {
526
+ witnesses: parameters.witnesses,
527
+ threshold: parameters.witnessThreshold || parameters.witnesses.length,
528
+ };
529
+ }
530
+ return undefined;
531
+ };
532
+ const isWitnessActive = (witness) => {
533
+ if (!witness?.witnesses || witness.witnesses.length === 0) {
534
+ return false;
535
+ }
536
+ const threshold = parseInt((witness.threshold ?? 0).toString(), 10);
537
+ return threshold > 0;
538
+ };
539
+ const getRequiredWitnessForEntry = (previousWitness, parameters, currentWitness) => {
540
+ const explicitWitness = getEntryWitnessParameter(parameters);
541
+ if (explicitWitness !== undefined) {
542
+ if (isWitnessActive(currentWitness)) {
543
+ return deepClone(currentWitness);
544
+ }
545
+ if (isWitnessActive(previousWitness)) {
546
+ return deepClone(previousWitness);
547
+ }
548
+ return undefined;
549
+ }
550
+ if (isWitnessActive(previousWitness)) {
551
+ return deepClone(previousWitness);
552
+ }
553
+ return undefined;
554
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * This file re-exports all types from interfaces.ts
3
+ * It's used to provide a clear entry point for type imports
4
+ */
5
+ export * from './interfaces.js';
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * This file re-exports all types from interfaces.ts
3
+ * It's used to provide a clear entry point for type imports
4
+ */
5
+ export * from './interfaces.js';
@@ -0,0 +1,3 @@
1
+ export declare const createBuffer: (input: string, encoding?: BufferEncoding) => Uint8Array;
2
+ export declare const bufferToString: (buffer: Uint8Array, encoding?: BufferEncoding) => string;
3
+ export declare const concatBuffers: (...buffers: Uint8Array[]) => Uint8Array;
@@ -0,0 +1,62 @@
1
+ import { config } from '../config.js';
2
+ // Helper to convert bytes to hex string
3
+ const bytesToHex = (bytes) => {
4
+ return Array.from(bytes)
5
+ .map((b) => b.toString(16).padStart(2, '0'))
6
+ .join('');
7
+ };
8
+ // Helper to convert hex string to bytes
9
+ const hexToBytes = (hex) => {
10
+ if (hex.length % 2 !== 0) {
11
+ throw new Error('Hex string must have an even number of characters');
12
+ }
13
+ const bytes = new Uint8Array(hex.length / 2);
14
+ for (let i = 0; i < hex.length; i += 2) {
15
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
16
+ }
17
+ return bytes;
18
+ };
19
+ // Buffer polyfill for browser environments
20
+ export const createBuffer = (input, encoding) => {
21
+ if (!config.isBrowser) {
22
+ return Buffer.from(input, encoding);
23
+ }
24
+ // Handle base64 encoding specifically
25
+ if (encoding === 'base64') {
26
+ const binaryString = atob(input);
27
+ return new Uint8Array(binaryString.length).map((_, i) => binaryString.charCodeAt(i));
28
+ }
29
+ // Default to UTF-8 encoding
30
+ return new TextEncoder().encode(input);
31
+ };
32
+ export const bufferToString = (buffer, encoding) => {
33
+ if (!config.isBrowser) {
34
+ return Buffer.from(buffer).toString(encoding);
35
+ }
36
+ // Handle hex encoding specifically
37
+ if (encoding === 'hex') {
38
+ return bytesToHex(buffer);
39
+ }
40
+ // Handle base64 encoding specifically
41
+ if (encoding === 'base64') {
42
+ const binary = String.fromCharCode(...buffer);
43
+ return btoa(binary);
44
+ }
45
+ // Default to UTF-8 encoding
46
+ return new TextDecoder().decode(buffer);
47
+ };
48
+ export const concatBuffers = (...buffers) => {
49
+ if (!config.isBrowser) {
50
+ return Buffer.concat(buffers);
51
+ }
52
+ // Calculate total length
53
+ const totalLength = buffers.reduce((acc, buf) => acc + buf.length, 0);
54
+ // Create new array and copy all buffers into it
55
+ const result = new Uint8Array(totalLength);
56
+ let offset = 0;
57
+ for (const buffer of buffers) {
58
+ result.set(buffer, offset);
59
+ offset += buffer.length;
60
+ }
61
+ return result;
62
+ };
@@ -0,0 +1,3 @@
1
+ import type { JsonValue } from '../interfaces.js';
2
+ export declare function validateStrictJsonValue(value: unknown): asserts value is JsonValue;
3
+ export declare const canonicalizeStrict: (value: unknown) => string;