@did-btcr2/method 0.28.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 (102) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/browser.js +20092 -31631
  3. package/dist/browser.mjs +20019 -31558
  4. package/dist/cjs/index.js +1164 -364
  5. package/dist/esm/core/aggregation/beacon-strategy.js +62 -0
  6. package/dist/esm/core/aggregation/beacon-strategy.js.map +1 -0
  7. package/dist/esm/core/aggregation/cohort.js +31 -8
  8. package/dist/esm/core/aggregation/cohort.js.map +1 -1
  9. package/dist/esm/core/aggregation/logger.js +15 -0
  10. package/dist/esm/core/aggregation/logger.js.map +1 -0
  11. package/dist/esm/core/aggregation/messages/base.js +12 -1
  12. package/dist/esm/core/aggregation/messages/base.js.map +1 -1
  13. package/dist/esm/core/aggregation/messages/bodies.js +90 -0
  14. package/dist/esm/core/aggregation/messages/bodies.js.map +1 -0
  15. package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
  16. package/dist/esm/core/aggregation/messages/index.js +1 -0
  17. package/dist/esm/core/aggregation/messages/index.js.map +1 -1
  18. package/dist/esm/core/aggregation/participant.js +39 -46
  19. package/dist/esm/core/aggregation/participant.js.map +1 -1
  20. package/dist/esm/core/aggregation/runner/participant-runner.js +33 -7
  21. package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
  22. package/dist/esm/core/aggregation/runner/service-runner.js +198 -19
  23. package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
  24. package/dist/esm/core/aggregation/service.js +143 -15
  25. package/dist/esm/core/aggregation/service.js.map +1 -1
  26. package/dist/esm/core/aggregation/signing-session.js +44 -5
  27. package/dist/esm/core/aggregation/signing-session.js.map +1 -1
  28. package/dist/esm/core/aggregation/transport/didcomm.js +9 -0
  29. package/dist/esm/core/aggregation/transport/didcomm.js.map +1 -1
  30. package/dist/esm/core/aggregation/transport/nostr.js +245 -16
  31. package/dist/esm/core/aggregation/transport/nostr.js.map +1 -1
  32. package/dist/esm/core/beacon/beacon.js +147 -61
  33. package/dist/esm/core/beacon/beacon.js.map +1 -1
  34. package/dist/esm/core/beacon/utils.js +14 -9
  35. package/dist/esm/core/beacon/utils.js.map +1 -1
  36. package/dist/esm/did-btcr2.js +0 -4
  37. package/dist/esm/did-btcr2.js.map +1 -1
  38. package/dist/esm/index.js +2 -0
  39. package/dist/esm/index.js.map +1 -1
  40. package/dist/esm/utils/did-document.js +2 -2
  41. package/dist/esm/utils/did-document.js.map +1 -1
  42. package/dist/types/core/aggregation/beacon-strategy.d.ts +52 -0
  43. package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -0
  44. package/dist/types/core/aggregation/cohort.d.ts +20 -3
  45. package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
  46. package/dist/types/core/aggregation/logger.d.ts +22 -0
  47. package/dist/types/core/aggregation/logger.d.ts.map +1 -0
  48. package/dist/types/core/aggregation/messages/base.d.ts +13 -1
  49. package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
  50. package/dist/types/core/aggregation/messages/bodies.d.ts +130 -0
  51. package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -0
  52. package/dist/types/core/aggregation/messages/factories.d.ts +1 -0
  53. package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
  54. package/dist/types/core/aggregation/messages/index.d.ts +1 -0
  55. package/dist/types/core/aggregation/messages/index.d.ts.map +1 -1
  56. package/dist/types/core/aggregation/participant.d.ts +2 -0
  57. package/dist/types/core/aggregation/participant.d.ts.map +1 -1
  58. package/dist/types/core/aggregation/runner/events.d.ts +32 -6
  59. package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
  60. package/dist/types/core/aggregation/runner/participant-runner.d.ts +7 -5
  61. package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
  62. package/dist/types/core/aggregation/runner/service-runner.d.ts +33 -3
  63. package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
  64. package/dist/types/core/aggregation/service.d.ts +33 -2
  65. package/dist/types/core/aggregation/service.d.ts.map +1 -1
  66. package/dist/types/core/aggregation/signing-session.d.ts +5 -1
  67. package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
  68. package/dist/types/core/aggregation/transport/didcomm.d.ts +3 -0
  69. package/dist/types/core/aggregation/transport/didcomm.d.ts.map +1 -1
  70. package/dist/types/core/aggregation/transport/nostr.d.ts +99 -1
  71. package/dist/types/core/aggregation/transport/nostr.d.ts.map +1 -1
  72. package/dist/types/core/aggregation/transport/transport.d.ts +25 -0
  73. package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
  74. package/dist/types/core/beacon/beacon.d.ts +85 -18
  75. package/dist/types/core/beacon/beacon.d.ts.map +1 -1
  76. package/dist/types/core/beacon/utils.d.ts +2 -2
  77. package/dist/types/core/beacon/utils.d.ts.map +1 -1
  78. package/dist/types/did-btcr2.d.ts.map +1 -1
  79. package/dist/types/index.d.ts +2 -0
  80. package/dist/types/index.d.ts.map +1 -1
  81. package/package.json +5 -7
  82. package/src/core/aggregation/beacon-strategy.ts +123 -0
  83. package/src/core/aggregation/cohort.ts +34 -8
  84. package/src/core/aggregation/logger.ts +33 -0
  85. package/src/core/aggregation/messages/base.ts +20 -5
  86. package/src/core/aggregation/messages/bodies.ts +223 -0
  87. package/src/core/aggregation/messages/factories.ts +1 -0
  88. package/src/core/aggregation/messages/index.ts +1 -0
  89. package/src/core/aggregation/participant.ts +40 -46
  90. package/src/core/aggregation/runner/events.ts +27 -3
  91. package/src/core/aggregation/runner/participant-runner.ts +41 -7
  92. package/src/core/aggregation/runner/service-runner.ts +227 -19
  93. package/src/core/aggregation/service.ts +189 -20
  94. package/src/core/aggregation/signing-session.ts +65 -7
  95. package/src/core/aggregation/transport/didcomm.ts +17 -0
  96. package/src/core/aggregation/transport/nostr.ts +266 -23
  97. package/src/core/aggregation/transport/transport.ts +33 -0
  98. package/src/core/beacon/beacon.ts +217 -76
  99. package/src/core/beacon/utils.ts +16 -11
  100. package/src/did-btcr2.ts +0 -5
  101. package/src/index.ts +2 -0
  102. package/src/utils/did-document.ts +2 -2
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,
@@ -90,6 +96,7 @@ __export(index_exports, {
90
96
  TypedEventEmitter: () => TypedEventEmitter,
91
97
  Updater: () => Updater,
92
98
  VALIDATION_ACK: () => VALIDATION_ACK,
99
+ buildAggregationBeaconTx: () => buildAggregationBeaconTx,
93
100
  createAggregatedNonceMessage: () => createAggregatedNonceMessage,
94
101
  createAuthorizationRequestMessage: () => createAuthorizationRequestMessage,
95
102
  createCohortAdvertMessage: () => createCohortAdvertMessage,
@@ -101,41 +108,114 @@ __export(index_exports, {
101
108
  createSignatureAuthorizationMessage: () => createSignatureAuthorizationMessage,
102
109
  createSubmitUpdateMessage: () => createSubmitUpdateMessage,
103
110
  createValidationAckMessage: () => createValidationAckMessage,
111
+ getBeaconStrategy: () => getBeaconStrategy,
112
+ isAggregatedNonceMessage: () => isAggregatedNonceMessage,
104
113
  isAggregationMessageType: () => isAggregationMessageType,
114
+ isAuthorizationRequestMessage: () => isAuthorizationRequestMessage,
115
+ isCohortAdvertMessage: () => isCohortAdvertMessage,
116
+ isCohortOptInAcceptMessage: () => isCohortOptInAcceptMessage,
117
+ isCohortOptInMessage: () => isCohortOptInMessage,
118
+ isCohortReadyMessage: () => isCohortReadyMessage,
119
+ isDistributeAggregatedDataMessage: () => isDistributeAggregatedDataMessage,
105
120
  isKeygenMessageType: () => isKeygenMessageType,
121
+ isNonceContributionMessage: () => isNonceContributionMessage,
106
122
  isSignMessageType: () => isSignMessageType,
107
- isUpdateMessageType: () => isUpdateMessageType
123
+ isSignatureAuthorizationMessage: () => isSignatureAuthorizationMessage,
124
+ isSubmitUpdateMessage: () => isSubmitUpdateMessage,
125
+ isUpdateMessageType: () => isUpdateMessageType,
126
+ isValidationAckMessage: () => isValidationAckMessage,
127
+ opReturnScript: () => opReturnScript,
128
+ registerBeaconStrategy: () => registerBeaconStrategy
108
129
  });
109
130
  module.exports = __toCommonJS(index_exports);
110
131
 
111
132
  // src/core/aggregation/service.ts
133
+ var import_common4 = require("@did-btcr2/common");
134
+ var import_cryptosuite = require("@did-btcr2/cryptosuite");
112
135
  var import_utils2 = require("@noble/hashes/utils");
113
136
 
114
- // src/core/aggregation/cohort.ts
115
- var import_common2 = require("@did-btcr2/common");
137
+ // src/core/aggregation/beacon-strategy.ts
138
+ var import_common = require("@did-btcr2/common");
116
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");
117
197
  var import_utils = require("@noble/hashes/utils");
198
+ var import_btc_signer = require("@scure/btc-signer");
118
199
  var import_musig2 = require("@scure/btc-signer/musig2");
119
- var import_bitcoinjs_lib = require("bitcoinjs-lib");
120
200
 
121
201
  // src/core/aggregation/errors.ts
122
- var import_common = require("@did-btcr2/common");
123
- var AggregationServiceError = class extends import_common.MethodError {
202
+ var import_common2 = require("@did-btcr2/common");
203
+ var AggregationServiceError = class extends import_common2.MethodError {
124
204
  constructor(message, type = "AggregationServiceError", data) {
125
205
  super(message, type, data);
126
206
  }
127
207
  };
128
- var AggregationParticipantError = class extends import_common.MethodError {
208
+ var AggregationParticipantError = class extends import_common2.MethodError {
129
209
  constructor(message, type = "AggregationParticipantError", data) {
130
210
  super(message, type, data);
131
211
  }
132
212
  };
133
- var AggregationCohortError = class extends import_common.MethodError {
213
+ var AggregationCohortError = class extends import_common2.MethodError {
134
214
  constructor(message, type = "AggregationCohortError", data) {
135
215
  super(message, type, data);
136
216
  }
137
217
  };
138
- var SigningSessionError = class extends import_common.MethodError {
218
+ var SigningSessionError = class extends import_common2.MethodError {
139
219
  constructor(message, type = "SigningSessionError", data) {
140
220
  super(message, type, data);
141
221
  }
@@ -155,10 +235,21 @@ var AggregationCohort = class {
155
235
  beaconType;
156
236
  /** List of participant DIDs that have been accepted into the cohort. */
157
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();
158
245
  /** Sorted list of cohort participants' compressed public keys. */
159
246
  #cohortKeys = [];
160
- /** Taproot tweak (BIP-341 key-path-only). */
161
- 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();
162
253
  /** The n-of-n MuSig2 Taproot beacon address. */
163
254
  beaconAddress = "";
164
255
  /** Pending DID updates submitted by participants, keyed by DID. */
@@ -189,7 +280,7 @@ var AggregationCohort = class {
189
280
  }
190
281
  /**
191
282
  * Computes the n-of-n MuSig2 Taproot beacon address from cohort keys.
192
- * Sets `trMerkleRoot` to the BIP-341 key-path-only tweak.
283
+ * Sets `tapTweak` to the BIP-341 key-path-only tweak.
193
284
  */
194
285
  computeBeaconAddress() {
195
286
  if (this.#cohortKeys.length === 0) {
@@ -201,8 +292,8 @@ var AggregationCohort = class {
201
292
  }
202
293
  const keyAggContext = (0, import_musig2.keyAggregate)(this.#cohortKeys);
203
294
  const aggPubkey = (0, import_musig2.keyAggExport)(keyAggContext);
204
- const payment = import_bitcoinjs_lib.payments.p2tr({ internalPubkey: aggPubkey });
205
- 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);
206
297
  if (!payment.address) {
207
298
  throw new AggregationCohortError(
208
299
  "Failed to compute Taproot address",
@@ -236,6 +327,18 @@ var AggregationCohort = class {
236
327
  );
237
328
  }
238
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
+ }
239
342
  addUpdate(participantDid, signedUpdate) {
240
343
  if (!this.participants.includes(participantDid)) {
241
344
  throw new AggregationCohortError(
@@ -264,10 +367,10 @@ var AggregationCohort = class {
264
367
  }
265
368
  const announcement = {};
266
369
  for (const [did, signedUpdate] of this.pendingUpdates) {
267
- announcement[did] = (0, import_common2.canonicalHash)(signedUpdate);
370
+ announcement[did] = (0, import_common3.canonicalHash)(signedUpdate);
268
371
  }
269
372
  this.casAnnouncement = announcement;
270
- this.signalBytes = (0, import_common2.hash)((0, import_common2.canonicalize)(announcement));
373
+ this.signalBytes = (0, import_common3.hash)((0, import_common3.canonicalize)(announcement));
271
374
  return announcement;
272
375
  }
273
376
  /**
@@ -283,11 +386,11 @@ var AggregationCohort = class {
283
386
  { cohortId: this.id }
284
387
  );
285
388
  }
286
- const tree = new import_smt.BTCR2MerkleTree();
389
+ const tree = new import_smt2.BTCR2MerkleTree();
287
390
  const entries = [];
288
391
  const encoder = new TextEncoder();
289
392
  for (const [did, signedUpdate] of this.pendingUpdates) {
290
- const canonicalBytes = encoder.encode((0, import_common2.canonicalize)(signedUpdate));
393
+ const canonicalBytes = encoder.encode((0, import_common3.canonicalize)(signedUpdate));
291
394
  const nonce = (0, import_utils.randomBytes)(32);
292
395
  entries.push({ did, nonce, signedUpdate: canonicalBytes });
293
396
  }
@@ -329,28 +432,17 @@ var AggregationCohort = class {
329
432
  }
330
433
  };
331
434
 
332
- // src/core/aggregation/messages/constants.ts
333
- var AGGREGATION_MESSAGE_PREFIX = "https://btcr2.dev/aggregation";
334
- var COHORT_ADVERT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_advert`;
335
- var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
336
- var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
337
- var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
338
- var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
339
- var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
340
- var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
341
- var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
342
- var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
343
- var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
344
- var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
345
-
346
435
  // src/core/aggregation/messages/base.ts
436
+ var AGGREGATION_WIRE_VERSION = 1;
347
437
  var BaseMessage = class {
348
438
  type;
439
+ version;
349
440
  to;
350
441
  from;
351
442
  body;
352
- constructor({ type, to, from: from2, body }) {
443
+ constructor({ type, version, to, from: from2, body }) {
353
444
  this.type = type;
445
+ this.version = version ?? AGGREGATION_WIRE_VERSION;
354
446
  this.to = to;
355
447
  this.from = from2;
356
448
  this.body = body;
@@ -362,6 +454,7 @@ var BaseMessage = class {
362
454
  toJSON() {
363
455
  return {
364
456
  type: this.type,
457
+ version: this.version,
365
458
  to: this.to,
366
459
  from: this.from,
367
460
  body: this.body
@@ -369,6 +462,20 @@ var BaseMessage = class {
369
462
  }
370
463
  };
371
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
+
372
479
  // src/core/aggregation/messages/factories.ts
373
480
  function createCohortAdvertMessage(fields) {
374
481
  const { from: from2, ...body } = fields;
@@ -456,8 +563,8 @@ var SigningSessionPhase = /* @__PURE__ */ ((SigningSessionPhase2) => {
456
563
  })(SigningSessionPhase || {});
457
564
 
458
565
  // src/core/aggregation/signing-session.ts
566
+ var import_btc_signer2 = require("@scure/btc-signer");
459
567
  var musig2 = __toESM(require("@scure/btc-signer/musig2"), 1);
460
- var import_bitcoinjs_lib2 = require("bitcoinjs-lib");
461
568
  var BeaconSigningSession = class {
462
569
  /** Unique identifier for this signing session. */
463
570
  id;
@@ -499,11 +606,11 @@ var BeaconSigningSession = class {
499
606
  "SIGHASH_ERROR"
500
607
  );
501
608
  }
502
- return this.pendingTx.hashForWitnessV1(
609
+ return this.pendingTx.preimageWitnessV1(
503
610
  0,
504
611
  this.prevOutScripts,
505
- this.prevOutValues,
506
- import_bitcoinjs_lib2.Transaction.SIGHASH_DEFAULT
612
+ import_btc_signer2.SigHash.DEFAULT,
613
+ this.prevOutValues
507
614
  );
508
615
  }
509
616
  addNonceContribution(participantDid, nonceContribution) {
@@ -514,6 +621,13 @@ var BeaconSigningSession = class {
514
621
  { phase: this.phase }
515
622
  );
516
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
+ }
517
631
  if (nonceContribution.length !== 66) {
518
632
  throw new SigningSessionError(
519
633
  `Invalid nonce contribution: expected 66 bytes, got ${nonceContribution.length}.`,
@@ -549,6 +663,13 @@ var BeaconSigningSession = class {
549
663
  "INVALID_PHASE"
550
664
  );
551
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
+ }
552
673
  if (this.partialSignatures.has(participantDid)) {
553
674
  throw new SigningSessionError(
554
675
  `Duplicate partial signature from ${participantDid}.`,
@@ -574,9 +695,39 @@ var BeaconSigningSession = class {
574
695
  this.aggregatedNonce,
575
696
  this.cohort.cohortKeys,
576
697
  this.sigHash,
577
- [this.cohort.trMerkleRoot],
698
+ [this.cohort.tapTweak],
578
699
  [true]
579
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
+ }
580
731
  this.signature = session.partialSigAgg([...this.partialSignatures.values()]);
581
732
  this.phase = "Complete" /* Complete */;
582
733
  return this.signature;
@@ -594,6 +745,10 @@ var BeaconSigningSession = class {
594
745
  /**
595
746
  * Generates a partial signature using the participant's secret key + secret nonce.
596
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.
597
752
  */
598
753
  generatePartialSignature(participantSecretKey) {
599
754
  if (!this.aggregatedNonce) {
@@ -606,10 +761,13 @@ var BeaconSigningSession = class {
606
761
  this.aggregatedNonce,
607
762
  this.cohort.cohortKeys,
608
763
  this.sigHash,
609
- [this.cohort.trMerkleRoot],
764
+ [this.cohort.tapTweak],
610
765
  [true]
611
766
  );
612
- 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;
613
771
  }
614
772
  isComplete() {
615
773
  return this.phase === "Complete" /* Complete */;
@@ -620,16 +778,32 @@ var BeaconSigningSession = class {
620
778
  };
621
779
 
622
780
  // src/core/aggregation/service.ts
781
+ var DEFAULT_MAX_UPDATE_SIZE_BYTES = 256 * 1024;
623
782
  var AggregationService = class {
624
783
  did;
625
784
  keys;
785
+ maxUpdateSizeBytes;
626
786
  /** Per-cohort state, keyed by cohortId. */
627
787
  #cohortStates = /* @__PURE__ */ new Map();
628
- constructor({ did, keys }) {
788
+ constructor({ did, keys, maxUpdateSizeBytes }) {
629
789
  this.did = did;
630
790
  this.keys = keys;
791
+ this.maxUpdateSizeBytes = maxUpdateSizeBytes ?? DEFAULT_MAX_UPDATE_SIZE_BYTES;
631
792
  }
632
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
+ }
633
807
  const type = message.type;
634
808
  switch (type) {
635
809
  case COHORT_OPT_IN:
@@ -651,6 +825,18 @@ var AggregationService = class {
651
825
  break;
652
826
  }
653
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
+ }
654
840
  /**
655
841
  * Create a new cohort with the given config. Returns the cohort ID.
656
842
  * Cohort starts in `Created` phase — call `advertise()` to broadcast.
@@ -667,7 +853,8 @@ var AggregationService = class {
667
853
  cohort,
668
854
  config,
669
855
  pendingOptIns: /* @__PURE__ */ new Map(),
670
- acceptedParticipants: /* @__PURE__ */ new Set()
856
+ acceptedParticipants: /* @__PURE__ */ new Set(),
857
+ rejections: []
671
858
  });
672
859
  return cohort.id;
673
860
  }
@@ -720,6 +907,7 @@ var AggregationService = class {
720
907
  const participantPk = message.body?.participantPk;
721
908
  const communicationPk = message.body?.communicationPk;
722
909
  if (!participantPk || !communicationPk) return;
910
+ if (state.acceptedParticipants.has(participantDid)) return;
723
911
  state.pendingOptIns.set(participantDid, {
724
912
  cohortId,
725
913
  participantDid,
@@ -753,6 +941,7 @@ var AggregationService = class {
753
941
  }
754
942
  state.acceptedParticipants.add(participantDid);
755
943
  state.cohort.participants.push(participantDid);
944
+ state.cohort.participantKeys.set(participantDid, optIn.participantPk);
756
945
  state.cohort.cohortKeys = [...state.cohort.cohortKeys, optIn.participantPk];
757
946
  return [createCohortOptInAcceptMessage({
758
947
  from: this.did,
@@ -803,6 +992,13 @@ var AggregationService = class {
803
992
  if (!state) return /* @__PURE__ */ new Map();
804
993
  return state.cohort.pendingUpdates;
805
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
+ */
806
1002
  #handleSubmitUpdate(message) {
807
1003
  const cohortId = message.body?.cohortId;
808
1004
  if (!cohortId) return;
@@ -810,7 +1006,31 @@ var AggregationService = class {
810
1006
  if (!state) return;
811
1007
  if (state.phase !== "CohortSet" /* CohortSet */ && state.phase !== "CollectingUpdates" /* CollectingUpdates */) return;
812
1008
  const signedUpdate = message.body?.signedUpdate;
813
- 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
+ }
814
1034
  state.cohort.addUpdate(message.from, signedUpdate);
815
1035
  if (state.phase === "CohortSet" /* CohortSet */) {
816
1036
  state.phase = "CollectingUpdates" /* CollectingUpdates */;
@@ -819,6 +1039,36 @@ var AggregationService = class {
819
1039
  state.phase = "UpdatesCollected" /* UpdatesCollected */;
820
1040
  }
821
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
+ }
822
1072
  /**
823
1073
  * Build the aggregated data structure (CAS Announcement or SMT tree) and
824
1074
  * return distribute messages to send to all participants for validation.
@@ -835,29 +1085,28 @@ var AggregationService = class {
835
1085
  { cohortId, phase: state.phase }
836
1086
  );
837
1087
  }
838
- if (state.config.beaconType === "CASBeacon") {
839
- state.cohort.buildCASAnnouncement();
840
- } else if (state.config.beaconType === "SMTBeacon") {
841
- state.cohort.buildSMTTree();
842
- } else {
1088
+ const strategy = getBeaconStrategy(state.config.beaconType);
1089
+ if (!strategy) {
843
1090
  throw new AggregationServiceError(
844
1091
  `Unsupported beacon type: ${state.config.beaconType}`,
845
1092
  "UNSUPPORTED_BEACON_TYPE",
846
1093
  { cohortId, beaconType: state.config.beaconType }
847
1094
  );
848
1095
  }
1096
+ strategy.buildAggregatedData(state.cohort);
849
1097
  const signalBytesHex = (0, import_utils2.bytesToHex)(state.cohort.signalBytes);
850
1098
  state.phase = "DataDistributed" /* DataDistributed */;
851
1099
  const messages = [];
852
1100
  for (const participantDid of state.cohort.participants) {
1101
+ const payload = strategy.getDistributePayload(state.cohort, participantDid);
853
1102
  messages.push(createDistributeAggregatedDataMessage({
854
1103
  from: this.did,
855
1104
  to: participantDid,
856
1105
  cohortId,
857
1106
  beaconType: state.config.beaconType,
858
1107
  signalBytesHex,
859
- casAnnouncement: state.config.beaconType === "CASBeacon" ? state.cohort.casAnnouncement : void 0,
860
- smtProof: state.config.beaconType === "SMTBeacon" ? state.cohort.smtProofs?.get(participantDid) : void 0
1108
+ casAnnouncement: payload.casAnnouncement,
1109
+ smtProof: payload.smtProof
861
1110
  }));
862
1111
  }
863
1112
  return messages;
@@ -919,6 +1168,14 @@ var AggregationService = class {
919
1168
  });
920
1169
  state.signingSession = session;
921
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
+ }
922
1179
  const messages = [];
923
1180
  for (const participantDid of state.cohort.participants) {
924
1181
  messages.push(createAuthorizationRequestMessage({
@@ -926,7 +1183,8 @@ var AggregationService = class {
926
1183
  to: participantDid,
927
1184
  cohortId,
928
1185
  sessionId: session.id,
929
- pendingTx: txData.tx.toHex(),
1186
+ pendingTx: txData.tx.hex,
1187
+ prevOutScriptHex: (0, import_utils2.bytesToHex)(prevOutScript),
930
1188
  prevOutValue: txData.prevOutValues[0]?.toString() ?? "0"
931
1189
  }));
932
1190
  }
@@ -990,7 +1248,7 @@ var AggregationService = class {
990
1248
  state.signingSession.addPartialSignature(message.from, partialSignature);
991
1249
  if (state.signingSession.partialSignatures.size === state.cohort.participants.length) {
992
1250
  const signature = state.signingSession.generateFinalSignature();
993
- state.signingSession.pendingTx.setWitness(0, [signature]);
1251
+ state.signingSession.pendingTx.updateInput(0, { finalScriptWitness: [signature] });
994
1252
  state.result = {
995
1253
  cohortId,
996
1254
  signature,
@@ -1019,13 +1277,19 @@ var AggregationService = class {
1019
1277
  get cohorts() {
1020
1278
  return [...this.#cohortStates.values()].map((s) => s.cohort);
1021
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
+ }
1022
1287
  };
1023
1288
 
1024
1289
  // src/core/aggregation/participant.ts
1025
- var import_common3 = require("@did-btcr2/common");
1026
- var import_smt2 = require("@did-btcr2/smt");
1290
+ var import_common5 = require("@did-btcr2/common");
1027
1291
  var import_utils3 = require("@noble/hashes/utils");
1028
- var import_bitcoinjs_lib3 = require("bitcoinjs-lib");
1292
+ var import_btc_signer3 = require("@scure/btc-signer");
1029
1293
  var AggregationParticipant = class {
1030
1294
  did;
1031
1295
  keys;
@@ -1040,6 +1304,9 @@ var AggregationParticipant = class {
1040
1304
  * outgoing messages — those come exclusively from action methods.
1041
1305
  */
1042
1306
  receive(message) {
1307
+ if (message.version === void 0 || message.version !== AGGREGATION_WIRE_VERSION) {
1308
+ return;
1309
+ }
1043
1310
  const type = message.type;
1044
1311
  switch (type) {
1045
1312
  case COHORT_ADVERT:
@@ -1196,42 +1463,26 @@ var AggregationParticipant = class {
1196
1463
  if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */) return;
1197
1464
  if (!state.submittedUpdate) return;
1198
1465
  const beaconType = message.body?.beaconType;
1466
+ if (!beaconType) return;
1467
+ const strategy = getBeaconStrategy(beaconType);
1468
+ if (!strategy) return;
1199
1469
  const signalBytesHex = message.body?.signalBytesHex ?? "";
1200
- const expectedHash = (0, import_common3.canonicalHash)(state.submittedUpdate);
1201
- let matches = false;
1202
- if (beaconType === "CASBeacon") {
1203
- const casAnnouncement = message.body?.casAnnouncement;
1204
- if (casAnnouncement) {
1205
- matches = casAnnouncement[this.did] === expectedHash;
1206
- state.validation = {
1207
- cohortId,
1208
- beaconType,
1209
- signalBytesHex,
1210
- casAnnouncement,
1211
- expectedHash,
1212
- matches
1213
- };
1214
- }
1215
- } else if (beaconType === "SMTBeacon") {
1216
- const smtProof = message.body?.smtProof;
1217
- if (smtProof?.updateId && smtProof?.nonce) {
1218
- const canonicalBytes = new TextEncoder().encode((0, import_common3.canonicalize)(state.submittedUpdate));
1219
- const expectedUpdateId = (0, import_smt2.hashToHex)((0, import_smt2.blockHash)(canonicalBytes));
1220
- if (smtProof.updateId === expectedUpdateId) {
1221
- const index = (0, import_smt2.didToIndex)(this.did);
1222
- const candidateHash = (0, import_smt2.blockHash)((0, import_smt2.blockHash)((0, import_smt2.hexToHash)(smtProof.nonce)), (0, import_smt2.hexToHash)(smtProof.updateId));
1223
- matches = (0, import_smt2.verifySerializedProof)(smtProof, index, candidateHash);
1224
- }
1225
- state.validation = {
1226
- cohortId,
1227
- beaconType,
1228
- signalBytesHex,
1229
- smtProof,
1230
- expectedHash,
1231
- matches
1232
- };
1233
- }
1234
- }
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
+ };
1235
1486
  state.phase = "AwaitingValidation" /* AwaitingValidation */;
1236
1487
  }
1237
1488
  /**
@@ -1292,12 +1543,14 @@ var AggregationParticipant = class {
1292
1543
  if (state.phase !== "ValidationSent" /* ValidationSent */) return;
1293
1544
  const sessionId = message.body?.sessionId;
1294
1545
  const pendingTxHex = message.body?.pendingTx;
1546
+ const prevOutScriptHex = message.body?.prevOutScriptHex;
1295
1547
  const prevOutValue = message.body?.prevOutValue;
1296
- if (!sessionId || !pendingTxHex || !prevOutValue) return;
1548
+ if (!sessionId || !pendingTxHex || !prevOutScriptHex || !prevOutValue) return;
1297
1549
  state.signingRequest = {
1298
1550
  cohortId,
1299
1551
  sessionId,
1300
1552
  pendingTxHex,
1553
+ prevOutScriptHex,
1301
1554
  prevOutValue
1302
1555
  };
1303
1556
  state.phase = "AwaitingSigning" /* AwaitingSigning */;
@@ -1321,8 +1574,8 @@ var AggregationParticipant = class {
1321
1574
  { cohortId }
1322
1575
  );
1323
1576
  }
1324
- const tx = import_bitcoinjs_lib3.Transaction.fromHex(state.signingRequest.pendingTxHex);
1325
- 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)];
1326
1579
  const prevOutValues = [BigInt(state.signingRequest.prevOutValue)];
1327
1580
  const session = new BeaconSigningSession({
1328
1581
  id: state.signingRequest.sessionId,
@@ -1392,6 +1645,67 @@ var AggregationParticipant = class {
1392
1645
  }
1393
1646
  };
1394
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
+
1395
1709
  // src/core/aggregation/messages/guards.ts
1396
1710
  var KEYGEN_VALUES = /* @__PURE__ */ new Set([
1397
1711
  COHORT_ADVERT,
@@ -1424,20 +1738,20 @@ function isSignMessageType(type) {
1424
1738
  }
1425
1739
 
1426
1740
  // src/core/aggregation/transport/error.ts
1427
- var import_common4 = require("@did-btcr2/common");
1428
- var TransportError = class extends import_common4.MethodError {
1741
+ var import_common6 = require("@did-btcr2/common");
1742
+ var TransportError = class extends import_common6.MethodError {
1429
1743
  constructor(message, type = "TransportError", data) {
1430
1744
  super(message, type, data);
1431
1745
  }
1432
1746
  };
1433
- var TransportAdapterError = class extends import_common4.MethodError {
1747
+ var TransportAdapterError = class extends import_common6.MethodError {
1434
1748
  constructor(message, type = "TransportAdapterError", data) {
1435
1749
  super(message, type, data);
1436
1750
  }
1437
1751
  };
1438
1752
 
1439
1753
  // src/core/aggregation/transport/factory.ts
1440
- var import_common5 = require("@did-btcr2/common");
1754
+ var import_common7 = require("@did-btcr2/common");
1441
1755
 
1442
1756
  // src/core/aggregation/transport/nostr.ts
1443
1757
  var import_keypair = require("@did-btcr2/keypair");
@@ -1450,6 +1764,7 @@ var DEFAULT_NOSTR_RELAYS = [
1450
1764
  "wss://relay.snort.social",
1451
1765
  "wss://nostr-pub.wellorder.net"
1452
1766
  ];
1767
+ var DEFAULT_BROADCAST_LOOKBACK_MS = 5 * 60 * 1e3;
1453
1768
  var NostrTransport = class _NostrTransport {
1454
1769
  name = "nostr";
1455
1770
  pool;
@@ -1457,8 +1772,12 @@ var NostrTransport = class _NostrTransport {
1457
1772
  #actors = /* @__PURE__ */ new Map();
1458
1773
  #peerRegistry = /* @__PURE__ */ new Map();
1459
1774
  #started = false;
1775
+ #logger;
1776
+ #broadcastLookbackMs;
1460
1777
  constructor(config) {
1461
1778
  this.#relays = config?.relays ?? DEFAULT_NOSTR_RELAYS;
1779
+ this.#logger = config?.logger ?? CONSOLE_LOGGER;
1780
+ this.#broadcastLookbackMs = config?.broadcastLookbackMs ?? DEFAULT_BROADCAST_LOOKBACK_MS;
1462
1781
  }
1463
1782
  /**
1464
1783
  * Registers an actor (DID + keys) to send/receive messages with.
@@ -1469,19 +1788,68 @@ var NostrTransport = class _NostrTransport {
1469
1788
  * @example
1470
1789
  * const transport = new NostrTransport();
1471
1790
  * const keys = SchnorrKeyPair.generate();
1472
- * transport.registerActor('did:btcr2:...', keys);
1791
+ * const did = DidBtcr2.create(keys.publicKey.compressed, { idType: 'KEY', network: 'mutinynet' });
1792
+ * transport.registerActor(did, keys);
1473
1793
  * transport.start();
1474
1794
  */
1475
1795
  registerActor(did, keys) {
1476
- const entry = { keys, handlers: /* @__PURE__ */ new Map() };
1796
+ const entry = { keys, handlers: /* @__PURE__ */ new Map(), subscriptions: [] };
1477
1797
  this.#actors.set(did, entry);
1478
1798
  if (this.#started && this.pool) {
1479
1799
  this.#subscribeDirected(did, entry);
1480
1800
  }
1481
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
+ */
1482
1845
  getActorPk(did) {
1483
1846
  return this.#actors.get(did)?.keys.publicKey.compressed;
1484
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
+ */
1485
1853
  registerPeer(did, communicationPk) {
1486
1854
  try {
1487
1855
  new import_keypair.CompressedSecp256k1PublicKey(communicationPk);
@@ -1494,9 +1862,25 @@ var NostrTransport = class _NostrTransport {
1494
1862
  }
1495
1863
  this.#peerRegistry.set(did, communicationPk);
1496
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
+ */
1497
1871
  getPeerPk(did) {
1498
1872
  return this.#peerRegistry.get(did);
1499
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
+ */
1500
1884
  registerMessageHandler(actorDid, messageType, handler) {
1501
1885
  const actor = this.#actors.get(actorDid);
1502
1886
  if (!actor) {
@@ -1508,21 +1892,48 @@ var NostrTransport = class _NostrTransport {
1508
1892
  }
1509
1893
  actor.handlers.set(messageType, handler);
1510
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
+ */
1511
1904
  start() {
1512
1905
  if (this.#started) return this;
1513
1906
  this.#started = true;
1514
1907
  this.pool = new import_pool.SimplePool();
1515
- const since = Math.floor(Date.now() / 1e3);
1516
- this.pool.subscribeMany(this.#relays, { kinds: [1], "#t": [COHORT_ADVERT], since }, {
1517
- 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),
1518
1917
  onevent: this.#handleBroadcastEvent.bind(this)
1519
1918
  });
1520
1919
  for (const [did, entry] of this.#actors) {
1521
1920
  this.#subscribeDirected(did, entry);
1522
1921
  }
1523
- console.info(`NostrTransport started, listening on ${this.#relays.length} relay(s)`);
1922
+ this.#logger.info(`NostrTransport started, listening on ${this.#relays.length} relay(s)`);
1524
1923
  return this;
1525
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
+ */
1526
1937
  async sendMessage(message, sender, to) {
1527
1938
  const type = message.type;
1528
1939
  if (!type) {
@@ -1559,7 +1970,7 @@ var NostrTransport = class _NostrTransport {
1559
1970
  tags,
1560
1971
  content: JSON.stringify(message, _NostrTransport.#jsonReplacer)
1561
1972
  }, senderKeys.secretKey.bytes);
1562
- console.debug(`Publishing kind 1 [${type}]`);
1973
+ this.#logger.debug(`Publishing kind 1 [${type}]`);
1563
1974
  await this.#publishToRelays(event);
1564
1975
  return;
1565
1976
  }
@@ -1591,25 +2002,70 @@ var NostrTransport = class _NostrTransport {
1591
2002
  tags,
1592
2003
  content
1593
2004
  }, senderKeys.secretKey.bytes);
1594
- console.debug(`Publishing kind 1059 [${type}]`);
2005
+ this.#logger.debug(`Publishing kind 1059 [${type}]`);
1595
2006
  await this.#publishToRelays(event);
1596
2007
  return;
1597
2008
  }
1598
- console.warn(`Unsupported message type: ${type}`);
2009
+ this.#logger.warn(`Unsupported message type: ${type}`);
1599
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
+ */
1600
2048
  #subscribeDirected(did, entry) {
1601
2049
  if (!this.pool) return;
1602
2050
  const pkHex = (0, import_utils4.bytesToHex)(entry.keys.publicKey.x);
1603
- const since = Math.floor(Date.now() / 1e3);
1604
- this.pool.subscribeMany(this.#relays, { kinds: [1, 1059], "#p": [pkHex], since }, {
1605
- 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),
1606
2053
  onevent: this.#makeActorEventHandler(did)
1607
2054
  });
2055
+ entry.subscriptions.push(sub);
1608
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
+ */
1609
2064
  #makeActorEventHandler(actorDid) {
1610
2065
  return async (event) => {
1611
2066
  const actor = this.#actors.get(actorDid);
1612
2067
  if (!actor) return;
2068
+ if (event.pubkey === (0, import_utils4.bytesToHex)(actor.keys.publicKey.x)) return;
1613
2069
  let message;
1614
2070
  try {
1615
2071
  if (event.kind === 1) {
@@ -1625,19 +2081,29 @@ var NostrTransport = class _NostrTransport {
1625
2081
  return;
1626
2082
  }
1627
2083
  } catch (err) {
1628
- console.debug(`Failed to parse event ${event.id} for ${actorDid}:`, err);
2084
+ this.#logger.debug(`Failed to parse event ${event.id} for ${actorDid}:`, err);
1629
2085
  return;
1630
2086
  }
1631
2087
  this.#dispatchMessage(message, actor);
1632
2088
  };
1633
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
+ */
1634
2100
  async #handleBroadcastEvent(event) {
1635
2101
  if (event.kind !== 1) return;
1636
2102
  let message;
1637
2103
  try {
1638
2104
  message = JSON.parse(event.content, _NostrTransport.#jsonReviver);
1639
2105
  } catch (err) {
1640
- console.debug(`Failed to parse broadcast event ${event.id}:`, err);
2106
+ this.#logger.debug(`Failed to parse broadcast event ${event.id}:`, err);
1641
2107
  return;
1642
2108
  }
1643
2109
  if (message.body && typeof message.body === "object") {
@@ -1650,6 +2116,18 @@ var NostrTransport = class _NostrTransport {
1650
2116
  if (handler) await handler(message);
1651
2117
  }
1652
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
+ */
1653
2131
  #dispatchMessage(message, actor) {
1654
2132
  if (message.body && typeof message.body === "object") {
1655
2133
  message = { ...message, ...message.body };
@@ -1659,6 +2137,16 @@ var NostrTransport = class _NostrTransport {
1659
2137
  const handler = actor.handlers.get(messageType);
1660
2138
  if (handler) handler(message);
1661
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
+ */
1662
2150
  async #publishToRelays(event) {
1663
2151
  const relayPromises = this.pool?.publish(this.#relays, event);
1664
2152
  if (!relayPromises?.length) return;
@@ -1666,7 +2154,7 @@ var NostrTransport = class _NostrTransport {
1666
2154
  const accepted = results.filter((r) => r.status === "fulfilled").length;
1667
2155
  const rejected = results.filter((r) => r.status === "rejected");
1668
2156
  for (const r of rejected) {
1669
- console.debug(`Relay rejected event ${event.id}: ${r.reason}`);
2157
+ this.#logger.debug(`Relay rejected event ${event.id}: ${r.reason}`);
1670
2158
  }
1671
2159
  if (accepted === 0) {
1672
2160
  throw new TransportAdapterError(
@@ -1676,12 +2164,36 @@ var NostrTransport = class _NostrTransport {
1676
2164
  );
1677
2165
  }
1678
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
+ */
1679
2179
  static #jsonReplacer(_key, value) {
1680
2180
  if (value instanceof Uint8Array) {
1681
2181
  return { __bytes: (0, import_utils4.bytesToHex)(value) };
1682
2182
  }
1683
2183
  return value;
1684
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
+ */
1685
2197
  static #jsonReviver(_key, value) {
1686
2198
  if (value && typeof value === "object" && "__bytes" in value) {
1687
2199
  return (0, import_utils4.hexToBytes)(value.__bytes);
@@ -1697,7 +2209,7 @@ var TransportFactory = class {
1697
2209
  case "nostr":
1698
2210
  return new NostrTransport({ relays: config.relays });
1699
2211
  case "didcomm":
1700
- throw new import_common5.NotImplementedError("DIDComm transport not implemented yet.");
2212
+ throw new import_common7.NotImplementedError("DIDComm transport not implemented yet.");
1701
2213
  default:
1702
2214
  throw new TransportError(
1703
2215
  `Invalid transport type: ${config.type}`,
@@ -1709,29 +2221,38 @@ var TransportFactory = class {
1709
2221
  };
1710
2222
 
1711
2223
  // src/core/aggregation/transport/didcomm.ts
1712
- var import_common6 = require("@did-btcr2/common");
2224
+ var import_common8 = require("@did-btcr2/common");
1713
2225
  var DidCommTransport = class {
1714
2226
  name = "didcomm";
1715
2227
  start() {
1716
- throw new import_common6.NotImplementedError("DidCommTransport not implemented. Use NostrTransport instead.");
2228
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented. Use NostrTransport instead.");
1717
2229
  }
1718
2230
  registerActor(_did, _keys) {
1719
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
2231
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
1720
2232
  }
1721
2233
  getActorPk(_did) {
1722
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
2234
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
1723
2235
  }
1724
2236
  registerPeer(_did, _communicationPk) {
1725
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
2237
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
1726
2238
  }
1727
2239
  getPeerPk(_did) {
1728
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
2240
+ throw new import_common8.NotImplementedError("DidCommTransport not implemented.");
1729
2241
  }
1730
2242
  registerMessageHandler(_actorDid, _messageType, _handler) {
1731
- 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.");
1732
2250
  }
1733
2251
  async sendMessage(_message, _sender, _recipient) {
1734
- 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.");
1735
2256
  }
1736
2257
  };
1737
2258
 
@@ -1790,7 +2311,8 @@ var TypedEventEmitter = class {
1790
2311
  };
1791
2312
 
1792
2313
  // src/core/aggregation/runner/service-runner.ts
1793
- var AggregationServiceRunner = class extends TypedEventEmitter {
2314
+ var DEFAULT_ADVERT_REPEAT_INTERVAL_MS = 6e4;
2315
+ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEventEmitter {
1794
2316
  /** Direct access to the underlying state machine for advanced use. */
1795
2317
  session;
1796
2318
  #transport;
@@ -1799,11 +2321,26 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1799
2321
  #onOptInReceived;
1800
2322
  #onReadyToFinalize;
1801
2323
  #onProvideTxData;
2324
+ #cohortTtlMs;
2325
+ #phaseTimeoutMs;
2326
+ #advertRepeatIntervalMs;
1802
2327
  #cohortId;
1803
2328
  #handlersRegistered = false;
1804
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;
1805
2337
  #resolveRun;
1806
2338
  #rejectRun;
2339
+ #cohortTtlTimer;
2340
+ #phaseTimer;
2341
+ #lastObservedPhase;
2342
+ /** Stop handle for the repeating COHORT_ADVERT publish loop. */
2343
+ #stopAdvertRepeat;
1807
2344
  constructor(options) {
1808
2345
  super();
1809
2346
  this.#transport = options.transport;
@@ -1814,7 +2351,25 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1814
2351
  finalize: acceptedCount >= minRequired
1815
2352
  }));
1816
2353
  this.#onProvideTxData = options.onProvideTxData;
1817
- 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
+ }
1818
2373
  }
1819
2374
  /**
1820
2375
  * Run the protocol to completion. Resolves with the final aggregation result
@@ -1829,22 +2384,103 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1829
2384
  try {
1830
2385
  this.#registerHandlers();
1831
2386
  this.#cohortId = this.session.createCohort(this.#config);
2387
+ this.#startTimers();
1832
2388
  const advertMsgs = this.session.advertise(this.#cohortId);
2389
+ this.#onPhaseMaybeChanged();
1833
2390
  this.emit("cohort-advertised", { cohortId: this.#cohortId });
1834
- 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
+ }
1835
2396
  } catch (err) {
1836
2397
  this.#fail(err);
1837
2398
  }
1838
2399
  });
1839
2400
  }
1840
2401
  /**
1841
- * Stop the runner early. Cleans up internal state.
1842
- * Note: does not unregister transport handlers (the transport interface
1843
- * 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.
1844
2468
  */
1845
2469
  stop() {
1846
2470
  this.#stopped = true;
1847
- }
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
+ ];
1848
2484
  /**
1849
2485
  * Internal: handler registration with the transport. Idempotent.
1850
2486
  */
@@ -1857,6 +2493,14 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1857
2493
  this.#transport.registerMessageHandler(this.#did, NONCE_CONTRIBUTION, this.#handleNonceContribution.bind(this));
1858
2494
  this.#transport.registerMessageHandler(this.#did, SIGNATURE_AUTHORIZATION, this.#handleSignatureAuthorization.bind(this));
1859
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
+ }
1860
2504
  /**
1861
2505
  * Internal: message handlers for each protocol step. Each handler:
1862
2506
  * 1) feeds the message into the state machine via session.receive()
@@ -1873,6 +2517,8 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1873
2517
  if (this.#stopped) return;
1874
2518
  try {
1875
2519
  this.session.receive(msg);
2520
+ this.#drainRejections();
2521
+ this.#onPhaseMaybeChanged();
1876
2522
  const optIn = this.session.pendingOptIns(this.#cohortId).get(msg.from);
1877
2523
  if (!optIn) return;
1878
2524
  this.emit("opt-in-received", optIn);
@@ -1884,19 +2530,23 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1884
2530
  await this.#sendAll(this.session.acceptParticipant(this.#cohortId, msg.from));
1885
2531
  this.emit("participant-accepted", { participantDid: msg.from });
1886
2532
  const cohort = this.session.getCohort(this.#cohortId);
1887
- if (cohort.participants.length >= this.#config.minParticipants) {
2533
+ if (cohort.participants.length >= this.#config.minParticipants && !this.#finalizing) {
2534
+ this.#finalizing = true;
1888
2535
  const finalizeDecision = await this.#onReadyToFinalize({
1889
2536
  acceptedCount: cohort.participants.length,
1890
2537
  minRequired: this.#config.minParticipants
1891
2538
  });
1892
- if (finalizeDecision.finalize) {
1893
- const readyMsgs = this.session.finalizeKeygen(this.#cohortId);
1894
- this.emit("keygen-complete", {
1895
- cohortId: this.#cohortId,
1896
- beaconAddress: cohort.beaconAddress
1897
- });
1898
- await this.#sendAll(readyMsgs);
2539
+ if (!finalizeDecision.finalize) {
2540
+ this.#finalizing = false;
2541
+ return;
1899
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);
1900
2550
  }
1901
2551
  } catch (err) {
1902
2552
  this.#fail(err);
@@ -1914,6 +2564,8 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1914
2564
  if (this.#stopped) return;
1915
2565
  try {
1916
2566
  this.session.receive(msg);
2567
+ this.#drainRejections();
2568
+ this.#onPhaseMaybeChanged();
1917
2569
  this.emit("update-received", { participantDid: msg.from });
1918
2570
  if (this.session.getCohortPhase(this.#cohortId) === "UpdatesCollected" /* UpdatesCollected */) {
1919
2571
  const distributeMsgs = this.session.buildAndDistribute(this.#cohortId);
@@ -1936,9 +2588,18 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1936
2588
  if (this.#stopped) return;
1937
2589
  try {
1938
2590
  this.session.receive(msg);
2591
+ this.#drainRejections();
2592
+ this.#onPhaseMaybeChanged();
1939
2593
  const approved = !!msg.body?.approved;
1940
2594
  this.emit("validation-received", { participantDid: msg.from, approved });
1941
- 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 */) {
1942
2603
  const cohort = this.session.getCohort(this.#cohortId);
1943
2604
  const txData = await this.#onProvideTxData({
1944
2605
  cohortId: this.#cohortId,
@@ -1966,6 +2627,8 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1966
2627
  if (this.#stopped) return;
1967
2628
  try {
1968
2629
  this.session.receive(msg);
2630
+ this.#drainRejections();
2631
+ this.#onPhaseMaybeChanged();
1969
2632
  this.emit("nonce-received", { participantDid: msg.from });
1970
2633
  if (this.session.getCohortPhase(this.#cohortId) === "NoncesCollected" /* NoncesCollected */) {
1971
2634
  await this.#sendAll(this.session.sendAggregatedNonce(this.#cohortId));
@@ -1986,8 +2649,12 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1986
2649
  if (this.#stopped) return;
1987
2650
  try {
1988
2651
  this.session.receive(msg);
2652
+ this.#drainRejections();
2653
+ this.#onPhaseMaybeChanged();
1989
2654
  const result = this.session.getResult(this.#cohortId);
1990
2655
  if (result) {
2656
+ this.#clearTimers();
2657
+ this.#unregisterHandlers();
1991
2658
  this.emit("signing-complete", result);
1992
2659
  this.#resolveRun?.(result);
1993
2660
  }
@@ -2012,6 +2679,10 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
2012
2679
  * @param {Error} err - The error to handle.
2013
2680
  */
2014
2681
  #fail(err) {
2682
+ this.#stopAdvertRepeating();
2683
+ this.#clearTimers();
2684
+ this.#unregisterHandlers();
2685
+ if (this.#cohortId) this.session.removeCohort(this.#cohortId);
2015
2686
  this.emit("error", err);
2016
2687
  this.#rejectRun?.(err);
2017
2688
  }
@@ -2046,9 +2717,27 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
2046
2717
  async start() {
2047
2718
  this.#registerHandlers();
2048
2719
  }
2049
- /** Stop the runner. Does not unregister transport handlers. */
2720
+ /** Stop the runner and detach transport handlers. Safe to call repeatedly. */
2050
2721
  stop() {
2051
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
+ }
2052
2741
  }
2053
2742
  /**
2054
2743
  * Single-shot helper: start, join the first cohort that passes `shouldJoin`,
@@ -2213,7 +2902,14 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
2213
2902
  if (this.session.getCohortPhase(cohortId) === "Complete" /* Complete */) {
2214
2903
  const info = this.session.joinedCohorts.get(cohortId);
2215
2904
  if (info) {
2216
- 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
+ });
2217
2913
  }
2218
2914
  }
2219
2915
  } catch (err) {
@@ -2235,33 +2931,33 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
2235
2931
  };
2236
2932
 
2237
2933
  // src/core/beacon/beacon.ts
2238
- var import_keypair2 = require("@did-btcr2/keypair");
2934
+ var import_secp256k12 = require("@noble/secp256k1");
2239
2935
  var import_utils5 = require("@noble/hashes/utils");
2240
- var import_bitcoinjs_lib4 = require("bitcoinjs-lib");
2936
+ var import_btc_signer4 = require("@scure/btc-signer");
2241
2937
 
2242
2938
  // src/core/beacon/error.ts
2243
- var import_common7 = require("@did-btcr2/common");
2244
- var BeaconError = class extends import_common7.MethodError {
2939
+ var import_common9 = require("@did-btcr2/common");
2940
+ var BeaconError = class extends import_common9.MethodError {
2245
2941
  constructor(message, type = "BeaconError", data) {
2246
2942
  super(message, type, data);
2247
2943
  }
2248
2944
  };
2249
- var SingletonBeaconError = class extends import_common7.MethodError {
2945
+ var SingletonBeaconError = class extends import_common9.MethodError {
2250
2946
  constructor(message, type = "SingletonBeaconError", data) {
2251
2947
  super(message, type, data);
2252
2948
  }
2253
2949
  };
2254
- var AggregateBeaconError = class extends import_common7.MethodError {
2950
+ var AggregateBeaconError = class extends import_common9.MethodError {
2255
2951
  constructor(message, type = "AggregateBeaconError", data) {
2256
2952
  super(message, type, data);
2257
2953
  }
2258
2954
  };
2259
- var CASBeaconError = class extends import_common7.MethodError {
2955
+ var CASBeaconError = class extends import_common9.MethodError {
2260
2956
  constructor(message, type = "CASBeaconError", data) {
2261
2957
  super(message, type, data);
2262
2958
  }
2263
2959
  };
2264
- var SMTBeaconError = class extends import_common7.MethodError {
2960
+ var SMTBeaconError = class extends import_common9.MethodError {
2265
2961
  constructor(message, type = "SMTBeaconError", data) {
2266
2962
  super(message, type, data);
2267
2963
  }
@@ -2289,6 +2985,62 @@ var StaticFeeEstimator = class {
2289
2985
 
2290
2986
  // src/core/beacon/beacon.ts
2291
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
+ }
2292
3044
  var Beacon = class {
2293
3045
  /**
2294
3046
  * The Beacon service configuration parsed from the DID Document.
@@ -2298,79 +3050,108 @@ var Beacon = class {
2298
3050
  this.service = service;
2299
3051
  }
2300
3052
  /**
2301
- * Shared PSBT construction + signing + broadcast helper used by all beacon types.
3053
+ * Build + sign + broadcast a single-party beacon signal transaction (P2WPKH spend).
2302
3054
  *
2303
- * Steps:
2304
- * 1. Parse the beacon's `serviceEndpoint` (stripping `bitcoin:` prefix) into a Bitcoin address.
2305
- * 2. Query the address for unconfirmed/confirmed UTXOs.
2306
- * 3. Select the most recent confirmed UTXO.
2307
- * 4. Fetch the previous transaction hex for `nonWitnessUtxo`.
2308
- * 5. Build a PSBT: input (UTXO) → change output + OP_RETURN(signalBytes).
2309
- * 6. Compute the fee via the supplied (or default) {@link FeeEstimator} against the tx vsize.
2310
- * 7. Sign input 0 with an ECDSA signer derived from `secretKey`.
2311
- * 8. Finalize, extract, and broadcast via the REST transaction endpoint.
2312
- *
2313
- * Fee handling: the PSBT is constructed with a placeholder change amount, signed to measure
2314
- * vsize, then the change is adjusted to pay the actual fee and the input re-signed. This
2315
- * two-pass approach avoids hardcoded fee constants and produces a tx that matches the
2316
- * 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.
2317
3060
  *
2318
3061
  * @param signalBytes 32-byte payload to embed in OP_RETURN.
2319
3062
  * @param secretKey Secret key used to sign the spending input.
2320
3063
  * @param bitcoin Bitcoin network connection.
2321
3064
  * @param options Broadcast options (fee estimator, etc.).
2322
3065
  * @returns The txid of the broadcast transaction.
2323
- * @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.
2324
3067
  */
2325
3068
  async buildSignAndBroadcast(signalBytes, secretKey, bitcoin, options) {
2326
3069
  const feeEstimator = options?.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
2327
- const bitcoinAddress = this.service.serviceEndpoint.replace("bitcoin:", "");
2328
- const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
2329
- if (!utxos.length) {
2330
- throw new BeaconError(
2331
- "No UTXOs found, please fund address!",
2332
- "UNFUNDED_BEACON_ADDRESS",
2333
- { bitcoinAddress }
2334
- );
2335
- }
2336
- const utxo = utxos.sort(
2337
- (a, b) => b.status.block_height - a.status.block_height
2338
- ).shift();
2339
- if (!utxo) {
2340
- throw new BeaconError(
2341
- "Beacon bitcoin address unfunded or utxos unconfirmed.",
2342
- "UNFUNDED_BEACON_ADDRESS",
2343
- { 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
2344
3107
  );
2345
- }
2346
- const prevTx = await bitcoin.rest.transaction.getHex(utxo.txid);
2347
- const keyPair = import_keypair2.SchnorrKeyPair.fromSecret(secretKey);
2348
- const signer = {
2349
- publicKey: keyPair.publicKey.compressed,
2350
- sign: (hash5) => keyPair.secretKey.sign(hash5, { scheme: "ecdsa" })
3108
+ tx2.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
3109
+ return tx2;
2351
3110
  };
2352
- const build = (fee2) => new import_bitcoinjs_lib4.Psbt({ network: bitcoin.data }).addInput({
2353
- hash: utxo.txid,
2354
- index: utxo.vout,
2355
- nonWitnessUtxo: (0, import_utils5.hexToBytes)(prevTx)
2356
- }).addOutput({ address: bitcoinAddress, value: BigInt(utxo.value) - fee2 }).addOutput({ script: import_bitcoinjs_lib4.script.compile([import_bitcoinjs_lib4.opcodes.OP_RETURN, signalBytes]), value: 0n });
2357
- const probeTx = build(0n).signInput(0, signer).finalizeAllInputs().extractTransaction();
2358
- const vsize = probeTx.virtualSize();
2359
- const fee = await feeEstimator.estimateFee(vsize);
2360
- 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) {
2361
3117
  throw new BeaconError(
2362
- `UTXO value (${utxo.value}) insufficient to cover fee (${fee}).`,
3118
+ `UTXO value (${opts.utxo.value}) insufficient to cover fee (${feeSats}).`,
2363
3119
  "INSUFFICIENT_FUNDS",
2364
- { bitcoinAddress, utxoValue: utxo.value, fee: fee.toString() }
3120
+ { bitcoinAddress: opts.beaconAddress, utxoValue: opts.utxo.value, fee: feeSats.toString() }
2365
3121
  );
2366
3122
  }
2367
- const signedTxHex = build(fee).signInput(0, signer).finalizeAllInputs().extractTransaction().toHex();
2368
- 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);
2369
3150
  }
2370
3151
  };
2371
3152
 
2372
3153
  // src/core/beacon/cas-beacon.ts
2373
- var import_common8 = require("@did-btcr2/common");
3154
+ var import_common10 = require("@did-btcr2/common");
2374
3155
  var CASBeacon = class extends Beacon {
2375
3156
  /**
2376
3157
  * Creates an instance of CASBeacon.
@@ -2411,7 +3192,7 @@ var CASBeacon = class extends Beacon {
2411
3192
  if (!updateHashEncoded) {
2412
3193
  continue;
2413
3194
  }
2414
- 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");
2415
3196
  const signedUpdate = sidecar.updateMap.get(updateHash);
2416
3197
  if (!signedUpdate) {
2417
3198
  needs.push({
@@ -2443,9 +3224,9 @@ var CASBeacon = class extends Beacon {
2443
3224
  */
2444
3225
  async broadcastSignal(signedUpdate, secretKey, bitcoin, options) {
2445
3226
  const did = this.service.id.split("#")[0];
2446
- const updateHash = (0, import_common8.canonicalHash)(signedUpdate);
3227
+ const updateHash = (0, import_common10.canonicalHash)(signedUpdate);
2447
3228
  const casAnnouncement = { [did]: updateHash };
2448
- const announcementHash = (0, import_common8.hash)((0, import_common8.canonicalize)(casAnnouncement));
3229
+ const announcementHash = (0, import_common10.hash)((0, import_common10.canonicalize)(casAnnouncement));
2449
3230
  await this.buildSignAndBroadcast(announcementHash, secretKey, bitcoin, options);
2450
3231
  if (options?.casPublish) {
2451
3232
  await options.casPublish(casAnnouncement);
@@ -2455,10 +3236,10 @@ var CASBeacon = class extends Beacon {
2455
3236
  };
2456
3237
 
2457
3238
  // src/core/beacon/factory.ts
2458
- var import_common11 = require("@did-btcr2/common");
3239
+ var import_common13 = require("@did-btcr2/common");
2459
3240
 
2460
3241
  // src/core/beacon/singleton-beacon.ts
2461
- var import_common9 = require("@did-btcr2/common");
3242
+ var import_common11 = require("@did-btcr2/common");
2462
3243
  var SingletonBeacon = class extends Beacon {
2463
3244
  /**
2464
3245
  * Creates an instance of SingletonBeacon.
@@ -2506,14 +3287,14 @@ var SingletonBeacon = class extends Beacon {
2506
3287
  * @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
2507
3288
  */
2508
3289
  async broadcastSignal(signedUpdate, secretKey, bitcoin, options) {
2509
- const signalBytes = (0, import_common9.hash)((0, import_common9.canonicalize)(signedUpdate));
3290
+ const signalBytes = (0, import_common11.hash)((0, import_common11.canonicalize)(signedUpdate));
2510
3291
  await this.buildSignAndBroadcast(signalBytes, secretKey, bitcoin, options);
2511
3292
  return signedUpdate;
2512
3293
  }
2513
3294
  };
2514
3295
 
2515
3296
  // src/core/beacon/smt-beacon.ts
2516
- var import_common10 = require("@did-btcr2/common");
3297
+ var import_common12 = require("@did-btcr2/common");
2517
3298
  var import_smt3 = require("@did-btcr2/smt");
2518
3299
  var import_utils6 = require("@noble/hashes/utils");
2519
3300
  var SMTBeacon = class extends Beacon {
@@ -2601,7 +3382,7 @@ var SMTBeacon = class extends Beacon {
2601
3382
  */
2602
3383
  async broadcastSignal(signedUpdate, secretKey, bitcoin, options) {
2603
3384
  const did = this.service.id.split("#")[0];
2604
- const canonicalBytes = new TextEncoder().encode((0, import_common10.canonicalize)(signedUpdate));
3385
+ const canonicalBytes = new TextEncoder().encode((0, import_common12.canonicalize)(signedUpdate));
2605
3386
  const nonce = (0, import_utils6.randomBytes)(32);
2606
3387
  const tree = new import_smt3.BTCR2MerkleTree();
2607
3388
  tree.addEntries([{ did, nonce, signedUpdate: canonicalBytes }]);
@@ -2627,19 +3408,19 @@ var BeaconFactory = class {
2627
3408
  case "SMTBeacon":
2628
3409
  return new SMTBeacon(service);
2629
3410
  default:
2630
- 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);
2631
3412
  }
2632
3413
  }
2633
3414
  };
2634
3415
 
2635
3416
  // src/core/beacon/signal-discovery.ts
2636
3417
  var import_bitcoin2 = require("@did-btcr2/bitcoin");
2637
- var import_common14 = require("@did-btcr2/common");
3418
+ var import_common16 = require("@did-btcr2/common");
2638
3419
 
2639
3420
  // src/core/beacon/utils.ts
2640
3421
  var import_bitcoin = require("@did-btcr2/bitcoin");
2641
- var import_common13 = require("@did-btcr2/common");
2642
- var import_bitcoinjs_lib5 = require("bitcoinjs-lib");
3422
+ var import_common15 = require("@did-btcr2/common");
3423
+ var import_btc_signer5 = require("@scure/btc-signer");
2643
3424
 
2644
3425
  // src/utils/appendix.ts
2645
3426
  var import_dids = require("@web5/dids");
@@ -3680,9 +4461,9 @@ var Appendix = class _Appendix {
3680
4461
  };
3681
4462
 
3682
4463
  // src/core/identifier.ts
3683
- var import_common12 = require("@did-btcr2/common");
3684
- var import_keypair3 = require("@did-btcr2/keypair");
3685
- 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");
3686
4467
  var Identifier = class _Identifier {
3687
4468
  /**
3688
4469
  * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-encoding | 3.2 did:btcr2 Identifier Encoding}.
@@ -3700,25 +4481,25 @@ var Identifier = class _Identifier {
3700
4481
  */
3701
4482
  static encode(genesisBytes, options) {
3702
4483
  const { idType, version = 1, network } = options;
3703
- if (!(idType in import_common12.IdentifierTypes)) {
3704
- 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 });
3705
4486
  }
3706
4487
  if (isNaN(version) || version > 1) {
3707
- 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 });
3708
4489
  }
3709
- if (typeof network === "string" && !(network in import_common12.BitcoinNetworkNames)) {
3710
- 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 });
3711
4492
  }
3712
4493
  if (typeof network === "number" && (network < 0 || network > 8)) {
3713
- 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 });
3714
4495
  }
3715
4496
  if (idType === "KEY") {
3716
4497
  try {
3717
- new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
4498
+ new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
3718
4499
  } catch {
3719
- throw new import_common12.IdentifierError(
4500
+ throw new import_common14.IdentifierError(
3720
4501
  'Expected "genesisBytes" to be a valid compressed secp256k1 public key',
3721
- import_common12.INVALID_DID,
4502
+ import_common14.INVALID_DID,
3722
4503
  { genesisBytes }
3723
4504
  );
3724
4505
  }
@@ -3731,7 +4512,7 @@ var Identifier = class _Identifier {
3731
4512
  }
3732
4513
  nibbles.push((version - 1) % 15);
3733
4514
  if (typeof network === "string") {
3734
- nibbles.push(import_common12.BitcoinNetworkNames[network]);
4515
+ nibbles.push(import_common14.BitcoinNetworkNames[network]);
3735
4516
  } else if (typeof network === "number") {
3736
4517
  nibbles.push(network + 11);
3737
4518
  }
@@ -3740,11 +4521,11 @@ var Identifier = class _Identifier {
3740
4521
  }
3741
4522
  if (fCount !== 0) {
3742
4523
  for (const index in Array.from({ length: nibbles.length / 2 - 1 })) {
3743
- throw new import_common12.IdentifierError("Not implemented", "NOT_IMPLEMENTED", { index });
4524
+ throw new import_common14.IdentifierError("Not implemented", "NOT_IMPLEMENTED", { index });
3744
4525
  }
3745
4526
  }
3746
4527
  const dataBytes = new Uint8Array([nibbles[2 * 0] << 4 | nibbles[2 * 0 + 1], ...genesisBytes]);
3747
- return `did:btcr2:${import_base5.bech32m.encodeFromBytes(hrp, dataBytes)}`;
4528
+ return `did:btcr2:${import_base7.bech32m.encodeFromBytes(hrp, dataBytes)}`;
3748
4529
  }
3749
4530
  /**
3750
4531
  * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}.
@@ -3757,24 +4538,24 @@ var Identifier = class _Identifier {
3757
4538
  static decode(identifier) {
3758
4539
  const components = identifier.split(":");
3759
4540
  if (components.length !== 3) {
3760
- 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 });
3761
4542
  }
3762
4543
  const [scheme, method, encoded] = components;
3763
4544
  if (scheme !== "did") {
3764
- 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 });
3765
4546
  }
3766
4547
  if (method !== "btcr2") {
3767
- 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 });
3768
4549
  }
3769
4550
  if (!encoded) {
3770
- 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 });
3771
4552
  }
3772
- const { prefix: hrp, bytes: dataBytes } = import_base5.bech32m.decodeToBytes(encoded);
4553
+ const { prefix: hrp, bytes: dataBytes } = import_base7.bech32m.decodeToBytes(encoded);
3773
4554
  if (!["x", "k"].includes(hrp)) {
3774
- 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 });
3775
4556
  }
3776
4557
  if (!dataBytes) {
3777
- 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 });
3778
4559
  }
3779
4560
  const idType = hrp === "k" ? "KEY" : "EXTERNAL";
3780
4561
  let version = 1;
@@ -3792,33 +4573,33 @@ var Identifier = class _Identifier {
3792
4573
  }
3793
4574
  nibblesConsumed += 1;
3794
4575
  if (version > 1) {
3795
- 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 });
3796
4577
  }
3797
4578
  }
3798
4579
  version += versionNibble;
3799
4580
  nibblesConsumed += 1;
3800
4581
  let networkValue = nibblesConsumed % 2 === 0 ? dataBytes[++byteIndex] >>> 4 : currentByte & 15;
3801
4582
  nibblesConsumed += 1;
3802
- let network = import_common12.BitcoinNetworkNames[networkValue];
4583
+ let network = import_common14.BitcoinNetworkNames[networkValue];
3803
4584
  if (!network) {
3804
4585
  if (networkValue >= 8 && networkValue <= 15) {
3805
4586
  network = networkValue - 11;
3806
4587
  } else {
3807
- 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 });
3808
4589
  }
3809
4590
  }
3810
4591
  if (nibblesConsumed % 2 === 1) {
3811
4592
  const fillerNibble = currentByte & 15;
3812
4593
  if (fillerNibble !== 0) {
3813
- 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 });
3814
4595
  }
3815
4596
  }
3816
4597
  const genesisBytes = dataBytes.slice(byteIndex + 1);
3817
4598
  if (idType === "KEY") {
3818
4599
  try {
3819
- new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
4600
+ new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
3820
4601
  } catch {
3821
- 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 });
3822
4603
  }
3823
4604
  }
3824
4605
  return { idType, hrp, version, network, genesisBytes };
@@ -3828,7 +4609,7 @@ var Identifier = class _Identifier {
3828
4609
  * @returns {string} The new did:btcr2 identifier.
3829
4610
  */
3830
4611
  static generate() {
3831
- const keyPair = import_keypair3.SchnorrKeyPair.generate();
4612
+ const keyPair = import_keypair2.SchnorrKeyPair.generate();
3832
4613
  const did = this.encode(
3833
4614
  keyPair.publicKey.compressed,
3834
4615
  {
@@ -3848,13 +4629,13 @@ var Identifier = class _Identifier {
3848
4629
  static getPublicKey(did) {
3849
4630
  const { idType, genesisBytes } = _Identifier.decode(did);
3850
4631
  if (idType !== "KEY") {
3851
- throw new import_common12.IdentifierError(
4632
+ throw new import_common14.IdentifierError(
3852
4633
  `Cannot extract public key from EXTERNAL DID: ${did}. EXTERNAL DIDs encode a document hash, not a public key.`,
3853
- import_common12.INVALID_DID,
4634
+ import_common14.INVALID_DID,
3854
4635
  { did, idType }
3855
4636
  );
3856
4637
  }
3857
- return new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
4638
+ return new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
3858
4639
  }
3859
4640
  /**
3860
4641
  * Validates a did:btcr2 identifier.
@@ -3881,7 +4662,7 @@ var BeaconUtils = class {
3881
4662
  */
3882
4663
  static parseBitcoinAddress(uri) {
3883
4664
  if (!uri.startsWith("bitcoin:")) {
3884
- 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 });
3885
4666
  }
3886
4667
  return uri.replace("bitcoin:", "").split("?")[0];
3887
4668
  }
@@ -3939,7 +4720,8 @@ var BeaconUtils = class {
3939
4720
  const network = (0, import_bitcoin.getNetwork)(components.network);
3940
4721
  const pubkey = components.genesisBytes;
3941
4722
  const id = `${did}#initial${addressType.toUpperCase()}`;
3942
- 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}`;
3943
4725
  return { id, type: beaconType, serviceEndpoint };
3944
4726
  } catch (error) {
3945
4727
  throw new BeaconError(
@@ -3957,27 +4739,27 @@ var BeaconUtils = class {
3957
4739
  */
3958
4740
  static generateBeaconServices({ id, publicKey, network, beaconType }) {
3959
4741
  try {
3960
- const p2pkh = import_bitcoinjs_lib5.payments.p2pkh({ pubkey: publicKey, network }).address;
3961
- const p2wpkh = import_bitcoinjs_lib5.payments.p2wpkh({ pubkey: publicKey, network }).address;
3962
- const p2tr = import_bitcoinjs_lib5.payments.p2tr({ network, internalPubkey: publicKey.slice(1, 33) }).address;
3963
- if (!p2pkh || !p2wpkh || !p2tr) {
3964
- 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");
3965
4747
  }
3966
4748
  return [
3967
4749
  {
3968
4750
  id: `${id}#initialP2PKH`,
3969
4751
  type: beaconType,
3970
- serviceEndpoint: `bitcoin:${p2pkh}`
4752
+ serviceEndpoint: `bitcoin:${p2pkhAddr}`
3971
4753
  },
3972
4754
  {
3973
4755
  id: `${id}#initialP2WPKH`,
3974
4756
  type: beaconType,
3975
- serviceEndpoint: `bitcoin:${p2wpkh}`
4757
+ serviceEndpoint: `bitcoin:${p2wpkhAddr}`
3976
4758
  },
3977
4759
  {
3978
4760
  id: `${id}#initialP2TR`,
3979
4761
  type: beaconType,
3980
- serviceEndpoint: `bitcoin:${p2tr}`
4762
+ serviceEndpoint: `bitcoin:${p2trAddr}`
3981
4763
  }
3982
4764
  ];
3983
4765
  } catch (error) {
@@ -4077,7 +4859,7 @@ var BeaconSignalDiscovery = class {
4077
4859
  }
4078
4860
  const rpc = bitcoin.rpc;
4079
4861
  if (!rpc) {
4080
- 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);
4081
4863
  }
4082
4864
  const targetHeight = await rpc.getBlockCount();
4083
4865
  const beaconServicesMap = BeaconUtils.getBeaconServicesMap(beaconServices);
@@ -4148,26 +4930,24 @@ var BeaconSignalDiscovery = class {
4148
4930
 
4149
4931
  // src/core/resolver.ts
4150
4932
  var import_bitcoin4 = require("@did-btcr2/bitcoin");
4151
- var import_common18 = require("@did-btcr2/common");
4152
- var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
4153
- 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");
4154
4936
 
4155
4937
  // src/did-btcr2.ts
4156
- var import_common17 = require("@did-btcr2/common");
4938
+ var import_common19 = require("@did-btcr2/common");
4157
4939
  var import_dids2 = require("@web5/dids");
4158
- var ecc = __toESM(require("@bitcoinerlab/secp256k1"), 1);
4159
- var import_bitcoinjs_lib7 = require("bitcoinjs-lib");
4160
4940
 
4161
4941
  // src/core/updater.ts
4162
- var import_common16 = require("@did-btcr2/common");
4163
- var import_cryptosuite = require("@did-btcr2/cryptosuite");
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,7 +5305,7 @@ 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
 
@@ -4572,19 +5352,19 @@ var Updater = class _Updater {
4572
5352
  patch: patches,
4573
5353
  targetHash: "",
4574
5354
  targetVersionId: sourceVersionId + 1,
4575
- sourceHash: (0, import_common16.canonicalHash)(sourceDocument)
5355
+ sourceHash: (0, import_common18.canonicalHash)(sourceDocument)
4576
5356
  };
4577
- const targetDocument = import_common16.JSONPatch.apply(sourceDocument, patches);
5357
+ const targetDocument = import_common18.JSONPatch.apply(sourceDocument, patches);
4578
5358
  try {
4579
5359
  DidDocument.isValid(targetDocument);
4580
5360
  } catch (error) {
4581
- throw new import_common16.UpdateError(
5361
+ throw new import_common18.UpdateError(
4582
5362
  "Error validating targetDocument: " + (error instanceof Error ? error.message : String(error)),
4583
- import_common16.INVALID_DID_UPDATE,
5363
+ import_common18.INVALID_DID_UPDATE,
4584
5364
  targetDocument
4585
5365
  );
4586
5366
  }
4587
- unsignedUpdate.targetHash = (0, import_common16.canonicalHash)(targetDocument);
5367
+ unsignedUpdate.targetHash = (0, import_common18.canonicalHash)(targetDocument);
4588
5368
  return unsignedUpdate;
4589
5369
  }
4590
5370
  /**
@@ -4599,7 +5379,7 @@ var Updater = class _Updater {
4599
5379
  static sign(did, unsignedUpdate, verificationMethod, secretKey) {
4600
5380
  const controller = verificationMethod.controller;
4601
5381
  const id = verificationMethod.id.slice(verificationMethod.id.indexOf("#"));
4602
- const multikey = import_cryptosuite.SchnorrMultikey.fromSecretKey(id, controller, secretKey);
5382
+ const multikey = import_cryptosuite2.SchnorrMultikey.fromSecretKey(id, controller, secretKey);
4603
5383
  const config = {
4604
5384
  "@context": [
4605
5385
  "https://w3id.org/security/v2",
@@ -4709,22 +5489,22 @@ var Updater = class _Updater {
4709
5489
  switch (need.kind) {
4710
5490
  case "NeedSigningKey": {
4711
5491
  if (this.#phase !== "Sign" /* Sign */) {
4712
- throw new import_common16.UpdateError(
5492
+ throw new import_common18.UpdateError(
4713
5493
  `Cannot provide NeedSigningKey: updater phase is ${this.#phase}, expected Sign.`,
4714
- import_common16.INVALID_DID_UPDATE,
5494
+ import_common18.INVALID_DID_UPDATE,
4715
5495
  { phase: this.#phase }
4716
5496
  );
4717
5497
  }
4718
5498
  if (!data) {
4719
- throw new import_common16.UpdateError(
5499
+ throw new import_common18.UpdateError(
4720
5500
  "NeedSigningKey requires secret key bytes.",
4721
- import_common16.INVALID_DID_UPDATE
5501
+ import_common18.INVALID_DID_UPDATE
4722
5502
  );
4723
5503
  }
4724
5504
  if (!this.#unsignedUpdate) {
4725
- throw new import_common16.UpdateError(
5505
+ throw new import_common18.UpdateError(
4726
5506
  "Internal error: unsigned update missing in Sign phase.",
4727
- import_common16.INVALID_DID_UPDATE
5507
+ import_common18.INVALID_DID_UPDATE
4728
5508
  );
4729
5509
  }
4730
5510
  this.#signedUpdate = this.#sign(data);
@@ -4733,9 +5513,9 @@ var Updater = class _Updater {
4733
5513
  }
4734
5514
  case "NeedFunding": {
4735
5515
  if (this.#phase !== "Fund" /* Fund */) {
4736
- throw new import_common16.UpdateError(
5516
+ throw new import_common18.UpdateError(
4737
5517
  `Cannot provide NeedFunding: updater phase is ${this.#phase}, expected Fund.`,
4738
- import_common16.INVALID_DID_UPDATE,
5518
+ import_common18.INVALID_DID_UPDATE,
4739
5519
  { phase: this.#phase }
4740
5520
  );
4741
5521
  }
@@ -4744,9 +5524,9 @@ var Updater = class _Updater {
4744
5524
  }
4745
5525
  case "NeedBroadcast": {
4746
5526
  if (this.#phase !== "Broadcast" /* Broadcast */) {
4747
- throw new import_common16.UpdateError(
5527
+ throw new import_common18.UpdateError(
4748
5528
  `Cannot provide NeedBroadcast: updater phase is ${this.#phase}, expected Broadcast.`,
4749
- import_common16.INVALID_DID_UPDATE,
5529
+ import_common18.INVALID_DID_UPDATE,
4750
5530
  { phase: this.#phase }
4751
5531
  );
4752
5532
  }
@@ -4758,7 +5538,6 @@ var Updater = class _Updater {
4758
5538
  };
4759
5539
 
4760
5540
  // src/did-btcr2.ts
4761
- (0, import_bitcoinjs_lib7.initEccLib)(ecc);
4762
5541
  var DidBtcr2 = class {
4763
5542
  /**
4764
5543
  * Name of the DID method, as defined in the DID BTCR2 specification
@@ -4783,9 +5562,9 @@ var DidBtcr2 = class {
4783
5562
  static create(genesisBytes, options) {
4784
5563
  const { idType, version = 1, network = "bitcoin" } = options || {};
4785
5564
  if (!idType) {
4786
- throw new import_common17.MethodError(
5565
+ throw new import_common19.MethodError(
4787
5566
  "idType is required for creating a did:btcr2 identifier",
4788
- import_common17.INVALID_DID_DOCUMENT,
5567
+ import_common19.INVALID_DID_DOCUMENT,
4789
5568
  options
4790
5569
  );
4791
5570
  }
@@ -4815,7 +5594,7 @@ var DidBtcr2 = class {
4815
5594
  static resolve(did, resolutionOptions = {}) {
4816
5595
  const didComponents = Identifier.decode(did);
4817
5596
  const sidecarData = Resolver.sidecarData(resolutionOptions.sidecar);
4818
- 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;
4819
5598
  return new Resolver(didComponents, sidecarData, currentDocument, {
4820
5599
  versionId: resolutionOptions.versionId,
4821
5600
  versionTime: resolutionOptions.versionTime,
@@ -4853,39 +5632,39 @@ var DidBtcr2 = class {
4853
5632
  beaconId
4854
5633
  }) {
4855
5634
  if (!sourceDocument.capabilityInvocation?.some((vr) => vr === verificationMethodId)) {
4856
- throw new import_common17.UpdateError(
5635
+ throw new import_common19.UpdateError(
4857
5636
  "Invalid verificationMethodId: not authorized for capabilityInvocation",
4858
- import_common17.INVALID_DID_DOCUMENT,
5637
+ import_common19.INVALID_DID_DOCUMENT,
4859
5638
  sourceDocument
4860
5639
  );
4861
5640
  }
4862
5641
  const verificationMethod = this.getSigningMethod(sourceDocument, verificationMethodId);
4863
5642
  if (!verificationMethod) {
4864
- throw new import_common17.UpdateError(
5643
+ throw new import_common19.UpdateError(
4865
5644
  "Invalid verificationMethod: not found in source document",
4866
- import_common17.INVALID_DID_DOCUMENT,
5645
+ import_common19.INVALID_DID_DOCUMENT,
4867
5646
  { sourceDocument, verificationMethodId }
4868
5647
  );
4869
5648
  }
4870
5649
  if (verificationMethod.type !== "Multikey") {
4871
- throw new import_common17.UpdateError(
5650
+ throw new import_common19.UpdateError(
4872
5651
  'Invalid verificationMethod: verificationMethod.type must be "Multikey"',
4873
- import_common17.INVALID_DID_DOCUMENT,
5652
+ import_common19.INVALID_DID_DOCUMENT,
4874
5653
  verificationMethod
4875
5654
  );
4876
5655
  }
4877
5656
  if (verificationMethod.publicKeyMultibase?.slice(0, 4) !== "zQ3s") {
4878
- throw new import_common17.UpdateError(
5657
+ throw new import_common19.UpdateError(
4879
5658
  'Invalid verificationMethodId: publicKeyMultibase prefix must start with "zQ3s"',
4880
- import_common17.INVALID_DID_DOCUMENT,
5659
+ import_common19.INVALID_DID_DOCUMENT,
4881
5660
  verificationMethod
4882
5661
  );
4883
5662
  }
4884
5663
  const beaconService = sourceDocument.service.filter((service) => service.id === beaconId).filter((service) => !!service).shift();
4885
5664
  if (!beaconService) {
4886
- throw new import_common17.UpdateError(
5665
+ throw new import_common19.UpdateError(
4887
5666
  "No beacon service found for provided beaconId",
4888
- import_common17.INVALID_DID_UPDATE,
5667
+ import_common19.INVALID_DID_UPDATE,
4889
5668
  { sourceDocument, beaconId }
4890
5669
  );
4891
5670
  }
@@ -4911,7 +5690,7 @@ var DidBtcr2 = class {
4911
5690
  methodId ??= "#initialKey";
4912
5691
  const parsedDid = import_dids2.Did.parse(didDocument.id);
4913
5692
  if (parsedDid && parsedDid.method !== this.methodName) {
4914
- 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 });
4915
5694
  }
4916
5695
  const verificationMethod = didDocument.verificationMethod?.find(
4917
5696
  (vm) => Appendix.extractDidFragment(vm.id) === (Appendix.extractDidFragment(methodId) ?? Appendix.extractDidFragment(didDocument.assertionMethod?.[0]))
@@ -4967,7 +5746,7 @@ var Resolver = class _Resolver {
4967
5746
  static deterministic(didComponents) {
4968
5747
  const genesisBytes = didComponents.genesisBytes;
4969
5748
  const did = Identifier.encode(genesisBytes, didComponents);
4970
- const { multibase } = new import_keypair5.CompressedSecp256k1PublicKey(genesisBytes);
5749
+ const { multibase } = new import_keypair4.CompressedSecp256k1PublicKey(genesisBytes);
4971
5750
  const service = BeaconUtils.generateBeaconServices({
4972
5751
  id: did,
4973
5752
  publicKey: genesisBytes,
@@ -4993,14 +5772,14 @@ var Resolver = class _Resolver {
4993
5772
  * @throws {ResolveError} InvalidDidDocument if not conformant to DID Core v1.1
4994
5773
  */
4995
5774
  static external(didComponents, genesisDocument) {
4996
- const genesisDocumentHash = (0, import_common18.canonicalHashBytes)(genesisDocument);
5775
+ const genesisDocumentHash = (0, import_common20.canonicalHashBytes)(genesisDocument);
4997
5776
  if (!(0, import_utils10.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
4998
- throw new import_common18.ResolveError(
5777
+ throw new import_common20.ResolveError(
4999
5778
  `Initial document mismatch: genesisBytes !== genesisDocumentHash`,
5000
- import_common18.INVALID_DID_DOCUMENT,
5779
+ import_common20.INVALID_DID_DOCUMENT,
5001
5780
  {
5002
- genesisBytes: (0, import_common18.encode)(didComponents.genesisBytes, "hex"),
5003
- genesisDocumentHash: (0, import_common18.encode)(genesisDocumentHash, "hex")
5781
+ genesisBytes: (0, import_common20.encode)(didComponents.genesisBytes, "hex"),
5782
+ genesisDocumentHash: (0, import_common20.encode)(genesisDocumentHash, "hex")
5004
5783
  }
5005
5784
  );
5006
5785
  }
@@ -5019,12 +5798,12 @@ var Resolver = class _Resolver {
5019
5798
  const updateMap = /* @__PURE__ */ new Map();
5020
5799
  if (sidecar.updates?.length)
5021
5800
  for (const update of sidecar.updates) {
5022
- updateMap.set((0, import_common18.canonicalHash)(update, { encoding: "hex" }), update);
5801
+ updateMap.set((0, import_common20.canonicalHash)(update, { encoding: "hex" }), update);
5023
5802
  }
5024
5803
  const casMap = /* @__PURE__ */ new Map();
5025
5804
  if (sidecar.casUpdates?.length)
5026
5805
  for (const update of sidecar.casUpdates) {
5027
- casMap.set((0, import_common18.canonicalHash)(update, { encoding: "hex" }), update);
5806
+ casMap.set((0, import_common20.canonicalHash)(update, { encoding: "hex" }), update);
5028
5807
  }
5029
5808
  const smtMap = /* @__PURE__ */ new Map();
5030
5809
  if (sidecar.smtProofs?.length)
@@ -5057,12 +5836,12 @@ var Resolver = class _Resolver {
5057
5836
  }
5058
5837
  };
5059
5838
  for (const [update, block] of updates) {
5060
- const currentDocumentHash = (0, import_common18.canonicalHashBytes)(response.didDocument);
5061
- const blocktime = import_common18.DateUtils.blocktimeToTimestamp(block.time);
5062
- 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);
5063
5842
  response.metadata.confirmations = block.confirmations;
5064
5843
  if (versionTime) {
5065
- if (blocktime > import_common18.DateUtils.dateStringToTimestamp(versionTime)) {
5844
+ if (blocktime > import_common20.DateUtils.dateStringToTimestamp(versionTime)) {
5066
5845
  return response;
5067
5846
  }
5068
5847
  }
@@ -5070,22 +5849,22 @@ var Resolver = class _Resolver {
5070
5849
  updateHashHistory.push(currentDocumentHash);
5071
5850
  this.confirmDuplicate(update, updateHashHistory);
5072
5851
  } else if (update.targetVersionId === currentVersionId + 1) {
5073
- const sourceHashBytes = (0, import_common18.decode)(update.sourceHash, "base64urlnopad");
5852
+ const sourceHashBytes = (0, import_common20.decode)(update.sourceHash, "base64urlnopad");
5074
5853
  if (!(0, import_utils10.equalBytes)(sourceHashBytes, currentDocumentHash)) {
5075
- throw new import_common18.ResolveError(
5854
+ throw new import_common20.ResolveError(
5076
5855
  `Hash mismatch: update.sourceHash !== currentDocumentHash`,
5077
- import_common18.INVALID_DID_UPDATE,
5856
+ import_common20.INVALID_DID_UPDATE,
5078
5857
  {
5079
5858
  sourceHash: update.sourceHash,
5080
- currentDocumentHash: (0, import_common18.encode)(currentDocumentHash, "hex")
5859
+ currentDocumentHash: (0, import_common20.encode)(currentDocumentHash, "hex")
5081
5860
  }
5082
5861
  );
5083
5862
  }
5084
5863
  response.didDocument = this.applyUpdate(response.didDocument, update);
5085
- const unsignedUpdate = import_common18.JSONUtils.deleteKeys(update, ["proof"]);
5086
- updateHashHistory.push((0, import_common18.canonicalHashBytes)(unsignedUpdate));
5864
+ const unsignedUpdate = import_common20.JSONUtils.deleteKeys(update, ["proof"]);
5865
+ updateHashHistory.push((0, import_common20.canonicalHashBytes)(unsignedUpdate));
5087
5866
  } else if (update.targetVersionId > currentVersionId + 1) {
5088
- throw new import_common18.ResolveError(
5867
+ throw new import_common20.ResolveError(
5089
5868
  `Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
5090
5869
  "LATE_PUBLISHING_ERROR",
5091
5870
  {
@@ -5116,15 +5895,15 @@ var Resolver = class _Resolver {
5116
5895
  */
5117
5896
  static confirmDuplicate(update, updateHashHistory) {
5118
5897
  const { proof: _, ...unsignedUpdate } = update;
5119
- const unsignedUpdateHash = (0, import_common18.canonicalHashBytes)(unsignedUpdate);
5898
+ const unsignedUpdateHash = (0, import_common20.canonicalHashBytes)(unsignedUpdate);
5120
5899
  const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
5121
5900
  if (!(0, import_utils10.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
5122
- throw new import_common18.ResolveError(
5901
+ throw new import_common20.ResolveError(
5123
5902
  `Invalid duplicate: unsigned update hash does not match historical hash`,
5124
- import_common18.LATE_PUBLISHING_ERROR,
5903
+ import_common20.LATE_PUBLISHING_ERROR,
5125
5904
  {
5126
- unsignedUpdateHash: (0, import_common18.encode)(unsignedUpdateHash, "hex"),
5127
- historicalHash: (0, import_common18.encode)(historicalUpdateHash, "hex")
5905
+ unsignedUpdateHash: (0, import_common20.encode)(unsignedUpdateHash, "hex"),
5906
+ historicalHash: (0, import_common20.encode)(historicalUpdateHash, "hex")
5128
5907
  }
5129
5908
  );
5130
5909
  }
@@ -5139,42 +5918,42 @@ var Resolver = class _Resolver {
5139
5918
  static applyUpdate(currentDocument, update) {
5140
5919
  const capabilityId = update.proof?.capability;
5141
5920
  if (!capabilityId) {
5142
- 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);
5143
5922
  }
5144
5923
  const rootCapability = Appendix.dereferenceZcapId(capabilityId);
5145
5924
  const { invocationTarget, controller: rootController } = rootCapability;
5146
5925
  if (![invocationTarget, rootController].every((id) => id === currentDocument.id)) {
5147
- throw new import_common18.ResolveError(
5926
+ throw new import_common20.ResolveError(
5148
5927
  "Invalid root capability",
5149
- import_common18.INVALID_DID_UPDATE,
5928
+ import_common20.INVALID_DID_UPDATE,
5150
5929
  { rootCapability, currentDocument }
5151
5930
  );
5152
5931
  }
5153
5932
  const verificationMethodId = update.proof?.verificationMethod;
5154
5933
  if (!verificationMethodId) {
5155
- 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);
5156
5935
  }
5157
5936
  const vm = DidBtcr2.getSigningMethod(currentDocument, verificationMethodId);
5158
- const multikey = import_cryptosuite2.SchnorrMultikey.fromVerificationMethod(vm);
5159
- const cryptosuite = new import_cryptosuite2.BIP340Cryptosuite(multikey);
5160
- const canonicalUpdate = (0, import_common18.canonicalize)(update);
5161
- 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);
5162
5941
  const verificationResult = diProof.verifyProof(canonicalUpdate, "capabilityInvocation");
5163
5942
  if (!verificationResult.verified) {
5164
- throw new import_common18.ResolveError(
5943
+ throw new import_common20.ResolveError(
5165
5944
  "Invalid update: proof not verified",
5166
- import_common18.INVALID_DID_UPDATE,
5945
+ import_common20.INVALID_DID_UPDATE,
5167
5946
  verificationResult
5168
5947
  );
5169
5948
  }
5170
- const updatedDocument = import_common18.JSONPatch.apply(currentDocument, update.patch);
5949
+ const updatedDocument = import_common20.JSONPatch.apply(currentDocument, update.patch);
5171
5950
  DidDocument.validate(updatedDocument);
5172
- const currentDocumentHash = (0, import_common18.canonicalHashBytes)(updatedDocument);
5173
- const updateTargetHash = (0, import_common18.decode)(update.targetHash);
5951
+ const currentDocumentHash = (0, import_common20.canonicalHashBytes)(updatedDocument);
5952
+ const updateTargetHash = (0, import_common20.decode)(update.targetHash);
5174
5953
  if (!(0, import_utils10.equalBytes)(updateTargetHash, currentDocumentHash)) {
5175
- throw new import_common18.ResolveError(
5954
+ throw new import_common20.ResolveError(
5176
5955
  `Invalid update: update.targetHash !== currentDocumentHash`,
5177
- import_common18.INVALID_DID_UPDATE,
5956
+ import_common20.INVALID_DID_UPDATE,
5178
5957
  { updateTargetHash, currentDocumentHash }
5179
5958
  );
5180
5959
  }
@@ -5202,7 +5981,7 @@ var Resolver = class _Resolver {
5202
5981
  this.#phase = "BeaconDiscovery" /* BeaconDiscovery */;
5203
5982
  continue;
5204
5983
  }
5205
- const genesisHash = (0, import_common18.encode)(this.#didComponents.genesisBytes, "hex");
5984
+ const genesisHash = (0, import_common20.encode)(this.#didComponents.genesisBytes, "hex");
5206
5985
  return {
5207
5986
  status: "action-required",
5208
5987
  needs: [{ kind: "NeedGenesisDocument", genesisHash }]
@@ -5306,21 +6085,21 @@ var Resolver = class _Resolver {
5306
6085
  }
5307
6086
  case "NeedCASAnnouncement": {
5308
6087
  const announcement = data;
5309
- 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);
5310
6089
  break;
5311
6090
  }
5312
6091
  case "NeedSignedUpdate": {
5313
6092
  const update = data;
5314
- 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);
5315
6094
  break;
5316
6095
  }
5317
6096
  case "NeedSMTProof": {
5318
6097
  const smtNeed = need;
5319
6098
  const proof = data;
5320
6099
  if (proof.id !== smtNeed.smtRootHash) {
5321
- throw new import_common18.ResolveError(
6100
+ throw new import_common20.ResolveError(
5322
6101
  `SMT proof root hash mismatch: expected ${smtNeed.smtRootHash}, got ${proof.id}`,
5323
- import_common18.INVALID_DID_UPDATE,
6102
+ import_common20.INVALID_DID_UPDATE,
5324
6103
  { expected: smtNeed.smtRootHash, actual: proof.id }
5325
6104
  );
5326
6105
  }
@@ -5332,12 +6111,12 @@ var Resolver = class _Resolver {
5332
6111
  };
5333
6112
 
5334
6113
  // src/utils/did-document-builder.ts
5335
- var import_common19 = require("@did-btcr2/common");
6114
+ var import_common21 = require("@did-btcr2/common");
5336
6115
  var DidDocumentBuilder = class {
5337
6116
  document = {};
5338
6117
  constructor(initialDocument) {
5339
6118
  if (!initialDocument.id) {
5340
- 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);
5341
6120
  }
5342
6121
  this.document.id = initialDocument.id;
5343
6122
  this.document.verificationMethod = initialDocument.verificationMethod ?? [];
@@ -5389,6 +6168,7 @@ var DidDocumentBuilder = class {
5389
6168
  0 && (module.exports = {
5390
6169
  AGGREGATED_NONCE,
5391
6170
  AGGREGATION_MESSAGE_PREFIX,
6171
+ AGGREGATION_WIRE_VERSION,
5392
6172
  AUTHORIZATION_REQUEST,
5393
6173
  AggregateBeaconError,
5394
6174
  AggregationCohort,
@@ -5415,6 +6195,10 @@ var DidDocumentBuilder = class {
5415
6195
  COHORT_OPT_IN,
5416
6196
  COHORT_OPT_IN_ACCEPT,
5417
6197
  COHORT_READY,
6198
+ CONSOLE_LOGGER,
6199
+ DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
6200
+ DEFAULT_BROADCAST_LOOKBACK_MS,
6201
+ DEFAULT_MAX_UPDATE_SIZE_BYTES,
5418
6202
  DEFAULT_NOSTR_RELAYS,
5419
6203
  DID_REGEX,
5420
6204
  DISTRIBUTE_AGGREGATED_DATA,
@@ -5432,6 +6216,7 @@ var DidDocumentBuilder = class {
5432
6216
  ParticipantCohortPhase,
5433
6217
  Resolver,
5434
6218
  SIGNATURE_AUTHORIZATION,
6219
+ SILENT_LOGGER,
5435
6220
  SMTBeacon,
5436
6221
  SMTBeaconError,
5437
6222
  SUBMIT_UPDATE,
@@ -5447,6 +6232,7 @@ var DidDocumentBuilder = class {
5447
6232
  TypedEventEmitter,
5448
6233
  Updater,
5449
6234
  VALIDATION_ACK,
6235
+ buildAggregationBeaconTx,
5450
6236
  createAggregatedNonceMessage,
5451
6237
  createAuthorizationRequestMessage,
5452
6238
  createCohortAdvertMessage,
@@ -5458,8 +6244,22 @@ var DidDocumentBuilder = class {
5458
6244
  createSignatureAuthorizationMessage,
5459
6245
  createSubmitUpdateMessage,
5460
6246
  createValidationAckMessage,
6247
+ getBeaconStrategy,
6248
+ isAggregatedNonceMessage,
5461
6249
  isAggregationMessageType,
6250
+ isAuthorizationRequestMessage,
6251
+ isCohortAdvertMessage,
6252
+ isCohortOptInAcceptMessage,
6253
+ isCohortOptInMessage,
6254
+ isCohortReadyMessage,
6255
+ isDistributeAggregatedDataMessage,
5462
6256
  isKeygenMessageType,
6257
+ isNonceContributionMessage,
5463
6258
  isSignMessageType,
5464
- isUpdateMessageType
6259
+ isSignatureAuthorizationMessage,
6260
+ isSubmitUpdateMessage,
6261
+ isUpdateMessageType,
6262
+ isValidationAckMessage,
6263
+ opReturnScript,
6264
+ registerBeaconStrategy
5465
6265
  });