@did-btcr2/common 3.0.0 → 3.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/src/interfaces.ts CHANGED
@@ -9,303 +9,3 @@ export interface PatchOperation {
9
9
  value?: unknown; // Required for add, replace, test
10
10
  from?: string; // Required for move, copy
11
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%3Abtcr2%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-didbtcr2-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
- [didbtcr2Identifier: 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-didbtcr2-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%3Abtcr2%3Ak1qq...",
301
- * "controller": "did:btcr2:k1qq...",
302
- * "invocationTarget": "did:btcr2:k1qq..."
303
- * }
304
- * ```
305
- */
306
- export interface DidBtcr2RootCapability {
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
- }
package/src/types.ts CHANGED
@@ -3,6 +3,7 @@ import { HDKey } from '@scure/bip32';
3
3
  /* Crypto Types */
4
4
  export type Bytes = Uint8Array;
5
5
  export type Hex = Bytes | string;
6
+ export type HexString = string;
6
7
  export type SignatureHex = Hex;
7
8
  export type HashHex = Hex;
8
9
 
package/src/utils/date.ts CHANGED
@@ -9,7 +9,7 @@ export class DateUtils {
9
9
  * @param {Date} [date=new Date()] - The date to format.
10
10
  * @returns {string} The formatted date string.
11
11
  */
12
- static getUTCDateTime(date: Date = new Date()): string {
12
+ static toISOStringNonFractional(date: Date = new Date()): string {
13
13
  const time = date.getTime();
14
14
  if (Number.isNaN(time)) {
15
15
  throw new Error(`Invalid date: ${date}`);
@@ -29,4 +29,102 @@ export class DateUtils {
29
29
  }
30
30
  return Math.floor(date.getTime() / 1000);
31
31
  }
32
+
33
+ /**
34
+ * Validate if a string is a valid UTC date string.
35
+ * @param {string} dateString - The date string to validate.
36
+ * @returns {boolean} True if valid, otherwise false.
37
+ * @throws {Error} If the date string is invalid.
38
+ */
39
+ static dateStringToTimestamp(dateString: string): Date {
40
+ const date = new Date(dateString);
41
+ if (Number.isNaN(date.getTime())) {
42
+ return new Date(0);
43
+ }
44
+ return date;
45
+ }
46
+
47
+ /**
48
+ * Convert a blocktime (Unix timestamp in seconds) to a Date object.
49
+ * @param {number} blocktime - The blocktime in seconds.
50
+ * @returns {Date} The corresponding Date object.
51
+ */
52
+ static blocktimeToTimestamp(blocktime: number): Date {
53
+ return new Date(blocktime * 1000);
54
+ }
55
+
56
+ /**
57
+ * Validates an XMLSCHEMA11-2 dateTime string.
58
+ * Format: [-]YYYY-MM-DDThh:mm:ss[.fractional][Z|(+|-)hh:mm]
59
+ *
60
+ * @see https://www.w3.org/TR/xmlschema11-2/#dateTime
61
+ */
62
+ static isValidXsdDateTime(value?: string): boolean {
63
+ // Empty or undefined value is not a valid dateTime
64
+ if(!value) return false;
65
+
66
+ // Regex for XML Schema dateTime:
67
+ // - Optional leading minus for BCE years
68
+ // - Year: 4+ digits
69
+ // - Month: 01-12
70
+ // - Day: 01-31 (further validated below)
71
+ // - T separator
72
+ // - Hour: 00-23 (24:00:00 is valid end-of-day per spec)
73
+ // - Minute: 00-59
74
+ // - Second: 00-59 (with optional fractional part)
75
+ // - Timezone: Z or (+|-)hh:mm
76
+ const xsdDateTimeRegex =
77
+ /^-?(\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;
78
+
79
+ const match = value.match(xsdDateTimeRegex);
80
+ if (!match) return false;
81
+
82
+ const [, y, m, d, H, M, S, , tz] = match;
83
+
84
+ const year = parseInt(y, 10);
85
+ const month = parseInt(m, 10);
86
+ const day = parseInt(d, 10);
87
+ const hour = parseInt(H, 10);
88
+ const minute = parseInt(M, 10);
89
+ const second = parseInt(S, 10);
90
+
91
+ // Year 0000 is not valid in XML Schema (no year zero)
92
+ if (year === 0) return false;
93
+
94
+ // Month: 1-12
95
+ if (month < 1 || month > 12) return false;
96
+
97
+ // Day: validate against month (and leap year for February)
98
+ const daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
99
+ const isLeapYear = (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
100
+ const maxDay = month === 2 && isLeapYear ? 29 : daysInMonth[month - 1];
101
+ if (day < 1 || day > maxDay) return false;
102
+
103
+ // Hour: 00-23, or 24:00:00 exactly (end-of-day)
104
+ if (hour === 24) {
105
+ if (minute !== 0 || second !== 0) return false;
106
+ } else if (hour > 23) {
107
+ return false;
108
+ }
109
+
110
+ // Minute: 00-59
111
+ if (minute > 59) return false;
112
+
113
+ // Second: 00-59 (leap second 60 is debatable; XML Schema doesn't explicitly allow it)
114
+ if (second > 59) return false;
115
+
116
+ // Validate timezone offset if present
117
+ if (tz && tz !== 'Z') {
118
+ const tzMatch = tz.match(/^[+-](\d{2}):(\d{2})$/);
119
+ if (tzMatch) {
120
+ const tzHour = parseInt(tzMatch[1], 10);
121
+ const tzMin = parseInt(tzMatch[2], 10);
122
+ if (tzHour > 14 || (tzHour === 14 && tzMin !== 0) || tzMin > 59) {
123
+ return false;
124
+ }
125
+ }
126
+ }
127
+
128
+ return true;
129
+ }
32
130
  }