@did-btcr2/method 0.58.0 → 0.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -32,6 +32,7 @@ __export(index_exports, {
32
32
  CASBeaconError: () => CASBeaconError,
33
33
  CHANGE_OUTPUT_VBYTES: () => CHANGE_OUTPUT_VBYTES,
34
34
  DEFAULT_FEE_ESTIMATOR: () => DEFAULT_FEE_ESTIMATOR,
35
+ DEFAULT_MIN_CONF: () => DEFAULT_MIN_CONF,
35
36
  DID_REGEX: () => DID_REGEX,
36
37
  DUST_LIMIT_SATS: () => DUST_LIMIT_SATS,
37
38
  DidBtcr2: () => DidBtcr2,
@@ -169,6 +170,9 @@ function opReturnScript(signalBytes) {
169
170
  return import_btc_signer.Script.encode(["RETURN", signalBytes]);
170
171
  }
171
172
  var SPENDABLE_DUST_LIMIT_SATS = 546;
173
+ function isConfirmedUtxo(utxo) {
174
+ return utxo.status.confirmed === true;
175
+ }
172
176
  function byDepthThenId(a, b) {
173
177
  if (a.status.block_height !== b.status.block_height) {
174
178
  return a.status.block_height - b.status.block_height;
@@ -184,7 +188,7 @@ function selectSpendableUtxo(utxos, address) {
184
188
  { address }
185
189
  );
186
190
  }
187
- const confirmed = utxos.filter((utxo) => utxo.status.confirmed === true);
191
+ const confirmed = utxos.filter(isConfirmedUtxo);
188
192
  const spendable = confirmed.filter((utxo) => utxo.value > SPENDABLE_DUST_LIMIT_SATS);
189
193
  if (!spendable.length) {
190
194
  const reason = confirmed.length === 0 ? `all ${utxos.length} UTXO(s) are unconfirmed` : `all ${confirmed.length} confirmed UTXO(s) are at or below the ${SPENDABLE_DUST_LIMIT_SATS}-sat dust limit`;
@@ -707,13 +711,13 @@ var BeaconFactory = class {
707
711
  };
708
712
 
709
713
  // src/core/beacon/signal-discovery.ts
710
- var import_bitcoin3 = require("@did-btcr2/bitcoin");
711
- var import_common9 = require("@did-btcr2/common");
714
+ var import_bitcoin4 = require("@did-btcr2/bitcoin");
715
+ var import_common10 = require("@did-btcr2/common");
712
716
 
713
717
  // src/core/beacon/utils.ts
714
- var import_bitcoin2 = require("@did-btcr2/bitcoin");
715
- var import_common8 = require("@did-btcr2/common");
716
- var import_btc_signer2 = require("@scure/btc-signer");
718
+ var import_bitcoin3 = require("@did-btcr2/bitcoin");
719
+ var import_common9 = require("@did-btcr2/common");
720
+ var import_btc_signer3 = require("@scure/btc-signer");
717
721
 
718
722
  // src/utils/appendix.ts
719
723
  var import_dids = require("@web5/dids");
@@ -895,883 +899,1048 @@ var Appendix = class _Appendix {
895
899
  };
896
900
 
897
901
  // src/core/identifier.ts
902
+ var import_common8 = require("@did-btcr2/common");
903
+ var import_keypair2 = require("@did-btcr2/keypair");
904
+ var import_utils4 = require("@noble/curves/utils.js");
905
+ var import_base = require("@scure/base");
906
+
907
+ // src/utils/did-document.ts
908
+ var import_bitcoin2 = require("@did-btcr2/bitcoin");
898
909
  var import_common7 = require("@did-btcr2/common");
899
910
  var import_keypair = require("@did-btcr2/keypair");
900
- var import_base = require("@scure/base");
901
- var Identifier = class _Identifier {
902
- /**
903
- * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-encoding | 3.2 did:btcr2 Identifier Encoding}.
904
- *
905
- * A did:btcr2 DID consists of a did:btcr2 prefix, followed by an id-bech32 value, which is a Bech32m encoding of:
906
- * - the specification version;
907
- * - the Bitcoin network identifier; and
908
- * - either:
909
- * - a key-value representing a secp256k1 public key; or
910
- * - a hash-value representing the hash of an initiating external DID document.
911
- *
912
- * @param {KeyBytes | DocumentBytes} genesisBytes The genesis bytes (public key or document bytes).
913
- * @param {DidCreateOptions} options The DID creation options.
914
- * @returns {string} The new did:btcr2 identifier.
915
- */
916
- static encode(genesisBytes, options) {
917
- const { idType, version = 1, network = "bitcoin" } = options;
918
- if (!(idType in import_common7.IdentifierTypes)) {
919
- throw new import_common7.IdentifierError('Expected "idType" to be "KEY" or "EXTERNAL"', import_common7.INVALID_DID, { idType });
920
- }
921
- if (version !== 1) {
922
- throw new import_common7.IdentifierError('Expected "version" to be 1', import_common7.INVALID_DID, { version });
923
- }
924
- if (typeof network !== "string") {
925
- throw new import_common7.IdentifierError('Expected "network" to be a known network name', import_common7.INVALID_DID, { network });
926
- }
927
- const networkValue = import_common7.BitcoinNetworkNames[network];
928
- if (networkValue === void 0) {
929
- throw new import_common7.IdentifierError('Invalid "network" name', import_common7.INVALID_DID, { network });
930
- }
931
- if (idType === "KEY") {
932
- try {
933
- new import_keypair.CompressedSecp256k1PublicKey(genesisBytes);
934
- } catch {
935
- throw new import_common7.IdentifierError(
936
- 'Expected "genesisBytes" to be a valid compressed secp256k1 public key',
937
- import_common7.INVALID_DID,
938
- { genesisBytes }
939
- );
940
- }
941
- } else if (genesisBytes.length !== 32) {
942
- throw new import_common7.IdentifierError(
943
- 'Expected "genesisBytes" to be a 32-byte hash for EXTERNAL identifiers',
944
- import_common7.INVALID_DID,
945
- { genesisBytes }
946
- );
947
- }
948
- const hrp = idType === "KEY" ? "k" : "x";
949
- const firstByte = version - 1 << 4 | networkValue;
950
- const dataBytes = new Uint8Array([firstByte, ...genesisBytes]);
951
- return `did:btcr2:${import_base.bech32m.encodeFromBytes(hrp, dataBytes)}`;
911
+ var import_utils3 = require("@web5/dids/utils");
912
+ var import_btc_signer2 = require("@scure/btc-signer");
913
+ var BTCR2_DID_DOCUMENT_CONTEXT = [
914
+ "https://www.w3.org/ns/did/v1.1",
915
+ "https://btcr2.dev/context/v1"
916
+ ];
917
+ var MULTIKEY_VERIFICATION_METHOD_TYPE = "Multikey";
918
+ var MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX = "zQ3s";
919
+ var ID_PLACEHOLDER_VALUE = "did:btcr2:_";
920
+ var BECH32M_CHARS = "";
921
+ var DID_REGEX = /did:btcr2:(x1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]*)/g;
922
+ function isMultikeyVerificationMethod(vm) {
923
+ if (!Appendix.isDidVerificationMethod(vm)) {
924
+ return false;
952
925
  }
953
- /**
954
- * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}.
955
- * @param {string} identifier The BTCR2 DID to be parsed
956
- * @returns {DidComponents} The parsed identifier components. See {@link DidComponents} for details.
957
- * @throws {DidError} if an error occurs while parsing the identifier
958
- * @throws {DidErrorCode.InvalidDid} if identifier is invalid
959
- * @throws {DidErrorCode.MethodNotSupported} if the method is not supported
960
- */
961
- static decode(identifier) {
962
- const components = identifier.split(":");
963
- if (components.length !== 3) {
964
- throw new import_common7.IdentifierError(`Invalid did: ${identifier}`, import_common7.INVALID_DID, { identifier });
965
- }
966
- const [scheme, method, encoded] = components;
967
- if (scheme !== "did") {
968
- throw new import_common7.IdentifierError(`Invalid did: ${identifier}`, import_common7.INVALID_DID, { identifier });
969
- }
970
- if (method !== "btcr2") {
971
- throw new import_common7.IdentifierError(`Invalid did method: ${method}`, import_common7.METHOD_NOT_SUPPORTED, { identifier });
926
+ const { type, publicKeyMultibase } = vm;
927
+ return type === MULTIKEY_VERIFICATION_METHOD_TYPE && typeof publicKeyMultibase === "string" && publicKeyMultibase.startsWith(MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX);
928
+ }
929
+ var DidVerificationMethod = class {
930
+ id;
931
+ type;
932
+ controller;
933
+ publicKeyMultibase;
934
+ secretKeyMultibase;
935
+ constructor({ id, type, controller, publicKeyMultibase, secretKeyMultibase }) {
936
+ if (type !== MULTIKEY_VERIFICATION_METHOD_TYPE) {
937
+ throw new import_common7.DidDocumentError(
938
+ `Invalid verification method: type must be "${MULTIKEY_VERIFICATION_METHOD_TYPE}"`,
939
+ import_common7.INVALID_DID_DOCUMENT,
940
+ { id, type }
941
+ );
972
942
  }
973
- if (!encoded) {
974
- throw new import_common7.IdentifierError(`Invalid method-specific id: ${identifier}`, import_common7.INVALID_DID, { identifier });
943
+ if (typeof publicKeyMultibase !== "string" || !publicKeyMultibase.startsWith(MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX)) {
944
+ throw new import_common7.DidDocumentError(
945
+ `Invalid verification method: publicKeyMultibase must start with "${MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX}"`,
946
+ import_common7.INVALID_DID_DOCUMENT,
947
+ { id, publicKeyMultibase }
948
+ );
975
949
  }
976
- const { prefix: hrp, bytes: dataBytes } = import_base.bech32m.decodeToBytes(encoded);
977
- if (!["x", "k"].includes(hrp)) {
978
- throw new import_common7.IdentifierError(`Invalid hrp: ${hrp}`, import_common7.INVALID_DID, { identifier });
950
+ this.id = id;
951
+ this.type = type;
952
+ this.controller = controller;
953
+ this.publicKeyMultibase = publicKeyMultibase;
954
+ this.secretKeyMultibase = secretKeyMultibase;
955
+ if (!secretKeyMultibase) {
956
+ delete this.secretKeyMultibase;
979
957
  }
980
- if (!dataBytes || dataBytes.length < 1) {
981
- throw new import_common7.IdentifierError(`Failed to decode id: ${encoded}`, import_common7.INVALID_DID, { identifier });
958
+ }
959
+ // TODO: Add helper methods and properties
960
+ };
961
+ var DidDocument = class _DidDocument {
962
+ id;
963
+ "@context" = [
964
+ "https://www.w3.org/ns/did/v1.1",
965
+ "https://btcr2.dev/context/v1"
966
+ ];
967
+ verificationMethod;
968
+ authentication;
969
+ assertionMethod;
970
+ capabilityInvocation;
971
+ capabilityDelegation;
972
+ service;
973
+ deactivated;
974
+ constructor(document) {
975
+ if (!document.id) {
976
+ throw new import_common7.DidDocumentError("DID Document must have an id", import_common7.INVALID_DID_DOCUMENT, document);
982
977
  }
983
- const idType = hrp === "k" ? "KEY" : "EXTERNAL";
984
- const btcr2Version = dataBytes[0] >>> 4;
985
- if (btcr2Version !== 0) {
986
- throw new import_common7.IdentifierError(`Invalid btcr2_version (expected 0): ${btcr2Version}`, import_common7.INVALID_DID, { identifier });
978
+ const idType = document.id.includes("k1") ? import_common7.IdentifierTypes.KEY : import_common7.IdentifierTypes.EXTERNAL;
979
+ const isGenesis = document.id === ID_PLACEHOLDER_VALUE;
980
+ const { id, verificationMethod: vm, service } = document;
981
+ if (!isGenesis) {
982
+ if (!_DidDocument.isValidId(id)) {
983
+ throw new import_common7.DidDocumentError(`Invalid id: ${id}`, import_common7.INVALID_DID_DOCUMENT, document);
984
+ }
985
+ if (!_DidDocument.isValidVerificationMethods(vm)) {
986
+ throw new import_common7.DidDocumentError("Invalid verificationMethod: " + vm, import_common7.INVALID_DID_DOCUMENT, document);
987
+ }
988
+ if (!_DidDocument.isValidServices(service)) {
989
+ throw new import_common7.DidDocumentError("Invalid service: " + service, import_common7.INVALID_DID_DOCUMENT, document);
990
+ }
987
991
  }
988
- const version = 1;
989
- const networkValue = dataBytes[0] & 15;
990
- const networkName = import_common7.BitcoinNetworkNames[networkValue];
991
- let network;
992
- if (typeof networkName === "string") {
993
- network = networkName;
994
- } else if (networkValue >= 12 && networkValue <= 14) {
995
- network = networkValue - 11;
992
+ this.id = document.id;
993
+ this.verificationMethod = document.verificationMethod || [];
994
+ this.service = document.service || [];
995
+ this["@context"] = document["@context"] || [
996
+ "https://www.w3.org/ns/did/v1.1",
997
+ "https://btcr2.dev/context/v1"
998
+ ];
999
+ if (idType === import_common7.IdentifierTypes.KEY) {
1000
+ const keyRef = `${this.id}#initialKey`;
1001
+ this.authentication = document.authentication || [keyRef];
1002
+ this.assertionMethod = document.assertionMethod || [keyRef];
1003
+ this.capabilityInvocation = document.capabilityInvocation || [keyRef];
1004
+ this.capabilityDelegation = document.capabilityDelegation || [keyRef];
996
1005
  } else {
997
- throw new import_common7.IdentifierError(`Invalid network: ${networkValue}`, import_common7.INVALID_DID, { identifier });
1006
+ this.authentication = document.authentication;
1007
+ this.assertionMethod = document.assertionMethod;
1008
+ this.capabilityInvocation = document.capabilityInvocation;
1009
+ this.capabilityDelegation = document.capabilityDelegation;
998
1010
  }
999
- const genesisBytes = dataBytes.slice(1);
1000
- if (idType === "KEY") {
1001
- try {
1002
- new import_keypair.CompressedSecp256k1PublicKey(genesisBytes);
1003
- } catch {
1004
- throw new import_common7.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common7.INVALID_DID, { identifier });
1005
- }
1006
- } else if (genesisBytes.length !== 32) {
1007
- throw new import_common7.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common7.INVALID_DID, { identifier });
1011
+ _DidDocument.sanitize(this);
1012
+ if (isGenesis) {
1013
+ this.validateGenesis();
1014
+ } else {
1015
+ _DidDocument.validate(this);
1008
1016
  }
1009
- return { idType, hrp, version, network, genesisBytes };
1010
1017
  }
1011
1018
  /**
1012
- * Generates a new did:btcr2 identifier based on a newly generated key pair.
1013
- * @returns {string} The new did:btcr2 identifier.
1019
+ * Convert the DidDocument to a JSON object.
1020
+ * @returns {DidDocument} The JSON representation of the DidDocument.
1014
1021
  */
1015
- static generate() {
1016
- const keyPair = import_keypair.SchnorrKeyPair.generate();
1017
- const did = this.encode(
1018
- keyPair.publicKey.compressed,
1019
- {
1020
- idType: "KEY",
1021
- version: 1,
1022
- network: "regtest"
1023
- }
1024
- );
1025
- return { keyPair: keyPair.exportJSON(), did };
1022
+ toJSON() {
1023
+ return {
1024
+ id: this.id,
1025
+ "@context": this["@context"],
1026
+ verificationMethod: this.verificationMethod,
1027
+ authentication: this.authentication,
1028
+ assertionMethod: this.assertionMethod,
1029
+ capabilityInvocation: this.capabilityInvocation,
1030
+ capabilityDelegation: this.capabilityDelegation,
1031
+ service: this.service,
1032
+ deactivated: this.deactivated
1033
+ };
1026
1034
  }
1027
1035
  /**
1028
- * Extracts the compressed secp256k1 public key from a KEY-type did:btcr2 identifier.
1029
- * @param {string} did The did:btcr2 identifier to extract the public key from.
1030
- * @returns {CompressedSecp256k1PublicKey} The compressed public key.
1031
- * @throws {IdentifierError} If the DID is EXTERNAL type (genesis bytes are a hash, not a pubkey).
1036
+ * Create a minimal DidDocument from "k1" btcr2 identifier.
1037
+ * @param {string} publicKeyMultibase The public key in multibase format.
1038
+ * @param {Array<BeaconService>} service The beacon services to be included in the document.
1039
+ * @returns {DidDocument} A new DidDocument with the placeholder ID.
1032
1040
  */
1033
- static getPublicKey(did) {
1034
- const { idType, genesisBytes } = _Identifier.decode(did);
1035
- if (idType !== "KEY") {
1036
- throw new import_common7.IdentifierError(
1037
- `Cannot extract public key from EXTERNAL DID: ${did}. EXTERNAL DIDs encode a document hash, not a public key.`,
1038
- import_common7.INVALID_DID,
1039
- { did, idType }
1040
- );
1041
- }
1042
- return new import_keypair.CompressedSecp256k1PublicKey(genesisBytes);
1041
+ static fromKeyIdentifier(id, publicKeyMultibase, service) {
1042
+ id = id.includes("#") ? id : `${id}#initialKey`;
1043
+ const document = {
1044
+ id,
1045
+ verificationMethod: [
1046
+ new DidVerificationMethod({
1047
+ id,
1048
+ type: "Multikey",
1049
+ controller: id,
1050
+ publicKeyMultibase
1051
+ })
1052
+ ],
1053
+ service
1054
+ };
1055
+ return new _DidDocument(document);
1043
1056
  }
1044
1057
  /**
1045
- * Validates a did:btcr2 identifier.
1046
- * @param {string} identifier The did:btcr2 identifier to validate.
1047
- * @returns {boolean} True if the identifier is valid, false otherwise.
1058
+ * Create a DidDocument from "x1" btcr2 identifier.
1059
+ * @param {ExternalData} data The verification methods of the DID Document.
1060
+ * @returns {DidDocument} A new DidDocument.
1048
1061
  */
1049
- static isValid(identifier) {
1050
- try {
1051
- this.decode(identifier);
1052
- return true;
1053
- } catch {
1054
- return false;
1055
- }
1062
+ static fromExternalIdentifier(data) {
1063
+ return new _DidDocument(data);
1056
1064
  }
1057
- };
1058
-
1059
- // src/core/beacon/utils.ts
1060
- var BeaconUtils = class {
1061
1065
  /**
1062
- * Converts a BIP21 Bitcoin URI to a Bitcoin address
1063
- * @param {string} uri The BIP21 Bitcoin URI to convert
1064
- * @returns {string} The Bitcoin address extracted from the URI
1065
- * @throws {DidMethodError} if the URI is not a valid Bitcoin URI
1066
+ * Sanitize the DID Document by removing undefined values
1067
+ * @returns {DidDocument} The sanitized DID Document
1066
1068
  */
1067
- static parseBitcoinAddress(uri) {
1068
- if (!uri.startsWith("bitcoin:")) {
1069
- throw new import_common8.MethodError("Invalid Bitcoin URI format", "BEACON_SERVICE_ERROR", { uri });
1069
+ static sanitize(doc) {
1070
+ for (const key of Object.keys(doc)) {
1071
+ if (doc[key] === void 0) {
1072
+ delete doc[key];
1073
+ }
1070
1074
  }
1071
- return uri.replace("bitcoin:", "").split("?")[0];
1075
+ return doc;
1072
1076
  }
1073
1077
  /**
1074
- * Validates that the given object is a Beacon Service
1075
- * @param {BeaconService} obj The object to validate
1076
- * @returns {boolean} A boolean indicating whether the object is a Beacon Service
1078
+ * Validates a DidDocument by breaking it into modular validation methods.
1079
+ * @param {DidDocument} didDocument The DID document to validate.
1080
+ * @returns {boolean} True if the DID document is valid.
1081
+ * @throws {DidDocumentError} If any validation check fails.
1077
1082
  */
1078
- static isBeaconService(obj) {
1079
- if (!Appendix.isDidService(obj)) return false;
1080
- if (!["SingletonBeacon", "CASBeacon", "SMTBeacon"].includes(obj.type)) return false;
1081
- if ([obj.serviceEndpoint].flat().some((ep) => typeof ep === "string" && !ep.startsWith("bitcoin:"))) return false;
1083
+ static isValid(didDocument) {
1084
+ if (!this.isValidContext(didDocument?.["@context"])) {
1085
+ throw new import_common7.DidDocumentError('Invalid "@context"', import_common7.INVALID_DID_DOCUMENT, didDocument);
1086
+ }
1087
+ if (!this.isValidId(didDocument?.id)) {
1088
+ throw new import_common7.DidDocumentError('Invalid "id"', import_common7.INVALID_DID_DOCUMENT, didDocument);
1089
+ }
1090
+ if (!this.isValidVerificationMethods(didDocument?.verificationMethod)) {
1091
+ throw new import_common7.DidDocumentError('Invalid "verificationMethod"', import_common7.INVALID_DID_DOCUMENT, didDocument);
1092
+ }
1093
+ if (!this.isValidServices(didDocument?.service)) {
1094
+ throw new import_common7.DidDocumentError('Invalid "service"', import_common7.INVALID_DID_DOCUMENT, didDocument);
1095
+ }
1096
+ if (!this.isValidVerificationRelationships(didDocument)) {
1097
+ throw new import_common7.DidDocumentError("Invalid verification relationships", import_common7.INVALID_DID_DOCUMENT, didDocument);
1098
+ }
1082
1099
  return true;
1083
1100
  }
1084
1101
  /**
1085
- * Extracts the services from a given DID Document
1086
- * @param {DidDocument} didDocument The DID Document to extract the services from
1087
- * @returns {DidService[]} An array of DidService objects
1088
- * @throws {TypeError} if the didDocument is not provided
1102
+ * Validates that "@context" exists and includes correct values.
1103
+ * @private
1104
+ * @param {DidDocument['@context']} context The context to validate.
1105
+ * @returns {boolean} True if the context is valid.
1089
1106
  */
1090
- static getBeaconServices(didDocument) {
1091
- return didDocument.service.filter(this.isBeaconService) ?? [];
1107
+ static isValidContext(context) {
1108
+ if (!Array.isArray(context) || context.length === 0) return false;
1109
+ return BTCR2_DID_DOCUMENT_CONTEXT.every((required) => context.includes(required));
1092
1110
  }
1093
1111
  /**
1094
- * Create the 3 default Beacon Service Endpoints for a given `k` (public-key-based) identifier.
1095
- * @param {string} did The DID for which to create the beacon services.
1096
- * @returns {Array<Array<string>>} 2D Array of bitcoin addresses (p2pkh, p2wpkh, p2tr).
1097
- * @throws {DidMethodError} if the bitcoin address is invalid.
1112
+ * Validates that the DID Document has a valid id.
1113
+ * @private
1114
+ * @param {string} id The id to validate.
1115
+ * @returns {boolean} True if the id is valid.
1098
1116
  */
1099
- static createBeaconServices(did, beaconType) {
1117
+ static isValidId(id) {
1118
+ if (typeof id !== "string") return false;
1100
1119
  try {
1101
- const addrTypes = ["p2pkh", "p2wpkh", "p2tr"];
1102
- return addrTypes.map(
1103
- (addrType) => this.createBeaconService(did, addrType, beaconType)
1104
- );
1105
- } catch (error) {
1106
- throw new BeaconError(
1107
- "Failed to create beacon services: " + error.message,
1108
- "BEACON_SERVICE_ERROR",
1109
- { did, beaconType }
1110
- );
1120
+ Identifier.decode(id);
1121
+ return true;
1122
+ } catch {
1123
+ return false;
1111
1124
  }
1112
1125
  }
1113
1126
  /**
1114
- * Generate a set of Beacon Services for a given public key.
1115
- * @param {string} did The did for the beacon service.
1116
- * @param {string} addressType The type of bitcoin address to generate (p2pkh, p2wpkh, p2tr).
1117
- * @param {string} beaconType The type of beacon service to create.
1118
- * @returns {BeaconService} A BeaconService object.
1119
- * @throws {DidMethodError} if the bitcoin address is invalid.
1127
+ * Validates that verification methods exist and are correctly formatted.
1128
+ * @private
1129
+ * @param {DidVerificationMethod[]} verificationMethod The verification methods to validate.
1130
+ * @returns {boolean} True if the verification methods are valid.
1120
1131
  */
1121
- static createBeaconService(did, addressType, beaconType) {
1122
- try {
1123
- const components = Identifier.decode(did);
1124
- const network = (0, import_bitcoin2.getNetwork)(components.network);
1125
- const pubkey = components.genesisBytes;
1126
- const id = `${did}#initial${addressType.toUpperCase()}`;
1127
- 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;
1128
- const serviceEndpoint = `bitcoin:${address}`;
1129
- return { id, type: beaconType, serviceEndpoint };
1130
- } catch (error) {
1131
- throw new BeaconError(
1132
- "Failed to create beacon service: " + error.message,
1133
- "BEACON_SERVICE_ERROR",
1134
- { did, beaconType }
1135
- );
1136
- }
1132
+ static isValidVerificationMethods(verificationMethod) {
1133
+ return Array.isArray(verificationMethod) && verificationMethod.every(isMultikeyVerificationMethod);
1137
1134
  }
1138
1135
  /**
1139
- * Generate three default Beacon Service Endpoints for a given `k` (public-key-based) identifier.
1140
- * @param {string} did The DID for which to create the beacon services.
1141
- * @returns {Array<Array<string>>} 2D Array of bitcoin addresses (p2pkh, p2wpkh, p2tr).
1142
- * @throws {DidMethodError} if the bitcoin address is invalid.
1136
+ * Validates that the DID Document has valid services.
1137
+ * @private
1138
+ * @param {DidService[]} service The services to validate.
1139
+ * @returns {boolean} True if the services are valid.
1143
1140
  */
1144
- static generateBeaconServices({ id, publicKey, network, beaconType }) {
1145
- try {
1146
- const p2pkhAddr = (0, import_btc_signer2.p2pkh)(publicKey, network).address;
1147
- const p2wpkhAddr = (0, import_btc_signer2.p2wpkh)(publicKey, network).address;
1148
- const p2trAddr = (0, import_btc_signer2.p2tr)(publicKey.slice(1, 33), void 0, network).address;
1149
- if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
1150
- throw new import_common8.DidMethodError("Failed to generate bitcoin addresses");
1151
- }
1152
- return [
1153
- {
1154
- id: `${id}#initialP2PKH`,
1155
- type: beaconType,
1156
- serviceEndpoint: `bitcoin:${p2pkhAddr}`
1157
- },
1158
- {
1159
- id: `${id}#initialP2WPKH`,
1160
- type: beaconType,
1161
- serviceEndpoint: `bitcoin:${p2wpkhAddr}`
1162
- },
1163
- {
1164
- id: `${id}#initialP2TR`,
1165
- type: beaconType,
1166
- serviceEndpoint: `bitcoin:${p2trAddr}`
1167
- }
1168
- ];
1169
- } catch (error) {
1170
- throw new BeaconError(
1171
- "Failed to create beacon services: " + error.message,
1172
- "BEACON_SERVICE_ERROR",
1173
- { id, publicKey, network, beaconType }
1141
+ static isValidServices(service) {
1142
+ return Array.isArray(service) && service.every(import_utils3.isDidService);
1143
+ }
1144
+ /**
1145
+ * Validates verification relationships (authentication, assertionMethod, capabilityInvocation, capabilityDelegation).
1146
+ * @private
1147
+ * @param {DidDocument} didDocument The DID Document to validate.
1148
+ * @returns {boolean} True if the verification relationships are valid.
1149
+ */
1150
+ static isValidVerificationRelationships(didDocument) {
1151
+ const possibleVerificationRelationships = [
1152
+ "authentication",
1153
+ "assertionMethod",
1154
+ "capabilityInvocation",
1155
+ "capabilityDelegation"
1156
+ ];
1157
+ const keys = Object.keys(didDocument);
1158
+ const availableKeys = possibleVerificationRelationships.filter((key) => keys.includes(key));
1159
+ return availableKeys.every((key) => {
1160
+ const value = didDocument[key];
1161
+ return value && Array.isArray(value) && value.every(
1162
+ (entry) => typeof entry === "string" || Appendix.isDidVerificationMethod(entry)
1174
1163
  );
1164
+ });
1165
+ }
1166
+ /**
1167
+ * Validate the DID Document
1168
+ * @returns {DidDocument} Validated DID Document.
1169
+ * @throws {DidDocumentError} If the DID Document is invalid.
1170
+ */
1171
+ static validate(didDocument) {
1172
+ if (didDocument.id === ID_PLACEHOLDER_VALUE) {
1173
+ didDocument.validateGenesis();
1174
+ } else {
1175
+ _DidDocument.isValid(didDocument);
1175
1176
  }
1177
+ return didDocument;
1176
1178
  }
1177
1179
  /**
1178
- * Convert beacon service endpoints from BIP-21 URIs to addresses.
1179
- * @param {BeaconService} beacon The beacon service to parse.
1180
- * @returns {BeaconServiceAddress} The beacon service with the address field extracted from the serviceEndpoint.
1180
+ * Validate the GenesisDocument.
1181
+ * @returns {boolean} True if the GenesisDocument is valid.
1181
1182
  */
1182
- static parseBeaconServiceEndpoint(beacon) {
1183
- return { ...beacon, serviceEndpoint: beacon.serviceEndpoint.replace("bitcoin:", "") };
1183
+ validateGenesis() {
1184
+ if (this.id !== ID_PLACEHOLDER_VALUE) {
1185
+ throw new import_common7.DidDocumentError("Invalid GenesisDocument ID", import_common7.INVALID_DID_DOCUMENT, this);
1186
+ }
1187
+ if (!this.verificationMethod.every((vm) => vm.id.includes(ID_PLACEHOLDER_VALUE) && vm.controller === ID_PLACEHOLDER_VALUE)) {
1188
+ throw new import_common7.DidDocumentError("Invalid GenesisDocument verificationMethod", import_common7.INVALID_DID_DOCUMENT, this);
1189
+ }
1190
+ if (!this.service.every((svc) => svc.id.includes(ID_PLACEHOLDER_VALUE))) {
1191
+ throw new import_common7.DidDocumentError("Invalid GenesisDocument service", import_common7.INVALID_DID_DOCUMENT, this);
1192
+ }
1193
+ if (!_DidDocument.isValidVerificationRelationships(this)) {
1194
+ throw new import_common7.DidDocumentError("Invalid GenesisDocument assertionMethod", import_common7.INVALID_DID_DOCUMENT, this);
1195
+ }
1196
+ return true;
1184
1197
  }
1185
1198
  /**
1186
- * Get the beacon service ids from a list of beacon services.
1187
- * @param {DidDocument} didDocument The DID Document to extract the services from.
1188
- * @returns {string[]} An array of beacon service ids.
1199
+ * Convert the DidDocument to an GenesisDocument.
1200
+ * @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
1189
1201
  */
1190
- static getBeaconServiceIds(didDocument) {
1191
- return this.getBeaconServices(didDocument).map((beacon) => beacon.id);
1202
+ toIntermediate() {
1203
+ if (this.id.includes("k1")) {
1204
+ throw new import_common7.DidDocumentError("Cannot convert a key identifier to an intermediate document", import_common7.INVALID_DID_DOCUMENT, this);
1205
+ }
1206
+ return new GenesisDocument(this);
1192
1207
  }
1193
1208
  };
1194
-
1195
- // src/core/beacon/signal-discovery.ts
1196
- var BEACON_SIGNAL_SCRIPT = /^6a20([0-9a-f]{64})$/i;
1197
- function extractOpReturnSignalHash(scriptPubKey) {
1198
- if (!scriptPubKey) {
1199
- return null;
1200
- }
1201
- const signal = BEACON_SIGNAL_SCRIPT.exec(scriptPubKey.trim());
1202
- if (!signal) {
1203
- return null;
1209
+ var GenesisDocument = class _GenesisDocument extends DidDocument {
1210
+ constructor(document) {
1211
+ super(document);
1204
1212
  }
1205
- return signal[1].toLowerCase();
1206
- }
1207
- var BeaconSignalDiscovery = class _BeaconSignalDiscovery {
1208
1213
  /**
1209
- * Determines whether a candidate transaction spends an output controlled by the given
1210
- * beacon address.
1211
- *
1212
- * A Beacon Signal is a transaction that *spends from* a Beacon Address, but an address
1213
- * transaction listing returns every transaction touching the address in either
1214
- * direction. Without this check, anyone able to pay dust to a beacon address could
1215
- * attach an arbitrary 32-byte OP_RETURN and have it read as a signal, so the input side
1216
- * has to be inspected before a transaction is treated as one.
1217
- *
1218
- * Esplora embeds the spent output in `vin[].prevout`; when a backend omits it the
1219
- * funding transaction is fetched instead, so a missing field cannot silently drop a
1220
- * real signal.
1221
- *
1222
- * @param {RawTransactionRest} tx The candidate transaction.
1223
- * @param {string} address The beacon address the transaction must spend from.
1224
- * @param {BitcoinConnection} bitcoin Bitcoin network connection to use for REST calls.
1225
- * @returns {Promise<boolean>} True if at least one input spends an output of the beacon address.
1214
+ * Convert the GenesisDocument to a DidDocument by replacing the placeholder value with the provided DID.
1215
+ * @param did The DID to replace the placeholder value in the document.
1216
+ * @returns {DidDocument} A new DidDocument with the placeholder value replaced by the provided DID.
1226
1217
  */
1227
- static async spendsFromAddress(tx, address, bitcoin) {
1228
- for (const vin of tx.vin ?? []) {
1229
- if (vin.is_coinbase) {
1230
- continue;
1231
- }
1232
- let prevout = vin.prevout;
1233
- if (!prevout && vin.txid) {
1234
- const fundingTx = await bitcoin.rest.transaction.get(vin.txid);
1235
- prevout = fundingTx?.vout?.[vin.vout];
1236
- }
1237
- if (prevout?.scriptpubkey_address === address) {
1238
- return true;
1239
- }
1240
- }
1241
- return false;
1218
+ toDidDocument(did) {
1219
+ const stringThis = JSON.stringify(this).replaceAll(ID_PLACEHOLDER_VALUE, did);
1220
+ const parseThis = JSON.parse(stringThis);
1221
+ return new DidDocument(parseThis);
1242
1222
  }
1243
1223
  /**
1244
- * Retrieves the beacon signals for the given array of BeaconService objects
1245
- * using an esplora/electrs REST API connection via a bitcoin I/O driver.
1246
- * @param {Array<BeaconService>} beaconServices Array of BeaconService objects to retrieve signals for
1247
- * @param {BitcoinConnection} bitcoin Bitcoin network connection to use for REST calls
1248
- * @returns {Promise<Map<BeaconService, Array<BeaconSignal>>>} Map of beacon service to its discovered signals
1224
+ * Create an GenesisDocument from a DidDocument by replacing the DID with a placeholder value.
1225
+ * @param {DidDocument} didDocument The DidDocument to convert.
1226
+ * @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
1249
1227
  */
1250
- static async indexer(beaconServices, bitcoin) {
1251
- const beaconServiceSignals = /* @__PURE__ */ new Map();
1252
- const currentBlockCount = await bitcoin.rest.block.count();
1253
- for (const beaconService of beaconServices) {
1254
- beaconServiceSignals.set(beaconService, []);
1255
- const beaconAddress = BeaconUtils.parseBitcoinAddress(beaconService.serviceEndpoint);
1256
- const beaconSignals = await bitcoin.rest.address.getTxs(beaconAddress);
1257
- if (!beaconSignals || !beaconSignals.length) {
1258
- continue;
1259
- }
1260
- for (const beaconSignal of beaconSignals) {
1261
- const lastSignalVout = beaconSignal.vout.slice(-1)[0];
1262
- if (!lastSignalVout) {
1263
- continue;
1264
- }
1265
- const updateHash = extractOpReturnSignalHash(lastSignalVout.scriptpubkey);
1266
- if (!updateHash) {
1267
- continue;
1268
- }
1269
- if (!await _BeaconSignalDiscovery.spendsFromAddress(beaconSignal, beaconAddress, bitcoin)) {
1270
- continue;
1271
- }
1272
- const confirmations = currentBlockCount - beaconSignal.status.block_height + 1;
1273
- beaconServiceSignals.get(beaconService)?.push({
1274
- tx: beaconSignal,
1275
- signalBytes: updateHash,
1276
- blockMetadata: {
1277
- confirmations,
1278
- height: beaconSignal.status.block_height,
1279
- time: beaconSignal.status.block_time
1280
- }
1281
- });
1282
- }
1283
- }
1284
- return beaconServiceSignals;
1228
+ static fromDidDocument(didDocument) {
1229
+ const intermediateDocument = import_common7.JSONUtils.cloneReplace(didDocument, DID_REGEX, ID_PLACEHOLDER_VALUE);
1230
+ return new _GenesisDocument(intermediateDocument);
1285
1231
  }
1286
1232
  /**
1287
- * Traverse the full blockchain from genesis to chain top looking for beacon signals.
1288
- * @param {Array<BeaconService>} beaconServices Array of BeaconService objects to search for signals.
1289
- * @param {BitcoinConnection} bitcoin Bitcoin network connection to use for RPC calls.
1290
- * @returns {Promise<Map<BeaconService, Array<BeaconSignal>>>} Map of beacon service to its discovered signals.
1233
+ * Create a minimal GenesisDocument with a placeholder ID.
1234
+ * @param {Array<DidVerificationMethod>} verificationMethod The public key in multibase format.
1235
+ * @param {VerificationRelationships} relationships The public key in multibase format.
1236
+ * @param {Array<BeaconService>} service The service to be included in the document.
1237
+ * @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
1291
1238
  */
1292
- static async fullnode(beaconServices, bitcoin) {
1293
- const beaconServiceSignals = /* @__PURE__ */ new Map();
1294
- for (const beaconService of beaconServices) {
1295
- beaconServiceSignals.set(beaconService, []);
1296
- }
1297
- const rpc = bitcoin.rpc;
1298
- if (!rpc) {
1299
- throw new import_common9.ResolveError("RPC connection is not available", "RPC_CONNECTION_ERROR", bitcoin);
1300
- }
1301
- const targetHeight = await rpc.getBlockCount();
1302
- const beaconServicesMap = new Map(
1303
- beaconServices.map((service) => [BeaconUtils.parseBitcoinAddress(service.serviceEndpoint), service])
1304
- );
1305
- let height = 0;
1306
- let block = await bitcoin.rpc.getBlock({ height });
1307
- console.info(`Searching for beacon signals, please wait ...`);
1308
- while (block.height <= targetHeight) {
1309
- for (const tx of block.tx) {
1310
- if (tx.txid === import_bitcoin3.GENESIS_TX_ID) {
1311
- continue;
1312
- }
1313
- const lastSignalVout = tx.vout.slice(-1)[0];
1314
- if (!lastSignalVout) {
1315
- continue;
1316
- }
1317
- const updateHash = extractOpReturnSignalHash(lastSignalVout.scriptPubKey?.hex);
1318
- if (!updateHash) {
1319
- continue;
1320
- }
1321
- const signaled = /* @__PURE__ */ new Set();
1322
- for (const vin of tx.vin) {
1323
- if (vin.coinbase) {
1324
- continue;
1325
- }
1326
- if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] === import_bitcoin3.TXIN_WITNESS_COINBASE) {
1327
- continue;
1328
- }
1329
- if (!vin.txid) {
1330
- continue;
1331
- }
1332
- if (vin.vout === void 0) {
1333
- continue;
1334
- }
1335
- const prevout = await rpc.getRawTransaction(vin.txid, 2);
1336
- if (!prevout.vout[vin.vout]) {
1337
- continue;
1338
- }
1339
- const scriptPubKey = prevout.vout[vin.vout].scriptPubKey;
1340
- if (!scriptPubKey.address) {
1341
- continue;
1342
- }
1343
- const beaconService = beaconServicesMap.get(scriptPubKey.address);
1344
- if (!beaconService || signaled.has(beaconService)) {
1345
- continue;
1346
- }
1347
- signaled.add(beaconService);
1348
- console.info(`Tx ${tx.txid} contains beacon address ${scriptPubKey.address}`);
1349
- beaconServiceSignals.get(beaconService)?.push({
1350
- tx,
1351
- signalBytes: updateHash,
1352
- blockMetadata: {
1353
- height: block.height,
1354
- time: block.time,
1355
- confirmations: block.confirmations
1356
- }
1357
- });
1358
- }
1359
- ;
1360
- }
1361
- height += 1;
1362
- if (height > targetHeight) {
1363
- console.info(`Chain tip reached ${height}, breaking ...`);
1364
- break;
1365
- }
1366
- block = await rpc.getBlock({ height });
1367
- }
1368
- return beaconServiceSignals;
1369
- }
1370
- };
1371
-
1372
- // src/core/did-sender-resolver.ts
1373
- var import_common14 = require("@did-btcr2/common");
1374
- var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
1375
- var import_keypair4 = require("@did-btcr2/keypair");
1376
-
1377
- // src/core/resolver.ts
1378
- var import_bitcoin5 = require("@did-btcr2/bitcoin");
1379
- var import_common13 = require("@did-btcr2/common");
1380
- var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
1381
- var import_keypair3 = require("@did-btcr2/keypair");
1382
-
1383
- // src/did-btcr2.ts
1384
- var import_common12 = require("@did-btcr2/common");
1385
- var import_dids2 = require("@web5/dids");
1386
-
1387
- // src/core/updater.ts
1388
- var import_common11 = require("@did-btcr2/common");
1389
- var import_cryptosuite = require("@did-btcr2/cryptosuite");
1390
-
1391
- // src/utils/did-document.ts
1392
- var import_bitcoin4 = require("@did-btcr2/bitcoin");
1393
- var import_common10 = require("@did-btcr2/common");
1394
- var import_keypair2 = require("@did-btcr2/keypair");
1395
- var import_utils4 = require("@web5/dids/utils");
1396
- var import_btc_signer3 = require("@scure/btc-signer");
1397
- var BTCR2_DID_DOCUMENT_CONTEXT = [
1398
- "https://www.w3.org/ns/did/v1.1",
1399
- "https://btcr2.dev/context/v1"
1400
- ];
1401
- var MULTIKEY_VERIFICATION_METHOD_TYPE = "Multikey";
1402
- var MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX = "zQ3s";
1403
- var ID_PLACEHOLDER_VALUE = "did:btcr2:_";
1404
- var BECH32M_CHARS = "";
1405
- var DID_REGEX = /did:btcr2:(x1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]*)/g;
1406
- function isMultikeyVerificationMethod(vm) {
1407
- if (!Appendix.isDidVerificationMethod(vm)) {
1408
- return false;
1409
- }
1410
- const { type, publicKeyMultibase } = vm;
1411
- return type === MULTIKEY_VERIFICATION_METHOD_TYPE && typeof publicKeyMultibase === "string" && publicKeyMultibase.startsWith(MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX);
1412
- }
1413
- var DidVerificationMethod = class {
1414
- id;
1415
- type;
1416
- controller;
1417
- publicKeyMultibase;
1418
- secretKeyMultibase;
1419
- constructor({ id, type, controller, publicKeyMultibase, secretKeyMultibase }) {
1420
- if (type !== MULTIKEY_VERIFICATION_METHOD_TYPE) {
1421
- throw new import_common10.DidDocumentError(
1422
- `Invalid verification method: type must be "${MULTIKEY_VERIFICATION_METHOD_TYPE}"`,
1423
- import_common10.INVALID_DID_DOCUMENT,
1424
- { id, type }
1425
- );
1426
- }
1427
- if (typeof publicKeyMultibase !== "string" || !publicKeyMultibase.startsWith(MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX)) {
1428
- throw new import_common10.DidDocumentError(
1429
- `Invalid verification method: publicKeyMultibase must start with "${MULTIKEY_PUBLIC_KEY_MULTIBASE_PREFIX}"`,
1430
- import_common10.INVALID_DID_DOCUMENT,
1431
- { id, publicKeyMultibase }
1432
- );
1433
- }
1434
- this.id = id;
1435
- this.type = type;
1436
- this.controller = controller;
1437
- this.publicKeyMultibase = publicKeyMultibase;
1438
- this.secretKeyMultibase = secretKeyMultibase;
1439
- if (!secretKeyMultibase) {
1440
- delete this.secretKeyMultibase;
1441
- }
1239
+ static create(verificationMethod, relationships, service) {
1240
+ return new _GenesisDocument({ id: ID_PLACEHOLDER_VALUE, ...relationships, verificationMethod, service });
1442
1241
  }
1443
- // TODO: Add helper methods and properties
1444
- };
1445
- var DidDocument = class _DidDocument {
1446
- id;
1447
- "@context" = [
1448
- "https://www.w3.org/ns/did/v1.1",
1449
- "https://btcr2.dev/context/v1"
1450
- ];
1451
- verificationMethod;
1452
- authentication;
1453
- assertionMethod;
1454
- capabilityInvocation;
1455
- capabilityDelegation;
1456
- service;
1457
- deactivated;
1458
- constructor(document) {
1459
- if (!document.id) {
1460
- throw new import_common10.DidDocumentError("DID Document must have an id", import_common10.INVALID_DID_DOCUMENT, document);
1461
- }
1462
- const idType = document.id.includes("k1") ? import_common10.IdentifierTypes.KEY : import_common10.IdentifierTypes.EXTERNAL;
1463
- const isGenesis = document.id === ID_PLACEHOLDER_VALUE;
1464
- const { id, verificationMethod: vm, service } = document;
1465
- if (!isGenesis) {
1466
- if (!_DidDocument.isValidId(id)) {
1467
- throw new import_common10.DidDocumentError(`Invalid id: ${id}`, import_common10.INVALID_DID_DOCUMENT, document);
1468
- }
1469
- if (!_DidDocument.isValidVerificationMethods(vm)) {
1470
- throw new import_common10.DidDocumentError("Invalid verificationMethod: " + vm, import_common10.INVALID_DID_DOCUMENT, document);
1471
- }
1472
- if (!_DidDocument.isValidServices(service)) {
1473
- throw new import_common10.DidDocumentError("Invalid service: " + service, import_common10.INVALID_DID_DOCUMENT, document);
1242
+ /**
1243
+ * Create a minimal GenesisDocument from a public key.
1244
+ * @param {KeyBytes} publicKey The public key in bytes format.
1245
+ * @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
1246
+ */
1247
+ static fromPublicKey(publicKey, network) {
1248
+ const pk = new import_keypair.CompressedSecp256k1PublicKey(publicKey);
1249
+ const id = ID_PLACEHOLDER_VALUE;
1250
+ const address = (0, import_btc_signer2.p2pkh)(pk.compressed, (0, import_bitcoin2.getNetwork)(network)).address;
1251
+ const services = [{
1252
+ id: `${id}#service-0`,
1253
+ serviceEndpoint: `bitcoin:${address}`,
1254
+ type: "SingletonBeacon"
1255
+ }];
1256
+ const relationships = {
1257
+ authentication: [`${id}#key-0`],
1258
+ assertionMethod: [`${id}#key-0`],
1259
+ capabilityInvocation: [`${id}#key-0`],
1260
+ capabilityDelegation: [`${id}#key-0`]
1261
+ };
1262
+ const verificationMethod = [
1263
+ {
1264
+ id: `${id}#key-0`,
1265
+ type: "Multikey",
1266
+ controller: id,
1267
+ publicKeyMultibase: pk.multibase.encoded
1474
1268
  }
1475
- }
1476
- this.id = document.id;
1477
- this.verificationMethod = document.verificationMethod || [];
1478
- this.service = document.service || [];
1479
- this["@context"] = document["@context"] || [
1480
- "https://www.w3.org/ns/did/v1.1",
1481
- "https://btcr2.dev/context/v1"
1482
1269
  ];
1483
- if (idType === import_common10.IdentifierTypes.KEY) {
1484
- const keyRef = `${this.id}#initialKey`;
1485
- this.authentication = document.authentication || [keyRef];
1486
- this.assertionMethod = document.assertionMethod || [keyRef];
1487
- this.capabilityInvocation = document.capabilityInvocation || [keyRef];
1488
- this.capabilityDelegation = document.capabilityDelegation || [keyRef];
1489
- } else {
1490
- this.authentication = document.authentication;
1491
- this.assertionMethod = document.assertionMethod;
1492
- this.capabilityInvocation = document.capabilityInvocation;
1493
- this.capabilityDelegation = document.capabilityDelegation;
1494
- }
1495
- _DidDocument.sanitize(this);
1496
- if (isGenesis) {
1497
- this.validateGenesis();
1498
- } else {
1499
- _DidDocument.validate(this);
1500
- }
1270
+ return _GenesisDocument.create(verificationMethod, relationships, services);
1501
1271
  }
1502
1272
  /**
1503
- * Convert the DidDocument to a JSON object.
1504
- * @returns {DidDocument} The JSON representation of the DidDocument.
1273
+ * Taken an object, convert it to an IntermediateDocuemnt and then to a DidDocument.
1274
+ * @param {object | DidDocument} object The JSON object to convert.
1275
+ * @returns {DidDocument} The created DidDocument.
1505
1276
  */
1506
- toJSON() {
1507
- return {
1508
- id: this.id,
1509
- "@context": this["@context"],
1510
- verificationMethod: this.verificationMethod,
1511
- authentication: this.authentication,
1512
- assertionMethod: this.assertionMethod,
1513
- capabilityInvocation: this.capabilityInvocation,
1514
- capabilityDelegation: this.capabilityDelegation,
1515
- service: this.service,
1516
- deactivated: this.deactivated
1517
- };
1277
+ static fromJSON(object) {
1278
+ return new _GenesisDocument(object);
1518
1279
  }
1519
1280
  /**
1520
- * Create a minimal DidDocument from "k1" btcr2 identifier.
1521
- * @param {string} publicKeyMultibase The public key in multibase format.
1522
- * @param {Array<BeaconService>} service The beacon services to be included in the document.
1523
- * @returns {DidDocument} A new DidDocument with the placeholder ID.
1281
+ * Convert a GenesisDocument to genesis bytes.
1282
+ * @param {GenesisDocument} genesisDocument The GenesisDocument to convert.
1283
+ * @returns {Bytes} The genesis bytes.
1524
1284
  */
1525
- static fromKeyIdentifier(id, publicKeyMultibase, service) {
1526
- id = id.includes("#") ? id : `${id}#initialKey`;
1527
- const document = {
1528
- id,
1529
- verificationMethod: [
1530
- new DidVerificationMethod({
1531
- id,
1532
- type: "Multikey",
1533
- controller: id,
1534
- publicKeyMultibase
1535
- })
1536
- ],
1537
- service
1538
- };
1539
- return new _DidDocument(document);
1285
+ static toGenesisBytes(genesisDocument) {
1286
+ return (0, import_common7.hash)((0, import_common7.canonicalize)(genesisDocument));
1540
1287
  }
1288
+ };
1289
+
1290
+ // src/core/identifier.ts
1291
+ var DID_PREFIX = "did:btcr2:";
1292
+ var Identifier = class _Identifier {
1541
1293
  /**
1542
- * Create a DidDocument from "x1" btcr2 identifier.
1543
- * @param {ExternalData} data The verification methods of the DID Document.
1544
- * @returns {DidDocument} A new DidDocument.
1294
+ * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-encoding | 3.2 did:btcr2 Identifier Encoding}.
1295
+ *
1296
+ * A did:btcr2 DID consists of a did:btcr2 prefix, followed by an id-bech32 value, which is a Bech32m encoding of:
1297
+ * - the specification version;
1298
+ * - the Bitcoin network identifier; and
1299
+ * - either:
1300
+ * - a key-value representing a secp256k1 public key; or
1301
+ * - a hash-value representing the hash of an initiating external DID document.
1302
+ *
1303
+ * @param {KeyBytes | DocumentBytes} genesisBytes The genesis bytes (public key or document bytes).
1304
+ * @param {DidCreateOptions} options The DID creation options.
1305
+ * @returns {string} The new did:btcr2 identifier.
1545
1306
  */
1546
- static fromExternalIdentifier(data) {
1547
- return new _DidDocument(data);
1307
+ static encode(genesisBytes, options) {
1308
+ const { idType, version = 1, network = "bitcoin" } = options;
1309
+ if (!(idType in import_common8.IdentifierTypes)) {
1310
+ throw new import_common8.IdentifierError('Expected "idType" to be "KEY" or "EXTERNAL"', import_common8.INVALID_DID, { idType });
1311
+ }
1312
+ if (version !== 1) {
1313
+ throw new import_common8.IdentifierError('Expected "version" to be 1', import_common8.INVALID_DID, { version });
1314
+ }
1315
+ if (typeof network !== "string") {
1316
+ throw new import_common8.IdentifierError('Expected "network" to be a known network name', import_common8.INVALID_DID, { network });
1317
+ }
1318
+ const networkValue = import_common8.BitcoinNetworkNames[network];
1319
+ if (networkValue === void 0) {
1320
+ throw new import_common8.IdentifierError('Invalid "network" name', import_common8.INVALID_DID, { network });
1321
+ }
1322
+ if (idType === "KEY") {
1323
+ try {
1324
+ new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
1325
+ } catch {
1326
+ throw new import_common8.IdentifierError(
1327
+ 'Expected "genesisBytes" to be a valid compressed secp256k1 public key',
1328
+ import_common8.INVALID_DID,
1329
+ { genesisBytes }
1330
+ );
1331
+ }
1332
+ } else if (genesisBytes.length !== 32) {
1333
+ throw new import_common8.IdentifierError(
1334
+ 'Expected "genesisBytes" to be a 32-byte hash for EXTERNAL identifiers',
1335
+ import_common8.INVALID_DID,
1336
+ { genesisBytes }
1337
+ );
1338
+ }
1339
+ const hrp = idType === "KEY" ? "k" : "x";
1340
+ const firstByte = version - 1 << 4 | networkValue;
1341
+ const dataBytes = new Uint8Array([firstByte, ...genesisBytes]);
1342
+ return `${DID_PREFIX}${import_base.bech32m.encodeFromBytes(hrp, dataBytes)}`;
1548
1343
  }
1549
1344
  /**
1550
- * Sanitize the DID Document by removing undefined values
1551
- * @returns {DidDocument} The sanitized DID Document
1345
+ * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}.
1346
+ * @param {string} identifier The BTCR2 DID to be parsed
1347
+ * @returns {DidComponents} The parsed identifier components. See {@link DidComponents} for details.
1348
+ * @throws {DidError} if an error occurs while parsing the identifier
1349
+ * @throws {DidErrorCode.InvalidDid} if identifier is invalid
1350
+ * @throws {DidErrorCode.MethodNotSupported} if the method is not supported
1552
1351
  */
1553
- static sanitize(doc) {
1554
- for (const key of Object.keys(doc)) {
1555
- if (doc[key] === void 0) {
1556
- delete doc[key];
1352
+ static decode(identifier) {
1353
+ const components = identifier.split(":");
1354
+ if (components.length !== 3) {
1355
+ throw new import_common8.IdentifierError(`Invalid did: ${identifier}`, import_common8.INVALID_DID, { identifier });
1356
+ }
1357
+ const [scheme, method, encoded] = components;
1358
+ if (scheme !== "did") {
1359
+ throw new import_common8.IdentifierError(`Invalid did: ${identifier}`, import_common8.INVALID_DID, { identifier });
1360
+ }
1361
+ if (method !== "btcr2") {
1362
+ throw new import_common8.IdentifierError(`Invalid did method: ${method}`, import_common8.METHOD_NOT_SUPPORTED, { identifier });
1363
+ }
1364
+ if (!encoded) {
1365
+ throw new import_common8.IdentifierError(`Invalid method-specific id: ${identifier}`, import_common8.INVALID_DID, { identifier });
1366
+ }
1367
+ if (encoded !== encoded.toLowerCase()) {
1368
+ throw new import_common8.IdentifierError(`Invalid method-specific id (must be lowercase): ${identifier}`, import_common8.INVALID_DID, { identifier });
1369
+ }
1370
+ const { prefix: hrp, bytes: dataBytes } = import_base.bech32m.decodeToBytes(encoded);
1371
+ if (!["x", "k"].includes(hrp)) {
1372
+ throw new import_common8.IdentifierError(`Invalid hrp: ${hrp}`, import_common8.INVALID_DID, { identifier });
1373
+ }
1374
+ if (!dataBytes || dataBytes.length < 1) {
1375
+ throw new import_common8.IdentifierError(`Failed to decode id: ${encoded}`, import_common8.INVALID_DID, { identifier });
1376
+ }
1377
+ const idType = hrp === "k" ? "KEY" : "EXTERNAL";
1378
+ const btcr2Version = dataBytes[0] >>> 4;
1379
+ if (btcr2Version !== 0) {
1380
+ throw new import_common8.IdentifierError(`Invalid btcr2_version (expected 0): ${btcr2Version}`, import_common8.INVALID_DID, { identifier });
1381
+ }
1382
+ const version = 1;
1383
+ const networkValue = dataBytes[0] & 15;
1384
+ const network = import_common8.BitcoinNetworkNames[networkValue];
1385
+ if (typeof network !== "string") {
1386
+ const reason = networkValue >= 12 ? "custom network not supported" : "reserved";
1387
+ throw new import_common8.IdentifierError(`Invalid network (${reason}): ${networkValue}`, import_common8.INVALID_DID, { identifier });
1388
+ }
1389
+ const genesisBytes = dataBytes.slice(1);
1390
+ if (idType === "KEY") {
1391
+ try {
1392
+ new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
1393
+ } catch {
1394
+ throw new import_common8.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common8.INVALID_DID, { identifier });
1557
1395
  }
1396
+ } else if (genesisBytes.length !== 32) {
1397
+ throw new import_common8.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common8.INVALID_DID, { identifier });
1558
1398
  }
1559
- return doc;
1399
+ return { idType, hrp, version, network, genesisBytes };
1560
1400
  }
1561
1401
  /**
1562
- * Validates a DidDocument by breaking it into modular validation methods.
1563
- * @param {DidDocument} didDocument The DID document to validate.
1564
- * @returns {boolean} True if the DID document is valid.
1565
- * @throws {DidDocumentError} If any validation check fails.
1402
+ * Validates that a did:btcr2 identifier conforms to
1403
+ * {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}
1404
+ * and returns a report of the checks. The method does not throw on an invalid identifier.
1405
+ *
1406
+ * The checks run in this order: `prefix`, `lowercase`, `bech32m`, `version`, `network`,
1407
+ * `genesisBytes`, `roundTrip`, `genesisBytesMatch`, and `genesisDocument`. The run stops at the
1408
+ * first failed check. The `network` check accepts a named network only: a reserved value (6 to
1409
+ * 11) and a custom value (12 to 15) fail, because this implementation supports no custom network.
1410
+ * The `genesisBytesMatch` check runs only if `options.genesisBytes` is present: the supplied bytes
1411
+ * must equal the genesis bytes of the identifier, for a KEY or an EXTERNAL identifier. The
1412
+ * `genesisDocument` check runs only if `options.genesisDocument` is present. For an EXTERNAL
1413
+ * identifier it confirms that the document is a valid Genesis Document and that its canonical
1414
+ * SHA-256 hash equals the genesis bytes. For a KEY identifier it fails.
1415
+ *
1416
+ * @param {string} identifier The did:btcr2 identifier to validate.
1417
+ * @param {IdentifierValidateOptions} [options] The validation options.
1418
+ * @returns {IdentifierReport} The report. See {@link IdentifierReport} for details.
1566
1419
  */
1567
- static isValid(didDocument) {
1568
- if (!this.isValidContext(didDocument?.["@context"])) {
1569
- throw new import_common10.DidDocumentError('Invalid "@context"', import_common10.INVALID_DID_DOCUMENT, didDocument);
1420
+ static validate(identifier, options = {}) {
1421
+ const checks = [];
1422
+ const pass = (name, detail) => {
1423
+ checks.push(detail === void 0 ? { name, ok: true } : { name, ok: true, detail });
1424
+ };
1425
+ const fail = (name, detail, partial = {}) => {
1426
+ checks.push({ name, ok: false, detail });
1427
+ return { did: identifier, valid: false, ...partial, checks };
1428
+ };
1429
+ if (typeof identifier !== "string") {
1430
+ return fail("prefix", "The identifier is not a string.");
1570
1431
  }
1571
- if (!this.isValidId(didDocument?.id)) {
1572
- throw new import_common10.DidDocumentError('Invalid "id"', import_common10.INVALID_DID_DOCUMENT, didDocument);
1432
+ const parts = identifier.split(":");
1433
+ if (parts.length !== 3 || parts[0] !== "did" || parts[1] !== "btcr2") {
1434
+ return fail("prefix", `The identifier must be "${DID_PREFIX}" followed by the method-specific id.`);
1573
1435
  }
1574
- if (!this.isValidVerificationMethods(didDocument?.verificationMethod)) {
1575
- throw new import_common10.DidDocumentError('Invalid "verificationMethod"', import_common10.INVALID_DID_DOCUMENT, didDocument);
1436
+ const encoded = parts[2];
1437
+ if (encoded.length === 0) {
1438
+ return fail("prefix", "The method-specific id is empty.");
1576
1439
  }
1577
- if (!this.isValidServices(didDocument?.service)) {
1578
- throw new import_common10.DidDocumentError('Invalid "service"', import_common10.INVALID_DID_DOCUMENT, didDocument);
1440
+ pass("prefix");
1441
+ if (encoded !== encoded.toLowerCase()) {
1442
+ return fail("lowercase", "The method-specific id must be lowercase.");
1579
1443
  }
1580
- if (!this.isValidVerificationRelationships(didDocument)) {
1581
- throw new import_common10.DidDocumentError("Invalid verification relationships", import_common10.INVALID_DID_DOCUMENT, didDocument);
1444
+ pass("lowercase");
1445
+ let hrp;
1446
+ let dataBytes;
1447
+ try {
1448
+ ({ prefix: hrp, bytes: dataBytes } = import_base.bech32m.decodeToBytes(encoded));
1449
+ } catch (error) {
1450
+ return fail("bech32m", `Bech32m decoding failed: ${error instanceof Error ? error.message : String(error)}`);
1582
1451
  }
1583
- return true;
1452
+ if (hrp !== "k" && hrp !== "x") {
1453
+ return fail("bech32m", `The hrp must be "k" or "x", got "${hrp}".`);
1454
+ }
1455
+ const idType = hrp === "k" ? import_common8.IdentifierTypes.KEY : import_common8.IdentifierTypes.EXTERNAL;
1456
+ if (dataBytes.length < 1) {
1457
+ return fail("bech32m", "The data bytes are empty.", { idType });
1458
+ }
1459
+ pass("bech32m", `hrp "${hrp}", ${dataBytes.length} data bytes`);
1460
+ const btcr2Version = dataBytes[0] >>> 4;
1461
+ if (btcr2Version !== 0) {
1462
+ return fail("version", `btcr2_version must be 0, got ${btcr2Version}.`, { idType });
1463
+ }
1464
+ pass("version", "btcr2_version 0 (version_number 1)");
1465
+ const networkValue = dataBytes[0] & 15;
1466
+ const network = import_common8.BitcoinNetworkNames[networkValue];
1467
+ if (typeof network !== "string") {
1468
+ const detail = networkValue >= 12 ? `network_value ${networkValue} is a custom network, not supported by this implementation.` : `network_value ${networkValue} is reserved.`;
1469
+ return fail("network", detail, { idType });
1470
+ }
1471
+ pass("network", `network_value ${networkValue} (${network})`);
1472
+ const genesisBytes = dataBytes.slice(1);
1473
+ if (idType === import_common8.IdentifierTypes.KEY) {
1474
+ try {
1475
+ new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
1476
+ } catch {
1477
+ return fail(
1478
+ "genesisBytes",
1479
+ `Expected a 33-byte SEC compressed secp256k1 public key, got ${genesisBytes.length} bytes that are not a valid key.`,
1480
+ { idType, network }
1481
+ );
1482
+ }
1483
+ pass("genesisBytes", "33-byte SEC compressed secp256k1 public key");
1484
+ } else {
1485
+ if (genesisBytes.length !== 32) {
1486
+ return fail("genesisBytes", `Expected a 32-byte SHA-256 hash, got ${genesisBytes.length} bytes.`, { idType, network });
1487
+ }
1488
+ pass("genesisBytes", "32-byte SHA-256 hash");
1489
+ }
1490
+ let reEncoded;
1491
+ try {
1492
+ reEncoded = _Identifier.encode(genesisBytes, { idType, version: 1, network });
1493
+ } catch (error) {
1494
+ return fail("roundTrip", `Re-encoding failed: ${error instanceof Error ? error.message : String(error)}`, { idType, network });
1495
+ }
1496
+ if (reEncoded !== identifier) {
1497
+ return fail("roundTrip", `Re-encoding produced "${reEncoded}".`, { idType, network });
1498
+ }
1499
+ pass("roundTrip");
1500
+ if (options.genesisBytes !== void 0) {
1501
+ const supplied = options.genesisBytes;
1502
+ if (!(supplied instanceof Uint8Array)) {
1503
+ return fail("genesisBytesMatch", "The supplied genesis bytes are not a Uint8Array.", { idType, network });
1504
+ }
1505
+ if (supplied.length !== genesisBytes.length) {
1506
+ return fail(
1507
+ "genesisBytesMatch",
1508
+ `Expected ${genesisBytes.length} genesis bytes for a ${idType} identifier, got ${supplied.length}.`,
1509
+ { idType, network }
1510
+ );
1511
+ }
1512
+ if (!(0, import_utils4.equalBytes)(supplied, genesisBytes)) {
1513
+ return fail(
1514
+ "genesisBytesMatch",
1515
+ `The supplied genesis bytes ${import_base.hex.encode(supplied)} do not equal the genesis bytes of the identifier ${import_base.hex.encode(genesisBytes)}.`,
1516
+ { idType, network }
1517
+ );
1518
+ }
1519
+ pass("genesisBytesMatch", "The supplied genesis bytes equal the genesis bytes of the identifier.");
1520
+ }
1521
+ if (options.genesisDocument !== void 0) {
1522
+ const document = options.genesisDocument;
1523
+ if (idType === import_common8.IdentifierTypes.KEY) {
1524
+ return fail("genesisDocument", "A KEY identifier has no genesis document.", { idType, network });
1525
+ }
1526
+ const id = document.id;
1527
+ if (id !== ID_PLACEHOLDER_VALUE) {
1528
+ return fail("genesisDocument", `The genesis document id must be "${ID_PLACEHOLDER_VALUE}", got ${JSON.stringify(id)}.`, { idType, network });
1529
+ }
1530
+ try {
1531
+ GenesisDocument.fromJSON(document);
1532
+ } catch (error) {
1533
+ return fail("genesisDocument", `Invalid genesis document: ${error instanceof Error ? error.message : String(error)}`, { idType, network });
1534
+ }
1535
+ const documentHash = (0, import_common8.canonicalHashBytes)(document);
1536
+ if (!(0, import_utils4.equalBytes)(documentHash, genesisBytes)) {
1537
+ return fail(
1538
+ "genesisDocument",
1539
+ `The genesis document hash ${import_base.hex.encode(documentHash)} does not equal the genesis bytes ${import_base.hex.encode(genesisBytes)}.`,
1540
+ { idType, network }
1541
+ );
1542
+ }
1543
+ pass("genesisDocument", "The genesis document hashes to the genesis bytes.");
1544
+ }
1545
+ return { did: identifier, valid: true, idType, network, checks };
1584
1546
  }
1585
1547
  /**
1586
- * Validates that "@context" exists and includes correct values.
1587
- * @private
1588
- * @param {DidDocument['@context']} context The context to validate.
1589
- * @returns {boolean} True if the context is valid.
1548
+ * Generates a new did:btcr2 identifier based on a newly generated key pair.
1549
+ * @returns {string} The new did:btcr2 identifier.
1590
1550
  */
1591
- static isValidContext(context) {
1592
- if (!Array.isArray(context) || context.length === 0) return false;
1593
- return BTCR2_DID_DOCUMENT_CONTEXT.every((required) => context.includes(required));
1551
+ static generate() {
1552
+ const keyPair = import_keypair2.SchnorrKeyPair.generate();
1553
+ const did = this.encode(
1554
+ keyPair.publicKey.compressed,
1555
+ {
1556
+ idType: "KEY",
1557
+ version: 1,
1558
+ network: "regtest"
1559
+ }
1560
+ );
1561
+ return { keyPair: keyPair.exportJSON(), did };
1594
1562
  }
1595
1563
  /**
1596
- * Validates that the DID Document has a valid id.
1597
- * @private
1598
- * @param {string} id The id to validate.
1599
- * @returns {boolean} True if the id is valid.
1564
+ * Extracts the compressed secp256k1 public key from a KEY-type did:btcr2 identifier.
1565
+ * @param {string} did The did:btcr2 identifier to extract the public key from.
1566
+ * @returns {CompressedSecp256k1PublicKey} The compressed public key.
1567
+ * @throws {IdentifierError} If the DID is EXTERNAL type (genesis bytes are a hash, not a pubkey).
1600
1568
  */
1601
- static isValidId(id) {
1602
- if (typeof id !== "string") return false;
1569
+ static getPublicKey(did) {
1570
+ const { idType, genesisBytes } = _Identifier.decode(did);
1571
+ if (idType !== "KEY") {
1572
+ throw new import_common8.IdentifierError(
1573
+ `Cannot extract public key from EXTERNAL DID: ${did}. EXTERNAL DIDs encode a document hash, not a public key.`,
1574
+ import_common8.INVALID_DID,
1575
+ { did, idType }
1576
+ );
1577
+ }
1578
+ return new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
1579
+ }
1580
+ /**
1581
+ * Validates a did:btcr2 identifier.
1582
+ * @param {string} identifier The did:btcr2 identifier to validate.
1583
+ * @returns {boolean} True if the identifier is valid, false otherwise.
1584
+ */
1585
+ static isValid(identifier) {
1603
1586
  try {
1604
- Identifier.decode(id);
1587
+ this.decode(identifier);
1605
1588
  return true;
1606
1589
  } catch {
1607
1590
  return false;
1608
1591
  }
1609
1592
  }
1593
+ };
1594
+
1595
+ // src/core/beacon/utils.ts
1596
+ var BeaconUtils = class {
1610
1597
  /**
1611
- * Validates that verification methods exist and are correctly formatted.
1612
- * @private
1613
- * @param {DidVerificationMethod[]} verificationMethod The verification methods to validate.
1614
- * @returns {boolean} True if the verification methods are valid.
1615
- */
1616
- static isValidVerificationMethods(verificationMethod) {
1617
- return Array.isArray(verificationMethod) && verificationMethod.every(isMultikeyVerificationMethod);
1618
- }
1619
- /**
1620
- * Validates that the DID Document has valid services.
1621
- * @private
1622
- * @param {DidService[]} service The services to validate.
1623
- * @returns {boolean} True if the services are valid.
1598
+ * Converts a BIP21 Bitcoin URI to a Bitcoin address
1599
+ * @param {string} uri The BIP21 Bitcoin URI to convert
1600
+ * @returns {string} The Bitcoin address extracted from the URI
1601
+ * @throws {DidMethodError} if the URI is not a valid Bitcoin URI
1624
1602
  */
1625
- static isValidServices(service) {
1626
- return Array.isArray(service) && service.every(import_utils4.isDidService);
1603
+ static parseBitcoinAddress(uri) {
1604
+ if (!uri.startsWith("bitcoin:")) {
1605
+ throw new import_common9.MethodError("Invalid Bitcoin URI format", "BEACON_SERVICE_ERROR", { uri });
1606
+ }
1607
+ return uri.replace("bitcoin:", "").split("?")[0];
1627
1608
  }
1628
1609
  /**
1629
- * Validates verification relationships (authentication, assertionMethod, capabilityInvocation, capabilityDelegation).
1630
- * @private
1631
- * @param {DidDocument} didDocument The DID Document to validate.
1632
- * @returns {boolean} True if the verification relationships are valid.
1610
+ * Validates that the given object is a Beacon Service
1611
+ * @param {BeaconService} obj The object to validate
1612
+ * @returns {boolean} A boolean indicating whether the object is a Beacon Service
1633
1613
  */
1634
- static isValidVerificationRelationships(didDocument) {
1635
- const possibleVerificationRelationships = [
1636
- "authentication",
1637
- "assertionMethod",
1638
- "capabilityInvocation",
1639
- "capabilityDelegation"
1640
- ];
1641
- const keys = Object.keys(didDocument);
1642
- const availableKeys = possibleVerificationRelationships.filter((key) => keys.includes(key));
1643
- return availableKeys.every((key) => {
1644
- const value = didDocument[key];
1645
- return value && Array.isArray(value) && value.every(
1646
- (entry) => typeof entry === "string" || Appendix.isDidVerificationMethod(entry)
1647
- );
1648
- });
1614
+ static isBeaconService(obj) {
1615
+ if (!Appendix.isDidService(obj)) return false;
1616
+ if (!["SingletonBeacon", "CASBeacon", "SMTBeacon"].includes(obj.type)) return false;
1617
+ if ([obj.serviceEndpoint].flat().some((ep) => typeof ep === "string" && !ep.startsWith("bitcoin:"))) return false;
1618
+ return true;
1649
1619
  }
1650
1620
  /**
1651
- * Validate the DID Document
1652
- * @returns {DidDocument} Validated DID Document.
1653
- * @throws {DidDocumentError} If the DID Document is invalid.
1621
+ * Extracts the services from a given DID Document
1622
+ * @param {DidDocument} didDocument The DID Document to extract the services from
1623
+ * @returns {DidService[]} An array of DidService objects
1624
+ * @throws {TypeError} if the didDocument is not provided
1654
1625
  */
1655
- static validate(didDocument) {
1656
- if (didDocument.id === ID_PLACEHOLDER_VALUE) {
1657
- didDocument.validateGenesis();
1658
- } else {
1659
- _DidDocument.isValid(didDocument);
1660
- }
1661
- return didDocument;
1626
+ static getBeaconServices(didDocument) {
1627
+ return didDocument.service.filter(this.isBeaconService) ?? [];
1662
1628
  }
1663
1629
  /**
1664
- * Validate the GenesisDocument.
1665
- * @returns {boolean} True if the GenesisDocument is valid.
1630
+ * Create the 3 default Beacon Service Endpoints for a given `k` (public-key-based) identifier.
1631
+ * @param {string} did The DID for which to create the beacon services.
1632
+ * @returns {Array<Array<string>>} 2D Array of bitcoin addresses (p2pkh, p2wpkh, p2tr).
1633
+ * @throws {DidMethodError} if the bitcoin address is invalid.
1666
1634
  */
1667
- validateGenesis() {
1668
- if (this.id !== ID_PLACEHOLDER_VALUE) {
1669
- throw new import_common10.DidDocumentError("Invalid GenesisDocument ID", import_common10.INVALID_DID_DOCUMENT, this);
1670
- }
1671
- if (!this.verificationMethod.every((vm) => vm.id.includes(ID_PLACEHOLDER_VALUE) && vm.controller === ID_PLACEHOLDER_VALUE)) {
1672
- throw new import_common10.DidDocumentError("Invalid GenesisDocument verificationMethod", import_common10.INVALID_DID_DOCUMENT, this);
1673
- }
1674
- if (!this.service.every((svc) => svc.id.includes(ID_PLACEHOLDER_VALUE))) {
1675
- throw new import_common10.DidDocumentError("Invalid GenesisDocument service", import_common10.INVALID_DID_DOCUMENT, this);
1676
- }
1677
- if (!_DidDocument.isValidVerificationRelationships(this)) {
1678
- throw new import_common10.DidDocumentError("Invalid GenesisDocument assertionMethod", import_common10.INVALID_DID_DOCUMENT, this);
1635
+ static createBeaconServices(did, beaconType) {
1636
+ try {
1637
+ const addrTypes = ["p2pkh", "p2wpkh", "p2tr"];
1638
+ return addrTypes.map(
1639
+ (addrType) => this.createBeaconService(did, addrType, beaconType)
1640
+ );
1641
+ } catch (error) {
1642
+ throw new BeaconError(
1643
+ "Failed to create beacon services: " + error.message,
1644
+ "BEACON_SERVICE_ERROR",
1645
+ { did, beaconType }
1646
+ );
1679
1647
  }
1680
- return true;
1681
1648
  }
1682
1649
  /**
1683
- * Convert the DidDocument to an GenesisDocument.
1684
- * @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
1650
+ * Generate a set of Beacon Services for a given public key.
1651
+ * @param {string} did The did for the beacon service.
1652
+ * @param {string} addressType The type of bitcoin address to generate (p2pkh, p2wpkh, p2tr).
1653
+ * @param {string} beaconType The type of beacon service to create.
1654
+ * @returns {BeaconService} A BeaconService object.
1655
+ * @throws {DidMethodError} if the bitcoin address is invalid.
1685
1656
  */
1686
- toIntermediate() {
1687
- if (this.id.includes("k1")) {
1688
- throw new import_common10.DidDocumentError("Cannot convert a key identifier to an intermediate document", import_common10.INVALID_DID_DOCUMENT, this);
1657
+ static createBeaconService(did, addressType, beaconType) {
1658
+ try {
1659
+ const components = Identifier.decode(did);
1660
+ const network = (0, import_bitcoin3.getNetwork)(components.network);
1661
+ const pubkey = components.genesisBytes;
1662
+ const id = `${did}#initial${addressType.toUpperCase()}`;
1663
+ 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;
1664
+ const serviceEndpoint = `bitcoin:${address}`;
1665
+ return { id, type: beaconType, serviceEndpoint };
1666
+ } catch (error) {
1667
+ throw new BeaconError(
1668
+ "Failed to create beacon service: " + error.message,
1669
+ "BEACON_SERVICE_ERROR",
1670
+ { did, beaconType }
1671
+ );
1689
1672
  }
1690
- return new GenesisDocument(this);
1691
- }
1692
- };
1693
- var GenesisDocument = class _GenesisDocument extends DidDocument {
1694
- constructor(document) {
1695
- super(document);
1696
1673
  }
1697
1674
  /**
1698
- * Convert the GenesisDocument to a DidDocument by replacing the placeholder value with the provided DID.
1699
- * @param did The DID to replace the placeholder value in the document.
1700
- * @returns {DidDocument} A new DidDocument with the placeholder value replaced by the provided DID.
1675
+ * Generate three default Beacon Service Endpoints for a given `k` (public-key-based) identifier.
1676
+ * @param {string} did The DID for which to create the beacon services.
1677
+ * @returns {Array<Array<string>>} 2D Array of bitcoin addresses (p2pkh, p2wpkh, p2tr).
1678
+ * @throws {DidMethodError} if the bitcoin address is invalid.
1701
1679
  */
1702
- toDidDocument(did) {
1703
- const stringThis = JSON.stringify(this).replaceAll(ID_PLACEHOLDER_VALUE, did);
1704
- const parseThis = JSON.parse(stringThis);
1705
- return new DidDocument(parseThis);
1680
+ static generateBeaconServices({ id, publicKey, network, beaconType }) {
1681
+ try {
1682
+ const p2pkhAddr = (0, import_btc_signer3.p2pkh)(publicKey, network).address;
1683
+ const p2wpkhAddr = (0, import_btc_signer3.p2wpkh)(publicKey, network).address;
1684
+ const p2trAddr = (0, import_btc_signer3.p2tr)(publicKey.slice(1, 33), void 0, network).address;
1685
+ if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
1686
+ throw new import_common9.DidMethodError("Failed to generate bitcoin addresses");
1687
+ }
1688
+ return [
1689
+ {
1690
+ id: `${id}#initialP2PKH`,
1691
+ type: beaconType,
1692
+ serviceEndpoint: `bitcoin:${p2pkhAddr}`
1693
+ },
1694
+ {
1695
+ id: `${id}#initialP2WPKH`,
1696
+ type: beaconType,
1697
+ serviceEndpoint: `bitcoin:${p2wpkhAddr}`
1698
+ },
1699
+ {
1700
+ id: `${id}#initialP2TR`,
1701
+ type: beaconType,
1702
+ serviceEndpoint: `bitcoin:${p2trAddr}`
1703
+ }
1704
+ ];
1705
+ } catch (error) {
1706
+ throw new BeaconError(
1707
+ "Failed to create beacon services: " + error.message,
1708
+ "BEACON_SERVICE_ERROR",
1709
+ { id, publicKey, network, beaconType }
1710
+ );
1711
+ }
1706
1712
  }
1707
1713
  /**
1708
- * Create an GenesisDocument from a DidDocument by replacing the DID with a placeholder value.
1709
- * @param {DidDocument} didDocument The DidDocument to convert.
1710
- * @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
1714
+ * Convert beacon service endpoints from BIP-21 URIs to addresses.
1715
+ * @param {BeaconService} beacon The beacon service to parse.
1716
+ * @returns {BeaconServiceAddress} The beacon service with the address field extracted from the serviceEndpoint.
1711
1717
  */
1712
- static fromDidDocument(didDocument) {
1713
- const intermediateDocument = import_common10.JSONUtils.cloneReplace(didDocument, DID_REGEX, ID_PLACEHOLDER_VALUE);
1714
- return new _GenesisDocument(intermediateDocument);
1718
+ static parseBeaconServiceEndpoint(beacon) {
1719
+ return { ...beacon, serviceEndpoint: beacon.serviceEndpoint.replace("bitcoin:", "") };
1715
1720
  }
1716
1721
  /**
1717
- * Create a minimal GenesisDocument with a placeholder ID.
1718
- * @param {Array<DidVerificationMethod>} verificationMethod The public key in multibase format.
1719
- * @param {VerificationRelationships} relationships The public key in multibase format.
1720
- * @param {Array<BeaconService>} service The service to be included in the document.
1721
- * @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
1722
+ * Get the beacon service ids from a list of beacon services.
1723
+ * @param {DidDocument} didDocument The DID Document to extract the services from.
1724
+ * @returns {string[]} An array of beacon service ids.
1722
1725
  */
1723
- static create(verificationMethod, relationships, service) {
1724
- return new _GenesisDocument({ id: ID_PLACEHOLDER_VALUE, ...relationships, verificationMethod, service });
1726
+ static getBeaconServiceIds(didDocument) {
1727
+ return this.getBeaconServices(didDocument).map((beacon) => beacon.id);
1728
+ }
1729
+ };
1730
+
1731
+ // src/core/beacon/signal-discovery.ts
1732
+ var BEACON_SIGNAL_SCRIPT = /^6a20([0-9a-f]{64})$/i;
1733
+ function extractOpReturnSignalHash(scriptPubKey) {
1734
+ if (!scriptPubKey) {
1735
+ return null;
1736
+ }
1737
+ const signal = BEACON_SIGNAL_SCRIPT.exec(scriptPubKey.trim());
1738
+ if (!signal) {
1739
+ return null;
1725
1740
  }
1741
+ return signal[1].toLowerCase();
1742
+ }
1743
+ var BeaconSignalDiscovery = class _BeaconSignalDiscovery {
1726
1744
  /**
1727
- * Create a minimal GenesisDocument from a public key.
1728
- * @param {KeyBytes} publicKey The public key in bytes format.
1729
- * @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
1745
+ * Determines whether a candidate transaction spends an output controlled by the given
1746
+ * beacon address.
1747
+ *
1748
+ * A Beacon Signal is a transaction that *spends from* a Beacon Address, but an address
1749
+ * transaction listing returns every transaction touching the address in either
1750
+ * direction. Without this check, anyone able to pay dust to a beacon address could
1751
+ * attach an arbitrary 32-byte OP_RETURN and have it read as a signal, so the input side
1752
+ * has to be inspected before a transaction is treated as one.
1753
+ *
1754
+ * Esplora embeds the spent output in `vin[].prevout`; when a backend omits it the
1755
+ * funding transaction is fetched instead, so a missing field cannot silently drop a
1756
+ * real signal.
1757
+ *
1758
+ * @param {RawTransactionRest} tx The candidate transaction.
1759
+ * @param {string} address The beacon address the transaction must spend from.
1760
+ * @param {BitcoinConnection} bitcoin Bitcoin network connection to use for REST calls.
1761
+ * @returns {Promise<boolean>} True if at least one input spends an output of the beacon address.
1730
1762
  */
1731
- static fromPublicKey(publicKey, network) {
1732
- const pk = new import_keypair2.CompressedSecp256k1PublicKey(publicKey);
1733
- const id = ID_PLACEHOLDER_VALUE;
1734
- const address = (0, import_btc_signer3.p2pkh)(pk.compressed, (0, import_bitcoin4.getNetwork)(network)).address;
1735
- const services = [{
1736
- id: `${id}#service-0`,
1737
- serviceEndpoint: `bitcoin:${address}`,
1738
- type: "SingletonBeacon"
1739
- }];
1740
- const relationships = {
1741
- authentication: [`${id}#key-0`],
1742
- assertionMethod: [`${id}#key-0`],
1743
- capabilityInvocation: [`${id}#key-0`],
1744
- capabilityDelegation: [`${id}#key-0`]
1745
- };
1746
- const verificationMethod = [
1747
- {
1748
- id: `${id}#key-0`,
1749
- type: "Multikey",
1750
- controller: id,
1751
- publicKeyMultibase: pk.multibase.encoded
1763
+ static async spendsFromAddress(tx, address, bitcoin) {
1764
+ for (const vin of tx.vin ?? []) {
1765
+ if (vin.is_coinbase) {
1766
+ continue;
1752
1767
  }
1753
- ];
1754
- return _GenesisDocument.create(verificationMethod, relationships, services);
1768
+ let prevout = vin.prevout;
1769
+ if (!prevout && vin.txid) {
1770
+ const fundingTx = await bitcoin.rest.transaction.get(vin.txid);
1771
+ prevout = fundingTx?.vout?.[vin.vout];
1772
+ }
1773
+ if (prevout?.scriptpubkey_address === address) {
1774
+ return true;
1775
+ }
1776
+ }
1777
+ return false;
1755
1778
  }
1756
1779
  /**
1757
- * Taken an object, convert it to an IntermediateDocuemnt and then to a DidDocument.
1758
- * @param {object | DidDocument} object The JSON object to convert.
1759
- * @returns {DidDocument} The created DidDocument.
1780
+ * Retrieves the beacon signals for the given array of BeaconService objects
1781
+ * using an esplora/electrs REST API connection via a bitcoin I/O driver.
1782
+ *
1783
+ * The address listing includes mempool transactions. The method skips a
1784
+ * transaction whose `status.confirmed` is not `true`. A mempool transaction
1785
+ * has no block height and no block time, so it cannot carry block metadata.
1786
+ * The specification also says that a resolver must not process an
1787
+ * unconfirmed transaction. An absent flag counts as unconfirmed, as it does
1788
+ * for UTXO selection. The check runs before the OP_RETURN parse, so a
1789
+ * mempool transaction costs no prevout fetch. The {@link fullnode} path
1790
+ * needs no such check: it walks mined blocks only.
1791
+ *
1792
+ * The `confirmations` count uses the block count fetched before the listing.
1793
+ * A block that arrives between the two calls yields a count of `0` for its
1794
+ * transactions. The resolver then excludes them, because its minimum is at
1795
+ * least `1`. An under-count is the safe direction, so keep that order.
1796
+ * @param {Array<BeaconService>} beaconServices Array of BeaconService objects to retrieve signals for
1797
+ * @param {BitcoinConnection} bitcoin Bitcoin network connection to use for REST calls
1798
+ * @returns {Promise<Map<BeaconService, Array<BeaconSignal>>>} Map of beacon service to its discovered signals
1760
1799
  */
1761
- static fromJSON(object) {
1762
- return new _GenesisDocument(object);
1800
+ static async indexer(beaconServices, bitcoin) {
1801
+ const beaconServiceSignals = /* @__PURE__ */ new Map();
1802
+ const currentBlockCount = await bitcoin.rest.block.count();
1803
+ for (const beaconService of beaconServices) {
1804
+ beaconServiceSignals.set(beaconService, []);
1805
+ const beaconAddress = BeaconUtils.parseBitcoinAddress(beaconService.serviceEndpoint);
1806
+ const beaconSignals = await bitcoin.rest.address.getTxs(beaconAddress);
1807
+ if (!beaconSignals || !beaconSignals.length) {
1808
+ continue;
1809
+ }
1810
+ for (const beaconSignal of beaconSignals) {
1811
+ const status = beaconSignal.status;
1812
+ if (status.confirmed !== true) {
1813
+ continue;
1814
+ }
1815
+ const lastSignalVout = beaconSignal.vout.slice(-1)[0];
1816
+ if (!lastSignalVout) {
1817
+ continue;
1818
+ }
1819
+ const updateHash = extractOpReturnSignalHash(lastSignalVout.scriptpubkey);
1820
+ if (!updateHash) {
1821
+ continue;
1822
+ }
1823
+ if (!await _BeaconSignalDiscovery.spendsFromAddress(beaconSignal, beaconAddress, bitcoin)) {
1824
+ continue;
1825
+ }
1826
+ const confirmations = currentBlockCount - status.block_height + 1;
1827
+ beaconServiceSignals.get(beaconService)?.push({
1828
+ tx: beaconSignal,
1829
+ signalBytes: updateHash,
1830
+ blockMetadata: {
1831
+ confirmations,
1832
+ height: status.block_height,
1833
+ time: status.block_time
1834
+ }
1835
+ });
1836
+ }
1837
+ }
1838
+ return beaconServiceSignals;
1763
1839
  }
1764
1840
  /**
1765
- * Convert a GenesisDocument to genesis bytes.
1766
- * @param {GenesisDocument} genesisDocument The GenesisDocument to convert.
1767
- * @returns {Bytes} The genesis bytes.
1841
+ * Traverse the full blockchain from genesis to chain top looking for beacon signals.
1842
+ * @param {Array<BeaconService>} beaconServices Array of BeaconService objects to search for signals.
1843
+ * @param {BitcoinConnection} bitcoin Bitcoin network connection to use for RPC calls.
1844
+ * @returns {Promise<Map<BeaconService, Array<BeaconSignal>>>} Map of beacon service to its discovered signals.
1768
1845
  */
1769
- static toGenesisBytes(genesisDocument) {
1770
- return (0, import_common10.hash)((0, import_common10.canonicalize)(genesisDocument));
1846
+ static async fullnode(beaconServices, bitcoin) {
1847
+ const beaconServiceSignals = /* @__PURE__ */ new Map();
1848
+ for (const beaconService of beaconServices) {
1849
+ beaconServiceSignals.set(beaconService, []);
1850
+ }
1851
+ const rpc = bitcoin.rpc;
1852
+ if (!rpc) {
1853
+ throw new import_common10.ResolveError("RPC connection is not available", "RPC_CONNECTION_ERROR", bitcoin);
1854
+ }
1855
+ const targetHeight = await rpc.getBlockCount();
1856
+ const beaconServicesMap = new Map(
1857
+ beaconServices.map((service) => [BeaconUtils.parseBitcoinAddress(service.serviceEndpoint), service])
1858
+ );
1859
+ let height = 0;
1860
+ let block = await bitcoin.rpc.getBlock({ height });
1861
+ console.info(`Searching for beacon signals, please wait ...`);
1862
+ while (block.height <= targetHeight) {
1863
+ for (const tx of block.tx) {
1864
+ if (tx.txid === import_bitcoin4.GENESIS_TX_ID) {
1865
+ continue;
1866
+ }
1867
+ const lastSignalVout = tx.vout.slice(-1)[0];
1868
+ if (!lastSignalVout) {
1869
+ continue;
1870
+ }
1871
+ const updateHash = extractOpReturnSignalHash(lastSignalVout.scriptPubKey?.hex);
1872
+ if (!updateHash) {
1873
+ continue;
1874
+ }
1875
+ const signaled = /* @__PURE__ */ new Set();
1876
+ for (const vin of tx.vin) {
1877
+ if (vin.coinbase) {
1878
+ continue;
1879
+ }
1880
+ if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] === import_bitcoin4.TXIN_WITNESS_COINBASE) {
1881
+ continue;
1882
+ }
1883
+ if (!vin.txid) {
1884
+ continue;
1885
+ }
1886
+ if (vin.vout === void 0) {
1887
+ continue;
1888
+ }
1889
+ const prevout = await rpc.getRawTransaction(vin.txid, 2);
1890
+ if (!prevout.vout[vin.vout]) {
1891
+ continue;
1892
+ }
1893
+ const scriptPubKey = prevout.vout[vin.vout].scriptPubKey;
1894
+ if (!scriptPubKey.address) {
1895
+ continue;
1896
+ }
1897
+ const beaconService = beaconServicesMap.get(scriptPubKey.address);
1898
+ if (!beaconService || signaled.has(beaconService)) {
1899
+ continue;
1900
+ }
1901
+ signaled.add(beaconService);
1902
+ console.info(`Tx ${tx.txid} contains beacon address ${scriptPubKey.address}`);
1903
+ beaconServiceSignals.get(beaconService)?.push({
1904
+ tx,
1905
+ signalBytes: updateHash,
1906
+ blockMetadata: {
1907
+ height: block.height,
1908
+ time: block.time,
1909
+ confirmations: block.confirmations
1910
+ }
1911
+ });
1912
+ }
1913
+ ;
1914
+ }
1915
+ height += 1;
1916
+ if (height > targetHeight) {
1917
+ console.info(`Chain tip reached ${height}, breaking ...`);
1918
+ break;
1919
+ }
1920
+ block = await rpc.getBlock({ height });
1921
+ }
1922
+ return beaconServiceSignals;
1771
1923
  }
1772
1924
  };
1773
1925
 
1926
+ // src/core/did-sender-resolver.ts
1927
+ var import_common14 = require("@did-btcr2/common");
1928
+ var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
1929
+ var import_keypair4 = require("@did-btcr2/keypair");
1930
+
1931
+ // src/core/resolver.ts
1932
+ var import_bitcoin5 = require("@did-btcr2/bitcoin");
1933
+ var import_common13 = require("@did-btcr2/common");
1934
+ var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
1935
+ var import_keypair3 = require("@did-btcr2/keypair");
1936
+
1937
+ // src/did-btcr2.ts
1938
+ var import_common12 = require("@did-btcr2/common");
1939
+ var import_dids2 = require("@web5/dids");
1940
+
1774
1941
  // src/core/updater.ts
1942
+ var import_common11 = require("@did-btcr2/common");
1943
+ var import_cryptosuite = require("@did-btcr2/cryptosuite");
1775
1944
  var Updater = class _Updater {
1776
1945
  #state = { phase: "Construct" };
1777
1946
  #sourceDocument;
@@ -2116,7 +2285,8 @@ var DidBtcr2 = class {
2116
2285
  versionId: resolutionOptions.versionId,
2117
2286
  versionTime: resolutionOptions.versionTime,
2118
2287
  genesisDocument: resolutionOptions.sidecar?.genesisDocument,
2119
- maxDiscoveryRounds: resolutionOptions.maxDiscoveryRounds
2288
+ maxDiscoveryRounds: resolutionOptions.maxDiscoveryRounds,
2289
+ minConf: resolutionOptions.minConf
2120
2290
  });
2121
2291
  }
2122
2292
  /**
@@ -2230,7 +2400,8 @@ var DidBtcr2 = class {
2230
2400
  };
2231
2401
 
2232
2402
  // src/core/resolver.ts
2233
- var import_utils6 = require("@noble/curves/utils.js");
2403
+ var import_utils7 = require("@noble/curves/utils.js");
2404
+ var DEFAULT_MIN_CONF = 6;
2234
2405
  function isRecord(value) {
2235
2406
  return typeof value === "object" && value !== null && !Array.isArray(value);
2236
2407
  }
@@ -2245,6 +2416,16 @@ function isSMTProof(value) {
2245
2416
  if (!isRecord(value)) return false;
2246
2417
  return typeof value.id === "string" && typeof value.collapsed === "string" && Array.isArray(value.hashes);
2247
2418
  }
2419
+ function validateMinConf(value) {
2420
+ if (value === void 0) return DEFAULT_MIN_CONF;
2421
+ if (typeof value === "number" && Number.isInteger(value) && value >= 1) return value;
2422
+ const shown = typeof value === "string" ? JSON.stringify(value) : String(value);
2423
+ throw new import_common13.ResolveError(
2424
+ `Invalid resolution option minConf: expected a positive integer (minimum 1), got ${shown}.`,
2425
+ import_common13.INVALID_OPTIONS,
2426
+ { minConf: value }
2427
+ );
2428
+ }
2248
2429
  var Resolver = class _Resolver {
2249
2430
  // --- Immutable inputs ---
2250
2431
  #didComponents;
@@ -2284,6 +2465,14 @@ var Resolver = class _Resolver {
2284
2465
  #maxDiscoveryRounds;
2285
2466
  /** Count of beacon-discovery passes driven by updates adding new beacon services. */
2286
2467
  #discoveryRounds = 0;
2468
+ /**
2469
+ * Minimum block confirmations a Beacon Signal must have before this resolver
2470
+ * processes it: `ResolutionOptions.minConf`, default {@link DEFAULT_MIN_CONF}.
2471
+ * Applied at signal intake in the BeaconProcess phase. A signal below the
2472
+ * threshold is excluded from the resolution; the rest of the signals are
2473
+ * processed.
2474
+ */
2475
+ #minConf;
2287
2476
  /**
2288
2477
  * @internal Use {@link DidBtcr2.resolve} to create instances.
2289
2478
  */
@@ -2295,6 +2484,7 @@ var Resolver = class _Resolver {
2295
2484
  this.#versionTime = options?.versionTime;
2296
2485
  const rounds = options?.maxDiscoveryRounds;
2297
2486
  this.#maxDiscoveryRounds = typeof rounds === "number" && rounds > 0 ? rounds : Infinity;
2487
+ this.#minConf = validateMinConf(options?.minConf);
2298
2488
  if (options?.genesisDocument) {
2299
2489
  this.#providedGenesisDocument = options.genesisDocument;
2300
2490
  }
@@ -2335,7 +2525,7 @@ var Resolver = class _Resolver {
2335
2525
  */
2336
2526
  static external(didComponents, genesisDocument) {
2337
2527
  const genesisDocumentHash = (0, import_common13.canonicalHashBytes)(genesisDocument);
2338
- if (!(0, import_utils6.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
2528
+ if (!(0, import_utils7.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
2339
2529
  throw new import_common13.ResolveError(
2340
2530
  `Initial document mismatch: genesisBytes !== genesisDocumentHash`,
2341
2531
  import_common13.INVALID_DID_DOCUMENT,
@@ -2384,6 +2574,10 @@ var Resolver = class _Resolver {
2384
2574
  * Version counter and update-hash history carried from earlier discovery rounds.
2385
2575
  * Standalone callers omit it and start fresh at version 1 with an empty history.
2386
2576
  * @returns {DidResolutionResponse} The updated DID Document, number of confirmations, and version id.
2577
+ *
2578
+ * Confirmation depth is not checked here. The BeaconProcess phase excludes a
2579
+ * signal below `ResolutionOptions.minConf` before its update reaches this method,
2580
+ * so every tuple here comes from a block at or above the threshold.
2387
2581
  */
2388
2582
  static updates(currentDocument, unsortedUpdates, versionTime, versionId, resolutionState = { currentVersionId: 1, updateHashHistory: [] }) {
2389
2583
  let currentVersionId = resolutionState.currentVersionId;
@@ -2416,7 +2610,7 @@ var Resolver = class _Resolver {
2416
2610
  }
2417
2611
  if (update.targetVersionId === currentVersionId + 1) {
2418
2612
  const sourceHashBytes = (0, import_common13.decode)(update.sourceHash, "base64urlnopad");
2419
- if (!(0, import_utils6.equalBytes)(sourceHashBytes, currentDocumentHash)) {
2613
+ if (!(0, import_utils7.equalBytes)(sourceHashBytes, currentDocumentHash)) {
2420
2614
  throw new import_common13.ResolveError(
2421
2615
  `Hash mismatch: update.sourceHash !== currentDocumentHash`,
2422
2616
  import_common13.INVALID_DID_UPDATE,
@@ -2480,7 +2674,7 @@ var Resolver = class _Resolver {
2480
2674
  }
2481
2675
  );
2482
2676
  }
2483
- if (!(0, import_utils6.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
2677
+ if (!(0, import_utils7.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
2484
2678
  throw new import_common13.ResolveError(
2485
2679
  `Invalid duplicate: unsigned update hash does not match historical hash`,
2486
2680
  import_common13.LATE_PUBLISHING_ERROR,
@@ -2547,7 +2741,7 @@ var Resolver = class _Resolver {
2547
2741
  DidDocument.validate(updatedDocument);
2548
2742
  const currentDocumentHash = (0, import_common13.canonicalHashBytes)(updatedDocument);
2549
2743
  const updateTargetHash = (0, import_common13.decode)(update.targetHash);
2550
- if (!(0, import_utils6.equalBytes)(updateTargetHash, currentDocumentHash)) {
2744
+ if (!(0, import_utils7.equalBytes)(updateTargetHash, currentDocumentHash)) {
2551
2745
  throw new import_common13.ResolveError(
2552
2746
  `Invalid update: update.targetHash !== currentDocumentHash`,
2553
2747
  import_common13.INVALID_DID_UPDATE,
@@ -2611,8 +2805,10 @@ var Resolver = class _Resolver {
2611
2805
  const allNeeds = [];
2612
2806
  for (const [service, signals] of this.#beaconServicesSignals) {
2613
2807
  if (this.#processedServices.has(service.id) || !signals.length) continue;
2808
+ const eligible = this.#eligibleSignals(signals);
2809
+ if (!eligible.length) continue;
2614
2810
  const beacon = BeaconFactory.establish(service, this.#currentDocument.id);
2615
- const result = beacon.processSignals(signals, this.#sidecarData);
2811
+ const result = beacon.processSignals(eligible, this.#sidecarData);
2616
2812
  if (result.needs.length > 0) {
2617
2813
  allNeeds.push(...result.needs);
2618
2814
  } else {
@@ -2676,6 +2872,41 @@ var Resolver = class _Resolver {
2676
2872
  }
2677
2873
  }
2678
2874
  }
2875
+ /**
2876
+ * Return the signals of one beacon service that resolution may process: the
2877
+ * signals with at least `#minConf` confirmations. The specification removes a
2878
+ * transaction below the threshold from the set of Beacon Signals, so an
2879
+ * excluded signal emits no data need and applies no update. A signal with no
2880
+ * integer confirmation count is excluded too: that is a mempool transaction
2881
+ * from a driver that did not skip it.
2882
+ *
2883
+ * An eligible signal must carry a finite block height and block time. A
2884
+ * signal that passes the count but lacks them is malformed. It fails fast
2885
+ * here with a typed error, in the style of the {@link provide} guards, and
2886
+ * not later with an invalid date inside {@link updates}.
2887
+ * @param {Array<BeaconSignal>} signals The signals the caller provided for one service.
2888
+ * @returns {Array<BeaconSignal>} The signals at or above the threshold, in the given order.
2889
+ * @throws {ResolveError} `INVALID_DID_UPDATE` for an eligible signal with no valid block metadata.
2890
+ */
2891
+ #eligibleSignals(signals) {
2892
+ const eligible = [];
2893
+ for (const signal of signals) {
2894
+ const block = signal.blockMetadata;
2895
+ const confirmations = block?.confirmations;
2896
+ if (!Number.isInteger(confirmations) || confirmations < this.#minConf) {
2897
+ continue;
2898
+ }
2899
+ if (!Number.isFinite(block?.height) || !Number.isFinite(block?.time)) {
2900
+ throw new import_common13.ResolveError(
2901
+ `Beacon signal ${signal.signalBytes} has ${confirmations} confirmations but no valid block height or block time.`,
2902
+ import_common13.INVALID_DID_UPDATE,
2903
+ { signalBytes: signal.signalBytes, confirmations, height: block?.height, time: block?.time }
2904
+ );
2905
+ }
2906
+ eligible.push(signal);
2907
+ }
2908
+ return eligible;
2909
+ }
2679
2910
  provide(need, data) {
2680
2911
  switch (need.kind) {
2681
2912
  case "NeedGenesisDocument": {
@@ -2869,6 +3100,7 @@ var DidDocumentBuilder = class {
2869
3100
  CASBeaconError,
2870
3101
  CHANGE_OUTPUT_VBYTES,
2871
3102
  DEFAULT_FEE_ESTIMATOR,
3103
+ DEFAULT_MIN_CONF,
2872
3104
  DID_REGEX,
2873
3105
  DUST_LIMIT_SATS,
2874
3106
  DidBtcr2,