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