@did-btcr2/method 0.59.0 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  Appendix: () => Appendix,
25
25
  BECH32M_CHARS: () => BECH32M_CHARS,
26
26
  BTCR2_DID_DOCUMENT_CONTEXT: () => BTCR2_DID_DOCUMENT_CONTEXT,
27
+ BTCR2_UPDATE_CONTEXT: () => BTCR2_UPDATE_CONTEXT,
27
28
  BeaconError: () => BeaconError,
28
29
  BeaconFactory: () => BeaconFactory,
29
30
  BeaconSignalDiscovery: () => BeaconSignalDiscovery,
@@ -63,6 +64,7 @@ __export(index_exports, {
63
64
  detectSingletonScriptKind: () => detectSingletonScriptKind,
64
65
  extractOpReturnSignalHash: () => extractOpReturnSignalHash,
65
66
  getAggregationCommunicationKey: () => getAggregationCommunicationKey,
67
+ isBtcr2UpdateContext: () => isBtcr2UpdateContext,
66
68
  isMultikeyVerificationMethod: () => isMultikeyVerificationMethod,
67
69
  opReturnScript: () => opReturnScript,
68
70
  resolveBtcr2SenderPk: () => resolveBtcr2SenderPk,
@@ -711,13 +713,13 @@ var BeaconFactory = class {
711
713
  };
712
714
 
713
715
  // src/core/beacon/signal-discovery.ts
714
- var import_bitcoin3 = require("@did-btcr2/bitcoin");
715
- var import_common9 = require("@did-btcr2/common");
716
+ var import_bitcoin4 = require("@did-btcr2/bitcoin");
717
+ var import_common10 = require("@did-btcr2/common");
716
718
 
717
719
  // src/core/beacon/utils.ts
718
- var import_bitcoin2 = require("@did-btcr2/bitcoin");
719
- var import_common8 = require("@did-btcr2/common");
720
- var import_btc_signer2 = require("@scure/btc-signer");
720
+ var import_bitcoin3 = require("@did-btcr2/bitcoin");
721
+ var import_common9 = require("@did-btcr2/common");
722
+ var import_btc_signer3 = require("@scure/btc-signer");
721
723
 
722
724
  // src/utils/appendix.ts
723
725
  var import_dids = require("@web5/dids");
@@ -847,9 +849,10 @@ var Appendix = class _Appendix {
847
849
  * ```
848
850
  * {
849
851
  * "@context": [
852
+ * "https://w3id.org/json-ld-patch/v1",
850
853
  * "https://w3id.org/zcap/v1",
851
854
  * "https://w3id.org/security/data-integrity/v2",
852
- * "https://w3id.org/json-ld-patch/v1"
855
+ * "https://btcr2.dev/context/v1"
853
856
  * ],
854
857
  * "patch": [
855
858
  * {
@@ -899,901 +902,1059 @@ var Appendix = class _Appendix {
899
902
  };
900
903
 
901
904
  // src/core/identifier.ts
905
+ var import_common8 = require("@did-btcr2/common");
906
+ var import_keypair2 = require("@did-btcr2/keypair");
907
+ var import_utils4 = require("@noble/curves/utils.js");
908
+ var import_base = require("@scure/base");
909
+
910
+ // src/utils/did-document.ts
911
+ var import_bitcoin2 = require("@did-btcr2/bitcoin");
902
912
  var import_common7 = require("@did-btcr2/common");
903
913
  var import_keypair = require("@did-btcr2/keypair");
904
- var import_base = require("@scure/base");
905
- var Identifier = class _Identifier {
906
- /**
907
- * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-encoding | 3.2 did:btcr2 Identifier Encoding}.
908
- *
909
- * A did:btcr2 DID consists of a did:btcr2 prefix, followed by an id-bech32 value, which is a Bech32m encoding of:
910
- * - the specification version;
911
- * - the Bitcoin network identifier; and
912
- * - either:
913
- * - a key-value representing a secp256k1 public key; or
914
- * - a hash-value representing the hash of an initiating external DID document.
915
- *
916
- * @param {KeyBytes | DocumentBytes} genesisBytes The genesis bytes (public key or document bytes).
917
- * @param {DidCreateOptions} options The DID creation options.
918
- * @returns {string} The new did:btcr2 identifier.
919
- */
920
- static encode(genesisBytes, options) {
921
- const { idType, version = 1, network = "bitcoin" } = options;
922
- if (!(idType in import_common7.IdentifierTypes)) {
923
- throw new import_common7.IdentifierError('Expected "idType" to be "KEY" or "EXTERNAL"', import_common7.INVALID_DID, { idType });
924
- }
925
- if (version !== 1) {
926
- throw new import_common7.IdentifierError('Expected "version" to be 1', import_common7.INVALID_DID, { version });
927
- }
928
- if (typeof network !== "string") {
929
- throw new import_common7.IdentifierError('Expected "network" to be a known network name', import_common7.INVALID_DID, { network });
930
- }
931
- const networkValue = import_common7.BitcoinNetworkNames[network];
932
- if (networkValue === void 0) {
933
- throw new import_common7.IdentifierError('Invalid "network" name', import_common7.INVALID_DID, { network });
934
- }
935
- if (idType === "KEY") {
936
- try {
937
- new import_keypair.CompressedSecp256k1PublicKey(genesisBytes);
938
- } catch {
939
- throw new import_common7.IdentifierError(
940
- 'Expected "genesisBytes" to be a valid compressed secp256k1 public key',
941
- import_common7.INVALID_DID,
942
- { genesisBytes }
943
- );
944
- }
945
- } else if (genesisBytes.length !== 32) {
946
- throw new import_common7.IdentifierError(
947
- 'Expected "genesisBytes" to be a 32-byte hash for EXTERNAL identifiers',
948
- import_common7.INVALID_DID,
949
- { genesisBytes }
950
- );
951
- }
952
- const hrp = idType === "KEY" ? "k" : "x";
953
- const firstByte = version - 1 << 4 | networkValue;
954
- const dataBytes = new Uint8Array([firstByte, ...genesisBytes]);
955
- return `did:btcr2:${import_base.bech32m.encodeFromBytes(hrp, dataBytes)}`;
914
+ var import_utils3 = require("@web5/dids/utils");
915
+ var import_btc_signer2 = require("@scure/btc-signer");
916
+ var BTCR2_DID_DOCUMENT_CONTEXT = [
917
+ "https://www.w3.org/ns/did/v1.1",
918
+ "https://btcr2.dev/context/v1"
919
+ ];
920
+ var MULTIKEY_VERIFICATION_METHOD_TYPE = "Multikey";
921
+ var MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX = "zQ3s";
922
+ var ID_PLACEHOLDER_VALUE = "did:btcr2:_";
923
+ var BECH32M_CHARS = "";
924
+ var DID_REGEX = /did:btcr2:(x1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]*)/g;
925
+ function isMultikeyVerificationMethod(vm) {
926
+ if (!Appendix.isDidVerificationMethod(vm)) {
927
+ return false;
956
928
  }
957
- /**
958
- * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}.
959
- * @param {string} identifier The BTCR2 DID to be parsed
960
- * @returns {DidComponents} The parsed identifier components. See {@link DidComponents} for details.
961
- * @throws {DidError} if an error occurs while parsing the identifier
962
- * @throws {DidErrorCode.InvalidDid} if identifier is invalid
963
- * @throws {DidErrorCode.MethodNotSupported} if the method is not supported
964
- */
965
- static decode(identifier) {
966
- const components = identifier.split(":");
967
- if (components.length !== 3) {
968
- throw new import_common7.IdentifierError(`Invalid did: ${identifier}`, import_common7.INVALID_DID, { identifier });
969
- }
970
- const [scheme, method, encoded] = components;
971
- if (scheme !== "did") {
972
- throw new import_common7.IdentifierError(`Invalid did: ${identifier}`, import_common7.INVALID_DID, { identifier });
973
- }
974
- if (method !== "btcr2") {
975
- throw new import_common7.IdentifierError(`Invalid did method: ${method}`, import_common7.METHOD_NOT_SUPPORTED, { identifier });
929
+ const { type, publicKeyMultibase } = vm;
930
+ return type === MULTIKEY_VERIFICATION_METHOD_TYPE && typeof publicKeyMultibase === "string" && publicKeyMultibase.startsWith(MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX);
931
+ }
932
+ var DidVerificationMethod = class {
933
+ id;
934
+ type;
935
+ controller;
936
+ publicKeyMultibase;
937
+ secretKeyMultibase;
938
+ constructor({ id, type, controller, publicKeyMultibase, secretKeyMultibase }) {
939
+ if (type !== MULTIKEY_VERIFICATION_METHOD_TYPE) {
940
+ throw new import_common7.DidDocumentError(
941
+ `Invalid verification method: type must be "${MULTIKEY_VERIFICATION_METHOD_TYPE}"`,
942
+ import_common7.INVALID_DID_DOCUMENT,
943
+ { id, type }
944
+ );
976
945
  }
977
- if (!encoded) {
978
- throw new import_common7.IdentifierError(`Invalid method-specific id: ${identifier}`, import_common7.INVALID_DID, { identifier });
946
+ if (typeof publicKeyMultibase !== "string" || !publicKeyMultibase.startsWith(MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX)) {
947
+ throw new import_common7.DidDocumentError(
948
+ `Invalid verification method: publicKeyMultibase must start with "${MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX}"`,
949
+ import_common7.INVALID_DID_DOCUMENT,
950
+ { id, publicKeyMultibase }
951
+ );
979
952
  }
980
- const { prefix: hrp, bytes: dataBytes } = import_base.bech32m.decodeToBytes(encoded);
981
- if (!["x", "k"].includes(hrp)) {
982
- throw new import_common7.IdentifierError(`Invalid hrp: ${hrp}`, import_common7.INVALID_DID, { identifier });
953
+ this.id = id;
954
+ this.type = type;
955
+ this.controller = controller;
956
+ this.publicKeyMultibase = publicKeyMultibase;
957
+ this.secretKeyMultibase = secretKeyMultibase;
958
+ if (!secretKeyMultibase) {
959
+ delete this.secretKeyMultibase;
983
960
  }
984
- if (!dataBytes || dataBytes.length < 1) {
985
- throw new import_common7.IdentifierError(`Failed to decode id: ${encoded}`, import_common7.INVALID_DID, { identifier });
961
+ }
962
+ // TODO: Add helper methods and properties
963
+ };
964
+ var DidDocument = class _DidDocument {
965
+ id;
966
+ "@context" = [
967
+ "https://www.w3.org/ns/did/v1.1",
968
+ "https://btcr2.dev/context/v1"
969
+ ];
970
+ verificationMethod;
971
+ authentication;
972
+ assertionMethod;
973
+ capabilityInvocation;
974
+ capabilityDelegation;
975
+ service;
976
+ deactivated;
977
+ constructor(document) {
978
+ if (!document.id) {
979
+ throw new import_common7.DidDocumentError("DID Document must have an id", import_common7.INVALID_DID_DOCUMENT, document);
986
980
  }
987
- const idType = hrp === "k" ? "KEY" : "EXTERNAL";
988
- const btcr2Version = dataBytes[0] >>> 4;
989
- if (btcr2Version !== 0) {
990
- throw new import_common7.IdentifierError(`Invalid btcr2_version (expected 0): ${btcr2Version}`, import_common7.INVALID_DID, { identifier });
981
+ const idType = document.id.includes("k1") ? import_common7.IdentifierTypes.KEY : import_common7.IdentifierTypes.EXTERNAL;
982
+ const isGenesis = document.id === ID_PLACEHOLDER_VALUE;
983
+ const { id, verificationMethod: vm, service } = document;
984
+ if (!isGenesis) {
985
+ if (!_DidDocument.isValidId(id)) {
986
+ throw new import_common7.DidDocumentError(`Invalid id: ${id}`, import_common7.INVALID_DID_DOCUMENT, document);
987
+ }
988
+ if (!_DidDocument.isValidVerificationMethods(vm)) {
989
+ throw new import_common7.DidDocumentError("Invalid verificationMethod: " + vm, import_common7.INVALID_DID_DOCUMENT, document);
990
+ }
991
+ if (!_DidDocument.isValidServices(service)) {
992
+ throw new import_common7.DidDocumentError("Invalid service: " + service, import_common7.INVALID_DID_DOCUMENT, document);
993
+ }
991
994
  }
992
- const version = 1;
993
- const networkValue = dataBytes[0] & 15;
994
- const networkName = import_common7.BitcoinNetworkNames[networkValue];
995
- let network;
996
- if (typeof networkName === "string") {
997
- network = networkName;
998
- } else if (networkValue >= 12 && networkValue <= 14) {
999
- network = networkValue - 11;
995
+ this.id = document.id;
996
+ this.verificationMethod = document.verificationMethod || [];
997
+ this.service = document.service || [];
998
+ this["@context"] = document["@context"] || [
999
+ "https://www.w3.org/ns/did/v1.1",
1000
+ "https://btcr2.dev/context/v1"
1001
+ ];
1002
+ if (idType === import_common7.IdentifierTypes.KEY) {
1003
+ const keyRef = `${this.id}#initialKey`;
1004
+ this.authentication = document.authentication || [keyRef];
1005
+ this.assertionMethod = document.assertionMethod || [keyRef];
1006
+ this.capabilityInvocation = document.capabilityInvocation || [keyRef];
1007
+ this.capabilityDelegation = document.capabilityDelegation || [keyRef];
1000
1008
  } else {
1001
- throw new import_common7.IdentifierError(`Invalid network: ${networkValue}`, import_common7.INVALID_DID, { identifier });
1009
+ this.authentication = document.authentication;
1010
+ this.assertionMethod = document.assertionMethod;
1011
+ this.capabilityInvocation = document.capabilityInvocation;
1012
+ this.capabilityDelegation = document.capabilityDelegation;
1002
1013
  }
1003
- const genesisBytes = dataBytes.slice(1);
1004
- if (idType === "KEY") {
1005
- try {
1006
- new import_keypair.CompressedSecp256k1PublicKey(genesisBytes);
1007
- } catch {
1008
- throw new import_common7.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common7.INVALID_DID, { identifier });
1009
- }
1010
- } else if (genesisBytes.length !== 32) {
1011
- throw new import_common7.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common7.INVALID_DID, { identifier });
1014
+ _DidDocument.sanitize(this);
1015
+ if (isGenesis) {
1016
+ this.validateGenesis();
1017
+ } else {
1018
+ _DidDocument.validate(this);
1012
1019
  }
1013
- return { idType, hrp, version, network, genesisBytes };
1014
1020
  }
1015
1021
  /**
1016
- * Generates a new did:btcr2 identifier based on a newly generated key pair.
1017
- * @returns {string} The new did:btcr2 identifier.
1022
+ * Convert the DidDocument to a JSON object.
1023
+ * @returns {DidDocument} The JSON representation of the DidDocument.
1018
1024
  */
1019
- static generate() {
1020
- const keyPair = import_keypair.SchnorrKeyPair.generate();
1021
- const did = this.encode(
1022
- keyPair.publicKey.compressed,
1023
- {
1024
- idType: "KEY",
1025
- version: 1,
1026
- network: "regtest"
1027
- }
1028
- );
1029
- return { keyPair: keyPair.exportJSON(), did };
1025
+ toJSON() {
1026
+ return {
1027
+ id: this.id,
1028
+ "@context": this["@context"],
1029
+ verificationMethod: this.verificationMethod,
1030
+ authentication: this.authentication,
1031
+ assertionMethod: this.assertionMethod,
1032
+ capabilityInvocation: this.capabilityInvocation,
1033
+ capabilityDelegation: this.capabilityDelegation,
1034
+ service: this.service,
1035
+ deactivated: this.deactivated
1036
+ };
1030
1037
  }
1031
1038
  /**
1032
- * Extracts the compressed secp256k1 public key from a KEY-type did:btcr2 identifier.
1033
- * @param {string} did The did:btcr2 identifier to extract the public key from.
1034
- * @returns {CompressedSecp256k1PublicKey} The compressed public key.
1035
- * @throws {IdentifierError} If the DID is EXTERNAL type (genesis bytes are a hash, not a pubkey).
1039
+ * Create a minimal DidDocument from "k1" btcr2 identifier.
1040
+ * @param {string} publicKeyMultibase The public key in multibase format.
1041
+ * @param {Array<BeaconService>} service The beacon services to be included in the document.
1042
+ * @returns {DidDocument} A new DidDocument with the placeholder ID.
1036
1043
  */
1037
- static getPublicKey(did) {
1038
- const { idType, genesisBytes } = _Identifier.decode(did);
1039
- if (idType !== "KEY") {
1040
- throw new import_common7.IdentifierError(
1041
- `Cannot extract public key from EXTERNAL DID: ${did}. EXTERNAL DIDs encode a document hash, not a public key.`,
1042
- import_common7.INVALID_DID,
1043
- { did, idType }
1044
- );
1045
- }
1046
- return new import_keypair.CompressedSecp256k1PublicKey(genesisBytes);
1044
+ static fromKeyIdentifier(id, publicKeyMultibase, service) {
1045
+ id = id.includes("#") ? id : `${id}#initialKey`;
1046
+ const document = {
1047
+ id,
1048
+ verificationMethod: [
1049
+ new DidVerificationMethod({
1050
+ id,
1051
+ type: "Multikey",
1052
+ controller: id,
1053
+ publicKeyMultibase
1054
+ })
1055
+ ],
1056
+ service
1057
+ };
1058
+ return new _DidDocument(document);
1047
1059
  }
1048
1060
  /**
1049
- * Validates a did:btcr2 identifier.
1050
- * @param {string} identifier The did:btcr2 identifier to validate.
1051
- * @returns {boolean} True if the identifier is valid, false otherwise.
1061
+ * Create a DidDocument from "x1" btcr2 identifier.
1062
+ * @param {ExternalData} data The verification methods of the DID Document.
1063
+ * @returns {DidDocument} A new DidDocument.
1052
1064
  */
1053
- static isValid(identifier) {
1054
- try {
1055
- this.decode(identifier);
1056
- return true;
1057
- } catch {
1058
- return false;
1059
- }
1065
+ static fromExternalIdentifier(data) {
1066
+ return new _DidDocument(data);
1060
1067
  }
1061
- };
1062
-
1063
- // src/core/beacon/utils.ts
1064
- var BeaconUtils = class {
1065
1068
  /**
1066
- * Converts a BIP21 Bitcoin URI to a Bitcoin address
1067
- * @param {string} uri The BIP21 Bitcoin URI to convert
1068
- * @returns {string} The Bitcoin address extracted from the URI
1069
- * @throws {DidMethodError} if the URI is not a valid Bitcoin URI
1069
+ * Sanitize the DID Document by removing undefined values
1070
+ * @returns {DidDocument} The sanitized DID Document
1070
1071
  */
1071
- static parseBitcoinAddress(uri) {
1072
- if (!uri.startsWith("bitcoin:")) {
1073
- throw new import_common8.MethodError("Invalid Bitcoin URI format", "BEACON_SERVICE_ERROR", { uri });
1072
+ static sanitize(doc) {
1073
+ for (const key of Object.keys(doc)) {
1074
+ if (doc[key] === void 0) {
1075
+ delete doc[key];
1076
+ }
1074
1077
  }
1075
- return uri.replace("bitcoin:", "").split("?")[0];
1078
+ return doc;
1076
1079
  }
1077
1080
  /**
1078
- * Validates that the given object is a Beacon Service
1079
- * @param {BeaconService} obj The object to validate
1080
- * @returns {boolean} A boolean indicating whether the object is a Beacon Service
1081
+ * Validates a DidDocument by breaking it into modular validation methods.
1082
+ * @param {DidDocument} didDocument The DID document to validate.
1083
+ * @returns {boolean} True if the DID document is valid.
1084
+ * @throws {DidDocumentError} If any validation check fails.
1081
1085
  */
1082
- static isBeaconService(obj) {
1083
- if (!Appendix.isDidService(obj)) return false;
1084
- if (!["SingletonBeacon", "CASBeacon", "SMTBeacon"].includes(obj.type)) return false;
1085
- if ([obj.serviceEndpoint].flat().some((ep) => typeof ep === "string" && !ep.startsWith("bitcoin:"))) return false;
1086
+ static isValid(didDocument) {
1087
+ if (!this.isValidContext(didDocument?.["@context"])) {
1088
+ throw new import_common7.DidDocumentError('Invalid "@context"', import_common7.INVALID_DID_DOCUMENT, didDocument);
1089
+ }
1090
+ if (!this.isValidId(didDocument?.id)) {
1091
+ throw new import_common7.DidDocumentError('Invalid "id"', import_common7.INVALID_DID_DOCUMENT, didDocument);
1092
+ }
1093
+ if (!this.isValidVerificationMethods(didDocument?.verificationMethod)) {
1094
+ throw new import_common7.DidDocumentError('Invalid "verificationMethod"', import_common7.INVALID_DID_DOCUMENT, didDocument);
1095
+ }
1096
+ if (!this.isValidServices(didDocument?.service)) {
1097
+ throw new import_common7.DidDocumentError('Invalid "service"', import_common7.INVALID_DID_DOCUMENT, didDocument);
1098
+ }
1099
+ if (!this.isValidVerificationRelationships(didDocument)) {
1100
+ throw new import_common7.DidDocumentError("Invalid verification relationships", import_common7.INVALID_DID_DOCUMENT, didDocument);
1101
+ }
1086
1102
  return true;
1087
1103
  }
1088
1104
  /**
1089
- * Extracts the services from a given DID Document
1090
- * @param {DidDocument} didDocument The DID Document to extract the services from
1091
- * @returns {DidService[]} An array of DidService objects
1092
- * @throws {TypeError} if the didDocument is not provided
1105
+ * Validates that "@context" exists and includes correct values.
1106
+ * @private
1107
+ * @param {DidDocument['@context']} context The context to validate.
1108
+ * @returns {boolean} True if the context is valid.
1093
1109
  */
1094
- static getBeaconServices(didDocument) {
1095
- return didDocument.service.filter(this.isBeaconService) ?? [];
1110
+ static isValidContext(context) {
1111
+ if (!Array.isArray(context) || context.length === 0) return false;
1112
+ return BTCR2_DID_DOCUMENT_CONTEXT.every((required) => context.includes(required));
1096
1113
  }
1097
1114
  /**
1098
- * Create the 3 default Beacon Service Endpoints for a given `k` (public-key-based) identifier.
1099
- * @param {string} did The DID for which to create the beacon services.
1100
- * @returns {Array<Array<string>>} 2D Array of bitcoin addresses (p2pkh, p2wpkh, p2tr).
1101
- * @throws {DidMethodError} if the bitcoin address is invalid.
1115
+ * Validates that the DID Document has a valid id.
1116
+ * @private
1117
+ * @param {string} id The id to validate.
1118
+ * @returns {boolean} True if the id is valid.
1102
1119
  */
1103
- static createBeaconServices(did, beaconType) {
1120
+ static isValidId(id) {
1121
+ if (typeof id !== "string") return false;
1104
1122
  try {
1105
- const addrTypes = ["p2pkh", "p2wpkh", "p2tr"];
1106
- return addrTypes.map(
1107
- (addrType) => this.createBeaconService(did, addrType, beaconType)
1108
- );
1109
- } catch (error) {
1110
- throw new BeaconError(
1111
- "Failed to create beacon services: " + error.message,
1112
- "BEACON_SERVICE_ERROR",
1113
- { did, beaconType }
1114
- );
1123
+ Identifier.decode(id);
1124
+ return true;
1125
+ } catch {
1126
+ return false;
1115
1127
  }
1116
1128
  }
1117
1129
  /**
1118
- * Generate a set of Beacon Services for a given public key.
1119
- * @param {string} did The did for the beacon service.
1120
- * @param {string} addressType The type of bitcoin address to generate (p2pkh, p2wpkh, p2tr).
1121
- * @param {string} beaconType The type of beacon service to create.
1122
- * @returns {BeaconService} A BeaconService object.
1123
- * @throws {DidMethodError} if the bitcoin address is invalid.
1130
+ * Validates that verification methods exist and are correctly formatted.
1131
+ * @private
1132
+ * @param {DidVerificationMethod[]} verificationMethod The verification methods to validate.
1133
+ * @returns {boolean} True if the verification methods are valid.
1124
1134
  */
1125
- static createBeaconService(did, addressType, beaconType) {
1126
- try {
1127
- const components = Identifier.decode(did);
1128
- const network = (0, import_bitcoin2.getNetwork)(components.network);
1129
- const pubkey = components.genesisBytes;
1130
- const id = `${did}#initial${addressType.toUpperCase()}`;
1131
- const address = addressType === "p2tr" ? (0, import_btc_signer2.p2tr)(pubkey.slice(1, 33), void 0, network).address : addressType === "p2wpkh" ? (0, import_btc_signer2.p2wpkh)(pubkey, network).address : (0, import_btc_signer2.p2pkh)(pubkey, network).address;
1132
- const serviceEndpoint = `bitcoin:${address}`;
1133
- return { id, type: beaconType, serviceEndpoint };
1134
- } catch (error) {
1135
- throw new BeaconError(
1136
- "Failed to create beacon service: " + error.message,
1137
- "BEACON_SERVICE_ERROR",
1138
- { did, beaconType }
1139
- );
1140
- }
1135
+ static isValidVerificationMethods(verificationMethod) {
1136
+ return Array.isArray(verificationMethod) && verificationMethod.every(isMultikeyVerificationMethod);
1141
1137
  }
1142
1138
  /**
1143
- * Generate three default Beacon Service Endpoints for a given `k` (public-key-based) identifier.
1144
- * @param {string} did The DID for which to create the beacon services.
1145
- * @returns {Array<Array<string>>} 2D Array of bitcoin addresses (p2pkh, p2wpkh, p2tr).
1146
- * @throws {DidMethodError} if the bitcoin address is invalid.
1139
+ * Validates that the DID Document has valid services.
1140
+ * @private
1141
+ * @param {DidService[]} service The services to validate.
1142
+ * @returns {boolean} True if the services are valid.
1147
1143
  */
1148
- static generateBeaconServices({ id, publicKey, network, beaconType }) {
1149
- try {
1150
- const p2pkhAddr = (0, import_btc_signer2.p2pkh)(publicKey, network).address;
1151
- const p2wpkhAddr = (0, import_btc_signer2.p2wpkh)(publicKey, network).address;
1152
- const p2trAddr = (0, import_btc_signer2.p2tr)(publicKey.slice(1, 33), void 0, network).address;
1153
- if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
1154
- throw new import_common8.DidMethodError("Failed to generate bitcoin addresses");
1155
- }
1156
- return [
1157
- {
1158
- id: `${id}#initialP2PKH`,
1159
- type: beaconType,
1160
- serviceEndpoint: `bitcoin:${p2pkhAddr}`
1161
- },
1162
- {
1163
- id: `${id}#initialP2WPKH`,
1164
- type: beaconType,
1165
- serviceEndpoint: `bitcoin:${p2wpkhAddr}`
1166
- },
1167
- {
1168
- id: `${id}#initialP2TR`,
1169
- type: beaconType,
1170
- serviceEndpoint: `bitcoin:${p2trAddr}`
1171
- }
1172
- ];
1173
- } catch (error) {
1174
- throw new BeaconError(
1175
- "Failed to create beacon services: " + error.message,
1176
- "BEACON_SERVICE_ERROR",
1177
- { id, publicKey, network, beaconType }
1144
+ static isValidServices(service) {
1145
+ return Array.isArray(service) && service.every(import_utils3.isDidService);
1146
+ }
1147
+ /**
1148
+ * Validates verification relationships (authentication, assertionMethod, capabilityInvocation, capabilityDelegation).
1149
+ * @private
1150
+ * @param {DidDocument} didDocument The DID Document to validate.
1151
+ * @returns {boolean} True if the verification relationships are valid.
1152
+ */
1153
+ static isValidVerificationRelationships(didDocument) {
1154
+ const possibleVerificationRelationships = [
1155
+ "authentication",
1156
+ "assertionMethod",
1157
+ "capabilityInvocation",
1158
+ "capabilityDelegation"
1159
+ ];
1160
+ const keys = Object.keys(didDocument);
1161
+ const availableKeys = possibleVerificationRelationships.filter((key) => keys.includes(key));
1162
+ return availableKeys.every((key) => {
1163
+ const value = didDocument[key];
1164
+ return value && Array.isArray(value) && value.every(
1165
+ (entry) => typeof entry === "string" || Appendix.isDidVerificationMethod(entry)
1178
1166
  );
1167
+ });
1168
+ }
1169
+ /**
1170
+ * Validate the DID Document
1171
+ * @returns {DidDocument} Validated DID Document.
1172
+ * @throws {DidDocumentError} If the DID Document is invalid.
1173
+ */
1174
+ static validate(didDocument) {
1175
+ if (didDocument.id === ID_PLACEHOLDER_VALUE) {
1176
+ didDocument.validateGenesis();
1177
+ } else {
1178
+ _DidDocument.isValid(didDocument);
1179
1179
  }
1180
+ return didDocument;
1180
1181
  }
1181
1182
  /**
1182
- * Convert beacon service endpoints from BIP-21 URIs to addresses.
1183
- * @param {BeaconService} beacon The beacon service to parse.
1184
- * @returns {BeaconServiceAddress} The beacon service with the address field extracted from the serviceEndpoint.
1183
+ * Validate the GenesisDocument.
1184
+ * @returns {boolean} True if the GenesisDocument is valid.
1185
1185
  */
1186
- static parseBeaconServiceEndpoint(beacon) {
1187
- return { ...beacon, serviceEndpoint: beacon.serviceEndpoint.replace("bitcoin:", "") };
1186
+ validateGenesis() {
1187
+ if (this.id !== ID_PLACEHOLDER_VALUE) {
1188
+ throw new import_common7.DidDocumentError("Invalid GenesisDocument ID", import_common7.INVALID_DID_DOCUMENT, this);
1189
+ }
1190
+ if (!this.verificationMethod.every((vm) => vm.id.includes(ID_PLACEHOLDER_VALUE) && vm.controller === ID_PLACEHOLDER_VALUE)) {
1191
+ throw new import_common7.DidDocumentError("Invalid GenesisDocument verificationMethod", import_common7.INVALID_DID_DOCUMENT, this);
1192
+ }
1193
+ if (!this.service.every((svc) => svc.id.includes(ID_PLACEHOLDER_VALUE))) {
1194
+ throw new import_common7.DidDocumentError("Invalid GenesisDocument service", import_common7.INVALID_DID_DOCUMENT, this);
1195
+ }
1196
+ if (!_DidDocument.isValidVerificationRelationships(this)) {
1197
+ throw new import_common7.DidDocumentError("Invalid GenesisDocument assertionMethod", import_common7.INVALID_DID_DOCUMENT, this);
1198
+ }
1199
+ return true;
1188
1200
  }
1189
1201
  /**
1190
- * Get the beacon service ids from a list of beacon services.
1191
- * @param {DidDocument} didDocument The DID Document to extract the services from.
1192
- * @returns {string[]} An array of beacon service ids.
1202
+ * Convert the DidDocument to an GenesisDocument.
1203
+ * @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
1193
1204
  */
1194
- static getBeaconServiceIds(didDocument) {
1195
- return this.getBeaconServices(didDocument).map((beacon) => beacon.id);
1205
+ toIntermediate() {
1206
+ if (this.id.includes("k1")) {
1207
+ throw new import_common7.DidDocumentError("Cannot convert a key identifier to an intermediate document", import_common7.INVALID_DID_DOCUMENT, this);
1208
+ }
1209
+ return new GenesisDocument(this);
1196
1210
  }
1197
1211
  };
1198
-
1199
- // src/core/beacon/signal-discovery.ts
1200
- var BEACON_SIGNAL_SCRIPT = /^6a20([0-9a-f]{64})$/i;
1201
- function extractOpReturnSignalHash(scriptPubKey) {
1202
- if (!scriptPubKey) {
1203
- return null;
1212
+ var GenesisDocument = class _GenesisDocument extends DidDocument {
1213
+ constructor(document) {
1214
+ super(document);
1204
1215
  }
1205
- const signal = BEACON_SIGNAL_SCRIPT.exec(scriptPubKey.trim());
1206
- if (!signal) {
1207
- return null;
1216
+ /**
1217
+ * Convert the GenesisDocument to a DidDocument by replacing the placeholder value with the provided DID.
1218
+ * @param did The DID to replace the placeholder value in the document.
1219
+ * @returns {DidDocument} A new DidDocument with the placeholder value replaced by the provided DID.
1220
+ */
1221
+ toDidDocument(did) {
1222
+ const stringThis = JSON.stringify(this).replaceAll(ID_PLACEHOLDER_VALUE, did);
1223
+ const parseThis = JSON.parse(stringThis);
1224
+ return new DidDocument(parseThis);
1208
1225
  }
1209
- return signal[1].toLowerCase();
1210
- }
1211
- var BeaconSignalDiscovery = class _BeaconSignalDiscovery {
1212
1226
  /**
1213
- * Determines whether a candidate transaction spends an output controlled by the given
1214
- * beacon address.
1215
- *
1216
- * A Beacon Signal is a transaction that *spends from* a Beacon Address, but an address
1217
- * transaction listing returns every transaction touching the address in either
1218
- * direction. Without this check, anyone able to pay dust to a beacon address could
1219
- * attach an arbitrary 32-byte OP_RETURN and have it read as a signal, so the input side
1220
- * has to be inspected before a transaction is treated as one.
1221
- *
1222
- * Esplora embeds the spent output in `vin[].prevout`; when a backend omits it the
1223
- * funding transaction is fetched instead, so a missing field cannot silently drop a
1224
- * real signal.
1225
- *
1226
- * @param {RawTransactionRest} tx The candidate transaction.
1227
- * @param {string} address The beacon address the transaction must spend from.
1228
- * @param {BitcoinConnection} bitcoin Bitcoin network connection to use for REST calls.
1229
- * @returns {Promise<boolean>} True if at least one input spends an output of the beacon address.
1227
+ * Create an GenesisDocument from a DidDocument by replacing the DID with a placeholder value.
1228
+ * @param {DidDocument} didDocument The DidDocument to convert.
1229
+ * @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
1230
1230
  */
1231
- static async spendsFromAddress(tx, address, bitcoin) {
1232
- for (const vin of tx.vin ?? []) {
1233
- if (vin.is_coinbase) {
1234
- continue;
1235
- }
1236
- let prevout = vin.prevout;
1237
- if (!prevout && vin.txid) {
1238
- const fundingTx = await bitcoin.rest.transaction.get(vin.txid);
1239
- prevout = fundingTx?.vout?.[vin.vout];
1240
- }
1241
- if (prevout?.scriptpubkey_address === address) {
1242
- return true;
1243
- }
1244
- }
1245
- return false;
1231
+ static fromDidDocument(didDocument) {
1232
+ const intermediateDocument = import_common7.JSONUtils.cloneReplace(didDocument, DID_REGEX, ID_PLACEHOLDER_VALUE);
1233
+ return new _GenesisDocument(intermediateDocument);
1246
1234
  }
1247
1235
  /**
1248
- * Retrieves the beacon signals for the given array of BeaconService objects
1249
- * using an esplora/electrs REST API connection via a bitcoin I/O driver.
1250
- *
1251
- * The address listing includes mempool transactions. The method skips a
1252
- * transaction whose `status.confirmed` is not `true`. A mempool transaction
1253
- * has no block height and no block time, so it cannot carry block metadata.
1254
- * The specification also says that a resolver must not process an
1255
- * unconfirmed transaction. An absent flag counts as unconfirmed, as it does
1256
- * for UTXO selection. The check runs before the OP_RETURN parse, so a
1257
- * mempool transaction costs no prevout fetch. The {@link fullnode} path
1258
- * needs no such check: it walks mined blocks only.
1259
- *
1260
- * The `confirmations` count uses the block count fetched before the listing.
1261
- * A block that arrives between the two calls yields a count of `0` for its
1262
- * transactions. The resolver then excludes them, because its minimum is at
1263
- * least `1`. An under-count is the safe direction, so keep that order.
1264
- * @param {Array<BeaconService>} beaconServices Array of BeaconService objects to retrieve signals for
1265
- * @param {BitcoinConnection} bitcoin Bitcoin network connection to use for REST calls
1266
- * @returns {Promise<Map<BeaconService, Array<BeaconSignal>>>} Map of beacon service to its discovered signals
1236
+ * Create a minimal GenesisDocument with a placeholder ID.
1237
+ * @param {Array<DidVerificationMethod>} verificationMethod The public key in multibase format.
1238
+ * @param {VerificationRelationships} relationships The public key in multibase format.
1239
+ * @param {Array<BeaconService>} service The service to be included in the document.
1240
+ * @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
1267
1241
  */
1268
- static async indexer(beaconServices, bitcoin) {
1269
- const beaconServiceSignals = /* @__PURE__ */ new Map();
1270
- const currentBlockCount = await bitcoin.rest.block.count();
1271
- for (const beaconService of beaconServices) {
1272
- beaconServiceSignals.set(beaconService, []);
1273
- const beaconAddress = BeaconUtils.parseBitcoinAddress(beaconService.serviceEndpoint);
1274
- const beaconSignals = await bitcoin.rest.address.getTxs(beaconAddress);
1275
- if (!beaconSignals || !beaconSignals.length) {
1276
- continue;
1277
- }
1278
- for (const beaconSignal of beaconSignals) {
1279
- const status = beaconSignal.status;
1280
- if (status.confirmed !== true) {
1281
- continue;
1282
- }
1283
- const lastSignalVout = beaconSignal.vout.slice(-1)[0];
1284
- if (!lastSignalVout) {
1285
- continue;
1286
- }
1287
- const updateHash = extractOpReturnSignalHash(lastSignalVout.scriptpubkey);
1288
- if (!updateHash) {
1289
- continue;
1290
- }
1291
- if (!await _BeaconSignalDiscovery.spendsFromAddress(beaconSignal, beaconAddress, bitcoin)) {
1292
- continue;
1293
- }
1294
- const confirmations = currentBlockCount - status.block_height + 1;
1295
- beaconServiceSignals.get(beaconService)?.push({
1296
- tx: beaconSignal,
1297
- signalBytes: updateHash,
1298
- blockMetadata: {
1299
- confirmations,
1300
- height: status.block_height,
1301
- time: status.block_time
1302
- }
1303
- });
1242
+ static create(verificationMethod, relationships, service) {
1243
+ return new _GenesisDocument({ id: ID_PLACEHOLDER_VALUE, ...relationships, verificationMethod, service });
1244
+ }
1245
+ /**
1246
+ * Create a minimal GenesisDocument from a public key.
1247
+ * @param {KeyBytes} publicKey The public key in bytes format.
1248
+ * @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
1249
+ */
1250
+ static fromPublicKey(publicKey, network) {
1251
+ const pk = new import_keypair.CompressedSecp256k1PublicKey(publicKey);
1252
+ const id = ID_PLACEHOLDER_VALUE;
1253
+ const address = (0, import_btc_signer2.p2pkh)(pk.compressed, (0, import_bitcoin2.getNetwork)(network)).address;
1254
+ const services = [{
1255
+ id: `${id}#service-0`,
1256
+ serviceEndpoint: `bitcoin:${address}`,
1257
+ type: "SingletonBeacon"
1258
+ }];
1259
+ const relationships = {
1260
+ authentication: [`${id}#key-0`],
1261
+ assertionMethod: [`${id}#key-0`],
1262
+ capabilityInvocation: [`${id}#key-0`],
1263
+ capabilityDelegation: [`${id}#key-0`]
1264
+ };
1265
+ const verificationMethod = [
1266
+ {
1267
+ id: `${id}#key-0`,
1268
+ type: "Multikey",
1269
+ controller: id,
1270
+ publicKeyMultibase: pk.multibase.encoded
1304
1271
  }
1305
- }
1306
- return beaconServiceSignals;
1272
+ ];
1273
+ return _GenesisDocument.create(verificationMethod, relationships, services);
1307
1274
  }
1308
1275
  /**
1309
- * Traverse the full blockchain from genesis to chain top looking for beacon signals.
1310
- * @param {Array<BeaconService>} beaconServices Array of BeaconService objects to search for signals.
1311
- * @param {BitcoinConnection} bitcoin Bitcoin network connection to use for RPC calls.
1312
- * @returns {Promise<Map<BeaconService, Array<BeaconSignal>>>} Map of beacon service to its discovered signals.
1276
+ * Taken an object, convert it to an IntermediateDocuemnt and then to a DidDocument.
1277
+ * @param {object | DidDocument} object The JSON object to convert.
1278
+ * @returns {DidDocument} The created DidDocument.
1313
1279
  */
1314
- static async fullnode(beaconServices, bitcoin) {
1315
- const beaconServiceSignals = /* @__PURE__ */ new Map();
1316
- for (const beaconService of beaconServices) {
1317
- beaconServiceSignals.set(beaconService, []);
1280
+ static fromJSON(object) {
1281
+ return new _GenesisDocument(object);
1282
+ }
1283
+ /**
1284
+ * Convert a GenesisDocument to genesis bytes.
1285
+ * @param {GenesisDocument} genesisDocument The GenesisDocument to convert.
1286
+ * @returns {Bytes} The genesis bytes.
1287
+ */
1288
+ static toGenesisBytes(genesisDocument) {
1289
+ return (0, import_common7.hash)((0, import_common7.canonicalize)(genesisDocument));
1290
+ }
1291
+ };
1292
+
1293
+ // src/core/identifier.ts
1294
+ var DID_PREFIX = "did:btcr2:";
1295
+ var Identifier = class _Identifier {
1296
+ /**
1297
+ * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-encoding | 3.2 did:btcr2 Identifier Encoding}.
1298
+ *
1299
+ * A did:btcr2 DID consists of a did:btcr2 prefix, followed by an id-bech32 value, which is a Bech32m encoding of:
1300
+ * - the specification version;
1301
+ * - the Bitcoin network identifier; and
1302
+ * - either:
1303
+ * - a key-value representing a secp256k1 public key; or
1304
+ * - a hash-value representing the hash of an initiating external DID document.
1305
+ *
1306
+ * @param {KeyBytes | DocumentBytes} genesisBytes The genesis bytes (public key or document bytes).
1307
+ * @param {DidCreateOptions} options The DID creation options.
1308
+ * @returns {string} The new did:btcr2 identifier.
1309
+ */
1310
+ static encode(genesisBytes, options) {
1311
+ const { idType, version = 1, network = "bitcoin" } = options;
1312
+ if (!(idType in import_common8.IdentifierTypes)) {
1313
+ throw new import_common8.IdentifierError('Expected "idType" to be "KEY" or "EXTERNAL"', import_common8.INVALID_DID, { idType });
1318
1314
  }
1319
- const rpc = bitcoin.rpc;
1320
- if (!rpc) {
1321
- throw new import_common9.ResolveError("RPC connection is not available", "RPC_CONNECTION_ERROR", bitcoin);
1315
+ if (version !== 1) {
1316
+ throw new import_common8.IdentifierError('Expected "version" to be 1', import_common8.INVALID_DID, { version });
1322
1317
  }
1323
- const targetHeight = await rpc.getBlockCount();
1324
- const beaconServicesMap = new Map(
1325
- beaconServices.map((service) => [BeaconUtils.parseBitcoinAddress(service.serviceEndpoint), service])
1326
- );
1327
- let height = 0;
1328
- let block = await bitcoin.rpc.getBlock({ height });
1329
- console.info(`Searching for beacon signals, please wait ...`);
1330
- while (block.height <= targetHeight) {
1331
- for (const tx of block.tx) {
1332
- if (tx.txid === import_bitcoin3.GENESIS_TX_ID) {
1333
- continue;
1334
- }
1335
- const lastSignalVout = tx.vout.slice(-1)[0];
1336
- if (!lastSignalVout) {
1337
- continue;
1338
- }
1339
- const updateHash = extractOpReturnSignalHash(lastSignalVout.scriptPubKey?.hex);
1340
- if (!updateHash) {
1341
- continue;
1342
- }
1343
- const signaled = /* @__PURE__ */ new Set();
1344
- for (const vin of tx.vin) {
1345
- if (vin.coinbase) {
1346
- continue;
1347
- }
1348
- if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] === import_bitcoin3.TXIN_WITNESS_COINBASE) {
1349
- continue;
1350
- }
1351
- if (!vin.txid) {
1352
- continue;
1353
- }
1354
- if (vin.vout === void 0) {
1355
- continue;
1356
- }
1357
- const prevout = await rpc.getRawTransaction(vin.txid, 2);
1358
- if (!prevout.vout[vin.vout]) {
1359
- continue;
1360
- }
1361
- const scriptPubKey = prevout.vout[vin.vout].scriptPubKey;
1362
- if (!scriptPubKey.address) {
1363
- continue;
1364
- }
1365
- const beaconService = beaconServicesMap.get(scriptPubKey.address);
1366
- if (!beaconService || signaled.has(beaconService)) {
1367
- continue;
1368
- }
1369
- signaled.add(beaconService);
1370
- console.info(`Tx ${tx.txid} contains beacon address ${scriptPubKey.address}`);
1371
- beaconServiceSignals.get(beaconService)?.push({
1372
- tx,
1373
- signalBytes: updateHash,
1374
- blockMetadata: {
1375
- height: block.height,
1376
- time: block.time,
1377
- confirmations: block.confirmations
1378
- }
1379
- });
1380
- }
1381
- ;
1382
- }
1383
- height += 1;
1384
- if (height > targetHeight) {
1385
- console.info(`Chain tip reached ${height}, breaking ...`);
1386
- break;
1318
+ if (typeof network !== "string") {
1319
+ throw new import_common8.IdentifierError('Expected "network" to be a known network name', import_common8.INVALID_DID, { network });
1320
+ }
1321
+ const networkValue = import_common8.BitcoinNetworkNames[network];
1322
+ if (networkValue === void 0) {
1323
+ throw new import_common8.IdentifierError('Invalid "network" name', import_common8.INVALID_DID, { network });
1324
+ }
1325
+ if (idType === "KEY") {
1326
+ try {
1327
+ new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
1328
+ } catch {
1329
+ throw new import_common8.IdentifierError(
1330
+ 'Expected "genesisBytes" to be a valid compressed secp256k1 public key',
1331
+ import_common8.INVALID_DID,
1332
+ { genesisBytes }
1333
+ );
1387
1334
  }
1388
- block = await rpc.getBlock({ height });
1335
+ } else if (genesisBytes.length !== 32) {
1336
+ throw new import_common8.IdentifierError(
1337
+ 'Expected "genesisBytes" to be a 32-byte hash for EXTERNAL identifiers',
1338
+ import_common8.INVALID_DID,
1339
+ { genesisBytes }
1340
+ );
1389
1341
  }
1390
- return beaconServiceSignals;
1391
- }
1392
- };
1393
-
1394
- // src/core/did-sender-resolver.ts
1395
- var import_common14 = require("@did-btcr2/common");
1396
- var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
1397
- var import_keypair4 = require("@did-btcr2/keypair");
1398
-
1399
- // src/core/resolver.ts
1400
- var import_bitcoin5 = require("@did-btcr2/bitcoin");
1401
- var import_common13 = require("@did-btcr2/common");
1402
- var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
1403
- var import_keypair3 = require("@did-btcr2/keypair");
1404
-
1405
- // src/did-btcr2.ts
1406
- var import_common12 = require("@did-btcr2/common");
1407
- var import_dids2 = require("@web5/dids");
1408
-
1409
- // src/core/updater.ts
1410
- var import_common11 = require("@did-btcr2/common");
1411
- var import_cryptosuite = require("@did-btcr2/cryptosuite");
1412
-
1413
- // src/utils/did-document.ts
1414
- var import_bitcoin4 = require("@did-btcr2/bitcoin");
1415
- var import_common10 = require("@did-btcr2/common");
1416
- var import_keypair2 = require("@did-btcr2/keypair");
1417
- var import_utils4 = require("@web5/dids/utils");
1418
- var import_btc_signer3 = require("@scure/btc-signer");
1419
- var BTCR2_DID_DOCUMENT_CONTEXT = [
1420
- "https://www.w3.org/ns/did/v1.1",
1421
- "https://btcr2.dev/context/v1"
1422
- ];
1423
- var MULTIKEY_VERIFICATION_METHOD_TYPE = "Multikey";
1424
- var MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX = "zQ3s";
1425
- var ID_PLACEHOLDER_VALUE = "did:btcr2:_";
1426
- var BECH32M_CHARS = "";
1427
- var DID_REGEX = /did:btcr2:(x1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]*)/g;
1428
- function isMultikeyVerificationMethod(vm) {
1429
- if (!Appendix.isDidVerificationMethod(vm)) {
1430
- return false;
1342
+ const hrp = idType === "KEY" ? "k" : "x";
1343
+ const firstByte = version - 1 << 4 | networkValue;
1344
+ const dataBytes = new Uint8Array([firstByte, ...genesisBytes]);
1345
+ return `${DID_PREFIX}${import_base.bech32m.encodeFromBytes(hrp, dataBytes)}`;
1431
1346
  }
1432
- const { type, publicKeyMultibase } = vm;
1433
- return type === MULTIKEY_VERIFICATION_METHOD_TYPE && typeof publicKeyMultibase === "string" && publicKeyMultibase.startsWith(MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX);
1434
- }
1435
- var DidVerificationMethod = class {
1436
- id;
1437
- type;
1438
- controller;
1439
- publicKeyMultibase;
1440
- secretKeyMultibase;
1441
- constructor({ id, type, controller, publicKeyMultibase, secretKeyMultibase }) {
1442
- if (type !== MULTIKEY_VERIFICATION_METHOD_TYPE) {
1443
- throw new import_common10.DidDocumentError(
1444
- `Invalid verification method: type must be "${MULTIKEY_VERIFICATION_METHOD_TYPE}"`,
1445
- import_common10.INVALID_DID_DOCUMENT,
1446
- { id, type }
1447
- );
1347
+ /**
1348
+ * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}.
1349
+ * @param {string} identifier The BTCR2 DID to be parsed
1350
+ * @returns {DidComponents} The parsed identifier components. See {@link DidComponents} for details.
1351
+ * @throws {DidError} if an error occurs while parsing the identifier
1352
+ * @throws {DidErrorCode.InvalidDid} if identifier is invalid
1353
+ * @throws {DidErrorCode.MethodNotSupported} if the method is not supported
1354
+ */
1355
+ static decode(identifier) {
1356
+ const components = identifier.split(":");
1357
+ if (components.length !== 3) {
1358
+ throw new import_common8.IdentifierError(`Invalid did: ${identifier}`, import_common8.INVALID_DID, { identifier });
1448
1359
  }
1449
- if (typeof publicKeyMultibase !== "string" || !publicKeyMultibase.startsWith(MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX)) {
1450
- throw new import_common10.DidDocumentError(
1451
- `Invalid verification method: publicKeyMultibase must start with "${MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX}"`,
1452
- import_common10.INVALID_DID_DOCUMENT,
1453
- { id, publicKeyMultibase }
1454
- );
1360
+ const [scheme, method, encoded] = components;
1361
+ if (scheme !== "did") {
1362
+ throw new import_common8.IdentifierError(`Invalid did: ${identifier}`, import_common8.INVALID_DID, { identifier });
1455
1363
  }
1456
- this.id = id;
1457
- this.type = type;
1458
- this.controller = controller;
1459
- this.publicKeyMultibase = publicKeyMultibase;
1460
- this.secretKeyMultibase = secretKeyMultibase;
1461
- if (!secretKeyMultibase) {
1462
- delete this.secretKeyMultibase;
1364
+ if (method !== "btcr2") {
1365
+ throw new import_common8.IdentifierError(`Invalid did method: ${method}`, import_common8.METHOD_NOT_SUPPORTED, { identifier });
1463
1366
  }
1464
- }
1465
- // TODO: Add helper methods and properties
1466
- };
1467
- var DidDocument = class _DidDocument {
1468
- id;
1469
- "@context" = [
1470
- "https://www.w3.org/ns/did/v1.1",
1471
- "https://btcr2.dev/context/v1"
1472
- ];
1473
- verificationMethod;
1474
- authentication;
1475
- assertionMethod;
1476
- capabilityInvocation;
1477
- capabilityDelegation;
1478
- service;
1479
- deactivated;
1480
- constructor(document) {
1481
- if (!document.id) {
1482
- throw new import_common10.DidDocumentError("DID Document must have an id", import_common10.INVALID_DID_DOCUMENT, document);
1367
+ if (!encoded) {
1368
+ throw new import_common8.IdentifierError(`Invalid method-specific id: ${identifier}`, import_common8.INVALID_DID, { identifier });
1483
1369
  }
1484
- const idType = document.id.includes("k1") ? import_common10.IdentifierTypes.KEY : import_common10.IdentifierTypes.EXTERNAL;
1485
- const isGenesis = document.id === ID_PLACEHOLDER_VALUE;
1486
- const { id, verificationMethod: vm, service } = document;
1487
- if (!isGenesis) {
1488
- if (!_DidDocument.isValidId(id)) {
1489
- throw new import_common10.DidDocumentError(`Invalid id: ${id}`, import_common10.INVALID_DID_DOCUMENT, document);
1490
- }
1491
- if (!_DidDocument.isValidVerificationMethods(vm)) {
1492
- throw new import_common10.DidDocumentError("Invalid verificationMethod: " + vm, import_common10.INVALID_DID_DOCUMENT, document);
1493
- }
1494
- if (!_DidDocument.isValidServices(service)) {
1495
- throw new import_common10.DidDocumentError("Invalid service: " + service, import_common10.INVALID_DID_DOCUMENT, document);
1496
- }
1370
+ if (encoded !== encoded.toLowerCase()) {
1371
+ throw new import_common8.IdentifierError(`Invalid method-specific id (must be lowercase): ${identifier}`, import_common8.INVALID_DID, { identifier });
1497
1372
  }
1498
- this.id = document.id;
1499
- this.verificationMethod = document.verificationMethod || [];
1500
- this.service = document.service || [];
1501
- this["@context"] = document["@context"] || [
1502
- "https://www.w3.org/ns/did/v1.1",
1503
- "https://btcr2.dev/context/v1"
1504
- ];
1505
- if (idType === import_common10.IdentifierTypes.KEY) {
1506
- const keyRef = `${this.id}#initialKey`;
1507
- this.authentication = document.authentication || [keyRef];
1508
- this.assertionMethod = document.assertionMethod || [keyRef];
1509
- this.capabilityInvocation = document.capabilityInvocation || [keyRef];
1510
- this.capabilityDelegation = document.capabilityDelegation || [keyRef];
1511
- } else {
1512
- this.authentication = document.authentication;
1513
- this.assertionMethod = document.assertionMethod;
1514
- this.capabilityInvocation = document.capabilityInvocation;
1515
- this.capabilityDelegation = document.capabilityDelegation;
1373
+ const { prefix: hrp, bytes: dataBytes } = import_base.bech32m.decodeToBytes(encoded);
1374
+ if (!["x", "k"].includes(hrp)) {
1375
+ throw new import_common8.IdentifierError(`Invalid hrp: ${hrp}`, import_common8.INVALID_DID, { identifier });
1516
1376
  }
1517
- _DidDocument.sanitize(this);
1518
- if (isGenesis) {
1519
- this.validateGenesis();
1520
- } else {
1521
- _DidDocument.validate(this);
1377
+ if (!dataBytes || dataBytes.length < 1) {
1378
+ throw new import_common8.IdentifierError(`Failed to decode id: ${encoded}`, import_common8.INVALID_DID, { identifier });
1379
+ }
1380
+ const idType = hrp === "k" ? "KEY" : "EXTERNAL";
1381
+ const btcr2Version = dataBytes[0] >>> 4;
1382
+ if (btcr2Version !== 0) {
1383
+ throw new import_common8.IdentifierError(`Invalid btcr2_version (expected 0): ${btcr2Version}`, import_common8.INVALID_DID, { identifier });
1384
+ }
1385
+ const version = 1;
1386
+ const networkValue = dataBytes[0] & 15;
1387
+ const network = import_common8.BitcoinNetworkNames[networkValue];
1388
+ if (typeof network !== "string") {
1389
+ const reason = networkValue >= 12 ? "custom network not supported" : "reserved";
1390
+ throw new import_common8.IdentifierError(`Invalid network (${reason}): ${networkValue}`, import_common8.INVALID_DID, { identifier });
1391
+ }
1392
+ const genesisBytes = dataBytes.slice(1);
1393
+ if (idType === "KEY") {
1394
+ try {
1395
+ new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
1396
+ } catch {
1397
+ throw new import_common8.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common8.INVALID_DID, { identifier });
1398
+ }
1399
+ } else if (genesisBytes.length !== 32) {
1400
+ throw new import_common8.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common8.INVALID_DID, { identifier });
1522
1401
  }
1402
+ return { idType, hrp, version, network, genesisBytes };
1523
1403
  }
1524
1404
  /**
1525
- * Convert the DidDocument to a JSON object.
1526
- * @returns {DidDocument} The JSON representation of the DidDocument.
1405
+ * Validates that a did:btcr2 identifier conforms to
1406
+ * {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}
1407
+ * and returns a report of the checks. The method does not throw on an invalid identifier.
1408
+ *
1409
+ * The checks run in this order: `prefix`, `lowercase`, `bech32m`, `version`, `network`,
1410
+ * `genesisBytes`, `roundTrip`, `genesisBytesMatch`, and `genesisDocument`. The run stops at the
1411
+ * first failed check. The `network` check accepts a named network only: a reserved value (6 to
1412
+ * 11) and a custom value (12 to 15) fail, because this implementation supports no custom network.
1413
+ * The `genesisBytesMatch` check runs only if `options.genesisBytes` is present: the supplied bytes
1414
+ * must equal the genesis bytes of the identifier, for a KEY or an EXTERNAL identifier. The
1415
+ * `genesisDocument` check runs only if `options.genesisDocument` is present. For an EXTERNAL
1416
+ * identifier it confirms that the document is a valid Genesis Document and that its canonical
1417
+ * SHA-256 hash equals the genesis bytes. For a KEY identifier it fails.
1418
+ *
1419
+ * @param {string} identifier The did:btcr2 identifier to validate.
1420
+ * @param {IdentifierValidateOptions} [options] The validation options.
1421
+ * @returns {IdentifierReport} The report. See {@link IdentifierReport} for details.
1527
1422
  */
1528
- toJSON() {
1529
- return {
1530
- id: this.id,
1531
- "@context": this["@context"],
1532
- verificationMethod: this.verificationMethod,
1533
- authentication: this.authentication,
1534
- assertionMethod: this.assertionMethod,
1535
- capabilityInvocation: this.capabilityInvocation,
1536
- capabilityDelegation: this.capabilityDelegation,
1537
- service: this.service,
1538
- deactivated: this.deactivated
1423
+ static validate(identifier, options = {}) {
1424
+ const checks = [];
1425
+ const pass = (name, detail) => {
1426
+ checks.push(detail === void 0 ? { name, ok: true } : { name, ok: true, detail });
1539
1427
  };
1540
- }
1541
- /**
1542
- * Create a minimal DidDocument from "k1" btcr2 identifier.
1543
- * @param {string} publicKeyMultibase The public key in multibase format.
1544
- * @param {Array<BeaconService>} service The beacon services to be included in the document.
1545
- * @returns {DidDocument} A new DidDocument with the placeholder ID.
1546
- */
1547
- static fromKeyIdentifier(id, publicKeyMultibase, service) {
1548
- id = id.includes("#") ? id : `${id}#initialKey`;
1549
- const document = {
1550
- id,
1551
- verificationMethod: [
1552
- new DidVerificationMethod({
1553
- id,
1554
- type: "Multikey",
1555
- controller: id,
1556
- publicKeyMultibase
1557
- })
1558
- ],
1559
- service
1428
+ const fail = (name, detail, partial = {}) => {
1429
+ checks.push({ name, ok: false, detail });
1430
+ return { did: identifier, valid: false, ...partial, checks };
1560
1431
  };
1561
- return new _DidDocument(document);
1562
- }
1563
- /**
1564
- * Create a DidDocument from "x1" btcr2 identifier.
1565
- * @param {ExternalData} data The verification methods of the DID Document.
1566
- * @returns {DidDocument} A new DidDocument.
1567
- */
1568
- static fromExternalIdentifier(data) {
1569
- return new _DidDocument(data);
1570
- }
1571
- /**
1572
- * Sanitize the DID Document by removing undefined values
1573
- * @returns {DidDocument} The sanitized DID Document
1574
- */
1575
- static sanitize(doc) {
1576
- for (const key of Object.keys(doc)) {
1577
- if (doc[key] === void 0) {
1578
- delete doc[key];
1432
+ if (typeof identifier !== "string") {
1433
+ return fail("prefix", "The identifier is not a string.");
1434
+ }
1435
+ const parts = identifier.split(":");
1436
+ if (parts.length !== 3 || parts[0] !== "did" || parts[1] !== "btcr2") {
1437
+ return fail("prefix", `The identifier must be "${DID_PREFIX}" followed by the method-specific id.`);
1438
+ }
1439
+ const encoded = parts[2];
1440
+ if (encoded.length === 0) {
1441
+ return fail("prefix", "The method-specific id is empty.");
1442
+ }
1443
+ pass("prefix");
1444
+ if (encoded !== encoded.toLowerCase()) {
1445
+ return fail("lowercase", "The method-specific id must be lowercase.");
1446
+ }
1447
+ pass("lowercase");
1448
+ let hrp;
1449
+ let dataBytes;
1450
+ try {
1451
+ ({ prefix: hrp, bytes: dataBytes } = import_base.bech32m.decodeToBytes(encoded));
1452
+ } catch (error) {
1453
+ return fail("bech32m", `Bech32m decoding failed: ${error instanceof Error ? error.message : String(error)}`);
1454
+ }
1455
+ if (hrp !== "k" && hrp !== "x") {
1456
+ return fail("bech32m", `The hrp must be "k" or "x", got "${hrp}".`);
1457
+ }
1458
+ const idType = hrp === "k" ? import_common8.IdentifierTypes.KEY : import_common8.IdentifierTypes.EXTERNAL;
1459
+ if (dataBytes.length < 1) {
1460
+ return fail("bech32m", "The data bytes are empty.", { idType });
1461
+ }
1462
+ pass("bech32m", `hrp "${hrp}", ${dataBytes.length} data bytes`);
1463
+ const btcr2Version = dataBytes[0] >>> 4;
1464
+ if (btcr2Version !== 0) {
1465
+ return fail("version", `btcr2_version must be 0, got ${btcr2Version}.`, { idType });
1466
+ }
1467
+ pass("version", "btcr2_version 0 (version_number 1)");
1468
+ const networkValue = dataBytes[0] & 15;
1469
+ const network = import_common8.BitcoinNetworkNames[networkValue];
1470
+ if (typeof network !== "string") {
1471
+ const detail = networkValue >= 12 ? `network_value ${networkValue} is a custom network, not supported by this implementation.` : `network_value ${networkValue} is reserved.`;
1472
+ return fail("network", detail, { idType });
1473
+ }
1474
+ pass("network", `network_value ${networkValue} (${network})`);
1475
+ const genesisBytes = dataBytes.slice(1);
1476
+ if (idType === import_common8.IdentifierTypes.KEY) {
1477
+ try {
1478
+ new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
1479
+ } catch {
1480
+ return fail(
1481
+ "genesisBytes",
1482
+ `Expected a 33-byte SEC compressed secp256k1 public key, got ${genesisBytes.length} bytes that are not a valid key.`,
1483
+ { idType, network }
1484
+ );
1579
1485
  }
1486
+ pass("genesisBytes", "33-byte SEC compressed secp256k1 public key");
1487
+ } else {
1488
+ if (genesisBytes.length !== 32) {
1489
+ return fail("genesisBytes", `Expected a 32-byte SHA-256 hash, got ${genesisBytes.length} bytes.`, { idType, network });
1490
+ }
1491
+ pass("genesisBytes", "32-byte SHA-256 hash");
1580
1492
  }
1581
- return doc;
1582
- }
1583
- /**
1584
- * Validates a DidDocument by breaking it into modular validation methods.
1585
- * @param {DidDocument} didDocument The DID document to validate.
1586
- * @returns {boolean} True if the DID document is valid.
1587
- * @throws {DidDocumentError} If any validation check fails.
1588
- */
1589
- static isValid(didDocument) {
1590
- if (!this.isValidContext(didDocument?.["@context"])) {
1591
- throw new import_common10.DidDocumentError('Invalid "@context"', import_common10.INVALID_DID_DOCUMENT, didDocument);
1592
- }
1593
- if (!this.isValidId(didDocument?.id)) {
1594
- throw new import_common10.DidDocumentError('Invalid "id"', import_common10.INVALID_DID_DOCUMENT, didDocument);
1493
+ let reEncoded;
1494
+ try {
1495
+ reEncoded = _Identifier.encode(genesisBytes, { idType, version: 1, network });
1496
+ } catch (error) {
1497
+ return fail("roundTrip", `Re-encoding failed: ${error instanceof Error ? error.message : String(error)}`, { idType, network });
1595
1498
  }
1596
- if (!this.isValidVerificationMethods(didDocument?.verificationMethod)) {
1597
- throw new import_common10.DidDocumentError('Invalid "verificationMethod"', import_common10.INVALID_DID_DOCUMENT, didDocument);
1499
+ if (reEncoded !== identifier) {
1500
+ return fail("roundTrip", `Re-encoding produced "${reEncoded}".`, { idType, network });
1598
1501
  }
1599
- if (!this.isValidServices(didDocument?.service)) {
1600
- throw new import_common10.DidDocumentError('Invalid "service"', import_common10.INVALID_DID_DOCUMENT, didDocument);
1502
+ pass("roundTrip");
1503
+ if (options.genesisBytes !== void 0) {
1504
+ const supplied = options.genesisBytes;
1505
+ if (!(supplied instanceof Uint8Array)) {
1506
+ return fail("genesisBytesMatch", "The supplied genesis bytes are not a Uint8Array.", { idType, network });
1507
+ }
1508
+ if (supplied.length !== genesisBytes.length) {
1509
+ return fail(
1510
+ "genesisBytesMatch",
1511
+ `Expected ${genesisBytes.length} genesis bytes for a ${idType} identifier, got ${supplied.length}.`,
1512
+ { idType, network }
1513
+ );
1514
+ }
1515
+ if (!(0, import_utils4.equalBytes)(supplied, genesisBytes)) {
1516
+ return fail(
1517
+ "genesisBytesMatch",
1518
+ `The supplied genesis bytes ${import_base.hex.encode(supplied)} do not equal the genesis bytes of the identifier ${import_base.hex.encode(genesisBytes)}.`,
1519
+ { idType, network }
1520
+ );
1521
+ }
1522
+ pass("genesisBytesMatch", "The supplied genesis bytes equal the genesis bytes of the identifier.");
1601
1523
  }
1602
- if (!this.isValidVerificationRelationships(didDocument)) {
1603
- throw new import_common10.DidDocumentError("Invalid verification relationships", import_common10.INVALID_DID_DOCUMENT, didDocument);
1524
+ if (options.genesisDocument !== void 0) {
1525
+ const document = options.genesisDocument;
1526
+ if (idType === import_common8.IdentifierTypes.KEY) {
1527
+ return fail("genesisDocument", "A KEY identifier has no genesis document.", { idType, network });
1528
+ }
1529
+ const id = document.id;
1530
+ if (id !== ID_PLACEHOLDER_VALUE) {
1531
+ return fail("genesisDocument", `The genesis document id must be "${ID_PLACEHOLDER_VALUE}", got ${JSON.stringify(id)}.`, { idType, network });
1532
+ }
1533
+ try {
1534
+ GenesisDocument.fromJSON(document);
1535
+ } catch (error) {
1536
+ return fail("genesisDocument", `Invalid genesis document: ${error instanceof Error ? error.message : String(error)}`, { idType, network });
1537
+ }
1538
+ const documentHash = (0, import_common8.canonicalHashBytes)(document);
1539
+ if (!(0, import_utils4.equalBytes)(documentHash, genesisBytes)) {
1540
+ return fail(
1541
+ "genesisDocument",
1542
+ `The genesis document hash ${import_base.hex.encode(documentHash)} does not equal the genesis bytes ${import_base.hex.encode(genesisBytes)}.`,
1543
+ { idType, network }
1544
+ );
1545
+ }
1546
+ pass("genesisDocument", "The genesis document hashes to the genesis bytes.");
1604
1547
  }
1605
- return true;
1548
+ return { did: identifier, valid: true, idType, network, checks };
1606
1549
  }
1607
1550
  /**
1608
- * Validates that "@context" exists and includes correct values.
1609
- * @private
1610
- * @param {DidDocument['@context']} context The context to validate.
1611
- * @returns {boolean} True if the context is valid.
1551
+ * Generates a new did:btcr2 identifier based on a newly generated key pair.
1552
+ * @returns {string} The new did:btcr2 identifier.
1612
1553
  */
1613
- static isValidContext(context) {
1614
- if (!Array.isArray(context) || context.length === 0) return false;
1615
- return BTCR2_DID_DOCUMENT_CONTEXT.every((required) => context.includes(required));
1554
+ static generate() {
1555
+ const keyPair = import_keypair2.SchnorrKeyPair.generate();
1556
+ const did = this.encode(
1557
+ keyPair.publicKey.compressed,
1558
+ {
1559
+ idType: "KEY",
1560
+ version: 1,
1561
+ network: "regtest"
1562
+ }
1563
+ );
1564
+ return { keyPair: keyPair.exportJSON(), did };
1616
1565
  }
1617
1566
  /**
1618
- * Validates that the DID Document has a valid id.
1619
- * @private
1620
- * @param {string} id The id to validate.
1621
- * @returns {boolean} True if the id is valid.
1567
+ * Extracts the compressed secp256k1 public key from a KEY-type did:btcr2 identifier.
1568
+ * @param {string} did The did:btcr2 identifier to extract the public key from.
1569
+ * @returns {CompressedSecp256k1PublicKey} The compressed public key.
1570
+ * @throws {IdentifierError} If the DID is EXTERNAL type (genesis bytes are a hash, not a pubkey).
1622
1571
  */
1623
- static isValidId(id) {
1624
- if (typeof id !== "string") return false;
1572
+ static getPublicKey(did) {
1573
+ const { idType, genesisBytes } = _Identifier.decode(did);
1574
+ if (idType !== "KEY") {
1575
+ throw new import_common8.IdentifierError(
1576
+ `Cannot extract public key from EXTERNAL DID: ${did}. EXTERNAL DIDs encode a document hash, not a public key.`,
1577
+ import_common8.INVALID_DID,
1578
+ { did, idType }
1579
+ );
1580
+ }
1581
+ return new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
1582
+ }
1583
+ /**
1584
+ * Validates a did:btcr2 identifier.
1585
+ * @param {string} identifier The did:btcr2 identifier to validate.
1586
+ * @returns {boolean} True if the identifier is valid, false otherwise.
1587
+ */
1588
+ static isValid(identifier) {
1625
1589
  try {
1626
- Identifier.decode(id);
1590
+ this.decode(identifier);
1627
1591
  return true;
1628
1592
  } catch {
1629
1593
  return false;
1630
1594
  }
1631
1595
  }
1596
+ };
1597
+
1598
+ // src/core/beacon/utils.ts
1599
+ var BeaconUtils = class {
1632
1600
  /**
1633
- * Validates that verification methods exist and are correctly formatted.
1634
- * @private
1635
- * @param {DidVerificationMethod[]} verificationMethod The verification methods to validate.
1636
- * @returns {boolean} True if the verification methods are valid.
1601
+ * Converts a BIP21 Bitcoin URI to a Bitcoin address
1602
+ * @param {string} uri The BIP21 Bitcoin URI to convert
1603
+ * @returns {string} The Bitcoin address extracted from the URI
1604
+ * @throws {DidMethodError} if the URI is not a valid Bitcoin URI
1637
1605
  */
1638
- static isValidVerificationMethods(verificationMethod) {
1639
- return Array.isArray(verificationMethod) && verificationMethod.every(isMultikeyVerificationMethod);
1606
+ static parseBitcoinAddress(uri) {
1607
+ if (!uri.startsWith("bitcoin:")) {
1608
+ throw new import_common9.MethodError("Invalid Bitcoin URI format", "BEACON_SERVICE_ERROR", { uri });
1609
+ }
1610
+ return uri.replace("bitcoin:", "").split("?")[0];
1640
1611
  }
1641
1612
  /**
1642
- * Validates that the DID Document has valid services.
1643
- * @private
1644
- * @param {DidService[]} service The services to validate.
1645
- * @returns {boolean} True if the services are valid.
1613
+ * Validates that the given object is a Beacon Service
1614
+ * @param {BeaconService} obj The object to validate
1615
+ * @returns {boolean} A boolean indicating whether the object is a Beacon Service
1646
1616
  */
1647
- static isValidServices(service) {
1648
- return Array.isArray(service) && service.every(import_utils4.isDidService);
1617
+ static isBeaconService(obj) {
1618
+ if (!Appendix.isDidService(obj)) return false;
1619
+ if (!["SingletonBeacon", "CASBeacon", "SMTBeacon"].includes(obj.type)) return false;
1620
+ if ([obj.serviceEndpoint].flat().some((ep) => typeof ep === "string" && !ep.startsWith("bitcoin:"))) return false;
1621
+ return true;
1649
1622
  }
1650
1623
  /**
1651
- * Validates verification relationships (authentication, assertionMethod, capabilityInvocation, capabilityDelegation).
1652
- * @private
1653
- * @param {DidDocument} didDocument The DID Document to validate.
1654
- * @returns {boolean} True if the verification relationships are valid.
1624
+ * Extracts the services from a given DID Document
1625
+ * @param {DidDocument} didDocument The DID Document to extract the services from
1626
+ * @returns {DidService[]} An array of DidService objects
1627
+ * @throws {TypeError} if the didDocument is not provided
1655
1628
  */
1656
- static isValidVerificationRelationships(didDocument) {
1657
- const possibleVerificationRelationships = [
1658
- "authentication",
1659
- "assertionMethod",
1660
- "capabilityInvocation",
1661
- "capabilityDelegation"
1662
- ];
1663
- const keys = Object.keys(didDocument);
1664
- const availableKeys = possibleVerificationRelationships.filter((key) => keys.includes(key));
1665
- return availableKeys.every((key) => {
1666
- const value = didDocument[key];
1667
- return value && Array.isArray(value) && value.every(
1668
- (entry) => typeof entry === "string" || Appendix.isDidVerificationMethod(entry)
1669
- );
1670
- });
1629
+ static getBeaconServices(didDocument) {
1630
+ return didDocument.service.filter(this.isBeaconService) ?? [];
1671
1631
  }
1672
1632
  /**
1673
- * Validate the DID Document
1674
- * @returns {DidDocument} Validated DID Document.
1675
- * @throws {DidDocumentError} If the DID Document is invalid.
1633
+ * Create the 3 default Beacon Service Endpoints for a given `k` (public-key-based) identifier.
1634
+ * @param {string} did The DID for which to create the beacon services.
1635
+ * @returns {Array<Array<string>>} 2D Array of bitcoin addresses (p2pkh, p2wpkh, p2tr).
1636
+ * @throws {DidMethodError} if the bitcoin address is invalid.
1676
1637
  */
1677
- static validate(didDocument) {
1678
- if (didDocument.id === ID_PLACEHOLDER_VALUE) {
1679
- didDocument.validateGenesis();
1680
- } else {
1681
- _DidDocument.isValid(didDocument);
1638
+ static createBeaconServices(did, beaconType) {
1639
+ try {
1640
+ const addrTypes = ["p2pkh", "p2wpkh", "p2tr"];
1641
+ return addrTypes.map(
1642
+ (addrType) => this.createBeaconService(did, addrType, beaconType)
1643
+ );
1644
+ } catch (error) {
1645
+ throw new BeaconError(
1646
+ "Failed to create beacon services: " + error.message,
1647
+ "BEACON_SERVICE_ERROR",
1648
+ { did, beaconType }
1649
+ );
1682
1650
  }
1683
- return didDocument;
1684
1651
  }
1685
1652
  /**
1686
- * Validate the GenesisDocument.
1687
- * @returns {boolean} True if the GenesisDocument is valid.
1653
+ * Generate a set of Beacon Services for a given public key.
1654
+ * @param {string} did The did for the beacon service.
1655
+ * @param {string} addressType The type of bitcoin address to generate (p2pkh, p2wpkh, p2tr).
1656
+ * @param {string} beaconType The type of beacon service to create.
1657
+ * @returns {BeaconService} A BeaconService object.
1658
+ * @throws {DidMethodError} if the bitcoin address is invalid.
1688
1659
  */
1689
- validateGenesis() {
1690
- if (this.id !== ID_PLACEHOLDER_VALUE) {
1691
- throw new import_common10.DidDocumentError("Invalid GenesisDocument ID", import_common10.INVALID_DID_DOCUMENT, this);
1692
- }
1693
- if (!this.verificationMethod.every((vm) => vm.id.includes(ID_PLACEHOLDER_VALUE) && vm.controller === ID_PLACEHOLDER_VALUE)) {
1694
- throw new import_common10.DidDocumentError("Invalid GenesisDocument verificationMethod", import_common10.INVALID_DID_DOCUMENT, this);
1695
- }
1696
- if (!this.service.every((svc) => svc.id.includes(ID_PLACEHOLDER_VALUE))) {
1697
- throw new import_common10.DidDocumentError("Invalid GenesisDocument service", import_common10.INVALID_DID_DOCUMENT, this);
1698
- }
1699
- if (!_DidDocument.isValidVerificationRelationships(this)) {
1700
- throw new import_common10.DidDocumentError("Invalid GenesisDocument assertionMethod", import_common10.INVALID_DID_DOCUMENT, this);
1660
+ static createBeaconService(did, addressType, beaconType) {
1661
+ try {
1662
+ const components = Identifier.decode(did);
1663
+ const network = (0, import_bitcoin3.getNetwork)(components.network);
1664
+ const pubkey = components.genesisBytes;
1665
+ const id = `${did}#initial${addressType.toUpperCase()}`;
1666
+ const address = addressType === "p2tr" ? (0, import_btc_signer3.p2tr)(pubkey.slice(1, 33), void 0, network).address : addressType === "p2wpkh" ? (0, import_btc_signer3.p2wpkh)(pubkey, network).address : (0, import_btc_signer3.p2pkh)(pubkey, network).address;
1667
+ const serviceEndpoint = `bitcoin:${address}`;
1668
+ return { id, type: beaconType, serviceEndpoint };
1669
+ } catch (error) {
1670
+ throw new BeaconError(
1671
+ "Failed to create beacon service: " + error.message,
1672
+ "BEACON_SERVICE_ERROR",
1673
+ { did, beaconType }
1674
+ );
1701
1675
  }
1702
- return true;
1703
1676
  }
1704
1677
  /**
1705
- * Convert the DidDocument to an GenesisDocument.
1706
- * @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
1678
+ * Generate three default Beacon Service Endpoints for a given `k` (public-key-based) identifier.
1679
+ * @param {string} did The DID for which to create the beacon services.
1680
+ * @returns {Array<Array<string>>} 2D Array of bitcoin addresses (p2pkh, p2wpkh, p2tr).
1681
+ * @throws {DidMethodError} if the bitcoin address is invalid.
1707
1682
  */
1708
- toIntermediate() {
1709
- if (this.id.includes("k1")) {
1710
- throw new import_common10.DidDocumentError("Cannot convert a key identifier to an intermediate document", import_common10.INVALID_DID_DOCUMENT, this);
1683
+ static generateBeaconServices({ id, publicKey, network, beaconType }) {
1684
+ try {
1685
+ const p2pkhAddr = (0, import_btc_signer3.p2pkh)(publicKey, network).address;
1686
+ const p2wpkhAddr = (0, import_btc_signer3.p2wpkh)(publicKey, network).address;
1687
+ const p2trAddr = (0, import_btc_signer3.p2tr)(publicKey.slice(1, 33), void 0, network).address;
1688
+ if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
1689
+ throw new import_common9.DidMethodError("Failed to generate bitcoin addresses");
1690
+ }
1691
+ return [
1692
+ {
1693
+ id: `${id}#initialP2PKH`,
1694
+ type: beaconType,
1695
+ serviceEndpoint: `bitcoin:${p2pkhAddr}`
1696
+ },
1697
+ {
1698
+ id: `${id}#initialP2WPKH`,
1699
+ type: beaconType,
1700
+ serviceEndpoint: `bitcoin:${p2wpkhAddr}`
1701
+ },
1702
+ {
1703
+ id: `${id}#initialP2TR`,
1704
+ type: beaconType,
1705
+ serviceEndpoint: `bitcoin:${p2trAddr}`
1706
+ }
1707
+ ];
1708
+ } catch (error) {
1709
+ throw new BeaconError(
1710
+ "Failed to create beacon services: " + error.message,
1711
+ "BEACON_SERVICE_ERROR",
1712
+ { id, publicKey, network, beaconType }
1713
+ );
1711
1714
  }
1712
- return new GenesisDocument(this);
1713
- }
1714
- };
1715
- var GenesisDocument = class _GenesisDocument extends DidDocument {
1716
- constructor(document) {
1717
- super(document);
1718
- }
1719
- /**
1720
- * Convert the GenesisDocument to a DidDocument by replacing the placeholder value with the provided DID.
1721
- * @param did The DID to replace the placeholder value in the document.
1722
- * @returns {DidDocument} A new DidDocument with the placeholder value replaced by the provided DID.
1723
- */
1724
- toDidDocument(did) {
1725
- const stringThis = JSON.stringify(this).replaceAll(ID_PLACEHOLDER_VALUE, did);
1726
- const parseThis = JSON.parse(stringThis);
1727
- return new DidDocument(parseThis);
1728
1715
  }
1729
1716
  /**
1730
- * Create an GenesisDocument from a DidDocument by replacing the DID with a placeholder value.
1731
- * @param {DidDocument} didDocument The DidDocument to convert.
1732
- * @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
1717
+ * Convert beacon service endpoints from BIP-21 URIs to addresses.
1718
+ * @param {BeaconService} beacon The beacon service to parse.
1719
+ * @returns {BeaconServiceAddress} The beacon service with the address field extracted from the serviceEndpoint.
1733
1720
  */
1734
- static fromDidDocument(didDocument) {
1735
- const intermediateDocument = import_common10.JSONUtils.cloneReplace(didDocument, DID_REGEX, ID_PLACEHOLDER_VALUE);
1736
- return new _GenesisDocument(intermediateDocument);
1737
- }
1738
- /**
1739
- * Create a minimal GenesisDocument with a placeholder ID.
1740
- * @param {Array<DidVerificationMethod>} verificationMethod The public key in multibase format.
1741
- * @param {VerificationRelationships} relationships The public key in multibase format.
1742
- * @param {Array<BeaconService>} service The service to be included in the document.
1743
- * @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
1721
+ static parseBeaconServiceEndpoint(beacon) {
1722
+ return { ...beacon, serviceEndpoint: beacon.serviceEndpoint.replace("bitcoin:", "") };
1723
+ }
1724
+ /**
1725
+ * Get the beacon service ids from a list of beacon services.
1726
+ * @param {DidDocument} didDocument The DID Document to extract the services from.
1727
+ * @returns {string[]} An array of beacon service ids.
1744
1728
  */
1745
- static create(verificationMethod, relationships, service) {
1746
- return new _GenesisDocument({ id: ID_PLACEHOLDER_VALUE, ...relationships, verificationMethod, service });
1729
+ static getBeaconServiceIds(didDocument) {
1730
+ return this.getBeaconServices(didDocument).map((beacon) => beacon.id);
1731
+ }
1732
+ };
1733
+
1734
+ // src/core/beacon/signal-discovery.ts
1735
+ var BEACON_SIGNAL_SCRIPT = /^6a20([0-9a-f]{64})$/i;
1736
+ function extractOpReturnSignalHash(scriptPubKey) {
1737
+ if (!scriptPubKey) {
1738
+ return null;
1739
+ }
1740
+ const signal = BEACON_SIGNAL_SCRIPT.exec(scriptPubKey.trim());
1741
+ if (!signal) {
1742
+ return null;
1747
1743
  }
1744
+ return signal[1].toLowerCase();
1745
+ }
1746
+ var BeaconSignalDiscovery = class _BeaconSignalDiscovery {
1748
1747
  /**
1749
- * Create a minimal GenesisDocument from a public key.
1750
- * @param {KeyBytes} publicKey The public key in bytes format.
1751
- * @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
1748
+ * Determines whether a candidate transaction spends an output controlled by the given
1749
+ * beacon address.
1750
+ *
1751
+ * A Beacon Signal is a transaction that *spends from* a Beacon Address, but an address
1752
+ * transaction listing returns every transaction touching the address in either
1753
+ * direction. Without this check, anyone able to pay dust to a beacon address could
1754
+ * attach an arbitrary 32-byte OP_RETURN and have it read as a signal, so the input side
1755
+ * has to be inspected before a transaction is treated as one.
1756
+ *
1757
+ * Esplora embeds the spent output in `vin[].prevout`; when a backend omits it the
1758
+ * funding transaction is fetched instead, so a missing field cannot silently drop a
1759
+ * real signal.
1760
+ *
1761
+ * @param {RawTransactionRest} tx The candidate transaction.
1762
+ * @param {string} address The beacon address the transaction must spend from.
1763
+ * @param {BitcoinConnection} bitcoin Bitcoin network connection to use for REST calls.
1764
+ * @returns {Promise<boolean>} True if at least one input spends an output of the beacon address.
1752
1765
  */
1753
- static fromPublicKey(publicKey, network) {
1754
- const pk = new import_keypair2.CompressedSecp256k1PublicKey(publicKey);
1755
- const id = ID_PLACEHOLDER_VALUE;
1756
- const address = (0, import_btc_signer3.p2pkh)(pk.compressed, (0, import_bitcoin4.getNetwork)(network)).address;
1757
- const services = [{
1758
- id: `${id}#service-0`,
1759
- serviceEndpoint: `bitcoin:${address}`,
1760
- type: "SingletonBeacon"
1761
- }];
1762
- const relationships = {
1763
- authentication: [`${id}#key-0`],
1764
- assertionMethod: [`${id}#key-0`],
1765
- capabilityInvocation: [`${id}#key-0`],
1766
- capabilityDelegation: [`${id}#key-0`]
1767
- };
1768
- const verificationMethod = [
1769
- {
1770
- id: `${id}#key-0`,
1771
- type: "Multikey",
1772
- controller: id,
1773
- publicKeyMultibase: pk.multibase.encoded
1766
+ static async spendsFromAddress(tx, address, bitcoin) {
1767
+ for (const vin of tx.vin ?? []) {
1768
+ if (vin.is_coinbase) {
1769
+ continue;
1774
1770
  }
1775
- ];
1776
- return _GenesisDocument.create(verificationMethod, relationships, services);
1771
+ let prevout = vin.prevout;
1772
+ if (!prevout && vin.txid) {
1773
+ const fundingTx = await bitcoin.rest.transaction.get(vin.txid);
1774
+ prevout = fundingTx?.vout?.[vin.vout];
1775
+ }
1776
+ if (prevout?.scriptpubkey_address === address) {
1777
+ return true;
1778
+ }
1779
+ }
1780
+ return false;
1777
1781
  }
1778
1782
  /**
1779
- * Taken an object, convert it to an IntermediateDocuemnt and then to a DidDocument.
1780
- * @param {object | DidDocument} object The JSON object to convert.
1781
- * @returns {DidDocument} The created DidDocument.
1783
+ * Retrieves the beacon signals for the given array of BeaconService objects
1784
+ * using an esplora/electrs REST API connection via a bitcoin I/O driver.
1785
+ *
1786
+ * The address listing includes mempool transactions. The method skips a
1787
+ * transaction whose `status.confirmed` is not `true`. A mempool transaction
1788
+ * has no block height and no block time, so it cannot carry block metadata.
1789
+ * The specification also says that a resolver must not process an
1790
+ * unconfirmed transaction. An absent flag counts as unconfirmed, as it does
1791
+ * for UTXO selection. The check runs before the OP_RETURN parse, so a
1792
+ * mempool transaction costs no prevout fetch. The {@link fullnode} path
1793
+ * needs no such check: it walks mined blocks only.
1794
+ *
1795
+ * The `confirmations` count uses the block count fetched before the listing.
1796
+ * A block that arrives between the two calls yields a count of `0` for its
1797
+ * transactions. The resolver then excludes them, because its minimum is at
1798
+ * least `1`. An under-count is the safe direction, so keep that order.
1799
+ * @param {Array<BeaconService>} beaconServices Array of BeaconService objects to retrieve signals for
1800
+ * @param {BitcoinConnection} bitcoin Bitcoin network connection to use for REST calls
1801
+ * @returns {Promise<Map<BeaconService, Array<BeaconSignal>>>} Map of beacon service to its discovered signals
1782
1802
  */
1783
- static fromJSON(object) {
1784
- return new _GenesisDocument(object);
1803
+ static async indexer(beaconServices, bitcoin) {
1804
+ const beaconServiceSignals = /* @__PURE__ */ new Map();
1805
+ const currentBlockCount = await bitcoin.rest.block.count();
1806
+ for (const beaconService of beaconServices) {
1807
+ beaconServiceSignals.set(beaconService, []);
1808
+ const beaconAddress = BeaconUtils.parseBitcoinAddress(beaconService.serviceEndpoint);
1809
+ const beaconSignals = await bitcoin.rest.address.getTxs(beaconAddress);
1810
+ if (!beaconSignals || !beaconSignals.length) {
1811
+ continue;
1812
+ }
1813
+ for (const beaconSignal of beaconSignals) {
1814
+ const status = beaconSignal.status;
1815
+ if (status.confirmed !== true) {
1816
+ continue;
1817
+ }
1818
+ const lastSignalVout = beaconSignal.vout.slice(-1)[0];
1819
+ if (!lastSignalVout) {
1820
+ continue;
1821
+ }
1822
+ const updateHash = extractOpReturnSignalHash(lastSignalVout.scriptpubkey);
1823
+ if (!updateHash) {
1824
+ continue;
1825
+ }
1826
+ if (!await _BeaconSignalDiscovery.spendsFromAddress(beaconSignal, beaconAddress, bitcoin)) {
1827
+ continue;
1828
+ }
1829
+ const confirmations = currentBlockCount - status.block_height + 1;
1830
+ beaconServiceSignals.get(beaconService)?.push({
1831
+ tx: beaconSignal,
1832
+ signalBytes: updateHash,
1833
+ blockMetadata: {
1834
+ confirmations,
1835
+ height: status.block_height,
1836
+ time: status.block_time
1837
+ }
1838
+ });
1839
+ }
1840
+ }
1841
+ return beaconServiceSignals;
1785
1842
  }
1786
1843
  /**
1787
- * Convert a GenesisDocument to genesis bytes.
1788
- * @param {GenesisDocument} genesisDocument The GenesisDocument to convert.
1789
- * @returns {Bytes} The genesis bytes.
1844
+ * Traverse the full blockchain from genesis to chain top looking for beacon signals.
1845
+ * @param {Array<BeaconService>} beaconServices Array of BeaconService objects to search for signals.
1846
+ * @param {BitcoinConnection} bitcoin Bitcoin network connection to use for RPC calls.
1847
+ * @returns {Promise<Map<BeaconService, Array<BeaconSignal>>>} Map of beacon service to its discovered signals.
1790
1848
  */
1791
- static toGenesisBytes(genesisDocument) {
1792
- return (0, import_common10.hash)((0, import_common10.canonicalize)(genesisDocument));
1849
+ static async fullnode(beaconServices, bitcoin) {
1850
+ const beaconServiceSignals = /* @__PURE__ */ new Map();
1851
+ for (const beaconService of beaconServices) {
1852
+ beaconServiceSignals.set(beaconService, []);
1853
+ }
1854
+ const rpc = bitcoin.rpc;
1855
+ if (!rpc) {
1856
+ throw new import_common10.ResolveError("RPC connection is not available", "RPC_CONNECTION_ERROR", bitcoin);
1857
+ }
1858
+ const targetHeight = await rpc.getBlockCount();
1859
+ const beaconServicesMap = new Map(
1860
+ beaconServices.map((service) => [BeaconUtils.parseBitcoinAddress(service.serviceEndpoint), service])
1861
+ );
1862
+ let height = 0;
1863
+ let block = await bitcoin.rpc.getBlock({ height });
1864
+ console.info(`Searching for beacon signals, please wait ...`);
1865
+ while (block.height <= targetHeight) {
1866
+ for (const tx of block.tx) {
1867
+ if (tx.txid === import_bitcoin4.GENESIS_TX_ID) {
1868
+ continue;
1869
+ }
1870
+ const lastSignalVout = tx.vout.slice(-1)[0];
1871
+ if (!lastSignalVout) {
1872
+ continue;
1873
+ }
1874
+ const updateHash = extractOpReturnSignalHash(lastSignalVout.scriptPubKey?.hex);
1875
+ if (!updateHash) {
1876
+ continue;
1877
+ }
1878
+ const signaled = /* @__PURE__ */ new Set();
1879
+ for (const vin of tx.vin) {
1880
+ if (vin.coinbase) {
1881
+ continue;
1882
+ }
1883
+ if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] === import_bitcoin4.TXIN_WITNESS_COINBASE) {
1884
+ continue;
1885
+ }
1886
+ if (!vin.txid) {
1887
+ continue;
1888
+ }
1889
+ if (vin.vout === void 0) {
1890
+ continue;
1891
+ }
1892
+ const prevout = await rpc.getRawTransaction(vin.txid, 2);
1893
+ if (!prevout.vout[vin.vout]) {
1894
+ continue;
1895
+ }
1896
+ const scriptPubKey = prevout.vout[vin.vout].scriptPubKey;
1897
+ if (!scriptPubKey.address) {
1898
+ continue;
1899
+ }
1900
+ const beaconService = beaconServicesMap.get(scriptPubKey.address);
1901
+ if (!beaconService || signaled.has(beaconService)) {
1902
+ continue;
1903
+ }
1904
+ signaled.add(beaconService);
1905
+ console.info(`Tx ${tx.txid} contains beacon address ${scriptPubKey.address}`);
1906
+ beaconServiceSignals.get(beaconService)?.push({
1907
+ tx,
1908
+ signalBytes: updateHash,
1909
+ blockMetadata: {
1910
+ height: block.height,
1911
+ time: block.time,
1912
+ confirmations: block.confirmations
1913
+ }
1914
+ });
1915
+ }
1916
+ ;
1917
+ }
1918
+ height += 1;
1919
+ if (height > targetHeight) {
1920
+ console.info(`Chain tip reached ${height}, breaking ...`);
1921
+ break;
1922
+ }
1923
+ block = await rpc.getBlock({ height });
1924
+ }
1925
+ return beaconServiceSignals;
1793
1926
  }
1794
1927
  };
1795
1928
 
1929
+ // src/core/btcr2-update.ts
1930
+ var BTCR2_UPDATE_CONTEXT = Object.freeze([
1931
+ "https://w3id.org/json-ld-patch/v1",
1932
+ "https://w3id.org/zcap/v1",
1933
+ "https://w3id.org/security/data-integrity/v2",
1934
+ "https://btcr2.dev/context/v1"
1935
+ ]);
1936
+ function isBtcr2UpdateContext(value, expected = BTCR2_UPDATE_CONTEXT) {
1937
+ return Array.isArray(value) && value.length === expected.length && value.every((url, i) => url === expected[i]);
1938
+ }
1939
+
1940
+ // src/core/did-sender-resolver.ts
1941
+ var import_common14 = require("@did-btcr2/common");
1942
+ var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
1943
+ var import_keypair4 = require("@did-btcr2/keypair");
1944
+
1945
+ // src/core/resolver.ts
1946
+ var import_bitcoin5 = require("@did-btcr2/bitcoin");
1947
+ var import_common13 = require("@did-btcr2/common");
1948
+ var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
1949
+ var import_keypair3 = require("@did-btcr2/keypair");
1950
+
1951
+ // src/did-btcr2.ts
1952
+ var import_common12 = require("@did-btcr2/common");
1953
+ var import_dids2 = require("@web5/dids");
1954
+
1796
1955
  // src/core/updater.ts
1956
+ var import_common11 = require("@did-btcr2/common");
1957
+ var import_cryptosuite = require("@did-btcr2/cryptosuite");
1797
1958
  var Updater = class _Updater {
1798
1959
  #state = { phase: "Construct" };
1799
1960
  #sourceDocument;
@@ -1825,12 +1986,9 @@ var Updater = class _Updater {
1825
1986
  */
1826
1987
  static construct(sourceDocument, patches, sourceVersionId) {
1827
1988
  const unsignedUpdate = {
1828
- "@context": [
1829
- "https://w3id.org/security/v2",
1830
- "https://w3id.org/zcap/v1",
1831
- "https://w3id.org/json-ld-patch/v1",
1832
- "https://btcr2.dev/context/v1"
1833
- ],
1989
+ // The array the specification pins, as a fresh copy: the update is a plain JSON
1990
+ // object that callers may edit, and the shared constant is frozen.
1991
+ "@context": [...BTCR2_UPDATE_CONTEXT],
1834
1992
  patch: patches,
1835
1993
  targetHash: "",
1836
1994
  targetVersionId: sourceVersionId + 1,
@@ -1898,12 +2056,10 @@ var Updater = class _Updater {
1898
2056
  );
1899
2057
  }
1900
2058
  const config = {
1901
- "@context": [
1902
- "https://w3id.org/security/v2",
1903
- "https://w3id.org/zcap/v1",
1904
- "https://w3id.org/json-ld-patch/v1",
1905
- "https://btcr2.dev/context/v1"
1906
- ],
2059
+ // The proof must carry the same array as the update. The cryptosuite copies the
2060
+ // document @context into the proof when the document has one, so the two arrays
2061
+ // are equal by construction; this value is the fallback for a document without one.
2062
+ "@context": [...BTCR2_UPDATE_CONTEXT],
1907
2063
  cryptosuite: "bip340-jcs-2025",
1908
2064
  type: "DataIntegrityProof",
1909
2065
  // The proof names the signing method by absolute DID URL, even when the document
@@ -2253,7 +2409,7 @@ var DidBtcr2 = class {
2253
2409
  };
2254
2410
 
2255
2411
  // src/core/resolver.ts
2256
- var import_utils6 = require("@noble/curves/utils.js");
2412
+ var import_utils7 = require("@noble/curves/utils.js");
2257
2413
  var DEFAULT_MIN_CONF = 6;
2258
2414
  function isRecord(value) {
2259
2415
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -2378,7 +2534,7 @@ var Resolver = class _Resolver {
2378
2534
  */
2379
2535
  static external(didComponents, genesisDocument) {
2380
2536
  const genesisDocumentHash = (0, import_common13.canonicalHashBytes)(genesisDocument);
2381
- if (!(0, import_utils6.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
2537
+ if (!(0, import_utils7.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
2382
2538
  throw new import_common13.ResolveError(
2383
2539
  `Initial document mismatch: genesisBytes !== genesisDocumentHash`,
2384
2540
  import_common13.INVALID_DID_DOCUMENT,
@@ -2463,7 +2619,7 @@ var Resolver = class _Resolver {
2463
2619
  }
2464
2620
  if (update.targetVersionId === currentVersionId + 1) {
2465
2621
  const sourceHashBytes = (0, import_common13.decode)(update.sourceHash, "base64urlnopad");
2466
- if (!(0, import_utils6.equalBytes)(sourceHashBytes, currentDocumentHash)) {
2622
+ if (!(0, import_utils7.equalBytes)(sourceHashBytes, currentDocumentHash)) {
2467
2623
  throw new import_common13.ResolveError(
2468
2624
  `Hash mismatch: update.sourceHash !== currentDocumentHash`,
2469
2625
  import_common13.INVALID_DID_UPDATE,
@@ -2527,7 +2683,7 @@ var Resolver = class _Resolver {
2527
2683
  }
2528
2684
  );
2529
2685
  }
2530
- if (!(0, import_utils6.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
2686
+ if (!(0, import_utils7.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
2531
2687
  throw new import_common13.ResolveError(
2532
2688
  `Invalid duplicate: unsigned update hash does not match historical hash`,
2533
2689
  import_common13.LATE_PUBLISHING_ERROR,
@@ -2539,13 +2695,28 @@ var Resolver = class _Resolver {
2539
2695
  }
2540
2696
  }
2541
2697
  /**
2542
- * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#apply-update | 7.2.f.3 Apply Update}.
2698
+ * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#apply-update | 7.2.f.3 Apply Update}
2699
+ * and its step {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#check-update-proof | Check update.proof}.
2543
2700
  * @param {DidDocument} currentDocument The current DID Document to apply the update to.
2544
2701
  * @param {SignedBTCR2Update} update The BTCR2 Signed Update to apply.
2545
2702
  * @returns {DidDocument} The updated DID Document after applying the update.
2546
2703
  * @throws {ResolveError} If the update is invalid or cannot be applied.
2547
2704
  */
2548
2705
  static applyUpdate(currentDocument, update) {
2706
+ if (!isBtcr2UpdateContext(update["@context"])) {
2707
+ throw new import_common13.ResolveError(
2708
+ "Invalid update: @context is not the array the specification pins for a BTCR2 Update",
2709
+ import_common13.INVALID_DID_UPDATE,
2710
+ { context: update["@context"], expected: [...BTCR2_UPDATE_CONTEXT] }
2711
+ );
2712
+ }
2713
+ if (!isBtcr2UpdateContext(update.proof?.["@context"], update["@context"])) {
2714
+ throw new import_common13.ResolveError(
2715
+ "Invalid update: proof @context does not equal the update @context",
2716
+ import_common13.INVALID_DID_UPDATE,
2717
+ { proofContext: update.proof?.["@context"], context: update["@context"] }
2718
+ );
2719
+ }
2549
2720
  const capabilityId = update.proof?.capability;
2550
2721
  if (!capabilityId) {
2551
2722
  throw new import_common13.ResolveError("No root capability found in update", import_common13.INVALID_DID_UPDATE, update);
@@ -2594,7 +2765,7 @@ var Resolver = class _Resolver {
2594
2765
  DidDocument.validate(updatedDocument);
2595
2766
  const currentDocumentHash = (0, import_common13.canonicalHashBytes)(updatedDocument);
2596
2767
  const updateTargetHash = (0, import_common13.decode)(update.targetHash);
2597
- if (!(0, import_utils6.equalBytes)(updateTargetHash, currentDocumentHash)) {
2768
+ if (!(0, import_utils7.equalBytes)(updateTargetHash, currentDocumentHash)) {
2598
2769
  throw new import_common13.ResolveError(
2599
2770
  `Invalid update: update.targetHash !== currentDocumentHash`,
2600
2771
  import_common13.INVALID_DID_UPDATE,
@@ -2945,6 +3116,7 @@ var DidDocumentBuilder = class {
2945
3116
  Appendix,
2946
3117
  BECH32M_CHARS,
2947
3118
  BTCR2_DID_DOCUMENT_CONTEXT,
3119
+ BTCR2_UPDATE_CONTEXT,
2948
3120
  BeaconError,
2949
3121
  BeaconFactory,
2950
3122
  BeaconSignalDiscovery,
@@ -2984,6 +3156,7 @@ var DidDocumentBuilder = class {
2984
3156
  detectSingletonScriptKind,
2985
3157
  extractOpReturnSignalHash,
2986
3158
  getAggregationCommunicationKey,
3159
+ isBtcr2UpdateContext,
2987
3160
  isMultikeyVerificationMethod,
2988
3161
  opReturnScript,
2989
3162
  resolveBtcr2SenderPk,