@did-btcr2/method 0.40.2 → 0.42.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 (46) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/browser.js +102 -24
  3. package/dist/browser.mjs +102 -24
  4. package/dist/cjs/index.js +90 -14
  5. package/dist/esm/core/btcr2-update.js +2 -0
  6. package/dist/esm/core/btcr2-update.js.map +1 -0
  7. package/dist/esm/core/resolver.js +82 -12
  8. package/dist/esm/core/resolver.js.map +1 -1
  9. package/dist/esm/core/updater.js.map +1 -1
  10. package/dist/esm/did-btcr2.js +2 -1
  11. package/dist/esm/did-btcr2.js.map +1 -1
  12. package/dist/esm/index.js +1 -0
  13. package/dist/esm/index.js.map +1 -1
  14. package/dist/types/core/beacon/beacon.d.ts +1 -1
  15. package/dist/types/core/beacon/beacon.d.ts.map +1 -1
  16. package/dist/types/core/beacon/cas-beacon.d.ts +1 -1
  17. package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
  18. package/dist/types/core/beacon/singleton-beacon.d.ts +1 -1
  19. package/dist/types/core/beacon/singleton-beacon.d.ts.map +1 -1
  20. package/dist/types/core/beacon/smt-beacon.d.ts +1 -1
  21. package/dist/types/core/beacon/smt-beacon.d.ts.map +1 -1
  22. package/dist/types/core/btcr2-update.d.ts +76 -0
  23. package/dist/types/core/btcr2-update.d.ts.map +1 -0
  24. package/dist/types/core/interfaces.d.ts +7 -0
  25. package/dist/types/core/interfaces.d.ts.map +1 -1
  26. package/dist/types/core/resolver.d.ts +9 -1
  27. package/dist/types/core/resolver.d.ts.map +1 -1
  28. package/dist/types/core/types.d.ts +1 -1
  29. package/dist/types/core/types.d.ts.map +1 -1
  30. package/dist/types/core/updater.d.ts +1 -1
  31. package/dist/types/core/updater.d.ts.map +1 -1
  32. package/dist/types/did-btcr2.d.ts.map +1 -1
  33. package/dist/types/index.d.ts +1 -0
  34. package/dist/types/index.d.ts.map +1 -1
  35. package/package.json +5 -5
  36. package/src/core/beacon/beacon.ts +1 -1
  37. package/src/core/beacon/cas-beacon.ts +1 -1
  38. package/src/core/beacon/singleton-beacon.ts +1 -1
  39. package/src/core/beacon/smt-beacon.ts +1 -1
  40. package/src/core/btcr2-update.ts +87 -0
  41. package/src/core/interfaces.ts +8 -0
  42. package/src/core/resolver.ts +115 -15
  43. package/src/core/types.ts +1 -1
  44. package/src/core/updater.ts +3 -2
  45. package/src/did-btcr2.ts +4 -3
  46. package/src/index.ts +1 -0
package/dist/browser.js CHANGED
@@ -93159,6 +93159,7 @@ a=end-of-candidates
93159
93159
  CASBeaconError: () => CASBeaconError,
93160
93160
  CHANGE_OUTPUT_VBYTES: () => CHANGE_OUTPUT_VBYTES,
93161
93161
  DEFAULT_FEE_ESTIMATOR: () => DEFAULT_FEE_ESTIMATOR,
93162
+ DEFAULT_MAX_DISCOVERY_ROUNDS: () => DEFAULT_MAX_DISCOVERY_ROUNDS,
93162
93163
  DID_REGEX: () => DID_REGEX,
93163
93164
  DUST_LIMIT_SATS: () => DUST_LIMIT_SATS,
93164
93165
  DidBtcr2: () => DidBtcr2,
@@ -117822,6 +117823,9 @@ a=end-of-candidates
117822
117823
  }
117823
117824
  };
117824
117825
 
117826
+ // src/core/btcr2-update.ts
117827
+ init_shim();
117828
+
117825
117829
  // src/core/did-sender-resolver.ts
117826
117830
  init_shim();
117827
117831
  function resolveBtcr2SenderPk(did) {
@@ -118140,9 +118144,9 @@ a=end-of-candidates
118140
118144
  }
118141
118145
  /**
118142
118146
  * Add a proof to a document.
118143
- * @param {UnsignedBTCR2Update} unsignedDocument The document to add the proof to.
118144
- * @param {DataIntegrityConfig} config The configuration for generating the proof.
118145
- * @returns {SignedBTCR2Update} A document with a proof added.
118147
+ * @param {UnsecuredDocument} unsignedDocument The document to add the proof to.
118148
+ * @param {DataIntegrityProofOptions} config The proof options for generating the proof.
118149
+ * @returns {SecuredDocument} A document with a proof added.
118146
118150
  */
118147
118151
  addProof(unsignedDocument, config) {
118148
118152
  const proof = this.cryptosuite.createProof(unsignedDocument, config);
@@ -118235,7 +118239,7 @@ a=end-of-candidates
118235
118239
  /**
118236
118240
  * Create a proof for an insecure document.
118237
118241
  * @param {DidUpdatePayload} document The document to create a proof for.
118238
- * @param {DataIntegrityConfig} config The options to use when creating the proof.
118242
+ * @param {DataIntegrityProofOptions} config The options to use when creating the proof.
118239
118243
  * @returns {DataIntegrityProofObject} The proof for the document.
118240
118244
  */
118241
118245
  createProof(document2, config) {
@@ -118252,7 +118256,7 @@ a=end-of-candidates
118252
118256
  }
118253
118257
  /**
118254
118258
  * Verify a proof for a secure document.
118255
- * @param {SignedBTCR2Update} secureDocument The secure document to verify.
118259
+ * @param {SecuredDocument} secureDocument The secured document to verify.
118256
118260
  * @returns {VerificationResult} The result of the verification.
118257
118261
  */
118258
118262
  verifyProof(secureDocument) {
@@ -118267,8 +118271,8 @@ a=end-of-candidates
118267
118271
  }
118268
118272
  /**
118269
118273
  * Transform a document into canonical form.
118270
- * @param {UnsignedBTCR2Update | SignedBTCR2Update} document The document to transform.
118271
- * @param {DataIntegrityConfig} config The config to use when transforming the document.
118274
+ * @param {UnsecuredDocument} document The document to transform.
118275
+ * @param {DataIntegrityProofOptions} config The config to use when transforming the document.
118272
118276
  * @returns {string} The canonicalized document.
118273
118277
  * @throws {MethodError} if the document cannot be transformed.
118274
118278
  */
@@ -118295,7 +118299,7 @@ a=end-of-candidates
118295
118299
  }
118296
118300
  /**
118297
118301
  * Configure the proof by canonicalzing it.
118298
- * @param {DataIntegrityConfig} config The config to use when transforming the proof.
118302
+ * @param {DataIntegrityProofOptions} config The config to use when transforming the proof.
118299
118303
  * @returns {string} The canonicalized proof configuration.
118300
118304
  * @throws {CryptosuiteError} if the proof configuration cannot be canonicalized.
118301
118305
  */
@@ -118315,7 +118319,7 @@ a=end-of-candidates
118315
118319
  /**
118316
118320
  * Serialize the proof into a byte array.
118317
118321
  * @param {HashBytes} hash The canonicalized proof configuration.
118318
- * @param {DataIntegrityConfig} config The config to use when serializing the proof.
118322
+ * @param {DataIntegrityProofOptions} config The config to use when serializing the proof.
118319
118323
  * @returns {SignatureBytes} The serialized proof.
118320
118324
  * @throws {CryptosuiteError} if the multikey does not match the verification method.
118321
118325
  */
@@ -118329,7 +118333,7 @@ a=end-of-candidates
118329
118333
  * Verify the proof by comparing the hash of the proof configuration and document to the proof bytes.
118330
118334
  * @param {HashBytes} hash The canonicalized proof configuration and document hash.
118331
118335
  * @param {SignatureBytes} signature The proof bytes to verify against.
118332
- * @param {DataIntegrityConfig} config The config to use when verifying the proof.
118336
+ * @param {DataIntegrityProofOptions} config The config to use when verifying the proof.
118333
118337
  * @returns {boolean} True if the proof is verified, false otherwise.
118334
118338
  * @throws {CryptosuiteError} if the multikey does not match the verification method.
118335
118339
  */
@@ -119286,7 +119290,8 @@ a=end-of-candidates
119286
119290
  return new Resolver(didComponents, sidecarData, currentDocument, {
119287
119291
  versionId: resolutionOptions.versionId,
119288
119292
  versionTime: resolutionOptions.versionTime,
119289
- genesisDocument: resolutionOptions.sidecar?.genesisDocument
119293
+ genesisDocument: resolutionOptions.sidecar?.genesisDocument,
119294
+ maxDiscoveryRounds: resolutionOptions.maxDiscoveryRounds
119290
119295
  });
119291
119296
  }
119292
119297
  /**
@@ -119395,6 +119400,21 @@ a=end-of-candidates
119395
119400
 
119396
119401
  // src/core/resolver.ts
119397
119402
  init_utils2();
119403
+ var DEFAULT_MAX_DISCOVERY_ROUNDS = 10;
119404
+ function isRecord(value2) {
119405
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
119406
+ }
119407
+ function isCASAnnouncement(value2) {
119408
+ return isRecord(value2) && Object.values(value2).every((v) => typeof v === "string");
119409
+ }
119410
+ function isSignedBTCR2Update(value2) {
119411
+ if (!isRecord(value2)) return false;
119412
+ return Array.isArray(value2.patch) && typeof value2.sourceHash === "string" && typeof value2.targetHash === "string" && typeof value2.targetVersionId === "number" && isRecord(value2.proof);
119413
+ }
119414
+ function isSMTProof(value2) {
119415
+ if (!isRecord(value2)) return false;
119416
+ return typeof value2.id === "string" && typeof value2.collapsed === "string" && Array.isArray(value2.hashes);
119417
+ }
119398
119418
  var Resolver = class _Resolver {
119399
119419
  // --- Immutable inputs ---
119400
119420
  #didComponents;
@@ -119412,6 +119432,10 @@ a=end-of-candidates
119412
119432
  #requestCache = /* @__PURE__ */ new Set();
119413
119433
  #unsortedUpdates = [];
119414
119434
  #resolvedResponse = null;
119435
+ /** Upper bound on multi-round beacon-discovery passes; see {@link DEFAULT_MAX_DISCOVERY_ROUNDS}. */
119436
+ #maxDiscoveryRounds;
119437
+ /** Count of beacon-discovery passes driven by updates adding new beacon services. */
119438
+ #discoveryRounds = 0;
119415
119439
  /**
119416
119440
  * @internal Use {@link DidBtcr2.resolve} to create instances.
119417
119441
  */
@@ -119421,6 +119445,7 @@ a=end-of-candidates
119421
119445
  this.#currentDocument = currentDocument;
119422
119446
  this.#versionId = options2?.versionId;
119423
119447
  this.#versionTime = options2?.versionTime;
119448
+ this.#maxDiscoveryRounds = options2?.maxDiscoveryRounds ?? DEFAULT_MAX_DISCOVERY_ROUNDS;
119424
119449
  if (options2?.genesisDocument) {
119425
119450
  this.#providedGenesisDocument = options2.genesisDocument;
119426
119451
  }
@@ -119735,6 +119760,13 @@ a=end-of-candidates
119735
119760
  return !this.#requestCache.has(address);
119736
119761
  });
119737
119762
  if (hasNewServices) {
119763
+ if (++this.#discoveryRounds > this.#maxDiscoveryRounds) {
119764
+ throw new ResolveError(
119765
+ `Exceeded maximum beacon-discovery rounds (${this.#maxDiscoveryRounds}); the DID document may chain beacon services without terminating.`,
119766
+ INVALID_DID_DOCUMENT,
119767
+ { maxDiscoveryRounds: this.#maxDiscoveryRounds }
119768
+ );
119769
+ }
119738
119770
  this.#phase = "BeaconDiscovery" /* BeaconDiscovery */;
119739
119771
  continue;
119740
119772
  }
@@ -119761,38 +119793,84 @@ a=end-of-candidates
119761
119793
  provide(need, data) {
119762
119794
  switch (need.kind) {
119763
119795
  case "NeedGenesisDocument": {
119796
+ if (!isRecord(data)) {
119797
+ throw new ResolveError(
119798
+ "Provided data for NeedGenesisDocument must be a document object.",
119799
+ INVALID_DID_UPDATE,
119800
+ { kind: need.kind }
119801
+ );
119802
+ }
119764
119803
  this.#providedGenesisDocument = data;
119765
119804
  break;
119766
119805
  }
119767
119806
  case "NeedBeaconSignals": {
119768
- const signals = data;
119769
- for (const [service, serviceSignals] of signals) {
119807
+ if (!(data instanceof Map)) {
119808
+ throw new ResolveError(
119809
+ "Provided data for NeedBeaconSignals must be a Map of beacon services to signals.",
119810
+ INVALID_DID_UPDATE,
119811
+ { kind: need.kind }
119812
+ );
119813
+ }
119814
+ for (const [service, serviceSignals] of data) {
119770
119815
  this.#beaconServicesSignals.set(service, serviceSignals);
119771
119816
  }
119772
119817
  break;
119773
119818
  }
119774
119819
  case "NeedCASAnnouncement": {
119775
- const announcement = data;
119776
- this.#sidecarData.casMap.set(canonicalHash(announcement, { encoding: "hex" }), announcement);
119820
+ if (!isCASAnnouncement(data)) {
119821
+ throw new ResolveError(
119822
+ "Provided data for NeedCASAnnouncement is not a CAS announcement.",
119823
+ INVALID_DID_UPDATE,
119824
+ { kind: need.kind }
119825
+ );
119826
+ }
119827
+ const announcementHash = canonicalHash(data, { encoding: "hex" });
119828
+ if (announcementHash !== need.announcementHash) {
119829
+ throw new ResolveError(
119830
+ `CAS announcement hash mismatch: expected ${need.announcementHash}, got ${announcementHash}.`,
119831
+ INVALID_DID_UPDATE,
119832
+ { expected: need.announcementHash, actual: announcementHash }
119833
+ );
119834
+ }
119835
+ this.#sidecarData.casMap.set(announcementHash, data);
119777
119836
  break;
119778
119837
  }
119779
119838
  case "NeedSignedUpdate": {
119780
- const update = data;
119781
- this.#sidecarData.updateMap.set(canonicalHash(update, { encoding: "hex" }), update);
119839
+ if (!isSignedBTCR2Update(data)) {
119840
+ throw new ResolveError(
119841
+ "Provided data for NeedSignedUpdate is not a signed BTCR2 update.",
119842
+ INVALID_DID_UPDATE,
119843
+ { kind: need.kind }
119844
+ );
119845
+ }
119846
+ const updateHash = canonicalHash(data, { encoding: "hex" });
119847
+ if (updateHash !== need.updateHash) {
119848
+ throw new ResolveError(
119849
+ `Signed update hash mismatch: expected ${need.updateHash}, got ${updateHash}.`,
119850
+ INVALID_DID_UPDATE,
119851
+ { expected: need.updateHash, actual: updateHash }
119852
+ );
119853
+ }
119854
+ this.#sidecarData.updateMap.set(updateHash, data);
119782
119855
  break;
119783
119856
  }
119784
119857
  case "NeedSMTProof": {
119785
- const smtNeed = need;
119786
- const proof = data;
119787
- const proofIdHex = encode(decode(proof.id, "base64urlnopad"), "hex");
119788
- if (proofIdHex !== smtNeed.smtRootHash) {
119858
+ if (!isSMTProof(data)) {
119859
+ throw new ResolveError(
119860
+ "Provided data for NeedSMTProof is not an SMT proof.",
119861
+ INVALID_DID_UPDATE,
119862
+ { kind: need.kind }
119863
+ );
119864
+ }
119865
+ const proofIdHex = encode(decode(data.id, "base64urlnopad"), "hex");
119866
+ if (proofIdHex !== need.smtRootHash) {
119789
119867
  throw new ResolveError(
119790
- `SMT proof root hash mismatch: expected ${smtNeed.smtRootHash}, got ${proofIdHex}`,
119868
+ `SMT proof root hash mismatch: expected ${need.smtRootHash}, got ${proofIdHex}`,
119791
119869
  INVALID_DID_UPDATE,
119792
- { expected: smtNeed.smtRootHash, actual: proofIdHex }
119870
+ { expected: need.smtRootHash, actual: proofIdHex }
119793
119871
  );
119794
119872
  }
119795
- this.#sidecarData.smtMap.set(smtNeed.smtRootHash, proof);
119873
+ this.#sidecarData.smtMap.set(need.smtRootHash, data);
119796
119874
  break;
119797
119875
  }
119798
119876
  }
package/dist/browser.mjs CHANGED
@@ -117775,6 +117775,9 @@ var BeaconSignalDiscovery = class {
117775
117775
  }
117776
117776
  };
117777
117777
 
117778
+ // src/core/btcr2-update.ts
117779
+ init_shim();
117780
+
117778
117781
  // src/core/did-sender-resolver.ts
117779
117782
  init_shim();
117780
117783
  function resolveBtcr2SenderPk(did) {
@@ -118093,9 +118096,9 @@ var BIP340DataIntegrityProof = class {
118093
118096
  }
118094
118097
  /**
118095
118098
  * Add a proof to a document.
118096
- * @param {UnsignedBTCR2Update} unsignedDocument The document to add the proof to.
118097
- * @param {DataIntegrityConfig} config The configuration for generating the proof.
118098
- * @returns {SignedBTCR2Update} A document with a proof added.
118099
+ * @param {UnsecuredDocument} unsignedDocument The document to add the proof to.
118100
+ * @param {DataIntegrityProofOptions} config The proof options for generating the proof.
118101
+ * @returns {SecuredDocument} A document with a proof added.
118099
118102
  */
118100
118103
  addProof(unsignedDocument, config) {
118101
118104
  const proof = this.cryptosuite.createProof(unsignedDocument, config);
@@ -118188,7 +118191,7 @@ var BIP340Cryptosuite = class {
118188
118191
  /**
118189
118192
  * Create a proof for an insecure document.
118190
118193
  * @param {DidUpdatePayload} document The document to create a proof for.
118191
- * @param {DataIntegrityConfig} config The options to use when creating the proof.
118194
+ * @param {DataIntegrityProofOptions} config The options to use when creating the proof.
118192
118195
  * @returns {DataIntegrityProofObject} The proof for the document.
118193
118196
  */
118194
118197
  createProof(document2, config) {
@@ -118205,7 +118208,7 @@ var BIP340Cryptosuite = class {
118205
118208
  }
118206
118209
  /**
118207
118210
  * Verify a proof for a secure document.
118208
- * @param {SignedBTCR2Update} secureDocument The secure document to verify.
118211
+ * @param {SecuredDocument} secureDocument The secured document to verify.
118209
118212
  * @returns {VerificationResult} The result of the verification.
118210
118213
  */
118211
118214
  verifyProof(secureDocument) {
@@ -118220,8 +118223,8 @@ var BIP340Cryptosuite = class {
118220
118223
  }
118221
118224
  /**
118222
118225
  * Transform a document into canonical form.
118223
- * @param {UnsignedBTCR2Update | SignedBTCR2Update} document The document to transform.
118224
- * @param {DataIntegrityConfig} config The config to use when transforming the document.
118226
+ * @param {UnsecuredDocument} document The document to transform.
118227
+ * @param {DataIntegrityProofOptions} config The config to use when transforming the document.
118225
118228
  * @returns {string} The canonicalized document.
118226
118229
  * @throws {MethodError} if the document cannot be transformed.
118227
118230
  */
@@ -118248,7 +118251,7 @@ var BIP340Cryptosuite = class {
118248
118251
  }
118249
118252
  /**
118250
118253
  * Configure the proof by canonicalzing it.
118251
- * @param {DataIntegrityConfig} config The config to use when transforming the proof.
118254
+ * @param {DataIntegrityProofOptions} config The config to use when transforming the proof.
118252
118255
  * @returns {string} The canonicalized proof configuration.
118253
118256
  * @throws {CryptosuiteError} if the proof configuration cannot be canonicalized.
118254
118257
  */
@@ -118268,7 +118271,7 @@ var BIP340Cryptosuite = class {
118268
118271
  /**
118269
118272
  * Serialize the proof into a byte array.
118270
118273
  * @param {HashBytes} hash The canonicalized proof configuration.
118271
- * @param {DataIntegrityConfig} config The config to use when serializing the proof.
118274
+ * @param {DataIntegrityProofOptions} config The config to use when serializing the proof.
118272
118275
  * @returns {SignatureBytes} The serialized proof.
118273
118276
  * @throws {CryptosuiteError} if the multikey does not match the verification method.
118274
118277
  */
@@ -118282,7 +118285,7 @@ var BIP340Cryptosuite = class {
118282
118285
  * Verify the proof by comparing the hash of the proof configuration and document to the proof bytes.
118283
118286
  * @param {HashBytes} hash The canonicalized proof configuration and document hash.
118284
118287
  * @param {SignatureBytes} signature The proof bytes to verify against.
118285
- * @param {DataIntegrityConfig} config The config to use when verifying the proof.
118288
+ * @param {DataIntegrityProofOptions} config The config to use when verifying the proof.
118286
118289
  * @returns {boolean} True if the proof is verified, false otherwise.
118287
118290
  * @throws {CryptosuiteError} if the multikey does not match the verification method.
118288
118291
  */
@@ -119239,7 +119242,8 @@ var DidBtcr2 = class {
119239
119242
  return new Resolver(didComponents, sidecarData, currentDocument, {
119240
119243
  versionId: resolutionOptions.versionId,
119241
119244
  versionTime: resolutionOptions.versionTime,
119242
- genesisDocument: resolutionOptions.sidecar?.genesisDocument
119245
+ genesisDocument: resolutionOptions.sidecar?.genesisDocument,
119246
+ maxDiscoveryRounds: resolutionOptions.maxDiscoveryRounds
119243
119247
  });
119244
119248
  }
119245
119249
  /**
@@ -119348,6 +119352,21 @@ var DidBtcr2 = class {
119348
119352
 
119349
119353
  // src/core/resolver.ts
119350
119354
  init_utils2();
119355
+ var DEFAULT_MAX_DISCOVERY_ROUNDS = 10;
119356
+ function isRecord(value2) {
119357
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
119358
+ }
119359
+ function isCASAnnouncement(value2) {
119360
+ return isRecord(value2) && Object.values(value2).every((v) => typeof v === "string");
119361
+ }
119362
+ function isSignedBTCR2Update(value2) {
119363
+ if (!isRecord(value2)) return false;
119364
+ return Array.isArray(value2.patch) && typeof value2.sourceHash === "string" && typeof value2.targetHash === "string" && typeof value2.targetVersionId === "number" && isRecord(value2.proof);
119365
+ }
119366
+ function isSMTProof(value2) {
119367
+ if (!isRecord(value2)) return false;
119368
+ return typeof value2.id === "string" && typeof value2.collapsed === "string" && Array.isArray(value2.hashes);
119369
+ }
119351
119370
  var Resolver = class _Resolver {
119352
119371
  // --- Immutable inputs ---
119353
119372
  #didComponents;
@@ -119365,6 +119384,10 @@ var Resolver = class _Resolver {
119365
119384
  #requestCache = /* @__PURE__ */ new Set();
119366
119385
  #unsortedUpdates = [];
119367
119386
  #resolvedResponse = null;
119387
+ /** Upper bound on multi-round beacon-discovery passes; see {@link DEFAULT_MAX_DISCOVERY_ROUNDS}. */
119388
+ #maxDiscoveryRounds;
119389
+ /** Count of beacon-discovery passes driven by updates adding new beacon services. */
119390
+ #discoveryRounds = 0;
119368
119391
  /**
119369
119392
  * @internal Use {@link DidBtcr2.resolve} to create instances.
119370
119393
  */
@@ -119374,6 +119397,7 @@ var Resolver = class _Resolver {
119374
119397
  this.#currentDocument = currentDocument;
119375
119398
  this.#versionId = options2?.versionId;
119376
119399
  this.#versionTime = options2?.versionTime;
119400
+ this.#maxDiscoveryRounds = options2?.maxDiscoveryRounds ?? DEFAULT_MAX_DISCOVERY_ROUNDS;
119377
119401
  if (options2?.genesisDocument) {
119378
119402
  this.#providedGenesisDocument = options2.genesisDocument;
119379
119403
  }
@@ -119688,6 +119712,13 @@ var Resolver = class _Resolver {
119688
119712
  return !this.#requestCache.has(address);
119689
119713
  });
119690
119714
  if (hasNewServices) {
119715
+ if (++this.#discoveryRounds > this.#maxDiscoveryRounds) {
119716
+ throw new ResolveError(
119717
+ `Exceeded maximum beacon-discovery rounds (${this.#maxDiscoveryRounds}); the DID document may chain beacon services without terminating.`,
119718
+ INVALID_DID_DOCUMENT,
119719
+ { maxDiscoveryRounds: this.#maxDiscoveryRounds }
119720
+ );
119721
+ }
119691
119722
  this.#phase = "BeaconDiscovery" /* BeaconDiscovery */;
119692
119723
  continue;
119693
119724
  }
@@ -119714,38 +119745,84 @@ var Resolver = class _Resolver {
119714
119745
  provide(need, data) {
119715
119746
  switch (need.kind) {
119716
119747
  case "NeedGenesisDocument": {
119748
+ if (!isRecord(data)) {
119749
+ throw new ResolveError(
119750
+ "Provided data for NeedGenesisDocument must be a document object.",
119751
+ INVALID_DID_UPDATE,
119752
+ { kind: need.kind }
119753
+ );
119754
+ }
119717
119755
  this.#providedGenesisDocument = data;
119718
119756
  break;
119719
119757
  }
119720
119758
  case "NeedBeaconSignals": {
119721
- const signals = data;
119722
- for (const [service, serviceSignals] of signals) {
119759
+ if (!(data instanceof Map)) {
119760
+ throw new ResolveError(
119761
+ "Provided data for NeedBeaconSignals must be a Map of beacon services to signals.",
119762
+ INVALID_DID_UPDATE,
119763
+ { kind: need.kind }
119764
+ );
119765
+ }
119766
+ for (const [service, serviceSignals] of data) {
119723
119767
  this.#beaconServicesSignals.set(service, serviceSignals);
119724
119768
  }
119725
119769
  break;
119726
119770
  }
119727
119771
  case "NeedCASAnnouncement": {
119728
- const announcement = data;
119729
- this.#sidecarData.casMap.set(canonicalHash(announcement, { encoding: "hex" }), announcement);
119772
+ if (!isCASAnnouncement(data)) {
119773
+ throw new ResolveError(
119774
+ "Provided data for NeedCASAnnouncement is not a CAS announcement.",
119775
+ INVALID_DID_UPDATE,
119776
+ { kind: need.kind }
119777
+ );
119778
+ }
119779
+ const announcementHash = canonicalHash(data, { encoding: "hex" });
119780
+ if (announcementHash !== need.announcementHash) {
119781
+ throw new ResolveError(
119782
+ `CAS announcement hash mismatch: expected ${need.announcementHash}, got ${announcementHash}.`,
119783
+ INVALID_DID_UPDATE,
119784
+ { expected: need.announcementHash, actual: announcementHash }
119785
+ );
119786
+ }
119787
+ this.#sidecarData.casMap.set(announcementHash, data);
119730
119788
  break;
119731
119789
  }
119732
119790
  case "NeedSignedUpdate": {
119733
- const update = data;
119734
- this.#sidecarData.updateMap.set(canonicalHash(update, { encoding: "hex" }), update);
119791
+ if (!isSignedBTCR2Update(data)) {
119792
+ throw new ResolveError(
119793
+ "Provided data for NeedSignedUpdate is not a signed BTCR2 update.",
119794
+ INVALID_DID_UPDATE,
119795
+ { kind: need.kind }
119796
+ );
119797
+ }
119798
+ const updateHash = canonicalHash(data, { encoding: "hex" });
119799
+ if (updateHash !== need.updateHash) {
119800
+ throw new ResolveError(
119801
+ `Signed update hash mismatch: expected ${need.updateHash}, got ${updateHash}.`,
119802
+ INVALID_DID_UPDATE,
119803
+ { expected: need.updateHash, actual: updateHash }
119804
+ );
119805
+ }
119806
+ this.#sidecarData.updateMap.set(updateHash, data);
119735
119807
  break;
119736
119808
  }
119737
119809
  case "NeedSMTProof": {
119738
- const smtNeed = need;
119739
- const proof = data;
119740
- const proofIdHex = encode(decode(proof.id, "base64urlnopad"), "hex");
119741
- if (proofIdHex !== smtNeed.smtRootHash) {
119810
+ if (!isSMTProof(data)) {
119811
+ throw new ResolveError(
119812
+ "Provided data for NeedSMTProof is not an SMT proof.",
119813
+ INVALID_DID_UPDATE,
119814
+ { kind: need.kind }
119815
+ );
119816
+ }
119817
+ const proofIdHex = encode(decode(data.id, "base64urlnopad"), "hex");
119818
+ if (proofIdHex !== need.smtRootHash) {
119742
119819
  throw new ResolveError(
119743
- `SMT proof root hash mismatch: expected ${smtNeed.smtRootHash}, got ${proofIdHex}`,
119820
+ `SMT proof root hash mismatch: expected ${need.smtRootHash}, got ${proofIdHex}`,
119744
119821
  INVALID_DID_UPDATE,
119745
- { expected: smtNeed.smtRootHash, actual: proofIdHex }
119822
+ { expected: need.smtRootHash, actual: proofIdHex }
119746
119823
  );
119747
119824
  }
119748
- this.#sidecarData.smtMap.set(smtNeed.smtRootHash, proof);
119825
+ this.#sidecarData.smtMap.set(need.smtRootHash, data);
119749
119826
  break;
119750
119827
  }
119751
119828
  }
@@ -119822,6 +119899,7 @@ export {
119822
119899
  CASBeaconError,
119823
119900
  CHANGE_OUTPUT_VBYTES,
119824
119901
  DEFAULT_FEE_ESTIMATOR,
119902
+ DEFAULT_MAX_DISCOVERY_ROUNDS,
119825
119903
  DID_REGEX,
119826
119904
  DUST_LIMIT_SATS,
119827
119905
  DidBtcr2,
package/dist/cjs/index.js CHANGED
@@ -42,6 +42,7 @@ __export(index_exports, {
42
42
  CASBeaconError: () => CASBeaconError,
43
43
  CHANGE_OUTPUT_VBYTES: () => CHANGE_OUTPUT_VBYTES,
44
44
  DEFAULT_FEE_ESTIMATOR: () => DEFAULT_FEE_ESTIMATOR,
45
+ DEFAULT_MAX_DISCOVERY_ROUNDS: () => DEFAULT_MAX_DISCOVERY_ROUNDS,
45
46
  DID_REGEX: () => DID_REGEX,
46
47
  DUST_LIMIT_SATS: () => DUST_LIMIT_SATS,
47
48
  DidBtcr2: () => DidBtcr2,
@@ -2913,7 +2914,8 @@ var DidBtcr2 = class {
2913
2914
  return new Resolver(didComponents, sidecarData, currentDocument, {
2914
2915
  versionId: resolutionOptions.versionId,
2915
2916
  versionTime: resolutionOptions.versionTime,
2916
- genesisDocument: resolutionOptions.sidecar?.genesisDocument
2917
+ genesisDocument: resolutionOptions.sidecar?.genesisDocument,
2918
+ maxDiscoveryRounds: resolutionOptions.maxDiscoveryRounds
2917
2919
  });
2918
2920
  }
2919
2921
  /**
@@ -3022,6 +3024,21 @@ var DidBtcr2 = class {
3022
3024
 
3023
3025
  // src/core/resolver.ts
3024
3026
  var import_utils6 = require("@noble/curves/utils.js");
3027
+ var DEFAULT_MAX_DISCOVERY_ROUNDS = 10;
3028
+ function isRecord(value) {
3029
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3030
+ }
3031
+ function isCASAnnouncement(value) {
3032
+ return isRecord(value) && Object.values(value).every((v) => typeof v === "string");
3033
+ }
3034
+ function isSignedBTCR2Update(value) {
3035
+ if (!isRecord(value)) return false;
3036
+ return Array.isArray(value.patch) && typeof value.sourceHash === "string" && typeof value.targetHash === "string" && typeof value.targetVersionId === "number" && isRecord(value.proof);
3037
+ }
3038
+ function isSMTProof(value) {
3039
+ if (!isRecord(value)) return false;
3040
+ return typeof value.id === "string" && typeof value.collapsed === "string" && Array.isArray(value.hashes);
3041
+ }
3025
3042
  var Resolver = class _Resolver {
3026
3043
  // --- Immutable inputs ---
3027
3044
  #didComponents;
@@ -3039,6 +3056,10 @@ var Resolver = class _Resolver {
3039
3056
  #requestCache = /* @__PURE__ */ new Set();
3040
3057
  #unsortedUpdates = [];
3041
3058
  #resolvedResponse = null;
3059
+ /** Upper bound on multi-round beacon-discovery passes; see {@link DEFAULT_MAX_DISCOVERY_ROUNDS}. */
3060
+ #maxDiscoveryRounds;
3061
+ /** Count of beacon-discovery passes driven by updates adding new beacon services. */
3062
+ #discoveryRounds = 0;
3042
3063
  /**
3043
3064
  * @internal Use {@link DidBtcr2.resolve} to create instances.
3044
3065
  */
@@ -3048,6 +3069,7 @@ var Resolver = class _Resolver {
3048
3069
  this.#currentDocument = currentDocument;
3049
3070
  this.#versionId = options?.versionId;
3050
3071
  this.#versionTime = options?.versionTime;
3072
+ this.#maxDiscoveryRounds = options?.maxDiscoveryRounds ?? DEFAULT_MAX_DISCOVERY_ROUNDS;
3051
3073
  if (options?.genesisDocument) {
3052
3074
  this.#providedGenesisDocument = options.genesisDocument;
3053
3075
  }
@@ -3362,6 +3384,13 @@ var Resolver = class _Resolver {
3362
3384
  return !this.#requestCache.has(address);
3363
3385
  });
3364
3386
  if (hasNewServices) {
3387
+ if (++this.#discoveryRounds > this.#maxDiscoveryRounds) {
3388
+ throw new import_common12.ResolveError(
3389
+ `Exceeded maximum beacon-discovery rounds (${this.#maxDiscoveryRounds}); the DID document may chain beacon services without terminating.`,
3390
+ import_common12.INVALID_DID_DOCUMENT,
3391
+ { maxDiscoveryRounds: this.#maxDiscoveryRounds }
3392
+ );
3393
+ }
3365
3394
  this.#phase = "BeaconDiscovery" /* BeaconDiscovery */;
3366
3395
  continue;
3367
3396
  }
@@ -3388,38 +3417,84 @@ var Resolver = class _Resolver {
3388
3417
  provide(need, data) {
3389
3418
  switch (need.kind) {
3390
3419
  case "NeedGenesisDocument": {
3420
+ if (!isRecord(data)) {
3421
+ throw new import_common12.ResolveError(
3422
+ "Provided data for NeedGenesisDocument must be a document object.",
3423
+ import_common12.INVALID_DID_UPDATE,
3424
+ { kind: need.kind }
3425
+ );
3426
+ }
3391
3427
  this.#providedGenesisDocument = data;
3392
3428
  break;
3393
3429
  }
3394
3430
  case "NeedBeaconSignals": {
3395
- const signals = data;
3396
- for (const [service, serviceSignals] of signals) {
3431
+ if (!(data instanceof Map)) {
3432
+ throw new import_common12.ResolveError(
3433
+ "Provided data for NeedBeaconSignals must be a Map of beacon services to signals.",
3434
+ import_common12.INVALID_DID_UPDATE,
3435
+ { kind: need.kind }
3436
+ );
3437
+ }
3438
+ for (const [service, serviceSignals] of data) {
3397
3439
  this.#beaconServicesSignals.set(service, serviceSignals);
3398
3440
  }
3399
3441
  break;
3400
3442
  }
3401
3443
  case "NeedCASAnnouncement": {
3402
- const announcement = data;
3403
- this.#sidecarData.casMap.set((0, import_common12.canonicalHash)(announcement, { encoding: "hex" }), announcement);
3444
+ if (!isCASAnnouncement(data)) {
3445
+ throw new import_common12.ResolveError(
3446
+ "Provided data for NeedCASAnnouncement is not a CAS announcement.",
3447
+ import_common12.INVALID_DID_UPDATE,
3448
+ { kind: need.kind }
3449
+ );
3450
+ }
3451
+ const announcementHash = (0, import_common12.canonicalHash)(data, { encoding: "hex" });
3452
+ if (announcementHash !== need.announcementHash) {
3453
+ throw new import_common12.ResolveError(
3454
+ `CAS announcement hash mismatch: expected ${need.announcementHash}, got ${announcementHash}.`,
3455
+ import_common12.INVALID_DID_UPDATE,
3456
+ { expected: need.announcementHash, actual: announcementHash }
3457
+ );
3458
+ }
3459
+ this.#sidecarData.casMap.set(announcementHash, data);
3404
3460
  break;
3405
3461
  }
3406
3462
  case "NeedSignedUpdate": {
3407
- const update = data;
3408
- this.#sidecarData.updateMap.set((0, import_common12.canonicalHash)(update, { encoding: "hex" }), update);
3463
+ if (!isSignedBTCR2Update(data)) {
3464
+ throw new import_common12.ResolveError(
3465
+ "Provided data for NeedSignedUpdate is not a signed BTCR2 update.",
3466
+ import_common12.INVALID_DID_UPDATE,
3467
+ { kind: need.kind }
3468
+ );
3469
+ }
3470
+ const updateHash = (0, import_common12.canonicalHash)(data, { encoding: "hex" });
3471
+ if (updateHash !== need.updateHash) {
3472
+ throw new import_common12.ResolveError(
3473
+ `Signed update hash mismatch: expected ${need.updateHash}, got ${updateHash}.`,
3474
+ import_common12.INVALID_DID_UPDATE,
3475
+ { expected: need.updateHash, actual: updateHash }
3476
+ );
3477
+ }
3478
+ this.#sidecarData.updateMap.set(updateHash, data);
3409
3479
  break;
3410
3480
  }
3411
3481
  case "NeedSMTProof": {
3412
- const smtNeed = need;
3413
- const proof = data;
3414
- const proofIdHex = (0, import_common12.encode)((0, import_common12.decode)(proof.id, "base64urlnopad"), "hex");
3415
- if (proofIdHex !== smtNeed.smtRootHash) {
3482
+ if (!isSMTProof(data)) {
3483
+ throw new import_common12.ResolveError(
3484
+ "Provided data for NeedSMTProof is not an SMT proof.",
3485
+ import_common12.INVALID_DID_UPDATE,
3486
+ { kind: need.kind }
3487
+ );
3488
+ }
3489
+ const proofIdHex = (0, import_common12.encode)((0, import_common12.decode)(data.id, "base64urlnopad"), "hex");
3490
+ if (proofIdHex !== need.smtRootHash) {
3416
3491
  throw new import_common12.ResolveError(
3417
- `SMT proof root hash mismatch: expected ${smtNeed.smtRootHash}, got ${proofIdHex}`,
3492
+ `SMT proof root hash mismatch: expected ${need.smtRootHash}, got ${proofIdHex}`,
3418
3493
  import_common12.INVALID_DID_UPDATE,
3419
- { expected: smtNeed.smtRootHash, actual: proofIdHex }
3494
+ { expected: need.smtRootHash, actual: proofIdHex }
3420
3495
  );
3421
3496
  }
3422
- this.#sidecarData.smtMap.set(smtNeed.smtRootHash, proof);
3497
+ this.#sidecarData.smtMap.set(need.smtRootHash, data);
3423
3498
  break;
3424
3499
  }
3425
3500
  }
@@ -3494,6 +3569,7 @@ var DidDocumentBuilder = class {
3494
3569
  CASBeaconError,
3495
3570
  CHANGE_OUTPUT_VBYTES,
3496
3571
  DEFAULT_FEE_ESTIMATOR,
3572
+ DEFAULT_MAX_DISCOVERY_ROUNDS,
3497
3573
  DID_REGEX,
3498
3574
  DUST_LIMIT_SATS,
3499
3575
  DidBtcr2,
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=btcr2-update.js.map