@did-btcr2/common 2.2.2 → 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.
Files changed (94) hide show
  1. package/dist/cjs/canonicalization.js +68 -56
  2. package/dist/cjs/canonicalization.js.map +1 -1
  3. package/dist/cjs/constants.js +17 -12
  4. package/dist/cjs/constants.js.map +1 -1
  5. package/dist/cjs/errors.js +20 -3
  6. package/dist/cjs/errors.js.map +1 -1
  7. package/dist/cjs/index.js +5 -3
  8. package/dist/cjs/index.js.map +1 -1
  9. package/dist/cjs/json-patch.js +98 -0
  10. package/dist/cjs/json-patch.js.map +1 -0
  11. package/dist/cjs/logger.js +46 -12
  12. package/dist/cjs/logger.js.map +1 -1
  13. package/dist/cjs/types.js.map +1 -1
  14. package/dist/cjs/utils/date.js +123 -0
  15. package/dist/cjs/utils/date.js.map +1 -0
  16. package/dist/cjs/utils/json.js +280 -0
  17. package/dist/cjs/utils/json.js.map +1 -0
  18. package/dist/cjs/utils/set.js +23 -0
  19. package/dist/cjs/utils/set.js.map +1 -0
  20. package/dist/cjs/utils/string.js +55 -0
  21. package/dist/cjs/utils/string.js.map +1 -0
  22. package/dist/esm/canonicalization.js +68 -56
  23. package/dist/esm/canonicalization.js.map +1 -1
  24. package/dist/esm/constants.js +17 -12
  25. package/dist/esm/constants.js.map +1 -1
  26. package/dist/esm/errors.js +20 -3
  27. package/dist/esm/errors.js.map +1 -1
  28. package/dist/esm/index.js +5 -3
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/json-patch.js +98 -0
  31. package/dist/esm/json-patch.js.map +1 -0
  32. package/dist/esm/logger.js +46 -12
  33. package/dist/esm/logger.js.map +1 -1
  34. package/dist/esm/types.js.map +1 -1
  35. package/dist/esm/utils/date.js +123 -0
  36. package/dist/esm/utils/date.js.map +1 -0
  37. package/dist/esm/utils/json.js +280 -0
  38. package/dist/esm/utils/json.js.map +1 -0
  39. package/dist/esm/utils/set.js +23 -0
  40. package/dist/esm/utils/set.js.map +1 -0
  41. package/dist/esm/utils/string.js +55 -0
  42. package/dist/esm/utils/string.js.map +1 -0
  43. package/dist/types/canonicalization.d.ts +40 -31
  44. package/dist/types/canonicalization.d.ts.map +1 -1
  45. package/dist/types/constants.d.ts +6 -9
  46. package/dist/types/constants.d.ts.map +1 -1
  47. package/dist/types/errors.d.ts +14 -3
  48. package/dist/types/errors.d.ts.map +1 -1
  49. package/dist/types/index.d.ts +5 -3
  50. package/dist/types/index.d.ts.map +1 -1
  51. package/dist/types/interfaces.d.ts +2 -273
  52. package/dist/types/interfaces.d.ts.map +1 -1
  53. package/dist/types/json-patch.d.ts +47 -0
  54. package/dist/types/json-patch.d.ts.map +1 -0
  55. package/dist/types/logger.d.ts +31 -8
  56. package/dist/types/logger.d.ts.map +1 -1
  57. package/dist/types/types.d.ts +12 -4
  58. package/dist/types/types.d.ts.map +1 -1
  59. package/dist/types/utils/date.d.ts +39 -0
  60. package/dist/types/utils/date.d.ts.map +1 -0
  61. package/dist/types/utils/json.d.ts +89 -0
  62. package/dist/types/utils/json.d.ts.map +1 -0
  63. package/dist/types/utils/set.d.ts +14 -0
  64. package/dist/types/utils/set.d.ts.map +1 -0
  65. package/dist/types/utils/string.d.ts +39 -0
  66. package/dist/types/utils/string.d.ts.map +1 -0
  67. package/package.json +3 -4
  68. package/src/canonicalization.ts +81 -64
  69. package/src/constants.ts +19 -13
  70. package/src/errors.ts +25 -3
  71. package/src/index.ts +5 -5
  72. package/src/interfaces.ts +2 -302
  73. package/src/json-patch.ts +103 -0
  74. package/src/logger.ts +59 -27
  75. package/src/types.ts +12 -6
  76. package/src/utils/date.ts +130 -0
  77. package/src/utils/json.ts +315 -0
  78. package/src/utils/set.ts +23 -0
  79. package/src/utils/string.ts +59 -0
  80. package/dist/cjs/exts.js +0 -189
  81. package/dist/cjs/exts.js.map +0 -1
  82. package/dist/cjs/patch.js +0 -163
  83. package/dist/cjs/patch.js.map +0 -1
  84. package/dist/esm/exts.js +0 -189
  85. package/dist/esm/exts.js.map +0 -1
  86. package/dist/esm/patch.js +0 -163
  87. package/dist/esm/patch.js.map +0 -1
  88. package/dist/types/exts.d.ts +0 -90
  89. package/dist/types/exts.d.ts.map +0 -1
  90. package/dist/types/patch.d.ts +0 -63
  91. package/dist/types/patch.d.ts.map +0 -1
  92. package/src/exts.ts +0 -310
  93. package/src/patch.ts +0 -181
  94. package/src/rdf-canonize.d.ts +0 -6
package/src/constants.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { sha256 } from '@noble/hashes/sha2';
2
+ import { bytesToHex } from '@noble/hashes/utils';
2
3
  import { Bytes, HashHex } from './types.js';
3
4
 
4
- export const ID_PLACEHOLDER_VALUE = 'did:btcr2:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
5
5
  export const OP_RETURN = 0x6a;
6
6
  export const OP_PUSH32 = 0x20;
7
7
  export const VALID_HRP = ['k', 'x'];
@@ -9,32 +9,38 @@ export const MULTIBASE_URI_PREFIX = 'urn:mb:';
9
9
  export const INITIAL_BLOCK_REWARD = 50;
10
10
  export const HALVING_INTERVAL = 150;
11
11
  export const COINBASE_MATURITY_DELAY = 100;
12
- export const POLAR_BOB_CLIENT_CONFIG = {
12
+ export const DEFAULT_POLAR_CONFIG = {
13
13
  username : 'polaruser',
14
14
  password : 'polarpass',
15
15
  host : 'http://127.0.0.1:18443',
16
16
  allowDefaultWallet : true,
17
17
  version : '28.1.0'
18
18
  };
19
- export const POLAR_ALICE_CLIENT_CONFIG = {
20
- username : 'polaruser',
21
- password : 'polarpass',
22
- host : 'http://127.0.0.1:18444',
23
- allowDefaultWallet : true,
24
- version : '28.1.0'
25
- };
26
19
  export const DEFAULT_REST_CONFIG = { host: 'http://localhost:3000' };
27
- export const DEFAULT_RPC_CONFIG = POLAR_BOB_CLIENT_CONFIG;
20
+ export const DEFAULT_RPC_CONFIG = DEFAULT_POLAR_CONFIG;
28
21
  export const DEFAULT_BLOCK_CONFIRMATIONS = 7;
29
22
 
23
+ /**
24
+ * Load a default RPC config, allowing environment overrides to avoid hard-coding credentials/hosts in bundles.
25
+ * @returns {typeof DEFAULT_POLAR_CONFIG} The RPC config.
26
+ */
27
+ export function getDefaultRpcConfig(): typeof DEFAULT_POLAR_CONFIG {
28
+ return {
29
+ ...DEFAULT_POLAR_CONFIG,
30
+ host : process.env.BTCR2_RPC_HOST ?? DEFAULT_POLAR_CONFIG.host,
31
+ username : process.env.BTCR2_RPC_USER ?? DEFAULT_POLAR_CONFIG.username,
32
+ password : process.env.BTCR2_RPC_PASS ?? DEFAULT_POLAR_CONFIG.password,
33
+ };
34
+ }
35
+
30
36
  // Fixed public key header bytes per the Data Integrity BIP340 Cryptosuite spec: [0xe7, 0x01] / [231, 1]
31
37
  export const BIP340_PUBLIC_KEY_MULTIBASE_PREFIX: Bytes = new Uint8Array([0xe7, 0x01]);
32
38
  // Hash of the BIP-340 Multikey prefix
33
- export const BIP340_PUBLIC_KEY_MULTIBASE_PREFIX_HASH: HashHex = Buffer.from(sha256(BIP340_PUBLIC_KEY_MULTIBASE_PREFIX)).toString('hex');
39
+ export const BIP340_PUBLIC_KEY_MULTIBASE_PREFIX_HASH: HashHex = bytesToHex(sha256(BIP340_PUBLIC_KEY_MULTIBASE_PREFIX));
34
40
  // Fixed secret key header bytes per the Data Integrity BIP340 Cryptosuite spec: [0x81, 0x26] / [129, 38]
35
41
  export const BIP340_SECRET_KEY_MULTIBASE_PREFIX: Bytes = new Uint8Array([0x81, 0x26]);
36
42
  // Hash of the BIP-340 Multikey prefix
37
- export const BIP340_SECRET_KEY_MULTIBASE_PREFIX_HASH: HashHex = Buffer.from(sha256(BIP340_SECRET_KEY_MULTIBASE_PREFIX)).toString('hex');
43
+ export const BIP340_SECRET_KEY_MULTIBASE_PREFIX_HASH: HashHex = bytesToHex(sha256(BIP340_SECRET_KEY_MULTIBASE_PREFIX));
38
44
  // curve's field size
39
45
  export const B256 = 2n ** 256n;
40
46
  // curve's field prime
@@ -118,4 +124,4 @@ export const BTCR2_DID_UPDATE_PAYLOAD_CONTEXT = [
118
124
  CONTEXT_URL_MAP.w3c.security.v2,
119
125
  CONTEXT_URL_MAP.w3c.zcap.v1,
120
126
  CONTEXT_URL_MAP.w3c.jsonldpatch.v1,
121
- ];
127
+ ];
package/src/errors.ts CHANGED
@@ -75,8 +75,11 @@ export enum MethodErrorCode {
75
75
  /** The sidecar data in the DID Update Payload was invalid. */
76
76
  INVALID_SIDECAR_DATA = 'INVALID_SIDECAR_DATA',
77
77
 
78
- /** The proof is missing or has a malformed challenge field. */
79
- INVALID_CHALLENGE_ERROR = 'INVALID_CHALLENGE_ERROR',
78
+ /** The update data required for resolution is missing. */
79
+ MISSING_UPDATE_DATA = 'MISSING_UPDATE_DATA',
80
+
81
+ /** The update is missing or has a malformed field(s). */
82
+ INVALID_UPDATE = 'INVALID_UPDATE',
80
83
 
81
84
  /** The proof is missing or has a malformed domain field. */
82
85
  INVALID_DOMAIN_ERROR = 'INVALID_DOMAIN_ERROR'
@@ -106,7 +109,8 @@ export const {
106
109
  VERIFICATION_METHOD_ERROR,
107
110
  LATE_PUBLISHING_ERROR,
108
111
  INVALID_SIDECAR_DATA,
109
- INVALID_CHALLENGE_ERROR,
112
+ MISSING_UPDATE_DATA,
113
+ INVALID_UPDATE,
110
114
  INVALID_DOMAIN_ERROR
111
115
  } = MethodErrorCode;
112
116
 
@@ -166,6 +170,18 @@ export class MethodError extends DidMethodError {
166
170
  }
167
171
  }
168
172
 
173
+ export class IdentifierError extends DidMethodError {
174
+ constructor(message: string, type: string = 'IdentifierError', data?: Record<string, any>) {
175
+ super(message, { type, name: type, data });
176
+ }
177
+ }
178
+
179
+ export class UpdateError extends DidMethodError {
180
+ constructor(message: string, type: string = 'UpdateError', data?: Record<string, any>) {
181
+ super(message, { type, name: type, data });
182
+ }
183
+ }
184
+
169
185
  export class ResolveError extends DidMethodError {
170
186
  constructor(message: string, type: string = 'ResolveError', data?: Record<string, any>) {
171
187
  super(message, { type, name: 'ResolveError', data });
@@ -190,6 +206,12 @@ export class CryptosuiteError extends DidMethodError {
190
206
  }
191
207
  }
192
208
 
209
+ export class DataIntegrityProofError extends DidMethodError {
210
+ constructor(message: string, type: string = 'DataIntegrityProofError', data?: Record<string, any>) {
211
+ super(message, { type, name: type, data });
212
+ }
213
+ }
214
+
193
215
  export class KeyPairError extends DidMethodError {
194
216
  constructor(message: string, type: string = 'KeyPairError', data?: Record<string, any>) {
195
217
  super(message, { type, name: type, data });
package/src/index.ts CHANGED
@@ -1,11 +1,11 @@
1
- import './exts.js';
2
-
3
1
  export * from './canonicalization.js';
4
2
  export * from './constants.js';
5
3
  export * from './errors.js';
6
4
  export * from './interfaces.js';
5
+ export * from './json-patch.js';
7
6
  export * from './logger.js';
8
- export * from './patch.js';
9
7
  export * from './types.js';
10
-
11
- export * from './exts.js';
8
+ export * from './utils/date.js';
9
+ export * from './utils/json.js';
10
+ export * from './utils/set.js';
11
+ export * from './utils/string.js';
package/src/interfaces.ts CHANGED
@@ -1,311 +1,11 @@
1
1
  export type JsonPatch = Array<PatchOperation>;
2
- export type PatchOpCode = 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test' | string;
2
+ export type PatchOpCode = 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test' | (string & {});
3
3
  /**
4
4
  * A JSON Patch operation, as defined in {@link https://datatracker.ietf.org/doc/html/rfc6902 | RFC 6902}.
5
5
  */
6
6
  export interface PatchOperation {
7
7
  op: PatchOpCode;
8
8
  path: string;
9
- value?: any; // Required for add, replace, test
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
- }
@@ -0,0 +1,103 @@
1
+ import jsonPatch, { Operation } from 'fast-json-patch';
2
+ import { MethodError } from './errors.js';
3
+ import { PatchOperation } from './interfaces.js';
4
+ import { JSONObject } from './types.js';
5
+
6
+ const { applyPatch, compare, deepClone } = jsonPatch;
7
+
8
+ /**
9
+ * Thin wrapper around fast-json-patch to keep a stable API within this package.
10
+ * @class JSONPatch
11
+ * @type {JSONPatch}
12
+ */
13
+ export class JSONPatch {
14
+ /**
15
+ * Applies a JSON Patch to a source document and returns the patched document.
16
+ * Does not mutate the input document.
17
+ * @param {JSONObject} sourceDocument - The source JSON document to apply the patch to.
18
+ * @param {PatchOperation[]} operations - The JSON Patch operations to apply.
19
+ * @returns {JSONObject} The patched JSON document.
20
+ */
21
+ static apply(
22
+ sourceDocument: Record<any, any>,
23
+ operations: PatchOperation[],
24
+ options: { mutate?: boolean; clone?: (value: any) => any } = {}
25
+ ): Record<any, any> {
26
+ const mutate = options.mutate ?? false;
27
+ const cloneFn = options.clone ?? deepClone;
28
+ const docClone = mutate ? sourceDocument : cloneFn(sourceDocument);
29
+ const validationError = this.validateOperations(operations);
30
+ if (validationError) {
31
+ throw new MethodError('Invalid JSON Patch operations', 'JSON_PATCH_APPLY_ERROR', { error: validationError });
32
+ }
33
+ try {
34
+ const result = applyPatch(docClone, operations as Operation[], true, mutate);
35
+ if (result.newDocument === undefined) {
36
+ throw new MethodError('JSON Patch application failed', 'JSON_PATCH_APPLY_ERROR', { result });
37
+ }
38
+ return result.newDocument as JSONObject;
39
+ } catch (error) {
40
+ throw new MethodError('JSON Patch application failed', 'JSON_PATCH_APPLY_ERROR', { error });
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Compute a JSON Patch diff from source => target.
46
+ * @param {JSONObject} sourceDocument - The source JSON document.
47
+ * @param {JSONObject} targetDocument - The target JSON document.
48
+ * @param {string} [path] - An optional base path to prefix to each operation.
49
+ * @returns {PatchOperation[]} The computed JSON Patch operations.
50
+ */
51
+ static diff(sourceDocument: JSONObject, targetDocument: JSONObject, path: string = ''): PatchOperation[] {
52
+ const ops = compare(sourceDocument ?? {}, targetDocument ?? {}) as PatchOperation[];
53
+ if (!path) return ops;
54
+
55
+ const prefix = path.endsWith('/') ? path.slice(0, -1) : path;
56
+ return ops.map(op => ({
57
+ ...op,
58
+ path : this.joinPointer(prefix, op.path)
59
+ }));
60
+ }
61
+
62
+ /**
63
+ * Join a base pointer prefix with an operation path ensuring correct escaping.
64
+ * @param {string} prefix - The base pointer prefix.
65
+ * @param {string} opPath - The operation path.
66
+ * @returns {string} The joined pointer.
67
+ */
68
+ static joinPointer(prefix: string, opPath: string): string {
69
+ if (!prefix) return opPath;
70
+ const normalizedPrefix = prefix.startsWith('/') ? prefix : `/${prefix}`;
71
+ return `${this.escapeSegmentPath(normalizedPrefix)}${opPath}`;
72
+ }
73
+
74
+ /**
75
+ * Escape a JSON Pointer segment according to RFC 6901.
76
+ * @param {string} pointer - The JSON Pointer to escape.
77
+ * @returns {string} The escaped JSON Pointer.
78
+ */
79
+ static escapeSegmentPath(pointer: string): string {
80
+ return pointer
81
+ .split('/')
82
+ .map((segment, idx) => idx === 0 ? segment : segment.replace(/~/g, '~0').replace(/\//g, '~1'))
83
+ .join('/');
84
+ }
85
+
86
+ /**
87
+ * Validate JSON Patch operations.
88
+ * @param {PatchOperation[]} operations - The operations to validate.
89
+ * @returns {Error | null} An Error if validation fails, otherwise null.
90
+ */
91
+ static validateOperations(operations: PatchOperation[]): Error | null {
92
+ if (!Array.isArray(operations)) return new Error('Operations must be an array');
93
+ for (const op of operations) {
94
+ if (!op || typeof op !== 'object') return new Error('Operation must be an object');
95
+ if (typeof op.op !== 'string') return new Error('Operation.op must be a string');
96
+ if (typeof op.path !== 'string') return new Error('Operation.path must be a string');
97
+ if ((op.op === 'move' || op.op === 'copy') && typeof op.from !== 'string') {
98
+ return new Error(`Operation.from must be a string for op=${op.op}`);
99
+ }
100
+ }
101
+ return null;
102
+ }
103
+ }