@did-btcr2/common 1.1.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/LICENSE +373 -0
- package/README.md +3 -0
- package/dist/cjs/canonicalization.js +163 -0
- package/dist/cjs/canonicalization.js.map +1 -0
- package/dist/cjs/constants.js +116 -0
- package/dist/cjs/constants.js.map +1 -0
- package/dist/cjs/errors.js +151 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/exts.js +182 -0
- package/dist/cjs/exts.js.map +1 -0
- package/dist/cjs/index.js +10 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/interfaces.js +2 -0
- package/dist/cjs/interfaces.js.map +1 -0
- package/dist/cjs/logger.browser.js +77 -0
- package/dist/cjs/logger.browser.js.map +1 -0
- package/dist/cjs/logger.js +139 -0
- package/dist/cjs/logger.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/patch.js +163 -0
- package/dist/cjs/patch.js.map +1 -0
- package/dist/cjs/types.js +20 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/canonicalization.js +163 -0
- package/dist/esm/canonicalization.js.map +1 -0
- package/dist/esm/constants.js +116 -0
- package/dist/esm/constants.js.map +1 -0
- package/dist/esm/errors.js +151 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/exts.js +182 -0
- package/dist/esm/exts.js.map +1 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/interfaces.js +2 -0
- package/dist/esm/interfaces.js.map +1 -0
- package/dist/esm/logger.browser.js +77 -0
- package/dist/esm/logger.browser.js.map +1 -0
- package/dist/esm/logger.js +139 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/patch.js +163 -0
- package/dist/esm/patch.js.map +1 -0
- package/dist/esm/types.js +20 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/types/canonicalization.d.ts +106 -0
- package/dist/types/canonicalization.d.ts.map +1 -0
- package/dist/types/constants.d.ts +103 -0
- package/dist/types/constants.d.ts.map +1 -0
- package/dist/types/errors.d.ts +113 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/exts.d.ts +82 -0
- package/dist/types/exts.d.ts.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/interfaces.d.ts +282 -0
- package/dist/types/interfaces.d.ts.map +1 -0
- package/dist/types/logger.browser.d.ts +28 -0
- package/dist/types/logger.browser.d.ts.map +1 -0
- package/dist/types/logger.d.ts +45 -0
- package/dist/types/logger.d.ts.map +1 -0
- package/dist/types/patch.d.ts +63 -0
- package/dist/types/patch.d.ts.map +1 -0
- package/dist/types/types.d.ts +104 -0
- package/dist/types/types.d.ts.map +1 -0
- package/package.json +109 -0
- package/src/canonicalization.ts +180 -0
- package/src/constants.ts +123 -0
- package/src/errors.ts +224 -0
- package/src/exts.ts +293 -0
- package/src/index.ts +11 -0
- package/src/interfaces.ts +311 -0
- package/src/logger.browser.ts +92 -0
- package/src/logger.ts +162 -0
- package/src/patch.ts +181 -0
- package/src/rdf-canonize.d.ts +6 -0
- package/src/types.ts +113 -0
package/src/exts.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { Canonicalization } from './canonicalization.js';
|
|
2
|
+
import { Patch } from './patch.js';
|
|
3
|
+
import { JSONObject, Maybe, Prototyped, Unprototyped } from './types.js';
|
|
4
|
+
|
|
5
|
+
/** Extend the global namespace */
|
|
6
|
+
declare global {
|
|
7
|
+
/** Extend the global Array interface */
|
|
8
|
+
interface Array<T> {
|
|
9
|
+
/** Get the last element of the array */
|
|
10
|
+
last(): T | undefined;
|
|
11
|
+
/** Get the last element of the array */
|
|
12
|
+
[-1](): T | undefined;
|
|
13
|
+
/** Convert Array to Uint8Array */
|
|
14
|
+
toUint8Array(): Uint8Array;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Extend the global Buffer interface */
|
|
18
|
+
interface BufferConstructor {
|
|
19
|
+
/** Convert a hex string to a Buffer */
|
|
20
|
+
fromHex(hex: string): Buffer<ArrayBuffer>;
|
|
21
|
+
/** Convert a Uint8Array to a hex string */
|
|
22
|
+
toHex(ui8: Uint8Array): string;
|
|
23
|
+
/** Convert Buffer to Uint8Array */
|
|
24
|
+
toUint8Array(buf: Buffer<ArrayBuffer>): Uint8Array;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Extend the global Date interface */
|
|
28
|
+
interface Date {
|
|
29
|
+
/** Get the UTC date and time in ISO 8601 format */
|
|
30
|
+
getUTCDateTime(): string;
|
|
31
|
+
/** Convert date to Unix timestamp */
|
|
32
|
+
toUnix(): number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Extend global JSON interface */
|
|
36
|
+
interface JSON {
|
|
37
|
+
/** Check if an object is a JSON object */
|
|
38
|
+
is(unknown: Maybe<JSONObject>): boolean;
|
|
39
|
+
/** Check if JSON string can be parsed to JSON object */
|
|
40
|
+
parsable(unknown: Maybe<string>): boolean;
|
|
41
|
+
/** Check if JSON object can be converted to JSON string */
|
|
42
|
+
stringifiable(unknown: Maybe<JSONObject>): boolean;
|
|
43
|
+
/** Check if JSON object is unprototyped [Object: null prototype] {} */
|
|
44
|
+
unprototyped(unknown: Maybe<Prototyped>): boolean;
|
|
45
|
+
/** Normalize unprototyped JSON object to prototyped JSON object */
|
|
46
|
+
normalize(unknown: Maybe<Unprototyped>): Prototyped;
|
|
47
|
+
/** Shallow copy of JSON object */
|
|
48
|
+
copy(o: JSONObject): JSONObject;
|
|
49
|
+
/** Clone (deep copy) of JSON object */
|
|
50
|
+
clone(o: JSONObject): JSONObject;
|
|
51
|
+
/** Deep copy of JSON object with replacement */
|
|
52
|
+
cloneReplace(o: JSONObject, e: RegExp, r: string): JSONObject;
|
|
53
|
+
/** Check if two objects are strictly equal */
|
|
54
|
+
equal(a: any, b: any): boolean;
|
|
55
|
+
/** Check if two objects are deeply equal */
|
|
56
|
+
deepEqual(a: any, b: any): boolean;
|
|
57
|
+
/** Delete key/value pair(s) from a JSON object */
|
|
58
|
+
delete(o: JSONObject, keys: Array<string | number | symbol>): JSONObject;
|
|
59
|
+
/** Sanitize a JSON object by removing any keys whose value is undefined */
|
|
60
|
+
sanitize(o: JSONObject): JSONObject;
|
|
61
|
+
/** Canonicalization object */
|
|
62
|
+
canonicalization: Canonicalization;
|
|
63
|
+
/** JSON Patch (IETF RFC 6902) */
|
|
64
|
+
patch: Patch;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
/** Extend global Set interface */
|
|
69
|
+
interface Set<T> {
|
|
70
|
+
/** Get the difference between two sets */
|
|
71
|
+
difference(other: Set<T>): Set<T>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface String {
|
|
75
|
+
/** Convert to SCREAMING_SNAKE_CASE */
|
|
76
|
+
toSnakeScream(): string;
|
|
77
|
+
/** Convert to snake_case */
|
|
78
|
+
toSnake(): string;
|
|
79
|
+
/** Remove the last character from a string */
|
|
80
|
+
chop(): string;
|
|
81
|
+
/** Replace the end of a string */
|
|
82
|
+
replaceEnd(e: string | RegExp, r?: string): string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Extend the global Uint8Array interface */
|
|
86
|
+
interface Uint8Array {
|
|
87
|
+
toArray(): number[];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Array Interface Extensions */
|
|
92
|
+
Array.prototype.last = function <T>(): T | undefined {
|
|
93
|
+
return this[this.length - 1] ?? undefined;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
Array.prototype[-1] = function <T>(): T | undefined {
|
|
97
|
+
return this.last();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
Array.prototype.toUint8Array = function (): Uint8Array {
|
|
101
|
+
return new Uint8Array(this);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/** BufferConstructor/Buffer Interface Extensions */
|
|
105
|
+
Buffer.fromHex = function (hex: string): Buffer<ArrayBuffer> {
|
|
106
|
+
return Buffer.from(hex, 'hex');
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
Buffer.toHex = function (ui8: Uint8Array): string {
|
|
110
|
+
return Buffer.from(ui8).toString('hex');
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
Buffer.toUint8Array = function (buf: Buffer<ArrayBuffer>): Uint8Array {
|
|
114
|
+
return new Uint8Array(buf);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** Date Interface Extensions */
|
|
118
|
+
Date.prototype.getUTCDateTime = function (): string {
|
|
119
|
+
return `${this.toISOString().slice(0, -5)}Z`;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
Date.prototype.toUnix = function (): number {
|
|
123
|
+
const time = this.getTime();
|
|
124
|
+
if (isNaN(time)) {
|
|
125
|
+
throw new Error(`Invalid date string: "${this}"`);
|
|
126
|
+
}
|
|
127
|
+
return time;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/** JSON Interface Extensions */
|
|
131
|
+
JSON.is = function (unknown: Maybe<JSONObject>): boolean {
|
|
132
|
+
if (unknown === null || typeof unknown !== 'object') return false;
|
|
133
|
+
if (Array.isArray(unknown))
|
|
134
|
+
return unknown.every(item => Object.getPrototypeOf(item) !== null);
|
|
135
|
+
else
|
|
136
|
+
return Object.getPrototypeOf(unknown) === null;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
JSON.parsable = function (unknown: Maybe<string>): boolean {
|
|
140
|
+
try {
|
|
141
|
+
JSON.parse(unknown);
|
|
142
|
+
return true;
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
JSON.stringifiable = function (unknown: Maybe<JSONObject>): boolean {
|
|
149
|
+
try {
|
|
150
|
+
JSON.stringify(unknown);
|
|
151
|
+
return true;
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
JSON.unprototyped = function (unknown: Maybe<Unprototyped>): boolean {
|
|
158
|
+
if (Array.isArray(unknown)) {
|
|
159
|
+
return unknown.every(item => Object.getPrototypeOf(item) === null);
|
|
160
|
+
}
|
|
161
|
+
return Object.getPrototypeOf(unknown) === null;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
JSON.normalize = function (unknown: Maybe<Unprototyped>): Prototyped {
|
|
165
|
+
try {
|
|
166
|
+
return JSON.parse(JSON.stringify(unknown));
|
|
167
|
+
} catch {
|
|
168
|
+
throw new Error('The object is not unprotocyped');
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
JSON.copy = function (o: JSONObject): JSONObject {
|
|
173
|
+
return Object.assign({}, o);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
JSON.clone = function (o: JSONObject): JSONObject {
|
|
177
|
+
return JSON.parse(JSON.stringify(o));
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
JSON.cloneReplace = function (o: JSONObject, e: RegExp, r: string): JSONObject {
|
|
181
|
+
return JSON.parse(JSON.stringify(o).replaceAll(e, r));
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
JSON.equal = function (a: any, b: any): boolean {
|
|
185
|
+
return a === b;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
JSON.deepEqual = function (a: any, b: any): boolean {
|
|
189
|
+
if(JSON.equal(a, b)) return true;
|
|
190
|
+
|
|
191
|
+
if (a === null || b === null || typeof a !== typeof b) return false;
|
|
192
|
+
|
|
193
|
+
if (typeof a === 'object') {
|
|
194
|
+
const isArrayA = Array.isArray(a);
|
|
195
|
+
const isArrayB = Array.isArray(b);
|
|
196
|
+
if (isArrayA !== isArrayB) return false;
|
|
197
|
+
|
|
198
|
+
if (isArrayA && isArrayB) {
|
|
199
|
+
if (a.length !== b.length) return false;
|
|
200
|
+
for (let i = 0; i < a.length; i++) {
|
|
201
|
+
if (!this.deepEqual(a[i], b[i])) return false;
|
|
202
|
+
}
|
|
203
|
+
return true;
|
|
204
|
+
} else {
|
|
205
|
+
const keysA = Object.keys(a);
|
|
206
|
+
const keysB = Object.keys(b);
|
|
207
|
+
if (keysA.length !== keysB.length) return false;
|
|
208
|
+
|
|
209
|
+
for (const key of keysA) {
|
|
210
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
if (!this.deepEqual(a[key], b[key])) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return false;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
JSON.delete = function(o: JSONObject, keys: Array<string | number | symbol>): JSONObject {
|
|
225
|
+
if (!JSON.is(o)) return o;
|
|
226
|
+
|
|
227
|
+
for(const key of keys) {
|
|
228
|
+
if (Object.prototype.hasOwnProperty.call(o, key)) {
|
|
229
|
+
delete o[key];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
for (const key in o) {
|
|
233
|
+
if (typeof o[key] === 'object') {
|
|
234
|
+
o[key] = this.delete(o[key], [key]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return o;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
JSON.sanitize = function (o: JSONObject): JSONObject {
|
|
243
|
+
for (const key of Object.keys(o)) {
|
|
244
|
+
if (o[key] === undefined) {
|
|
245
|
+
delete o[key];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return o;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
JSON.canonicalization = new Canonicalization();
|
|
252
|
+
JSON.patch = new Patch();
|
|
253
|
+
|
|
254
|
+
/** Set Interface Extensions */
|
|
255
|
+
Set.prototype.difference = function <T>(other: Set<T>): Set<T> {
|
|
256
|
+
const result = new Set<T>(this);
|
|
257
|
+
for (const item of other) {
|
|
258
|
+
if (result.has(item)) {
|
|
259
|
+
result.delete(item);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/** String Interface Extensions */
|
|
266
|
+
String.prototype.toSnake = function (): string {
|
|
267
|
+
return this
|
|
268
|
+
.replace(/([a-z])([A-Z])/g, '$1_$2')
|
|
269
|
+
.toLowerCase();
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
String.prototype.toSnakeScream = function (): string {
|
|
273
|
+
return this.toSnake().toUpperCase();
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
String.prototype.chop = function (): string {
|
|
277
|
+
return this.length > 0 ? this.slice(0, -1) : '';
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
String.prototype.replaceEnd = function (e: string | RegExp, r?: string): string {
|
|
281
|
+
const pattern = e instanceof RegExp
|
|
282
|
+
? new RegExp(e.source.endsWith('$') ? e.source : `${e.source}$`, e.flags.replace('g', ''))
|
|
283
|
+
: new RegExp(`${e.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}$`);
|
|
284
|
+
|
|
285
|
+
return this.replace(pattern, r ?? '');
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/** Uint8Array Interface Extensions */
|
|
289
|
+
Uint8Array.prototype.toArray = function (): number[] {
|
|
290
|
+
return Array.from(this);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export default global;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import './exts.js';
|
|
2
|
+
|
|
3
|
+
export * from './canonicalization.js';
|
|
4
|
+
export * from './constants.js';
|
|
5
|
+
export * from './errors.js';
|
|
6
|
+
export * from './interfaces.js';
|
|
7
|
+
export * from './logger.js';
|
|
8
|
+
export * from './patch.js';
|
|
9
|
+
export * from './types.js';
|
|
10
|
+
|
|
11
|
+
export * from './exts.js';
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
export type JsonPatch = Array<PatchOperation>;
|
|
2
|
+
export type PatchOpCode = 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test' | string;
|
|
3
|
+
/**
|
|
4
|
+
* A JSON Patch operation, as defined in {@link https://datatracker.ietf.org/doc/html/rfc6902 | RFC 6902}.
|
|
5
|
+
*/
|
|
6
|
+
export interface PatchOperation {
|
|
7
|
+
op: PatchOpCode;
|
|
8
|
+
path: string;
|
|
9
|
+
value?: any; // Required for add, replace, test
|
|
10
|
+
from?: string; // Required for move, copy
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The unsigned payload object containing instructions for how to update a
|
|
15
|
+
* did:btcr2 DID Document. Once signed, it becomes a
|
|
16
|
+
* {@link DidUpdateInvocation | DID Update Invocation}
|
|
17
|
+
*
|
|
18
|
+
* DID BTCR2
|
|
19
|
+
* {@link https://dcdpr.github.io/did-btcr2/#construct-did-update-payload | 4.3.1 Construct DID Update Payload}.
|
|
20
|
+
*
|
|
21
|
+
* Found in DID BTCR2 Specification {@link https://dcdpr.github.io/did-btcr2/#dereference-root-capability-identifier | Section 9.4.2}
|
|
22
|
+
* @example
|
|
23
|
+
* ```
|
|
24
|
+
* {
|
|
25
|
+
* "@context": [
|
|
26
|
+
* "https://w3id.org/zcap/v1",
|
|
27
|
+
* "https://w3id.org/security/data-integrity/v2",
|
|
28
|
+
* "https://w3id.org/json-ld-patch/v1"
|
|
29
|
+
* ],
|
|
30
|
+
* "patch": [
|
|
31
|
+
* {
|
|
32
|
+
* "op": "add",
|
|
33
|
+
* "path": "/service/4",
|
|
34
|
+
* "value": {
|
|
35
|
+
* "id": "#linked-domain",
|
|
36
|
+
* "type": "LinkedDomains",
|
|
37
|
+
* "serviceEndpoint": "https://contact-me.com"
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* ],
|
|
41
|
+
* "proof":{
|
|
42
|
+
* "type": "DataIntegrityProof,
|
|
43
|
+
* "cryptosuite": "schnorr-secp256k1-jcs-2025,
|
|
44
|
+
* "verificationMethod": "did:btcr2:k1qqpuwwde82nennsavvf0lqfnlvx7frrgzs57lchr02q8mz49qzaaxmqphnvcx#initialKey,
|
|
45
|
+
* "invocationTarget": "did:btcr2:k1qqpuwwde82nennsavvf0lqfnlvx7frrgzs57lchr02q8mz49qzaaxmqphnvcx,
|
|
46
|
+
* "capability": "urn:zcap:root:did%3Abtc1%3Ak1qqpuwwde82nennsavvf0lqfnlvx7frrgzs57lchr02q8mz49qzaaxmqphnvcx,
|
|
47
|
+
* "capabilityAction": "Write,
|
|
48
|
+
* "proofPurpose": "assertionMethod,
|
|
49
|
+
* "proofValue": "z381yXYmxU8NudZ4HXY56DfMN6zfD8syvWcRXzT9xD9uYoQToo8QsXD7ahM3gXTzuay5WJbqTswt2BKaGWYn2hHhVFKJLXaD
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export interface DidUpdatePayload {
|
|
55
|
+
/**
|
|
56
|
+
* JSON-LD context URIs for interpreting this payload, including contexts
|
|
57
|
+
* for ZCAP (capabilities), Data Integrity proofs, and JSON-LD patch ops.
|
|
58
|
+
*/
|
|
59
|
+
'@context': string[];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A JSON Patch (or JSON-LD Patch) object defining the mutations to apply to
|
|
63
|
+
* the DID Document. Applying this patch to the current DID Document yields
|
|
64
|
+
* the new DID Document (which must remain valid per DID Core spec).
|
|
65
|
+
*/
|
|
66
|
+
patch: JsonPatch;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The multihash of the current (source) DID Document, encoded as a multibase
|
|
70
|
+
* base58-btc string. This is a SHA-256 hash of the canonicalized source DID
|
|
71
|
+
* Document, used to ensure the patch is applied to the correct document state.
|
|
72
|
+
*/
|
|
73
|
+
sourceHash: string;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The multihash of the updated (target) DID Document, encoded as multibase
|
|
77
|
+
* base58-btc. This is the SHA-256 hash of the canonicalized
|
|
78
|
+
* DID Document after applying the patch, used to verify the update result.
|
|
79
|
+
*/
|
|
80
|
+
targetHash: string;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The version number of the DID Document after this update.
|
|
84
|
+
* It is equal to the previous document version + 1.
|
|
85
|
+
*/
|
|
86
|
+
targetVersionId: number;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A proof object (Data Integrity proof) that authorizes this update.
|
|
90
|
+
* It is a JSON-LD proof indicating a capability invocation on the DID's
|
|
91
|
+
* root capability, typically signed with the DID's verification key (using
|
|
92
|
+
* Schnorr secp256k1 in did:btcr2).
|
|
93
|
+
*/
|
|
94
|
+
proof?: Proof;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* An extension of {@link DidUpdatePayload | DID Update Payload} containing a
|
|
99
|
+
* Data Integrity proof that authorizes the update. Once signed, the spec calls
|
|
100
|
+
* this an 'invoked DID Update Payload' or 'didUpdateInvocation'.
|
|
101
|
+
*
|
|
102
|
+
* DID BTCR2
|
|
103
|
+
* {@link https://dcdpr.github.io/did-btcr2/#invoke-did-update-payload | 4.3.2 Invoke DID Update Payload}
|
|
104
|
+
* and
|
|
105
|
+
* {@link https://dcdpr.github.io/did-btcr2/#root-didbtc1-update-capabilities | 9.4 Root did:btcr2 Update Capabilities}.
|
|
106
|
+
*/
|
|
107
|
+
export interface DidUpdateInvocation extends DidUpdatePayload {
|
|
108
|
+
proof: Proof;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Proof is the Data Integrity proof (ZCAP-LD style) added to a did:btcr2 DID
|
|
113
|
+
* Update Payload.
|
|
114
|
+
*
|
|
115
|
+
* Verifiable Credential Data Integrity
|
|
116
|
+
* {@link https://w3c.github.io/vc-data-integrity/#proofs | 2.1 Proofs}.
|
|
117
|
+
*
|
|
118
|
+
* DID BTCR2
|
|
119
|
+
* {@link https://dcdpr.github.io/did-btcr2/#invoke-did-update-payload | 4.3.2 Invoke DID Update Payload}.
|
|
120
|
+
*/
|
|
121
|
+
export interface Proof extends ProofOptions {
|
|
122
|
+
/**
|
|
123
|
+
* The cryptographic signature value. The exact property name may be defined
|
|
124
|
+
* by the cryptosuite (for instance, `proofValue` for a raw signature) and
|
|
125
|
+
* contains the actual signature bytes in an encoded form.
|
|
126
|
+
*/
|
|
127
|
+
proofValue: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Proof Options used when adding a Data Integrity proof (ZCAP-LD style)
|
|
132
|
+
* to a did:btcr2 DID Update Payload.
|
|
133
|
+
*
|
|
134
|
+
* Verifiable Credential Data Integrity
|
|
135
|
+
* {@link https://w3c.github.io/vc-data-integrity/#proofs | 2.1 Proofs}.
|
|
136
|
+
*
|
|
137
|
+
* DID BTCR2
|
|
138
|
+
* {@link https://dcdpr.github.io/did-btcr2/#invoke-did-update-payload | 4.3.2 Invoke DID Update Payload}.
|
|
139
|
+
*/
|
|
140
|
+
export interface ProofOptions {
|
|
141
|
+
/**
|
|
142
|
+
* The proof type—per the spec’s example, "DataIntegrityProof".
|
|
143
|
+
*/
|
|
144
|
+
type: string;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The cryptographic suite used, e.g. "schnorr-secp256k1-jcs-2025".
|
|
148
|
+
*/
|
|
149
|
+
cryptosuite: string;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* DID URL of the key invoking the capability, i.e. the DID
|
|
153
|
+
* Document's verificationMethod.id used to sign this update.
|
|
154
|
+
*/
|
|
155
|
+
verificationMethod: string;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The purpose of the proof, which the spec sets to "capabilityInvocation".
|
|
159
|
+
*/
|
|
160
|
+
proofPurpose: string;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The root capability being invoked. In did:btcr2, this is typically
|
|
164
|
+
* `urn:zcap:root:<urlencoded-did>` (see Section 9.4.1).
|
|
165
|
+
*/
|
|
166
|
+
capability?: string;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The action performed under the capability—set to "Write" in the spec
|
|
170
|
+
* for DID document updates.
|
|
171
|
+
*/
|
|
172
|
+
capabilityAction?: string;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* (Optional) Some cryptosuites or proofs may include a timestamp, domain,
|
|
176
|
+
* or challenge. Although not explicitly required in the doc's steps, they
|
|
177
|
+
* often appear in Data Integrity proofs and may be included as needed.
|
|
178
|
+
*/
|
|
179
|
+
created?: string;
|
|
180
|
+
domain?: string;
|
|
181
|
+
challenge?: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* A JSON object that maps did:btcr2 identifiers to the CID of the corresponding
|
|
186
|
+
* DID Update Payload.
|
|
187
|
+
*
|
|
188
|
+
* DID BTCR2
|
|
189
|
+
* {@link https://dcdpr.github.io/did-btcr2/#cidaggregate-beacon | 5.2 CIDAggregate Beacons}.
|
|
190
|
+
*/
|
|
191
|
+
export interface DidUpdateBundle {
|
|
192
|
+
/**
|
|
193
|
+
* The keys are did:btcr2 identifiers as strings. The values are
|
|
194
|
+
* IPFS CIDs (or other CAS IDs) referencing the actual DID Update Payload.
|
|
195
|
+
*/
|
|
196
|
+
[didBtc1Identifier: string]: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* A container for out-of-band data the resolver may need. This includes the
|
|
201
|
+
* initial DID document if it isn't stored in IPFS, plus references for each
|
|
202
|
+
* on-chain Beacon signal.
|
|
203
|
+
*
|
|
204
|
+
* DID BTCR2
|
|
205
|
+
* {@link https://dcdpr.github.io/did-btcr2/#sidecar-initial-document-validation | 4.2.1.2.1 Sidecar Initial Document Validation},
|
|
206
|
+
* {@link https://dcdpr.github.io/did-btcr2/#resolve-target-document | 4.2.2 Resolve Target Document},
|
|
207
|
+
* {@link https://dcdpr.github.io/did-btcr2/#traverse-blockchain-history | 4.2.2.2 Traverse Blockchain History},
|
|
208
|
+
* {@link https://dcdpr.github.io/did-btcr2/#find-next-signals | 4.2.2.3 Find Next Signals}.
|
|
209
|
+
*/
|
|
210
|
+
export interface SidecarData {
|
|
211
|
+
/**
|
|
212
|
+
* The initial DID Document for an externally created did:btcr2,
|
|
213
|
+
* if not fetched from IPFS or another CAS.
|
|
214
|
+
*/
|
|
215
|
+
initialDocument?: Record<string, any>; // or a typed DIDDocument from W3C DID Core
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* A map from Bitcoin transaction IDs to the sidecar info about that signal.
|
|
219
|
+
* Each signal might provide a single DID Update Payload, or (for aggregator beacons)
|
|
220
|
+
* a bundle or proofs.
|
|
221
|
+
*/
|
|
222
|
+
signalsMetadata: {
|
|
223
|
+
[txid: string]: SignalSidecarData;
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Sidecar data for a specific Beacon Signal. Different Beacon types store different fields.
|
|
229
|
+
* - SingletonBeacon might just store one `updatePayload`.
|
|
230
|
+
* - CIDAggregateBeacon might store `updateBundle` + an `updatePayload`.
|
|
231
|
+
* - SMTAggregateBeacon might store `updatePayload` + a `smtProof`.
|
|
232
|
+
*/
|
|
233
|
+
export interface SignalSidecarData {
|
|
234
|
+
updatePayload?: DidUpdateInvocation; // or DidUpdatePayload if not yet invoked
|
|
235
|
+
updateBundle?: DidUpdateBundle; // for CIDAggregateBeacon
|
|
236
|
+
/**
|
|
237
|
+
* For SMTAggregateBeacon, a Merkle proof that the `updatePayload`
|
|
238
|
+
* is included (or not included) in the aggregator's Sparse Merkle Tree.
|
|
239
|
+
*/
|
|
240
|
+
smtProof?: SmtProof;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* A placeholder for the actual Sparse Merkle Tree inclusion/non-inclusion proof.
|
|
245
|
+
*
|
|
246
|
+
* DID BTCR2
|
|
247
|
+
* {@link https://dcdpr.github.io/did-btcr2/#smtaggregate-beacon | 5.3 SMTAggregate Beacon}.
|
|
248
|
+
*/
|
|
249
|
+
export interface SmtProof {
|
|
250
|
+
// Implementation-specific structure for SMT proofs, e.g.:
|
|
251
|
+
siblingHashes: string[];
|
|
252
|
+
leafIndex?: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* The known Beacon types from the spec.
|
|
257
|
+
*/
|
|
258
|
+
export type BeaconType =
|
|
259
|
+
| 'SingletonBeacon'
|
|
260
|
+
| 'CIDAggregateBeacon'
|
|
261
|
+
| 'SMTAggregateBeacon';
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Represents a transaction discovered on the Bitcoin blockchain that
|
|
265
|
+
* spends from a Beacon address, thus announcing DID updates.
|
|
266
|
+
*
|
|
267
|
+
* DID BTCR2
|
|
268
|
+
* {@link https://dcdpr.github.io/did-btcr2/#find-next-signals | 4.2.2.3 Find Next Signals}
|
|
269
|
+
* and
|
|
270
|
+
* {@link https://dcdpr.github.io/did-btcr2/#process-beacon-signals | 4.2.2.4 Process Beacon Signals}.
|
|
271
|
+
*/
|
|
272
|
+
export interface BeaconSignal {
|
|
273
|
+
/**
|
|
274
|
+
* The DID Document's `service` ID of the Beacon that produced this signal, e.g. "#cidAggregateBeacon".
|
|
275
|
+
*/
|
|
276
|
+
beaconId: string;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The type of Beacon, e.g. "SingletonBeacon".
|
|
280
|
+
*/
|
|
281
|
+
beaconType: BeaconType;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The Bitcoin transaction that is the actual on-chain Beacon Signal.
|
|
285
|
+
* Typically you'd store a minimal subset or a reference/ID for real usage.
|
|
286
|
+
*/
|
|
287
|
+
tx: any;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* A ZCAP-LD root capability object that authorizes updates for a particular did:btcr2.
|
|
292
|
+
*
|
|
293
|
+
* DID BTCR2
|
|
294
|
+
* {@link https://dcdpr.github.io/did-btcr2/#derive-root-capability-from-didbtc1-identifier | 9.4.1 Derive Root Capability from did:btcr2 Identifier}.
|
|
295
|
+
*
|
|
296
|
+
* @example Found in DID BTCR2 Specification Section 9.4.1
|
|
297
|
+
* ```
|
|
298
|
+
* {
|
|
299
|
+
* "@context": "https://w3id.org/zcap/v1",
|
|
300
|
+
* "id": "urn:zcap:root:did%3Abtc1%3Ak1qq...",
|
|
301
|
+
* "controller": "did:btcr2:k1qq...",
|
|
302
|
+
* "invocationTarget": "did:btcr2:k1qq..."
|
|
303
|
+
* }
|
|
304
|
+
* ```
|
|
305
|
+
*/
|
|
306
|
+
export interface DidBtc1RootCapability {
|
|
307
|
+
'@context': string | string[]; // e.g. "https://w3id.org/zcap/v1"
|
|
308
|
+
id: string; // e.g. "urn:zcap:root:<urlencoded-did>"
|
|
309
|
+
controller: string; // the DID
|
|
310
|
+
invocationTarget: string; // same as DID
|
|
311
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export enum Env {
|
|
2
|
+
Development = 'development',
|
|
3
|
+
Production = 'production',
|
|
4
|
+
Test = 'test',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type Level = 'debug' | 'error' | 'info' | 'log' | 'warn' | 'security';
|
|
8
|
+
|
|
9
|
+
export const NODE_ENV: Env =
|
|
10
|
+
(typeof process !== 'undefined' && (process.env?.NODE_ENV as Env)) ||
|
|
11
|
+
Env.Development;
|
|
12
|
+
|
|
13
|
+
// Browser-friendly level "styles" using CSS in console
|
|
14
|
+
const LEVEL_CSS: Record<Level, string> = {
|
|
15
|
+
debug : 'color: #16a34a; font-weight: 600;',
|
|
16
|
+
error : 'color: #ef4444; font-weight: 600;',
|
|
17
|
+
info : 'color: #3b82f6; font-weight: 600;',
|
|
18
|
+
log : 'color: #6b7280;',
|
|
19
|
+
warn : 'color: #eab308; font-weight: 600;',
|
|
20
|
+
security : 'color: #a855f7; font-weight: 700;',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const LOG_LEVELS: Record<Env, Level[]> = {
|
|
24
|
+
development : ['debug', 'info', 'log', 'warn', 'security'],
|
|
25
|
+
test : ['log', 'info', 'error'],
|
|
26
|
+
production : ['error'],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// eslint-disable-next-line no-undef
|
|
30
|
+
const LEVEL_METHODS: Record<Level, keyof Console> = {
|
|
31
|
+
debug : 'debug',
|
|
32
|
+
error : 'error',
|
|
33
|
+
info : 'info',
|
|
34
|
+
log : 'log',
|
|
35
|
+
warn : 'warn',
|
|
36
|
+
security : 'warn',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export class Logger {
|
|
40
|
+
private levels: Level[];
|
|
41
|
+
private namespace?: string;
|
|
42
|
+
|
|
43
|
+
constructor(namespace?: string) {
|
|
44
|
+
this.levels = LOG_LEVELS[NODE_ENV] || [];
|
|
45
|
+
this.namespace = namespace ?? 'did-btcr2-js';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private _log(level: Level, message?: unknown, ...args: unknown[]) {
|
|
49
|
+
if (!this.levels.includes(level)) return;
|
|
50
|
+
|
|
51
|
+
const ts = new Date().toISOString();
|
|
52
|
+
const ns = this.namespace ? `[${this.namespace}]` : '';
|
|
53
|
+
const method = LEVEL_METHODS[level];
|
|
54
|
+
|
|
55
|
+
// %c applies CSS to the next string token
|
|
56
|
+
(console[method] as (...a: any[]) => void)(
|
|
57
|
+
'%c' + ts + '%c ' + ns + ' ' + level + ':%c ' + String(message ?? ''),
|
|
58
|
+
'color:#9ca3af;', // timestamp (gray)
|
|
59
|
+
'color:#9ca3af;', // namespace (same gray)
|
|
60
|
+
LEVEL_CSS[level],
|
|
61
|
+
...args,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public debug(m?: unknown, ...a: unknown[]) { this._log('debug', m, ...a); return this; }
|
|
66
|
+
public error(m?: unknown, ...a: unknown[]) { this._log('error', m, ...a); return this; }
|
|
67
|
+
public info(m?: unknown, ...a: unknown[]) { this._log('info', m, ...a); return this; }
|
|
68
|
+
public warn(m?: unknown, ...a: unknown[]) { this._log('warn', m, ...a); return this; }
|
|
69
|
+
public security(m?: unknown, ...a: unknown[]) { this._log('security', m, ...a); return this; }
|
|
70
|
+
public log(m?: unknown, ...a: unknown[]) { this._log('log', m, ...a); return this; }
|
|
71
|
+
public newline() { console.log(); return this; }
|
|
72
|
+
|
|
73
|
+
// Static convenience API
|
|
74
|
+
public static debug(m?: unknown, ...a: unknown[]) { new Logger().debug(m, ...a); }
|
|
75
|
+
public static error(m?: unknown, ...a: unknown[]) { new Logger().error(m, ...a); }
|
|
76
|
+
public static info(m?: unknown, ...a: unknown[]) { new Logger().info(m, ...a); }
|
|
77
|
+
public static warn(m?: unknown, ...a: unknown[]) { new Logger().warn(m, ...a); }
|
|
78
|
+
public static security(m?: unknown, ...a: unknown[]) { new Logger().security(m, ...a); }
|
|
79
|
+
public static log(m?: unknown, ...a: unknown[]) { new Logger().log(m, ...a); }
|
|
80
|
+
public static newline() { new Logger().newline(); }
|
|
81
|
+
|
|
82
|
+
// Keep signature but avoid Node 'path' – do a lightweight parse
|
|
83
|
+
// (unused by default; safe if you later enable it)
|
|
84
|
+
private static getCallerLocation(): string {
|
|
85
|
+
const stack = new Error().stack?.split('\n');
|
|
86
|
+
const line = stack?.[3] || stack?.[2] || '';
|
|
87
|
+
const match = line.match(/\((.*):(\d+):(\d+)\)/) || line.match(/at (.*):(\d+):(\d+)/);
|
|
88
|
+
if (!match) return '';
|
|
89
|
+
const file = (match[1] || '').split(/[\\/]/).pop() || '';
|
|
90
|
+
return `${file}:${match[2]}`;
|
|
91
|
+
}
|
|
92
|
+
}
|