@did-btcr2/method 0.63.0 → 0.65.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/dist/cjs/index.js CHANGED
@@ -32,6 +32,7 @@ __export(index_exports, {
32
32
  CASBeacon: () => CASBeacon,
33
33
  CASBeaconError: () => CASBeaconError,
34
34
  CHANGE_OUTPUT_VBYTES: () => CHANGE_OUTPUT_VBYTES,
35
+ DEACTIVATION_PATCH: () => DEACTIVATION_PATCH,
35
36
  DEFAULT_FEE_ESTIMATOR: () => DEFAULT_FEE_ESTIMATOR,
36
37
  DEFAULT_MIN_CONF: () => DEFAULT_MIN_CONF,
37
38
  DID_REGEX: () => DID_REGEX,
@@ -764,6 +765,43 @@ var Appendix = class _Appendix {
764
765
  const id = typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry.id : entry;
765
766
  return _Appendix.absoluteDidUrl(id, did);
766
767
  }
768
+ /**
769
+ * Finds the entry of `document.capabilityInvocation` that identifies `methodId`. A
770
+ * reference entry identifies it when the two DID URLs are equal. An embedded verification
771
+ * method object identifies it when its `id` is equal. Both spellings of a DID URL compare
772
+ * equal, as in {@link relationshipMethodId}. This is the lookup of the specification steps
773
+ * "Check `update.proof`" (the read path) and "Construct BTCR2 Signed Update" (the write
774
+ * path); the caller raises `INVALID_DID_UPDATE` when no entry identifies the method.
775
+ *
776
+ * @param {DidDocument} document The DID document.
777
+ * @param {unknown} methodId The verification method id, absolute or relative. A non-string yields `undefined`.
778
+ * @returns {string | DidVerificationMethod | undefined} The entry, or `undefined` if no entry identifies the id.
779
+ */
780
+ static capabilityInvocationEntry(document, methodId) {
781
+ const targetId = _Appendix.relationshipMethodId(methodId, document.id);
782
+ if (targetId === void 0) return void 0;
783
+ return document.capabilityInvocation?.find(
784
+ (entry) => _Appendix.relationshipMethodId(entry, document.id) === targetId
785
+ );
786
+ }
787
+ /**
788
+ * Returns the verification method that a relationship entry denotes: the object itself
789
+ * when the entry embeds the method, else the member of `document.verificationMethod` whose
790
+ * `id` equals the reference. Both spellings of a DID URL compare equal. The caller raises
791
+ * `INVALID_DID_UPDATE` when a reference names no member.
792
+ *
793
+ * @param {DidDocument} document The DID document.
794
+ * @param {string | DidVerificationMethod} entry The relationship entry: a reference, or an embedded method.
795
+ * @returns {DidVerificationMethod | undefined} The method, or `undefined` if a reference names no member.
796
+ */
797
+ static verificationMethodOfEntry(document, entry) {
798
+ if (_Appendix.isDidVerificationMethod(entry)) return entry;
799
+ const targetId = _Appendix.absoluteDidUrl(entry, document.id);
800
+ if (targetId === void 0) return void 0;
801
+ return document.verificationMethod?.find(
802
+ (method) => _Appendix.absoluteDidUrl(method?.id, document.id) === targetId
803
+ );
804
+ }
767
805
  /**
768
806
  * Validates that the given object is a DidVerificationMethod
769
807
  * @param {unknown} obj The object to validate
@@ -879,10 +917,11 @@ var Appendix = class _Appendix {
879
917
  */
880
918
  static dereferenceZcapId(capabilityId) {
881
919
  const rootCapability = {};
882
- const [urn, zcap, root, did] = capabilityId.split(":") ?? [];
883
- if ([urn, zcap, root, did].length !== 4) {
920
+ const components = capabilityId.split(":");
921
+ if (components.length !== 4) {
884
922
  throw new import_dids.DidError(import_dids.DidErrorCode.InvalidDid, `Invalid capabilityId: ${capabilityId}`);
885
923
  }
924
+ const [urn, zcap, root, did] = components;
886
925
  if (!urn || urn !== "urn") {
887
926
  throw new import_dids.DidError(import_dids.DidErrorCode.InvalidDid, `Invalid capabilityId: ${capabilityId}`);
888
927
  }
@@ -1979,822 +2018,497 @@ var BTCR2_UPDATE_CONTEXT = Object.freeze([
1979
2018
  function isBtcr2UpdateContext(value, expected = BTCR2_UPDATE_CONTEXT) {
1980
2019
  return Array.isArray(value) && value.length === expected.length && value.every((url, i) => url === expected[i]);
1981
2020
  }
2021
+ var DEACTIVATION_PATCH = Object.freeze({
2022
+ op: "add",
2023
+ path: "/deactivated",
2024
+ value: true
2025
+ });
1982
2026
 
1983
2027
  // src/core/did-sender-resolver.ts
1984
- var import_common14 = require("@did-btcr2/common");
1985
- var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
2028
+ var import_common13 = require("@did-btcr2/common");
2029
+ var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
1986
2030
  var import_keypair4 = require("@did-btcr2/keypair");
1987
2031
 
1988
2032
  // src/core/resolver.ts
1989
2033
  var import_bitcoin5 = require("@did-btcr2/bitcoin");
1990
- var import_common13 = require("@did-btcr2/common");
1991
- var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
1992
- var import_keypair3 = require("@did-btcr2/keypair");
1993
-
1994
- // src/did-btcr2.ts
1995
2034
  var import_common12 = require("@did-btcr2/common");
1996
- var import_dids2 = require("@web5/dids");
2035
+ var import_cryptosuite = require("@did-btcr2/cryptosuite");
2036
+ var import_keypair3 = require("@did-btcr2/keypair");
1997
2037
 
1998
- // src/core/updater.ts
2038
+ // src/utils/error-cause.ts
1999
2039
  var import_common11 = require("@did-btcr2/common");
2000
- var import_cryptosuite = require("@did-btcr2/cryptosuite");
2001
- var Updater = class _Updater {
2002
- #state = { phase: "Construct" };
2003
- #sourceDocument;
2004
- #patches;
2005
- #sourceVersionId;
2006
- #verificationMethod;
2007
- #beaconService;
2040
+ function errorCause(error) {
2041
+ if (error instanceof import_common11.DidMethodError) return { type: error.type, message: error.message };
2042
+ if (error instanceof Error) return { type: error.name, message: error.message };
2043
+ return { type: "unknown", message: String(error) };
2044
+ }
2045
+
2046
+ // src/core/resolver.ts
2047
+ var import_utils7 = require("@noble/curves/utils.js");
2048
+ var DEFAULT_MIN_CONF = 6;
2049
+ function isRecord(value) {
2050
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2051
+ }
2052
+ function isCASAnnouncement(value) {
2053
+ return isRecord(value) && Object.values(value).every((v) => typeof v === "string");
2054
+ }
2055
+ function isSignedBTCR2Update(value) {
2056
+ if (!isRecord(value)) return false;
2057
+ return Array.isArray(value.patch) && typeof value.sourceHash === "string" && typeof value.targetHash === "string" && Number.isInteger(value.targetVersionId) && value.targetVersionId >= 2 && isRecord(value.proof);
2058
+ }
2059
+ function isSMTProof(value) {
2060
+ if (!isRecord(value)) return false;
2061
+ return typeof value.id === "string" && typeof value.collapsed === "string" && Array.isArray(value.hashes);
2062
+ }
2063
+ function validateMinConf(value) {
2064
+ if (value === void 0) return DEFAULT_MIN_CONF;
2065
+ if (typeof value === "number" && Number.isInteger(value) && value >= 1) return value;
2066
+ throw new import_common12.ResolveError(
2067
+ `Invalid resolution option minConf: expected a positive integer (minimum 1), got ${shown(value)}.`,
2068
+ import_common12.INVALID_OPTIONS,
2069
+ { minConf: value }
2070
+ );
2071
+ }
2072
+ function shown(value) {
2073
+ return typeof value === "string" ? JSON.stringify(value) : String(value);
2074
+ }
2075
+ var ASCII_INTEGER = /^-?[0-9]+$/;
2076
+ function validateVersionId(value) {
2077
+ if (value === void 0) return void 0;
2078
+ if (typeof value === "string" && ASCII_INTEGER.test(value) && Number.isSafeInteger(Number(value))) {
2079
+ return Number(value);
2080
+ }
2081
+ throw new import_common12.ResolveError(
2082
+ `Invalid resolution option versionId: expected an ASCII string of an integer, got ${shown(value)}.`,
2083
+ import_common12.INVALID_OPTIONS,
2084
+ { versionId: value }
2085
+ );
2086
+ }
2087
+ var UTC_XSD_DATETIME = /^-?\d{4,}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
2088
+ var XSD_TIMEZONE = /(Z|[+-]\d{2}:\d{2})$/;
2089
+ function validateVersionTime(value) {
2090
+ if (value === void 0) return void 0;
2091
+ if (typeof value === "string" && UTC_XSD_DATETIME.test(value) && import_common12.DateUtils.isValidXsdDateTime(value)) {
2092
+ const ms = Date.parse(value);
2093
+ if (Number.isFinite(ms)) return ms;
2094
+ }
2095
+ throw new import_common12.ResolveError(
2096
+ `Invalid resolution option versionTime: expected an XML Datetime in UTC without a fraction (for example "2026-07-01T00:00:00Z"), got ${shown(value)}.`,
2097
+ import_common12.INVALID_OPTIONS,
2098
+ { versionTime: value }
2099
+ );
2100
+ }
2101
+ var Resolver = class _Resolver {
2102
+ // --- Immutable inputs ---
2103
+ #didComponents;
2104
+ /** The parsed `ResolutionOptions.versionId`, or `undefined` when the option is absent. */
2105
+ #versionId;
2106
+ /** The parsed `ResolutionOptions.versionTime` in milliseconds since the Unix epoch, or `undefined`. */
2107
+ #versionTime;
2008
2108
  /**
2009
- * @internal Use {@link DidBtcr2.update} to create instances.
2109
+ * The specific phase the Resolver is current in.
2010
2110
  */
2011
- constructor(params) {
2012
- this.#sourceDocument = params.sourceDocument;
2013
- this.#patches = params.patches;
2014
- this.#sourceVersionId = params.sourceVersionId;
2015
- this.#verificationMethod = params.verificationMethod;
2016
- this.#beaconService = params.beaconService;
2017
- }
2018
- // ─── Public static utility methods ─────────────────────────────────────────
2019
- // Used by generate-vector.ts and other scripts that need direct access to
2020
- // individual update steps outside the state machine flow.
2111
+ #phase;
2112
+ #sidecarData;
2113
+ #currentDocument;
2114
+ #providedGenesisDocument = null;
2115
+ #beaconServicesSignals = /* @__PURE__ */ new Map();
2116
+ #processedServices = /* @__PURE__ */ new Set();
2117
+ /** The beacon addresses the resolver requested signals for: `scanned_beacons` of the specification. */
2118
+ #requestCache = /* @__PURE__ */ new Set();
2021
2119
  /**
2022
- * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/update.html#construct-btcr2-unsigned-update | 7.3.b Construct BTCR2 Unsigned Update}.
2023
- *
2024
- * @param {Btcr2DidDocument} sourceDocument The source DID document to be updated.
2025
- * @param {PatchOperation[]} patches The JSON Patch operations to apply.
2026
- * @param {number} sourceVersionId The version ID of the source document.
2027
- * @returns {UnsignedBTCR2Update} The constructed UnsignedBTCR2Update object.
2028
- * @throws {UpdateError} If the target document fails DID Core validation.
2120
+ * The tuples of the specification's `updates` list: a signed update, the metadata of
2121
+ * the block that announced it, and the beacon address of the signal. BeaconProcess
2122
+ * appends; ProcessUpdate sorts the list and removes one tuple per step. A tuple that
2123
+ * one pass does not reach waits for the next.
2029
2124
  */
2030
- static construct(sourceDocument, patches, sourceVersionId) {
2031
- const unsignedUpdate = {
2032
- // The array the specification pins, as a fresh copy: the update is a plain JSON
2033
- // object that callers may edit, and the shared constant is frozen.
2034
- "@context": [...BTCR2_UPDATE_CONTEXT],
2035
- patch: patches,
2036
- targetHash: "",
2037
- targetVersionId: sourceVersionId + 1,
2038
- sourceHash: (0, import_common11.canonicalHash)(sourceDocument)
2039
- };
2040
- const targetDocument = import_common11.JSONPatch.apply(sourceDocument, patches);
2041
- if (targetDocument.id !== sourceDocument.id) {
2042
- throw new import_common11.UpdateError(
2043
- `Patches must not change the DID document id (source "${sourceDocument.id}" to target "${targetDocument.id}").`,
2044
- import_common11.INVALID_DID_UPDATE,
2045
- { sourceId: sourceDocument.id, targetId: targetDocument.id }
2125
+ #unsortedUpdates = [];
2126
+ #resolvedResponse = null;
2127
+ /**
2128
+ * The state of the specification loop, carried across every pass: the version counter
2129
+ * (`current_version_id`), the update-hash history that backs duplicate confirmation
2130
+ * (`update_hash_history`), the confirmations of the block that contains the most
2131
+ * recently applied unique update (`block_confirmations`), and the header time of that
2132
+ * block as `updated`. A pass that finds a new beacon address returns to discovery, so
2133
+ * the state must not restart: a restart would reject a linear history whose later
2134
+ * updates are announced on beacons that earlier updates added.
2135
+ */
2136
+ #currentVersionId = 1;
2137
+ #updateHashHistory = [];
2138
+ #blockConfirmations = 0;
2139
+ #updated;
2140
+ /**
2141
+ * Opt-in upper bound on multi-round beacon-discovery passes. `Infinity` (the
2142
+ * default) leaves discovery unbounded; termination is already guaranteed by
2143
+ * de-duplicating already-queried beacon addresses. A positive value is a
2144
+ * caller-imposed resource guard; a non-positive value or omission means no limit.
2145
+ */
2146
+ #maxDiscoveryRounds;
2147
+ /** Count of beacon-discovery passes driven by updates adding new beacon services. */
2148
+ #discoveryRounds = 0;
2149
+ /**
2150
+ * Minimum block confirmations a Beacon Signal must have before this resolver
2151
+ * processes it: `ResolutionOptions.minConf`, default {@link DEFAULT_MIN_CONF}.
2152
+ * Applied at signal intake in the BeaconProcess phase. A signal below the
2153
+ * threshold is excluded from the resolution; the rest of the signals are
2154
+ * processed.
2155
+ */
2156
+ #minConf;
2157
+ /**
2158
+ * @internal Use {@link DidBtcr2.resolve} to create instances.
2159
+ */
2160
+ constructor(didComponents, sidecarData, currentDocument, options) {
2161
+ this.#didComponents = didComponents;
2162
+ this.#sidecarData = sidecarData;
2163
+ this.#currentDocument = currentDocument;
2164
+ if (options?.versionId !== void 0 && options?.versionTime !== void 0) {
2165
+ throw new import_common12.ResolveError(
2166
+ "Invalid resolution options: versionId and versionTime are mutually exclusive. Pass one of them.",
2167
+ import_common12.INVALID_OPTIONS,
2168
+ { versionId: options.versionId, versionTime: options.versionTime }
2046
2169
  );
2047
2170
  }
2048
- try {
2049
- DidDocument.isValid(targetDocument);
2050
- } catch (error) {
2051
- throw new import_common11.UpdateError(
2052
- "Error validating targetDocument: " + (error instanceof Error ? error.message : String(error)),
2053
- import_common11.INVALID_DID_UPDATE,
2054
- targetDocument
2055
- );
2171
+ this.#versionId = validateVersionId(options?.versionId);
2172
+ this.#versionTime = validateVersionTime(options?.versionTime);
2173
+ const rounds = options?.maxDiscoveryRounds;
2174
+ this.#maxDiscoveryRounds = typeof rounds === "number" && rounds > 0 ? rounds : Infinity;
2175
+ this.#minConf = validateMinConf(options?.minConf);
2176
+ if (options?.genesisDocument) {
2177
+ this.#providedGenesisDocument = options.genesisDocument;
2056
2178
  }
2057
- unsignedUpdate.targetHash = (0, import_common11.canonicalHash)(targetDocument);
2058
- return unsignedUpdate;
2179
+ this.#phase = currentDocument ? "BeaconDiscovery" /* BeaconDiscovery */ : "GenesisDocument" /* GenesisDocument */;
2059
2180
  }
2060
2181
  /**
2061
- * Implements subsection {@link http://dcdpr.github.io/did-btcr2/operations/update.html#construct-btcr2-signed-update | 7.3.c Construct BTCR2 Signed Update }.
2062
- *
2063
- * @param {string} did The did-btcr2 identifier to derive the root capability from.
2064
- * @param {UnsignedBTCR2Update} unsignedUpdate The unsigned update to sign.
2065
- * @param {DidVerificationMethod} verificationMethod The verification method for signing.
2066
- * @param {Signer} signer Signer that produces the BIP-340 Schnorr signature.
2067
- * @returns {SignedBTCR2Update} The signed update with a Data Integrity proof.
2182
+ * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#if-genesis_bytes-is-a-secp256k1-public-key | 7.2.d.1 if genesis bytes is a secp256k1 Public Key}.
2183
+ * @param {DidComponents} didComponents The decoded components of the did.
2184
+ * @returns {DidDocument} The resolved DID Document object.
2068
2185
  */
2069
- static sign(did, unsignedUpdate, verificationMethod, signer) {
2070
- if (!did.startsWith("did:btcr2:")) {
2071
- throw new import_common11.UpdateError(
2072
- `Expected a did:btcr2 identifier for the root capability; got "${did}".`,
2073
- import_common11.INVALID_DID_UPDATE,
2074
- { did }
2075
- );
2076
- }
2077
- const controller = verificationMethod.controller;
2078
- const hashIdx = verificationMethod.id.indexOf("#");
2079
- if (hashIdx < 0) {
2080
- throw new import_common11.UpdateError(
2081
- `Verification method id must contain a fragment (e.g. "${verificationMethod.id}#initialKey"); got "${verificationMethod.id}".`,
2082
- import_common11.INVALID_DID_UPDATE,
2083
- { verificationMethodId: verificationMethod.id }
2084
- );
2085
- }
2086
- const id = verificationMethod.id.slice(hashIdx);
2087
- const multikey = import_cryptosuite.SchnorrMultikey.fromSigner(id, controller, signer);
2088
- const absoluteMethodId = hashIdx === 0 ? `${did}${id}` : verificationMethod.id;
2089
- const signerKey = multikey.publicKey.multibase.encoded;
2090
- if (verificationMethod.publicKeyMultibase && signerKey !== verificationMethod.publicKeyMultibase) {
2091
- throw new import_common11.UpdateError(
2092
- `Signing key does not match verification method "${verificationMethod.id}": the signer's public key differs from the method's published publicKeyMultibase.`,
2093
- import_common11.INVALID_DID_UPDATE,
2186
+ static deterministic(didComponents) {
2187
+ const genesisBytes = didComponents.genesisBytes;
2188
+ const did = Identifier.encode(genesisBytes, didComponents);
2189
+ const { multibase } = new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
2190
+ const service = BeaconUtils.generateBeaconServices({
2191
+ id: did,
2192
+ publicKey: genesisBytes,
2193
+ network: (0, import_bitcoin5.getNetwork)(didComponents.network),
2194
+ beaconType: "SingletonBeacon"
2195
+ });
2196
+ return new DidDocument({
2197
+ id: did,
2198
+ verificationMethod: [{
2199
+ id: `${did}#initialKey`,
2200
+ type: "Multikey",
2201
+ controller: did,
2202
+ publicKeyMultibase: multibase.encoded
2203
+ }],
2204
+ service
2205
+ });
2206
+ }
2207
+ /**
2208
+ * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#if-genesis_bytes-is-a-sha-256-hash | 7.2.d.2 if genesis_bytes is a SHA-256 Hash}.
2209
+ * @param {DidComponents} didComponents BTCR2 DID components used to resolve the DID Document
2210
+ * @param {object} genesisDocument The genesis document for resolving the DID Document.
2211
+ * @returns {DidDocument} The resolved DID Document object
2212
+ * @throws {ResolveError} `INVALID_DID` if the hash of the genesis document is not the genesis bytes of the identifier
2213
+ */
2214
+ static external(didComponents, genesisDocument) {
2215
+ const genesisDocumentHash = (0, import_common12.canonicalHashBytes)(genesisDocument);
2216
+ if (!(0, import_utils7.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
2217
+ throw new import_common12.ResolveError(
2218
+ `Initial document mismatch: genesisBytes !== genesisDocumentHash`,
2219
+ import_common12.INVALID_DID,
2094
2220
  {
2095
- verificationMethodId: verificationMethod.id,
2096
- expected: verificationMethod.publicKeyMultibase,
2097
- actual: signerKey
2221
+ genesisBytes: (0, import_common12.encode)(didComponents.genesisBytes, "hex"),
2222
+ genesisDocumentHash: (0, import_common12.encode)(genesisDocumentHash, "hex")
2098
2223
  }
2099
2224
  );
2100
2225
  }
2101
- const config = {
2102
- // The proof must carry the same array as the update. The cryptosuite copies the
2103
- // document @context into the proof when the document has one, so the two arrays
2104
- // are equal by construction; this value is the fallback for a document without one.
2105
- "@context": [...BTCR2_UPDATE_CONTEXT],
2106
- cryptosuite: "bip340-jcs-2025",
2107
- type: "DataIntegrityProof",
2108
- // The proof names the signing method by absolute DID URL, even when the document
2109
- // spells that method's own id relatively: a proof travels apart from the document
2110
- // that defines the method, so a bare `#initialKey` in it resolves against nothing.
2111
- // For a document that already spells its ids absolutely this is the id unchanged,
2112
- // so proofs over such documents are byte-identical to before.
2113
- verificationMethod: absoluteMethodId,
2114
- proofPurpose: "capabilityInvocation",
2115
- capability: `urn:zcap:root:${encodeURIComponent(did)}`,
2116
- capabilityAction: "Write"
2117
- };
2118
- const diproof = multikey.toCryptosuite().toDataIntegrityProof();
2119
- return diproof.addProof(unsignedUpdate, config);
2226
+ const did = Identifier.encode(didComponents.genesisBytes, didComponents);
2227
+ const currentDocument = JSON.parse(
2228
+ JSON.stringify(genesisDocument).replaceAll(ID_PLACEHOLDER_VALUE, did)
2229
+ );
2230
+ return new DidDocument(currentDocument);
2120
2231
  }
2121
2232
  /**
2122
- * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/update.html#announce-did-update | 7.3.d Announce DID Update}.
2123
- * Announces a signed update to the Bitcoin blockchain via the specified beacon.
2124
- *
2125
- * @param {BeaconService} beaconService The beacon service to broadcast through.
2126
- * @param {string} did The DID being updated. Required because a beacon service
2127
- * `id` may be a relative DID URL and so cannot supply the subject.
2128
- * @param {SignedBTCR2Update} update The signed update to announce.
2129
- * @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
2130
- * @param {BitcoinConnection} bitcoin The Bitcoin network connection.
2131
- * @param {CASBroadcastOptions} [options] Optional broadcast configuration (fee estimator,
2132
- * change address, and, for CAS beacons, a `casPublish` callback invoked before the
2133
- * transaction broadcast; other beacon types ignore `casPublish`).
2134
- * @returns {Promise<BroadcastResult>} The broadcast artifacts: the signed update, the signal
2135
- * txid, and any per-beacon-type sidecar data (CAS announcement / SMT proof).
2233
+ * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process-sidecar-data | Process Sidecar Data}
2234
+ * @param {Sidecar} sidecar The sidecar data to process.
2235
+ * @returns {SidecarData} The processed sidecar data containing maps of updates, CAS announcements, and SMT proofs.
2136
2236
  */
2137
- static async announce(beaconService, did, update, signer, bitcoin, options) {
2138
- const beacon = BeaconFactory.establish(beaconService, did);
2139
- return beacon.broadcastSignal(update, signer, bitcoin, options);
2140
- }
2141
- // Private instance wrappers
2142
- // Delegate to the public statics with bound instance fields for cleaner
2143
- // advance/provide code.
2144
- #construct() {
2145
- return _Updater.construct(this.#sourceDocument, this.#patches, this.#sourceVersionId);
2237
+ static sidecarData(sidecar = {}) {
2238
+ const updateMap = /* @__PURE__ */ new Map();
2239
+ if (sidecar.updates?.length)
2240
+ for (const update of sidecar.updates) {
2241
+ updateMap.set((0, import_common12.canonicalHash)(update, { encoding: "hex" }), update);
2242
+ }
2243
+ const casMap = /* @__PURE__ */ new Map();
2244
+ if (sidecar.casUpdates?.length)
2245
+ for (const update of sidecar.casUpdates) {
2246
+ casMap.set((0, import_common12.canonicalHash)(update, { encoding: "hex" }), update);
2247
+ }
2248
+ const smtMap = /* @__PURE__ */ new Map();
2249
+ if (sidecar.smtProofs?.length)
2250
+ for (const proof of sidecar.smtProofs) {
2251
+ smtMap.set((0, import_common12.encode)((0, import_common12.decode)(proof.id, "base64urlnopad"), "hex"), proof);
2252
+ }
2253
+ return { updateMap, casMap, smtMap };
2146
2254
  }
2147
2255
  /**
2148
- * Advance the state machine. Returns either:
2149
- * - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
2150
- * - `{ status: 'complete', result }` update is signed and broadcast
2256
+ * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#confirm-duplicate-update | Confirm Duplicate Update}.
2257
+ * This step confirms that an update with a lower-than-expected targetVersionId is a true duplicate.
2258
+ * @param {SignedBTCR2Update} update The BTCR2 Signed Update to confirm as a duplicate.
2259
+ * @param {HashBytes[]} updateHashHistory The accumulated hash history for comparison.
2260
+ * @returns {void} Does not return a value, but throws an error if the update is not a valid duplicate.
2151
2261
  */
2152
- advance() {
2153
- while (true) {
2154
- switch (this.#state.phase) {
2155
- // Phase: Construct
2156
- // Build the unsigned update from source doc + patches. Pure, synchronous.
2157
- case "Construct": {
2158
- const unsignedUpdate = this.#construct();
2159
- this.#state = { phase: "Sign", unsignedUpdate };
2160
- continue;
2161
- }
2162
- // Phase: Sign
2163
- // Emit NeedSigningKey: the caller supplies the secret key (or a KMS signature).
2164
- case "Sign": {
2165
- return {
2166
- status: "action-required",
2167
- needs: [{
2168
- kind: "NeedSigningKey",
2169
- verificationMethodId: this.#verificationMethod.id,
2170
- unsignedUpdate: this.#state.unsignedUpdate
2171
- }]
2172
- };
2173
- }
2174
- // Phase: Fund
2175
- // Emit NeedFunding with the beacon address. The caller checks UTXOs,
2176
- // funds the address if needed, and provides to continue.
2177
- case "Fund": {
2178
- const beaconAddress = this.#beaconService.serviceEndpoint.replace("bitcoin:", "");
2179
- return {
2180
- status: "action-required",
2181
- needs: [{
2182
- kind: "NeedFunding",
2183
- beaconAddress,
2184
- beaconService: this.#beaconService
2185
- }]
2186
- };
2187
- }
2188
- // Phase: Broadcast
2189
- // Emit NeedBroadcast with the signed update + beacon service. The caller performs
2190
- // the actual on-chain announcement (or hands off to the aggregation protocol).
2191
- case "Broadcast": {
2192
- return {
2193
- status: "action-required",
2194
- needs: [{
2195
- kind: "NeedBroadcast",
2196
- beaconService: this.#beaconService,
2197
- signedUpdate: this.#state.signedUpdate,
2198
- did: this.#sourceDocument.id
2199
- }]
2200
- };
2262
+ static confirmDuplicate(update, updateHashHistory) {
2263
+ if (!Number.isInteger(update.targetVersionId) || update.targetVersionId < 2) {
2264
+ throw new import_common12.ResolveError(
2265
+ `Invalid duplicate: targetVersionId must be an integer >= 2`,
2266
+ import_common12.INVALID_DID_UPDATE,
2267
+ { targetVersionId: update.targetVersionId }
2268
+ );
2269
+ }
2270
+ const { proof: _, ...unsignedUpdate } = update;
2271
+ const unsignedUpdateHash = (0, import_common12.canonicalHashBytes)(unsignedUpdate);
2272
+ const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
2273
+ if (historicalUpdateHash === void 0) {
2274
+ throw new import_common12.ResolveError(
2275
+ `Invalid duplicate: no applied update in history for targetVersionId`,
2276
+ import_common12.LATE_PUBLISHING_ERROR,
2277
+ {
2278
+ targetVersionId: update.targetVersionId,
2279
+ historyLength: updateHashHistory.length
2201
2280
  }
2202
- // Phase: Complete
2203
- case "Complete": {
2204
- return {
2205
- status: "complete",
2206
- result: { signedUpdate: this.#state.signedUpdate }
2207
- };
2281
+ );
2282
+ }
2283
+ if (!(0, import_utils7.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
2284
+ throw new import_common12.ResolveError(
2285
+ `Invalid duplicate: unsigned update hash does not match historical hash`,
2286
+ import_common12.LATE_PUBLISHING_ERROR,
2287
+ {
2288
+ unsignedUpdateHash: (0, import_common12.encode)(unsignedUpdateHash, "hex"),
2289
+ historicalHash: (0, import_common12.encode)(historicalUpdateHash, "hex")
2208
2290
  }
2209
- }
2291
+ );
2210
2292
  }
2211
2293
  }
2212
- provide(need, data) {
2213
- switch (need.kind) {
2214
- case "NeedSigningKey": {
2215
- if (this.#state.phase !== "Sign") {
2216
- throw new import_common11.UpdateError(
2217
- `Cannot provide NeedSigningKey: updater phase is ${this.#state.phase}, expected Sign.`,
2218
- import_common11.INVALID_DID_UPDATE,
2219
- { phase: this.#state.phase }
2220
- );
2221
- }
2222
- if (!data) {
2223
- throw new import_common11.UpdateError(
2224
- "NeedSigningKey requires a Signer.",
2225
- import_common11.INVALID_DID_UPDATE
2226
- );
2227
- }
2228
- const unsignedUpdate = this.#state.unsignedUpdate;
2229
- const signedUpdate = _Updater.sign(
2230
- this.#sourceDocument.id,
2231
- unsignedUpdate,
2232
- this.#verificationMethod,
2233
- data
2294
+ /**
2295
+ * Decode a hash of a BTCR2 Update (`sourceHash` or `targetHash`). The specification encodes
2296
+ * both with base64url without padding.
2297
+ * @param {unknown} value The encoded hash.
2298
+ * @param {'sourceHash' | 'targetHash'} field The name of the field, for the error.
2299
+ * @returns {HashBytes} The decoded bytes.
2300
+ * @throws {ResolveError} `INVALID_DID_UPDATE` if the value is not a string or does not decode.
2301
+ */
2302
+ static decodeUpdateHash(value, field) {
2303
+ if (typeof value === "string") {
2304
+ try {
2305
+ return (0, import_common12.decode)(value, "base64urlnopad");
2306
+ } catch (error) {
2307
+ throw new import_common12.ResolveError(
2308
+ `Invalid update: ${field} does not decode as base64url: ${errorCause(error).message}`,
2309
+ import_common12.INVALID_DID_UPDATE,
2310
+ { [field]: value, cause: errorCause(error) }
2234
2311
  );
2235
- this.#state = { phase: "Fund", unsignedUpdate, signedUpdate };
2236
- break;
2237
- }
2238
- case "NeedFunding": {
2239
- if (this.#state.phase !== "Fund") {
2240
- throw new import_common11.UpdateError(
2241
- `Cannot provide NeedFunding: updater phase is ${this.#state.phase}, expected Fund.`,
2242
- import_common11.INVALID_DID_UPDATE,
2243
- { phase: this.#state.phase }
2244
- );
2245
- }
2246
- if (data !== void 0) {
2247
- const proof = data;
2248
- if (typeof proof.utxoCount !== "number" || !Number.isFinite(proof.utxoCount) || proof.utxoCount < 1) {
2249
- throw new import_common11.UpdateError(
2250
- `NeedFunding proof must have utxoCount >= 1; got ${String(proof.utxoCount)}.`,
2251
- import_common11.INVALID_DID_UPDATE,
2252
- { utxoCount: proof.utxoCount }
2253
- );
2254
- }
2255
- }
2256
- this.#state = {
2257
- phase: "Broadcast",
2258
- unsignedUpdate: this.#state.unsignedUpdate,
2259
- signedUpdate: this.#state.signedUpdate
2260
- };
2261
- break;
2262
- }
2263
- case "NeedBroadcast": {
2264
- if (this.#state.phase !== "Broadcast") {
2265
- throw new import_common11.UpdateError(
2266
- `Cannot provide NeedBroadcast: updater phase is ${this.#state.phase}, expected Broadcast.`,
2267
- import_common11.INVALID_DID_UPDATE,
2268
- { phase: this.#state.phase }
2269
- );
2270
- }
2271
- this.#state = { phase: "Complete", signedUpdate: this.#state.signedUpdate };
2272
- break;
2273
2312
  }
2274
2313
  }
2314
+ throw new import_common12.ResolveError(`Invalid update: ${field} is not a string`, import_common12.INVALID_DID_UPDATE, { [field]: value });
2275
2315
  }
2276
- };
2277
-
2278
- // src/did-btcr2.ts
2279
- var DidBtcr2 = class {
2280
2316
  /**
2281
- * Name of the DID method, as defined in the DID BTCR2 specification
2317
+ * Parse a `created` or `expires` value of an update proof. Data Integrity types both as an
2318
+ * XML Schema `dateTimeStamp`: an XML Datetime with a timezone. A value without a timezone
2319
+ * names no fixed instant, so two resolvers would read two instants; it is rejected.
2320
+ * @param {Btcr2DataIntegrityProof} proof The update proof.
2321
+ * @param {'created' | 'expires'} field The field to parse.
2322
+ * @returns {number | undefined} The instant in milliseconds since the Unix epoch, or `undefined` when the field is absent.
2323
+ * @throws {ResolveError} `INVALID_DID_UPDATE` for a value that is not an XML Datetime with a timezone.
2282
2324
  */
2283
- static methodName = "btcr2";
2325
+ static proofInstant(proof, field) {
2326
+ const value = proof[field];
2327
+ if (value === void 0) return void 0;
2328
+ if (typeof value === "string" && XSD_TIMEZONE.test(value) && import_common12.DateUtils.isValidXsdDateTime(value)) {
2329
+ const ms = Date.parse(value);
2330
+ if (Number.isFinite(ms)) return ms;
2331
+ }
2332
+ throw new import_common12.ResolveError(
2333
+ `Invalid update: proof.${field} is not an XML Datetime with a timezone`,
2334
+ import_common12.INVALID_DID_UPDATE,
2335
+ { [field]: value }
2336
+ );
2337
+ }
2284
2338
  /**
2285
- * Implements section {@link https://dcdpr.github.io/did-btcr2/operations/create.html | 7.1 Create}.
2286
- * @param {KeyBytes | DocumentBytes} genesisBytes The bytes used to create the genesis document for a did:btcr2 identifier.
2287
- * This can be either the bytes of the genesis document itself or the bytes of a key that will be used to create the genesis document.
2288
- * @param {DidCreateOptions} options Options for creating the identifier, including the idType (key or external), version, and network.
2289
- * @param {string} options.idType The type of identifier to create, either 'KEY' or 'EXTERNAL'. Defaults to 'KEY'.
2290
- * @param {number} options.version The version number of the did:btcr2 specification to use for creating the identifier. Defaults to 1.
2291
- * @param {string} options.network The Bitcoin network to use for the identifier, e.g. 'bitcoin', 'testnet', etc. Defaults to 'bitcoin'.
2292
- * @returns {Promise<string>} Promise resolving to an identifier string.
2293
- * @throws {MethodError} if any of the checks fail
2294
- * @example
2295
- * ```ts
2296
- * const genesisBytes = SchnorrKeyPair.generate().publicKey.compressed;
2297
- * const did = DidBtcr2.create(genesisBytes, { idType: 'KEY', network: 'regtest' });
2298
- * ```
2299
- */
2300
- static create(genesisBytes, options) {
2301
- const { idType, version = 1, network = "bitcoin" } = options || {};
2302
- if (!idType) {
2303
- throw new import_common12.MethodError(
2304
- "idType is required for creating a did:btcr2 identifier",
2305
- import_common12.INVALID_DID_DOCUMENT,
2306
- options
2339
+ * Spec "Check `update.proof`": the proof time window against the block that contains the
2340
+ * Beacon Signal. `created` must not be after the header time of the block: a controller
2341
+ * signs a short time before the block, and on mainnet the header time is about one hour
2342
+ * after the `mediantime`. `expires` must not be before the block `mediantime`: it limits a
2343
+ * replay, and a single miner cannot change `mediantime`. `expires` must not be before
2344
+ * `created`. Each comparison has no tolerance.
2345
+ * @param {Btcr2DataIntegrityProof} proof The update proof.
2346
+ * @param {BlockMetadata} block The block of the Beacon Signal.
2347
+ * @throws {ResolveError} `INVALID_DID_UPDATE` if a value is outside the window.
2348
+ */
2349
+ static checkProofWindow(proof, block) {
2350
+ const created = _Resolver.proofInstant(proof, "created");
2351
+ const expires = _Resolver.proofInstant(proof, "expires");
2352
+ if (created !== void 0 && created > block.time * 1e3) {
2353
+ throw new import_common12.ResolveError(
2354
+ "Invalid update: proof.created is after the header time of the block that contains the Beacon Signal",
2355
+ import_common12.INVALID_DID_UPDATE,
2356
+ { created: proof.created, blockTime: block.time }
2357
+ );
2358
+ }
2359
+ if (expires !== void 0 && expires < block.mediantime * 1e3) {
2360
+ throw new import_common12.ResolveError(
2361
+ "Invalid update: proof.expires is before the mediantime of the block that contains the Beacon Signal",
2362
+ import_common12.INVALID_DID_UPDATE,
2363
+ { expires: proof.expires, mediantime: block.mediantime }
2364
+ );
2365
+ }
2366
+ if (created !== void 0 && expires !== void 0 && expires < created) {
2367
+ throw new import_common12.ResolveError(
2368
+ "Invalid update: proof.expires is before proof.created",
2369
+ import_common12.INVALID_DID_UPDATE,
2370
+ { created: proof.created, expires: proof.expires }
2307
2371
  );
2308
2372
  }
2309
- return Identifier.encode(genesisBytes, { idType, version, network });
2310
2373
  }
2311
2374
  /**
2312
- * Entry point for section {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html | 7.2 Resolve}.
2313
- *
2314
- * Factory method that performs pure setup and returns a {@link Resolver} state machine.
2315
- * The caller drives resolution by calling `resolver.resolve()` and `resolver.provide()`.
2316
- * Analogous to Rust's `Document::read()`.
2317
- *
2318
- * @param {string} did The did:btcr2 identifier to be resolved.
2319
- * @param {ResolutionOptions} resolutionOptions Options used during the resolution process.
2320
- * @returns {Resolver} A sans-I/O state machine the caller drives to completion.
2321
- * @example
2322
- * ```ts
2323
- * const resolver = DidBtcr2.resolve(did, { sidecar });
2324
- * let state = resolver.resolve();
2325
- * while (state.status === 'action-required') {
2326
- * for (const need of state.needs) { ... provide data ... }
2327
- * state = resolver.resolve();
2328
- * }
2329
- * const { didDocument, metadata } = state.result;
2330
- * ```
2375
+ * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#apply-update | Apply update}
2376
+ * and its step {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#check-update-proof | Check update.proof}.
2377
+ * Every failure that the specification names raises `INVALID_DID_UPDATE`. An error of the
2378
+ * cryptosuite, the multikey, the hash decoder, or the patch rides along as `data.cause`.
2379
+ * @param {DidDocument} currentDocument The current DID Document to apply the update to.
2380
+ * @param {SignedBTCR2Update} update The BTCR2 Signed Update to apply.
2381
+ * @param {BlockMetadata} block The block that contains the Beacon Signal that announced the update.
2382
+ * @returns {DidDocument} The updated DID Document after applying the update.
2383
+ * @throws {ResolveError} `INVALID_DID_UPDATE` if the update is invalid or cannot be applied.
2331
2384
  */
2332
- static resolve(did, resolutionOptions = {}) {
2333
- const didComponents = Identifier.decode(did);
2334
- const sidecarData = Resolver.sidecarData(resolutionOptions.sidecar);
2335
- const currentDocument = didComponents.hrp === import_common12.IdentifierHrp.k ? Resolver.deterministic(didComponents) : null;
2336
- return new Resolver(didComponents, sidecarData, currentDocument, {
2337
- versionId: resolutionOptions.versionId,
2338
- versionTime: resolutionOptions.versionTime,
2339
- genesisDocument: resolutionOptions.sidecar?.genesisDocument,
2340
- maxDiscoveryRounds: resolutionOptions.maxDiscoveryRounds,
2341
- minConf: resolutionOptions.minConf
2342
- });
2343
- }
2344
- /**
2345
- * Entry point for section {@link https://dcdpr.github.io/did-btcr2/#update | 7.3 Update}.
2346
- *
2347
- * Factory method that validates the update parameters and returns a sans-I/O
2348
- * {@link Updater} state machine. The caller drives the updater through its
2349
- * phases (Construct -> Sign -> Broadcast -> Complete) by calling `advance()` and
2350
- * `provide()`. The method package performs **zero I/O**: signing key retrieval
2351
- * (or KMS delegation) and the on-chain broadcast are the caller's responsibility.
2352
- *
2353
- * For a fully-wired version with Bitcoin broadcast and key handling, see
2354
- * `DidMethodApi.update()` in `@did-btcr2/api`.
2355
- *
2356
- * @param params Update construction parameters.
2357
- * @param {Btcr2DidDocument} params.sourceDocument The DID document being updated.
2358
- * @param {PatchOperation[]} params.patches The JSON Patch operations to apply.
2359
- * @param {number} params.sourceVersionId The version ID before applying the update.
2360
- * @param {string} params.verificationMethodId The verification method ID to sign with.
2361
- * @param {string} params.beaconId The beacon service ID to broadcast through.
2362
- * @returns {Updater} A sans-I/O state machine for driving the update.
2363
- * @throws {UpdateError} If the verification method is not authorized, not found,
2364
- * not of type `Multikey`, or does not have a `zQ3s` publicKeyMultibase prefix.
2365
- * Also throws if the beacon service is not found.
2366
- */
2367
- static update({
2368
- sourceDocument,
2369
- patches,
2370
- sourceVersionId,
2371
- verificationMethodId,
2372
- beaconId
2373
- }) {
2374
- const authorizedMethodId = Appendix.relationshipMethodId(verificationMethodId, sourceDocument.id);
2375
- const authorized = authorizedMethodId !== void 0 && sourceDocument.capabilityInvocation?.some(
2376
- (entry) => Appendix.relationshipMethodId(entry, sourceDocument.id) === authorizedMethodId
2377
- );
2378
- if (!authorized) {
2379
- throw new import_common12.UpdateError(
2380
- "Invalid verificationMethodId: not authorized for capabilityInvocation",
2381
- import_common12.INVALID_DID_DOCUMENT,
2382
- sourceDocument
2385
+ static applyUpdate(currentDocument, update, block) {
2386
+ const currentDocumentHash = (0, import_common12.canonicalHashBytes)(currentDocument);
2387
+ const sourceHashBytes = _Resolver.decodeUpdateHash(update.sourceHash, "sourceHash");
2388
+ if (!(0, import_utils7.equalBytes)(sourceHashBytes, currentDocumentHash)) {
2389
+ throw new import_common12.ResolveError(
2390
+ `Hash mismatch: update.sourceHash !== currentDocumentHash`,
2391
+ import_common12.INVALID_DID_UPDATE,
2392
+ {
2393
+ sourceHash: update.sourceHash,
2394
+ currentDocumentHash: (0, import_common12.encode)(currentDocumentHash, "hex")
2395
+ }
2383
2396
  );
2384
2397
  }
2385
- const verificationMethod = this.getSigningMethod(sourceDocument, verificationMethodId);
2386
- if (!verificationMethod) {
2387
- throw new import_common12.UpdateError(
2388
- "Invalid verificationMethod: not found in source document",
2389
- import_common12.INVALID_DID_DOCUMENT,
2390
- { sourceDocument, verificationMethodId }
2398
+ if (!isBtcr2UpdateContext(update["@context"])) {
2399
+ throw new import_common12.ResolveError(
2400
+ "Invalid update: @context is not the array the specification pins for a BTCR2 Update",
2401
+ import_common12.INVALID_DID_UPDATE,
2402
+ { context: update["@context"], expected: [...BTCR2_UPDATE_CONTEXT] }
2391
2403
  );
2392
2404
  }
2393
- if (verificationMethod.type !== MULTIKEY_VERIFICATION_METHOD_TYPE) {
2394
- throw new import_common12.UpdateError(
2395
- `Invalid verificationMethod: verificationMethod.type must be "${MULTIKEY_VERIFICATION_METHOD_TYPE}"`,
2396
- import_common12.INVALID_DID_DOCUMENT,
2397
- verificationMethod
2405
+ if (!isBtcr2UpdateContext(update.proof?.["@context"], update["@context"])) {
2406
+ throw new import_common12.ResolveError(
2407
+ "Invalid update: proof @context does not equal the update @context",
2408
+ import_common12.INVALID_DID_UPDATE,
2409
+ { proofContext: update.proof?.["@context"], context: update["@context"] }
2398
2410
  );
2399
2411
  }
2400
- if (!verificationMethod.publicKeyMultibase?.startsWith(MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX)) {
2401
- throw new import_common12.UpdateError(
2402
- `Invalid verificationMethodId: publicKeyMultibase prefix must start with "${MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX}"`,
2403
- import_common12.INVALID_DID_DOCUMENT,
2404
- verificationMethod
2412
+ const proof = update.proof;
2413
+ const expectedFields = [
2414
+ ["type", "DataIntegrityProof"],
2415
+ ["cryptosuite", "bip340-jcs-2025"],
2416
+ ["proofPurpose", "capabilityInvocation"],
2417
+ ["capabilityAction", "Write"],
2418
+ ["capability", `urn:zcap:root:${encodeURIComponent(currentDocument.id)}`]
2419
+ ];
2420
+ for (const [field, expected] of expectedFields) {
2421
+ const actual = proof[field];
2422
+ if (actual !== expected) {
2423
+ throw new import_common12.ResolveError(
2424
+ `Invalid update: proof.${field} must equal "${expected}"`,
2425
+ import_common12.INVALID_DID_UPDATE,
2426
+ { field, expected, actual }
2427
+ );
2428
+ }
2429
+ }
2430
+ const verificationMethodId = proof.verificationMethod;
2431
+ const entry = Appendix.capabilityInvocationEntry(currentDocument, verificationMethodId);
2432
+ if (entry === void 0) {
2433
+ throw new import_common12.ResolveError(
2434
+ "Invalid update: verificationMethod is not authorized for capabilityInvocation",
2435
+ import_common12.INVALID_DID_UPDATE,
2436
+ {
2437
+ verificationMethodId,
2438
+ capabilityInvocation: currentDocument.capabilityInvocation
2439
+ }
2405
2440
  );
2406
2441
  }
2407
- const targetBeaconId = Appendix.absoluteDidUrl(beaconId, sourceDocument.id);
2408
- const beaconService = sourceDocument.service.filter((service) => targetBeaconId !== void 0 && Appendix.absoluteDidUrl(service.id, sourceDocument.id) === targetBeaconId).filter((service) => !!service).shift();
2409
- if (!beaconService) {
2410
- throw new import_common12.UpdateError(
2411
- "No beacon service found for provided beaconId",
2442
+ const vm = Appendix.verificationMethodOfEntry(currentDocument, entry);
2443
+ if (vm === void 0) {
2444
+ throw new import_common12.ResolveError(
2445
+ "Invalid update: verificationMethod is not found in the verificationMethod of the current document",
2412
2446
  import_common12.INVALID_DID_UPDATE,
2413
- { sourceDocument, beaconId }
2447
+ { verificationMethodId }
2414
2448
  );
2415
2449
  }
2416
- return new Updater({
2417
- sourceDocument,
2418
- patches,
2419
- sourceVersionId,
2420
- verificationMethod,
2421
- beaconService
2422
- });
2423
- }
2424
- /**
2425
- * Given the W3C DID Document of a `did:btcr2` identifier, return the signing verification method that will be used
2426
- * for signing messages and credentials. If given, the `methodId` parameter is used to select the
2427
- * verification method. If not given, the Identity Key's verification method with an ID fragment
2428
- * of '#initialKey' is used.
2429
- * @param {Btcr2DidDocument} didDocument The DID Document of the `did:btcr2` identifier.
2430
- * @param {string} [methodId] Optional verification method ID to be used for signing.
2431
- * @returns {DidVerificationMethod} Promise resolving to the {@link DidVerificationMethod} object used for signing.
2432
- * @throws {DidError} if the parsed did method does not match `btcr2` or signing method could not be determined.
2433
- */
2434
- static getSigningMethod(didDocument, methodId) {
2435
- methodId ??= "#initialKey";
2436
- const parsedDid = import_dids2.Did.parse(didDocument.id);
2437
- if (parsedDid && parsedDid.method !== this.methodName) {
2438
- throw new import_common12.MethodError(`Method not supported: ${parsedDid.method}`, import_common12.METHOD_NOT_SUPPORTED, { identifier: didDocument.id });
2450
+ _Resolver.checkProofWindow(proof, block);
2451
+ let verified;
2452
+ try {
2453
+ const multikey = import_cryptosuite.SchnorrMultikey.fromVerificationMethod({
2454
+ ...vm,
2455
+ id: Appendix.absoluteDidUrl(vm.id, currentDocument.id) ?? vm.id
2456
+ });
2457
+ const diProof = new import_cryptosuite.BIP340DataIntegrityProof(new import_cryptosuite.BIP340Cryptosuite(multikey));
2458
+ verified = diProof.verifyProof((0, import_common12.canonicalize)(update), "capabilityInvocation").verified;
2459
+ } catch (error) {
2460
+ throw new import_common12.ResolveError(
2461
+ `Invalid update: proof verification failed: ${errorCause(error).message}`,
2462
+ import_common12.INVALID_DID_UPDATE,
2463
+ { verificationMethodId, cause: errorCause(error) }
2464
+ );
2439
2465
  }
2440
- const targetId = Appendix.absoluteDidUrl(methodId, didDocument.id) ?? Appendix.relationshipMethodId(didDocument.assertionMethod?.[0], didDocument.id);
2441
- const verificationMethod = targetId === void 0 ? void 0 : didDocument.verificationMethod?.find(
2442
- (vm) => Appendix.absoluteDidUrl(vm.id, didDocument.id) === targetId
2443
- );
2444
- if (!(verificationMethod && verificationMethod.publicKeyMultibase)) {
2445
- throw new import_dids2.DidError(
2446
- import_dids2.DidErrorCode.InternalError,
2447
- "A verification method intended for signing could not be determined from the DID Document"
2466
+ if (!verified) {
2467
+ throw new import_common12.ResolveError("Invalid update: proof not verified", import_common12.INVALID_DID_UPDATE, { verificationMethodId });
2468
+ }
2469
+ let updatedDocument;
2470
+ try {
2471
+ updatedDocument = import_common12.JSONPatch.apply(currentDocument, update.patch, { strict: true });
2472
+ } catch (error) {
2473
+ throw new import_common12.ResolveError(
2474
+ `Invalid update: ${errorCause(error).message}`,
2475
+ import_common12.INVALID_DID_UPDATE,
2476
+ { cause: errorCause(error) }
2448
2477
  );
2449
2478
  }
2450
- return verificationMethod;
2451
- }
2452
- };
2453
-
2454
- // src/core/resolver.ts
2455
- var import_utils7 = require("@noble/curves/utils.js");
2456
- var DEFAULT_MIN_CONF = 6;
2457
- function isRecord(value) {
2458
- return typeof value === "object" && value !== null && !Array.isArray(value);
2459
- }
2460
- function isCASAnnouncement(value) {
2461
- return isRecord(value) && Object.values(value).every((v) => typeof v === "string");
2462
- }
2463
- function isSignedBTCR2Update(value) {
2464
- if (!isRecord(value)) return false;
2465
- return Array.isArray(value.patch) && typeof value.sourceHash === "string" && typeof value.targetHash === "string" && Number.isInteger(value.targetVersionId) && value.targetVersionId >= 2 && isRecord(value.proof);
2466
- }
2467
- function isSMTProof(value) {
2468
- if (!isRecord(value)) return false;
2469
- return typeof value.id === "string" && typeof value.collapsed === "string" && Array.isArray(value.hashes);
2470
- }
2471
- function validateMinConf(value) {
2472
- if (value === void 0) return DEFAULT_MIN_CONF;
2473
- if (typeof value === "number" && Number.isInteger(value) && value >= 1) return value;
2474
- throw new import_common13.ResolveError(
2475
- `Invalid resolution option minConf: expected a positive integer (minimum 1), got ${shown(value)}.`,
2476
- import_common13.INVALID_OPTIONS,
2477
- { minConf: value }
2478
- );
2479
- }
2480
- function shown(value) {
2481
- return typeof value === "string" ? JSON.stringify(value) : String(value);
2482
- }
2483
- var ASCII_INTEGER = /^-?[0-9]+$/;
2484
- function validateVersionId(value) {
2485
- if (value === void 0) return void 0;
2486
- if (typeof value === "string" && ASCII_INTEGER.test(value) && Number.isSafeInteger(Number(value))) {
2487
- return Number(value);
2488
- }
2489
- throw new import_common13.ResolveError(
2490
- `Invalid resolution option versionId: expected an ASCII string of an integer, got ${shown(value)}.`,
2491
- import_common13.INVALID_OPTIONS,
2492
- { versionId: value }
2493
- );
2494
- }
2495
- var UTC_XSD_DATETIME = /^-?\d{4,}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
2496
- function validateVersionTime(value) {
2497
- if (value === void 0) return void 0;
2498
- if (typeof value === "string" && UTC_XSD_DATETIME.test(value) && import_common13.DateUtils.isValidXsdDateTime(value)) {
2499
- const ms = Date.parse(value);
2500
- if (Number.isFinite(ms)) return ms;
2501
- }
2502
- throw new import_common13.ResolveError(
2503
- `Invalid resolution option versionTime: expected an XML Datetime in UTC without a fraction (for example "2026-07-01T00:00:00Z"), got ${shown(value)}.`,
2504
- import_common13.INVALID_OPTIONS,
2505
- { versionTime: value }
2506
- );
2507
- }
2508
- var Resolver = class _Resolver {
2509
- // --- Immutable inputs ---
2510
- #didComponents;
2511
- /** The parsed `ResolutionOptions.versionId`, or `undefined` when the option is absent. */
2512
- #versionId;
2513
- /** The parsed `ResolutionOptions.versionTime` in milliseconds since the Unix epoch, or `undefined`. */
2514
- #versionTime;
2515
- /**
2516
- * The specific phase the Resolver is current in.
2517
- */
2518
- #phase;
2519
- #sidecarData;
2520
- #currentDocument;
2521
- #providedGenesisDocument = null;
2522
- #beaconServicesSignals = /* @__PURE__ */ new Map();
2523
- #processedServices = /* @__PURE__ */ new Set();
2524
- /** The beacon addresses the resolver requested signals for: `scanned_beacons` of the specification. */
2525
- #requestCache = /* @__PURE__ */ new Set();
2526
- /**
2527
- * The tuples of the specification's `updates` list: a signed update and the metadata of
2528
- * the block that announced it. BeaconProcess appends; ProcessUpdate sorts the list and
2529
- * removes one tuple per step. A tuple that one pass does not reach waits for the next.
2530
- */
2531
- #unsortedUpdates = [];
2532
- #resolvedResponse = null;
2533
- /**
2534
- * The state of the specification loop, carried across every pass: the version counter
2535
- * (`current_version_id`), the update-hash history that backs duplicate confirmation
2536
- * (`update_hash_history`), the confirmations of the block that contains the most
2537
- * recently applied unique update (`block_confirmations`), and the header time of that
2538
- * block as `updated`. A pass that finds a new beacon address returns to discovery, so
2539
- * the state must not restart: a restart would reject a linear history whose later
2540
- * updates are announced on beacons that earlier updates added.
2541
- */
2542
- #currentVersionId = 1;
2543
- #updateHashHistory = [];
2544
- #blockConfirmations = 0;
2545
- #updated;
2546
- /**
2547
- * Opt-in upper bound on multi-round beacon-discovery passes. `Infinity` (the
2548
- * default) leaves discovery unbounded; termination is already guaranteed by
2549
- * de-duplicating already-queried beacon addresses. A positive value is a
2550
- * caller-imposed resource guard; a non-positive value or omission means no limit.
2551
- */
2552
- #maxDiscoveryRounds;
2553
- /** Count of beacon-discovery passes driven by updates adding new beacon services. */
2554
- #discoveryRounds = 0;
2555
- /**
2556
- * Minimum block confirmations a Beacon Signal must have before this resolver
2557
- * processes it: `ResolutionOptions.minConf`, default {@link DEFAULT_MIN_CONF}.
2558
- * Applied at signal intake in the BeaconProcess phase. A signal below the
2559
- * threshold is excluded from the resolution; the rest of the signals are
2560
- * processed.
2561
- */
2562
- #minConf;
2563
- /**
2564
- * @internal Use {@link DidBtcr2.resolve} to create instances.
2565
- */
2566
- constructor(didComponents, sidecarData, currentDocument, options) {
2567
- this.#didComponents = didComponents;
2568
- this.#sidecarData = sidecarData;
2569
- this.#currentDocument = currentDocument;
2570
- if (options?.versionId !== void 0 && options?.versionTime !== void 0) {
2571
- throw new import_common13.ResolveError(
2572
- "Invalid resolution options: versionId and versionTime are mutually exclusive. Pass one of them.",
2573
- import_common13.INVALID_OPTIONS,
2574
- { versionId: options.versionId, versionTime: options.versionTime }
2479
+ if (updatedDocument?.id !== currentDocument.id) {
2480
+ throw new import_common12.ResolveError(
2481
+ `Invalid update: the patch changes the document id (from "${currentDocument.id}" to "${String(updatedDocument?.id)}")`,
2482
+ import_common12.INVALID_DID_UPDATE,
2483
+ { sourceId: currentDocument.id, targetId: updatedDocument?.id }
2575
2484
  );
2576
2485
  }
2577
- this.#versionId = validateVersionId(options?.versionId);
2578
- this.#versionTime = validateVersionTime(options?.versionTime);
2579
- const rounds = options?.maxDiscoveryRounds;
2580
- this.#maxDiscoveryRounds = typeof rounds === "number" && rounds > 0 ? rounds : Infinity;
2581
- this.#minConf = validateMinConf(options?.minConf);
2582
- if (options?.genesisDocument) {
2583
- this.#providedGenesisDocument = options.genesisDocument;
2486
+ try {
2487
+ DidDocument.validate(updatedDocument);
2488
+ } catch (error) {
2489
+ throw new import_common12.ResolveError(
2490
+ `Invalid update: the patched document does not conform to DID Core: ${errorCause(error).message}`,
2491
+ import_common12.INVALID_DID_UPDATE,
2492
+ { cause: errorCause(error) }
2493
+ );
2584
2494
  }
2585
- this.#phase = currentDocument ? "BeaconDiscovery" /* BeaconDiscovery */ : "GenesisDocument" /* GenesisDocument */;
2495
+ const updatedDocumentHash = (0, import_common12.canonicalHashBytes)(updatedDocument);
2496
+ const updateTargetHash = _Resolver.decodeUpdateHash(update.targetHash, "targetHash");
2497
+ if (!(0, import_utils7.equalBytes)(updateTargetHash, updatedDocumentHash)) {
2498
+ throw new import_common12.ResolveError(
2499
+ `Invalid update: update.targetHash !== updatedDocumentHash`,
2500
+ import_common12.INVALID_DID_UPDATE,
2501
+ { updateTargetHash, updatedDocumentHash }
2502
+ );
2503
+ }
2504
+ return updatedDocument;
2586
2505
  }
2587
2506
  /**
2588
- * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#if-genesis_bytes-is-a-secp256k1-public-key | 7.2.d.1 if genesis bytes is a secp256k1 Public Key}.
2589
- * @param {DidComponents} didComponents The decoded components of the did.
2590
- * @returns {DidDocument} The resolved DID Document object.
2591
- */
2592
- static deterministic(didComponents) {
2593
- const genesisBytes = didComponents.genesisBytes;
2594
- const did = Identifier.encode(genesisBytes, didComponents);
2595
- const { multibase } = new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
2596
- const service = BeaconUtils.generateBeaconServices({
2597
- id: did,
2598
- publicKey: genesisBytes,
2599
- network: (0, import_bitcoin5.getNetwork)(didComponents.network),
2600
- beaconType: "SingletonBeacon"
2601
- });
2602
- return new DidDocument({
2603
- id: did,
2604
- verificationMethod: [{
2605
- id: `${did}#initialKey`,
2606
- type: "Multikey",
2607
- controller: did,
2608
- publicKeyMultibase: multibase.encoded
2609
- }],
2610
- service
2611
- });
2612
- }
2613
- /**
2614
- * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#if-genesis_bytes-is-a-sha-256-hash | 7.2.d.2 if genesis_bytes is a SHA-256 Hash}.
2615
- * @param {DidComponents} didComponents BTCR2 DID components used to resolve the DID Document
2616
- * @param {object} genesisDocument The genesis document for resolving the DID Document.
2617
- * @returns {DidDocument} The resolved DID Document object
2618
- * @throws {ResolveError} `INVALID_DID` if the hash of the genesis document is not the genesis bytes of the identifier
2619
- */
2620
- static external(didComponents, genesisDocument) {
2621
- const genesisDocumentHash = (0, import_common13.canonicalHashBytes)(genesisDocument);
2622
- if (!(0, import_utils7.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
2623
- throw new import_common13.ResolveError(
2624
- `Initial document mismatch: genesisBytes !== genesisDocumentHash`,
2625
- import_common13.INVALID_DID,
2626
- {
2627
- genesisBytes: (0, import_common13.encode)(didComponents.genesisBytes, "hex"),
2628
- genesisDocumentHash: (0, import_common13.encode)(genesisDocumentHash, "hex")
2629
- }
2630
- );
2631
- }
2632
- const did = Identifier.encode(didComponents.genesisBytes, didComponents);
2633
- const currentDocument = JSON.parse(
2634
- JSON.stringify(genesisDocument).replaceAll(ID_PLACEHOLDER_VALUE, did)
2635
- );
2636
- return new DidDocument(currentDocument);
2637
- }
2638
- /**
2639
- * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process-sidecar-data | Process Sidecar Data}
2640
- * @param {Sidecar} sidecar The sidecar data to process.
2641
- * @returns {SidecarData} The processed sidecar data containing maps of updates, CAS announcements, and SMT proofs.
2642
- */
2643
- static sidecarData(sidecar = {}) {
2644
- const updateMap = /* @__PURE__ */ new Map();
2645
- if (sidecar.updates?.length)
2646
- for (const update of sidecar.updates) {
2647
- updateMap.set((0, import_common13.canonicalHash)(update, { encoding: "hex" }), update);
2648
- }
2649
- const casMap = /* @__PURE__ */ new Map();
2650
- if (sidecar.casUpdates?.length)
2651
- for (const update of sidecar.casUpdates) {
2652
- casMap.set((0, import_common13.canonicalHash)(update, { encoding: "hex" }), update);
2653
- }
2654
- const smtMap = /* @__PURE__ */ new Map();
2655
- if (sidecar.smtProofs?.length)
2656
- for (const proof of sidecar.smtProofs) {
2657
- smtMap.set((0, import_common13.encode)((0, import_common13.decode)(proof.id, "base64urlnopad"), "hex"), proof);
2658
- }
2659
- return { updateMap, casMap, smtMap };
2660
- }
2661
- /**
2662
- * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#confirm-duplicate-update | Confirm Duplicate Update}.
2663
- * This step confirms that an update with a lower-than-expected targetVersionId is a true duplicate.
2664
- * @param {SignedBTCR2Update} update The BTCR2 Signed Update to confirm as a duplicate.
2665
- * @param {HashBytes[]} updateHashHistory The accumulated hash history for comparison.
2666
- * @returns {void} Does not return a value, but throws an error if the update is not a valid duplicate.
2667
- */
2668
- static confirmDuplicate(update, updateHashHistory) {
2669
- if (!Number.isInteger(update.targetVersionId) || update.targetVersionId < 2) {
2670
- throw new import_common13.ResolveError(
2671
- `Invalid duplicate: targetVersionId must be an integer >= 2`,
2672
- import_common13.INVALID_DID_UPDATE,
2673
- { targetVersionId: update.targetVersionId }
2674
- );
2675
- }
2676
- const { proof: _, ...unsignedUpdate } = update;
2677
- const unsignedUpdateHash = (0, import_common13.canonicalHashBytes)(unsignedUpdate);
2678
- const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
2679
- if (historicalUpdateHash === void 0) {
2680
- throw new import_common13.ResolveError(
2681
- `Invalid duplicate: no applied update in history for targetVersionId`,
2682
- import_common13.LATE_PUBLISHING_ERROR,
2683
- {
2684
- targetVersionId: update.targetVersionId,
2685
- historyLength: updateHashHistory.length
2686
- }
2687
- );
2688
- }
2689
- if (!(0, import_utils7.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
2690
- throw new import_common13.ResolveError(
2691
- `Invalid duplicate: unsigned update hash does not match historical hash`,
2692
- import_common13.LATE_PUBLISHING_ERROR,
2693
- {
2694
- unsignedUpdateHash: (0, import_common13.encode)(unsignedUpdateHash, "hex"),
2695
- historicalHash: (0, import_common13.encode)(historicalUpdateHash, "hex")
2696
- }
2697
- );
2698
- }
2699
- }
2700
- /**
2701
- * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#apply-update | Apply update}
2702
- * and its step {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#check-update-proof | Check update.proof}.
2703
- * @param {DidDocument} currentDocument The current DID Document to apply the update to.
2704
- * @param {SignedBTCR2Update} update The BTCR2 Signed Update to apply.
2705
- * @returns {DidDocument} The updated DID Document after applying the update.
2706
- * @throws {ResolveError} `INVALID_DID_UPDATE` if the update is invalid or cannot be applied.
2707
- */
2708
- static applyUpdate(currentDocument, update) {
2709
- const currentDocumentHash = (0, import_common13.canonicalHashBytes)(currentDocument);
2710
- const sourceHashBytes = (0, import_common13.decode)(update.sourceHash, "base64urlnopad");
2711
- if (!(0, import_utils7.equalBytes)(sourceHashBytes, currentDocumentHash)) {
2712
- throw new import_common13.ResolveError(
2713
- `Hash mismatch: update.sourceHash !== currentDocumentHash`,
2714
- import_common13.INVALID_DID_UPDATE,
2715
- {
2716
- sourceHash: update.sourceHash,
2717
- currentDocumentHash: (0, import_common13.encode)(currentDocumentHash, "hex")
2718
- }
2719
- );
2720
- }
2721
- if (!isBtcr2UpdateContext(update["@context"])) {
2722
- throw new import_common13.ResolveError(
2723
- "Invalid update: @context is not the array the specification pins for a BTCR2 Update",
2724
- import_common13.INVALID_DID_UPDATE,
2725
- { context: update["@context"], expected: [...BTCR2_UPDATE_CONTEXT] }
2726
- );
2727
- }
2728
- if (!isBtcr2UpdateContext(update.proof?.["@context"], update["@context"])) {
2729
- throw new import_common13.ResolveError(
2730
- "Invalid update: proof @context does not equal the update @context",
2731
- import_common13.INVALID_DID_UPDATE,
2732
- { proofContext: update.proof?.["@context"], context: update["@context"] }
2733
- );
2734
- }
2735
- const capabilityId = update.proof?.capability;
2736
- if (!capabilityId) {
2737
- throw new import_common13.ResolveError("No root capability found in update", import_common13.INVALID_DID_UPDATE, update);
2738
- }
2739
- const rootCapability = Appendix.dereferenceZcapId(capabilityId);
2740
- const { invocationTarget, controller: rootController } = rootCapability;
2741
- if (![invocationTarget, rootController].every((id) => id === currentDocument.id)) {
2742
- throw new import_common13.ResolveError(
2743
- "Invalid root capability",
2744
- import_common13.INVALID_DID_UPDATE,
2745
- { rootCapability, currentDocument }
2746
- );
2747
- }
2748
- const verificationMethodId = update.proof?.verificationMethod;
2749
- if (!verificationMethodId) {
2750
- throw new import_common13.ResolveError("No verificationMethod found in update", import_common13.INVALID_DID_UPDATE, update);
2751
- }
2752
- const authorizedMethodId = Appendix.relationshipMethodId(verificationMethodId, currentDocument.id);
2753
- const authorized = authorizedMethodId !== void 0 && currentDocument.capabilityInvocation?.some(
2754
- (entry) => Appendix.relationshipMethodId(entry, currentDocument.id) === authorizedMethodId
2755
- );
2756
- if (!authorized) {
2757
- throw new import_common13.ResolveError(
2758
- "Invalid update: verificationMethod is not authorized for capabilityInvocation",
2759
- import_common13.INVALID_DID_UPDATE,
2760
- {
2761
- verificationMethodId,
2762
- capabilityInvocation: currentDocument.capabilityInvocation
2763
- }
2764
- );
2765
- }
2766
- const vm = DidBtcr2.getSigningMethod(currentDocument, verificationMethodId);
2767
- const multikey = import_cryptosuite2.SchnorrMultikey.fromVerificationMethod(vm);
2768
- const cryptosuite = new import_cryptosuite2.BIP340Cryptosuite(multikey);
2769
- const canonicalUpdate = (0, import_common13.canonicalize)(update);
2770
- const diProof = new import_cryptosuite2.BIP340DataIntegrityProof(cryptosuite);
2771
- const verificationResult = diProof.verifyProof(canonicalUpdate, "capabilityInvocation");
2772
- if (!verificationResult.verified) {
2773
- throw new import_common13.ResolveError(
2774
- "Invalid update: proof not verified",
2775
- import_common13.INVALID_DID_UPDATE,
2776
- verificationResult
2777
- );
2778
- }
2779
- const updatedDocument = import_common13.JSONPatch.apply(currentDocument, update.patch);
2780
- DidDocument.validate(updatedDocument);
2781
- const updatedDocumentHash = (0, import_common13.canonicalHashBytes)(updatedDocument);
2782
- const updateTargetHash = (0, import_common13.decode)(update.targetHash);
2783
- if (!(0, import_utils7.equalBytes)(updateTargetHash, updatedDocumentHash)) {
2784
- throw new import_common13.ResolveError(
2785
- `Invalid update: update.targetHash !== updatedDocumentHash`,
2786
- import_common13.INVALID_DID_UPDATE,
2787
- { updateTargetHash, updatedDocumentHash }
2788
- );
2789
- }
2790
- return updatedDocument;
2791
- }
2792
- /**
2793
- * Advance the state machine. Returns either:
2794
- * - `{ status: 'action-required', needs }` - caller must provide data via {@link provide}
2795
- * - `{ status: 'resolved', result }` - resolution complete
2796
- *
2797
- * Analogous to Rust's `Resolver::resolve()`.
2507
+ * Advance the state machine. Returns either:
2508
+ * - `{ status: 'action-required', needs }` - caller must provide data via {@link provide}
2509
+ * - `{ status: 'resolved', result }` - resolution complete
2510
+ *
2511
+ * Analogous to Rust's `Resolver::resolve()`.
2798
2512
  */
2799
2513
  resolve() {
2800
2514
  while (true) {
@@ -2811,7 +2525,7 @@ var Resolver = class _Resolver {
2811
2525
  this.#phase = "BeaconDiscovery" /* BeaconDiscovery */;
2812
2526
  continue;
2813
2527
  }
2814
- const genesisHash = (0, import_common13.encode)(this.#didComponents.genesisBytes, "hex");
2528
+ const genesisHash = (0, import_common12.encode)(this.#didComponents.genesisBytes, "hex");
2815
2529
  return {
2816
2530
  status: "action-required",
2817
2531
  needs: [{ kind: "NeedGenesisDocument", genesisHash }]
@@ -2851,7 +2565,10 @@ var Resolver = class _Resolver {
2851
2565
  if (result.needs.length > 0) {
2852
2566
  allNeeds.push(...result.needs);
2853
2567
  } else {
2854
- this.#unsortedUpdates.push(...result.updates);
2568
+ const address = BeaconUtils.parseBitcoinAddress(service.serviceEndpoint);
2569
+ this.#unsortedUpdates.push(...result.updates.map(
2570
+ ([update, block]) => [update, block, address]
2571
+ ));
2855
2572
  this.#processedServices.add(service.id);
2856
2573
  }
2857
2574
  }
@@ -2865,6 +2582,8 @@ var Resolver = class _Resolver {
2865
2582
  // Spec "Process Next Update": one tuple per step. The phase repeats until
2866
2583
  // the document resolves, or until an applied update adds a beacon address
2867
2584
  // that the resolver did not scan (then the pass returns to BeaconDiscovery).
2585
+ // A tuple whose beacon address the current document no longer carries is
2586
+ // ignored (step 4).
2868
2587
  case "ProcessUpdate" /* ProcessUpdate */: {
2869
2588
  const document = this.#currentDocument;
2870
2589
  if (this.#versionId !== void 0 && this.#currentVersionId === this.#versionId) {
@@ -2873,9 +2592,9 @@ var Resolver = class _Resolver {
2873
2592
  }
2874
2593
  if (this.#unsortedUpdates.length === 0 || document.deactivated) {
2875
2594
  if (this.#versionId !== void 0) {
2876
- throw new import_common13.ResolveError(
2595
+ throw new import_common12.ResolveError(
2877
2596
  `Version ${this.#versionId} of the DID does not exist: the history ` + (document.deactivated ? `ends with the deactivation at version ${this.#currentVersionId}.` : `ends at version ${this.#currentVersionId}.`),
2878
- import_common13.NOT_FOUND,
2597
+ import_common12.NOT_FOUND,
2879
2598
  { versionId: this.#versionId, currentVersionId: this.#currentVersionId }
2880
2599
  );
2881
2600
  }
@@ -2885,7 +2604,11 @@ var Resolver = class _Resolver {
2885
2604
  this.#unsortedUpdates.sort(
2886
2605
  ([upd0, blk0], [upd1, blk1]) => upd0.targetVersionId - upd1.targetVersionId || blk0.height - blk1.height
2887
2606
  );
2888
- const [update, block] = this.#unsortedUpdates.shift();
2607
+ const [update, block, address] = this.#unsortedUpdates.shift();
2608
+ const removed = !BeaconUtils.getBeaconServices(document).some(
2609
+ (service) => BeaconUtils.parseBitcoinAddress(service.serviceEndpoint) === address
2610
+ );
2611
+ if (removed) continue;
2889
2612
  if (update.targetVersionId <= this.#currentVersionId) {
2890
2613
  _Resolver.confirmDuplicate(update, this.#updateHashHistory);
2891
2614
  continue;
@@ -2895,26 +2618,26 @@ var Resolver = class _Resolver {
2895
2618
  continue;
2896
2619
  }
2897
2620
  if (update.targetVersionId !== this.#currentVersionId + 1) {
2898
- throw new import_common13.ResolveError(
2621
+ throw new import_common12.ResolveError(
2899
2622
  `Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
2900
- import_common13.LATE_PUBLISHING_ERROR,
2623
+ import_common12.LATE_PUBLISHING_ERROR,
2901
2624
  {
2902
2625
  targetVersionId: update.targetVersionId,
2903
2626
  currentVersionId: this.#currentVersionId + 1
2904
2627
  }
2905
2628
  );
2906
2629
  }
2907
- this.#currentDocument = _Resolver.applyUpdate(document, update);
2908
- const unsignedUpdate = import_common13.JSONUtils.deleteKeys(update, ["proof"]);
2909
- this.#updateHashHistory.push((0, import_common13.canonicalHashBytes)(unsignedUpdate));
2630
+ this.#currentDocument = _Resolver.applyUpdate(document, update, block);
2631
+ const unsignedUpdate = import_common12.JSONUtils.deleteKeys(update, ["proof"]);
2632
+ this.#updateHashHistory.push((0, import_common12.canonicalHashBytes)(unsignedUpdate));
2910
2633
  this.#currentVersionId++;
2911
2634
  this.#blockConfirmations = block.confirmations;
2912
- this.#updated = import_common13.DateUtils.toISOStringNonFractional(import_common13.DateUtils.blocktimeToTimestamp(block.time));
2635
+ this.#updated = import_common12.DateUtils.toISOStringNonFractional(import_common12.DateUtils.blocktimeToTimestamp(block.time));
2913
2636
  if (this.#hasUnscannedBeacons()) {
2914
2637
  if (++this.#discoveryRounds > this.#maxDiscoveryRounds) {
2915
- throw new import_common13.ResolveError(
2638
+ throw new import_common12.ResolveError(
2916
2639
  `Exceeded the configured maximum of ${this.#maxDiscoveryRounds} beacon-discovery rounds. Raise or remove ResolutionOptions.maxDiscoveryRounds to resolve this DID.`,
2917
- import_common13.INTERNAL_ERROR,
2640
+ import_common12.INTERNAL_ERROR,
2918
2641
  { maxDiscoveryRounds: this.#maxDiscoveryRounds, discoveryRounds: this.#discoveryRounds }
2919
2642
  );
2920
2643
  }
@@ -2977,9 +2700,9 @@ var Resolver = class _Resolver {
2977
2700
  continue;
2978
2701
  }
2979
2702
  if (!Number.isFinite(block?.height) || !Number.isFinite(block?.time) || !Number.isFinite(block?.mediantime)) {
2980
- throw new import_common13.ResolveError(
2703
+ throw new import_common12.ResolveError(
2981
2704
  `Beacon signal ${signal.signalBytes} has ${confirmations} confirmations but no valid block height, block time, or block mediantime.`,
2982
- import_common13.INVALID_DID_UPDATE,
2705
+ import_common12.INVALID_DID_UPDATE,
2983
2706
  {
2984
2707
  signalBytes: signal.signalBytes,
2985
2708
  confirmations,
@@ -2997,9 +2720,9 @@ var Resolver = class _Resolver {
2997
2720
  switch (need.kind) {
2998
2721
  case "NeedGenesisDocument": {
2999
2722
  if (!isRecord(data)) {
3000
- throw new import_common13.ResolveError(
2723
+ throw new import_common12.ResolveError(
3001
2724
  "Provided data for NeedGenesisDocument must be a document object.",
3002
- import_common13.INVALID_DID_UPDATE,
2725
+ import_common12.INVALID_DID_UPDATE,
3003
2726
  { kind: need.kind }
3004
2727
  );
3005
2728
  }
@@ -3008,9 +2731,9 @@ var Resolver = class _Resolver {
3008
2731
  }
3009
2732
  case "NeedBeaconSignals": {
3010
2733
  if (!(data instanceof Map)) {
3011
- throw new import_common13.ResolveError(
2734
+ throw new import_common12.ResolveError(
3012
2735
  "Provided data for NeedBeaconSignals must be a Map of beacon services to signals.",
3013
- import_common13.INVALID_DID_UPDATE,
2736
+ import_common12.INVALID_DID_UPDATE,
3014
2737
  { kind: need.kind }
3015
2738
  );
3016
2739
  }
@@ -3021,103 +2744,411 @@ var Resolver = class _Resolver {
3021
2744
  }
3022
2745
  case "NeedCASAnnouncement": {
3023
2746
  if (!isCASAnnouncement(data)) {
3024
- throw new import_common13.ResolveError(
2747
+ throw new import_common12.ResolveError(
3025
2748
  "Provided data for NeedCASAnnouncement is not a CAS announcement.",
3026
- import_common13.INVALID_DID_UPDATE,
2749
+ import_common12.INVALID_DID_UPDATE,
2750
+ { kind: need.kind }
2751
+ );
2752
+ }
2753
+ const announcementHash = (0, import_common12.canonicalHash)(data, { encoding: "hex" });
2754
+ if (announcementHash !== need.announcementHash) {
2755
+ throw new import_common12.ResolveError(
2756
+ `CAS announcement hash mismatch: expected ${need.announcementHash}, got ${announcementHash}.`,
2757
+ import_common12.INVALID_DID_UPDATE,
2758
+ { expected: need.announcementHash, actual: announcementHash }
2759
+ );
2760
+ }
2761
+ this.#sidecarData.casMap.set(announcementHash, data);
2762
+ break;
2763
+ }
2764
+ case "NeedSignedUpdate": {
2765
+ if (!isSignedBTCR2Update(data)) {
2766
+ throw new import_common12.ResolveError(
2767
+ "Provided data for NeedSignedUpdate is not a signed BTCR2 update.",
2768
+ import_common12.INVALID_DID_UPDATE,
2769
+ { kind: need.kind }
2770
+ );
2771
+ }
2772
+ const updateHash = (0, import_common12.canonicalHash)(data, { encoding: "hex" });
2773
+ if (updateHash !== need.updateHash) {
2774
+ throw new import_common12.ResolveError(
2775
+ `Signed update hash mismatch: expected ${need.updateHash}, got ${updateHash}.`,
2776
+ import_common12.INVALID_DID_UPDATE,
2777
+ { expected: need.updateHash, actual: updateHash }
2778
+ );
2779
+ }
2780
+ this.#sidecarData.updateMap.set(updateHash, data);
2781
+ break;
2782
+ }
2783
+ case "NeedSMTProof": {
2784
+ if (!isSMTProof(data)) {
2785
+ throw new import_common12.ResolveError(
2786
+ "Provided data for NeedSMTProof is not an SMT proof.",
2787
+ import_common12.INVALID_DID_UPDATE,
3027
2788
  { kind: need.kind }
3028
2789
  );
3029
2790
  }
3030
- const announcementHash = (0, import_common13.canonicalHash)(data, { encoding: "hex" });
3031
- if (announcementHash !== need.announcementHash) {
3032
- throw new import_common13.ResolveError(
3033
- `CAS announcement hash mismatch: expected ${need.announcementHash}, got ${announcementHash}.`,
3034
- import_common13.INVALID_DID_UPDATE,
3035
- { expected: need.announcementHash, actual: announcementHash }
2791
+ const proofIdHex = (0, import_common12.encode)((0, import_common12.decode)(data.id, "base64urlnopad"), "hex");
2792
+ if (proofIdHex !== need.smtRootHash) {
2793
+ throw new import_common12.ResolveError(
2794
+ `SMT proof root hash mismatch: expected ${need.smtRootHash}, got ${proofIdHex}`,
2795
+ import_common12.INVALID_DID_UPDATE,
2796
+ { expected: need.smtRootHash, actual: proofIdHex }
2797
+ );
2798
+ }
2799
+ this.#sidecarData.smtMap.set(need.smtRootHash, data);
2800
+ break;
2801
+ }
2802
+ }
2803
+ }
2804
+ };
2805
+
2806
+ // src/core/did-sender-resolver.ts
2807
+ function getAggregationCommunicationKey(document) {
2808
+ const invocation = document.capabilityInvocation?.[0];
2809
+ if (invocation === void 0) {
2810
+ throw new import_common13.DidDocumentError(
2811
+ "Cannot derive aggregation communication key: capabilityInvocation is absent",
2812
+ import_common13.INVALID_DID_DOCUMENT,
2813
+ { id: document.id }
2814
+ );
2815
+ }
2816
+ const invocationId = Appendix.absoluteDidUrl(invocation, document.id);
2817
+ const vm = typeof invocation === "string" ? invocationId === void 0 ? void 0 : document.verificationMethod?.find(
2818
+ (method) => Appendix.absoluteDidUrl(method.id, document.id) === invocationId
2819
+ ) : invocation;
2820
+ if (!vm) {
2821
+ throw new import_common13.DidDocumentError(
2822
+ `Cannot derive aggregation communication key: capabilityInvocation[0] "${invocation}" does not resolve to a verification method`,
2823
+ import_common13.INVALID_DID_DOCUMENT,
2824
+ { id: document.id, invocation }
2825
+ );
2826
+ }
2827
+ return import_cryptosuite2.SchnorrMultikey.fromVerificationMethod(vm).publicKey;
2828
+ }
2829
+ function resolveBtcr2SenderPk(did, opts) {
2830
+ try {
2831
+ const components = Identifier.decode(did);
2832
+ if (components.idType === "KEY") {
2833
+ return new import_keypair4.CompressedSecp256k1PublicKey(components.genesisBytes);
2834
+ }
2835
+ if (opts?.genesisDocument) {
2836
+ const document = Resolver.external(components, opts.genesisDocument);
2837
+ return getAggregationCommunicationKey(document);
2838
+ }
2839
+ } catch {
2840
+ }
2841
+ return void 0;
2842
+ }
2843
+
2844
+ // src/core/updater.ts
2845
+ var import_common14 = require("@did-btcr2/common");
2846
+ var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
2847
+ var Updater = class _Updater {
2848
+ #state = { phase: "Construct" };
2849
+ #sourceDocument;
2850
+ #patches;
2851
+ #sourceVersionId;
2852
+ #verificationMethod;
2853
+ #beaconService;
2854
+ /**
2855
+ * @internal Use {@link DidBtcr2.update} to create instances.
2856
+ */
2857
+ constructor(params) {
2858
+ this.#sourceDocument = params.sourceDocument;
2859
+ this.#patches = params.patches;
2860
+ this.#sourceVersionId = params.sourceVersionId;
2861
+ this.#verificationMethod = params.verificationMethod;
2862
+ this.#beaconService = params.beaconService;
2863
+ }
2864
+ // ─── Public static utility methods ─────────────────────────────────────────
2865
+ // Used by generate-vector.ts and other scripts that need direct access to
2866
+ // individual update steps outside the state machine flow.
2867
+ /**
2868
+ * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/update.html#construct-btcr2-unsigned-update | 7.3.b Construct BTCR2 Unsigned Update}.
2869
+ *
2870
+ * @param {Btcr2DidDocument} sourceDocument The source DID document to be updated.
2871
+ * @param {PatchOperation[]} patches The JSON Patch operations to apply.
2872
+ * @param {number} sourceVersionId The version ID of the source document.
2873
+ * @returns {UnsignedBTCR2Update} The constructed UnsignedBTCR2Update object.
2874
+ * @throws {UpdateError} If the target document fails DID Core validation.
2875
+ */
2876
+ static construct(sourceDocument, patches, sourceVersionId) {
2877
+ const unsignedUpdate = {
2878
+ // The array the specification pins, as a fresh copy: the update is a plain JSON
2879
+ // object that callers may edit, and the shared constant is frozen.
2880
+ "@context": [...BTCR2_UPDATE_CONTEXT],
2881
+ patch: patches,
2882
+ targetHash: "",
2883
+ targetVersionId: sourceVersionId + 1,
2884
+ sourceHash: (0, import_common14.canonicalHash)(sourceDocument)
2885
+ };
2886
+ let targetDocument;
2887
+ try {
2888
+ targetDocument = import_common14.JSONPatch.apply(sourceDocument, patches, { strict: true });
2889
+ } catch (error) {
2890
+ throw new import_common14.UpdateError(
2891
+ `Invalid patch: ${errorCause(error).message}`,
2892
+ import_common14.INVALID_DID_UPDATE,
2893
+ { cause: errorCause(error) }
2894
+ );
2895
+ }
2896
+ if (targetDocument.id !== sourceDocument.id) {
2897
+ throw new import_common14.UpdateError(
2898
+ `Patches must not change the DID document id (source "${sourceDocument.id}" to target "${targetDocument.id}").`,
2899
+ import_common14.INVALID_DID_UPDATE,
2900
+ { sourceId: sourceDocument.id, targetId: targetDocument.id }
2901
+ );
2902
+ }
2903
+ try {
2904
+ DidDocument.isValid(targetDocument);
2905
+ } catch (error) {
2906
+ throw new import_common14.UpdateError(
2907
+ "Error validating targetDocument: " + (error instanceof Error ? error.message : String(error)),
2908
+ import_common14.INVALID_DID_UPDATE,
2909
+ targetDocument
2910
+ );
2911
+ }
2912
+ unsignedUpdate.targetHash = (0, import_common14.canonicalHash)(targetDocument);
2913
+ return unsignedUpdate;
2914
+ }
2915
+ /**
2916
+ * Implements subsection {@link http://dcdpr.github.io/did-btcr2/operations/update.html#construct-btcr2-signed-update | 7.3.c Construct BTCR2 Signed Update }.
2917
+ *
2918
+ * @param {string} did The did-btcr2 identifier to derive the root capability from.
2919
+ * @param {UnsignedBTCR2Update} unsignedUpdate The unsigned update to sign.
2920
+ * @param {DidVerificationMethod} verificationMethod The verification method for signing.
2921
+ * @param {Signer} signer Signer that produces the BIP-340 Schnorr signature.
2922
+ * @returns {SignedBTCR2Update} The signed update with a Data Integrity proof.
2923
+ */
2924
+ static sign(did, unsignedUpdate, verificationMethod, signer) {
2925
+ if (!did.startsWith("did:btcr2:")) {
2926
+ throw new import_common14.UpdateError(
2927
+ `Expected a did:btcr2 identifier for the root capability; got "${did}".`,
2928
+ import_common14.INVALID_DID_UPDATE,
2929
+ { did }
2930
+ );
2931
+ }
2932
+ const controller = verificationMethod.controller;
2933
+ const hashIdx = verificationMethod.id.indexOf("#");
2934
+ if (hashIdx < 0) {
2935
+ throw new import_common14.UpdateError(
2936
+ `Verification method id must contain a fragment (e.g. "${verificationMethod.id}#initialKey"); got "${verificationMethod.id}".`,
2937
+ import_common14.INVALID_DID_UPDATE,
2938
+ { verificationMethodId: verificationMethod.id }
2939
+ );
2940
+ }
2941
+ const id = verificationMethod.id.slice(hashIdx);
2942
+ const multikey = import_cryptosuite3.SchnorrMultikey.fromSigner(id, controller, signer);
2943
+ const absoluteMethodId = hashIdx === 0 ? `${did}${id}` : verificationMethod.id;
2944
+ const signerKey = multikey.publicKey.multibase.encoded;
2945
+ if (verificationMethod.publicKeyMultibase && signerKey !== verificationMethod.publicKeyMultibase) {
2946
+ throw new import_common14.UpdateError(
2947
+ `Signing key does not match verification method "${verificationMethod.id}": the signer's public key differs from the method's published publicKeyMultibase.`,
2948
+ import_common14.INVALID_DID_UPDATE,
2949
+ {
2950
+ verificationMethodId: verificationMethod.id,
2951
+ expected: verificationMethod.publicKeyMultibase,
2952
+ actual: signerKey
2953
+ }
2954
+ );
2955
+ }
2956
+ const config = {
2957
+ // The proof must carry the same array as the update. The cryptosuite copies the
2958
+ // document @context into the proof when the document has one, so the two arrays
2959
+ // are equal by construction; this value is the fallback for a document without one.
2960
+ "@context": [...BTCR2_UPDATE_CONTEXT],
2961
+ cryptosuite: "bip340-jcs-2025",
2962
+ type: "DataIntegrityProof",
2963
+ // The proof names the signing method by absolute DID URL, even when the document
2964
+ // spells that method's own id relatively: a proof travels apart from the document
2965
+ // that defines the method, so a bare `#initialKey` in it resolves against nothing.
2966
+ // For a document that already spells its ids absolutely this is the id unchanged,
2967
+ // so proofs over such documents are byte-identical to before.
2968
+ verificationMethod: absoluteMethodId,
2969
+ proofPurpose: "capabilityInvocation",
2970
+ capability: `urn:zcap:root:${encodeURIComponent(did)}`,
2971
+ capabilityAction: "Write"
2972
+ };
2973
+ const diproof = multikey.toCryptosuite().toDataIntegrityProof();
2974
+ const signedUpdate = diproof.addProof(unsignedUpdate, config);
2975
+ let verified;
2976
+ try {
2977
+ const verifier = import_cryptosuite3.SchnorrMultikey.fromVerificationMethod({ ...verificationMethod, id: absoluteMethodId });
2978
+ verified = verifier.toCryptosuite().toDataIntegrityProof().verifyProof((0, import_common14.canonicalize)(signedUpdate), "capabilityInvocation").verified;
2979
+ } catch (error) {
2980
+ throw new import_common14.UpdateError(
2981
+ `Invalid update: the proof does not verify with the public key of "${verificationMethod.id}": ` + errorCause(error).message,
2982
+ import_common14.INVALID_DID_UPDATE,
2983
+ { verificationMethodId: verificationMethod.id, cause: errorCause(error) }
2984
+ );
2985
+ }
2986
+ if (!verified) {
2987
+ throw new import_common14.UpdateError(
2988
+ `Invalid update: the proof does not verify with the public key of "${verificationMethod.id}".`,
2989
+ import_common14.INVALID_DID_UPDATE,
2990
+ { verificationMethodId: verificationMethod.id }
2991
+ );
2992
+ }
2993
+ return signedUpdate;
2994
+ }
2995
+ /**
2996
+ * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/update.html#announce-did-update | 7.3.d Announce DID Update}.
2997
+ * Announces a signed update to the Bitcoin blockchain via the specified beacon.
2998
+ *
2999
+ * @param {BeaconService} beaconService The beacon service to broadcast through.
3000
+ * @param {string} did The DID being updated. Required because a beacon service
3001
+ * `id` may be a relative DID URL and so cannot supply the subject.
3002
+ * @param {SignedBTCR2Update} update The signed update to announce.
3003
+ * @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
3004
+ * @param {BitcoinConnection} bitcoin The Bitcoin network connection.
3005
+ * @param {CASBroadcastOptions} [options] Optional broadcast configuration (fee estimator,
3006
+ * change address, and, for CAS beacons, a `casPublish` callback invoked before the
3007
+ * transaction broadcast; other beacon types ignore `casPublish`).
3008
+ * @returns {Promise<BroadcastResult>} The broadcast artifacts: the signed update, the signal
3009
+ * txid, and any per-beacon-type sidecar data (CAS announcement / SMT proof).
3010
+ */
3011
+ static async announce(beaconService, did, update, signer, bitcoin, options) {
3012
+ const beacon = BeaconFactory.establish(beaconService, did);
3013
+ return beacon.broadcastSignal(update, signer, bitcoin, options);
3014
+ }
3015
+ // Private instance wrappers
3016
+ // Delegate to the public statics with bound instance fields for cleaner
3017
+ // advance/provide code.
3018
+ #construct() {
3019
+ return _Updater.construct(this.#sourceDocument, this.#patches, this.#sourceVersionId);
3020
+ }
3021
+ /**
3022
+ * Advance the state machine. Returns either:
3023
+ * - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
3024
+ * - `{ status: 'complete', result }` update is signed and broadcast
3025
+ */
3026
+ advance() {
3027
+ while (true) {
3028
+ switch (this.#state.phase) {
3029
+ // Phase: Construct
3030
+ // Build the unsigned update from source doc + patches. Pure, synchronous.
3031
+ case "Construct": {
3032
+ const unsignedUpdate = this.#construct();
3033
+ this.#state = { phase: "Sign", unsignedUpdate };
3034
+ continue;
3035
+ }
3036
+ // Phase: Sign
3037
+ // Emit NeedSigningKey: the caller supplies the secret key (or a KMS signature).
3038
+ case "Sign": {
3039
+ return {
3040
+ status: "action-required",
3041
+ needs: [{
3042
+ kind: "NeedSigningKey",
3043
+ verificationMethodId: this.#verificationMethod.id,
3044
+ unsignedUpdate: this.#state.unsignedUpdate
3045
+ }]
3046
+ };
3047
+ }
3048
+ // Phase: Fund
3049
+ // Emit NeedFunding with the beacon address. The caller checks UTXOs,
3050
+ // funds the address if needed, and provides to continue.
3051
+ case "Fund": {
3052
+ const beaconAddress = this.#beaconService.serviceEndpoint.replace("bitcoin:", "");
3053
+ return {
3054
+ status: "action-required",
3055
+ needs: [{
3056
+ kind: "NeedFunding",
3057
+ beaconAddress,
3058
+ beaconService: this.#beaconService
3059
+ }]
3060
+ };
3061
+ }
3062
+ // Phase: Broadcast
3063
+ // Emit NeedBroadcast with the signed update + beacon service. The caller performs
3064
+ // the actual on-chain announcement (or hands off to the aggregation protocol).
3065
+ case "Broadcast": {
3066
+ return {
3067
+ status: "action-required",
3068
+ needs: [{
3069
+ kind: "NeedBroadcast",
3070
+ beaconService: this.#beaconService,
3071
+ signedUpdate: this.#state.signedUpdate,
3072
+ did: this.#sourceDocument.id
3073
+ }]
3074
+ };
3075
+ }
3076
+ // Phase: Complete
3077
+ case "Complete": {
3078
+ return {
3079
+ status: "complete",
3080
+ result: { signedUpdate: this.#state.signedUpdate }
3081
+ };
3082
+ }
3083
+ }
3084
+ }
3085
+ }
3086
+ provide(need, data) {
3087
+ switch (need.kind) {
3088
+ case "NeedSigningKey": {
3089
+ if (this.#state.phase !== "Sign") {
3090
+ throw new import_common14.UpdateError(
3091
+ `Cannot provide NeedSigningKey: updater phase is ${this.#state.phase}, expected Sign.`,
3092
+ import_common14.INVALID_DID_UPDATE,
3093
+ { phase: this.#state.phase }
3094
+ );
3095
+ }
3096
+ if (!data) {
3097
+ throw new import_common14.UpdateError(
3098
+ "NeedSigningKey requires a Signer.",
3099
+ import_common14.INVALID_DID_UPDATE
3036
3100
  );
3037
3101
  }
3038
- this.#sidecarData.casMap.set(announcementHash, data);
3102
+ const unsignedUpdate = this.#state.unsignedUpdate;
3103
+ const signedUpdate = _Updater.sign(
3104
+ this.#sourceDocument.id,
3105
+ unsignedUpdate,
3106
+ this.#verificationMethod,
3107
+ data
3108
+ );
3109
+ this.#state = { phase: "Fund", unsignedUpdate, signedUpdate };
3039
3110
  break;
3040
3111
  }
3041
- case "NeedSignedUpdate": {
3042
- if (!isSignedBTCR2Update(data)) {
3043
- throw new import_common13.ResolveError(
3044
- "Provided data for NeedSignedUpdate is not a signed BTCR2 update.",
3045
- import_common13.INVALID_DID_UPDATE,
3046
- { kind: need.kind }
3112
+ case "NeedFunding": {
3113
+ if (this.#state.phase !== "Fund") {
3114
+ throw new import_common14.UpdateError(
3115
+ `Cannot provide NeedFunding: updater phase is ${this.#state.phase}, expected Fund.`,
3116
+ import_common14.INVALID_DID_UPDATE,
3117
+ { phase: this.#state.phase }
3047
3118
  );
3048
3119
  }
3049
- const updateHash = (0, import_common13.canonicalHash)(data, { encoding: "hex" });
3050
- if (updateHash !== need.updateHash) {
3051
- throw new import_common13.ResolveError(
3052
- `Signed update hash mismatch: expected ${need.updateHash}, got ${updateHash}.`,
3053
- import_common13.INVALID_DID_UPDATE,
3054
- { expected: need.updateHash, actual: updateHash }
3055
- );
3120
+ if (data !== void 0) {
3121
+ const proof = data;
3122
+ if (typeof proof.utxoCount !== "number" || !Number.isFinite(proof.utxoCount) || proof.utxoCount < 1) {
3123
+ throw new import_common14.UpdateError(
3124
+ `NeedFunding proof must have utxoCount >= 1; got ${String(proof.utxoCount)}.`,
3125
+ import_common14.INVALID_DID_UPDATE,
3126
+ { utxoCount: proof.utxoCount }
3127
+ );
3128
+ }
3056
3129
  }
3057
- this.#sidecarData.updateMap.set(updateHash, data);
3130
+ this.#state = {
3131
+ phase: "Broadcast",
3132
+ unsignedUpdate: this.#state.unsignedUpdate,
3133
+ signedUpdate: this.#state.signedUpdate
3134
+ };
3058
3135
  break;
3059
3136
  }
3060
- case "NeedSMTProof": {
3061
- if (!isSMTProof(data)) {
3062
- throw new import_common13.ResolveError(
3063
- "Provided data for NeedSMTProof is not an SMT proof.",
3064
- import_common13.INVALID_DID_UPDATE,
3065
- { kind: need.kind }
3066
- );
3067
- }
3068
- const proofIdHex = (0, import_common13.encode)((0, import_common13.decode)(data.id, "base64urlnopad"), "hex");
3069
- if (proofIdHex !== need.smtRootHash) {
3070
- throw new import_common13.ResolveError(
3071
- `SMT proof root hash mismatch: expected ${need.smtRootHash}, got ${proofIdHex}`,
3072
- import_common13.INVALID_DID_UPDATE,
3073
- { expected: need.smtRootHash, actual: proofIdHex }
3137
+ case "NeedBroadcast": {
3138
+ if (this.#state.phase !== "Broadcast") {
3139
+ throw new import_common14.UpdateError(
3140
+ `Cannot provide NeedBroadcast: updater phase is ${this.#state.phase}, expected Broadcast.`,
3141
+ import_common14.INVALID_DID_UPDATE,
3142
+ { phase: this.#state.phase }
3074
3143
  );
3075
3144
  }
3076
- this.#sidecarData.smtMap.set(need.smtRootHash, data);
3145
+ this.#state = { phase: "Complete", signedUpdate: this.#state.signedUpdate };
3077
3146
  break;
3078
3147
  }
3079
3148
  }
3080
3149
  }
3081
3150
  };
3082
3151
 
3083
- // src/core/did-sender-resolver.ts
3084
- function getAggregationCommunicationKey(document) {
3085
- const invocation = document.capabilityInvocation?.[0];
3086
- if (invocation === void 0) {
3087
- throw new import_common14.DidDocumentError(
3088
- "Cannot derive aggregation communication key: capabilityInvocation is absent",
3089
- import_common14.INVALID_DID_DOCUMENT,
3090
- { id: document.id }
3091
- );
3092
- }
3093
- const invocationId = Appendix.absoluteDidUrl(invocation, document.id);
3094
- const vm = typeof invocation === "string" ? invocationId === void 0 ? void 0 : document.verificationMethod?.find(
3095
- (method) => Appendix.absoluteDidUrl(method.id, document.id) === invocationId
3096
- ) : invocation;
3097
- if (!vm) {
3098
- throw new import_common14.DidDocumentError(
3099
- `Cannot derive aggregation communication key: capabilityInvocation[0] "${invocation}" does not resolve to a verification method`,
3100
- import_common14.INVALID_DID_DOCUMENT,
3101
- { id: document.id, invocation }
3102
- );
3103
- }
3104
- return import_cryptosuite3.SchnorrMultikey.fromVerificationMethod(vm).publicKey;
3105
- }
3106
- function resolveBtcr2SenderPk(did, opts) {
3107
- try {
3108
- const components = Identifier.decode(did);
3109
- if (components.idType === "KEY") {
3110
- return new import_keypair4.CompressedSecp256k1PublicKey(components.genesisBytes);
3111
- }
3112
- if (opts?.genesisDocument) {
3113
- const document = Resolver.external(components, opts.genesisDocument);
3114
- return getAggregationCommunicationKey(document);
3115
- }
3116
- } catch {
3117
- }
3118
- return void 0;
3119
- }
3120
-
3121
3152
  // src/utils/did-document-builder.ts
3122
3153
  var import_common15 = require("@did-btcr2/common");
3123
3154
  var DidDocumentBuilder = class {
@@ -3172,6 +3203,217 @@ var DidDocumentBuilder = class {
3172
3203
  return didDocument;
3173
3204
  }
3174
3205
  };
3206
+
3207
+ // src/did-btcr2.ts
3208
+ var import_common16 = require("@did-btcr2/common");
3209
+ var import_dids2 = require("@web5/dids");
3210
+ var DidBtcr2 = class {
3211
+ /**
3212
+ * Name of the DID method, as defined in the DID BTCR2 specification
3213
+ */
3214
+ static methodName = "btcr2";
3215
+ /**
3216
+ * Implements section {@link https://dcdpr.github.io/did-btcr2/operations/create.html | 7.1 Create}.
3217
+ * @param {KeyBytes | DocumentBytes} genesisBytes The bytes used to create the genesis document for a did:btcr2 identifier.
3218
+ * This can be either the bytes of the genesis document itself or the bytes of a key that will be used to create the genesis document.
3219
+ * @param {DidCreateOptions} options Options for creating the identifier, including the idType (key or external), version, and network.
3220
+ * @param {string} options.idType The type of identifier to create, either 'KEY' or 'EXTERNAL'. Defaults to 'KEY'.
3221
+ * @param {number} options.version The version number of the did:btcr2 specification to use for creating the identifier. Defaults to 1.
3222
+ * @param {string} options.network The Bitcoin network to use for the identifier, e.g. 'bitcoin', 'testnet', etc. Defaults to 'bitcoin'.
3223
+ * @returns {Promise<string>} Promise resolving to an identifier string.
3224
+ * @throws {MethodError} if any of the checks fail
3225
+ * @example
3226
+ * ```ts
3227
+ * const genesisBytes = SchnorrKeyPair.generate().publicKey.compressed;
3228
+ * const did = DidBtcr2.create(genesisBytes, { idType: 'KEY', network: 'regtest' });
3229
+ * ```
3230
+ */
3231
+ static create(genesisBytes, options) {
3232
+ const { idType, version = 1, network = "bitcoin" } = options || {};
3233
+ if (!idType) {
3234
+ throw new import_common16.MethodError(
3235
+ "idType is required for creating a did:btcr2 identifier",
3236
+ import_common16.INVALID_DID_DOCUMENT,
3237
+ options
3238
+ );
3239
+ }
3240
+ return Identifier.encode(genesisBytes, { idType, version, network });
3241
+ }
3242
+ /**
3243
+ * Entry point for section {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html | 7.2 Resolve}.
3244
+ *
3245
+ * Factory method that performs pure setup and returns a {@link Resolver} state machine.
3246
+ * The caller drives resolution by calling `resolver.resolve()` and `resolver.provide()`.
3247
+ * Analogous to Rust's `Document::read()`.
3248
+ *
3249
+ * @param {string} did The did:btcr2 identifier to be resolved.
3250
+ * @param {ResolutionOptions} resolutionOptions Options used during the resolution process.
3251
+ * @returns {Resolver} A sans-I/O state machine the caller drives to completion.
3252
+ * @example
3253
+ * ```ts
3254
+ * const resolver = DidBtcr2.resolve(did, { sidecar });
3255
+ * let state = resolver.resolve();
3256
+ * while (state.status === 'action-required') {
3257
+ * for (const need of state.needs) { ... provide data ... }
3258
+ * state = resolver.resolve();
3259
+ * }
3260
+ * const { didDocument, metadata } = state.result;
3261
+ * ```
3262
+ */
3263
+ static resolve(did, resolutionOptions = {}) {
3264
+ const didComponents = Identifier.decode(did);
3265
+ const sidecarData = Resolver.sidecarData(resolutionOptions.sidecar);
3266
+ const currentDocument = didComponents.hrp === import_common16.IdentifierHrp.k ? Resolver.deterministic(didComponents) : null;
3267
+ return new Resolver(didComponents, sidecarData, currentDocument, {
3268
+ versionId: resolutionOptions.versionId,
3269
+ versionTime: resolutionOptions.versionTime,
3270
+ genesisDocument: resolutionOptions.sidecar?.genesisDocument,
3271
+ maxDiscoveryRounds: resolutionOptions.maxDiscoveryRounds,
3272
+ minConf: resolutionOptions.minConf
3273
+ });
3274
+ }
3275
+ /**
3276
+ * Entry point for section {@link https://dcdpr.github.io/did-btcr2/#update | 7.3 Update}.
3277
+ *
3278
+ * Factory method that validates the update parameters and returns a sans-I/O
3279
+ * {@link Updater} state machine. The caller drives the updater through its
3280
+ * phases (Construct -> Sign -> Broadcast -> Complete) by calling `advance()` and
3281
+ * `provide()`. The method package performs **zero I/O**: signing key retrieval
3282
+ * (or KMS delegation) and the on-chain broadcast are the caller's responsibility.
3283
+ *
3284
+ * For a fully-wired version with Bitcoin broadcast and key handling, see
3285
+ * `DidMethodApi.update()` in `@did-btcr2/api`.
3286
+ *
3287
+ * @param params Update construction parameters.
3288
+ * @param {Btcr2DidDocument} params.sourceDocument The DID document being updated.
3289
+ * @param {PatchOperation[]} params.patches The JSON Patch operations to apply.
3290
+ * @param {number} params.sourceVersionId The version ID before applying the update.
3291
+ * @param {string} params.verificationMethodId The verification method ID to sign with.
3292
+ * @param {string} params.beaconId The beacon service ID to broadcast through.
3293
+ * @returns {Updater} A sans-I/O state machine for driving the update.
3294
+ * @throws {UpdateError} `INVALID_DID_UPDATE` if `sourceVersionId` is not an integer of at
3295
+ * least 1, if no entry of `capabilityInvocation` identifies the verification method, if a
3296
+ * reference entry names no member of `verificationMethod`, or if the beacon service is not
3297
+ * found. `INVALID_DID_DOCUMENT` if the method is not of type `Multikey` or does not have a
3298
+ * `zQ3s` publicKeyMultibase prefix.
3299
+ */
3300
+ static update({
3301
+ sourceDocument,
3302
+ patches,
3303
+ sourceVersionId,
3304
+ verificationMethodId,
3305
+ beaconId
3306
+ }) {
3307
+ if (!Number.isInteger(sourceVersionId) || sourceVersionId < 1) {
3308
+ throw new import_common16.UpdateError(
3309
+ `Invalid sourceVersionId: expected an integer of at least 1, got ${String(sourceVersionId)}.`,
3310
+ import_common16.INVALID_DID_UPDATE,
3311
+ { sourceVersionId }
3312
+ );
3313
+ }
3314
+ const entry = Appendix.capabilityInvocationEntry(sourceDocument, verificationMethodId);
3315
+ if (entry === void 0) {
3316
+ throw new import_common16.UpdateError(
3317
+ "Invalid verificationMethodId: not authorized for capabilityInvocation",
3318
+ import_common16.INVALID_DID_UPDATE,
3319
+ { verificationMethodId, capabilityInvocation: sourceDocument.capabilityInvocation }
3320
+ );
3321
+ }
3322
+ const verificationMethod = Appendix.verificationMethodOfEntry(sourceDocument, entry);
3323
+ if (!verificationMethod) {
3324
+ throw new import_common16.UpdateError(
3325
+ "Invalid verificationMethodId: not found in source document",
3326
+ import_common16.INVALID_DID_UPDATE,
3327
+ { verificationMethodId }
3328
+ );
3329
+ }
3330
+ if (verificationMethod.type !== MULTIKEY_VERIFICATION_METHOD_TYPE) {
3331
+ throw new import_common16.UpdateError(
3332
+ `Invalid verificationMethod: verificationMethod.type must be "${MULTIKEY_VERIFICATION_METHOD_TYPE}"`,
3333
+ import_common16.INVALID_DID_DOCUMENT,
3334
+ verificationMethod
3335
+ );
3336
+ }
3337
+ if (!verificationMethod.publicKeyMultibase?.startsWith(MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX)) {
3338
+ throw new import_common16.UpdateError(
3339
+ `Invalid verificationMethodId: publicKeyMultibase prefix must start with "${MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX}"`,
3340
+ import_common16.INVALID_DID_DOCUMENT,
3341
+ verificationMethod
3342
+ );
3343
+ }
3344
+ const targetBeaconId = Appendix.absoluteDidUrl(beaconId, sourceDocument.id);
3345
+ const beaconService = sourceDocument.service.filter((service) => targetBeaconId !== void 0 && Appendix.absoluteDidUrl(service.id, sourceDocument.id) === targetBeaconId).filter((service) => !!service).shift();
3346
+ if (!beaconService) {
3347
+ throw new import_common16.UpdateError(
3348
+ "No beacon service found for provided beaconId",
3349
+ import_common16.INVALID_DID_UPDATE,
3350
+ { sourceDocument, beaconId }
3351
+ );
3352
+ }
3353
+ return new Updater({
3354
+ sourceDocument,
3355
+ patches,
3356
+ sourceVersionId,
3357
+ verificationMethod,
3358
+ beaconService
3359
+ });
3360
+ }
3361
+ /**
3362
+ * Entry point for section {@link https://dcdpr.github.io/did-btcr2/operations/deactivate.html | 7.4 Deactivate}.
3363
+ *
3364
+ * Deactivate is the Update operation with the predetermined patch {@link DEACTIVATION_PATCH}:
3365
+ * it adds the `deactivated` property with the value `true`. The factory returns the
3366
+ * {@link Updater} that {@link DidBtcr2.update} returns for that patch, and the caller drives
3367
+ * it in the same way. Resolution stops at the deactivation for good. The factory does not
3368
+ * refuse a source document that is deactivated already; the api does (ADR 100).
3369
+ *
3370
+ * @param params Deactivation parameters: the parameters of {@link DidBtcr2.update} without `patches`.
3371
+ * @returns {Updater} A sans-I/O state machine for driving the deactivation.
3372
+ * @throws {UpdateError} As {@link DidBtcr2.update}.
3373
+ */
3374
+ static deactivate({
3375
+ sourceDocument,
3376
+ sourceVersionId,
3377
+ verificationMethodId,
3378
+ beaconId
3379
+ }) {
3380
+ return this.update({
3381
+ sourceDocument,
3382
+ patches: [{ ...DEACTIVATION_PATCH }],
3383
+ sourceVersionId,
3384
+ verificationMethodId,
3385
+ beaconId
3386
+ });
3387
+ }
3388
+ /**
3389
+ * Given the W3C DID Document of a `did:btcr2` identifier, return the signing verification method that will be used
3390
+ * for signing messages and credentials. If given, the `methodId` parameter is used to select the
3391
+ * verification method. If not given, the Identity Key's verification method with an ID fragment
3392
+ * of '#initialKey' is used.
3393
+ * @param {Btcr2DidDocument} didDocument The DID Document of the `did:btcr2` identifier.
3394
+ * @param {string} [methodId] Optional verification method ID to be used for signing.
3395
+ * @returns {DidVerificationMethod} Promise resolving to the {@link DidVerificationMethod} object used for signing.
3396
+ * @throws {DidError} if the parsed did method does not match `btcr2` or signing method could not be determined.
3397
+ */
3398
+ static getSigningMethod(didDocument, methodId) {
3399
+ methodId ??= "#initialKey";
3400
+ const parsedDid = import_dids2.Did.parse(didDocument.id);
3401
+ if (parsedDid && parsedDid.method !== this.methodName) {
3402
+ throw new import_common16.MethodError(`Method not supported: ${parsedDid.method}`, import_common16.METHOD_NOT_SUPPORTED, { identifier: didDocument.id });
3403
+ }
3404
+ const targetId = Appendix.absoluteDidUrl(methodId, didDocument.id) ?? Appendix.relationshipMethodId(didDocument.assertionMethod?.[0], didDocument.id);
3405
+ const verificationMethod = targetId === void 0 ? void 0 : Appendix.getVerificationMethods(didDocument).find(
3406
+ (vm) => Appendix.absoluteDidUrl(vm.id, didDocument.id) === targetId
3407
+ );
3408
+ if (!(verificationMethod && verificationMethod.publicKeyMultibase)) {
3409
+ throw new import_dids2.DidError(
3410
+ import_dids2.DidErrorCode.InternalError,
3411
+ "A verification method intended for signing could not be determined from the DID Document"
3412
+ );
3413
+ }
3414
+ return verificationMethod;
3415
+ }
3416
+ };
3175
3417
  // Annotate the CommonJS export names for ESM import in node:
3176
3418
  0 && (module.exports = {
3177
3419
  AggregateBeaconError,
@@ -3186,6 +3428,7 @@ var DidDocumentBuilder = class {
3186
3428
  CASBeacon,
3187
3429
  CASBeaconError,
3188
3430
  CHANGE_OUTPUT_VBYTES,
3431
+ DEACTIVATION_PATCH,
3189
3432
  DEFAULT_FEE_ESTIMATOR,
3190
3433
  DEFAULT_MIN_CONF,
3191
3434
  DID_REGEX,