@did-btcr2/method 0.27.0 → 0.29.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.
Files changed (114) hide show
  1. package/README.md +38 -9
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/browser.js +20181 -31588
  4. package/dist/browser.mjs +20110 -31517
  5. package/dist/cjs/index.js +1355 -422
  6. package/dist/esm/core/aggregation/beacon-strategy.js +62 -0
  7. package/dist/esm/core/aggregation/beacon-strategy.js.map +1 -0
  8. package/dist/esm/core/aggregation/cohort.js +31 -8
  9. package/dist/esm/core/aggregation/cohort.js.map +1 -1
  10. package/dist/esm/core/aggregation/logger.js +15 -0
  11. package/dist/esm/core/aggregation/logger.js.map +1 -0
  12. package/dist/esm/core/aggregation/messages/base.js +12 -1
  13. package/dist/esm/core/aggregation/messages/base.js.map +1 -1
  14. package/dist/esm/core/aggregation/messages/bodies.js +90 -0
  15. package/dist/esm/core/aggregation/messages/bodies.js.map +1 -0
  16. package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
  17. package/dist/esm/core/aggregation/messages/index.js +1 -0
  18. package/dist/esm/core/aggregation/messages/index.js.map +1 -1
  19. package/dist/esm/core/aggregation/participant.js +39 -46
  20. package/dist/esm/core/aggregation/participant.js.map +1 -1
  21. package/dist/esm/core/aggregation/runner/participant-runner.js +34 -4
  22. package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
  23. package/dist/esm/core/aggregation/runner/service-runner.js +198 -19
  24. package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
  25. package/dist/esm/core/aggregation/service.js +143 -15
  26. package/dist/esm/core/aggregation/service.js.map +1 -1
  27. package/dist/esm/core/aggregation/signing-session.js +44 -5
  28. package/dist/esm/core/aggregation/signing-session.js.map +1 -1
  29. package/dist/esm/core/aggregation/transport/didcomm.js +9 -0
  30. package/dist/esm/core/aggregation/transport/didcomm.js.map +1 -1
  31. package/dist/esm/core/aggregation/transport/nostr.js +245 -16
  32. package/dist/esm/core/aggregation/transport/nostr.js.map +1 -1
  33. package/dist/esm/core/beacon/beacon.js +147 -61
  34. package/dist/esm/core/beacon/beacon.js.map +1 -1
  35. package/dist/esm/core/beacon/utils.js +14 -9
  36. package/dist/esm/core/beacon/utils.js.map +1 -1
  37. package/dist/esm/core/updater.js +269 -0
  38. package/dist/esm/core/updater.js.map +1 -0
  39. package/dist/esm/did-btcr2.js +30 -46
  40. package/dist/esm/did-btcr2.js.map +1 -1
  41. package/dist/esm/index.js +4 -1
  42. package/dist/esm/index.js.map +1 -1
  43. package/dist/esm/utils/did-document.js +2 -2
  44. package/dist/esm/utils/did-document.js.map +1 -1
  45. package/dist/types/core/aggregation/beacon-strategy.d.ts +52 -0
  46. package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -0
  47. package/dist/types/core/aggregation/cohort.d.ts +20 -3
  48. package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
  49. package/dist/types/core/aggregation/logger.d.ts +22 -0
  50. package/dist/types/core/aggregation/logger.d.ts.map +1 -0
  51. package/dist/types/core/aggregation/messages/base.d.ts +13 -1
  52. package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
  53. package/dist/types/core/aggregation/messages/bodies.d.ts +130 -0
  54. package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -0
  55. package/dist/types/core/aggregation/messages/factories.d.ts +1 -0
  56. package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
  57. package/dist/types/core/aggregation/messages/index.d.ts +1 -0
  58. package/dist/types/core/aggregation/messages/index.d.ts.map +1 -1
  59. package/dist/types/core/aggregation/participant.d.ts +2 -0
  60. package/dist/types/core/aggregation/participant.d.ts.map +1 -1
  61. package/dist/types/core/aggregation/runner/events.d.ts +32 -6
  62. package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
  63. package/dist/types/core/aggregation/runner/participant-runner.d.ts +8 -2
  64. package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
  65. package/dist/types/core/aggregation/runner/service-runner.d.ts +33 -3
  66. package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
  67. package/dist/types/core/aggregation/service.d.ts +33 -2
  68. package/dist/types/core/aggregation/service.d.ts.map +1 -1
  69. package/dist/types/core/aggregation/signing-session.d.ts +5 -1
  70. package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
  71. package/dist/types/core/aggregation/transport/didcomm.d.ts +3 -0
  72. package/dist/types/core/aggregation/transport/didcomm.d.ts.map +1 -1
  73. package/dist/types/core/aggregation/transport/nostr.d.ts +99 -1
  74. package/dist/types/core/aggregation/transport/nostr.d.ts.map +1 -1
  75. package/dist/types/core/aggregation/transport/transport.d.ts +25 -0
  76. package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
  77. package/dist/types/core/beacon/beacon.d.ts +85 -18
  78. package/dist/types/core/beacon/beacon.d.ts.map +1 -1
  79. package/dist/types/core/beacon/utils.d.ts +2 -2
  80. package/dist/types/core/beacon/utils.d.ts.map +1 -1
  81. package/dist/types/core/updater.d.ts +178 -0
  82. package/dist/types/core/updater.d.ts.map +1 -0
  83. package/dist/types/did-btcr2.d.ts +23 -23
  84. package/dist/types/did-btcr2.d.ts.map +1 -1
  85. package/dist/types/index.d.ts +4 -1
  86. package/dist/types/index.d.ts.map +1 -1
  87. package/package.json +4 -6
  88. package/src/core/aggregation/beacon-strategy.ts +123 -0
  89. package/src/core/aggregation/cohort.ts +34 -8
  90. package/src/core/aggregation/logger.ts +33 -0
  91. package/src/core/aggregation/messages/base.ts +20 -5
  92. package/src/core/aggregation/messages/bodies.ts +223 -0
  93. package/src/core/aggregation/messages/factories.ts +1 -0
  94. package/src/core/aggregation/messages/index.ts +1 -0
  95. package/src/core/aggregation/participant.ts +40 -46
  96. package/src/core/aggregation/runner/events.ts +27 -3
  97. package/src/core/aggregation/runner/participant-runner.ts +42 -4
  98. package/src/core/aggregation/runner/service-runner.ts +227 -19
  99. package/src/core/aggregation/service.ts +189 -20
  100. package/src/core/aggregation/signing-session.ts +65 -7
  101. package/src/core/aggregation/transport/didcomm.ts +17 -0
  102. package/src/core/aggregation/transport/nostr.ts +266 -23
  103. package/src/core/aggregation/transport/transport.ts +33 -0
  104. package/src/core/beacon/beacon.ts +217 -76
  105. package/src/core/beacon/utils.ts +16 -11
  106. package/src/core/updater.ts +415 -0
  107. package/src/did-btcr2.ts +36 -71
  108. package/src/index.ts +4 -1
  109. package/src/utils/did-document.ts +2 -2
  110. package/dist/esm/core/update.js +0 -112
  111. package/dist/esm/core/update.js.map +0 -1
  112. package/dist/types/core/update.d.ts +0 -52
  113. package/dist/types/core/update.d.ts.map +0 -1
  114. package/src/core/update.ts +0 -158
package/dist/cjs/index.js CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  AGGREGATED_NONCE: () => AGGREGATED_NONCE,
34
34
  AGGREGATION_MESSAGE_PREFIX: () => AGGREGATION_MESSAGE_PREFIX,
35
+ AGGREGATION_WIRE_VERSION: () => AGGREGATION_WIRE_VERSION,
35
36
  AUTHORIZATION_REQUEST: () => AUTHORIZATION_REQUEST,
36
37
  AggregateBeaconError: () => AggregateBeaconError,
37
38
  AggregationCohort: () => AggregationCohort,
@@ -58,6 +59,10 @@ __export(index_exports, {
58
59
  COHORT_OPT_IN: () => COHORT_OPT_IN,
59
60
  COHORT_OPT_IN_ACCEPT: () => COHORT_OPT_IN_ACCEPT,
60
61
  COHORT_READY: () => COHORT_READY,
62
+ CONSOLE_LOGGER: () => CONSOLE_LOGGER,
63
+ DEFAULT_ADVERT_REPEAT_INTERVAL_MS: () => DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
64
+ DEFAULT_BROADCAST_LOOKBACK_MS: () => DEFAULT_BROADCAST_LOOKBACK_MS,
65
+ DEFAULT_MAX_UPDATE_SIZE_BYTES: () => DEFAULT_MAX_UPDATE_SIZE_BYTES,
61
66
  DEFAULT_NOSTR_RELAYS: () => DEFAULT_NOSTR_RELAYS,
62
67
  DID_REGEX: () => DID_REGEX,
63
68
  DISTRIBUTE_AGGREGATED_DATA: () => DISTRIBUTE_AGGREGATED_DATA,
@@ -75,6 +80,7 @@ __export(index_exports, {
75
80
  ParticipantCohortPhase: () => ParticipantCohortPhase,
76
81
  Resolver: () => Resolver,
77
82
  SIGNATURE_AUTHORIZATION: () => SIGNATURE_AUTHORIZATION,
83
+ SILENT_LOGGER: () => SILENT_LOGGER,
78
84
  SMTBeacon: () => SMTBeacon,
79
85
  SMTBeaconError: () => SMTBeaconError,
80
86
  SUBMIT_UPDATE: () => SUBMIT_UPDATE,
@@ -83,12 +89,14 @@ __export(index_exports, {
83
89
  SigningSessionPhase: () => SigningSessionPhase,
84
90
  SingletonBeacon: () => SingletonBeacon,
85
91
  SingletonBeaconError: () => SingletonBeaconError,
92
+ StaticFeeEstimator: () => StaticFeeEstimator,
86
93
  TransportAdapterError: () => TransportAdapterError,
87
94
  TransportError: () => TransportError,
88
95
  TransportFactory: () => TransportFactory,
89
96
  TypedEventEmitter: () => TypedEventEmitter,
90
- Update: () => Update,
97
+ Updater: () => Updater,
91
98
  VALIDATION_ACK: () => VALIDATION_ACK,
99
+ buildAggregationBeaconTx: () => buildAggregationBeaconTx,
92
100
  createAggregatedNonceMessage: () => createAggregatedNonceMessage,
93
101
  createAuthorizationRequestMessage: () => createAuthorizationRequestMessage,
94
102
  createCohortAdvertMessage: () => createCohortAdvertMessage,
@@ -100,41 +108,114 @@ __export(index_exports, {
100
108
  createSignatureAuthorizationMessage: () => createSignatureAuthorizationMessage,
101
109
  createSubmitUpdateMessage: () => createSubmitUpdateMessage,
102
110
  createValidationAckMessage: () => createValidationAckMessage,
111
+ getBeaconStrategy: () => getBeaconStrategy,
112
+ isAggregatedNonceMessage: () => isAggregatedNonceMessage,
103
113
  isAggregationMessageType: () => isAggregationMessageType,
114
+ isAuthorizationRequestMessage: () => isAuthorizationRequestMessage,
115
+ isCohortAdvertMessage: () => isCohortAdvertMessage,
116
+ isCohortOptInAcceptMessage: () => isCohortOptInAcceptMessage,
117
+ isCohortOptInMessage: () => isCohortOptInMessage,
118
+ isCohortReadyMessage: () => isCohortReadyMessage,
119
+ isDistributeAggregatedDataMessage: () => isDistributeAggregatedDataMessage,
104
120
  isKeygenMessageType: () => isKeygenMessageType,
121
+ isNonceContributionMessage: () => isNonceContributionMessage,
105
122
  isSignMessageType: () => isSignMessageType,
106
- isUpdateMessageType: () => isUpdateMessageType
123
+ isSignatureAuthorizationMessage: () => isSignatureAuthorizationMessage,
124
+ isSubmitUpdateMessage: () => isSubmitUpdateMessage,
125
+ isUpdateMessageType: () => isUpdateMessageType,
126
+ isValidationAckMessage: () => isValidationAckMessage,
127
+ opReturnScript: () => opReturnScript,
128
+ registerBeaconStrategy: () => registerBeaconStrategy
107
129
  });
108
130
  module.exports = __toCommonJS(index_exports);
109
131
 
110
132
  // src/core/aggregation/service.ts
133
+ var import_common4 = require("@did-btcr2/common");
134
+ var import_cryptosuite = require("@did-btcr2/cryptosuite");
111
135
  var import_utils2 = require("@noble/hashes/utils");
112
136
 
113
- // src/core/aggregation/cohort.ts
114
- var import_common2 = require("@did-btcr2/common");
137
+ // src/core/aggregation/beacon-strategy.ts
138
+ var import_common = require("@did-btcr2/common");
115
139
  var import_smt = require("@did-btcr2/smt");
140
+ var CAS_STRATEGY = {
141
+ type: "CASBeacon",
142
+ buildAggregatedData(cohort) {
143
+ cohort.buildCASAnnouncement();
144
+ },
145
+ getDistributePayload(cohort) {
146
+ return { casAnnouncement: cohort.casAnnouncement };
147
+ },
148
+ validateParticipantView({ participantDid, expectedHash, body }) {
149
+ const casAnnouncement = body.casAnnouncement;
150
+ if (!casAnnouncement) return { matches: false };
151
+ return {
152
+ matches: casAnnouncement[participantDid] === expectedHash,
153
+ casAnnouncement
154
+ };
155
+ }
156
+ };
157
+ var SMT_STRATEGY = {
158
+ type: "SMTBeacon",
159
+ buildAggregatedData(cohort) {
160
+ cohort.buildSMTTree();
161
+ },
162
+ getDistributePayload(cohort, participantDid) {
163
+ const proof = cohort.smtProofs?.get(participantDid);
164
+ return { smtProof: proof };
165
+ },
166
+ validateParticipantView({ participantDid, submittedUpdate, body }) {
167
+ const smtProof = body.smtProof;
168
+ if (!smtProof?.updateId || !smtProof?.nonce) return { matches: false };
169
+ const canonicalBytes = new TextEncoder().encode((0, import_common.canonicalize)(submittedUpdate));
170
+ const expectedUpdateId = (0, import_smt.hashToHex)((0, import_smt.blockHash)(canonicalBytes));
171
+ if (smtProof.updateId !== expectedUpdateId) {
172
+ return { matches: false, smtProof };
173
+ }
174
+ const index = (0, import_smt.didToIndex)(participantDid);
175
+ const candidateHash = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.hexToHash)(smtProof.nonce)), (0, import_smt.hexToHash)(smtProof.updateId));
176
+ return {
177
+ matches: (0, import_smt.verifySerializedProof)(smtProof, index, candidateHash),
178
+ smtProof
179
+ };
180
+ }
181
+ };
182
+ var STRATEGIES = /* @__PURE__ */ new Map([
183
+ [CAS_STRATEGY.type, CAS_STRATEGY],
184
+ [SMT_STRATEGY.type, SMT_STRATEGY]
185
+ ]);
186
+ function registerBeaconStrategy(strategy) {
187
+ STRATEGIES.set(strategy.type, strategy);
188
+ }
189
+ function getBeaconStrategy(type) {
190
+ return STRATEGIES.get(type);
191
+ }
192
+
193
+ // src/core/aggregation/cohort.ts
194
+ var import_common3 = require("@did-btcr2/common");
195
+ var import_smt2 = require("@did-btcr2/smt");
196
+ var import_secp256k1 = require("@noble/curves/secp256k1.js");
116
197
  var import_utils = require("@noble/hashes/utils");
198
+ var import_btc_signer = require("@scure/btc-signer");
117
199
  var import_musig2 = require("@scure/btc-signer/musig2");
118
- var import_bitcoinjs_lib = require("bitcoinjs-lib");
119
200
 
120
201
  // src/core/aggregation/errors.ts
121
- var import_common = require("@did-btcr2/common");
122
- var AggregationServiceError = class extends import_common.MethodError {
202
+ var import_common2 = require("@did-btcr2/common");
203
+ var AggregationServiceError = class extends import_common2.MethodError {
123
204
  constructor(message, type = "AggregationServiceError", data) {
124
205
  super(message, type, data);
125
206
  }
126
207
  };
127
- var AggregationParticipantError = class extends import_common.MethodError {
208
+ var AggregationParticipantError = class extends import_common2.MethodError {
128
209
  constructor(message, type = "AggregationParticipantError", data) {
129
210
  super(message, type, data);
130
211
  }
131
212
  };
132
- var AggregationCohortError = class extends import_common.MethodError {
213
+ var AggregationCohortError = class extends import_common2.MethodError {
133
214
  constructor(message, type = "AggregationCohortError", data) {
134
215
  super(message, type, data);
135
216
  }
136
217
  };
137
- var SigningSessionError = class extends import_common.MethodError {
218
+ var SigningSessionError = class extends import_common2.MethodError {
138
219
  constructor(message, type = "SigningSessionError", data) {
139
220
  super(message, type, data);
140
221
  }
@@ -154,10 +235,21 @@ var AggregationCohort = class {
154
235
  beaconType;
155
236
  /** List of participant DIDs that have been accepted into the cohort. */
156
237
  participants = [];
238
+ /**
239
+ * Mapping from participant DID → their compressed secp256k1 public key.
240
+ * Distinct from {@link cohortKeys} (which is sorted per BIP-327) — this lets
241
+ * callers look up a participant's key without knowing their position in the
242
+ * sorted array. Populated by the service at `acceptParticipant` time.
243
+ */
244
+ participantKeys = /* @__PURE__ */ new Map();
157
245
  /** Sorted list of cohort participants' compressed public keys. */
158
246
  #cohortKeys = [];
159
- /** Taproot tweak (BIP-341 key-path-only). */
160
- trMerkleRoot = new Uint8Array();
247
+ /**
248
+ * BIP-341 TapTweak — `taggedHash("TapTweak", internalPubkey)` for a key-path-only
249
+ * Taproot output. Despite prior naming, this is NOT a Merkle root: key-path-only
250
+ * spends have no script tree.
251
+ */
252
+ tapTweak = new Uint8Array();
161
253
  /** The n-of-n MuSig2 Taproot beacon address. */
162
254
  beaconAddress = "";
163
255
  /** Pending DID updates submitted by participants, keyed by DID. */
@@ -188,7 +280,7 @@ var AggregationCohort = class {
188
280
  }
189
281
  /**
190
282
  * Computes the n-of-n MuSig2 Taproot beacon address from cohort keys.
191
- * Sets `trMerkleRoot` to the BIP-341 key-path-only tweak.
283
+ * Sets `tapTweak` to the BIP-341 key-path-only tweak.
192
284
  */
193
285
  computeBeaconAddress() {
194
286
  if (this.#cohortKeys.length === 0) {
@@ -200,8 +292,8 @@ var AggregationCohort = class {
200
292
  }
201
293
  const keyAggContext = (0, import_musig2.keyAggregate)(this.#cohortKeys);
202
294
  const aggPubkey = (0, import_musig2.keyAggExport)(keyAggContext);
203
- const payment = import_bitcoinjs_lib.payments.p2tr({ internalPubkey: aggPubkey });
204
- this.trMerkleRoot = payment.hash ?? import_bitcoinjs_lib.crypto.taggedHash("TapTweak", aggPubkey);
295
+ const payment = (0, import_btc_signer.p2tr)(aggPubkey);
296
+ this.tapTweak = import_secp256k1.schnorr.utils.taggedHash("TapTweak", aggPubkey);
205
297
  if (!payment.address) {
206
298
  throw new AggregationCohortError(
207
299
  "Failed to compute Taproot address",
@@ -235,6 +327,18 @@ var AggregationCohort = class {
235
327
  );
236
328
  }
237
329
  }
330
+ /**
331
+ * Returns the position of a participant's public key in the sorted
332
+ * {@link cohortKeys} array, or -1 if the participant is not in the cohort.
333
+ * Required by MuSig2 partial-sig verification which indexes by signer position.
334
+ */
335
+ indexOfParticipant(did) {
336
+ const pk = this.participantKeys.get(did);
337
+ if (!pk) return -1;
338
+ return this.#cohortKeys.findIndex(
339
+ (k) => k.length === pk.length && k.every((b, i) => b === pk[i])
340
+ );
341
+ }
238
342
  addUpdate(participantDid, signedUpdate) {
239
343
  if (!this.participants.includes(participantDid)) {
240
344
  throw new AggregationCohortError(
@@ -263,10 +367,10 @@ var AggregationCohort = class {
263
367
  }
264
368
  const announcement = {};
265
369
  for (const [did, signedUpdate] of this.pendingUpdates) {
266
- announcement[did] = (0, import_common2.canonicalHash)(signedUpdate);
370
+ announcement[did] = (0, import_common3.canonicalHash)(signedUpdate);
267
371
  }
268
372
  this.casAnnouncement = announcement;
269
- this.signalBytes = (0, import_common2.hash)((0, import_common2.canonicalize)(announcement));
373
+ this.signalBytes = (0, import_common3.hash)((0, import_common3.canonicalize)(announcement));
270
374
  return announcement;
271
375
  }
272
376
  /**
@@ -282,11 +386,11 @@ var AggregationCohort = class {
282
386
  { cohortId: this.id }
283
387
  );
284
388
  }
285
- const tree = new import_smt.BTCR2MerkleTree();
389
+ const tree = new import_smt2.BTCR2MerkleTree();
286
390
  const entries = [];
287
391
  const encoder = new TextEncoder();
288
392
  for (const [did, signedUpdate] of this.pendingUpdates) {
289
- const canonicalBytes = encoder.encode((0, import_common2.canonicalize)(signedUpdate));
393
+ const canonicalBytes = encoder.encode((0, import_common3.canonicalize)(signedUpdate));
290
394
  const nonce = (0, import_utils.randomBytes)(32);
291
395
  entries.push({ did, nonce, signedUpdate: canonicalBytes });
292
396
  }
@@ -328,28 +432,17 @@ var AggregationCohort = class {
328
432
  }
329
433
  };
330
434
 
331
- // src/core/aggregation/messages/constants.ts
332
- var AGGREGATION_MESSAGE_PREFIX = "https://btcr2.dev/aggregation";
333
- var COHORT_ADVERT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_advert`;
334
- var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
335
- var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
336
- var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
337
- var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
338
- var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
339
- var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
340
- var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
341
- var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
342
- var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
343
- var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
344
-
345
435
  // src/core/aggregation/messages/base.ts
436
+ var AGGREGATION_WIRE_VERSION = 1;
346
437
  var BaseMessage = class {
347
438
  type;
439
+ version;
348
440
  to;
349
441
  from;
350
442
  body;
351
- constructor({ type, to, from: from2, body }) {
443
+ constructor({ type, version, to, from: from2, body }) {
352
444
  this.type = type;
445
+ this.version = version ?? AGGREGATION_WIRE_VERSION;
353
446
  this.to = to;
354
447
  this.from = from2;
355
448
  this.body = body;
@@ -361,6 +454,7 @@ var BaseMessage = class {
361
454
  toJSON() {
362
455
  return {
363
456
  type: this.type,
457
+ version: this.version,
364
458
  to: this.to,
365
459
  from: this.from,
366
460
  body: this.body
@@ -368,6 +462,20 @@ var BaseMessage = class {
368
462
  }
369
463
  };
370
464
 
465
+ // src/core/aggregation/messages/constants.ts
466
+ var AGGREGATION_MESSAGE_PREFIX = "https://btcr2.dev/aggregation";
467
+ var COHORT_ADVERT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_advert`;
468
+ var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
469
+ var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
470
+ var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
471
+ var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
472
+ var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
473
+ var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
474
+ var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
475
+ var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
476
+ var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
477
+ var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
478
+
371
479
  // src/core/aggregation/messages/factories.ts
372
480
  function createCohortAdvertMessage(fields) {
373
481
  const { from: from2, ...body } = fields;
@@ -455,8 +563,8 @@ var SigningSessionPhase = /* @__PURE__ */ ((SigningSessionPhase2) => {
455
563
  })(SigningSessionPhase || {});
456
564
 
457
565
  // src/core/aggregation/signing-session.ts
566
+ var import_btc_signer2 = require("@scure/btc-signer");
458
567
  var musig2 = __toESM(require("@scure/btc-signer/musig2"), 1);
459
- var import_bitcoinjs_lib2 = require("bitcoinjs-lib");
460
568
  var BeaconSigningSession = class {
461
569
  /** Unique identifier for this signing session. */
462
570
  id;
@@ -498,11 +606,11 @@ var BeaconSigningSession = class {
498
606
  "SIGHASH_ERROR"
499
607
  );
500
608
  }
501
- return this.pendingTx.hashForWitnessV1(
609
+ return this.pendingTx.preimageWitnessV1(
502
610
  0,
503
611
  this.prevOutScripts,
504
- this.prevOutValues,
505
- import_bitcoinjs_lib2.Transaction.SIGHASH_DEFAULT
612
+ import_btc_signer2.SigHash.DEFAULT,
613
+ this.prevOutValues
506
614
  );
507
615
  }
508
616
  addNonceContribution(participantDid, nonceContribution) {
@@ -513,6 +621,13 @@ var BeaconSigningSession = class {
513
621
  { phase: this.phase }
514
622
  );
515
623
  }
624
+ if (!this.cohort.participants.includes(participantDid)) {
625
+ throw new SigningSessionError(
626
+ `Participant ${participantDid} is not a member of cohort ${this.cohort.id}.`,
627
+ "UNKNOWN_PARTICIPANT",
628
+ { cohortId: this.cohort.id, participantDid }
629
+ );
630
+ }
516
631
  if (nonceContribution.length !== 66) {
517
632
  throw new SigningSessionError(
518
633
  `Invalid nonce contribution: expected 66 bytes, got ${nonceContribution.length}.`,
@@ -548,6 +663,13 @@ var BeaconSigningSession = class {
548
663
  "INVALID_PHASE"
549
664
  );
550
665
  }
666
+ if (!this.cohort.participants.includes(participantDid)) {
667
+ throw new SigningSessionError(
668
+ `Participant ${participantDid} is not a member of cohort ${this.cohort.id}.`,
669
+ "UNKNOWN_PARTICIPANT",
670
+ { cohortId: this.cohort.id, participantDid }
671
+ );
672
+ }
551
673
  if (this.partialSignatures.has(participantDid)) {
552
674
  throw new SigningSessionError(
553
675
  `Duplicate partial signature from ${participantDid}.`,
@@ -573,9 +695,39 @@ var BeaconSigningSession = class {
573
695
  this.aggregatedNonce,
574
696
  this.cohort.cohortKeys,
575
697
  this.sigHash,
576
- [this.cohort.trMerkleRoot],
698
+ [this.cohort.tapTweak],
577
699
  [true]
578
700
  );
701
+ const pubNoncesByIndex = new Array(this.cohort.cohortKeys.length);
702
+ for (const [did, nonce] of this.nonceContributions) {
703
+ const idx = this.cohort.indexOfParticipant(did);
704
+ if (idx < 0) {
705
+ throw new SigningSessionError(
706
+ `Cannot verify nonce from ${did}: participant key missing from cohort.`,
707
+ "UNKNOWN_PARTICIPANT_KEY",
708
+ { participantDid: did }
709
+ );
710
+ }
711
+ pubNoncesByIndex[idx] = nonce;
712
+ }
713
+ for (const [did, partialSig] of this.partialSignatures) {
714
+ const idx = this.cohort.indexOfParticipant(did);
715
+ if (idx < 0) {
716
+ throw new SigningSessionError(
717
+ `Cannot verify partial signature from ${did}: participant key missing from cohort.`,
718
+ "UNKNOWN_PARTICIPANT_KEY",
719
+ { participantDid: did }
720
+ );
721
+ }
722
+ const ok = session.partialSigVerify(partialSig, pubNoncesByIndex, idx);
723
+ if (!ok) {
724
+ throw new SigningSessionError(
725
+ `Bad partial signature from ${did}.`,
726
+ "BAD_PARTIAL_SIG",
727
+ { participantDid: did, index: idx }
728
+ );
729
+ }
730
+ }
579
731
  this.signature = session.partialSigAgg([...this.partialSignatures.values()]);
580
732
  this.phase = "Complete" /* Complete */;
581
733
  return this.signature;
@@ -593,6 +745,10 @@ var BeaconSigningSession = class {
593
745
  /**
594
746
  * Generates a partial signature using the participant's secret key + secret nonce.
595
747
  * Requires the aggregated nonce to have been set first (via the service).
748
+ *
749
+ * Zeros the stored `secretNonce` after use. JS cannot truly erase memory (GC
750
+ * and immutable strings), but overwriting the bytes shortens the exposure
751
+ * window and prevents accidental reuse or serialization of a spent nonce.
596
752
  */
597
753
  generatePartialSignature(participantSecretKey) {
598
754
  if (!this.aggregatedNonce) {
@@ -605,10 +761,13 @@ var BeaconSigningSession = class {
605
761
  this.aggregatedNonce,
606
762
  this.cohort.cohortKeys,
607
763
  this.sigHash,
608
- [this.cohort.trMerkleRoot],
764
+ [this.cohort.tapTweak],
609
765
  [true]
610
766
  );
611
- return session.sign(this.secretNonce, participantSecretKey);
767
+ const partialSig = session.sign(this.secretNonce, participantSecretKey);
768
+ this.secretNonce.fill(0);
769
+ this.secretNonce = void 0;
770
+ return partialSig;
612
771
  }
613
772
  isComplete() {
614
773
  return this.phase === "Complete" /* Complete */;
@@ -619,16 +778,32 @@ var BeaconSigningSession = class {
619
778
  };
620
779
 
621
780
  // src/core/aggregation/service.ts
781
+ var DEFAULT_MAX_UPDATE_SIZE_BYTES = 256 * 1024;
622
782
  var AggregationService = class {
623
783
  did;
624
784
  keys;
785
+ maxUpdateSizeBytes;
625
786
  /** Per-cohort state, keyed by cohortId. */
626
787
  #cohortStates = /* @__PURE__ */ new Map();
627
- constructor({ did, keys }) {
788
+ constructor({ did, keys, maxUpdateSizeBytes }) {
628
789
  this.did = did;
629
790
  this.keys = keys;
791
+ this.maxUpdateSizeBytes = maxUpdateSizeBytes ?? DEFAULT_MAX_UPDATE_SIZE_BYTES;
630
792
  }
631
793
  receive(message) {
794
+ const version = message.version;
795
+ if (version === void 0 || version !== AGGREGATION_WIRE_VERSION) {
796
+ const cohortId = message.body?.cohortId;
797
+ const state = cohortId ? this.#cohortStates.get(cohortId) : void 0;
798
+ if (state) {
799
+ state.rejections.push({
800
+ from: message.from,
801
+ code: "WRONG_VERSION",
802
+ reason: `Expected wire version ${AGGREGATION_WIRE_VERSION}, got ${String(version)}`
803
+ });
804
+ }
805
+ return;
806
+ }
632
807
  const type = message.type;
633
808
  switch (type) {
634
809
  case COHORT_OPT_IN:
@@ -650,6 +825,18 @@ var AggregationService = class {
650
825
  break;
651
826
  }
652
827
  }
828
+ /**
829
+ * Drain the rejection log for a cohort. Used by runners to surface silent
830
+ * drops (bad proof, oversized update, wrong version, etc.) as structured
831
+ * events without breaking the sans-I/O state machine contract.
832
+ */
833
+ drainRejections(cohortId) {
834
+ const state = this.#cohortStates.get(cohortId);
835
+ if (!state) return [];
836
+ const out = state.rejections;
837
+ state.rejections = [];
838
+ return out;
839
+ }
653
840
  /**
654
841
  * Create a new cohort with the given config. Returns the cohort ID.
655
842
  * Cohort starts in `Created` phase — call `advertise()` to broadcast.
@@ -666,7 +853,8 @@ var AggregationService = class {
666
853
  cohort,
667
854
  config,
668
855
  pendingOptIns: /* @__PURE__ */ new Map(),
669
- acceptedParticipants: /* @__PURE__ */ new Set()
856
+ acceptedParticipants: /* @__PURE__ */ new Set(),
857
+ rejections: []
670
858
  });
671
859
  return cohort.id;
672
860
  }
@@ -719,6 +907,7 @@ var AggregationService = class {
719
907
  const participantPk = message.body?.participantPk;
720
908
  const communicationPk = message.body?.communicationPk;
721
909
  if (!participantPk || !communicationPk) return;
910
+ if (state.acceptedParticipants.has(participantDid)) return;
722
911
  state.pendingOptIns.set(participantDid, {
723
912
  cohortId,
724
913
  participantDid,
@@ -752,6 +941,7 @@ var AggregationService = class {
752
941
  }
753
942
  state.acceptedParticipants.add(participantDid);
754
943
  state.cohort.participants.push(participantDid);
944
+ state.cohort.participantKeys.set(participantDid, optIn.participantPk);
755
945
  state.cohort.cohortKeys = [...state.cohort.cohortKeys, optIn.participantPk];
756
946
  return [createCohortOptInAcceptMessage({
757
947
  from: this.did,
@@ -802,6 +992,13 @@ var AggregationService = class {
802
992
  if (!state) return /* @__PURE__ */ new Map();
803
993
  return state.cohort.pendingUpdates;
804
994
  }
995
+ /**
996
+ * Handle an incoming SUBMIT_UPDATE message from a participant containing their signed update to
997
+ * submit for aggregation.
998
+ * @param {BaseMessage} message - incoming SUBMIT_UPDATE message containing a participant's signed
999
+ * update to submit for aggregation
1000
+ * @returns {void} - no return value; updates the service state with the submitted update if valid
1001
+ */
805
1002
  #handleSubmitUpdate(message) {
806
1003
  const cohortId = message.body?.cohortId;
807
1004
  if (!cohortId) return;
@@ -809,7 +1006,31 @@ var AggregationService = class {
809
1006
  if (!state) return;
810
1007
  if (state.phase !== "CohortSet" /* CohortSet */ && state.phase !== "CollectingUpdates" /* CollectingUpdates */) return;
811
1008
  const signedUpdate = message.body?.signedUpdate;
812
- if (!signedUpdate) return;
1009
+ if (!signedUpdate) {
1010
+ state.rejections.push({
1011
+ from: message.from,
1012
+ code: "UPDATE_MALFORMED",
1013
+ reason: "SUBMIT_UPDATE missing signedUpdate body"
1014
+ });
1015
+ return;
1016
+ }
1017
+ const canonicalSize = (0, import_common4.canonicalize)(signedUpdate).length;
1018
+ if (canonicalSize > this.maxUpdateSizeBytes) {
1019
+ state.rejections.push({
1020
+ from: message.from,
1021
+ code: "UPDATE_TOO_LARGE",
1022
+ reason: `Canonicalized update is ${canonicalSize} bytes; max allowed is ${this.maxUpdateSizeBytes}`
1023
+ });
1024
+ return;
1025
+ }
1026
+ if (!this.#verifySubmittedUpdate(state, message.from, signedUpdate)) {
1027
+ state.rejections.push({
1028
+ from: message.from,
1029
+ code: "UPDATE_VERIFICATION_FAILED",
1030
+ reason: "BIP-340 Data Integrity proof verification failed"
1031
+ });
1032
+ return;
1033
+ }
813
1034
  state.cohort.addUpdate(message.from, signedUpdate);
814
1035
  if (state.phase === "CohortSet" /* CohortSet */) {
815
1036
  state.phase = "CollectingUpdates" /* CollectingUpdates */;
@@ -818,6 +1039,36 @@ var AggregationService = class {
818
1039
  state.phase = "UpdatesCollected" /* UpdatesCollected */;
819
1040
  }
820
1041
  }
1042
+ /**
1043
+ * Verify the BIP-340 Schnorr Data Integrity proof on a submitted update using the
1044
+ * participant's public key from their cohort opt-in. Returns `false` (and the
1045
+ * update is silently dropped) if the proof is missing, the verificationMethod does
1046
+ * not name the sender's DID, the participant has no opt-in on record, or the
1047
+ * signature fails verification.
1048
+ * @param {ServiceCohortState} state - the current state of the cohort to which the update was submitted
1049
+ * @param {string} sender - the DID of the participant who submitted the update
1050
+ * @param {SignedBTCR2Update} signedUpdate - the signed update containing the proof to verify
1051
+ * @returns {boolean} - `true` if the proof is valid and the update can be accepted; `false` otherwise
1052
+ */
1053
+ #verifySubmittedUpdate(state, sender, signedUpdate) {
1054
+ const proof = signedUpdate.proof;
1055
+ if (!proof?.verificationMethod || !proof.proofValue) return false;
1056
+ const vmDid = proof.verificationMethod.split("#")[0];
1057
+ if (vmDid !== sender) return false;
1058
+ const optIn = state.pendingOptIns.get(sender);
1059
+ if (!optIn) return false;
1060
+ try {
1061
+ const multikey = import_cryptosuite.SchnorrMultikey.fromPublicKey({
1062
+ id: proof.verificationMethod,
1063
+ controller: sender,
1064
+ publicKeyBytes: optIn.participantPk
1065
+ });
1066
+ const suite = new import_cryptosuite.BIP340Cryptosuite(multikey);
1067
+ return suite.verifyProof(signedUpdate).verified === true;
1068
+ } catch {
1069
+ return false;
1070
+ }
1071
+ }
821
1072
  /**
822
1073
  * Build the aggregated data structure (CAS Announcement or SMT tree) and
823
1074
  * return distribute messages to send to all participants for validation.
@@ -834,29 +1085,28 @@ var AggregationService = class {
834
1085
  { cohortId, phase: state.phase }
835
1086
  );
836
1087
  }
837
- if (state.config.beaconType === "CASBeacon") {
838
- state.cohort.buildCASAnnouncement();
839
- } else if (state.config.beaconType === "SMTBeacon") {
840
- state.cohort.buildSMTTree();
841
- } else {
1088
+ const strategy = getBeaconStrategy(state.config.beaconType);
1089
+ if (!strategy) {
842
1090
  throw new AggregationServiceError(
843
1091
  `Unsupported beacon type: ${state.config.beaconType}`,
844
1092
  "UNSUPPORTED_BEACON_TYPE",
845
1093
  { cohortId, beaconType: state.config.beaconType }
846
1094
  );
847
1095
  }
1096
+ strategy.buildAggregatedData(state.cohort);
848
1097
  const signalBytesHex = (0, import_utils2.bytesToHex)(state.cohort.signalBytes);
849
1098
  state.phase = "DataDistributed" /* DataDistributed */;
850
1099
  const messages = [];
851
1100
  for (const participantDid of state.cohort.participants) {
1101
+ const payload = strategy.getDistributePayload(state.cohort, participantDid);
852
1102
  messages.push(createDistributeAggregatedDataMessage({
853
1103
  from: this.did,
854
1104
  to: participantDid,
855
1105
  cohortId,
856
1106
  beaconType: state.config.beaconType,
857
1107
  signalBytesHex,
858
- casAnnouncement: state.config.beaconType === "CASBeacon" ? state.cohort.casAnnouncement : void 0,
859
- smtProof: state.config.beaconType === "SMTBeacon" ? state.cohort.smtProofs?.get(participantDid) : void 0
1108
+ casAnnouncement: payload.casAnnouncement,
1109
+ smtProof: payload.smtProof
860
1110
  }));
861
1111
  }
862
1112
  return messages;
@@ -918,6 +1168,14 @@ var AggregationService = class {
918
1168
  });
919
1169
  state.signingSession = session;
920
1170
  state.phase = "SigningStarted" /* SigningStarted */;
1171
+ const prevOutScript = txData.prevOutScripts[0];
1172
+ if (!prevOutScript) {
1173
+ throw new AggregationServiceError(
1174
+ `Cannot start signing for cohort ${cohortId}: txData.prevOutScripts[0] is missing.`,
1175
+ "MISSING_PREV_OUT_SCRIPT",
1176
+ { cohortId }
1177
+ );
1178
+ }
921
1179
  const messages = [];
922
1180
  for (const participantDid of state.cohort.participants) {
923
1181
  messages.push(createAuthorizationRequestMessage({
@@ -925,7 +1183,8 @@ var AggregationService = class {
925
1183
  to: participantDid,
926
1184
  cohortId,
927
1185
  sessionId: session.id,
928
- pendingTx: txData.tx.toHex(),
1186
+ pendingTx: txData.tx.hex,
1187
+ prevOutScriptHex: (0, import_utils2.bytesToHex)(prevOutScript),
929
1188
  prevOutValue: txData.prevOutValues[0]?.toString() ?? "0"
930
1189
  }));
931
1190
  }
@@ -989,7 +1248,7 @@ var AggregationService = class {
989
1248
  state.signingSession.addPartialSignature(message.from, partialSignature);
990
1249
  if (state.signingSession.partialSignatures.size === state.cohort.participants.length) {
991
1250
  const signature = state.signingSession.generateFinalSignature();
992
- state.signingSession.pendingTx.setWitness(0, [signature]);
1251
+ state.signingSession.pendingTx.updateInput(0, { finalScriptWitness: [signature] });
993
1252
  state.result = {
994
1253
  cohortId,
995
1254
  signature,
@@ -1018,13 +1277,19 @@ var AggregationService = class {
1018
1277
  get cohorts() {
1019
1278
  return [...this.#cohortStates.values()].map((s) => s.cohort);
1020
1279
  }
1280
+ /**
1281
+ * Remove a cohort from the state map. Used by runners to GC state on cohort
1282
+ * completion, failure, or expiry. No-op if the cohort doesn't exist.
1283
+ */
1284
+ removeCohort(cohortId) {
1285
+ this.#cohortStates.delete(cohortId);
1286
+ }
1021
1287
  };
1022
1288
 
1023
1289
  // src/core/aggregation/participant.ts
1024
- var import_common3 = require("@did-btcr2/common");
1025
- var import_smt2 = require("@did-btcr2/smt");
1290
+ var import_common5 = require("@did-btcr2/common");
1026
1291
  var import_utils3 = require("@noble/hashes/utils");
1027
- var import_bitcoinjs_lib3 = require("bitcoinjs-lib");
1292
+ var import_btc_signer3 = require("@scure/btc-signer");
1028
1293
  var AggregationParticipant = class {
1029
1294
  did;
1030
1295
  keys;
@@ -1039,6 +1304,9 @@ var AggregationParticipant = class {
1039
1304
  * outgoing messages — those come exclusively from action methods.
1040
1305
  */
1041
1306
  receive(message) {
1307
+ if (message.version === void 0 || message.version !== AGGREGATION_WIRE_VERSION) {
1308
+ return;
1309
+ }
1042
1310
  const type = message.type;
1043
1311
  switch (type) {
1044
1312
  case COHORT_ADVERT:
@@ -1195,42 +1463,26 @@ var AggregationParticipant = class {
1195
1463
  if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */) return;
1196
1464
  if (!state.submittedUpdate) return;
1197
1465
  const beaconType = message.body?.beaconType;
1466
+ if (!beaconType) return;
1467
+ const strategy = getBeaconStrategy(beaconType);
1468
+ if (!strategy) return;
1198
1469
  const signalBytesHex = message.body?.signalBytesHex ?? "";
1199
- const expectedHash = (0, import_common3.canonicalHash)(state.submittedUpdate);
1200
- let matches = false;
1201
- if (beaconType === "CASBeacon") {
1202
- const casAnnouncement = message.body?.casAnnouncement;
1203
- if (casAnnouncement) {
1204
- matches = casAnnouncement[this.did] === expectedHash;
1205
- state.validation = {
1206
- cohortId,
1207
- beaconType,
1208
- signalBytesHex,
1209
- casAnnouncement,
1210
- expectedHash,
1211
- matches
1212
- };
1213
- }
1214
- } else if (beaconType === "SMTBeacon") {
1215
- const smtProof = message.body?.smtProof;
1216
- if (smtProof?.updateId && smtProof?.nonce) {
1217
- const canonicalBytes = new TextEncoder().encode((0, import_common3.canonicalize)(state.submittedUpdate));
1218
- const expectedUpdateId = (0, import_smt2.hashToHex)((0, import_smt2.blockHash)(canonicalBytes));
1219
- if (smtProof.updateId === expectedUpdateId) {
1220
- const index = (0, import_smt2.didToIndex)(this.did);
1221
- const candidateHash = (0, import_smt2.blockHash)((0, import_smt2.blockHash)((0, import_smt2.hexToHash)(smtProof.nonce)), (0, import_smt2.hexToHash)(smtProof.updateId));
1222
- matches = (0, import_smt2.verifySerializedProof)(smtProof, index, candidateHash);
1223
- }
1224
- state.validation = {
1225
- cohortId,
1226
- beaconType,
1227
- signalBytesHex,
1228
- smtProof,
1229
- expectedHash,
1230
- matches
1231
- };
1232
- }
1233
- }
1470
+ const expectedHash = (0, import_common5.canonicalHash)(state.submittedUpdate);
1471
+ const result = strategy.validateParticipantView({
1472
+ participantDid: this.did,
1473
+ submittedUpdate: state.submittedUpdate,
1474
+ expectedHash,
1475
+ body: message.body
1476
+ });
1477
+ state.validation = {
1478
+ cohortId,
1479
+ beaconType,
1480
+ signalBytesHex,
1481
+ expectedHash,
1482
+ matches: result.matches,
1483
+ casAnnouncement: result.casAnnouncement,
1484
+ smtProof: result.smtProof
1485
+ };
1234
1486
  state.phase = "AwaitingValidation" /* AwaitingValidation */;
1235
1487
  }
1236
1488
  /**
@@ -1291,12 +1543,14 @@ var AggregationParticipant = class {
1291
1543
  if (state.phase !== "ValidationSent" /* ValidationSent */) return;
1292
1544
  const sessionId = message.body?.sessionId;
1293
1545
  const pendingTxHex = message.body?.pendingTx;
1546
+ const prevOutScriptHex = message.body?.prevOutScriptHex;
1294
1547
  const prevOutValue = message.body?.prevOutValue;
1295
- if (!sessionId || !pendingTxHex || !prevOutValue) return;
1548
+ if (!sessionId || !pendingTxHex || !prevOutScriptHex || !prevOutValue) return;
1296
1549
  state.signingRequest = {
1297
1550
  cohortId,
1298
1551
  sessionId,
1299
1552
  pendingTxHex,
1553
+ prevOutScriptHex,
1300
1554
  prevOutValue
1301
1555
  };
1302
1556
  state.phase = "AwaitingSigning" /* AwaitingSigning */;
@@ -1320,8 +1574,8 @@ var AggregationParticipant = class {
1320
1574
  { cohortId }
1321
1575
  );
1322
1576
  }
1323
- const tx = import_bitcoinjs_lib3.Transaction.fromHex(state.signingRequest.pendingTxHex);
1324
- const prevOutScripts = tx.outs.length > 0 ? [tx.outs[0].script] : [];
1577
+ const tx = import_btc_signer3.Transaction.fromRaw((0, import_utils3.hexToBytes)(state.signingRequest.pendingTxHex));
1578
+ const prevOutScripts = [(0, import_utils3.hexToBytes)(state.signingRequest.prevOutScriptHex)];
1325
1579
  const prevOutValues = [BigInt(state.signingRequest.prevOutValue)];
1326
1580
  const session = new BeaconSigningSession({
1327
1581
  id: state.signingRequest.sessionId,
@@ -1391,6 +1645,67 @@ var AggregationParticipant = class {
1391
1645
  }
1392
1646
  };
1393
1647
 
1648
+ // src/core/aggregation/logger.ts
1649
+ var CONSOLE_LOGGER = {
1650
+ debug: (msg, ...args) => console.debug(msg, ...args),
1651
+ info: (msg, ...args) => console.info(msg, ...args),
1652
+ warn: (msg, ...args) => console.warn(msg, ...args),
1653
+ error: (msg, ...args) => console.error(msg, ...args)
1654
+ };
1655
+ var SILENT_LOGGER = {
1656
+ debug: () => {
1657
+ },
1658
+ info: () => {
1659
+ },
1660
+ warn: () => {
1661
+ },
1662
+ error: () => {
1663
+ }
1664
+ };
1665
+
1666
+ // src/core/aggregation/messages/bodies.ts
1667
+ var hasStr = (b, k) => !!b && typeof b[k] === "string";
1668
+ var hasNum = (b, k) => !!b && typeof b[k] === "number";
1669
+ var hasBool = (b, k) => !!b && typeof b[k] === "boolean";
1670
+ var hasBytes = (b, k) => !!b && b[k] instanceof Uint8Array;
1671
+ var hasBytesArray = (b, k) => {
1672
+ const v = b ? b[k] : void 0;
1673
+ return Array.isArray(v) && v.every((x) => x instanceof Uint8Array);
1674
+ };
1675
+ function isCohortAdvertMessage(m) {
1676
+ return m.type === COHORT_ADVERT && hasStr(m.body, "cohortId") && hasNum(m.body, "cohortSize") && hasStr(m.body, "beaconType") && hasStr(m.body, "network") && hasBytes(m.body, "communicationPk");
1677
+ }
1678
+ function isCohortOptInMessage(m) {
1679
+ return m.type === COHORT_OPT_IN && hasStr(m.body, "cohortId") && hasBytes(m.body, "participantPk") && hasBytes(m.body, "communicationPk");
1680
+ }
1681
+ function isCohortOptInAcceptMessage(m) {
1682
+ return m.type === COHORT_OPT_IN_ACCEPT && hasStr(m.body, "cohortId");
1683
+ }
1684
+ function isCohortReadyMessage(m) {
1685
+ return m.type === COHORT_READY && hasStr(m.body, "cohortId") && hasStr(m.body, "beaconAddress") && hasBytesArray(m.body, "cohortKeys");
1686
+ }
1687
+ function isSubmitUpdateMessage(m) {
1688
+ return m.type === SUBMIT_UPDATE && hasStr(m.body, "cohortId") && !!m.body && typeof m.body.signedUpdate === "object";
1689
+ }
1690
+ function isDistributeAggregatedDataMessage(m) {
1691
+ return m.type === DISTRIBUTE_AGGREGATED_DATA && hasStr(m.body, "cohortId") && hasStr(m.body, "beaconType") && hasStr(m.body, "signalBytesHex");
1692
+ }
1693
+ function isValidationAckMessage(m) {
1694
+ return m.type === VALIDATION_ACK && hasStr(m.body, "cohortId") && hasBool(m.body, "approved");
1695
+ }
1696
+ function isAuthorizationRequestMessage(m) {
1697
+ return m.type === AUTHORIZATION_REQUEST && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasStr(m.body, "pendingTx") && hasStr(m.body, "prevOutScriptHex") && hasStr(m.body, "prevOutValue");
1698
+ }
1699
+ function isNonceContributionMessage(m) {
1700
+ return m.type === NONCE_CONTRIBUTION && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "nonceContribution");
1701
+ }
1702
+ function isAggregatedNonceMessage(m) {
1703
+ return m.type === AGGREGATED_NONCE && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "aggregatedNonce");
1704
+ }
1705
+ function isSignatureAuthorizationMessage(m) {
1706
+ return m.type === SIGNATURE_AUTHORIZATION && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "partialSignature");
1707
+ }
1708
+
1394
1709
  // src/core/aggregation/messages/guards.ts
1395
1710
  var KEYGEN_VALUES = /* @__PURE__ */ new Set([
1396
1711
  COHORT_ADVERT,
@@ -1423,20 +1738,20 @@ function isSignMessageType(type) {
1423
1738
  }
1424
1739
 
1425
1740
  // src/core/aggregation/transport/error.ts
1426
- var import_common4 = require("@did-btcr2/common");
1427
- var TransportError = class extends import_common4.MethodError {
1741
+ var import_common6 = require("@did-btcr2/common");
1742
+ var TransportError = class extends import_common6.MethodError {
1428
1743
  constructor(message, type = "TransportError", data) {
1429
1744
  super(message, type, data);
1430
1745
  }
1431
1746
  };
1432
- var TransportAdapterError = class extends import_common4.MethodError {
1747
+ var TransportAdapterError = class extends import_common6.MethodError {
1433
1748
  constructor(message, type = "TransportAdapterError", data) {
1434
1749
  super(message, type, data);
1435
1750
  }
1436
1751
  };
1437
1752
 
1438
1753
  // src/core/aggregation/transport/factory.ts
1439
- var import_common5 = require("@did-btcr2/common");
1754
+ var import_common7 = require("@did-btcr2/common");
1440
1755
 
1441
1756
  // src/core/aggregation/transport/nostr.ts
1442
1757
  var import_keypair = require("@did-btcr2/keypair");
@@ -1449,6 +1764,7 @@ var DEFAULT_NOSTR_RELAYS = [
1449
1764
  "wss://relay.snort.social",
1450
1765
  "wss://nostr-pub.wellorder.net"
1451
1766
  ];
1767
+ var DEFAULT_BROADCAST_LOOKBACK_MS = 5 * 60 * 1e3;
1452
1768
  var NostrTransport = class _NostrTransport {
1453
1769
  name = "nostr";
1454
1770
  pool;
@@ -1456,8 +1772,12 @@ var NostrTransport = class _NostrTransport {
1456
1772
  #actors = /* @__PURE__ */ new Map();
1457
1773
  #peerRegistry = /* @__PURE__ */ new Map();
1458
1774
  #started = false;
1775
+ #logger;
1776
+ #broadcastLookbackMs;
1459
1777
  constructor(config) {
1460
1778
  this.#relays = config?.relays ?? DEFAULT_NOSTR_RELAYS;
1779
+ this.#logger = config?.logger ?? CONSOLE_LOGGER;
1780
+ this.#broadcastLookbackMs = config?.broadcastLookbackMs ?? DEFAULT_BROADCAST_LOOKBACK_MS;
1461
1781
  }
1462
1782
  /**
1463
1783
  * Registers an actor (DID + keys) to send/receive messages with.
@@ -1468,19 +1788,68 @@ var NostrTransport = class _NostrTransport {
1468
1788
  * @example
1469
1789
  * const transport = new NostrTransport();
1470
1790
  * const keys = SchnorrKeyPair.generate();
1471
- * transport.registerActor('did:btcr2:...', keys);
1791
+ * const did = DidBtcr2.create(keys.publicKey.compressed, { idType: 'KEY', network: 'mutinynet' });
1792
+ * transport.registerActor(did, keys);
1472
1793
  * transport.start();
1473
1794
  */
1474
1795
  registerActor(did, keys) {
1475
- const entry = { keys, handlers: /* @__PURE__ */ new Map() };
1796
+ const entry = { keys, handlers: /* @__PURE__ */ new Map(), subscriptions: [] };
1476
1797
  this.#actors.set(did, entry);
1477
1798
  if (this.#started && this.pool) {
1478
1799
  this.#subscribeDirected(did, entry);
1479
1800
  }
1480
1801
  }
1802
+ /**
1803
+ * Detach an actor: drop its handlers, close its relay subscriptions, and remove its keys + peer
1804
+ * mapping. Idempotent.
1805
+ * @param {string} did - The DID of the actor to unregister.
1806
+ * @returns {void}
1807
+ * @throws {TransportAdapterError} If the transport is not started or if the pool is unavailable.
1808
+ * @example
1809
+ * transport.unregisterActor(did);
1810
+ */
1811
+ unregisterActor(did) {
1812
+ const entry = this.#actors.get(did);
1813
+ if (!entry) return;
1814
+ for (const sub of entry.subscriptions) {
1815
+ try {
1816
+ sub.close();
1817
+ } catch (err) {
1818
+ this.#logger.debug(`Error closing subscription for ${did}:`, err);
1819
+ }
1820
+ }
1821
+ entry.subscriptions.length = 0;
1822
+ entry.handlers.clear();
1823
+ this.#actors.delete(did);
1824
+ this.#peerRegistry.delete(did);
1825
+ }
1826
+ /**
1827
+ * Remove a single (actor, messageType) handler. Idempotent.
1828
+ * @param {string} actorDid - The DID of the actor.
1829
+ * @param {string} messageType - The type of message to unregister the handler for.
1830
+ * @returns {void}
1831
+ * @example
1832
+ * transport.unregisterMessageHandler(actorDid, messageType);
1833
+ */
1834
+ unregisterMessageHandler(actorDid, messageType) {
1835
+ const actor = this.#actors.get(actorDid);
1836
+ if (!actor) return;
1837
+ actor.handlers.delete(messageType);
1838
+ }
1839
+ /**
1840
+ * Gets the public key for a registered actor by their DID.
1841
+ * @param {string} did - The DID of the registered actor to get the public key for.
1842
+ * @returns {Uint8Array | undefined} The compressed public key bytes for the actor's DID, or
1843
+ * undefined if the DID is not registered.
1844
+ */
1481
1845
  getActorPk(did) {
1482
1846
  return this.#actors.get(did)?.keys.publicKey.compressed;
1483
1847
  }
1848
+ /**
1849
+ * Registers a peer's communication public key for encrypted messages.
1850
+ * @param {string} did - The DID of the peer to register.
1851
+ * @param {Uint8Array} communicationPk - The compressed secp256k1 public key bytes for the peer.
1852
+ */
1484
1853
  registerPeer(did, communicationPk) {
1485
1854
  try {
1486
1855
  new import_keypair.CompressedSecp256k1PublicKey(communicationPk);
@@ -1493,9 +1862,25 @@ var NostrTransport = class _NostrTransport {
1493
1862
  }
1494
1863
  this.#peerRegistry.set(did, communicationPk);
1495
1864
  }
1865
+ /**
1866
+ * Gets the registered communication public key for a peer by their DID.
1867
+ * @param {string} did - The DID of the peer to get the communication public key for.
1868
+ * @returns {Uint8Array | undefined} The compressed secp256k1 public key bytes for the peer, or
1869
+ * undefined if the peer is not registered.
1870
+ */
1496
1871
  getPeerPk(did) {
1497
1872
  return this.#peerRegistry.get(did);
1498
1873
  }
1874
+ /**
1875
+ * Registers a message handler function for a specific actor and message type. The handler will be called
1876
+ * when a message of the specified type is received for the actor's DID. The transport must have been
1877
+ * started for handlers to be invoked. If the transport is already started, the handler will be registered
1878
+ * immediately; otherwise, it will be registered when the transport starts and the actor's subscription is created.
1879
+ * @param {string} actorDid - The DID of the actor to register the message handler for.
1880
+ * @param {string} messageType - The type of message to handle.
1881
+ * @param {MessageHandler} handler - The function to handle incoming messages of the specified type.
1882
+ * @throws {TransportAdapterError} If the actor DID is not registered or if the handler is invalid.
1883
+ */
1499
1884
  registerMessageHandler(actorDid, messageType, handler) {
1500
1885
  const actor = this.#actors.get(actorDid);
1501
1886
  if (!actor) {
@@ -1507,21 +1892,48 @@ var NostrTransport = class _NostrTransport {
1507
1892
  }
1508
1893
  actor.handlers.set(messageType, handler);
1509
1894
  }
1895
+ /**
1896
+ * Starts the transport by connecting to the configured Nostr relays and setting up subscriptions
1897
+ * for all registered actors. This method must be called after registering actors via registerActor()
1898
+ * and before sending or receiving messages. The transport will subscribe to broadcast events (kind 1)
1899
+ * for cohort adverts and directed events (kinds 1 and 1059) for each registered actor based on their
1900
+ * public keys. Incoming events are processed and dispatched to the appropriate handlers based on
1901
+ * message type. If the transport is already started, this method has no effect.
1902
+ * @returns {NostrTransport}
1903
+ */
1510
1904
  start() {
1511
1905
  if (this.#started) return this;
1512
1906
  this.#started = true;
1513
1907
  this.pool = new import_pool.SimplePool();
1514
- const since = Math.floor(Date.now() / 1e3);
1515
- this.pool.subscribeMany(this.#relays, { kinds: [1], "#t": [COHORT_ADVERT], since }, {
1516
- onclose: (reasons) => console.debug("Nostr broadcast subscription closed", reasons),
1908
+ const broadcastFilter = {
1909
+ kinds: [1],
1910
+ "#t": [COHORT_ADVERT]
1911
+ };
1912
+ if (this.#broadcastLookbackMs > 0) {
1913
+ broadcastFilter.since = Math.floor((Date.now() - this.#broadcastLookbackMs) / 1e3);
1914
+ }
1915
+ this.pool.subscribeMany(this.#relays, broadcastFilter, {
1916
+ onclose: (reasons) => this.#logger.debug("Nostr broadcast subscription closed", reasons),
1517
1917
  onevent: this.#handleBroadcastEvent.bind(this)
1518
1918
  });
1519
1919
  for (const [did, entry] of this.#actors) {
1520
1920
  this.#subscribeDirected(did, entry);
1521
1921
  }
1522
- console.info(`NostrTransport started, listening on ${this.#relays.length} relay(s)`);
1922
+ this.#logger.info(`NostrTransport started, listening on ${this.#relays.length} relay(s)`);
1523
1923
  return this;
1524
1924
  }
1925
+ /**
1926
+ * Sends a message by publishing a Nostr event to the configured relays. The message is serialized
1927
+ * as JSON and included in the event content.
1928
+ * @param {BaseMessage} message - The aggregation message to send. Must include a valid `type` property.
1929
+ * @param {Did} sender - The DID of the registered actor sending the message. Must have been
1930
+ * registered via registerActor().
1931
+ * @param {Did} [to] - Optional recipient DID for directed messages. Required for encrypted message
1932
+ * types. If provided, must have been registered via registerPeer().
1933
+ * @returns {Promise<void>} Resolves when the message has been published to the relays. Note that
1934
+ * publication is best-effort: the method will resolve as long as at least one relay accepts the
1935
+ * event, even if others reject it.
1936
+ */
1525
1937
  async sendMessage(message, sender, to) {
1526
1938
  const type = message.type;
1527
1939
  if (!type) {
@@ -1558,7 +1970,7 @@ var NostrTransport = class _NostrTransport {
1558
1970
  tags,
1559
1971
  content: JSON.stringify(message, _NostrTransport.#jsonReplacer)
1560
1972
  }, senderKeys.secretKey.bytes);
1561
- console.debug(`Publishing kind 1 [${type}]`);
1973
+ this.#logger.debug(`Publishing kind 1 [${type}]`);
1562
1974
  await this.#publishToRelays(event);
1563
1975
  return;
1564
1976
  }
@@ -1590,25 +2002,70 @@ var NostrTransport = class _NostrTransport {
1590
2002
  tags,
1591
2003
  content
1592
2004
  }, senderKeys.secretKey.bytes);
1593
- console.debug(`Publishing kind 1059 [${type}]`);
2005
+ this.#logger.debug(`Publishing kind 1059 [${type}]`);
1594
2006
  await this.#publishToRelays(event);
1595
2007
  return;
1596
2008
  }
1597
- console.warn(`Unsupported message type: ${type}`);
2009
+ this.#logger.warn(`Unsupported message type: ${type}`);
1598
2010
  }
2011
+ /**
2012
+ * Publish the message once immediately and then re-publish on an interval
2013
+ * until the returned stop function is invoked.
2014
+ *
2015
+ * Useful for broadcast messages (COHORT_ADVERT) on relays that don't
2016
+ * backfill historical events to late subscribers: republishing gives late
2017
+ * joiners a window to discover the message without requiring protocol
2018
+ * changes. Relay rate-limit / publish failures inside the interval are
2019
+ * caught and logged rather than propagated — the caller should stop the
2020
+ * repeater once the protocol condition is satisfied.
2021
+ */
2022
+ publishRepeating(message, sender, intervalMs, recipient) {
2023
+ let stopped = false;
2024
+ void this.sendMessage(message, sender, recipient).catch((err) => {
2025
+ this.#logger.debug("publishRepeating first send failed:", err);
2026
+ });
2027
+ const timer = setInterval(() => {
2028
+ if (stopped) return;
2029
+ void this.sendMessage(message, sender, recipient).catch((err) => {
2030
+ this.#logger.debug("publishRepeating retry failed:", err);
2031
+ });
2032
+ }, intervalMs);
2033
+ return () => {
2034
+ if (stopped) return;
2035
+ stopped = true;
2036
+ clearInterval(timer);
2037
+ };
2038
+ }
2039
+ /**
2040
+ * Creates a directed subscription for the given actor, filtering for messages that match the
2041
+ * actor's public key. Messages received on this subscription are dispatched to the actor's
2042
+ * registered handlers based on message type.
2043
+ * @param {string} did - The DID of the actor to create the subscription for.
2044
+ * @param {ActorEntry} entry - The actor's registration entry containing keys and handlers.
2045
+ * @returns {void}
2046
+ * @throws {TransportAdapterError} If the transport is not started or if the pool is unavailable.
2047
+ */
1599
2048
  #subscribeDirected(did, entry) {
1600
2049
  if (!this.pool) return;
1601
2050
  const pkHex = (0, import_utils4.bytesToHex)(entry.keys.publicKey.x);
1602
- const since = Math.floor(Date.now() / 1e3);
1603
- this.pool.subscribeMany(this.#relays, { kinds: [1, 1059], "#p": [pkHex], since }, {
1604
- onclose: (reasons) => console.debug(`Nostr directed subscription closed for ${did}`, reasons),
2051
+ const sub = this.pool.subscribeMany(this.#relays, { kinds: [1, 1059], "#p": [pkHex] }, {
2052
+ onclose: (reasons) => this.#logger.debug(`Nostr directed subscription closed for ${did}`, reasons),
1605
2053
  onevent: this.#makeActorEventHandler(did)
1606
2054
  });
2055
+ entry.subscriptions.push(sub);
1607
2056
  }
2057
+ /**
2058
+ * Creates an event handler function for a specific actor that processes incoming events, decrypts
2059
+ * if necessary, and dispatches messages to the actor's registered handlers based on message type.
2060
+ * @param {string} actorDid - The DID of the actor to create the event handler for.
2061
+ * @returns {(event: Event) => Promise<void>} An asynchronous event handler function that
2062
+ * processes incoming events for the specified actor.
2063
+ */
1608
2064
  #makeActorEventHandler(actorDid) {
1609
2065
  return async (event) => {
1610
2066
  const actor = this.#actors.get(actorDid);
1611
2067
  if (!actor) return;
2068
+ if (event.pubkey === (0, import_utils4.bytesToHex)(actor.keys.publicKey.x)) return;
1612
2069
  let message;
1613
2070
  try {
1614
2071
  if (event.kind === 1) {
@@ -1624,19 +2081,29 @@ var NostrTransport = class _NostrTransport {
1624
2081
  return;
1625
2082
  }
1626
2083
  } catch (err) {
1627
- console.debug(`Failed to parse event ${event.id} for ${actorDid}:`, err);
2084
+ this.#logger.debug(`Failed to parse event ${event.id} for ${actorDid}:`, err);
1628
2085
  return;
1629
2086
  }
1630
2087
  this.#dispatchMessage(message, actor);
1631
2088
  };
1632
2089
  }
2090
+ /**
2091
+ * Handles incoming broadcast events (kind 1) by parsing the event content, validating it as an
2092
+ * aggregation message, and dispatching it to all registered actors that have handlers for the
2093
+ * message type. This is used for COHORT_ADVERT messages that need to be received by all actors
2094
+ * regardless of DID.
2095
+ * @param {Event} event - The Nostr event to handle, expected to be a kind 1 broadcast containing
2096
+ * a COHORT_ADVERT message. The event content is parsed and dispatched to all registered actors
2097
+ * that have handlers for the
2098
+ * @returns
2099
+ */
1633
2100
  async #handleBroadcastEvent(event) {
1634
2101
  if (event.kind !== 1) return;
1635
2102
  let message;
1636
2103
  try {
1637
2104
  message = JSON.parse(event.content, _NostrTransport.#jsonReviver);
1638
2105
  } catch (err) {
1639
- console.debug(`Failed to parse broadcast event ${event.id}:`, err);
2106
+ this.#logger.debug(`Failed to parse broadcast event ${event.id}:`, err);
1640
2107
  return;
1641
2108
  }
1642
2109
  if (message.body && typeof message.body === "object") {
@@ -1649,6 +2116,18 @@ var NostrTransport = class _NostrTransport {
1649
2116
  if (handler) await handler(message);
1650
2117
  }
1651
2118
  }
2119
+ /**
2120
+ * Dispatches a parsed message to the appropriate handler of a registered actor based on message type.
2121
+ * The message is expected to have already been parsed from the Nostr event content and validated as
2122
+ * an aggregation message. If the message has a body, its properties are merged into the top-level
2123
+ * message object for easier handler access. The message is then dispatched to the handler registered
2124
+ * for its type, if one exists.
2125
+ * @param {Record<string, unknown>} message - The message object parsed from a Nostr event, expected to
2126
+ * @param {ActorEntry} actor - The registered actor entry containing keys and handlers to dispatch the message to.
2127
+ * @returns {void}
2128
+ * @throws {TransportAdapterError} If the message type is unsupported or if no handler is registered
2129
+ * for the message type.
2130
+ */
1652
2131
  #dispatchMessage(message, actor) {
1653
2132
  if (message.body && typeof message.body === "object") {
1654
2133
  message = { ...message, ...message.body };
@@ -1658,6 +2137,16 @@ var NostrTransport = class _NostrTransport {
1658
2137
  const handler = actor.handlers.get(messageType);
1659
2138
  if (handler) handler(message);
1660
2139
  }
2140
+ /**
2141
+ * Publishes a Nostr event to the configured relays and handles the results. The method waits for all
2142
+ * relay promises to settle and checks how many accepted or rejected the event. If all relays reject the event,
2143
+ * an error is thrown. Otherwise, the method completes successfully even if some relays rejected the event,
2144
+ * as long as at least one relay accepted it. Relay rejections are logged for debugging purposes.
2145
+ * @param {Event} event - The Nostr event to publish to the configured relays. The event should already
2146
+ * @returns {Promise<void>} A promise that resolves if the event was accepted by at least one relay, or rejects
2147
+ * with a TransportAdapterError if all relays rejected the event.
2148
+ * @throws {TransportAdapterError} If the pool is not initialized or if all relays reject the event.
2149
+ */
1661
2150
  async #publishToRelays(event) {
1662
2151
  const relayPromises = this.pool?.publish(this.#relays, event);
1663
2152
  if (!relayPromises?.length) return;
@@ -1665,7 +2154,7 @@ var NostrTransport = class _NostrTransport {
1665
2154
  const accepted = results.filter((r) => r.status === "fulfilled").length;
1666
2155
  const rejected = results.filter((r) => r.status === "rejected");
1667
2156
  for (const r of rejected) {
1668
- console.debug(`Relay rejected event ${event.id}: ${r.reason}`);
2157
+ this.#logger.debug(`Relay rejected event ${event.id}: ${r.reason}`);
1669
2158
  }
1670
2159
  if (accepted === 0) {
1671
2160
  throw new TransportAdapterError(
@@ -1675,12 +2164,36 @@ var NostrTransport = class _NostrTransport {
1675
2164
  );
1676
2165
  }
1677
2166
  }
2167
+ /**
2168
+ * Custom JSON replacer to handle serialization of Uint8Array values as hex strings in message
2169
+ * content. This allows messages containing binary data (e.g. public keys, signatures) to be correctly
2170
+ * serialized to JSON for Nostr event content. The replacer checks if a value is a Uint8Array and, if so,
2171
+ * converts it to a hex string wrapped in an object with a __bytes property. The corresponding reviver
2172
+ * can then convert this back to a Uint8Array when parsing the message content from the event.
2173
+ * @param {string} _key - The key of the property being processed.
2174
+ * @param {unknown} value - The value to check if the message type is valid.
2175
+ * @returns {unknown} The transformed value for JSON serialization. If the value is a Uint8Array,
2176
+ * it returns an object with a __bytes property containing the hex string. Otherwise, it returns
2177
+ * the value unchanged.
2178
+ */
1678
2179
  static #jsonReplacer(_key, value) {
1679
2180
  if (value instanceof Uint8Array) {
1680
2181
  return { __bytes: (0, import_utils4.bytesToHex)(value) };
1681
2182
  }
1682
2183
  return value;
1683
2184
  }
2185
+ /**
2186
+ * Custom JSON reviver to handle deserialization of hex strings back into Uint8Array values in message
2187
+ * content. This complements the custom replacer used during serialization, allowing messages that contain
2188
+ * binary data (e.g. public keys, signatures) to be correctly reconstructed when parsing JSON from
2189
+ * Nostr event content. The reviver checks if a value is an object with a __bytes property and,
2190
+ * if so, converts the hex string back into a Uint8Array. Otherwise, it returns the value unchanged.
2191
+ * @param {string} _key - The key of the property being processed.
2192
+ * @param {unknown} value - The value to check if it is an object containing a __bytes property for
2193
+ * hex string conversion.
2194
+ * @returns {unknown} The transformed value for JSON deserialization. If the value is an object
2195
+ * with a __bytes property, it returns a Uint8Array. Otherwise, it returns the value unchanged.
2196
+ */
1684
2197
  static #jsonReviver(_key, value) {
1685
2198
  if (value && typeof value === "object" && "__bytes" in value) {
1686
2199
  return (0, import_utils4.hexToBytes)(value.__bytes);
@@ -1696,7 +2209,7 @@ var TransportFactory = class {
1696
2209
  case "nostr":
1697
2210
  return new NostrTransport({ relays: config.relays });
1698
2211
  case "didcomm":
1699
- throw new import_common5.NotImplementedError("DIDComm transport not implemented yet.");
2212
+ throw new import_common7.NotImplementedError("DIDComm transport not implemented yet.");
1700
2213
  default:
1701
2214
  throw new TransportError(
1702
2215
  `Invalid transport type: ${config.type}`,
@@ -1708,29 +2221,38 @@ var TransportFactory = class {
1708
2221
  };
1709
2222
 
1710
2223
  // src/core/aggregation/transport/didcomm.ts
1711
- var import_common6 = require("@did-btcr2/common");
2224
+ var import_common8 = require("@did-btcr2/common");
1712
2225
  var DidCommTransport = class {
1713
2226
  name = "didcomm";
1714
2227
  start() {
1715
- throw new import_common6.NotImplementedError("DidCommTransport not implemented. Use NostrTransport instead.");
2228
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented. Use NostrTransport instead.");
1716
2229
  }
1717
2230
  registerActor(_did, _keys) {
1718
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
2231
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
1719
2232
  }
1720
2233
  getActorPk(_did) {
1721
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
2234
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
1722
2235
  }
1723
2236
  registerPeer(_did, _communicationPk) {
1724
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
2237
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
1725
2238
  }
1726
2239
  getPeerPk(_did) {
1727
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
2240
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
1728
2241
  }
1729
2242
  registerMessageHandler(_actorDid, _messageType, _handler) {
1730
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
2243
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
2244
+ }
2245
+ unregisterMessageHandler(_actorDid, _messageType) {
2246
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
2247
+ }
2248
+ unregisterActor(_did) {
2249
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
1731
2250
  }
1732
2251
  async sendMessage(_message, _sender, _recipient) {
1733
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
2252
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
2253
+ }
2254
+ publishRepeating(_message, _sender, _intervalMs, _recipient) {
2255
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
1734
2256
  }
1735
2257
  };
1736
2258
 
@@ -1789,7 +2311,8 @@ var TypedEventEmitter = class {
1789
2311
  };
1790
2312
 
1791
2313
  // src/core/aggregation/runner/service-runner.ts
1792
- var AggregationServiceRunner = class extends TypedEventEmitter {
2314
+ var DEFAULT_ADVERT_REPEAT_INTERVAL_MS = 6e4;
2315
+ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEventEmitter {
1793
2316
  /** Direct access to the underlying state machine for advanced use. */
1794
2317
  session;
1795
2318
  #transport;
@@ -1798,11 +2321,26 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1798
2321
  #onOptInReceived;
1799
2322
  #onReadyToFinalize;
1800
2323
  #onProvideTxData;
2324
+ #cohortTtlMs;
2325
+ #phaseTimeoutMs;
2326
+ #advertRepeatIntervalMs;
1801
2327
  #cohortId;
1802
2328
  #handlersRegistered = false;
1803
2329
  #stopped = false;
2330
+ /**
2331
+ * Guard against the async race where two concurrent #handleOptIn invocations
2332
+ * both pass the `participants.length >= minParticipants` check before either
2333
+ * mutates the cohort phase. Set synchronously before any `await` so subsequent
2334
+ * handlers observe it on their next resumption.
2335
+ */
2336
+ #finalizing = false;
1804
2337
  #resolveRun;
1805
2338
  #rejectRun;
2339
+ #cohortTtlTimer;
2340
+ #phaseTimer;
2341
+ #lastObservedPhase;
2342
+ /** Stop handle for the repeating COHORT_ADVERT publish loop. */
2343
+ #stopAdvertRepeat;
1806
2344
  constructor(options) {
1807
2345
  super();
1808
2346
  this.#transport = options.transport;
@@ -1813,7 +2351,25 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1813
2351
  finalize: acceptedCount >= minRequired
1814
2352
  }));
1815
2353
  this.#onProvideTxData = options.onProvideTxData;
1816
- this.session = new AggregationService({ did: options.did, keys: options.keys });
2354
+ this.#cohortTtlMs = options.cohortTtlMs;
2355
+ this.#phaseTimeoutMs = options.phaseTimeoutMs;
2356
+ this.#advertRepeatIntervalMs = options.advertRepeatIntervalMs ?? DEFAULT_ADVERT_REPEAT_INTERVAL_MS;
2357
+ this.session = new AggregationService({
2358
+ did: options.did,
2359
+ keys: options.keys,
2360
+ maxUpdateSizeBytes: options.maxUpdateSizeBytes
2361
+ });
2362
+ }
2363
+ /**
2364
+ * Drain any silent rejections the state machine recorded during the most
2365
+ * recent receive() and surface them as `message-rejected` events. Safe to
2366
+ * call even before a cohortId is assigned.
2367
+ */
2368
+ #drainRejections() {
2369
+ if (!this.#cohortId) return;
2370
+ for (const r of this.session.drainRejections(this.#cohortId)) {
2371
+ this.emit("message-rejected", { cohortId: this.#cohortId, ...r });
2372
+ }
1817
2373
  }
1818
2374
  /**
1819
2375
  * Run the protocol to completion. Resolves with the final aggregation result
@@ -1828,22 +2384,103 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1828
2384
  try {
1829
2385
  this.#registerHandlers();
1830
2386
  this.#cohortId = this.session.createCohort(this.#config);
2387
+ this.#startTimers();
1831
2388
  const advertMsgs = this.session.advertise(this.#cohortId);
2389
+ this.#onPhaseMaybeChanged();
1832
2390
  this.emit("cohort-advertised", { cohortId: this.#cohortId });
1833
- this.#sendAll(advertMsgs).catch((err) => this.#fail(err));
2391
+ if (this.#advertRepeatIntervalMs > 0) {
2392
+ this.#startAdvertRepeat(advertMsgs);
2393
+ } else {
2394
+ this.#sendAll(advertMsgs).catch((err) => this.#fail(err));
2395
+ }
1834
2396
  } catch (err) {
1835
2397
  this.#fail(err);
1836
2398
  }
1837
2399
  });
1838
2400
  }
1839
2401
  /**
1840
- * Stop the runner early. Cleans up internal state.
1841
- * Note: does not unregister transport handlers (the transport interface
1842
- * does not currently expose unregister).
2402
+ * Begin publishing the cohort advert immediately and on a repeating interval
2403
+ * until {@link #stopAdvertRepeating} is called. Each advert is broadcast
2404
+ * (no recipient) via the transport's `publishRepeating` primitive.
2405
+ */
2406
+ #startAdvertRepeat(advertMsgs) {
2407
+ const stops = [];
2408
+ for (const msg of advertMsgs) {
2409
+ stops.push(this.#transport.publishRepeating(msg, this.#did, this.#advertRepeatIntervalMs));
2410
+ }
2411
+ this.#stopAdvertRepeat = () => {
2412
+ for (const stop of stops) {
2413
+ try {
2414
+ stop();
2415
+ } catch {
2416
+ }
2417
+ }
2418
+ };
2419
+ }
2420
+ /** Stop the advert republish loop. Idempotent. */
2421
+ #stopAdvertRepeating() {
2422
+ if (!this.#stopAdvertRepeat) return;
2423
+ const stop = this.#stopAdvertRepeat;
2424
+ this.#stopAdvertRepeat = void 0;
2425
+ stop();
2426
+ }
2427
+ /** Schedule cohort TTL + phase timeout at the start of a run. */
2428
+ #startTimers() {
2429
+ if (this.#cohortTtlMs !== void 0) {
2430
+ this.#cohortTtlTimer = setTimeout(() => {
2431
+ const reason = `Cohort ${this.#cohortId ?? ""} exceeded TTL of ${this.#cohortTtlMs}ms`;
2432
+ this.emit("cohort-failed", { cohortId: this.#cohortId ?? "", reason });
2433
+ this.#fail(new Error(reason));
2434
+ }, this.#cohortTtlMs);
2435
+ }
2436
+ this.#resetPhaseTimer();
2437
+ }
2438
+ /** Reset the per-phase stall timer. Called when a phase transition is observed. */
2439
+ #resetPhaseTimer() {
2440
+ if (this.#phaseTimer) clearTimeout(this.#phaseTimer);
2441
+ this.#phaseTimer = void 0;
2442
+ if (this.#phaseTimeoutMs === void 0) return;
2443
+ this.#phaseTimer = setTimeout(() => {
2444
+ const reason = `Cohort ${this.#cohortId ?? ""} stalled in phase ${this.#lastObservedPhase ?? "?"} for ${this.#phaseTimeoutMs}ms`;
2445
+ this.emit("cohort-failed", { cohortId: this.#cohortId ?? "", reason });
2446
+ this.#fail(new Error(reason));
2447
+ }, this.#phaseTimeoutMs);
2448
+ }
2449
+ /** Detect a phase change since the last observation and reset the phase timer. */
2450
+ #onPhaseMaybeChanged() {
2451
+ if (!this.#cohortId) return;
2452
+ const phase = this.session.getCohortPhase(this.#cohortId);
2453
+ if (phase !== this.#lastObservedPhase) {
2454
+ this.#lastObservedPhase = phase;
2455
+ this.#resetPhaseTimer();
2456
+ }
2457
+ }
2458
+ /** Clear both timers. Called on successful completion, stop(), and #fail. */
2459
+ #clearTimers() {
2460
+ if (this.#cohortTtlTimer) clearTimeout(this.#cohortTtlTimer);
2461
+ if (this.#phaseTimer) clearTimeout(this.#phaseTimer);
2462
+ this.#cohortTtlTimer = void 0;
2463
+ this.#phaseTimer = void 0;
2464
+ }
2465
+ /**
2466
+ * Stop the runner early. Marks the runner stopped and detaches transport
2467
+ * handlers so a restart or a new runner doesn't inherit stale dispatch.
1843
2468
  */
1844
2469
  stop() {
1845
2470
  this.#stopped = true;
1846
- }
2471
+ this.#stopAdvertRepeating();
2472
+ this.#clearTimers();
2473
+ this.#unregisterHandlers();
2474
+ if (this.#cohortId) this.session.removeCohort(this.#cohortId);
2475
+ }
2476
+ /** Message types this runner listens for on the transport. */
2477
+ static #HANDLED_MESSAGE_TYPES = [
2478
+ COHORT_OPT_IN,
2479
+ SUBMIT_UPDATE,
2480
+ VALIDATION_ACK,
2481
+ NONCE_CONTRIBUTION,
2482
+ SIGNATURE_AUTHORIZATION
2483
+ ];
1847
2484
  /**
1848
2485
  * Internal: handler registration with the transport. Idempotent.
1849
2486
  */
@@ -1856,6 +2493,14 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1856
2493
  this.#transport.registerMessageHandler(this.#did, NONCE_CONTRIBUTION, this.#handleNonceContribution.bind(this));
1857
2494
  this.#transport.registerMessageHandler(this.#did, SIGNATURE_AUTHORIZATION, this.#handleSignatureAuthorization.bind(this));
1858
2495
  }
2496
+ /** Internal: detach from the transport. Safe to call repeatedly. */
2497
+ #unregisterHandlers() {
2498
+ if (!this.#handlersRegistered) return;
2499
+ this.#handlersRegistered = false;
2500
+ for (const type of _AggregationServiceRunner.#HANDLED_MESSAGE_TYPES) {
2501
+ this.#transport.unregisterMessageHandler(this.#did, type);
2502
+ }
2503
+ }
1859
2504
  /**
1860
2505
  * Internal: message handlers for each protocol step. Each handler:
1861
2506
  * 1) feeds the message into the state machine via session.receive()
@@ -1872,6 +2517,8 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1872
2517
  if (this.#stopped) return;
1873
2518
  try {
1874
2519
  this.session.receive(msg);
2520
+ this.#drainRejections();
2521
+ this.#onPhaseMaybeChanged();
1875
2522
  const optIn = this.session.pendingOptIns(this.#cohortId).get(msg.from);
1876
2523
  if (!optIn) return;
1877
2524
  this.emit("opt-in-received", optIn);
@@ -1883,19 +2530,23 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1883
2530
  await this.#sendAll(this.session.acceptParticipant(this.#cohortId, msg.from));
1884
2531
  this.emit("participant-accepted", { participantDid: msg.from });
1885
2532
  const cohort = this.session.getCohort(this.#cohortId);
1886
- if (cohort.participants.length >= this.#config.minParticipants) {
2533
+ if (cohort.participants.length >= this.#config.minParticipants && !this.#finalizing) {
2534
+ this.#finalizing = true;
1887
2535
  const finalizeDecision = await this.#onReadyToFinalize({
1888
2536
  acceptedCount: cohort.participants.length,
1889
2537
  minRequired: this.#config.minParticipants
1890
2538
  });
1891
- if (finalizeDecision.finalize) {
1892
- const readyMsgs = this.session.finalizeKeygen(this.#cohortId);
1893
- this.emit("keygen-complete", {
1894
- cohortId: this.#cohortId,
1895
- beaconAddress: cohort.beaconAddress
1896
- });
1897
- await this.#sendAll(readyMsgs);
2539
+ if (!finalizeDecision.finalize) {
2540
+ this.#finalizing = false;
2541
+ return;
1898
2542
  }
2543
+ const readyMsgs = this.session.finalizeKeygen(this.#cohortId);
2544
+ this.#stopAdvertRepeating();
2545
+ this.emit("keygen-complete", {
2546
+ cohortId: this.#cohortId,
2547
+ beaconAddress: cohort.beaconAddress
2548
+ });
2549
+ await this.#sendAll(readyMsgs);
1899
2550
  }
1900
2551
  } catch (err) {
1901
2552
  this.#fail(err);
@@ -1913,6 +2564,8 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1913
2564
  if (this.#stopped) return;
1914
2565
  try {
1915
2566
  this.session.receive(msg);
2567
+ this.#drainRejections();
2568
+ this.#onPhaseMaybeChanged();
1916
2569
  this.emit("update-received", { participantDid: msg.from });
1917
2570
  if (this.session.getCohortPhase(this.#cohortId) === "UpdatesCollected" /* UpdatesCollected */) {
1918
2571
  const distributeMsgs = this.session.buildAndDistribute(this.#cohortId);
@@ -1935,9 +2588,18 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1935
2588
  if (this.#stopped) return;
1936
2589
  try {
1937
2590
  this.session.receive(msg);
2591
+ this.#drainRejections();
2592
+ this.#onPhaseMaybeChanged();
1938
2593
  const approved = !!msg.body?.approved;
1939
2594
  this.emit("validation-received", { participantDid: msg.from, approved });
1940
- if (this.session.getCohortPhase(this.#cohortId) === "Validated" /* Validated */) {
2595
+ const phase = this.session.getCohortPhase(this.#cohortId);
2596
+ if (phase === "Failed" /* Failed */) {
2597
+ const reason = `Validation rejected by participant ${msg.from}`;
2598
+ this.emit("cohort-failed", { cohortId: this.#cohortId, reason });
2599
+ this.#fail(new Error(reason));
2600
+ return;
2601
+ }
2602
+ if (phase === "Validated" /* Validated */) {
1941
2603
  const cohort = this.session.getCohort(this.#cohortId);
1942
2604
  const txData = await this.#onProvideTxData({
1943
2605
  cohortId: this.#cohortId,
@@ -1965,6 +2627,8 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1965
2627
  if (this.#stopped) return;
1966
2628
  try {
1967
2629
  this.session.receive(msg);
2630
+ this.#drainRejections();
2631
+ this.#onPhaseMaybeChanged();
1968
2632
  this.emit("nonce-received", { participantDid: msg.from });
1969
2633
  if (this.session.getCohortPhase(this.#cohortId) === "NoncesCollected" /* NoncesCollected */) {
1970
2634
  await this.#sendAll(this.session.sendAggregatedNonce(this.#cohortId));
@@ -1985,8 +2649,12 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1985
2649
  if (this.#stopped) return;
1986
2650
  try {
1987
2651
  this.session.receive(msg);
2652
+ this.#drainRejections();
2653
+ this.#onPhaseMaybeChanged();
1988
2654
  const result = this.session.getResult(this.#cohortId);
1989
2655
  if (result) {
2656
+ this.#clearTimers();
2657
+ this.#unregisterHandlers();
1990
2658
  this.emit("signing-complete", result);
1991
2659
  this.#resolveRun?.(result);
1992
2660
  }
@@ -2011,6 +2679,10 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
2011
2679
  * @param {Error} err - The error to handle.
2012
2680
  */
2013
2681
  #fail(err) {
2682
+ this.#stopAdvertRepeating();
2683
+ this.#clearTimers();
2684
+ this.#unregisterHandlers();
2685
+ if (this.#cohortId) this.session.removeCohort(this.#cohortId);
2014
2686
  this.emit("error", err);
2015
2687
  this.#rejectRun?.(err);
2016
2688
  }
@@ -2045,9 +2717,27 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
2045
2717
  async start() {
2046
2718
  this.#registerHandlers();
2047
2719
  }
2048
- /** Stop the runner. Does not unregister transport handlers. */
2720
+ /** Stop the runner and detach transport handlers. Safe to call repeatedly. */
2049
2721
  stop() {
2050
2722
  this.#stopped = true;
2723
+ this.#unregisterHandlers();
2724
+ }
2725
+ /** Message types this runner listens for on the transport. */
2726
+ static #HANDLED_MESSAGE_TYPES = [
2727
+ COHORT_ADVERT,
2728
+ COHORT_OPT_IN_ACCEPT,
2729
+ COHORT_READY,
2730
+ DISTRIBUTE_AGGREGATED_DATA,
2731
+ AUTHORIZATION_REQUEST,
2732
+ AGGREGATED_NONCE
2733
+ ];
2734
+ /** Internal: detach from the transport. Safe to call repeatedly. */
2735
+ #unregisterHandlers() {
2736
+ if (!this.#handlersRegistered) return;
2737
+ this.#handlersRegistered = false;
2738
+ for (const type of _AggregationParticipantRunner.#HANDLED_MESSAGE_TYPES) {
2739
+ this.#transport.unregisterMessageHandler(this.#did, type);
2740
+ }
2051
2741
  }
2052
2742
  /**
2053
2743
  * Single-shot helper: start, join the first cohort that passes `shouldJoin`,
@@ -2212,7 +2902,14 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
2212
2902
  if (this.session.getCohortPhase(cohortId) === "Complete" /* Complete */) {
2213
2903
  const info = this.session.joinedCohorts.get(cohortId);
2214
2904
  if (info) {
2215
- this.emit("cohort-complete", { cohortId, beaconAddress: info.beaconAddress });
2905
+ const validation = this.session.pendingValidations.get(cohortId);
2906
+ this.emit("cohort-complete", {
2907
+ cohortId,
2908
+ beaconAddress: info.beaconAddress,
2909
+ beaconType: validation?.beaconType ?? "",
2910
+ casAnnouncement: validation?.casAnnouncement,
2911
+ smtProof: validation?.smtProof
2912
+ });
2216
2913
  }
2217
2914
  }
2218
2915
  } catch (err) {
@@ -2234,33 +2931,33 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
2234
2931
  };
2235
2932
 
2236
2933
  // src/core/beacon/beacon.ts
2237
- var import_keypair2 = require("@did-btcr2/keypair");
2934
+ var import_secp256k12 = require("@noble/secp256k1");
2238
2935
  var import_utils5 = require("@noble/hashes/utils");
2239
- var import_bitcoinjs_lib4 = require("bitcoinjs-lib");
2936
+ var import_btc_signer4 = require("@scure/btc-signer");
2240
2937
 
2241
2938
  // src/core/beacon/error.ts
2242
- var import_common7 = require("@did-btcr2/common");
2243
- var BeaconError = class extends import_common7.MethodError {
2939
+ var import_common9 = require("@did-btcr2/common");
2940
+ var BeaconError = class extends import_common9.MethodError {
2244
2941
  constructor(message, type = "BeaconError", data) {
2245
2942
  super(message, type, data);
2246
2943
  }
2247
2944
  };
2248
- var SingletonBeaconError = class extends import_common7.MethodError {
2945
+ var SingletonBeaconError = class extends import_common9.MethodError {
2249
2946
  constructor(message, type = "SingletonBeaconError", data) {
2250
2947
  super(message, type, data);
2251
2948
  }
2252
2949
  };
2253
- var AggregateBeaconError = class extends import_common7.MethodError {
2950
+ var AggregateBeaconError = class extends import_common9.MethodError {
2254
2951
  constructor(message, type = "AggregateBeaconError", data) {
2255
2952
  super(message, type, data);
2256
2953
  }
2257
2954
  };
2258
- var CASBeaconError = class extends import_common7.MethodError {
2955
+ var CASBeaconError = class extends import_common9.MethodError {
2259
2956
  constructor(message, type = "CASBeaconError", data) {
2260
2957
  super(message, type, data);
2261
2958
  }
2262
2959
  };
2263
- var SMTBeaconError = class extends import_common7.MethodError {
2960
+ var SMTBeaconError = class extends import_common9.MethodError {
2264
2961
  constructor(message, type = "SMTBeaconError", data) {
2265
2962
  super(message, type, data);
2266
2963
  }
@@ -2288,6 +2985,62 @@ var StaticFeeEstimator = class {
2288
2985
 
2289
2986
  // src/core/beacon/beacon.ts
2290
2987
  var DEFAULT_FEE_ESTIMATOR = new StaticFeeEstimator(5);
2988
+ var P2TR_BEACON_TX_VSIZE = 140;
2989
+ function opReturnScript(signalBytes) {
2990
+ return import_btc_signer4.Script.encode([import_btc_signer4.OP.RETURN, signalBytes]);
2991
+ }
2992
+ async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
2993
+ const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
2994
+ if (!utxos.length) {
2995
+ throw new BeaconError(
2996
+ "No UTXOs found, please fund address!",
2997
+ "UNFUNDED_BEACON_ADDRESS",
2998
+ { bitcoinAddress }
2999
+ );
3000
+ }
3001
+ const utxo = utxos.sort((a, b) => b.status.block_height - a.status.block_height).shift();
3002
+ if (!utxo) {
3003
+ throw new BeaconError(
3004
+ "Beacon bitcoin address unfunded or utxos unconfirmed.",
3005
+ "UNFUNDED_BEACON_ADDRESS",
3006
+ { bitcoinAddress }
3007
+ );
3008
+ }
3009
+ const prevTxHex = await bitcoin.rest.transaction.getHex(utxo.txid);
3010
+ return { utxo, prevTxBytes: (0, import_utils5.hexToBytes)(prevTxHex) };
3011
+ }
3012
+ async function buildAggregationBeaconTx(opts) {
3013
+ const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
3014
+ const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
3015
+ const tapOut = (0, import_btc_signer4.p2tr)(opts.internalPubkey, void 0, opts.network);
3016
+ const witnessScript = tapOut.script;
3017
+ const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
3018
+ if (BigInt(utxo.value) <= feeSats) {
3019
+ throw new BeaconError(
3020
+ `UTXO value (${utxo.value}) insufficient to cover fee (${feeSats}).`,
3021
+ "INSUFFICIENT_FUNDS",
3022
+ { bitcoinAddress: opts.beaconAddress, utxoValue: utxo.value, fee: feeSats.toString() }
3023
+ );
3024
+ }
3025
+ const tx = new import_btc_signer4.Transaction();
3026
+ tx.addInput({
3027
+ txid: utxo.txid,
3028
+ index: utxo.vout,
3029
+ nonWitnessUtxo: prevTxBytes,
3030
+ witnessUtxo: { amount: BigInt(utxo.value), script: witnessScript },
3031
+ tapInternalKey: opts.internalPubkey
3032
+ });
3033
+ tx.addOutputAddress(opts.beaconAddress, BigInt(utxo.value) - feeSats, opts.network);
3034
+ tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
3035
+ return {
3036
+ tx,
3037
+ prevOutScripts: [witnessScript],
3038
+ prevOutValues: [BigInt(utxo.value)],
3039
+ beaconAddress: opts.beaconAddress,
3040
+ utxo,
3041
+ feeSats
3042
+ };
3043
+ }
2291
3044
  var Beacon = class {
2292
3045
  /**
2293
3046
  * The Beacon service configuration parsed from the DID Document.
@@ -2297,79 +3050,108 @@ var Beacon = class {
2297
3050
  this.service = service;
2298
3051
  }
2299
3052
  /**
2300
- * Shared PSBT construction + signing + broadcast helper used by all beacon types.
2301
- *
2302
- * Steps:
2303
- * 1. Parse the beacon's `serviceEndpoint` (stripping `bitcoin:` prefix) into a Bitcoin address.
2304
- * 2. Query the address for unconfirmed/confirmed UTXOs.
2305
- * 3. Select the most recent confirmed UTXO.
2306
- * 4. Fetch the previous transaction hex for `nonWitnessUtxo`.
2307
- * 5. Build a PSBT: input (UTXO) → change output + OP_RETURN(signalBytes).
2308
- * 6. Compute the fee via the supplied (or default) {@link FeeEstimator} against the tx vsize.
2309
- * 7. Sign input 0 with an ECDSA signer derived from `secretKey`.
2310
- * 8. Finalize, extract, and broadcast via the REST transaction endpoint.
3053
+ * Build + sign + broadcast a single-party beacon signal transaction (P2WPKH spend).
2311
3054
  *
2312
- * Fee handling: the PSBT is constructed with a placeholder change amount, signed to measure
2313
- * vsize, then the change is adjusted to pay the actual fee and the input re-signed. This
2314
- * two-pass approach avoids hardcoded fee constants and produces a tx that matches the
2315
- * estimator's rate.
3055
+ * Composed from the three extracted phases ({@link buildSinglePartyTx},
3056
+ * {@link signSinglePartyTx}, {@link broadcastRawTx}) so each piece can be exercised
3057
+ * in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead
3058
+ * the multi-party path can't share the signing phase, but the tx-construction
3059
+ * plumbing (UTXO fetch + OP_RETURN output + change output) is shared.
2316
3060
  *
2317
3061
  * @param signalBytes 32-byte payload to embed in OP_RETURN.
2318
3062
  * @param secretKey Secret key used to sign the spending input.
2319
3063
  * @param bitcoin Bitcoin network connection.
2320
3064
  * @param options Broadcast options (fee estimator, etc.).
2321
3065
  * @returns The txid of the broadcast transaction.
2322
- * @throws {BeaconError} if the address is unfunded or no UTXO is available.
3066
+ * @throws {BeaconError} if the address is unfunded, no UTXO is available, or fee exceeds value.
2323
3067
  */
2324
3068
  async buildSignAndBroadcast(signalBytes, secretKey, bitcoin, options) {
2325
3069
  const feeEstimator = options?.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
2326
- const bitcoinAddress = this.service.serviceEndpoint.replace("bitcoin:", "");
2327
- const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
2328
- if (!utxos.length) {
2329
- throw new BeaconError(
2330
- "No UTXOs found, please fund address!",
2331
- "UNFUNDED_BEACON_ADDRESS",
2332
- { bitcoinAddress }
2333
- );
2334
- }
2335
- const utxo = utxos.sort(
2336
- (a, b) => b.status.block_height - a.status.block_height
2337
- ).shift();
2338
- if (!utxo) {
2339
- throw new BeaconError(
2340
- "Beacon bitcoin address unfunded or utxos unconfirmed.",
2341
- "UNFUNDED_BEACON_ADDRESS",
2342
- { bitcoinAddress }
3070
+ const beaconAddress = this.service.serviceEndpoint.replace("bitcoin:", "");
3071
+ const { utxo, prevTxBytes } = await fetchSpendableUtxo(beaconAddress, bitcoin);
3072
+ const plan = await this.buildSinglePartyTx({
3073
+ signalBytes,
3074
+ beaconAddress,
3075
+ utxo,
3076
+ prevTxBytes,
3077
+ secretKey,
3078
+ bitcoin,
3079
+ feeEstimator
3080
+ });
3081
+ const signedHex = this.signSinglePartyTx(plan.tx, secretKey);
3082
+ return this.broadcastRawTx(bitcoin, signedHex);
3083
+ }
3084
+ /**
3085
+ * Build an unsigned P2WPKH single-party beacon tx + probe-sign to determine vsize,
3086
+ * then rebuild with the real fee. Returns the tx and prev-output metadata.
3087
+ *
3088
+ * The secret key is required here (not just in `signSinglePartyTx`) because the
3089
+ * two-pass fee estimation requires an actual signature to measure vsize accurately.
3090
+ */
3091
+ async buildSinglePartyTx(opts) {
3092
+ const pubkey = this.#derivePubkey(opts.secretKey);
3093
+ const witnessOut = (0, import_btc_signer4.p2wpkh)(pubkey, opts.bitcoin.data);
3094
+ const witnessScript = witnessOut.script;
3095
+ const build = (feeSats2) => {
3096
+ const tx2 = new import_btc_signer4.Transaction();
3097
+ tx2.addInput({
3098
+ txid: opts.utxo.txid,
3099
+ index: opts.utxo.vout,
3100
+ nonWitnessUtxo: opts.prevTxBytes,
3101
+ witnessUtxo: { amount: BigInt(opts.utxo.value), script: witnessScript }
3102
+ });
3103
+ tx2.addOutputAddress(
3104
+ opts.beaconAddress,
3105
+ BigInt(opts.utxo.value) - feeSats2,
3106
+ opts.bitcoin.data
2343
3107
  );
2344
- }
2345
- const prevTx = await bitcoin.rest.transaction.getHex(utxo.txid);
2346
- const keyPair = import_keypair2.SchnorrKeyPair.fromSecret(secretKey);
2347
- const signer = {
2348
- publicKey: keyPair.publicKey.compressed,
2349
- sign: (hash5) => keyPair.secretKey.sign(hash5, { scheme: "ecdsa" })
3108
+ tx2.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
3109
+ return tx2;
2350
3110
  };
2351
- const build = (fee2) => new import_bitcoinjs_lib4.Psbt({ network: bitcoin.data }).addInput({
2352
- hash: utxo.txid,
2353
- index: utxo.vout,
2354
- nonWitnessUtxo: (0, import_utils5.hexToBytes)(prevTx)
2355
- }).addOutput({ address: bitcoinAddress, value: BigInt(utxo.value) - fee2 }).addOutput({ script: import_bitcoinjs_lib4.script.compile([import_bitcoinjs_lib4.opcodes.OP_RETURN, signalBytes]), value: 0n });
2356
- const probeTx = build(0n).signInput(0, signer).finalizeAllInputs().extractTransaction();
2357
- const vsize = probeTx.virtualSize();
2358
- const fee = await feeEstimator.estimateFee(vsize);
2359
- if (BigInt(utxo.value) <= fee) {
3111
+ const probe = build(0n);
3112
+ probe.signIdx(opts.secretKey, 0);
3113
+ probe.finalize();
3114
+ const vsize = probe.vsize;
3115
+ const feeSats = await opts.feeEstimator.estimateFee(vsize);
3116
+ if (BigInt(opts.utxo.value) <= feeSats) {
2360
3117
  throw new BeaconError(
2361
- `UTXO value (${utxo.value}) insufficient to cover fee (${fee}).`,
3118
+ `UTXO value (${opts.utxo.value}) insufficient to cover fee (${feeSats}).`,
2362
3119
  "INSUFFICIENT_FUNDS",
2363
- { bitcoinAddress, utxoValue: utxo.value, fee: fee.toString() }
3120
+ { bitcoinAddress: opts.beaconAddress, utxoValue: opts.utxo.value, fee: feeSats.toString() }
2364
3121
  );
2365
3122
  }
2366
- const signedTxHex = build(fee).signInput(0, signer).finalizeAllInputs().extractTransaction().toHex();
2367
- return bitcoin.rest.transaction.send(signedTxHex);
3123
+ const tx = build(feeSats);
3124
+ return {
3125
+ tx,
3126
+ prevOutScripts: [witnessScript],
3127
+ prevOutValues: [BigInt(opts.utxo.value)],
3128
+ beaconAddress: opts.beaconAddress,
3129
+ utxo: opts.utxo,
3130
+ feeSats
3131
+ };
3132
+ }
3133
+ /**
3134
+ * Sign + finalize the unsigned single-party tx and return its raw hex.
3135
+ */
3136
+ signSinglePartyTx(tx, secretKey) {
3137
+ tx.signIdx(secretKey, 0);
3138
+ tx.finalize();
3139
+ return tx.hex;
3140
+ }
3141
+ /**
3142
+ * Broadcast raw transaction hex via the Bitcoin REST endpoint. Returns the txid.
3143
+ */
3144
+ async broadcastRawTx(bitcoin, rawHex) {
3145
+ return bitcoin.rest.transaction.send(rawHex);
3146
+ }
3147
+ /** Derive the compressed secp256k1 public key from a raw secret key. */
3148
+ #derivePubkey(secretKey) {
3149
+ return (0, import_secp256k12.getPublicKey)(secretKey, true);
2368
3150
  }
2369
3151
  };
2370
3152
 
2371
3153
  // src/core/beacon/cas-beacon.ts
2372
- var import_common8 = require("@did-btcr2/common");
3154
+ var import_common10 = require("@did-btcr2/common");
2373
3155
  var CASBeacon = class extends Beacon {
2374
3156
  /**
2375
3157
  * Creates an instance of CASBeacon.
@@ -2410,7 +3192,7 @@ var CASBeacon = class extends Beacon {
2410
3192
  if (!updateHashEncoded) {
2411
3193
  continue;
2412
3194
  }
2413
- const updateHash = (0, import_common8.encode)((0, import_common8.decode)(updateHashEncoded, "base64urlnopad"), "hex");
3195
+ const updateHash = (0, import_common10.encode)((0, import_common10.decode)(updateHashEncoded, "base64urlnopad"), "hex");
2414
3196
  const signedUpdate = sidecar.updateMap.get(updateHash);
2415
3197
  if (!signedUpdate) {
2416
3198
  needs.push({
@@ -2442,9 +3224,9 @@ var CASBeacon = class extends Beacon {
2442
3224
  */
2443
3225
  async broadcastSignal(signedUpdate, secretKey, bitcoin, options) {
2444
3226
  const did = this.service.id.split("#")[0];
2445
- const updateHash = (0, import_common8.canonicalHash)(signedUpdate);
3227
+ const updateHash = (0, import_common10.canonicalHash)(signedUpdate);
2446
3228
  const casAnnouncement = { [did]: updateHash };
2447
- const announcementHash = (0, import_common8.hash)((0, import_common8.canonicalize)(casAnnouncement));
3229
+ const announcementHash = (0, import_common10.hash)((0, import_common10.canonicalize)(casAnnouncement));
2448
3230
  await this.buildSignAndBroadcast(announcementHash, secretKey, bitcoin, options);
2449
3231
  if (options?.casPublish) {
2450
3232
  await options.casPublish(casAnnouncement);
@@ -2454,10 +3236,10 @@ var CASBeacon = class extends Beacon {
2454
3236
  };
2455
3237
 
2456
3238
  // src/core/beacon/factory.ts
2457
- var import_common11 = require("@did-btcr2/common");
3239
+ var import_common13 = require("@did-btcr2/common");
2458
3240
 
2459
3241
  // src/core/beacon/singleton-beacon.ts
2460
- var import_common9 = require("@did-btcr2/common");
3242
+ var import_common11 = require("@did-btcr2/common");
2461
3243
  var SingletonBeacon = class extends Beacon {
2462
3244
  /**
2463
3245
  * Creates an instance of SingletonBeacon.
@@ -2505,14 +3287,14 @@ var SingletonBeacon = class extends Beacon {
2505
3287
  * @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
2506
3288
  */
2507
3289
  async broadcastSignal(signedUpdate, secretKey, bitcoin, options) {
2508
- const signalBytes = (0, import_common9.hash)((0, import_common9.canonicalize)(signedUpdate));
3290
+ const signalBytes = (0, import_common11.hash)((0, import_common11.canonicalize)(signedUpdate));
2509
3291
  await this.buildSignAndBroadcast(signalBytes, secretKey, bitcoin, options);
2510
3292
  return signedUpdate;
2511
3293
  }
2512
3294
  };
2513
3295
 
2514
3296
  // src/core/beacon/smt-beacon.ts
2515
- var import_common10 = require("@did-btcr2/common");
3297
+ var import_common12 = require("@did-btcr2/common");
2516
3298
  var import_smt3 = require("@did-btcr2/smt");
2517
3299
  var import_utils6 = require("@noble/hashes/utils");
2518
3300
  var SMTBeacon = class extends Beacon {
@@ -2600,7 +3382,7 @@ var SMTBeacon = class extends Beacon {
2600
3382
  */
2601
3383
  async broadcastSignal(signedUpdate, secretKey, bitcoin, options) {
2602
3384
  const did = this.service.id.split("#")[0];
2603
- const canonicalBytes = new TextEncoder().encode((0, import_common10.canonicalize)(signedUpdate));
3385
+ const canonicalBytes = new TextEncoder().encode((0, import_common12.canonicalize)(signedUpdate));
2604
3386
  const nonce = (0, import_utils6.randomBytes)(32);
2605
3387
  const tree = new import_smt3.BTCR2MerkleTree();
2606
3388
  tree.addEntries([{ did, nonce, signedUpdate: canonicalBytes }]);
@@ -2626,19 +3408,19 @@ var BeaconFactory = class {
2626
3408
  case "SMTBeacon":
2627
3409
  return new SMTBeacon(service);
2628
3410
  default:
2629
- throw new import_common11.MethodError("Invalid Beacon Type", "INVALID_BEACON_ERROR", service);
3411
+ throw new import_common13.MethodError("Invalid Beacon Type", "INVALID_BEACON_ERROR", service);
2630
3412
  }
2631
3413
  }
2632
3414
  };
2633
3415
 
2634
3416
  // src/core/beacon/signal-discovery.ts
2635
3417
  var import_bitcoin2 = require("@did-btcr2/bitcoin");
2636
- var import_common14 = require("@did-btcr2/common");
3418
+ var import_common16 = require("@did-btcr2/common");
2637
3419
 
2638
3420
  // src/core/beacon/utils.ts
2639
3421
  var import_bitcoin = require("@did-btcr2/bitcoin");
2640
- var import_common13 = require("@did-btcr2/common");
2641
- var import_bitcoinjs_lib5 = require("bitcoinjs-lib");
3422
+ var import_common15 = require("@did-btcr2/common");
3423
+ var import_btc_signer5 = require("@scure/btc-signer");
2642
3424
 
2643
3425
  // src/utils/appendix.ts
2644
3426
  var import_dids = require("@web5/dids");
@@ -3679,9 +4461,9 @@ var Appendix = class _Appendix {
3679
4461
  };
3680
4462
 
3681
4463
  // src/core/identifier.ts
3682
- var import_common12 = require("@did-btcr2/common");
3683
- var import_keypair3 = require("@did-btcr2/keypair");
3684
- var import_base5 = require("@scure/base");
4464
+ var import_common14 = require("@did-btcr2/common");
4465
+ var import_keypair2 = require("@did-btcr2/keypair");
4466
+ var import_base7 = require("@scure/base");
3685
4467
  var Identifier = class _Identifier {
3686
4468
  /**
3687
4469
  * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-encoding | 3.2 did:btcr2 Identifier Encoding}.
@@ -3699,25 +4481,25 @@ var Identifier = class _Identifier {
3699
4481
  */
3700
4482
  static encode(genesisBytes, options) {
3701
4483
  const { idType, version = 1, network } = options;
3702
- if (!(idType in import_common12.IdentifierTypes)) {
3703
- throw new import_common12.IdentifierError('Expected "idType" to be "KEY" or "EXTERNAL"', import_common12.INVALID_DID, { idType });
4484
+ if (!(idType in import_common14.IdentifierTypes)) {
4485
+ throw new import_common14.IdentifierError('Expected "idType" to be "KEY" or "EXTERNAL"', import_common14.INVALID_DID, { idType });
3704
4486
  }
3705
4487
  if (isNaN(version) || version > 1) {
3706
- throw new import_common12.IdentifierError('Expected "version" to be 1', import_common12.INVALID_DID, { version });
4488
+ throw new import_common14.IdentifierError('Expected "version" to be 1', import_common14.INVALID_DID, { version });
3707
4489
  }
3708
- if (typeof network === "string" && !(network in import_common12.BitcoinNetworkNames)) {
3709
- throw new import_common12.IdentifierError('Invalid "network" name', import_common12.INVALID_DID, { network });
4490
+ if (typeof network === "string" && !(network in import_common14.BitcoinNetworkNames)) {
4491
+ throw new import_common14.IdentifierError('Invalid "network" name', import_common14.INVALID_DID, { network });
3710
4492
  }
3711
4493
  if (typeof network === "number" && (network < 0 || network > 8)) {
3712
- throw new import_common12.IdentifierError('Invalid "network" number', import_common12.INVALID_DID, { network });
4494
+ throw new import_common14.IdentifierError('Invalid "network" number', import_common14.INVALID_DID, { network });
3713
4495
  }
3714
4496
  if (idType === "KEY") {
3715
4497
  try {
3716
- new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
4498
+ new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
3717
4499
  } catch {
3718
- throw new import_common12.IdentifierError(
4500
+ throw new import_common14.IdentifierError(
3719
4501
  'Expected "genesisBytes" to be a valid compressed secp256k1 public key',
3720
- import_common12.INVALID_DID,
4502
+ import_common14.INVALID_DID,
3721
4503
  { genesisBytes }
3722
4504
  );
3723
4505
  }
@@ -3730,7 +4512,7 @@ var Identifier = class _Identifier {
3730
4512
  }
3731
4513
  nibbles.push((version - 1) % 15);
3732
4514
  if (typeof network === "string") {
3733
- nibbles.push(import_common12.BitcoinNetworkNames[network]);
4515
+ nibbles.push(import_common14.BitcoinNetworkNames[network]);
3734
4516
  } else if (typeof network === "number") {
3735
4517
  nibbles.push(network + 11);
3736
4518
  }
@@ -3739,11 +4521,11 @@ var Identifier = class _Identifier {
3739
4521
  }
3740
4522
  if (fCount !== 0) {
3741
4523
  for (const index in Array.from({ length: nibbles.length / 2 - 1 })) {
3742
- throw new import_common12.IdentifierError("Not implemented", "NOT_IMPLEMENTED", { index });
4524
+ throw new import_common14.IdentifierError("Not implemented", "NOT_IMPLEMENTED", { index });
3743
4525
  }
3744
4526
  }
3745
4527
  const dataBytes = new Uint8Array([nibbles[2 * 0] << 4 | nibbles[2 * 0 + 1], ...genesisBytes]);
3746
- return `did:btcr2:${import_base5.bech32m.encodeFromBytes(hrp, dataBytes)}`;
4528
+ return `did:btcr2:${import_base7.bech32m.encodeFromBytes(hrp, dataBytes)}`;
3747
4529
  }
3748
4530
  /**
3749
4531
  * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}.
@@ -3756,24 +4538,24 @@ var Identifier = class _Identifier {
3756
4538
  static decode(identifier) {
3757
4539
  const components = identifier.split(":");
3758
4540
  if (components.length !== 3) {
3759
- throw new import_common12.IdentifierError(`Invalid did: ${identifier}`, import_common12.INVALID_DID, { identifier });
4541
+ throw new import_common14.IdentifierError(`Invalid did: ${identifier}`, import_common14.INVALID_DID, { identifier });
3760
4542
  }
3761
4543
  const [scheme, method, encoded] = components;
3762
4544
  if (scheme !== "did") {
3763
- throw new import_common12.IdentifierError(`Invalid did: ${identifier}`, import_common12.INVALID_DID, { identifier });
4545
+ throw new import_common14.IdentifierError(`Invalid did: ${identifier}`, import_common14.INVALID_DID, { identifier });
3764
4546
  }
3765
4547
  if (method !== "btcr2") {
3766
- throw new import_common12.IdentifierError(`Invalid did method: ${method}`, import_common12.METHOD_NOT_SUPPORTED, { identifier });
4548
+ throw new import_common14.IdentifierError(`Invalid did method: ${method}`, import_common14.METHOD_NOT_SUPPORTED, { identifier });
3767
4549
  }
3768
4550
  if (!encoded) {
3769
- throw new import_common12.IdentifierError(`Invalid method-specific id: ${identifier}`, import_common12.INVALID_DID, { identifier });
4551
+ throw new import_common14.IdentifierError(`Invalid method-specific id: ${identifier}`, import_common14.INVALID_DID, { identifier });
3770
4552
  }
3771
- const { prefix: hrp, bytes: dataBytes } = import_base5.bech32m.decodeToBytes(encoded);
4553
+ const { prefix: hrp, bytes: dataBytes } = import_base7.bech32m.decodeToBytes(encoded);
3772
4554
  if (!["x", "k"].includes(hrp)) {
3773
- throw new import_common12.IdentifierError(`Invalid hrp: ${hrp}`, import_common12.INVALID_DID, { identifier });
4555
+ throw new import_common14.IdentifierError(`Invalid hrp: ${hrp}`, import_common14.INVALID_DID, { identifier });
3774
4556
  }
3775
4557
  if (!dataBytes) {
3776
- throw new import_common12.IdentifierError(`Failed to decode id: ${encoded}`, import_common12.INVALID_DID, { identifier });
4558
+ throw new import_common14.IdentifierError(`Failed to decode id: ${encoded}`, import_common14.INVALID_DID, { identifier });
3777
4559
  }
3778
4560
  const idType = hrp === "k" ? "KEY" : "EXTERNAL";
3779
4561
  let version = 1;
@@ -3791,33 +4573,33 @@ var Identifier = class _Identifier {
3791
4573
  }
3792
4574
  nibblesConsumed += 1;
3793
4575
  if (version > 1) {
3794
- throw new import_common12.IdentifierError(`Invalid version: ${version}`, import_common12.INVALID_DID, { identifier });
4576
+ throw new import_common14.IdentifierError(`Invalid version: ${version}`, import_common14.INVALID_DID, { identifier });
3795
4577
  }
3796
4578
  }
3797
4579
  version += versionNibble;
3798
4580
  nibblesConsumed += 1;
3799
4581
  let networkValue = nibblesConsumed % 2 === 0 ? dataBytes[++byteIndex] >>> 4 : currentByte & 15;
3800
4582
  nibblesConsumed += 1;
3801
- let network = import_common12.BitcoinNetworkNames[networkValue];
4583
+ let network = import_common14.BitcoinNetworkNames[networkValue];
3802
4584
  if (!network) {
3803
4585
  if (networkValue >= 8 && networkValue <= 15) {
3804
4586
  network = networkValue - 11;
3805
4587
  } else {
3806
- throw new import_common12.IdentifierError(`Invalid did: ${identifier}`, import_common12.INVALID_DID, { identifier });
4588
+ throw new import_common14.IdentifierError(`Invalid did: ${identifier}`, import_common14.INVALID_DID, { identifier });
3807
4589
  }
3808
4590
  }
3809
4591
  if (nibblesConsumed % 2 === 1) {
3810
4592
  const fillerNibble = currentByte & 15;
3811
4593
  if (fillerNibble !== 0) {
3812
- throw new import_common12.IdentifierError(`Invalid did: ${identifier}`, import_common12.INVALID_DID, { identifier });
4594
+ throw new import_common14.IdentifierError(`Invalid did: ${identifier}`, import_common14.INVALID_DID, { identifier });
3813
4595
  }
3814
4596
  }
3815
4597
  const genesisBytes = dataBytes.slice(byteIndex + 1);
3816
4598
  if (idType === "KEY") {
3817
4599
  try {
3818
- new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
4600
+ new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
3819
4601
  } catch {
3820
- throw new import_common12.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common12.INVALID_DID, { identifier });
4602
+ throw new import_common14.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common14.INVALID_DID, { identifier });
3821
4603
  }
3822
4604
  }
3823
4605
  return { idType, hrp, version, network, genesisBytes };
@@ -3827,7 +4609,7 @@ var Identifier = class _Identifier {
3827
4609
  * @returns {string} The new did:btcr2 identifier.
3828
4610
  */
3829
4611
  static generate() {
3830
- const keyPair = import_keypair3.SchnorrKeyPair.generate();
4612
+ const keyPair = import_keypair2.SchnorrKeyPair.generate();
3831
4613
  const did = this.encode(
3832
4614
  keyPair.publicKey.compressed,
3833
4615
  {
@@ -3847,13 +4629,13 @@ var Identifier = class _Identifier {
3847
4629
  static getPublicKey(did) {
3848
4630
  const { idType, genesisBytes } = _Identifier.decode(did);
3849
4631
  if (idType !== "KEY") {
3850
- throw new import_common12.IdentifierError(
4632
+ throw new import_common14.IdentifierError(
3851
4633
  `Cannot extract public key from EXTERNAL DID: ${did}. EXTERNAL DIDs encode a document hash, not a public key.`,
3852
- import_common12.INVALID_DID,
4634
+ import_common14.INVALID_DID,
3853
4635
  { did, idType }
3854
4636
  );
3855
4637
  }
3856
- return new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
4638
+ return new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
3857
4639
  }
3858
4640
  /**
3859
4641
  * Validates a did:btcr2 identifier.
@@ -3880,7 +4662,7 @@ var BeaconUtils = class {
3880
4662
  */
3881
4663
  static parseBitcoinAddress(uri) {
3882
4664
  if (!uri.startsWith("bitcoin:")) {
3883
- throw new import_common13.MethodError("Invalid Bitcoin URI format", "BEACON_SERVICE_ERROR", { uri });
4665
+ throw new import_common15.MethodError("Invalid Bitcoin URI format", "BEACON_SERVICE_ERROR", { uri });
3884
4666
  }
3885
4667
  return uri.replace("bitcoin:", "").split("?")[0];
3886
4668
  }
@@ -3938,7 +4720,8 @@ var BeaconUtils = class {
3938
4720
  const network = (0, import_bitcoin.getNetwork)(components.network);
3939
4721
  const pubkey = components.genesisBytes;
3940
4722
  const id = `${did}#initial${addressType.toUpperCase()}`;
3941
- const serviceEndpoint = `bitcoin:${import_bitcoinjs_lib5.payments[addressType]({ pubkey, network }).address}`;
4723
+ const address = addressType === "p2tr" ? (0, import_btc_signer5.p2tr)(pubkey.slice(1, 33), void 0, network).address : addressType === "p2wpkh" ? (0, import_btc_signer5.p2wpkh)(pubkey, network).address : (0, import_btc_signer5.p2pkh)(pubkey, network).address;
4724
+ const serviceEndpoint = `bitcoin:${address}`;
3942
4725
  return { id, type: beaconType, serviceEndpoint };
3943
4726
  } catch (error) {
3944
4727
  throw new BeaconError(
@@ -3956,27 +4739,27 @@ var BeaconUtils = class {
3956
4739
  */
3957
4740
  static generateBeaconServices({ id, publicKey, network, beaconType }) {
3958
4741
  try {
3959
- const p2pkh = import_bitcoinjs_lib5.payments.p2pkh({ pubkey: publicKey, network }).address;
3960
- const p2wpkh = import_bitcoinjs_lib5.payments.p2wpkh({ pubkey: publicKey, network }).address;
3961
- const p2tr = import_bitcoinjs_lib5.payments.p2tr({ network, internalPubkey: publicKey.slice(1, 33) }).address;
3962
- if (!p2pkh || !p2wpkh || !p2tr) {
3963
- throw new import_common13.DidMethodError("Failed to generate bitcoin addresses");
4742
+ const p2pkhAddr = (0, import_btc_signer5.p2pkh)(publicKey, network).address;
4743
+ const p2wpkhAddr = (0, import_btc_signer5.p2wpkh)(publicKey, network).address;
4744
+ const p2trAddr = (0, import_btc_signer5.p2tr)(publicKey.slice(1, 33), void 0, network).address;
4745
+ if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
4746
+ throw new import_common15.DidMethodError("Failed to generate bitcoin addresses");
3964
4747
  }
3965
4748
  return [
3966
4749
  {
3967
4750
  id: `${id}#initialP2PKH`,
3968
4751
  type: beaconType,
3969
- serviceEndpoint: `bitcoin:${p2pkh}`
4752
+ serviceEndpoint: `bitcoin:${p2pkhAddr}`
3970
4753
  },
3971
4754
  {
3972
4755
  id: `${id}#initialP2WPKH`,
3973
4756
  type: beaconType,
3974
- serviceEndpoint: `bitcoin:${p2wpkh}`
4757
+ serviceEndpoint: `bitcoin:${p2wpkhAddr}`
3975
4758
  },
3976
4759
  {
3977
4760
  id: `${id}#initialP2TR`,
3978
4761
  type: beaconType,
3979
- serviceEndpoint: `bitcoin:${p2tr}`
4762
+ serviceEndpoint: `bitcoin:${p2trAddr}`
3980
4763
  }
3981
4764
  ];
3982
4765
  } catch (error) {
@@ -4076,7 +4859,7 @@ var BeaconSignalDiscovery = class {
4076
4859
  }
4077
4860
  const rpc = bitcoin.rpc;
4078
4861
  if (!rpc) {
4079
- throw new import_common14.ResolveError("RPC connection is not available", "RPC_CONNECTION_ERROR", bitcoin);
4862
+ throw new import_common16.ResolveError("RPC connection is not available", "RPC_CONNECTION_ERROR", bitcoin);
4080
4863
  }
4081
4864
  const targetHeight = await rpc.getBlockCount();
4082
4865
  const beaconServicesMap = BeaconUtils.getBeaconServicesMap(beaconServices);
@@ -4147,27 +4930,24 @@ var BeaconSignalDiscovery = class {
4147
4930
 
4148
4931
  // src/core/resolver.ts
4149
4932
  var import_bitcoin4 = require("@did-btcr2/bitcoin");
4150
- var import_common18 = require("@did-btcr2/common");
4151
- var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
4152
- var import_keypair5 = require("@did-btcr2/keypair");
4933
+ var import_common20 = require("@did-btcr2/common");
4934
+ var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
4935
+ var import_keypair4 = require("@did-btcr2/keypair");
4153
4936
 
4154
4937
  // src/did-btcr2.ts
4155
- var import_common17 = require("@did-btcr2/common");
4938
+ var import_common19 = require("@did-btcr2/common");
4156
4939
  var import_dids2 = require("@web5/dids");
4157
- var ecc = __toESM(require("@bitcoinerlab/secp256k1"), 1);
4158
- var import_utils9 = require("@noble/hashes/utils");
4159
- var import_bitcoinjs_lib7 = require("bitcoinjs-lib");
4160
4940
 
4161
- // src/core/update.ts
4162
- var import_common16 = require("@did-btcr2/common");
4163
- var import_cryptosuite = require("@did-btcr2/cryptosuite");
4941
+ // src/core/updater.ts
4942
+ var import_common18 = require("@did-btcr2/common");
4943
+ var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
4164
4944
 
4165
4945
  // src/utils/did-document.ts
4166
4946
  var import_bitcoin3 = require("@did-btcr2/bitcoin");
4167
- var import_common15 = require("@did-btcr2/common");
4168
- var import_keypair4 = require("@did-btcr2/keypair");
4947
+ var import_common17 = require("@did-btcr2/common");
4948
+ var import_keypair3 = require("@did-btcr2/keypair");
4169
4949
  var import_utils8 = require("@web5/dids/utils");
4170
- var import_bitcoinjs_lib6 = require("bitcoinjs-lib");
4950
+ var import_btc_signer6 = require("@scure/btc-signer");
4171
4951
  var BTCR2_DID_DOCUMENT_CONTEXT = [
4172
4952
  "https://www.w3.org/ns/did/v1.1",
4173
4953
  "https://btcr2.dev/context/v1"
@@ -4208,20 +4988,20 @@ var DidDocument = class _DidDocument {
4208
4988
  deactivated;
4209
4989
  constructor(document) {
4210
4990
  if (!document.id) {
4211
- throw new import_common15.DidDocumentError("DID Document must have an id", import_common15.INVALID_DID_DOCUMENT, document);
4991
+ throw new import_common17.DidDocumentError("DID Document must have an id", import_common17.INVALID_DID_DOCUMENT, document);
4212
4992
  }
4213
- const idType = document.id.includes("k1") ? import_common15.IdentifierTypes.KEY : import_common15.IdentifierTypes.EXTERNAL;
4993
+ const idType = document.id.includes("k1") ? import_common17.IdentifierTypes.KEY : import_common17.IdentifierTypes.EXTERNAL;
4214
4994
  const isGenesis = document.id === ID_PLACEHOLDER_VALUE;
4215
4995
  const { id, verificationMethod: vm, service } = document;
4216
4996
  if (!isGenesis) {
4217
4997
  if (!_DidDocument.isValidId(id)) {
4218
- throw new import_common15.DidDocumentError(`Invalid id: ${id}`, import_common15.INVALID_DID_DOCUMENT, document);
4998
+ throw new import_common17.DidDocumentError(`Invalid id: ${id}`, import_common17.INVALID_DID_DOCUMENT, document);
4219
4999
  }
4220
5000
  if (!_DidDocument.isValidVerificationMethods(vm)) {
4221
- throw new import_common15.DidDocumentError("Invalid verificationMethod: " + vm, import_common15.INVALID_DID_DOCUMENT, document);
5001
+ throw new import_common17.DidDocumentError("Invalid verificationMethod: " + vm, import_common17.INVALID_DID_DOCUMENT, document);
4222
5002
  }
4223
5003
  if (!_DidDocument.isValidServices(service)) {
4224
- throw new import_common15.DidDocumentError("Invalid service: " + service, import_common15.INVALID_DID_DOCUMENT, document);
5004
+ throw new import_common17.DidDocumentError("Invalid service: " + service, import_common17.INVALID_DID_DOCUMENT, document);
4225
5005
  }
4226
5006
  }
4227
5007
  this.id = document.id;
@@ -4231,7 +5011,7 @@ var DidDocument = class _DidDocument {
4231
5011
  "https://www.w3.org/ns/did/v1.1",
4232
5012
  "https://btcr2.dev/context/v1"
4233
5013
  ];
4234
- if (idType === import_common15.IdentifierTypes.KEY) {
5014
+ if (idType === import_common17.IdentifierTypes.KEY) {
4235
5015
  const keyRef = `${this.id}#initialKey`;
4236
5016
  this.authentication = document.authentication || [keyRef];
4237
5017
  this.assertionMethod = document.assertionMethod || [keyRef];
@@ -4317,19 +5097,19 @@ var DidDocument = class _DidDocument {
4317
5097
  */
4318
5098
  static isValid(didDocument) {
4319
5099
  if (!this.isValidContext(didDocument?.["@context"])) {
4320
- throw new import_common15.DidDocumentError('Invalid "@context"', import_common15.INVALID_DID_DOCUMENT, didDocument);
5100
+ throw new import_common17.DidDocumentError('Invalid "@context"', import_common17.INVALID_DID_DOCUMENT, didDocument);
4321
5101
  }
4322
5102
  if (!this.isValidId(didDocument?.id)) {
4323
- throw new import_common15.DidDocumentError('Invalid "id"', import_common15.INVALID_DID_DOCUMENT, didDocument);
5103
+ throw new import_common17.DidDocumentError('Invalid "id"', import_common17.INVALID_DID_DOCUMENT, didDocument);
4324
5104
  }
4325
5105
  if (!this.isValidVerificationMethods(didDocument?.verificationMethod)) {
4326
- throw new import_common15.DidDocumentError('Invalid "verificationMethod"', import_common15.INVALID_DID_DOCUMENT, didDocument);
5106
+ throw new import_common17.DidDocumentError('Invalid "verificationMethod"', import_common17.INVALID_DID_DOCUMENT, didDocument);
4327
5107
  }
4328
5108
  if (!this.isValidServices(didDocument?.service)) {
4329
- throw new import_common15.DidDocumentError('Invalid "service"', import_common15.INVALID_DID_DOCUMENT, didDocument);
5109
+ throw new import_common17.DidDocumentError('Invalid "service"', import_common17.INVALID_DID_DOCUMENT, didDocument);
4330
5110
  }
4331
5111
  if (!this.isValidVerificationRelationships(didDocument)) {
4332
- throw new import_common15.DidDocumentError("Invalid verification relationships", import_common15.INVALID_DID_DOCUMENT, didDocument);
5112
+ throw new import_common17.DidDocumentError("Invalid verification relationships", import_common17.INVALID_DID_DOCUMENT, didDocument);
4333
5113
  }
4334
5114
  return true;
4335
5115
  }
@@ -4419,16 +5199,16 @@ var DidDocument = class _DidDocument {
4419
5199
  */
4420
5200
  validateGenesis() {
4421
5201
  if (this.id !== ID_PLACEHOLDER_VALUE) {
4422
- throw new import_common15.DidDocumentError("Invalid GenesisDocument ID", import_common15.INVALID_DID_DOCUMENT, this);
5202
+ throw new import_common17.DidDocumentError("Invalid GenesisDocument ID", import_common17.INVALID_DID_DOCUMENT, this);
4423
5203
  }
4424
5204
  if (!this.verificationMethod.every((vm) => vm.id.includes(ID_PLACEHOLDER_VALUE) && vm.controller === ID_PLACEHOLDER_VALUE)) {
4425
- throw new import_common15.DidDocumentError("Invalid GenesisDocument verificationMethod", import_common15.INVALID_DID_DOCUMENT, this);
5205
+ throw new import_common17.DidDocumentError("Invalid GenesisDocument verificationMethod", import_common17.INVALID_DID_DOCUMENT, this);
4426
5206
  }
4427
5207
  if (!this.service.every((svc) => svc.id.includes(ID_PLACEHOLDER_VALUE))) {
4428
- throw new import_common15.DidDocumentError("Invalid GenesisDocument service", import_common15.INVALID_DID_DOCUMENT, this);
5208
+ throw new import_common17.DidDocumentError("Invalid GenesisDocument service", import_common17.INVALID_DID_DOCUMENT, this);
4429
5209
  }
4430
5210
  if (!_DidDocument.isValidVerificationRelationships(this)) {
4431
- throw new import_common15.DidDocumentError("Invalid GenesisDocument assertionMethod", import_common15.INVALID_DID_DOCUMENT, this);
5211
+ throw new import_common17.DidDocumentError("Invalid GenesisDocument assertionMethod", import_common17.INVALID_DID_DOCUMENT, this);
4432
5212
  }
4433
5213
  return true;
4434
5214
  }
@@ -4438,7 +5218,7 @@ var DidDocument = class _DidDocument {
4438
5218
  */
4439
5219
  toIntermediate() {
4440
5220
  if (this.id.includes("k1")) {
4441
- throw new import_common15.DidDocumentError("Cannot convert a key identifier to an intermediate document", import_common15.INVALID_DID_DOCUMENT, this);
5221
+ throw new import_common17.DidDocumentError("Cannot convert a key identifier to an intermediate document", import_common17.INVALID_DID_DOCUMENT, this);
4442
5222
  }
4443
5223
  return new GenesisDocument(this);
4444
5224
  }
@@ -4468,7 +5248,7 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
4468
5248
  * @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
4469
5249
  */
4470
5250
  static fromDidDocument(didDocument) {
4471
- const intermediateDocument = import_common15.JSONUtils.cloneReplace(didDocument, DID_REGEX, ID_PLACEHOLDER_VALUE);
5251
+ const intermediateDocument = import_common17.JSONUtils.cloneReplace(didDocument, DID_REGEX, ID_PLACEHOLDER_VALUE);
4472
5252
  return new _GenesisDocument(intermediateDocument);
4473
5253
  }
4474
5254
  /**
@@ -4487,9 +5267,9 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
4487
5267
  * @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
4488
5268
  */
4489
5269
  static fromPublicKey(publicKey, network) {
4490
- const pk = new import_keypair4.CompressedSecp256k1PublicKey(publicKey);
5270
+ const pk = new import_keypair3.CompressedSecp256k1PublicKey(publicKey);
4491
5271
  const id = ID_PLACEHOLDER_VALUE;
4492
- const address = import_bitcoinjs_lib6.payments.p2pkh({ pubkey: pk.compressed, network: (0, import_bitcoin3.getNetwork)(network) })?.address;
5272
+ const address = (0, import_btc_signer6.p2pkh)(pk.compressed, (0, import_bitcoin3.getNetwork)(network)).address;
4493
5273
  const services = [{
4494
5274
  id: `${id}#service-0`,
4495
5275
  serviceEndpoint: `bitcoin:${address}`,
@@ -4525,21 +5305,41 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
4525
5305
  * @returns {Bytes} The genesis bytes.
4526
5306
  */
4527
5307
  static toGenesisBytes(genesisDocument) {
4528
- return (0, import_common15.hash)((0, import_common15.canonicalize)(genesisDocument));
5308
+ return (0, import_common17.hash)((0, import_common17.canonicalize)(genesisDocument));
4529
5309
  }
4530
5310
  };
4531
5311
 
4532
- // src/core/update.ts
4533
- var Update = class {
5312
+ // src/core/updater.ts
5313
+ var Updater = class _Updater {
5314
+ #phase = "Construct" /* Construct */;
5315
+ #sourceDocument;
5316
+ #patches;
5317
+ #sourceVersionId;
5318
+ #verificationMethod;
5319
+ #beaconService;
5320
+ #unsignedUpdate = null;
5321
+ #signedUpdate = null;
5322
+ /**
5323
+ * @internal — Use {@link DidBtcr2.update} to create instances.
5324
+ */
5325
+ constructor(params) {
5326
+ this.#sourceDocument = params.sourceDocument;
5327
+ this.#patches = params.patches;
5328
+ this.#sourceVersionId = params.sourceVersionId;
5329
+ this.#verificationMethod = params.verificationMethod;
5330
+ this.#beaconService = params.beaconService;
5331
+ }
5332
+ // ─── Public static utility methods ─────────────────────────────────────────
5333
+ // Used by generate-vector.ts and other scripts that need direct access to
5334
+ // individual update steps outside the state machine flow.
4534
5335
  /**
4535
5336
  * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/update.html#construct-btcr2-unsigned-update | 7.3.b Construct BTCR2 Unsigned Update}.
4536
- * This process constructs a BTCR2 Unsigned Update conformant to the spec template.
4537
5337
  *
4538
5338
  * @param {Btcr2DidDocument} sourceDocument The source DID document to be updated.
4539
- * @param {PatchOperation[]} patches The array of JSON Patch operations to apply to the sourceDocument.
5339
+ * @param {PatchOperation[]} patches The JSON Patch operations to apply.
4540
5340
  * @param {number} sourceVersionId The version ID of the source document.
4541
5341
  * @returns {UnsignedBTCR2Update} The constructed UnsignedBTCR2Update object.
4542
- * @throws {UpdateError} InvalidDid if sourceDocument.id does not match identifier.
5342
+ * @throws {UpdateError} If the target document fails DID Core validation.
4543
5343
  */
4544
5344
  static construct(sourceDocument, patches, sourceVersionId) {
4545
5345
  const unsignedUpdate = {
@@ -4552,35 +5352,34 @@ var Update = class {
4552
5352
  patch: patches,
4553
5353
  targetHash: "",
4554
5354
  targetVersionId: sourceVersionId + 1,
4555
- sourceHash: (0, import_common16.canonicalHash)(sourceDocument)
5355
+ sourceHash: (0, import_common18.canonicalHash)(sourceDocument)
4556
5356
  };
4557
- const targetDocument = import_common16.JSONPatch.apply(sourceDocument, patches);
5357
+ const targetDocument = import_common18.JSONPatch.apply(sourceDocument, patches);
4558
5358
  try {
4559
5359
  DidDocument.isValid(targetDocument);
4560
5360
  } catch (error) {
4561
- throw new import_common16.UpdateError(
5361
+ throw new import_common18.UpdateError(
4562
5362
  "Error validating targetDocument: " + (error instanceof Error ? error.message : String(error)),
4563
- import_common16.INVALID_DID_UPDATE,
5363
+ import_common18.INVALID_DID_UPDATE,
4564
5364
  targetDocument
4565
5365
  );
4566
5366
  }
4567
- unsignedUpdate.targetHash = (0, import_common16.canonicalHash)(targetDocument);
5367
+ unsignedUpdate.targetHash = (0, import_common18.canonicalHash)(targetDocument);
4568
5368
  return unsignedUpdate;
4569
5369
  }
4570
5370
  /**
4571
5371
  * Implements subsection {@link http://dcdpr.github.io/did-btcr2/operations/update.html#construct-btcr2-signed-update | 7.3.c Construct BTCR2 Signed Update }.
4572
- * This process constructs a BTCR2 Signed Update from a BTCR2 Unsigned Update.
4573
5372
  *
4574
- * @param {string} did The did-btcr2 identifier to derive the root capability from
4575
- * @param {UnsignedBTCR2Update} unsignedUpdate The updatePayload object to be signed
4576
- * @param {DidVerificationMethod} verificationMethod The verificationMethod object to be used for signing
4577
- * @returns {SignedBTCR2Update} Did update payload secured with a proof => SignedBTCR2Update
4578
- * @throws {UpdateError} if the privateKeyBytes are invalid
5373
+ * @param {string} did The did-btcr2 identifier to derive the root capability from.
5374
+ * @param {UnsignedBTCR2Update} unsignedUpdate The unsigned update to sign.
5375
+ * @param {DidVerificationMethod} verificationMethod The verification method for signing.
5376
+ * @param {KeyBytes} secretKey The secret key bytes.
5377
+ * @returns {SignedBTCR2Update} The signed update with a Data Integrity proof.
4579
5378
  */
4580
5379
  static sign(did, unsignedUpdate, verificationMethod, secretKey) {
4581
5380
  const controller = verificationMethod.controller;
4582
5381
  const id = verificationMethod.id.slice(verificationMethod.id.indexOf("#"));
4583
- const multikey = import_cryptosuite.SchnorrMultikey.fromSecretKey(id, controller, secretKey);
5382
+ const multikey = import_cryptosuite2.SchnorrMultikey.fromSecretKey(id, controller, secretKey);
4584
5383
  const config = {
4585
5384
  "@context": [
4586
5385
  "https://w3id.org/security/v2",
@@ -4600,22 +5399,145 @@ var Update = class {
4600
5399
  }
4601
5400
  /**
4602
5401
  * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/update.html#announce-did-update | 7.3.d Announce DID Update}.
4603
- * BTCR2 Signed Updates are announced to the Bitcoin blockchain depending on the Beacon Type.
4604
- * @param {BeaconService} beaconService The BeaconService object representing the funded beacon to announce the update to.
4605
- * @param {SignedBTCR2Update} update The signed update object to be announced.
4606
- * @param {KeyBytes} secretKey The private key used to sign the update for announcement.
4607
- * @returns {SignedBTCR2Update} The SignedBTCR2Update object containing data to validate the Beacon Signal
4608
- * @throws {UpdateError} if the beaconService type is invalid
5402
+ * Announces a signed update to the Bitcoin blockchain via the specified beacon.
5403
+ *
5404
+ * @param {BeaconService} beaconService The beacon service to broadcast through.
5405
+ * @param {SignedBTCR2Update} update The signed update to announce.
5406
+ * @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
5407
+ * @param {BitcoinConnection} bitcoin The Bitcoin network connection.
5408
+ * @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
4609
5409
  */
4610
5410
  static async announce(beaconService, update, secretKey, bitcoin) {
4611
5411
  const beacon = BeaconFactory.establish(beaconService);
4612
- const result = await beacon.broadcastSignal(update, secretKey, bitcoin);
4613
- return result;
5412
+ return beacon.broadcastSignal(update, secretKey, bitcoin);
5413
+ }
5414
+ // ─── Private instance wrappers ─────────────────────────────────────────────
5415
+ // Delegate to the public statics with bound instance fields for cleaner
5416
+ // advance/provide code.
5417
+ #construct() {
5418
+ return _Updater.construct(this.#sourceDocument, this.#patches, this.#sourceVersionId);
5419
+ }
5420
+ #sign(secretKey) {
5421
+ return _Updater.sign(this.#sourceDocument.id, this.#unsignedUpdate, this.#verificationMethod, secretKey);
5422
+ }
5423
+ // ─── State machine ─────────────────────────────────────────────────────────
5424
+ /**
5425
+ * Advance the state machine. Returns either:
5426
+ * - `{ status: 'action-required', needs }` — caller must provide data via {@link provide}
5427
+ * - `{ status: 'complete', result }` — update is signed and broadcast
5428
+ */
5429
+ advance() {
5430
+ while (true) {
5431
+ switch (this.#phase) {
5432
+ // Phase: Construct
5433
+ // Build the unsigned update from source doc + patches. Pure, synchronous.
5434
+ case "Construct" /* Construct */: {
5435
+ this.#unsignedUpdate = this.#construct();
5436
+ this.#phase = "Sign" /* Sign */;
5437
+ continue;
5438
+ }
5439
+ // Phase: Sign
5440
+ // Emit NeedSigningKey — the caller supplies the secret key (or a KMS signature).
5441
+ case "Sign" /* Sign */: {
5442
+ return {
5443
+ status: "action-required",
5444
+ needs: [{
5445
+ kind: "NeedSigningKey",
5446
+ verificationMethodId: this.#verificationMethod.id,
5447
+ unsignedUpdate: this.#unsignedUpdate
5448
+ }]
5449
+ };
5450
+ }
5451
+ // Phase: Fund
5452
+ // Emit NeedFunding with the beacon address. The caller checks UTXOs,
5453
+ // funds the address if needed, and provides to continue.
5454
+ case "Fund" /* Fund */: {
5455
+ const beaconAddress = this.#beaconService.serviceEndpoint.replace("bitcoin:", "");
5456
+ return {
5457
+ status: "action-required",
5458
+ needs: [{
5459
+ kind: "NeedFunding",
5460
+ beaconAddress,
5461
+ beaconService: this.#beaconService
5462
+ }]
5463
+ };
5464
+ }
5465
+ // Phase: Broadcast
5466
+ // Emit NeedBroadcast with the signed update + beacon service. The caller performs
5467
+ // the actual on-chain announcement (or hands off to the aggregation protocol).
5468
+ case "Broadcast" /* Broadcast */: {
5469
+ return {
5470
+ status: "action-required",
5471
+ needs: [{
5472
+ kind: "NeedBroadcast",
5473
+ beaconService: this.#beaconService,
5474
+ signedUpdate: this.#signedUpdate
5475
+ }]
5476
+ };
5477
+ }
5478
+ // Phase: Complete
5479
+ case "Complete" /* Complete */: {
5480
+ return {
5481
+ status: "complete",
5482
+ result: { signedUpdate: this.#signedUpdate }
5483
+ };
5484
+ }
5485
+ }
5486
+ }
5487
+ }
5488
+ provide(need, data) {
5489
+ switch (need.kind) {
5490
+ case "NeedSigningKey": {
5491
+ if (this.#phase !== "Sign" /* Sign */) {
5492
+ throw new import_common18.UpdateError(
5493
+ `Cannot provide NeedSigningKey: updater phase is ${this.#phase}, expected Sign.`,
5494
+ import_common18.INVALID_DID_UPDATE,
5495
+ { phase: this.#phase }
5496
+ );
5497
+ }
5498
+ if (!data) {
5499
+ throw new import_common18.UpdateError(
5500
+ "NeedSigningKey requires secret key bytes.",
5501
+ import_common18.INVALID_DID_UPDATE
5502
+ );
5503
+ }
5504
+ if (!this.#unsignedUpdate) {
5505
+ throw new import_common18.UpdateError(
5506
+ "Internal error: unsigned update missing in Sign phase.",
5507
+ import_common18.INVALID_DID_UPDATE
5508
+ );
5509
+ }
5510
+ this.#signedUpdate = this.#sign(data);
5511
+ this.#phase = "Fund" /* Fund */;
5512
+ break;
5513
+ }
5514
+ case "NeedFunding": {
5515
+ if (this.#phase !== "Fund" /* Fund */) {
5516
+ throw new import_common18.UpdateError(
5517
+ `Cannot provide NeedFunding: updater phase is ${this.#phase}, expected Fund.`,
5518
+ import_common18.INVALID_DID_UPDATE,
5519
+ { phase: this.#phase }
5520
+ );
5521
+ }
5522
+ this.#phase = "Broadcast" /* Broadcast */;
5523
+ break;
5524
+ }
5525
+ case "NeedBroadcast": {
5526
+ if (this.#phase !== "Broadcast" /* Broadcast */) {
5527
+ throw new import_common18.UpdateError(
5528
+ `Cannot provide NeedBroadcast: updater phase is ${this.#phase}, expected Broadcast.`,
5529
+ import_common18.INVALID_DID_UPDATE,
5530
+ { phase: this.#phase }
5531
+ );
5532
+ }
5533
+ this.#phase = "Complete" /* Complete */;
5534
+ break;
5535
+ }
5536
+ }
4614
5537
  }
4615
5538
  };
4616
5539
 
4617
5540
  // src/did-btcr2.ts
4618
- (0, import_bitcoinjs_lib7.initEccLib)(ecc);
4619
5541
  var DidBtcr2 = class {
4620
5542
  /**
4621
5543
  * Name of the DID method, as defined in the DID BTCR2 specification
@@ -4640,9 +5562,9 @@ var DidBtcr2 = class {
4640
5562
  static create(genesisBytes, options) {
4641
5563
  const { idType, version = 1, network = "bitcoin" } = options || {};
4642
5564
  if (!idType) {
4643
- throw new import_common17.MethodError(
5565
+ throw new import_common19.MethodError(
4644
5566
  "idType is required for creating a did:btcr2 identifier",
4645
- import_common17.INVALID_DID_DOCUMENT,
5567
+ import_common19.INVALID_DID_DOCUMENT,
4646
5568
  options
4647
5569
  );
4648
5570
  }
@@ -4672,7 +5594,7 @@ var DidBtcr2 = class {
4672
5594
  static resolve(did, resolutionOptions = {}) {
4673
5595
  const didComponents = Identifier.decode(did);
4674
5596
  const sidecarData = Resolver.sidecarData(resolutionOptions.sidecar);
4675
- const currentDocument = didComponents.hrp === import_common17.IdentifierHrp.k ? Resolver.deterministic(didComponents) : null;
5597
+ const currentDocument = didComponents.hrp === import_common19.IdentifierHrp.k ? Resolver.deterministic(didComponents) : null;
4676
5598
  return new Resolver(didComponents, sidecarData, currentDocument, {
4677
5599
  versionId: resolutionOptions.versionId,
4678
5600
  versionTime: resolutionOptions.versionTime,
@@ -4680,90 +5602,79 @@ var DidBtcr2 = class {
4680
5602
  });
4681
5603
  }
4682
5604
  /**
4683
- * Entry point for section {@link https://dcdpr.github.io/did-btcr2/#read | 7.3 Update}.
4684
- * See specification for the {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process | Resolve Process}.
4685
- * See {@link Update | Update (class)} for class implementation.
5605
+ * Entry point for section {@link https://dcdpr.github.io/did-btcr2/#update | 7.3 Update}.
5606
+ *
5607
+ * Factory method that validates the update parameters and returns a sans-I/O
5608
+ * {@link Updater} state machine. The caller drives the updater through its
5609
+ * phases (Construct → Sign → Broadcast → Complete) by calling `advance()` and
5610
+ * `provide()`. The method package performs **zero I/O** — signing key retrieval
5611
+ * (or KMS delegation) and the on-chain broadcast are the caller's responsibility.
5612
+ *
5613
+ * For a fully-wired version with Bitcoin broadcast and key handling, see
5614
+ * `DidMethodApi.update()` in `@did-btcr2/api`.
4686
5615
  *
4687
- * BTCR2 DID documents can be updated by anchoring BTCR2 Updates to Bitcoin transactions. These transactions MAY be
4688
- * published to the Bitcoin network. Any property in the DID document may be updated except the id. Doing so would
4689
- * invalidate the DID document.
4690
- * @param params An object containing the parameters for the update operation.
5616
+ * @param params Update construction parameters.
4691
5617
  * @param {Btcr2DidDocument} params.sourceDocument The DID document being updated.
4692
- * @param {PatchOperation[]} params.patches The array of JSON Patch operations to apply to the sourceDocument.
4693
- * @param {string} params.sourceVersionId The version ID before applying the update.
4694
- * @param {string} params.verificationMethodId The verificationMethod ID to sign the update with.
4695
- * @param {string} params.beaconId The beacon ID associated with the update.
4696
- * @param {KeyBytes | HexString} [params.signingMaterial] Optional signing material (key bytes or hex string).
4697
- * @param {BitcoinConnection} [params.bitcoin] Optional Bitcoin network connection for announcing the update. If not provided, a default connection will be initialized.
4698
- * @return {Promise<SignedBTCR2Update>} Promise resolving to the signed BTCR2 update.
4699
- * @throws {UpdateError} if no verificationMethod, verificationMethod type is not `Multikey` or the publicKeyMultibase
4700
- * header is not `zQ3s`
4701
- */
4702
- static async update({
5618
+ * @param {PatchOperation[]} params.patches The JSON Patch operations to apply.
5619
+ * @param {number} params.sourceVersionId The version ID before applying the update.
5620
+ * @param {string} params.verificationMethodId The verification method ID to sign with.
5621
+ * @param {string} params.beaconId The beacon service ID to broadcast through.
5622
+ * @returns {Updater} A sans-I/O state machine for driving the update.
5623
+ * @throws {UpdateError} If the verification method is not authorized, not found,
5624
+ * not of type `Multikey`, or does not have a `zQ3s` publicKeyMultibase prefix.
5625
+ * Also throws if the beacon service is not found.
5626
+ */
5627
+ static update({
4703
5628
  sourceDocument,
4704
5629
  patches,
4705
5630
  sourceVersionId,
4706
5631
  verificationMethodId,
4707
- beaconId,
4708
- signingMaterial,
4709
- bitcoin
5632
+ beaconId
4710
5633
  }) {
4711
- if (!signingMaterial) {
4712
- throw new import_common17.UpdateError(
4713
- "Missing signing material for update",
4714
- import_common17.INVALID_DID_UPDATE,
4715
- { signingMaterial }
4716
- );
4717
- }
4718
- const secretKey = typeof signingMaterial === "string" ? (0, import_utils9.hexToBytes)(signingMaterial) : signingMaterial;
4719
5634
  if (!sourceDocument.capabilityInvocation?.some((vr) => vr === verificationMethodId)) {
4720
- throw new import_common17.UpdateError(
5635
+ throw new import_common19.UpdateError(
4721
5636
  "Invalid verificationMethodId: not authorized for capabilityInvocation",
4722
- import_common17.INVALID_DID_DOCUMENT,
5637
+ import_common19.INVALID_DID_DOCUMENT,
4723
5638
  sourceDocument
4724
5639
  );
4725
5640
  }
4726
5641
  const verificationMethod = this.getSigningMethod(sourceDocument, verificationMethodId);
4727
5642
  if (!verificationMethod) {
4728
- throw new import_common17.UpdateError(
5643
+ throw new import_common19.UpdateError(
4729
5644
  "Invalid verificationMethod: not found in source document",
4730
- import_common17.INVALID_DID_DOCUMENT,
5645
+ import_common19.INVALID_DID_DOCUMENT,
4731
5646
  { sourceDocument, verificationMethodId }
4732
5647
  );
4733
5648
  }
4734
5649
  if (verificationMethod.type !== "Multikey") {
4735
- throw new import_common17.UpdateError(
5650
+ throw new import_common19.UpdateError(
4736
5651
  'Invalid verificationMethod: verificationMethod.type must be "Multikey"',
4737
- import_common17.INVALID_DID_DOCUMENT,
5652
+ import_common19.INVALID_DID_DOCUMENT,
4738
5653
  verificationMethod
4739
5654
  );
4740
5655
  }
4741
5656
  if (verificationMethod.publicKeyMultibase?.slice(0, 4) !== "zQ3s") {
4742
- throw new import_common17.UpdateError(
5657
+ throw new import_common19.UpdateError(
4743
5658
  'Invalid verificationMethodId: publicKeyMultibase prefix must start with "zQ3s"',
4744
- import_common17.INVALID_DID_DOCUMENT,
5659
+ import_common19.INVALID_DID_DOCUMENT,
4745
5660
  verificationMethod
4746
5661
  );
4747
5662
  }
4748
- const update = Update.construct(sourceDocument, patches, sourceVersionId);
4749
- const signed = Update.sign(sourceDocument.id, update, verificationMethod, secretKey);
4750
5663
  const beaconService = sourceDocument.service.filter((service) => service.id === beaconId).filter((service) => !!service).shift();
4751
5664
  if (!beaconService) {
4752
- throw new import_common17.UpdateError(
5665
+ throw new import_common19.UpdateError(
4753
5666
  "No beacon service found for provided beaconId",
4754
- import_common17.INVALID_DID_UPDATE,
5667
+ import_common19.INVALID_DID_UPDATE,
4755
5668
  { sourceDocument, beaconId }
4756
5669
  );
4757
5670
  }
4758
- if (!bitcoin) {
4759
- throw new import_common17.UpdateError(
4760
- "Bitcoin connection required for update. Pass a configured `bitcoin` parameter or use DidBtcr2Api which injects it automatically.",
4761
- import_common17.INVALID_DID_UPDATE,
4762
- { beaconId }
4763
- );
4764
- }
4765
- await Update.announce(beaconService, signed, secretKey, bitcoin);
4766
- return signed;
5671
+ return new Updater({
5672
+ sourceDocument,
5673
+ patches,
5674
+ sourceVersionId,
5675
+ verificationMethod,
5676
+ beaconService
5677
+ });
4767
5678
  }
4768
5679
  /**
4769
5680
  * Given the W3C DID Document of a `did:btcr2` identifier, return the signing verification method that will be used
@@ -4779,7 +5690,7 @@ var DidBtcr2 = class {
4779
5690
  methodId ??= "#initialKey";
4780
5691
  const parsedDid = import_dids2.Did.parse(didDocument.id);
4781
5692
  if (parsedDid && parsedDid.method !== this.methodName) {
4782
- throw new import_common17.MethodError(`Method not supported: ${parsedDid.method}`, import_common17.METHOD_NOT_SUPPORTED, { identifier: didDocument.id });
5693
+ throw new import_common19.MethodError(`Method not supported: ${parsedDid.method}`, import_common19.METHOD_NOT_SUPPORTED, { identifier: didDocument.id });
4783
5694
  }
4784
5695
  const verificationMethod = didDocument.verificationMethod?.find(
4785
5696
  (vm) => Appendix.extractDidFragment(vm.id) === (Appendix.extractDidFragment(methodId) ?? Appendix.extractDidFragment(didDocument.assertionMethod?.[0]))
@@ -4795,7 +5706,7 @@ var DidBtcr2 = class {
4795
5706
  };
4796
5707
 
4797
5708
  // src/core/resolver.ts
4798
- var import_utils11 = require("@noble/curves/utils.js");
5709
+ var import_utils10 = require("@noble/curves/utils.js");
4799
5710
  var Resolver = class _Resolver {
4800
5711
  // --- Immutable inputs ---
4801
5712
  #didComponents;
@@ -4835,7 +5746,7 @@ var Resolver = class _Resolver {
4835
5746
  static deterministic(didComponents) {
4836
5747
  const genesisBytes = didComponents.genesisBytes;
4837
5748
  const did = Identifier.encode(genesisBytes, didComponents);
4838
- const { multibase } = new import_keypair5.CompressedSecp256k1PublicKey(genesisBytes);
5749
+ const { multibase } = new import_keypair4.CompressedSecp256k1PublicKey(genesisBytes);
4839
5750
  const service = BeaconUtils.generateBeaconServices({
4840
5751
  id: did,
4841
5752
  publicKey: genesisBytes,
@@ -4861,14 +5772,14 @@ var Resolver = class _Resolver {
4861
5772
  * @throws {ResolveError} InvalidDidDocument if not conformant to DID Core v1.1
4862
5773
  */
4863
5774
  static external(didComponents, genesisDocument) {
4864
- const genesisDocumentHash = (0, import_common18.canonicalHashBytes)(genesisDocument);
4865
- if (!(0, import_utils11.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
4866
- throw new import_common18.ResolveError(
5775
+ const genesisDocumentHash = (0, import_common20.canonicalHashBytes)(genesisDocument);
5776
+ if (!(0, import_utils10.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
5777
+ throw new import_common20.ResolveError(
4867
5778
  `Initial document mismatch: genesisBytes !== genesisDocumentHash`,
4868
- import_common18.INVALID_DID_DOCUMENT,
5779
+ import_common20.INVALID_DID_DOCUMENT,
4869
5780
  {
4870
- genesisBytes: (0, import_common18.encode)(didComponents.genesisBytes, "hex"),
4871
- genesisDocumentHash: (0, import_common18.encode)(genesisDocumentHash, "hex")
5781
+ genesisBytes: (0, import_common20.encode)(didComponents.genesisBytes, "hex"),
5782
+ genesisDocumentHash: (0, import_common20.encode)(genesisDocumentHash, "hex")
4872
5783
  }
4873
5784
  );
4874
5785
  }
@@ -4887,12 +5798,12 @@ var Resolver = class _Resolver {
4887
5798
  const updateMap = /* @__PURE__ */ new Map();
4888
5799
  if (sidecar.updates?.length)
4889
5800
  for (const update of sidecar.updates) {
4890
- updateMap.set((0, import_common18.canonicalHash)(update, { encoding: "hex" }), update);
5801
+ updateMap.set((0, import_common20.canonicalHash)(update, { encoding: "hex" }), update);
4891
5802
  }
4892
5803
  const casMap = /* @__PURE__ */ new Map();
4893
5804
  if (sidecar.casUpdates?.length)
4894
5805
  for (const update of sidecar.casUpdates) {
4895
- casMap.set((0, import_common18.canonicalHash)(update, { encoding: "hex" }), update);
5806
+ casMap.set((0, import_common20.canonicalHash)(update, { encoding: "hex" }), update);
4896
5807
  }
4897
5808
  const smtMap = /* @__PURE__ */ new Map();
4898
5809
  if (sidecar.smtProofs?.length)
@@ -4925,12 +5836,12 @@ var Resolver = class _Resolver {
4925
5836
  }
4926
5837
  };
4927
5838
  for (const [update, block] of updates) {
4928
- const currentDocumentHash = (0, import_common18.canonicalHashBytes)(response.didDocument);
4929
- const blocktime = import_common18.DateUtils.blocktimeToTimestamp(block.time);
4930
- response.metadata.updated = import_common18.DateUtils.toISOStringNonFractional(blocktime);
5839
+ const currentDocumentHash = (0, import_common20.canonicalHashBytes)(response.didDocument);
5840
+ const blocktime = import_common20.DateUtils.blocktimeToTimestamp(block.time);
5841
+ response.metadata.updated = import_common20.DateUtils.toISOStringNonFractional(blocktime);
4931
5842
  response.metadata.confirmations = block.confirmations;
4932
5843
  if (versionTime) {
4933
- if (blocktime > import_common18.DateUtils.dateStringToTimestamp(versionTime)) {
5844
+ if (blocktime > import_common20.DateUtils.dateStringToTimestamp(versionTime)) {
4934
5845
  return response;
4935
5846
  }
4936
5847
  }
@@ -4938,22 +5849,22 @@ var Resolver = class _Resolver {
4938
5849
  updateHashHistory.push(currentDocumentHash);
4939
5850
  this.confirmDuplicate(update, updateHashHistory);
4940
5851
  } else if (update.targetVersionId === currentVersionId + 1) {
4941
- const sourceHashBytes = (0, import_common18.decode)(update.sourceHash, "base64urlnopad");
4942
- if (!(0, import_utils11.equalBytes)(sourceHashBytes, currentDocumentHash)) {
4943
- throw new import_common18.ResolveError(
5852
+ const sourceHashBytes = (0, import_common20.decode)(update.sourceHash, "base64urlnopad");
5853
+ if (!(0, import_utils10.equalBytes)(sourceHashBytes, currentDocumentHash)) {
5854
+ throw new import_common20.ResolveError(
4944
5855
  `Hash mismatch: update.sourceHash !== currentDocumentHash`,
4945
- import_common18.INVALID_DID_UPDATE,
5856
+ import_common20.INVALID_DID_UPDATE,
4946
5857
  {
4947
5858
  sourceHash: update.sourceHash,
4948
- currentDocumentHash: (0, import_common18.encode)(currentDocumentHash, "hex")
5859
+ currentDocumentHash: (0, import_common20.encode)(currentDocumentHash, "hex")
4949
5860
  }
4950
5861
  );
4951
5862
  }
4952
5863
  response.didDocument = this.applyUpdate(response.didDocument, update);
4953
- const unsignedUpdate = import_common18.JSONUtils.deleteKeys(update, ["proof"]);
4954
- updateHashHistory.push((0, import_common18.canonicalHashBytes)(unsignedUpdate));
5864
+ const unsignedUpdate = import_common20.JSONUtils.deleteKeys(update, ["proof"]);
5865
+ updateHashHistory.push((0, import_common20.canonicalHashBytes)(unsignedUpdate));
4955
5866
  } else if (update.targetVersionId > currentVersionId + 1) {
4956
- throw new import_common18.ResolveError(
5867
+ throw new import_common20.ResolveError(
4957
5868
  `Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
4958
5869
  "LATE_PUBLISHING_ERROR",
4959
5870
  {
@@ -4984,15 +5895,15 @@ var Resolver = class _Resolver {
4984
5895
  */
4985
5896
  static confirmDuplicate(update, updateHashHistory) {
4986
5897
  const { proof: _, ...unsignedUpdate } = update;
4987
- const unsignedUpdateHash = (0, import_common18.canonicalHashBytes)(unsignedUpdate);
5898
+ const unsignedUpdateHash = (0, import_common20.canonicalHashBytes)(unsignedUpdate);
4988
5899
  const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
4989
- if (!(0, import_utils11.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
4990
- throw new import_common18.ResolveError(
5900
+ if (!(0, import_utils10.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
5901
+ throw new import_common20.ResolveError(
4991
5902
  `Invalid duplicate: unsigned update hash does not match historical hash`,
4992
- import_common18.LATE_PUBLISHING_ERROR,
5903
+ import_common20.LATE_PUBLISHING_ERROR,
4993
5904
  {
4994
- unsignedUpdateHash: (0, import_common18.encode)(unsignedUpdateHash, "hex"),
4995
- historicalHash: (0, import_common18.encode)(historicalUpdateHash, "hex")
5905
+ unsignedUpdateHash: (0, import_common20.encode)(unsignedUpdateHash, "hex"),
5906
+ historicalHash: (0, import_common20.encode)(historicalUpdateHash, "hex")
4996
5907
  }
4997
5908
  );
4998
5909
  }
@@ -5007,42 +5918,42 @@ var Resolver = class _Resolver {
5007
5918
  static applyUpdate(currentDocument, update) {
5008
5919
  const capabilityId = update.proof?.capability;
5009
5920
  if (!capabilityId) {
5010
- throw new import_common18.ResolveError("No root capability found in update", import_common18.INVALID_DID_UPDATE, update);
5921
+ throw new import_common20.ResolveError("No root capability found in update", import_common20.INVALID_DID_UPDATE, update);
5011
5922
  }
5012
5923
  const rootCapability = Appendix.dereferenceZcapId(capabilityId);
5013
5924
  const { invocationTarget, controller: rootController } = rootCapability;
5014
5925
  if (![invocationTarget, rootController].every((id) => id === currentDocument.id)) {
5015
- throw new import_common18.ResolveError(
5926
+ throw new import_common20.ResolveError(
5016
5927
  "Invalid root capability",
5017
- import_common18.INVALID_DID_UPDATE,
5928
+ import_common20.INVALID_DID_UPDATE,
5018
5929
  { rootCapability, currentDocument }
5019
5930
  );
5020
5931
  }
5021
5932
  const verificationMethodId = update.proof?.verificationMethod;
5022
5933
  if (!verificationMethodId) {
5023
- throw new import_common18.ResolveError("No verificationMethod found in update", import_common18.INVALID_DID_UPDATE, update);
5934
+ throw new import_common20.ResolveError("No verificationMethod found in update", import_common20.INVALID_DID_UPDATE, update);
5024
5935
  }
5025
5936
  const vm = DidBtcr2.getSigningMethod(currentDocument, verificationMethodId);
5026
- const multikey = import_cryptosuite2.SchnorrMultikey.fromVerificationMethod(vm);
5027
- const cryptosuite = new import_cryptosuite2.BIP340Cryptosuite(multikey);
5028
- const canonicalUpdate = (0, import_common18.canonicalize)(update);
5029
- const diProof = new import_cryptosuite2.BIP340DataIntegrityProof(cryptosuite);
5937
+ const multikey = import_cryptosuite3.SchnorrMultikey.fromVerificationMethod(vm);
5938
+ const cryptosuite = new import_cryptosuite3.BIP340Cryptosuite(multikey);
5939
+ const canonicalUpdate = (0, import_common20.canonicalize)(update);
5940
+ const diProof = new import_cryptosuite3.BIP340DataIntegrityProof(cryptosuite);
5030
5941
  const verificationResult = diProof.verifyProof(canonicalUpdate, "capabilityInvocation");
5031
5942
  if (!verificationResult.verified) {
5032
- throw new import_common18.ResolveError(
5943
+ throw new import_common20.ResolveError(
5033
5944
  "Invalid update: proof not verified",
5034
- import_common18.INVALID_DID_UPDATE,
5945
+ import_common20.INVALID_DID_UPDATE,
5035
5946
  verificationResult
5036
5947
  );
5037
5948
  }
5038
- const updatedDocument = import_common18.JSONPatch.apply(currentDocument, update.patch);
5949
+ const updatedDocument = import_common20.JSONPatch.apply(currentDocument, update.patch);
5039
5950
  DidDocument.validate(updatedDocument);
5040
- const currentDocumentHash = (0, import_common18.canonicalHashBytes)(updatedDocument);
5041
- const updateTargetHash = (0, import_common18.decode)(update.targetHash);
5042
- if (!(0, import_utils11.equalBytes)(updateTargetHash, currentDocumentHash)) {
5043
- throw new import_common18.ResolveError(
5951
+ const currentDocumentHash = (0, import_common20.canonicalHashBytes)(updatedDocument);
5952
+ const updateTargetHash = (0, import_common20.decode)(update.targetHash);
5953
+ if (!(0, import_utils10.equalBytes)(updateTargetHash, currentDocumentHash)) {
5954
+ throw new import_common20.ResolveError(
5044
5955
  `Invalid update: update.targetHash !== currentDocumentHash`,
5045
- import_common18.INVALID_DID_UPDATE,
5956
+ import_common20.INVALID_DID_UPDATE,
5046
5957
  { updateTargetHash, currentDocumentHash }
5047
5958
  );
5048
5959
  }
@@ -5070,7 +5981,7 @@ var Resolver = class _Resolver {
5070
5981
  this.#phase = "BeaconDiscovery" /* BeaconDiscovery */;
5071
5982
  continue;
5072
5983
  }
5073
- const genesisHash = (0, import_common18.encode)(this.#didComponents.genesisBytes, "hex");
5984
+ const genesisHash = (0, import_common20.encode)(this.#didComponents.genesisBytes, "hex");
5074
5985
  return {
5075
5986
  status: "action-required",
5076
5987
  needs: [{ kind: "NeedGenesisDocument", genesisHash }]
@@ -5174,21 +6085,21 @@ var Resolver = class _Resolver {
5174
6085
  }
5175
6086
  case "NeedCASAnnouncement": {
5176
6087
  const announcement = data;
5177
- this.#sidecarData.casMap.set((0, import_common18.canonicalHash)(announcement, { encoding: "hex" }), announcement);
6088
+ this.#sidecarData.casMap.set((0, import_common20.canonicalHash)(announcement, { encoding: "hex" }), announcement);
5178
6089
  break;
5179
6090
  }
5180
6091
  case "NeedSignedUpdate": {
5181
6092
  const update = data;
5182
- this.#sidecarData.updateMap.set((0, import_common18.canonicalHash)(update, { encoding: "hex" }), update);
6093
+ this.#sidecarData.updateMap.set((0, import_common20.canonicalHash)(update, { encoding: "hex" }), update);
5183
6094
  break;
5184
6095
  }
5185
6096
  case "NeedSMTProof": {
5186
6097
  const smtNeed = need;
5187
6098
  const proof = data;
5188
6099
  if (proof.id !== smtNeed.smtRootHash) {
5189
- throw new import_common18.ResolveError(
6100
+ throw new import_common20.ResolveError(
5190
6101
  `SMT proof root hash mismatch: expected ${smtNeed.smtRootHash}, got ${proof.id}`,
5191
- import_common18.INVALID_DID_UPDATE,
6102
+ import_common20.INVALID_DID_UPDATE,
5192
6103
  { expected: smtNeed.smtRootHash, actual: proof.id }
5193
6104
  );
5194
6105
  }
@@ -5200,12 +6111,12 @@ var Resolver = class _Resolver {
5200
6111
  };
5201
6112
 
5202
6113
  // src/utils/did-document-builder.ts
5203
- var import_common19 = require("@did-btcr2/common");
6114
+ var import_common21 = require("@did-btcr2/common");
5204
6115
  var DidDocumentBuilder = class {
5205
6116
  document = {};
5206
6117
  constructor(initialDocument) {
5207
6118
  if (!initialDocument.id) {
5208
- throw new import_common19.DidDocumentError('Missing required "id" property', import_common19.INVALID_DID_DOCUMENT, initialDocument);
6119
+ throw new import_common21.DidDocumentError('Missing required "id" property', import_common21.INVALID_DID_DOCUMENT, initialDocument);
5209
6120
  }
5210
6121
  this.document.id = initialDocument.id;
5211
6122
  this.document.verificationMethod = initialDocument.verificationMethod ?? [];
@@ -5257,6 +6168,7 @@ var DidDocumentBuilder = class {
5257
6168
  0 && (module.exports = {
5258
6169
  AGGREGATED_NONCE,
5259
6170
  AGGREGATION_MESSAGE_PREFIX,
6171
+ AGGREGATION_WIRE_VERSION,
5260
6172
  AUTHORIZATION_REQUEST,
5261
6173
  AggregateBeaconError,
5262
6174
  AggregationCohort,
@@ -5283,6 +6195,10 @@ var DidDocumentBuilder = class {
5283
6195
  COHORT_OPT_IN,
5284
6196
  COHORT_OPT_IN_ACCEPT,
5285
6197
  COHORT_READY,
6198
+ CONSOLE_LOGGER,
6199
+ DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
6200
+ DEFAULT_BROADCAST_LOOKBACK_MS,
6201
+ DEFAULT_MAX_UPDATE_SIZE_BYTES,
5286
6202
  DEFAULT_NOSTR_RELAYS,
5287
6203
  DID_REGEX,
5288
6204
  DISTRIBUTE_AGGREGATED_DATA,
@@ -5300,6 +6216,7 @@ var DidDocumentBuilder = class {
5300
6216
  ParticipantCohortPhase,
5301
6217
  Resolver,
5302
6218
  SIGNATURE_AUTHORIZATION,
6219
+ SILENT_LOGGER,
5303
6220
  SMTBeacon,
5304
6221
  SMTBeaconError,
5305
6222
  SUBMIT_UPDATE,
@@ -5308,12 +6225,14 @@ var DidDocumentBuilder = class {
5308
6225
  SigningSessionPhase,
5309
6226
  SingletonBeacon,
5310
6227
  SingletonBeaconError,
6228
+ StaticFeeEstimator,
5311
6229
  TransportAdapterError,
5312
6230
  TransportError,
5313
6231
  TransportFactory,
5314
6232
  TypedEventEmitter,
5315
- Update,
6233
+ Updater,
5316
6234
  VALIDATION_ACK,
6235
+ buildAggregationBeaconTx,
5317
6236
  createAggregatedNonceMessage,
5318
6237
  createAuthorizationRequestMessage,
5319
6238
  createCohortAdvertMessage,
@@ -5325,8 +6244,22 @@ var DidDocumentBuilder = class {
5325
6244
  createSignatureAuthorizationMessage,
5326
6245
  createSubmitUpdateMessage,
5327
6246
  createValidationAckMessage,
6247
+ getBeaconStrategy,
6248
+ isAggregatedNonceMessage,
5328
6249
  isAggregationMessageType,
6250
+ isAuthorizationRequestMessage,
6251
+ isCohortAdvertMessage,
6252
+ isCohortOptInAcceptMessage,
6253
+ isCohortOptInMessage,
6254
+ isCohortReadyMessage,
6255
+ isDistributeAggregatedDataMessage,
5329
6256
  isKeygenMessageType,
6257
+ isNonceContributionMessage,
5330
6258
  isSignMessageType,
5331
- isUpdateMessageType
6259
+ isSignatureAuthorizationMessage,
6260
+ isSubmitUpdateMessage,
6261
+ isUpdateMessageType,
6262
+ isValidationAckMessage,
6263
+ opReturnScript,
6264
+ registerBeaconStrategy
5332
6265
  });