@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/README.md +9 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +3 -3
- package/dist/browser.mjs +3 -3
- package/dist/cjs/index.js +1080 -847
- package/dist/esm/core/btcr2-update.js +11 -0
- package/dist/esm/core/btcr2-update.js.map +1 -1
- package/dist/esm/core/resolver.js +142 -59
- package/dist/esm/core/resolver.js.map +1 -1
- package/dist/esm/core/updater.js +33 -3
- package/dist/esm/core/updater.js.map +1 -1
- package/dist/esm/did-btcr2.js +53 -20
- package/dist/esm/did-btcr2.js.map +1 -1
- package/dist/esm/utils/appendix.js +39 -2
- package/dist/esm/utils/appendix.js.map +1 -1
- package/dist/esm/utils/error-cause.js +16 -0
- package/dist/esm/utils/error-cause.js.map +1 -0
- package/dist/types/core/btcr2-update.d.ts +15 -8
- package/dist/types/core/btcr2-update.d.ts.map +1 -1
- package/dist/types/core/resolver.d.ts +34 -0
- package/dist/types/core/resolver.d.ts.map +1 -1
- package/dist/types/core/updater.d.ts.map +1 -1
- package/dist/types/did-btcr2.d.ts +24 -3
- package/dist/types/did-btcr2.d.ts.map +1 -1
- package/dist/types/utils/appendix.d.ts +24 -0
- package/dist/types/utils/appendix.d.ts.map +1 -1
- package/dist/types/utils/error-cause.d.ts +16 -0
- package/dist/types/utils/error-cause.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/core/btcr2-update.ts +20 -8
- package/src/core/resolver.ts +174 -74
- package/src/core/updater.ts +41 -3
- package/src/did-btcr2.ts +70 -25
- package/src/utils/appendix.ts +48 -2
- package/src/utils/error-cause.ts +23 -0
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
|
|
883
|
-
if (
|
|
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
|
|
1985
|
-
var
|
|
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
|
|
2035
|
+
var import_cryptosuite = require("@did-btcr2/cryptosuite");
|
|
2036
|
+
var import_keypair3 = require("@did-btcr2/keypair");
|
|
1997
2037
|
|
|
1998
|
-
// src/
|
|
2038
|
+
// src/utils/error-cause.ts
|
|
1999
2039
|
var import_common11 = require("@did-btcr2/common");
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
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
|
-
*
|
|
2109
|
+
* The specific phase the Resolver is current in.
|
|
2010
2110
|
*/
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
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
|
-
*
|
|
2023
|
-
*
|
|
2024
|
-
*
|
|
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
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
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
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
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
|
-
|
|
2058
|
-
return unsignedUpdate;
|
|
2178
|
+
this.#phase = currentDocument ? "BeaconDiscovery" /* BeaconDiscovery */ : "GenesisDocument" /* GenesisDocument */;
|
|
2059
2179
|
}
|
|
2060
2180
|
/**
|
|
2061
|
-
* Implements subsection {@link
|
|
2062
|
-
*
|
|
2063
|
-
* @
|
|
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
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
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
|
-
|
|
2096
|
-
|
|
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
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
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/
|
|
2123
|
-
*
|
|
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
|
|
2138
|
-
const
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
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
|
-
*
|
|
2149
|
-
*
|
|
2150
|
-
*
|
|
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
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
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
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
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
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
)
|
|
2227
|
-
|
|
2228
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
2286
|
-
*
|
|
2287
|
-
*
|
|
2288
|
-
*
|
|
2289
|
-
*
|
|
2290
|
-
*
|
|
2291
|
-
* @param {
|
|
2292
|
-
* @
|
|
2293
|
-
* @throws {
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
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
|
-
*
|
|
2313
|
-
*
|
|
2314
|
-
*
|
|
2315
|
-
*
|
|
2316
|
-
*
|
|
2317
|
-
*
|
|
2318
|
-
* @param {
|
|
2319
|
-
* @
|
|
2320
|
-
* @
|
|
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
|
|
2333
|
-
const
|
|
2334
|
-
const
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
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
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
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 (
|
|
2394
|
-
throw new import_common12.
|
|
2395
|
-
|
|
2396
|
-
import_common12.
|
|
2397
|
-
|
|
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
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
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
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
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
|
-
{
|
|
2446
|
+
{ verificationMethodId }
|
|
2414
2447
|
);
|
|
2415
2448
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
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
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
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
|
-
|
|
2451
|
-
|
|
2452
|
-
}
|
|
2453
|
-
|
|
2454
|
-
|
|
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
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
2589
|
-
*
|
|
2590
|
-
*
|
|
2591
|
-
|
|
2592
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
2611
|
+
throw new import_common12.ResolveError(
|
|
2899
2612
|
`Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
|
|
2900
|
-
|
|
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 =
|
|
2909
|
-
this.#updateHashHistory.push((0,
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
2713
|
+
throw new import_common12.ResolveError(
|
|
3001
2714
|
"Provided data for NeedGenesisDocument must be a document object.",
|
|
3002
|
-
|
|
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
|
|
2724
|
+
throw new import_common12.ResolveError(
|
|
3012
2725
|
"Provided data for NeedBeaconSignals must be a Map of beacon services to signals.",
|
|
3013
|
-
|
|
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
|
|
2737
|
+
throw new import_common12.ResolveError(
|
|
3025
2738
|
"Provided data for NeedCASAnnouncement is not a CAS announcement.",
|
|
3026
|
-
|
|
2739
|
+
import_common12.INVALID_DID_UPDATE,
|
|
3027
2740
|
{ kind: need.kind }
|
|
3028
2741
|
);
|
|
3029
2742
|
}
|
|
3030
|
-
const announcementHash = (0,
|
|
3031
|
-
if (announcementHash !== need.announcementHash) {
|
|
3032
|
-
throw new
|
|
3033
|
-
`CAS announcement hash mismatch: expected ${need.announcementHash}, got ${announcementHash}.`,
|
|
3034
|
-
|
|
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.#
|
|
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 "
|
|
3042
|
-
if (
|
|
3043
|
-
throw new
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
{
|
|
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
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
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.#
|
|
3120
|
+
this.#state = {
|
|
3121
|
+
phase: "Broadcast",
|
|
3122
|
+
unsignedUpdate: this.#state.unsignedUpdate,
|
|
3123
|
+
signedUpdate: this.#state.signedUpdate
|
|
3124
|
+
};
|
|
3058
3125
|
break;
|
|
3059
3126
|
}
|
|
3060
|
-
case "
|
|
3061
|
-
if (
|
|
3062
|
-
throw new
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
{
|
|
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.#
|
|
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,
|