@did-btcr2/method 0.28.0 → 0.32.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 (193) hide show
  1. package/README.md +13 -5
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/browser.js +34125 -44647
  4. package/dist/browser.mjs +26409 -36931
  5. package/dist/cjs/index.js +2869 -679
  6. package/dist/esm/core/aggregation/beacon-strategy.js +62 -0
  7. package/dist/esm/core/aggregation/beacon-strategy.js.map +1 -0
  8. package/dist/esm/core/aggregation/cohort.js +31 -8
  9. package/dist/esm/core/aggregation/cohort.js.map +1 -1
  10. package/dist/esm/core/aggregation/logger.js +15 -0
  11. package/dist/esm/core/aggregation/logger.js.map +1 -0
  12. package/dist/esm/core/aggregation/messages/base.js +12 -1
  13. package/dist/esm/core/aggregation/messages/base.js.map +1 -1
  14. package/dist/esm/core/aggregation/messages/bodies.js +90 -0
  15. package/dist/esm/core/aggregation/messages/bodies.js.map +1 -0
  16. package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
  17. package/dist/esm/core/aggregation/messages/index.js +1 -0
  18. package/dist/esm/core/aggregation/messages/index.js.map +1 -1
  19. package/dist/esm/core/aggregation/participant.js +39 -46
  20. package/dist/esm/core/aggregation/participant.js.map +1 -1
  21. package/dist/esm/core/aggregation/runner/participant-runner.js +33 -7
  22. package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
  23. package/dist/esm/core/aggregation/runner/service-runner.js +198 -19
  24. package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
  25. package/dist/esm/core/aggregation/service.js +143 -15
  26. package/dist/esm/core/aggregation/service.js.map +1 -1
  27. package/dist/esm/core/aggregation/signing-session.js +44 -5
  28. package/dist/esm/core/aggregation/signing-session.js.map +1 -1
  29. package/dist/esm/core/aggregation/transport/didcomm.js +9 -0
  30. package/dist/esm/core/aggregation/transport/didcomm.js.map +1 -1
  31. package/dist/esm/core/aggregation/transport/factory.js +15 -6
  32. package/dist/esm/core/aggregation/transport/factory.js.map +1 -1
  33. package/dist/esm/core/aggregation/transport/http/client.js +350 -0
  34. package/dist/esm/core/aggregation/transport/http/client.js.map +1 -0
  35. package/dist/esm/core/aggregation/transport/http/envelope.js +126 -0
  36. package/dist/esm/core/aggregation/transport/http/envelope.js.map +1 -0
  37. package/dist/esm/core/aggregation/transport/http/errors.js +11 -0
  38. package/dist/esm/core/aggregation/transport/http/errors.js.map +1 -0
  39. package/dist/esm/core/aggregation/transport/http/inbox-buffer.js +45 -0
  40. package/dist/esm/core/aggregation/transport/http/inbox-buffer.js.map +1 -0
  41. package/dist/esm/core/aggregation/transport/http/index.js +12 -0
  42. package/dist/esm/core/aggregation/transport/http/index.js.map +1 -0
  43. package/dist/esm/core/aggregation/transport/http/nonce-cache.js +38 -0
  44. package/dist/esm/core/aggregation/transport/http/nonce-cache.js.map +1 -0
  45. package/dist/esm/core/aggregation/transport/http/protocol.js +28 -0
  46. package/dist/esm/core/aggregation/transport/http/protocol.js.map +1 -0
  47. package/dist/esm/core/aggregation/transport/http/rate-limiter.js +45 -0
  48. package/dist/esm/core/aggregation/transport/http/rate-limiter.js.map +1 -0
  49. package/dist/esm/core/aggregation/transport/http/request-auth.js +100 -0
  50. package/dist/esm/core/aggregation/transport/http/request-auth.js.map +1 -0
  51. package/dist/esm/core/aggregation/transport/http/server.js +481 -0
  52. package/dist/esm/core/aggregation/transport/http/server.js.map +1 -0
  53. package/dist/esm/core/aggregation/transport/http/sse-stream.js +110 -0
  54. package/dist/esm/core/aggregation/transport/http/sse-stream.js.map +1 -0
  55. package/dist/esm/core/aggregation/transport/http/sse-writer.js +25 -0
  56. package/dist/esm/core/aggregation/transport/http/sse-writer.js.map +1 -0
  57. package/dist/esm/core/aggregation/transport/index.js +1 -0
  58. package/dist/esm/core/aggregation/transport/index.js.map +1 -1
  59. package/dist/esm/core/aggregation/transport/nostr.js +245 -16
  60. package/dist/esm/core/aggregation/transport/nostr.js.map +1 -1
  61. package/dist/esm/core/beacon/beacon.js +295 -63
  62. package/dist/esm/core/beacon/beacon.js.map +1 -1
  63. package/dist/esm/core/beacon/cas-beacon.js +3 -3
  64. package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
  65. package/dist/esm/core/beacon/singleton-beacon.js +3 -3
  66. package/dist/esm/core/beacon/singleton-beacon.js.map +1 -1
  67. package/dist/esm/core/beacon/smt-beacon.js +3 -3
  68. package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
  69. package/dist/esm/core/beacon/utils.js +14 -9
  70. package/dist/esm/core/beacon/utils.js.map +1 -1
  71. package/dist/esm/core/updater.js +63 -55
  72. package/dist/esm/core/updater.js.map +1 -1
  73. package/dist/esm/did-btcr2.js +0 -4
  74. package/dist/esm/did-btcr2.js.map +1 -1
  75. package/dist/esm/index.js +2 -0
  76. package/dist/esm/index.js.map +1 -1
  77. package/dist/esm/utils/did-document.js +2 -2
  78. package/dist/esm/utils/did-document.js.map +1 -1
  79. package/dist/types/core/aggregation/beacon-strategy.d.ts +52 -0
  80. package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -0
  81. package/dist/types/core/aggregation/cohort.d.ts +20 -3
  82. package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
  83. package/dist/types/core/aggregation/logger.d.ts +22 -0
  84. package/dist/types/core/aggregation/logger.d.ts.map +1 -0
  85. package/dist/types/core/aggregation/messages/base.d.ts +13 -1
  86. package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
  87. package/dist/types/core/aggregation/messages/bodies.d.ts +130 -0
  88. package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -0
  89. package/dist/types/core/aggregation/messages/factories.d.ts +1 -0
  90. package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
  91. package/dist/types/core/aggregation/messages/index.d.ts +1 -0
  92. package/dist/types/core/aggregation/messages/index.d.ts.map +1 -1
  93. package/dist/types/core/aggregation/participant.d.ts +2 -0
  94. package/dist/types/core/aggregation/participant.d.ts.map +1 -1
  95. package/dist/types/core/aggregation/runner/events.d.ts +32 -6
  96. package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
  97. package/dist/types/core/aggregation/runner/participant-runner.d.ts +7 -5
  98. package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
  99. package/dist/types/core/aggregation/runner/service-runner.d.ts +33 -3
  100. package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
  101. package/dist/types/core/aggregation/service.d.ts +33 -2
  102. package/dist/types/core/aggregation/service.d.ts.map +1 -1
  103. package/dist/types/core/aggregation/signing-session.d.ts +5 -1
  104. package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
  105. package/dist/types/core/aggregation/transport/didcomm.d.ts +3 -0
  106. package/dist/types/core/aggregation/transport/didcomm.d.ts.map +1 -1
  107. package/dist/types/core/aggregation/transport/factory.d.ts +22 -7
  108. package/dist/types/core/aggregation/transport/factory.d.ts.map +1 -1
  109. package/dist/types/core/aggregation/transport/http/client.d.ts +48 -0
  110. package/dist/types/core/aggregation/transport/http/client.d.ts.map +1 -0
  111. package/dist/types/core/aggregation/transport/http/envelope.d.ts +64 -0
  112. package/dist/types/core/aggregation/transport/http/envelope.d.ts.map +1 -0
  113. package/dist/types/core/aggregation/transport/http/errors.d.ts +9 -0
  114. package/dist/types/core/aggregation/transport/http/errors.d.ts.map +1 -0
  115. package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts +32 -0
  116. package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts.map +1 -0
  117. package/dist/types/core/aggregation/transport/http/index.d.ts +12 -0
  118. package/dist/types/core/aggregation/transport/http/index.d.ts.map +1 -0
  119. package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts +26 -0
  120. package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts.map +1 -0
  121. package/dist/types/core/aggregation/transport/http/protocol.d.ts +53 -0
  122. package/dist/types/core/aggregation/transport/http/protocol.d.ts.map +1 -0
  123. package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts +41 -0
  124. package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts.map +1 -0
  125. package/dist/types/core/aggregation/transport/http/request-auth.d.ts +50 -0
  126. package/dist/types/core/aggregation/transport/http/request-auth.d.ts.map +1 -0
  127. package/dist/types/core/aggregation/transport/http/server.d.ts +110 -0
  128. package/dist/types/core/aggregation/transport/http/server.d.ts.map +1 -0
  129. package/dist/types/core/aggregation/transport/http/sse-stream.d.ts +34 -0
  130. package/dist/types/core/aggregation/transport/http/sse-stream.d.ts.map +1 -0
  131. package/dist/types/core/aggregation/transport/http/sse-writer.d.ts +12 -0
  132. package/dist/types/core/aggregation/transport/http/sse-writer.d.ts.map +1 -0
  133. package/dist/types/core/aggregation/transport/index.d.ts +1 -0
  134. package/dist/types/core/aggregation/transport/index.d.ts.map +1 -1
  135. package/dist/types/core/aggregation/transport/nostr.d.ts +99 -1
  136. package/dist/types/core/aggregation/transport/nostr.d.ts.map +1 -1
  137. package/dist/types/core/aggregation/transport/transport.d.ts +26 -1
  138. package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
  139. package/dist/types/core/beacon/beacon.d.ts +149 -22
  140. package/dist/types/core/beacon/beacon.d.ts.map +1 -1
  141. package/dist/types/core/beacon/cas-beacon.d.ts +3 -3
  142. package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
  143. package/dist/types/core/beacon/singleton-beacon.d.ts +3 -3
  144. package/dist/types/core/beacon/singleton-beacon.d.ts.map +1 -1
  145. package/dist/types/core/beacon/smt-beacon.d.ts +3 -3
  146. package/dist/types/core/beacon/smt-beacon.d.ts.map +1 -1
  147. package/dist/types/core/beacon/utils.d.ts +2 -2
  148. package/dist/types/core/beacon/utils.d.ts.map +1 -1
  149. package/dist/types/core/updater.d.ts +27 -12
  150. package/dist/types/core/updater.d.ts.map +1 -1
  151. package/dist/types/did-btcr2.d.ts.map +1 -1
  152. package/dist/types/index.d.ts +2 -0
  153. package/dist/types/index.d.ts.map +1 -1
  154. package/package.json +5 -7
  155. package/src/core/aggregation/beacon-strategy.ts +123 -0
  156. package/src/core/aggregation/cohort.ts +34 -8
  157. package/src/core/aggregation/logger.ts +33 -0
  158. package/src/core/aggregation/messages/base.ts +20 -5
  159. package/src/core/aggregation/messages/bodies.ts +223 -0
  160. package/src/core/aggregation/messages/factories.ts +1 -0
  161. package/src/core/aggregation/messages/index.ts +1 -0
  162. package/src/core/aggregation/participant.ts +40 -46
  163. package/src/core/aggregation/runner/events.ts +27 -3
  164. package/src/core/aggregation/runner/participant-runner.ts +41 -7
  165. package/src/core/aggregation/runner/service-runner.ts +227 -19
  166. package/src/core/aggregation/service.ts +189 -20
  167. package/src/core/aggregation/signing-session.ts +65 -7
  168. package/src/core/aggregation/transport/didcomm.ts +17 -0
  169. package/src/core/aggregation/transport/factory.ts +48 -12
  170. package/src/core/aggregation/transport/http/client.ts +409 -0
  171. package/src/core/aggregation/transport/http/envelope.ts +204 -0
  172. package/src/core/aggregation/transport/http/errors.ts +11 -0
  173. package/src/core/aggregation/transport/http/inbox-buffer.ts +53 -0
  174. package/src/core/aggregation/transport/http/index.ts +11 -0
  175. package/src/core/aggregation/transport/http/nonce-cache.ts +43 -0
  176. package/src/core/aggregation/transport/http/protocol.ts +57 -0
  177. package/src/core/aggregation/transport/http/rate-limiter.ts +75 -0
  178. package/src/core/aggregation/transport/http/request-auth.ts +164 -0
  179. package/src/core/aggregation/transport/http/server.ts +615 -0
  180. package/src/core/aggregation/transport/http/sse-stream.ts +121 -0
  181. package/src/core/aggregation/transport/http/sse-writer.ts +23 -0
  182. package/src/core/aggregation/transport/index.ts +1 -0
  183. package/src/core/aggregation/transport/nostr.ts +266 -23
  184. package/src/core/aggregation/transport/transport.ts +34 -1
  185. package/src/core/beacon/beacon.ts +411 -79
  186. package/src/core/beacon/cas-beacon.ts +4 -4
  187. package/src/core/beacon/singleton-beacon.ts +4 -4
  188. package/src/core/beacon/smt-beacon.ts +4 -4
  189. package/src/core/beacon/utils.ts +16 -11
  190. package/src/core/updater.ts +113 -67
  191. package/src/did-btcr2.ts +0 -5
  192. package/src/index.ts +2 -0
  193. 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,12 @@ __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_CLOCK_SKEW_SEC: () => DEFAULT_CLOCK_SKEW_SEC,
66
+ DEFAULT_MAX_UPDATE_SIZE_BYTES: () => DEFAULT_MAX_UPDATE_SIZE_BYTES,
67
+ DEFAULT_NONCE_LEN_BYTES: () => DEFAULT_NONCE_LEN_BYTES,
61
68
  DEFAULT_NOSTR_RELAYS: () => DEFAULT_NOSTR_RELAYS,
62
69
  DID_REGEX: () => DID_REGEX,
63
70
  DISTRIBUTE_AGGREGATED_DATA: () => DISTRIBUTE_AGGREGATED_DATA,
@@ -68,15 +75,31 @@ __export(index_exports, {
68
75
  DidVerificationMethod: () => DidVerificationMethod,
69
76
  Document: () => Document,
70
77
  GenesisDocument: () => GenesisDocument,
78
+ HTTP_ENVELOPE_VERSION: () => HTTP_ENVELOPE_VERSION,
79
+ HTTP_ROUTE: () => HTTP_ROUTE,
80
+ HttpClientTransport: () => HttpClientTransport,
81
+ HttpServerTransport: () => HttpServerTransport,
82
+ HttpTransportError: () => HttpTransportError,
71
83
  ID_PLACEHOLDER_VALUE: () => ID_PLACEHOLDER_VALUE,
72
84
  Identifier: () => Identifier,
85
+ InMemoryRateLimitStore: () => InMemoryRateLimitStore,
86
+ InboxBuffer: () => InboxBuffer,
73
87
  NONCE_CONTRIBUTION: () => NONCE_CONTRIBUTION,
88
+ NonceCache: () => NonceCache,
74
89
  NostrTransport: () => NostrTransport,
90
+ P2PKH_BEACON_TX_VSIZE: () => P2PKH_BEACON_TX_VSIZE,
91
+ P2TR_BEACON_TX_VSIZE: () => P2TR_BEACON_TX_VSIZE,
92
+ P2WPKH_BEACON_TX_VSIZE: () => P2WPKH_BEACON_TX_VSIZE,
75
93
  ParticipantCohortPhase: () => ParticipantCohortPhase,
94
+ REQUEST_AUTH_SCHEME: () => REQUEST_AUTH_SCHEME,
95
+ RateLimiter: () => RateLimiter,
76
96
  Resolver: () => Resolver,
77
97
  SIGNATURE_AUTHORIZATION: () => SIGNATURE_AUTHORIZATION,
98
+ SILENT_LOGGER: () => SILENT_LOGGER,
99
+ SINGLETON_BEACON_TX_VSIZE: () => SINGLETON_BEACON_TX_VSIZE,
78
100
  SMTBeacon: () => SMTBeacon,
79
101
  SMTBeaconError: () => SMTBeaconError,
102
+ SSE_EVENT: () => SSE_EVENT,
80
103
  SUBMIT_UPDATE: () => SUBMIT_UPDATE,
81
104
  ServiceCohortPhase: () => ServiceCohortPhase,
82
105
  SigningSessionError: () => SigningSessionError,
@@ -90,6 +113,8 @@ __export(index_exports, {
90
113
  TypedEventEmitter: () => TypedEventEmitter,
91
114
  Updater: () => Updater,
92
115
  VALIDATION_ACK: () => VALIDATION_ACK,
116
+ buildAggregationBeaconTx: () => buildAggregationBeaconTx,
117
+ buildRequestAuth: () => buildRequestAuth,
93
118
  createAggregatedNonceMessage: () => createAggregatedNonceMessage,
94
119
  createAuthorizationRequestMessage: () => createAuthorizationRequestMessage,
95
120
  createCohortAdvertMessage: () => createCohortAdvertMessage,
@@ -101,41 +126,126 @@ __export(index_exports, {
101
126
  createSignatureAuthorizationMessage: () => createSignatureAuthorizationMessage,
102
127
  createSubmitUpdateMessage: () => createSubmitUpdateMessage,
103
128
  createValidationAckMessage: () => createValidationAckMessage,
129
+ defaultReconnectBackoff: () => defaultReconnectBackoff,
130
+ deriveSingletonAddress: () => deriveSingletonAddress,
131
+ detectSingletonScriptKind: () => detectSingletonScriptKind,
132
+ formatSseComment: () => formatSseComment,
133
+ formatSseEvent: () => formatSseEvent,
134
+ getBeaconStrategy: () => getBeaconStrategy,
135
+ isAggregatedNonceMessage: () => isAggregatedNonceMessage,
104
136
  isAggregationMessageType: () => isAggregationMessageType,
137
+ isAuthorizationRequestMessage: () => isAuthorizationRequestMessage,
138
+ isCohortAdvertMessage: () => isCohortAdvertMessage,
139
+ isCohortOptInAcceptMessage: () => isCohortOptInAcceptMessage,
140
+ isCohortOptInMessage: () => isCohortOptInMessage,
141
+ isCohortReadyMessage: () => isCohortReadyMessage,
142
+ isDistributeAggregatedDataMessage: () => isDistributeAggregatedDataMessage,
105
143
  isKeygenMessageType: () => isKeygenMessageType,
144
+ isNonceContributionMessage: () => isNonceContributionMessage,
106
145
  isSignMessageType: () => isSignMessageType,
107
- isUpdateMessageType: () => isUpdateMessageType
146
+ isSignatureAuthorizationMessage: () => isSignatureAuthorizationMessage,
147
+ isSubmitUpdateMessage: () => isSubmitUpdateMessage,
148
+ isUpdateMessageType: () => isUpdateMessageType,
149
+ isValidationAckMessage: () => isValidationAckMessage,
150
+ normalizeForWire: () => normalizeForWire,
151
+ opReturnScript: () => opReturnScript,
152
+ parseRequestAuth: () => parseRequestAuth,
153
+ parseSseStream: () => parseSseStream,
154
+ registerBeaconStrategy: () => registerBeaconStrategy,
155
+ reviveFromWire: () => reviveFromWire,
156
+ signEnvelope: () => signEnvelope,
157
+ verifyEnvelope: () => verifyEnvelope,
158
+ verifyRequestAuth: () => verifyRequestAuth
108
159
  });
109
160
  module.exports = __toCommonJS(index_exports);
110
161
 
111
162
  // src/core/aggregation/service.ts
163
+ var import_common4 = require("@did-btcr2/common");
164
+ var import_cryptosuite = require("@did-btcr2/cryptosuite");
112
165
  var import_utils2 = require("@noble/hashes/utils");
113
166
 
114
- // src/core/aggregation/cohort.ts
115
- var import_common2 = require("@did-btcr2/common");
167
+ // src/core/aggregation/beacon-strategy.ts
168
+ var import_common = require("@did-btcr2/common");
116
169
  var import_smt = require("@did-btcr2/smt");
170
+ var CAS_STRATEGY = {
171
+ type: "CASBeacon",
172
+ buildAggregatedData(cohort) {
173
+ cohort.buildCASAnnouncement();
174
+ },
175
+ getDistributePayload(cohort) {
176
+ return { casAnnouncement: cohort.casAnnouncement };
177
+ },
178
+ validateParticipantView({ participantDid, expectedHash, body }) {
179
+ const casAnnouncement = body.casAnnouncement;
180
+ if (!casAnnouncement) return { matches: false };
181
+ return {
182
+ matches: casAnnouncement[participantDid] === expectedHash,
183
+ casAnnouncement
184
+ };
185
+ }
186
+ };
187
+ var SMT_STRATEGY = {
188
+ type: "SMTBeacon",
189
+ buildAggregatedData(cohort) {
190
+ cohort.buildSMTTree();
191
+ },
192
+ getDistributePayload(cohort, participantDid) {
193
+ const proof = cohort.smtProofs?.get(participantDid);
194
+ return { smtProof: proof };
195
+ },
196
+ validateParticipantView({ participantDid, submittedUpdate, body }) {
197
+ const smtProof = body.smtProof;
198
+ if (!smtProof?.updateId || !smtProof?.nonce) return { matches: false };
199
+ const canonicalBytes = new TextEncoder().encode((0, import_common.canonicalize)(submittedUpdate));
200
+ const expectedUpdateId = (0, import_smt.hashToHex)((0, import_smt.blockHash)(canonicalBytes));
201
+ if (smtProof.updateId !== expectedUpdateId) {
202
+ return { matches: false, smtProof };
203
+ }
204
+ const index = (0, import_smt.didToIndex)(participantDid);
205
+ const candidateHash = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.hexToHash)(smtProof.nonce)), (0, import_smt.hexToHash)(smtProof.updateId));
206
+ return {
207
+ matches: (0, import_smt.verifySerializedProof)(smtProof, index, candidateHash),
208
+ smtProof
209
+ };
210
+ }
211
+ };
212
+ var STRATEGIES = /* @__PURE__ */ new Map([
213
+ [CAS_STRATEGY.type, CAS_STRATEGY],
214
+ [SMT_STRATEGY.type, SMT_STRATEGY]
215
+ ]);
216
+ function registerBeaconStrategy(strategy) {
217
+ STRATEGIES.set(strategy.type, strategy);
218
+ }
219
+ function getBeaconStrategy(type) {
220
+ return STRATEGIES.get(type);
221
+ }
222
+
223
+ // src/core/aggregation/cohort.ts
224
+ var import_common3 = require("@did-btcr2/common");
225
+ var import_smt2 = require("@did-btcr2/smt");
226
+ var import_secp256k1 = require("@noble/curves/secp256k1.js");
117
227
  var import_utils = require("@noble/hashes/utils");
228
+ var import_btc_signer = require("@scure/btc-signer");
118
229
  var import_musig2 = require("@scure/btc-signer/musig2");
119
- var import_bitcoinjs_lib = require("bitcoinjs-lib");
120
230
 
121
231
  // src/core/aggregation/errors.ts
122
- var import_common = require("@did-btcr2/common");
123
- var AggregationServiceError = class extends import_common.MethodError {
232
+ var import_common2 = require("@did-btcr2/common");
233
+ var AggregationServiceError = class extends import_common2.MethodError {
124
234
  constructor(message, type = "AggregationServiceError", data) {
125
235
  super(message, type, data);
126
236
  }
127
237
  };
128
- var AggregationParticipantError = class extends import_common.MethodError {
238
+ var AggregationParticipantError = class extends import_common2.MethodError {
129
239
  constructor(message, type = "AggregationParticipantError", data) {
130
240
  super(message, type, data);
131
241
  }
132
242
  };
133
- var AggregationCohortError = class extends import_common.MethodError {
243
+ var AggregationCohortError = class extends import_common2.MethodError {
134
244
  constructor(message, type = "AggregationCohortError", data) {
135
245
  super(message, type, data);
136
246
  }
137
247
  };
138
- var SigningSessionError = class extends import_common.MethodError {
248
+ var SigningSessionError = class extends import_common2.MethodError {
139
249
  constructor(message, type = "SigningSessionError", data) {
140
250
  super(message, type, data);
141
251
  }
@@ -155,10 +265,21 @@ var AggregationCohort = class {
155
265
  beaconType;
156
266
  /** List of participant DIDs that have been accepted into the cohort. */
157
267
  participants = [];
268
+ /**
269
+ * Mapping from participant DID → their compressed secp256k1 public key.
270
+ * Distinct from {@link cohortKeys} (which is sorted per BIP-327) — this lets
271
+ * callers look up a participant's key without knowing their position in the
272
+ * sorted array. Populated by the service at `acceptParticipant` time.
273
+ */
274
+ participantKeys = /* @__PURE__ */ new Map();
158
275
  /** Sorted list of cohort participants' compressed public keys. */
159
276
  #cohortKeys = [];
160
- /** Taproot tweak (BIP-341 key-path-only). */
161
- trMerkleRoot = new Uint8Array();
277
+ /**
278
+ * BIP-341 TapTweak — `taggedHash("TapTweak", internalPubkey)` for a key-path-only
279
+ * Taproot output. Despite prior naming, this is NOT a Merkle root: key-path-only
280
+ * spends have no script tree.
281
+ */
282
+ tapTweak = new Uint8Array();
162
283
  /** The n-of-n MuSig2 Taproot beacon address. */
163
284
  beaconAddress = "";
164
285
  /** Pending DID updates submitted by participants, keyed by DID. */
@@ -189,7 +310,7 @@ var AggregationCohort = class {
189
310
  }
190
311
  /**
191
312
  * Computes the n-of-n MuSig2 Taproot beacon address from cohort keys.
192
- * Sets `trMerkleRoot` to the BIP-341 key-path-only tweak.
313
+ * Sets `tapTweak` to the BIP-341 key-path-only tweak.
193
314
  */
194
315
  computeBeaconAddress() {
195
316
  if (this.#cohortKeys.length === 0) {
@@ -201,8 +322,8 @@ var AggregationCohort = class {
201
322
  }
202
323
  const keyAggContext = (0, import_musig2.keyAggregate)(this.#cohortKeys);
203
324
  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);
325
+ const payment = (0, import_btc_signer.p2tr)(aggPubkey);
326
+ this.tapTweak = import_secp256k1.schnorr.utils.taggedHash("TapTweak", aggPubkey);
206
327
  if (!payment.address) {
207
328
  throw new AggregationCohortError(
208
329
  "Failed to compute Taproot address",
@@ -236,6 +357,18 @@ var AggregationCohort = class {
236
357
  );
237
358
  }
238
359
  }
360
+ /**
361
+ * Returns the position of a participant's public key in the sorted
362
+ * {@link cohortKeys} array, or -1 if the participant is not in the cohort.
363
+ * Required by MuSig2 partial-sig verification which indexes by signer position.
364
+ */
365
+ indexOfParticipant(did) {
366
+ const pk = this.participantKeys.get(did);
367
+ if (!pk) return -1;
368
+ return this.#cohortKeys.findIndex(
369
+ (k) => k.length === pk.length && k.every((b, i) => b === pk[i])
370
+ );
371
+ }
239
372
  addUpdate(participantDid, signedUpdate) {
240
373
  if (!this.participants.includes(participantDid)) {
241
374
  throw new AggregationCohortError(
@@ -264,10 +397,10 @@ var AggregationCohort = class {
264
397
  }
265
398
  const announcement = {};
266
399
  for (const [did, signedUpdate] of this.pendingUpdates) {
267
- announcement[did] = (0, import_common2.canonicalHash)(signedUpdate);
400
+ announcement[did] = (0, import_common3.canonicalHash)(signedUpdate);
268
401
  }
269
402
  this.casAnnouncement = announcement;
270
- this.signalBytes = (0, import_common2.hash)((0, import_common2.canonicalize)(announcement));
403
+ this.signalBytes = (0, import_common3.hash)((0, import_common3.canonicalize)(announcement));
271
404
  return announcement;
272
405
  }
273
406
  /**
@@ -283,11 +416,11 @@ var AggregationCohort = class {
283
416
  { cohortId: this.id }
284
417
  );
285
418
  }
286
- const tree = new import_smt.BTCR2MerkleTree();
419
+ const tree = new import_smt2.BTCR2MerkleTree();
287
420
  const entries = [];
288
421
  const encoder = new TextEncoder();
289
422
  for (const [did, signedUpdate] of this.pendingUpdates) {
290
- const canonicalBytes = encoder.encode((0, import_common2.canonicalize)(signedUpdate));
423
+ const canonicalBytes = encoder.encode((0, import_common3.canonicalize)(signedUpdate));
291
424
  const nonce = (0, import_utils.randomBytes)(32);
292
425
  entries.push({ did, nonce, signedUpdate: canonicalBytes });
293
426
  }
@@ -329,28 +462,17 @@ var AggregationCohort = class {
329
462
  }
330
463
  };
331
464
 
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
465
  // src/core/aggregation/messages/base.ts
466
+ var AGGREGATION_WIRE_VERSION = 1;
347
467
  var BaseMessage = class {
348
468
  type;
469
+ version;
349
470
  to;
350
471
  from;
351
472
  body;
352
- constructor({ type, to, from: from2, body }) {
473
+ constructor({ type, version, to, from: from2, body }) {
353
474
  this.type = type;
475
+ this.version = version ?? AGGREGATION_WIRE_VERSION;
354
476
  this.to = to;
355
477
  this.from = from2;
356
478
  this.body = body;
@@ -362,6 +484,7 @@ var BaseMessage = class {
362
484
  toJSON() {
363
485
  return {
364
486
  type: this.type,
487
+ version: this.version,
365
488
  to: this.to,
366
489
  from: this.from,
367
490
  body: this.body
@@ -369,6 +492,20 @@ var BaseMessage = class {
369
492
  }
370
493
  };
371
494
 
495
+ // src/core/aggregation/messages/constants.ts
496
+ var AGGREGATION_MESSAGE_PREFIX = "https://btcr2.dev/aggregation";
497
+ var COHORT_ADVERT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_advert`;
498
+ var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
499
+ var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
500
+ var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
501
+ var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
502
+ var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
503
+ var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
504
+ var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
505
+ var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
506
+ var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
507
+ var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
508
+
372
509
  // src/core/aggregation/messages/factories.ts
373
510
  function createCohortAdvertMessage(fields) {
374
511
  const { from: from2, ...body } = fields;
@@ -456,8 +593,8 @@ var SigningSessionPhase = /* @__PURE__ */ ((SigningSessionPhase2) => {
456
593
  })(SigningSessionPhase || {});
457
594
 
458
595
  // src/core/aggregation/signing-session.ts
596
+ var import_btc_signer2 = require("@scure/btc-signer");
459
597
  var musig2 = __toESM(require("@scure/btc-signer/musig2"), 1);
460
- var import_bitcoinjs_lib2 = require("bitcoinjs-lib");
461
598
  var BeaconSigningSession = class {
462
599
  /** Unique identifier for this signing session. */
463
600
  id;
@@ -499,11 +636,11 @@ var BeaconSigningSession = class {
499
636
  "SIGHASH_ERROR"
500
637
  );
501
638
  }
502
- return this.pendingTx.hashForWitnessV1(
639
+ return this.pendingTx.preimageWitnessV1(
503
640
  0,
504
641
  this.prevOutScripts,
505
- this.prevOutValues,
506
- import_bitcoinjs_lib2.Transaction.SIGHASH_DEFAULT
642
+ import_btc_signer2.SigHash.DEFAULT,
643
+ this.prevOutValues
507
644
  );
508
645
  }
509
646
  addNonceContribution(participantDid, nonceContribution) {
@@ -514,6 +651,13 @@ var BeaconSigningSession = class {
514
651
  { phase: this.phase }
515
652
  );
516
653
  }
654
+ if (!this.cohort.participants.includes(participantDid)) {
655
+ throw new SigningSessionError(
656
+ `Participant ${participantDid} is not a member of cohort ${this.cohort.id}.`,
657
+ "UNKNOWN_PARTICIPANT",
658
+ { cohortId: this.cohort.id, participantDid }
659
+ );
660
+ }
517
661
  if (nonceContribution.length !== 66) {
518
662
  throw new SigningSessionError(
519
663
  `Invalid nonce contribution: expected 66 bytes, got ${nonceContribution.length}.`,
@@ -549,6 +693,13 @@ var BeaconSigningSession = class {
549
693
  "INVALID_PHASE"
550
694
  );
551
695
  }
696
+ if (!this.cohort.participants.includes(participantDid)) {
697
+ throw new SigningSessionError(
698
+ `Participant ${participantDid} is not a member of cohort ${this.cohort.id}.`,
699
+ "UNKNOWN_PARTICIPANT",
700
+ { cohortId: this.cohort.id, participantDid }
701
+ );
702
+ }
552
703
  if (this.partialSignatures.has(participantDid)) {
553
704
  throw new SigningSessionError(
554
705
  `Duplicate partial signature from ${participantDid}.`,
@@ -574,9 +725,39 @@ var BeaconSigningSession = class {
574
725
  this.aggregatedNonce,
575
726
  this.cohort.cohortKeys,
576
727
  this.sigHash,
577
- [this.cohort.trMerkleRoot],
728
+ [this.cohort.tapTweak],
578
729
  [true]
579
730
  );
731
+ const pubNoncesByIndex = new Array(this.cohort.cohortKeys.length);
732
+ for (const [did, nonce] of this.nonceContributions) {
733
+ const idx = this.cohort.indexOfParticipant(did);
734
+ if (idx < 0) {
735
+ throw new SigningSessionError(
736
+ `Cannot verify nonce from ${did}: participant key missing from cohort.`,
737
+ "UNKNOWN_PARTICIPANT_KEY",
738
+ { participantDid: did }
739
+ );
740
+ }
741
+ pubNoncesByIndex[idx] = nonce;
742
+ }
743
+ for (const [did, partialSig] of this.partialSignatures) {
744
+ const idx = this.cohort.indexOfParticipant(did);
745
+ if (idx < 0) {
746
+ throw new SigningSessionError(
747
+ `Cannot verify partial signature from ${did}: participant key missing from cohort.`,
748
+ "UNKNOWN_PARTICIPANT_KEY",
749
+ { participantDid: did }
750
+ );
751
+ }
752
+ const ok = session.partialSigVerify(partialSig, pubNoncesByIndex, idx);
753
+ if (!ok) {
754
+ throw new SigningSessionError(
755
+ `Bad partial signature from ${did}.`,
756
+ "BAD_PARTIAL_SIG",
757
+ { participantDid: did, index: idx }
758
+ );
759
+ }
760
+ }
580
761
  this.signature = session.partialSigAgg([...this.partialSignatures.values()]);
581
762
  this.phase = "Complete" /* Complete */;
582
763
  return this.signature;
@@ -594,6 +775,10 @@ var BeaconSigningSession = class {
594
775
  /**
595
776
  * Generates a partial signature using the participant's secret key + secret nonce.
596
777
  * Requires the aggregated nonce to have been set first (via the service).
778
+ *
779
+ * Zeros the stored `secretNonce` after use. JS cannot truly erase memory (GC
780
+ * and immutable strings), but overwriting the bytes shortens the exposure
781
+ * window and prevents accidental reuse or serialization of a spent nonce.
597
782
  */
598
783
  generatePartialSignature(participantSecretKey) {
599
784
  if (!this.aggregatedNonce) {
@@ -606,10 +791,13 @@ var BeaconSigningSession = class {
606
791
  this.aggregatedNonce,
607
792
  this.cohort.cohortKeys,
608
793
  this.sigHash,
609
- [this.cohort.trMerkleRoot],
794
+ [this.cohort.tapTweak],
610
795
  [true]
611
796
  );
612
- return session.sign(this.secretNonce, participantSecretKey);
797
+ const partialSig = session.sign(this.secretNonce, participantSecretKey);
798
+ this.secretNonce.fill(0);
799
+ this.secretNonce = void 0;
800
+ return partialSig;
613
801
  }
614
802
  isComplete() {
615
803
  return this.phase === "Complete" /* Complete */;
@@ -620,16 +808,32 @@ var BeaconSigningSession = class {
620
808
  };
621
809
 
622
810
  // src/core/aggregation/service.ts
811
+ var DEFAULT_MAX_UPDATE_SIZE_BYTES = 256 * 1024;
623
812
  var AggregationService = class {
624
813
  did;
625
814
  keys;
815
+ maxUpdateSizeBytes;
626
816
  /** Per-cohort state, keyed by cohortId. */
627
817
  #cohortStates = /* @__PURE__ */ new Map();
628
- constructor({ did, keys }) {
818
+ constructor({ did, keys, maxUpdateSizeBytes }) {
629
819
  this.did = did;
630
820
  this.keys = keys;
821
+ this.maxUpdateSizeBytes = maxUpdateSizeBytes ?? DEFAULT_MAX_UPDATE_SIZE_BYTES;
631
822
  }
632
823
  receive(message) {
824
+ const version = message.version;
825
+ if (version === void 0 || version !== AGGREGATION_WIRE_VERSION) {
826
+ const cohortId = message.body?.cohortId;
827
+ const state = cohortId ? this.#cohortStates.get(cohortId) : void 0;
828
+ if (state) {
829
+ state.rejections.push({
830
+ from: message.from,
831
+ code: "WRONG_VERSION",
832
+ reason: `Expected wire version ${AGGREGATION_WIRE_VERSION}, got ${String(version)}`
833
+ });
834
+ }
835
+ return;
836
+ }
633
837
  const type = message.type;
634
838
  switch (type) {
635
839
  case COHORT_OPT_IN:
@@ -651,6 +855,18 @@ var AggregationService = class {
651
855
  break;
652
856
  }
653
857
  }
858
+ /**
859
+ * Drain the rejection log for a cohort. Used by runners to surface silent
860
+ * drops (bad proof, oversized update, wrong version, etc.) as structured
861
+ * events without breaking the sans-I/O state machine contract.
862
+ */
863
+ drainRejections(cohortId) {
864
+ const state = this.#cohortStates.get(cohortId);
865
+ if (!state) return [];
866
+ const out = state.rejections;
867
+ state.rejections = [];
868
+ return out;
869
+ }
654
870
  /**
655
871
  * Create a new cohort with the given config. Returns the cohort ID.
656
872
  * Cohort starts in `Created` phase — call `advertise()` to broadcast.
@@ -667,7 +883,8 @@ var AggregationService = class {
667
883
  cohort,
668
884
  config,
669
885
  pendingOptIns: /* @__PURE__ */ new Map(),
670
- acceptedParticipants: /* @__PURE__ */ new Set()
886
+ acceptedParticipants: /* @__PURE__ */ new Set(),
887
+ rejections: []
671
888
  });
672
889
  return cohort.id;
673
890
  }
@@ -720,6 +937,7 @@ var AggregationService = class {
720
937
  const participantPk = message.body?.participantPk;
721
938
  const communicationPk = message.body?.communicationPk;
722
939
  if (!participantPk || !communicationPk) return;
940
+ if (state.acceptedParticipants.has(participantDid)) return;
723
941
  state.pendingOptIns.set(participantDid, {
724
942
  cohortId,
725
943
  participantDid,
@@ -753,6 +971,7 @@ var AggregationService = class {
753
971
  }
754
972
  state.acceptedParticipants.add(participantDid);
755
973
  state.cohort.participants.push(participantDid);
974
+ state.cohort.participantKeys.set(participantDid, optIn.participantPk);
756
975
  state.cohort.cohortKeys = [...state.cohort.cohortKeys, optIn.participantPk];
757
976
  return [createCohortOptInAcceptMessage({
758
977
  from: this.did,
@@ -803,6 +1022,13 @@ var AggregationService = class {
803
1022
  if (!state) return /* @__PURE__ */ new Map();
804
1023
  return state.cohort.pendingUpdates;
805
1024
  }
1025
+ /**
1026
+ * Handle an incoming SUBMIT_UPDATE message from a participant containing their signed update to
1027
+ * submit for aggregation.
1028
+ * @param {BaseMessage} message - incoming SUBMIT_UPDATE message containing a participant's signed
1029
+ * update to submit for aggregation
1030
+ * @returns {void} - no return value; updates the service state with the submitted update if valid
1031
+ */
806
1032
  #handleSubmitUpdate(message) {
807
1033
  const cohortId = message.body?.cohortId;
808
1034
  if (!cohortId) return;
@@ -810,7 +1036,31 @@ var AggregationService = class {
810
1036
  if (!state) return;
811
1037
  if (state.phase !== "CohortSet" /* CohortSet */ && state.phase !== "CollectingUpdates" /* CollectingUpdates */) return;
812
1038
  const signedUpdate = message.body?.signedUpdate;
813
- if (!signedUpdate) return;
1039
+ if (!signedUpdate) {
1040
+ state.rejections.push({
1041
+ from: message.from,
1042
+ code: "UPDATE_MALFORMED",
1043
+ reason: "SUBMIT_UPDATE missing signedUpdate body"
1044
+ });
1045
+ return;
1046
+ }
1047
+ const canonicalSize = (0, import_common4.canonicalize)(signedUpdate).length;
1048
+ if (canonicalSize > this.maxUpdateSizeBytes) {
1049
+ state.rejections.push({
1050
+ from: message.from,
1051
+ code: "UPDATE_TOO_LARGE",
1052
+ reason: `Canonicalized update is ${canonicalSize} bytes; max allowed is ${this.maxUpdateSizeBytes}`
1053
+ });
1054
+ return;
1055
+ }
1056
+ if (!this.#verifySubmittedUpdate(state, message.from, signedUpdate)) {
1057
+ state.rejections.push({
1058
+ from: message.from,
1059
+ code: "UPDATE_VERIFICATION_FAILED",
1060
+ reason: "BIP-340 Data Integrity proof verification failed"
1061
+ });
1062
+ return;
1063
+ }
814
1064
  state.cohort.addUpdate(message.from, signedUpdate);
815
1065
  if (state.phase === "CohortSet" /* CohortSet */) {
816
1066
  state.phase = "CollectingUpdates" /* CollectingUpdates */;
@@ -819,6 +1069,36 @@ var AggregationService = class {
819
1069
  state.phase = "UpdatesCollected" /* UpdatesCollected */;
820
1070
  }
821
1071
  }
1072
+ /**
1073
+ * Verify the BIP-340 Schnorr Data Integrity proof on a submitted update using the
1074
+ * participant's public key from their cohort opt-in. Returns `false` (and the
1075
+ * update is silently dropped) if the proof is missing, the verificationMethod does
1076
+ * not name the sender's DID, the participant has no opt-in on record, or the
1077
+ * signature fails verification.
1078
+ * @param {ServiceCohortState} state - the current state of the cohort to which the update was submitted
1079
+ * @param {string} sender - the DID of the participant who submitted the update
1080
+ * @param {SignedBTCR2Update} signedUpdate - the signed update containing the proof to verify
1081
+ * @returns {boolean} - `true` if the proof is valid and the update can be accepted; `false` otherwise
1082
+ */
1083
+ #verifySubmittedUpdate(state, sender, signedUpdate) {
1084
+ const proof = signedUpdate.proof;
1085
+ if (!proof?.verificationMethod || !proof.proofValue) return false;
1086
+ const vmDid = proof.verificationMethod.split("#")[0];
1087
+ if (vmDid !== sender) return false;
1088
+ const optIn = state.pendingOptIns.get(sender);
1089
+ if (!optIn) return false;
1090
+ try {
1091
+ const multikey = import_cryptosuite.SchnorrMultikey.fromPublicKey({
1092
+ id: proof.verificationMethod,
1093
+ controller: sender,
1094
+ publicKeyBytes: optIn.participantPk
1095
+ });
1096
+ const suite = new import_cryptosuite.BIP340Cryptosuite(multikey);
1097
+ return suite.verifyProof(signedUpdate).verified === true;
1098
+ } catch {
1099
+ return false;
1100
+ }
1101
+ }
822
1102
  /**
823
1103
  * Build the aggregated data structure (CAS Announcement or SMT tree) and
824
1104
  * return distribute messages to send to all participants for validation.
@@ -835,29 +1115,28 @@ var AggregationService = class {
835
1115
  { cohortId, phase: state.phase }
836
1116
  );
837
1117
  }
838
- if (state.config.beaconType === "CASBeacon") {
839
- state.cohort.buildCASAnnouncement();
840
- } else if (state.config.beaconType === "SMTBeacon") {
841
- state.cohort.buildSMTTree();
842
- } else {
1118
+ const strategy = getBeaconStrategy(state.config.beaconType);
1119
+ if (!strategy) {
843
1120
  throw new AggregationServiceError(
844
1121
  `Unsupported beacon type: ${state.config.beaconType}`,
845
1122
  "UNSUPPORTED_BEACON_TYPE",
846
1123
  { cohortId, beaconType: state.config.beaconType }
847
1124
  );
848
1125
  }
1126
+ strategy.buildAggregatedData(state.cohort);
849
1127
  const signalBytesHex = (0, import_utils2.bytesToHex)(state.cohort.signalBytes);
850
1128
  state.phase = "DataDistributed" /* DataDistributed */;
851
1129
  const messages = [];
852
1130
  for (const participantDid of state.cohort.participants) {
1131
+ const payload = strategy.getDistributePayload(state.cohort, participantDid);
853
1132
  messages.push(createDistributeAggregatedDataMessage({
854
1133
  from: this.did,
855
1134
  to: participantDid,
856
1135
  cohortId,
857
1136
  beaconType: state.config.beaconType,
858
1137
  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
1138
+ casAnnouncement: payload.casAnnouncement,
1139
+ smtProof: payload.smtProof
861
1140
  }));
862
1141
  }
863
1142
  return messages;
@@ -919,6 +1198,14 @@ var AggregationService = class {
919
1198
  });
920
1199
  state.signingSession = session;
921
1200
  state.phase = "SigningStarted" /* SigningStarted */;
1201
+ const prevOutScript = txData.prevOutScripts[0];
1202
+ if (!prevOutScript) {
1203
+ throw new AggregationServiceError(
1204
+ `Cannot start signing for cohort ${cohortId}: txData.prevOutScripts[0] is missing.`,
1205
+ "MISSING_PREV_OUT_SCRIPT",
1206
+ { cohortId }
1207
+ );
1208
+ }
922
1209
  const messages = [];
923
1210
  for (const participantDid of state.cohort.participants) {
924
1211
  messages.push(createAuthorizationRequestMessage({
@@ -926,7 +1213,8 @@ var AggregationService = class {
926
1213
  to: participantDid,
927
1214
  cohortId,
928
1215
  sessionId: session.id,
929
- pendingTx: txData.tx.toHex(),
1216
+ pendingTx: txData.tx.hex,
1217
+ prevOutScriptHex: (0, import_utils2.bytesToHex)(prevOutScript),
930
1218
  prevOutValue: txData.prevOutValues[0]?.toString() ?? "0"
931
1219
  }));
932
1220
  }
@@ -990,7 +1278,7 @@ var AggregationService = class {
990
1278
  state.signingSession.addPartialSignature(message.from, partialSignature);
991
1279
  if (state.signingSession.partialSignatures.size === state.cohort.participants.length) {
992
1280
  const signature = state.signingSession.generateFinalSignature();
993
- state.signingSession.pendingTx.setWitness(0, [signature]);
1281
+ state.signingSession.pendingTx.updateInput(0, { finalScriptWitness: [signature] });
994
1282
  state.result = {
995
1283
  cohortId,
996
1284
  signature,
@@ -1019,13 +1307,19 @@ var AggregationService = class {
1019
1307
  get cohorts() {
1020
1308
  return [...this.#cohortStates.values()].map((s) => s.cohort);
1021
1309
  }
1310
+ /**
1311
+ * Remove a cohort from the state map. Used by runners to GC state on cohort
1312
+ * completion, failure, or expiry. No-op if the cohort doesn't exist.
1313
+ */
1314
+ removeCohort(cohortId) {
1315
+ this.#cohortStates.delete(cohortId);
1316
+ }
1022
1317
  };
1023
1318
 
1024
1319
  // src/core/aggregation/participant.ts
1025
- var import_common3 = require("@did-btcr2/common");
1026
- var import_smt2 = require("@did-btcr2/smt");
1320
+ var import_common5 = require("@did-btcr2/common");
1027
1321
  var import_utils3 = require("@noble/hashes/utils");
1028
- var import_bitcoinjs_lib3 = require("bitcoinjs-lib");
1322
+ var import_btc_signer3 = require("@scure/btc-signer");
1029
1323
  var AggregationParticipant = class {
1030
1324
  did;
1031
1325
  keys;
@@ -1040,6 +1334,9 @@ var AggregationParticipant = class {
1040
1334
  * outgoing messages — those come exclusively from action methods.
1041
1335
  */
1042
1336
  receive(message) {
1337
+ if (message.version === void 0 || message.version !== AGGREGATION_WIRE_VERSION) {
1338
+ return;
1339
+ }
1043
1340
  const type = message.type;
1044
1341
  switch (type) {
1045
1342
  case COHORT_ADVERT:
@@ -1196,42 +1493,26 @@ var AggregationParticipant = class {
1196
1493
  if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */) return;
1197
1494
  if (!state.submittedUpdate) return;
1198
1495
  const beaconType = message.body?.beaconType;
1496
+ if (!beaconType) return;
1497
+ const strategy = getBeaconStrategy(beaconType);
1498
+ if (!strategy) return;
1199
1499
  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
- }
1500
+ const expectedHash = (0, import_common5.canonicalHash)(state.submittedUpdate);
1501
+ const result = strategy.validateParticipantView({
1502
+ participantDid: this.did,
1503
+ submittedUpdate: state.submittedUpdate,
1504
+ expectedHash,
1505
+ body: message.body
1506
+ });
1507
+ state.validation = {
1508
+ cohortId,
1509
+ beaconType,
1510
+ signalBytesHex,
1511
+ expectedHash,
1512
+ matches: result.matches,
1513
+ casAnnouncement: result.casAnnouncement,
1514
+ smtProof: result.smtProof
1515
+ };
1235
1516
  state.phase = "AwaitingValidation" /* AwaitingValidation */;
1236
1517
  }
1237
1518
  /**
@@ -1292,12 +1573,14 @@ var AggregationParticipant = class {
1292
1573
  if (state.phase !== "ValidationSent" /* ValidationSent */) return;
1293
1574
  const sessionId = message.body?.sessionId;
1294
1575
  const pendingTxHex = message.body?.pendingTx;
1576
+ const prevOutScriptHex = message.body?.prevOutScriptHex;
1295
1577
  const prevOutValue = message.body?.prevOutValue;
1296
- if (!sessionId || !pendingTxHex || !prevOutValue) return;
1578
+ if (!sessionId || !pendingTxHex || !prevOutScriptHex || !prevOutValue) return;
1297
1579
  state.signingRequest = {
1298
1580
  cohortId,
1299
1581
  sessionId,
1300
1582
  pendingTxHex,
1583
+ prevOutScriptHex,
1301
1584
  prevOutValue
1302
1585
  };
1303
1586
  state.phase = "AwaitingSigning" /* AwaitingSigning */;
@@ -1321,8 +1604,8 @@ var AggregationParticipant = class {
1321
1604
  { cohortId }
1322
1605
  );
1323
1606
  }
1324
- const tx = import_bitcoinjs_lib3.Transaction.fromHex(state.signingRequest.pendingTxHex);
1325
- const prevOutScripts = tx.outs.length > 0 ? [tx.outs[0].script] : [];
1607
+ const tx = import_btc_signer3.Transaction.fromRaw((0, import_utils3.hexToBytes)(state.signingRequest.pendingTxHex));
1608
+ const prevOutScripts = [(0, import_utils3.hexToBytes)(state.signingRequest.prevOutScriptHex)];
1326
1609
  const prevOutValues = [BigInt(state.signingRequest.prevOutValue)];
1327
1610
  const session = new BeaconSigningSession({
1328
1611
  id: state.signingRequest.sessionId,
@@ -1392,6 +1675,67 @@ var AggregationParticipant = class {
1392
1675
  }
1393
1676
  };
1394
1677
 
1678
+ // src/core/aggregation/logger.ts
1679
+ var CONSOLE_LOGGER = {
1680
+ debug: (msg, ...args) => console.debug(msg, ...args),
1681
+ info: (msg, ...args) => console.info(msg, ...args),
1682
+ warn: (msg, ...args) => console.warn(msg, ...args),
1683
+ error: (msg, ...args) => console.error(msg, ...args)
1684
+ };
1685
+ var SILENT_LOGGER = {
1686
+ debug: () => {
1687
+ },
1688
+ info: () => {
1689
+ },
1690
+ warn: () => {
1691
+ },
1692
+ error: () => {
1693
+ }
1694
+ };
1695
+
1696
+ // src/core/aggregation/messages/bodies.ts
1697
+ var hasStr = (b, k) => !!b && typeof b[k] === "string";
1698
+ var hasNum = (b, k) => !!b && typeof b[k] === "number";
1699
+ var hasBool = (b, k) => !!b && typeof b[k] === "boolean";
1700
+ var hasBytes = (b, k) => !!b && b[k] instanceof Uint8Array;
1701
+ var hasBytesArray = (b, k) => {
1702
+ const v = b ? b[k] : void 0;
1703
+ return Array.isArray(v) && v.every((x) => x instanceof Uint8Array);
1704
+ };
1705
+ function isCohortAdvertMessage(m) {
1706
+ 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");
1707
+ }
1708
+ function isCohortOptInMessage(m) {
1709
+ return m.type === COHORT_OPT_IN && hasStr(m.body, "cohortId") && hasBytes(m.body, "participantPk") && hasBytes(m.body, "communicationPk");
1710
+ }
1711
+ function isCohortOptInAcceptMessage(m) {
1712
+ return m.type === COHORT_OPT_IN_ACCEPT && hasStr(m.body, "cohortId");
1713
+ }
1714
+ function isCohortReadyMessage(m) {
1715
+ return m.type === COHORT_READY && hasStr(m.body, "cohortId") && hasStr(m.body, "beaconAddress") && hasBytesArray(m.body, "cohortKeys");
1716
+ }
1717
+ function isSubmitUpdateMessage(m) {
1718
+ return m.type === SUBMIT_UPDATE && hasStr(m.body, "cohortId") && !!m.body && typeof m.body.signedUpdate === "object";
1719
+ }
1720
+ function isDistributeAggregatedDataMessage(m) {
1721
+ return m.type === DISTRIBUTE_AGGREGATED_DATA && hasStr(m.body, "cohortId") && hasStr(m.body, "beaconType") && hasStr(m.body, "signalBytesHex");
1722
+ }
1723
+ function isValidationAckMessage(m) {
1724
+ return m.type === VALIDATION_ACK && hasStr(m.body, "cohortId") && hasBool(m.body, "approved");
1725
+ }
1726
+ function isAuthorizationRequestMessage(m) {
1727
+ 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");
1728
+ }
1729
+ function isNonceContributionMessage(m) {
1730
+ return m.type === NONCE_CONTRIBUTION && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "nonceContribution");
1731
+ }
1732
+ function isAggregatedNonceMessage(m) {
1733
+ return m.type === AGGREGATED_NONCE && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "aggregatedNonce");
1734
+ }
1735
+ function isSignatureAuthorizationMessage(m) {
1736
+ return m.type === SIGNATURE_AUTHORIZATION && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "partialSignature");
1737
+ }
1738
+
1395
1739
  // src/core/aggregation/messages/guards.ts
1396
1740
  var KEYGEN_VALUES = /* @__PURE__ */ new Set([
1397
1741
  COHORT_ADVERT,
@@ -1424,132 +1768,1610 @@ function isSignMessageType(type) {
1424
1768
  }
1425
1769
 
1426
1770
  // src/core/aggregation/transport/error.ts
1427
- var import_common4 = require("@did-btcr2/common");
1428
- var TransportError = class extends import_common4.MethodError {
1771
+ var import_common6 = require("@did-btcr2/common");
1772
+ var TransportError = class extends import_common6.MethodError {
1429
1773
  constructor(message, type = "TransportError", data) {
1430
1774
  super(message, type, data);
1431
1775
  }
1432
1776
  };
1433
- var TransportAdapterError = class extends import_common4.MethodError {
1777
+ var TransportAdapterError = class extends import_common6.MethodError {
1434
1778
  constructor(message, type = "TransportAdapterError", data) {
1435
1779
  super(message, type, data);
1436
1780
  }
1437
1781
  };
1438
1782
 
1439
1783
  // src/core/aggregation/transport/factory.ts
1440
- var import_common5 = require("@did-btcr2/common");
1784
+ var import_common10 = require("@did-btcr2/common");
1441
1785
 
1442
- // src/core/aggregation/transport/nostr.ts
1786
+ // src/core/aggregation/transport/http/client.ts
1787
+ var import_keypair2 = require("@did-btcr2/keypair");
1788
+
1789
+ // src/core/identifier.ts
1790
+ var import_common7 = require("@did-btcr2/common");
1443
1791
  var import_keypair = require("@did-btcr2/keypair");
1444
- var import_utils4 = require("@noble/hashes/utils");
1445
- var import_nostr_tools = require("nostr-tools");
1446
- var import_pool = require("nostr-tools/pool");
1447
- var DEFAULT_NOSTR_RELAYS = [
1448
- "wss://relay.damus.io",
1449
- "wss://nos.lol",
1450
- "wss://relay.snort.social",
1451
- "wss://nostr-pub.wellorder.net"
1452
- ];
1453
- var NostrTransport = class _NostrTransport {
1454
- name = "nostr";
1455
- pool;
1456
- #relays;
1457
- #actors = /* @__PURE__ */ new Map();
1458
- #peerRegistry = /* @__PURE__ */ new Map();
1459
- #started = false;
1460
- constructor(config) {
1461
- this.#relays = config?.relays ?? DEFAULT_NOSTR_RELAYS;
1462
- }
1792
+ var import_base4 = require("@scure/base");
1793
+ var Identifier = class _Identifier {
1463
1794
  /**
1464
- * Registers an actor (DID + keys) to send/receive messages with.
1465
- * Must be called before start() to ensure subscriptions are created for the actor.
1466
- * @param {string} did - The DID of the actor.
1467
- * @param {SchnorrKeyPair} keys - The Schnorr key pair for the actor.
1468
- * @throws {TransportAdapterError} If the actor is already registered or if the transport has already started.
1469
- * @example
1470
- * const transport = new NostrTransport();
1471
- * const keys = SchnorrKeyPair.generate();
1472
- * transport.registerActor('did:btcr2:...', keys);
1473
- * transport.start();
1795
+ * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-encoding | 3.2 did:btcr2 Identifier Encoding}.
1796
+ *
1797
+ * A did:btcr2 DID consists of a did:btcr2 prefix, followed by an id-bech32 value, which is a Bech32m encoding of:
1798
+ * - the specification version;
1799
+ * - the Bitcoin network identifier; and
1800
+ * - either:
1801
+ * - a key-value representing a secp256k1 public key; or
1802
+ * - a hash-value representing the hash of an initiating external DID document.
1803
+ *
1804
+ * @param {KeyBytes | DocumentBytes} genesisBytes The genesis bytes (public key or document bytes).
1805
+ * @param {DidCreateOptions} options The DID creation options.
1806
+ * @returns {string} The new did:btcr2 identifier.
1474
1807
  */
1475
- registerActor(did, keys) {
1476
- const entry = { keys, handlers: /* @__PURE__ */ new Map() };
1477
- this.#actors.set(did, entry);
1478
- if (this.#started && this.pool) {
1479
- this.#subscribeDirected(did, entry);
1808
+ static encode(genesisBytes, options) {
1809
+ const { idType, version = 1, network } = options;
1810
+ if (!(idType in import_common7.IdentifierTypes)) {
1811
+ throw new import_common7.IdentifierError('Expected "idType" to be "KEY" or "EXTERNAL"', import_common7.INVALID_DID, { idType });
1480
1812
  }
1481
- }
1482
- getActorPk(did) {
1483
- return this.#actors.get(did)?.keys.publicKey.compressed;
1484
- }
1485
- registerPeer(did, communicationPk) {
1486
- try {
1487
- new import_keypair.CompressedSecp256k1PublicKey(communicationPk);
1488
- } catch {
1489
- throw new TransportAdapterError(
1490
- `Invalid communication public key for peer ${did}: expected a 33-byte compressed secp256k1 key.`,
1491
- "INVALID_PEER_KEY",
1492
- { adapter: this.name, did, keyLength: communicationPk.length }
1493
- );
1813
+ if (isNaN(version) || version > 1) {
1814
+ throw new import_common7.IdentifierError('Expected "version" to be 1', import_common7.INVALID_DID, { version });
1494
1815
  }
1495
- this.#peerRegistry.set(did, communicationPk);
1496
- }
1497
- getPeerPk(did) {
1498
- return this.#peerRegistry.get(did);
1499
- }
1500
- registerMessageHandler(actorDid, messageType, handler) {
1501
- const actor = this.#actors.get(actorDid);
1502
- if (!actor) {
1503
- throw new TransportAdapterError(
1504
- `Cannot register handler: actor ${actorDid} not registered. Call registerActor() first.`,
1505
- "UNKNOWN_ACTOR_ERROR",
1506
- { adapter: this.name, did: actorDid }
1507
- );
1816
+ if (typeof network === "string" && !(network in import_common7.BitcoinNetworkNames)) {
1817
+ throw new import_common7.IdentifierError('Invalid "network" name', import_common7.INVALID_DID, { network });
1508
1818
  }
1509
- actor.handlers.set(messageType, handler);
1510
- }
1511
- start() {
1512
- if (this.#started) return this;
1513
- this.#started = true;
1514
- 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),
1518
- onevent: this.#handleBroadcastEvent.bind(this)
1519
- });
1520
- for (const [did, entry] of this.#actors) {
1521
- this.#subscribeDirected(did, entry);
1819
+ if (typeof network === "number" && (network < 0 || network > 8)) {
1820
+ throw new import_common7.IdentifierError('Invalid "network" number', import_common7.INVALID_DID, { network });
1522
1821
  }
1523
- console.info(`NostrTransport started, listening on ${this.#relays.length} relay(s)`);
1524
- return this;
1525
- }
1526
- async sendMessage(message, sender, to) {
1527
- const type = message.type;
1528
- if (!type) {
1529
- throw new TransportAdapterError(
1530
- "Message type is undefined",
1531
- "SEND_MESSAGE_ERROR",
1532
- { adapter: this.name, type }
1533
- );
1822
+ if (idType === "KEY") {
1823
+ try {
1824
+ new import_keypair.CompressedSecp256k1PublicKey(genesisBytes);
1825
+ } catch {
1826
+ throw new import_common7.IdentifierError(
1827
+ 'Expected "genesisBytes" to be a valid compressed secp256k1 public key',
1828
+ import_common7.INVALID_DID,
1829
+ { genesisBytes }
1830
+ );
1831
+ }
1534
1832
  }
1535
- const actor = this.#actors.get(sender);
1536
- if (!actor) {
1537
- throw new TransportAdapterError(
1538
- `Unknown sender: ${sender}. Call registerActor() before sending messages.`,
1833
+ const hrp = idType === "KEY" ? "k" : "x";
1834
+ const nibbles = [];
1835
+ const fCount = Math.floor((version - 1) / 15);
1836
+ for (let i = 0; i < fCount; i++) {
1837
+ nibbles.push(15);
1838
+ }
1839
+ nibbles.push((version - 1) % 15);
1840
+ if (typeof network === "string") {
1841
+ nibbles.push(import_common7.BitcoinNetworkNames[network]);
1842
+ } else if (typeof network === "number") {
1843
+ nibbles.push(network + 11);
1844
+ }
1845
+ if (nibbles.length % 2 !== 0) {
1846
+ nibbles.push(0);
1847
+ }
1848
+ if (fCount !== 0) {
1849
+ for (const index in Array.from({ length: nibbles.length / 2 - 1 })) {
1850
+ throw new import_common7.IdentifierError("Not implemented", "NOT_IMPLEMENTED", { index });
1851
+ }
1852
+ }
1853
+ const dataBytes = new Uint8Array([nibbles[2 * 0] << 4 | nibbles[2 * 0 + 1], ...genesisBytes]);
1854
+ return `did:btcr2:${import_base4.bech32m.encodeFromBytes(hrp, dataBytes)}`;
1855
+ }
1856
+ /**
1857
+ * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}.
1858
+ * @param {string} identifier The BTCR2 DID to be parsed
1859
+ * @returns {DidComponents} The parsed identifier components. See {@link DidComponents} for details.
1860
+ * @throws {DidError} if an error occurs while parsing the identifier
1861
+ * @throws {DidErrorCode.InvalidDid} if identifier is invalid
1862
+ * @throws {DidErrorCode.MethodNotSupported} if the method is not supported
1863
+ */
1864
+ static decode(identifier) {
1865
+ const components = identifier.split(":");
1866
+ if (components.length !== 3) {
1867
+ throw new import_common7.IdentifierError(`Invalid did: ${identifier}`, import_common7.INVALID_DID, { identifier });
1868
+ }
1869
+ const [scheme, method, encoded] = components;
1870
+ if (scheme !== "did") {
1871
+ throw new import_common7.IdentifierError(`Invalid did: ${identifier}`, import_common7.INVALID_DID, { identifier });
1872
+ }
1873
+ if (method !== "btcr2") {
1874
+ throw new import_common7.IdentifierError(`Invalid did method: ${method}`, import_common7.METHOD_NOT_SUPPORTED, { identifier });
1875
+ }
1876
+ if (!encoded) {
1877
+ throw new import_common7.IdentifierError(`Invalid method-specific id: ${identifier}`, import_common7.INVALID_DID, { identifier });
1878
+ }
1879
+ const { prefix: hrp, bytes: dataBytes } = import_base4.bech32m.decodeToBytes(encoded);
1880
+ if (!["x", "k"].includes(hrp)) {
1881
+ throw new import_common7.IdentifierError(`Invalid hrp: ${hrp}`, import_common7.INVALID_DID, { identifier });
1882
+ }
1883
+ if (!dataBytes) {
1884
+ throw new import_common7.IdentifierError(`Failed to decode id: ${encoded}`, import_common7.INVALID_DID, { identifier });
1885
+ }
1886
+ const idType = hrp === "k" ? "KEY" : "EXTERNAL";
1887
+ let version = 1;
1888
+ let byteIndex = 0;
1889
+ let nibblesConsumed = 0;
1890
+ let currentByte = dataBytes[byteIndex];
1891
+ let versionNibble = currentByte >>> 4;
1892
+ while (versionNibble === 15) {
1893
+ version += 15;
1894
+ if (nibblesConsumed % 2 === 0) {
1895
+ versionNibble = currentByte & 15;
1896
+ } else {
1897
+ currentByte = dataBytes[++byteIndex];
1898
+ versionNibble = currentByte >>> 4;
1899
+ }
1900
+ nibblesConsumed += 1;
1901
+ if (version > 1) {
1902
+ throw new import_common7.IdentifierError(`Invalid version: ${version}`, import_common7.INVALID_DID, { identifier });
1903
+ }
1904
+ }
1905
+ version += versionNibble;
1906
+ nibblesConsumed += 1;
1907
+ let networkValue = nibblesConsumed % 2 === 0 ? dataBytes[++byteIndex] >>> 4 : currentByte & 15;
1908
+ nibblesConsumed += 1;
1909
+ let network = import_common7.BitcoinNetworkNames[networkValue];
1910
+ if (!network) {
1911
+ if (networkValue >= 8 && networkValue <= 15) {
1912
+ network = networkValue - 11;
1913
+ } else {
1914
+ throw new import_common7.IdentifierError(`Invalid did: ${identifier}`, import_common7.INVALID_DID, { identifier });
1915
+ }
1916
+ }
1917
+ if (nibblesConsumed % 2 === 1) {
1918
+ const fillerNibble = currentByte & 15;
1919
+ if (fillerNibble !== 0) {
1920
+ throw new import_common7.IdentifierError(`Invalid did: ${identifier}`, import_common7.INVALID_DID, { identifier });
1921
+ }
1922
+ }
1923
+ const genesisBytes = dataBytes.slice(byteIndex + 1);
1924
+ if (idType === "KEY") {
1925
+ try {
1926
+ new import_keypair.CompressedSecp256k1PublicKey(genesisBytes);
1927
+ } catch {
1928
+ throw new import_common7.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common7.INVALID_DID, { identifier });
1929
+ }
1930
+ }
1931
+ return { idType, hrp, version, network, genesisBytes };
1932
+ }
1933
+ /**
1934
+ * Generates a new did:btcr2 identifier based on a newly generated key pair.
1935
+ * @returns {string} The new did:btcr2 identifier.
1936
+ */
1937
+ static generate() {
1938
+ const keyPair = import_keypair.SchnorrKeyPair.generate();
1939
+ const did = this.encode(
1940
+ keyPair.publicKey.compressed,
1941
+ {
1942
+ idType: "KEY",
1943
+ version: 1,
1944
+ network: "regtest"
1945
+ }
1946
+ );
1947
+ return { keyPair: keyPair.exportJSON(), did };
1948
+ }
1949
+ /**
1950
+ * Extracts the compressed secp256k1 public key from a KEY-type did:btcr2 identifier.
1951
+ * @param {string} did The did:btcr2 identifier to extract the public key from.
1952
+ * @returns {CompressedSecp256k1PublicKey} The compressed public key.
1953
+ * @throws {IdentifierError} If the DID is EXTERNAL type (genesis bytes are a hash, not a pubkey).
1954
+ */
1955
+ static getPublicKey(did) {
1956
+ const { idType, genesisBytes } = _Identifier.decode(did);
1957
+ if (idType !== "KEY") {
1958
+ throw new import_common7.IdentifierError(
1959
+ `Cannot extract public key from EXTERNAL DID: ${did}. EXTERNAL DIDs encode a document hash, not a public key.`,
1960
+ import_common7.INVALID_DID,
1961
+ { did, idType }
1962
+ );
1963
+ }
1964
+ return new import_keypair.CompressedSecp256k1PublicKey(genesisBytes);
1965
+ }
1966
+ /**
1967
+ * Validates a did:btcr2 identifier.
1968
+ * @param {string} identifier The did:btcr2 identifier to validate.
1969
+ * @returns {boolean} True if the identifier is valid, false otherwise.
1970
+ */
1971
+ static isValid(identifier) {
1972
+ try {
1973
+ this.decode(identifier);
1974
+ return true;
1975
+ } catch {
1976
+ return false;
1977
+ }
1978
+ }
1979
+ };
1980
+
1981
+ // src/core/aggregation/transport/http/envelope.ts
1982
+ var import_common8 = require("@did-btcr2/common");
1983
+ var import_utils4 = require("@noble/hashes/utils");
1984
+
1985
+ // src/core/aggregation/transport/http/errors.ts
1986
+ var HttpTransportError = class extends TransportAdapterError {
1987
+ constructor(message, type = "HttpTransportError", data) {
1988
+ super(message, type, { adapter: "http", ...data ?? {} });
1989
+ }
1990
+ };
1991
+
1992
+ // src/core/aggregation/transport/http/protocol.ts
1993
+ var HTTP_ENVELOPE_VERSION = 1;
1994
+ var HTTP_ROUTE = {
1995
+ ADVERTS: "/v1/adverts",
1996
+ MESSAGES: "/v1/messages",
1997
+ ACTOR_INBOX: "/v1/actors/{did}/inbox",
1998
+ WELL_KNOWN: "/v1/.well-known/aggregation"
1999
+ };
2000
+ var SSE_EVENT = {
2001
+ ADVERT: "advert",
2002
+ MESSAGE: "message",
2003
+ HEARTBEAT: "heartbeat"
2004
+ };
2005
+ var DEFAULT_CLOCK_SKEW_SEC = 60;
2006
+ var DEFAULT_NONCE_LEN_BYTES = 16;
2007
+
2008
+ // src/core/aggregation/transport/http/envelope.ts
2009
+ function signEnvelope(message, sender, opts = {}) {
2010
+ const timestamp = opts.timestamp ?? Math.floor(Date.now() / 1e3);
2011
+ const nonce = opts.nonce ?? (0, import_utils4.bytesToHex)((0, import_utils4.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
2012
+ const messageJson = normalizeForWire(normalizeMessage(message));
2013
+ const unsigned = {
2014
+ v: HTTP_ENVELOPE_VERSION,
2015
+ from: sender.did,
2016
+ ...opts.to !== void 0 ? { to: opts.to } : {},
2017
+ timestamp,
2018
+ nonce,
2019
+ message: messageJson
2020
+ };
2021
+ const digest = (0, import_common8.canonicalHashBytes)(unsigned);
2022
+ const sig = sender.keys.secretKey.sign(digest, { scheme: "schnorr" });
2023
+ return { ...unsigned, sig: (0, import_utils4.bytesToHex)(sig) };
2024
+ }
2025
+ function verifyEnvelope(envelope, senderPk, opts = {}) {
2026
+ if (envelope.v !== HTTP_ENVELOPE_VERSION) {
2027
+ throw new HttpTransportError(
2028
+ `Unsupported envelope version: ${envelope.v}`,
2029
+ "ENVELOPE_VERSION_MISMATCH",
2030
+ { version: envelope.v, expected: HTTP_ENVELOPE_VERSION }
2031
+ );
2032
+ }
2033
+ if (opts.expectedFrom !== void 0 && envelope.from !== opts.expectedFrom) {
2034
+ throw new HttpTransportError(
2035
+ `Envelope from mismatch: expected ${opts.expectedFrom}, got ${envelope.from}`,
2036
+ "ENVELOPE_FROM_MISMATCH",
2037
+ { expected: opts.expectedFrom, got: envelope.from }
2038
+ );
2039
+ }
2040
+ if ("expectedTo" in opts && envelope.to !== opts.expectedTo) {
2041
+ throw new HttpTransportError(
2042
+ `Envelope to mismatch: expected ${opts.expectedTo ?? "<broadcast>"}, got ${envelope.to ?? "<broadcast>"}`,
2043
+ "ENVELOPE_TO_MISMATCH",
2044
+ { expected: opts.expectedTo, got: envelope.to }
2045
+ );
2046
+ }
2047
+ const skewSec = opts.clockSkewSec ?? DEFAULT_CLOCK_SKEW_SEC;
2048
+ const nowMs = opts.now ? opts.now() : Date.now();
2049
+ const nowSec = Math.floor(nowMs / 1e3);
2050
+ const diff = Math.abs(nowSec - envelope.timestamp);
2051
+ if (diff > skewSec) {
2052
+ throw new HttpTransportError(
2053
+ `Envelope timestamp out of skew: ${diff}s > ${skewSec}s`,
2054
+ "ENVELOPE_TIMESTAMP_SKEW",
2055
+ { diff, skewSec, timestamp: envelope.timestamp, now: nowSec }
2056
+ );
2057
+ }
2058
+ let sigBytes;
2059
+ try {
2060
+ sigBytes = (0, import_utils4.hexToBytes)(envelope.sig);
2061
+ } catch {
2062
+ throw new HttpTransportError(
2063
+ "Envelope signature is not valid hex",
2064
+ "ENVELOPE_SIG_HEX"
2065
+ );
2066
+ }
2067
+ if (sigBytes.length !== 64) {
2068
+ throw new HttpTransportError(
2069
+ `Invalid signature length: ${sigBytes.length} (expected 64)`,
2070
+ "ENVELOPE_SIG_LENGTH",
2071
+ { length: sigBytes.length }
2072
+ );
2073
+ }
2074
+ const { sig: _sig, ...unsigned } = envelope;
2075
+ const digest = (0, import_common8.canonicalHashBytes)(unsigned);
2076
+ const ok = senderPk.verify(sigBytes, digest, { scheme: "schnorr" });
2077
+ if (!ok) {
2078
+ throw new HttpTransportError(
2079
+ "Envelope signature verification failed",
2080
+ "ENVELOPE_SIG_INVALID"
2081
+ );
2082
+ }
2083
+ }
2084
+ function normalizeMessage(message) {
2085
+ const maybeToJSON = message.toJSON;
2086
+ if (typeof maybeToJSON === "function") {
2087
+ return maybeToJSON.call(message);
2088
+ }
2089
+ return message;
2090
+ }
2091
+ function normalizeForWire(value) {
2092
+ if (value instanceof Uint8Array) {
2093
+ return { __bytes: (0, import_utils4.bytesToHex)(value) };
2094
+ }
2095
+ if (Array.isArray(value)) {
2096
+ return value.map((v) => normalizeForWire(v));
2097
+ }
2098
+ if (value && typeof value === "object") {
2099
+ const out = {};
2100
+ for (const [k, v] of Object.entries(value)) {
2101
+ out[k] = normalizeForWire(v);
2102
+ }
2103
+ return out;
2104
+ }
2105
+ return value;
2106
+ }
2107
+ function reviveFromWire(value) {
2108
+ if (value && typeof value === "object" && !Array.isArray(value)) {
2109
+ const rec = value;
2110
+ const keys = Object.keys(rec);
2111
+ if (keys.length === 1 && keys[0] === "__bytes" && typeof rec.__bytes === "string") {
2112
+ return (0, import_utils4.hexToBytes)(rec.__bytes);
2113
+ }
2114
+ const out = {};
2115
+ for (const [k, v] of Object.entries(rec)) out[k] = reviveFromWire(v);
2116
+ return out;
2117
+ }
2118
+ if (Array.isArray(value)) {
2119
+ return value.map((v) => reviveFromWire(v));
2120
+ }
2121
+ return value;
2122
+ }
2123
+
2124
+ // src/core/aggregation/transport/http/request-auth.ts
2125
+ var import_common9 = require("@did-btcr2/common");
2126
+ var import_utils5 = require("@noble/hashes/utils");
2127
+ var REQUEST_AUTH_SCHEME = "BTCR2-Sig";
2128
+ function buildRequestAuth(did, keys, path, opts = {}) {
2129
+ const ts = opts.timestamp ?? Math.floor(Date.now() / 1e3);
2130
+ const nonce = opts.nonce ?? (0, import_utils5.bytesToHex)((0, import_utils5.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
2131
+ const digest = (0, import_common9.canonicalHashBytes)({
2132
+ v: HTTP_ENVELOPE_VERSION,
2133
+ did,
2134
+ ts,
2135
+ nonce,
2136
+ path
2137
+ });
2138
+ const sig = keys.secretKey.sign(digest, { scheme: "schnorr" });
2139
+ return `${REQUEST_AUTH_SCHEME} v=${HTTP_ENVELOPE_VERSION},did=${did},ts=${ts},nonce=${nonce},sig=${(0, import_utils5.bytesToHex)(sig)}`;
2140
+ }
2141
+ function parseRequestAuth(headerValue) {
2142
+ const prefix = `${REQUEST_AUTH_SCHEME} `;
2143
+ if (!headerValue.startsWith(prefix)) {
2144
+ throw new HttpTransportError(
2145
+ `Unexpected auth scheme (want ${REQUEST_AUTH_SCHEME})`,
2146
+ "REQUEST_AUTH_SCHEME"
2147
+ );
2148
+ }
2149
+ const params = {};
2150
+ for (const piece of headerValue.slice(prefix.length).split(",")) {
2151
+ const eq = piece.indexOf("=");
2152
+ if (eq === -1) continue;
2153
+ const key = piece.slice(0, eq).trim();
2154
+ const val = piece.slice(eq + 1).trim();
2155
+ if (key.length > 0) params[key] = val;
2156
+ }
2157
+ const v = Number(params.v);
2158
+ const ts = Number(params.ts);
2159
+ if (!Number.isInteger(v) || !Number.isInteger(ts) || !params.did || !params.nonce || !params.sig) {
2160
+ throw new HttpTransportError(
2161
+ "Malformed auth header (missing or invalid field)",
2162
+ "REQUEST_AUTH_MALFORMED",
2163
+ { received: Object.keys(params) }
2164
+ );
2165
+ }
2166
+ return { v, did: params.did, ts, nonce: params.nonce, sig: params.sig };
2167
+ }
2168
+ function verifyRequestAuth(headerValue, expectedPath, senderPk, opts = {}) {
2169
+ const parsed = parseRequestAuth(headerValue);
2170
+ if (parsed.v !== HTTP_ENVELOPE_VERSION) {
2171
+ throw new HttpTransportError(
2172
+ `Unsupported auth version: ${parsed.v}`,
2173
+ "REQUEST_AUTH_VERSION_MISMATCH",
2174
+ { version: parsed.v, expected: HTTP_ENVELOPE_VERSION }
2175
+ );
2176
+ }
2177
+ const skewSec = opts.clockSkewSec ?? DEFAULT_CLOCK_SKEW_SEC;
2178
+ const nowMs = opts.now ? opts.now() : Date.now();
2179
+ const nowSec = Math.floor(nowMs / 1e3);
2180
+ const diff = Math.abs(nowSec - parsed.ts);
2181
+ if (diff > skewSec) {
2182
+ throw new HttpTransportError(
2183
+ `Auth timestamp out of skew: ${diff}s > ${skewSec}s`,
2184
+ "REQUEST_AUTH_TIMESTAMP_SKEW",
2185
+ { diff, skewSec }
2186
+ );
2187
+ }
2188
+ let sigBytes;
2189
+ try {
2190
+ sigBytes = (0, import_utils5.hexToBytes)(parsed.sig);
2191
+ } catch {
2192
+ throw new HttpTransportError("Auth signature is not valid hex", "REQUEST_AUTH_SIG_HEX");
2193
+ }
2194
+ if (sigBytes.length !== 64) {
2195
+ throw new HttpTransportError(
2196
+ `Invalid auth signature length: ${sigBytes.length}`,
2197
+ "REQUEST_AUTH_SIG_LENGTH",
2198
+ { length: sigBytes.length }
2199
+ );
2200
+ }
2201
+ const digest = (0, import_common9.canonicalHashBytes)({
2202
+ v: parsed.v,
2203
+ did: parsed.did,
2204
+ ts: parsed.ts,
2205
+ nonce: parsed.nonce,
2206
+ path: expectedPath
2207
+ });
2208
+ const ok = senderPk.verify(sigBytes, digest, { scheme: "schnorr" });
2209
+ if (!ok) {
2210
+ throw new HttpTransportError("Auth signature verification failed", "REQUEST_AUTH_SIG_INVALID");
2211
+ }
2212
+ return parsed;
2213
+ }
2214
+
2215
+ // src/core/aggregation/transport/http/sse-stream.ts
2216
+ async function* parseSseStream(readable) {
2217
+ const decoder = new TextDecoder("utf-8");
2218
+ const reader = readable.getReader();
2219
+ let buffer = "";
2220
+ let pending = {};
2221
+ const dispatchPending = () => {
2222
+ if (pending.data === void 0) {
2223
+ pending = {};
2224
+ return null;
2225
+ }
2226
+ const ev = { data: pending.data };
2227
+ if (pending.event !== void 0) ev.event = pending.event;
2228
+ if (pending.id !== void 0) ev.id = pending.id;
2229
+ if (pending.retry !== void 0) ev.retry = pending.retry;
2230
+ pending = {};
2231
+ return ev;
2232
+ };
2233
+ const processLine = (line) => {
2234
+ if (line.startsWith(":")) return;
2235
+ const colon = line.indexOf(":");
2236
+ const field = colon === -1 ? line : line.slice(0, colon);
2237
+ let value = colon === -1 ? "" : line.slice(colon + 1);
2238
+ if (value.startsWith(" ")) value = value.slice(1);
2239
+ switch (field) {
2240
+ case "data":
2241
+ pending.data = pending.data === void 0 ? value : `${pending.data}
2242
+ ${value}`;
2243
+ break;
2244
+ case "event":
2245
+ pending.event = value;
2246
+ break;
2247
+ case "id":
2248
+ if (!value.includes("\0")) pending.id = value;
2249
+ break;
2250
+ case "retry": {
2251
+ const n = Number(value);
2252
+ if (Number.isInteger(n) && n >= 0) pending.retry = n;
2253
+ break;
2254
+ }
2255
+ }
2256
+ };
2257
+ try {
2258
+ for (; ; ) {
2259
+ const { value, done } = await reader.read();
2260
+ if (done) {
2261
+ buffer += decoder.decode();
2262
+ if (buffer.length > 0) {
2263
+ const line = buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer;
2264
+ if (line.length > 0) processLine(line);
2265
+ buffer = "";
2266
+ }
2267
+ const tail = dispatchPending();
2268
+ if (tail) yield tail;
2269
+ return;
2270
+ }
2271
+ buffer += decoder.decode(value, { stream: true });
2272
+ let lineEnd = buffer.indexOf("\n");
2273
+ while (lineEnd !== -1) {
2274
+ let line = buffer.slice(0, lineEnd);
2275
+ if (line.endsWith("\r")) line = line.slice(0, -1);
2276
+ buffer = buffer.slice(lineEnd + 1);
2277
+ if (line.length === 0) {
2278
+ const ev = dispatchPending();
2279
+ if (ev) yield ev;
2280
+ } else {
2281
+ processLine(line);
2282
+ }
2283
+ lineEnd = buffer.indexOf("\n");
2284
+ }
2285
+ }
2286
+ } finally {
2287
+ try {
2288
+ reader.releaseLock();
2289
+ } catch {
2290
+ }
2291
+ }
2292
+ }
2293
+
2294
+ // src/core/aggregation/transport/http/client.ts
2295
+ function defaultReconnectBackoff(attempt) {
2296
+ const base2 = Math.min(1e3 * 2 ** attempt, 3e4);
2297
+ const jitter = base2 * 0.2 * Math.random();
2298
+ return Math.floor(base2 + jitter);
2299
+ }
2300
+ var HttpClientTransport = class {
2301
+ name = "http";
2302
+ #baseUrl;
2303
+ #fetch;
2304
+ #logger;
2305
+ #backoff;
2306
+ #clockSkewSec;
2307
+ #actors = /* @__PURE__ */ new Map();
2308
+ #peers = /* @__PURE__ */ new Map();
2309
+ #started = false;
2310
+ #broadcastAbort;
2311
+ constructor(config) {
2312
+ const base2 = typeof config.baseUrl === "string" ? new URL(config.baseUrl) : new URL(config.baseUrl.href);
2313
+ if (!base2.pathname.endsWith("/")) base2.pathname += "/";
2314
+ this.#baseUrl = base2;
2315
+ this.#logger = config.logger ?? CONSOLE_LOGGER;
2316
+ this.#backoff = config.reconnectBackoff ?? defaultReconnectBackoff;
2317
+ this.#clockSkewSec = config.clockSkewSec ?? DEFAULT_CLOCK_SKEW_SEC;
2318
+ const fetchImpl = config.fetchImpl ?? globalThis.fetch;
2319
+ if (typeof fetchImpl !== "function") {
2320
+ throw new HttpTransportError(
2321
+ "No fetch implementation available. Pass config.fetchImpl explicitly.",
2322
+ "NO_FETCH_IMPL"
2323
+ );
2324
+ }
2325
+ this.#fetch = fetchImpl;
2326
+ }
2327
+ start() {
2328
+ if (this.#started) return;
2329
+ this.#started = true;
2330
+ this.#broadcastAbort = new AbortController();
2331
+ this.#runBroadcastLoop(this.#broadcastAbort.signal);
2332
+ for (const [did, entry] of this.#actors) {
2333
+ this.#openInbox(did, entry);
2334
+ }
2335
+ }
2336
+ /**
2337
+ * Tear down all SSE subscriptions and stop reconnect loops. Not part of the
2338
+ * {@link Transport} interface, but needed in tests and whenever a client
2339
+ * wants to cleanly disconnect without unregistering every actor.
2340
+ *
2341
+ * Idempotent. Actors remain registered (re-call {@link start} to resume).
2342
+ */
2343
+ stop() {
2344
+ this.#broadcastAbort?.abort();
2345
+ this.#broadcastAbort = void 0;
2346
+ for (const entry of this.#actors.values()) {
2347
+ entry.inboxAbort?.abort();
2348
+ entry.inboxAbort = void 0;
2349
+ }
2350
+ this.#started = false;
2351
+ }
2352
+ registerActor(did, keys) {
2353
+ const existing = this.#actors.get(did);
2354
+ if (existing?.inboxAbort) existing.inboxAbort.abort();
2355
+ const entry = { keys, handlers: /* @__PURE__ */ new Map() };
2356
+ this.#actors.set(did, entry);
2357
+ if (this.#started) this.#openInbox(did, entry);
2358
+ }
2359
+ unregisterActor(did) {
2360
+ const entry = this.#actors.get(did);
2361
+ if (!entry) return;
2362
+ entry.inboxAbort?.abort();
2363
+ this.#actors.delete(did);
2364
+ this.#peers.delete(did);
2365
+ }
2366
+ getActorPk(did) {
2367
+ return this.#actors.get(did)?.keys.publicKey.compressed;
2368
+ }
2369
+ registerPeer(did, communicationPk) {
2370
+ try {
2371
+ new import_keypair2.CompressedSecp256k1PublicKey(communicationPk);
2372
+ } catch {
2373
+ throw new HttpTransportError(
2374
+ `Invalid communication public key for peer ${did}: expected a 33-byte compressed secp256k1 key.`,
2375
+ "INVALID_PEER_KEY",
2376
+ { did, keyLength: communicationPk.length }
2377
+ );
2378
+ }
2379
+ this.#peers.set(did, communicationPk);
2380
+ }
2381
+ getPeerPk(did) {
2382
+ return this.#peers.get(did);
2383
+ }
2384
+ registerMessageHandler(actorDid, messageType, handler) {
2385
+ const actor = this.#actors.get(actorDid);
2386
+ if (!actor) {
2387
+ throw new HttpTransportError(
2388
+ `Cannot register handler: actor ${actorDid} not registered. Call registerActor() first.`,
2389
+ "UNKNOWN_ACTOR",
2390
+ { did: actorDid }
2391
+ );
2392
+ }
2393
+ actor.handlers.set(messageType, handler);
2394
+ }
2395
+ unregisterMessageHandler(actorDid, messageType) {
2396
+ this.#actors.get(actorDid)?.handlers.delete(messageType);
2397
+ }
2398
+ async sendMessage(message, sender, recipient) {
2399
+ const actor = this.#actors.get(sender);
2400
+ if (!actor) {
2401
+ throw new HttpTransportError(
2402
+ `Unknown sender: ${sender}. Call registerActor() before sending messages.`,
2403
+ "UNKNOWN_SENDER",
2404
+ { did: sender }
2405
+ );
2406
+ }
2407
+ const envelope = signEnvelope(
2408
+ message,
2409
+ { did: sender, keys: actor.keys },
2410
+ { to: recipient }
2411
+ );
2412
+ const url = this.#route(HTTP_ROUTE.MESSAGES);
2413
+ const res = await this.#fetch(url, {
2414
+ method: "POST",
2415
+ headers: { "content-type": "application/json" },
2416
+ body: JSON.stringify(envelope)
2417
+ });
2418
+ if (!res.ok) {
2419
+ const body = await safeText(res);
2420
+ throw new HttpTransportError(
2421
+ `sendMessage failed: HTTP ${res.status}`,
2422
+ "SEND_MESSAGE_HTTP",
2423
+ { status: res.status, body: body.slice(0, 256), messageType: message.type }
2424
+ );
2425
+ }
2426
+ }
2427
+ publishRepeating(message, sender, intervalMs, recipient) {
2428
+ let stopped = false;
2429
+ const attempt = () => {
2430
+ if (stopped) return;
2431
+ this.sendMessage(message, sender, recipient).catch((err) => {
2432
+ this.#logger.debug("publishRepeating send failed:", err);
2433
+ });
2434
+ };
2435
+ attempt();
2436
+ const timer = setInterval(attempt, intervalMs);
2437
+ return () => {
2438
+ if (stopped) return;
2439
+ stopped = true;
2440
+ clearInterval(timer);
2441
+ };
2442
+ }
2443
+ #route(template) {
2444
+ return new URL(template.replace(/^\//, ""), this.#baseUrl);
2445
+ }
2446
+ #openInbox(did, entry) {
2447
+ const abort = new AbortController();
2448
+ entry.inboxAbort = abort;
2449
+ this.#runInboxLoop(did, entry, abort.signal);
2450
+ }
2451
+ async #runBroadcastLoop(signal) {
2452
+ const url = this.#route(HTTP_ROUTE.ADVERTS);
2453
+ let attempt = 0;
2454
+ while (!signal.aborted) {
2455
+ try {
2456
+ const res = await this.#fetch(url, {
2457
+ method: "GET",
2458
+ headers: { accept: "text/event-stream" },
2459
+ signal
2460
+ });
2461
+ if (!res.ok || !res.body) {
2462
+ this.#logger.warn(`Broadcast subscribe failed: HTTP ${res.status}`);
2463
+ await sleep(this.#backoff(attempt++), signal);
2464
+ continue;
2465
+ }
2466
+ attempt = 0;
2467
+ for await (const ev of parseSseStream(res.body)) {
2468
+ if (signal.aborted) return;
2469
+ if (ev.event !== SSE_EVENT.ADVERT) continue;
2470
+ this.#dispatchBroadcast(ev.data);
2471
+ }
2472
+ } catch (err) {
2473
+ if (signal.aborted) return;
2474
+ this.#logger.debug("Broadcast loop error:", err);
2475
+ try {
2476
+ await sleep(this.#backoff(attempt++), signal);
2477
+ } catch {
2478
+ return;
2479
+ }
2480
+ }
2481
+ }
2482
+ }
2483
+ async #runInboxLoop(did, entry, signal) {
2484
+ const url = this.#route(HTTP_ROUTE.ACTOR_INBOX.replace("{did}", encodeURIComponent(did)));
2485
+ let attempt = 0;
2486
+ while (!signal.aborted) {
2487
+ try {
2488
+ const auth = buildRequestAuth(did, entry.keys, url.pathname);
2489
+ const res = await this.#fetch(url, {
2490
+ method: "GET",
2491
+ headers: { accept: "text/event-stream", authorization: auth },
2492
+ signal
2493
+ });
2494
+ if (!res.ok || !res.body) {
2495
+ this.#logger.warn(`Inbox subscribe failed for ${did}: HTTP ${res.status}`);
2496
+ await sleep(this.#backoff(attempt++), signal);
2497
+ continue;
2498
+ }
2499
+ attempt = 0;
2500
+ for await (const ev of parseSseStream(res.body)) {
2501
+ if (signal.aborted) return;
2502
+ if (ev.event !== SSE_EVENT.MESSAGE) continue;
2503
+ await this.#dispatchInbox(ev.data, did, entry);
2504
+ }
2505
+ } catch (err) {
2506
+ if (signal.aborted) return;
2507
+ this.#logger.debug(`Inbox loop error for ${did}:`, err);
2508
+ try {
2509
+ await sleep(this.#backoff(attempt++), signal);
2510
+ } catch {
2511
+ return;
2512
+ }
2513
+ }
2514
+ }
2515
+ }
2516
+ #dispatchBroadcast(dataJson) {
2517
+ const envelope = parseEnvelope(dataJson, this.#logger);
2518
+ if (!envelope) return;
2519
+ const senderPk = this.#resolveSenderPk(envelope.from);
2520
+ if (!senderPk) {
2521
+ this.#logger.debug(`Broadcast from unresolvable DID: ${envelope.from}`);
2522
+ return;
2523
+ }
2524
+ try {
2525
+ verifyEnvelope(envelope, senderPk, { clockSkewSec: this.#clockSkewSec });
2526
+ } catch (err) {
2527
+ this.#logger.debug("Broadcast envelope verification failed:", err);
2528
+ return;
2529
+ }
2530
+ const revived = reviveFromWire(envelope.message);
2531
+ const flat = flattenMessage(revived);
2532
+ const messageType = typeof flat.type === "string" ? flat.type : void 0;
2533
+ if (!messageType) return;
2534
+ for (const actor of this.#actors.values()) {
2535
+ const handler = actor.handlers.get(messageType);
2536
+ if (handler) void Promise.resolve(handler(flat));
2537
+ }
2538
+ }
2539
+ async #dispatchInbox(dataJson, actorDid, entry) {
2540
+ const envelope = parseEnvelope(dataJson, this.#logger);
2541
+ if (!envelope) return;
2542
+ const senderPk = this.#resolveSenderPk(envelope.from);
2543
+ if (!senderPk) {
2544
+ this.#logger.debug(`Inbox message from unresolvable DID: ${envelope.from}`);
2545
+ return;
2546
+ }
2547
+ try {
2548
+ verifyEnvelope(envelope, senderPk, {
2549
+ clockSkewSec: this.#clockSkewSec,
2550
+ expectedTo: actorDid
2551
+ });
2552
+ } catch (err) {
2553
+ this.#logger.debug(`Inbox envelope verification failed for ${actorDid}:`, err);
2554
+ return;
2555
+ }
2556
+ const revived = reviveFromWire(envelope.message);
2557
+ const flat = flattenMessage(revived);
2558
+ const messageType = typeof flat.type === "string" ? flat.type : void 0;
2559
+ if (!messageType) return;
2560
+ const handler = entry.handlers.get(messageType);
2561
+ if (handler) await handler(flat);
2562
+ }
2563
+ #resolveSenderPk(did) {
2564
+ const peerBytes = this.#peers.get(did);
2565
+ if (peerBytes) {
2566
+ try {
2567
+ return new import_keypair2.CompressedSecp256k1PublicKey(peerBytes);
2568
+ } catch {
2569
+ }
2570
+ }
2571
+ try {
2572
+ const components = Identifier.decode(did);
2573
+ if (components.idType === "KEY") {
2574
+ return new import_keypair2.CompressedSecp256k1PublicKey(components.genesisBytes);
2575
+ }
2576
+ } catch {
2577
+ }
2578
+ return void 0;
2579
+ }
2580
+ };
2581
+ function sleep(ms, signal) {
2582
+ if (ms <= 0) return Promise.resolve();
2583
+ return new Promise((resolve, reject) => {
2584
+ const onAbort = () => {
2585
+ clearTimeout(timer);
2586
+ reject(new Error("aborted"));
2587
+ };
2588
+ const timer = setTimeout(() => {
2589
+ signal.removeEventListener("abort", onAbort);
2590
+ resolve();
2591
+ }, ms);
2592
+ if (signal.aborted) onAbort();
2593
+ else signal.addEventListener("abort", onAbort, { once: true });
2594
+ });
2595
+ }
2596
+ async function safeText(res) {
2597
+ try {
2598
+ return await res.text();
2599
+ } catch {
2600
+ return "";
2601
+ }
2602
+ }
2603
+ function parseEnvelope(dataJson, logger) {
2604
+ try {
2605
+ return JSON.parse(dataJson);
2606
+ } catch (err) {
2607
+ logger.debug("SSE event: failed to parse envelope JSON:", err);
2608
+ return void 0;
2609
+ }
2610
+ }
2611
+ function flattenMessage(msg) {
2612
+ if (msg.body && typeof msg.body === "object") {
2613
+ return { ...msg, ...msg.body };
2614
+ }
2615
+ return msg;
2616
+ }
2617
+
2618
+ // src/core/aggregation/transport/http/server.ts
2619
+ var import_keypair3 = require("@did-btcr2/keypair");
2620
+
2621
+ // src/core/aggregation/transport/http/inbox-buffer.ts
2622
+ var InboxBuffer = class {
2623
+ #capacity;
2624
+ #entries = [];
2625
+ #nextId = 1;
2626
+ constructor(capacity = 100) {
2627
+ if (capacity < 1) throw new Error(`InboxBuffer capacity must be >= 1; got ${capacity}`);
2628
+ this.#capacity = capacity;
2629
+ }
2630
+ /** Append an event. Returns the stored record (including its assigned id). */
2631
+ append(event, data) {
2632
+ const stored = { id: String(this.#nextId++), event, data };
2633
+ this.#entries.push(stored);
2634
+ if (this.#entries.length > this.#capacity) this.#entries.shift();
2635
+ return stored;
2636
+ }
2637
+ /**
2638
+ * Return stored events with id strictly greater than `lastEventId`. If
2639
+ * `lastEventId` is unset or unparseable, returns everything currently
2640
+ * retained.
2641
+ */
2642
+ since(lastEventId) {
2643
+ if (!lastEventId) return this.#entries.slice();
2644
+ const boundary = Number(lastEventId);
2645
+ if (!Number.isFinite(boundary)) return this.#entries.slice();
2646
+ return this.#entries.filter((e) => Number(e.id) > boundary);
2647
+ }
2648
+ /** Currently retained event count. */
2649
+ size() {
2650
+ return this.#entries.length;
2651
+ }
2652
+ };
2653
+
2654
+ // src/core/aggregation/transport/http/nonce-cache.ts
2655
+ var NonceCache = class {
2656
+ #maxEntries;
2657
+ #entries = /* @__PURE__ */ new Map();
2658
+ constructor(config = {}) {
2659
+ this.#maxEntries = config.maxEntries ?? 1e4;
2660
+ }
2661
+ /**
2662
+ * Record a nonce. Returns `true` if it was novel (caller should accept the
2663
+ * request) or `false` if it was a replay (caller should reject).
2664
+ */
2665
+ store(did, nonce, timestampSec) {
2666
+ const key = `${did}:${nonce}`;
2667
+ if (this.#entries.has(key)) return false;
2668
+ this.#entries.set(key, timestampSec);
2669
+ if (this.#entries.size > this.#maxEntries) {
2670
+ const oldest = this.#entries.keys().next();
2671
+ if (!oldest.done) this.#entries.delete(oldest.value);
2672
+ }
2673
+ return true;
2674
+ }
2675
+ /** Current cache size. Exposed for observability and tests. */
2676
+ size() {
2677
+ return this.#entries.size;
2678
+ }
2679
+ };
2680
+
2681
+ // src/core/aggregation/transport/http/rate-limiter.ts
2682
+ var InMemoryRateLimitStore = class {
2683
+ #buckets = /* @__PURE__ */ new Map();
2684
+ get(key) {
2685
+ return this.#buckets.get(key);
2686
+ }
2687
+ set(key, state) {
2688
+ this.#buckets.set(key, state);
2689
+ }
2690
+ };
2691
+ var RateLimiter = class {
2692
+ #rps;
2693
+ #burst;
2694
+ #store;
2695
+ constructor(config = {}) {
2696
+ this.#rps = config.rps ?? 10;
2697
+ this.#burst = config.burst ?? 30;
2698
+ this.#store = config.store ?? new InMemoryRateLimitStore();
2699
+ }
2700
+ /** Consume one token for `key`. Returns `true` if accepted, `false` if throttled. */
2701
+ consume(key, nowMs) {
2702
+ const existing = this.#store.get(key);
2703
+ const state = existing ?? { tokens: this.#burst, lastRefillMs: nowMs };
2704
+ if (existing) {
2705
+ const elapsedSec = Math.max(0, (nowMs - existing.lastRefillMs) / 1e3);
2706
+ state.tokens = Math.min(this.#burst, existing.tokens + elapsedSec * this.#rps);
2707
+ state.lastRefillMs = nowMs;
2708
+ }
2709
+ if (state.tokens < 1) {
2710
+ this.#store.set(key, state);
2711
+ return false;
2712
+ }
2713
+ state.tokens -= 1;
2714
+ this.#store.set(key, state);
2715
+ return true;
2716
+ }
2717
+ };
2718
+
2719
+ // src/core/aggregation/transport/http/server.ts
2720
+ var INBOX_PATH_PREFIX = "/v1/actors/";
2721
+ var INBOX_PATH_SUFFIX = "/inbox";
2722
+ var DEFAULT_ADVERT_TTL_MS = 5 * 60 * 1e3;
2723
+ var DEFAULT_HEARTBEAT_MS = 2e4;
2724
+ var HttpServerTransport = class {
2725
+ name = "http";
2726
+ #logger;
2727
+ #cors;
2728
+ #clockSkewSec;
2729
+ #inboxBufferSize;
2730
+ #advertTtlMs;
2731
+ #heartbeatMs;
2732
+ #rateLimiter;
2733
+ #nonceCache;
2734
+ #now;
2735
+ #actors = /* @__PURE__ */ new Map();
2736
+ #peers = /* @__PURE__ */ new Map();
2737
+ #inboxes = /* @__PURE__ */ new Map();
2738
+ #broadcastSubscribers = /* @__PURE__ */ new Set();
2739
+ #currentAdvert;
2740
+ #advertSeq = 0;
2741
+ constructor(config = {}) {
2742
+ this.#logger = config.logger ?? CONSOLE_LOGGER;
2743
+ this.#cors = config.cors ?? { mode: "permissive" };
2744
+ this.#clockSkewSec = config.clockSkewSec ?? DEFAULT_CLOCK_SKEW_SEC;
2745
+ this.#inboxBufferSize = config.inboxBufferSize ?? 100;
2746
+ this.#advertTtlMs = config.advertTtlMs ?? DEFAULT_ADVERT_TTL_MS;
2747
+ this.#heartbeatMs = config.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
2748
+ this.#rateLimiter = config.rateLimiter ?? new RateLimiter();
2749
+ this.#nonceCache = config.nonceCache ?? new NonceCache();
2750
+ this.#now = config.now ?? (() => Date.now());
2751
+ }
2752
+ // ----------------------------------------------------------------
2753
+ // Transport interface
2754
+ // ----------------------------------------------------------------
2755
+ start() {
2756
+ }
2757
+ /**
2758
+ * Detach the transport: close every open SSE subscription, clear the advert
2759
+ * cache, and drop all actor / peer / inbox state. Intended for shutdown and
2760
+ * for test teardown.
2761
+ */
2762
+ stop() {
2763
+ for (const sub of this.#broadcastSubscribers) this.#closeBroadcastSubscriber(sub);
2764
+ this.#broadcastSubscribers.clear();
2765
+ for (const inbox of this.#inboxes.values()) {
2766
+ for (const sub of inbox.subscribers) this.#closeInboxSubscriber(sub);
2767
+ inbox.subscribers.clear();
2768
+ }
2769
+ this.#inboxes.clear();
2770
+ this.#currentAdvert = void 0;
2771
+ }
2772
+ registerActor(did, keys) {
2773
+ this.#actors.set(did, { keys, handlers: /* @__PURE__ */ new Map() });
2774
+ }
2775
+ unregisterActor(did) {
2776
+ this.#actors.delete(did);
2777
+ this.#peers.delete(did);
2778
+ }
2779
+ getActorPk(did) {
2780
+ return this.#actors.get(did)?.keys.publicKey.compressed;
2781
+ }
2782
+ registerPeer(did, communicationPk) {
2783
+ try {
2784
+ new import_keypair3.CompressedSecp256k1PublicKey(communicationPk);
2785
+ } catch {
2786
+ throw new HttpTransportError(
2787
+ `Invalid peer public key for ${did}`,
2788
+ "INVALID_PEER_KEY",
2789
+ { did, keyLength: communicationPk.length }
2790
+ );
2791
+ }
2792
+ this.#peers.set(did, communicationPk);
2793
+ }
2794
+ getPeerPk(did) {
2795
+ return this.#peers.get(did);
2796
+ }
2797
+ registerMessageHandler(actorDid, messageType, handler) {
2798
+ const actor = this.#actors.get(actorDid);
2799
+ if (!actor) {
2800
+ throw new HttpTransportError(
2801
+ `Cannot register handler: actor ${actorDid} not registered`,
2802
+ "UNKNOWN_ACTOR",
2803
+ { did: actorDid }
2804
+ );
2805
+ }
2806
+ actor.handlers.set(messageType, handler);
2807
+ }
2808
+ unregisterMessageHandler(actorDid, messageType) {
2809
+ this.#actors.get(actorDid)?.handlers.delete(messageType);
2810
+ }
2811
+ async sendMessage(message, sender, recipient) {
2812
+ if (!recipient) {
2813
+ throw new HttpTransportError(
2814
+ "HttpServerTransport.sendMessage requires a recipient. Use publishRepeating for broadcasts.",
2815
+ "MISSING_RECIPIENT",
2816
+ { messageType: message.type }
2817
+ );
2818
+ }
2819
+ const actor = this.#actors.get(sender);
2820
+ if (!actor) {
2821
+ throw new HttpTransportError(
2822
+ `Unknown sender: ${sender}`,
2823
+ "UNKNOWN_SENDER",
2824
+ { did: sender }
2825
+ );
2826
+ }
2827
+ const envelope = signEnvelope(message, { did: sender, keys: actor.keys }, { to: recipient });
2828
+ const dataJson = JSON.stringify(envelope);
2829
+ const inbox = this.#getOrCreateInbox(recipient);
2830
+ const stored = inbox.buffer.append(SSE_EVENT.MESSAGE, dataJson);
2831
+ for (const sub of inbox.subscribers) {
2832
+ this.#safeWrite(sub.stream, stored.event, stored.data, stored.id);
2833
+ }
2834
+ }
2835
+ publishRepeating(message, sender, _intervalMs, _recipient) {
2836
+ const actor = this.#actors.get(sender);
2837
+ if (!actor) {
2838
+ throw new HttpTransportError(`Unknown sender: ${sender}`, "UNKNOWN_SENDER", { did: sender });
2839
+ }
2840
+ const envelope = signEnvelope(message, { did: sender, keys: actor.keys });
2841
+ const dataJson = JSON.stringify(envelope);
2842
+ const id = String(++this.#advertSeq);
2843
+ const expiresAtMs = this.#now() + this.#advertTtlMs;
2844
+ this.#currentAdvert = { dataJson, id, expiresAtMs };
2845
+ for (const sub of this.#broadcastSubscribers) {
2846
+ this.#safeWrite(sub.stream, SSE_EVENT.ADVERT, dataJson, id);
2847
+ }
2848
+ return () => {
2849
+ if (this.#currentAdvert?.id === id) this.#currentAdvert = void 0;
2850
+ };
2851
+ }
2852
+ // ----------------------------------------------------------------
2853
+ // Sans-I/O HTTP surface
2854
+ // ----------------------------------------------------------------
2855
+ /**
2856
+ * Handle a POST / GET request (non-SSE). The caller dispatches SSE paths to
2857
+ * {@link handleSse} instead. Returns a fully formed response; the caller's
2858
+ * adapter turns it into an HTTP write.
2859
+ */
2860
+ async handleRequest(req) {
2861
+ const method = req.method.toUpperCase();
2862
+ if (method === "OPTIONS") return this.#respond(204, "", req);
2863
+ const path = extractPath(req.url);
2864
+ if (method === "GET" && path === HTTP_ROUTE.WELL_KNOWN) {
2865
+ return this.#respondJson(200, this.#wellKnownMetadata(), req);
2866
+ }
2867
+ if (method === "POST" && path === HTTP_ROUTE.MESSAGES) {
2868
+ return await this.#handleMessagesPost(req);
2869
+ }
2870
+ if (method === "POST" && path === HTTP_ROUTE.ADVERTS) {
2871
+ return await this.#handleAdvertsPost(req);
2872
+ }
2873
+ return this.#respondJson(404, { error: "not_found" }, req);
2874
+ }
2875
+ /**
2876
+ * Open an SSE stream for a GET request. The caller is responsible for
2877
+ * flushing writes and propagating the `onClose` callback when the HTTP
2878
+ * connection ends.
2879
+ */
2880
+ handleSse(req, stream) {
2881
+ if (req.method.toUpperCase() !== "GET") {
2882
+ stream.close();
2883
+ return;
2884
+ }
2885
+ const path = extractPath(req.url);
2886
+ if (path === HTTP_ROUTE.ADVERTS) {
2887
+ this.#openBroadcastSubscription(stream);
2888
+ return;
2889
+ }
2890
+ const inboxMatch = matchInboxPath(path);
2891
+ if (inboxMatch) {
2892
+ this.#openInboxSubscription(req, stream, inboxMatch.did, path);
2893
+ return;
2894
+ }
2895
+ stream.close();
2896
+ }
2897
+ // ----------------------------------------------------------------
2898
+ // Request handlers
2899
+ // ----------------------------------------------------------------
2900
+ async #handleMessagesPost(req) {
2901
+ const envelope = parseJsonBody(req.body);
2902
+ if (!envelope) return this.#respondJson(400, { error: "invalid_json" }, req);
2903
+ const senderPk = this.#resolveSenderPk(envelope.from);
2904
+ if (!senderPk) {
2905
+ return this.#respondJson(401, { error: "unknown_sender" }, req);
2906
+ }
2907
+ try {
2908
+ verifyEnvelope(envelope, senderPk, { clockSkewSec: this.#clockSkewSec });
2909
+ } catch (err) {
2910
+ this.#logger.debug("POST /v1/messages: envelope verification failed:", err);
2911
+ return this.#respondJson(401, { error: "invalid_envelope" }, req);
2912
+ }
2913
+ if (!this.#nonceCache.store(envelope.from, envelope.nonce, envelope.timestamp)) {
2914
+ return this.#respondJson(409, { error: "replay" }, req);
2915
+ }
2916
+ if (!this.#rateLimiter.consume(envelope.from, this.#now())) {
2917
+ return this.#respondJson(429, { error: "rate_limited" }, req);
2918
+ }
2919
+ if (!envelope.to) {
2920
+ return this.#respondJson(400, { error: "missing_recipient" }, req);
2921
+ }
2922
+ const actor = this.#actors.get(envelope.to);
2923
+ if (!actor) {
2924
+ return this.#respondJson(404, { error: "unknown_recipient" }, req);
2925
+ }
2926
+ const revived = reviveFromWire(envelope.message);
2927
+ const flat = flattenMessage2(revived);
2928
+ const messageType = typeof flat.type === "string" ? flat.type : void 0;
2929
+ if (!messageType) return this.#respondJson(400, { error: "missing_message_type" }, req);
2930
+ const handler = actor.handlers.get(messageType);
2931
+ if (handler) {
2932
+ try {
2933
+ await handler(flat);
2934
+ } catch (err) {
2935
+ this.#logger.debug(`Handler threw for ${messageType}:`, err);
2936
+ }
2937
+ }
2938
+ return this.#respondJson(202, { ok: true }, req);
2939
+ }
2940
+ async #handleAdvertsPost(req) {
2941
+ const envelope = parseJsonBody(req.body);
2942
+ if (!envelope) return this.#respondJson(400, { error: "invalid_json" }, req);
2943
+ const senderPk = this.#resolveSenderPk(envelope.from);
2944
+ if (!senderPk) return this.#respondJson(401, { error: "unknown_sender" }, req);
2945
+ try {
2946
+ verifyEnvelope(envelope, senderPk, { clockSkewSec: this.#clockSkewSec });
2947
+ } catch {
2948
+ return this.#respondJson(401, { error: "invalid_envelope" }, req);
2949
+ }
2950
+ if (!this.#nonceCache.store(envelope.from, envelope.nonce, envelope.timestamp)) {
2951
+ return this.#respondJson(409, { error: "replay" }, req);
2952
+ }
2953
+ if (!this.#rateLimiter.consume(envelope.from, this.#now())) {
2954
+ return this.#respondJson(429, { error: "rate_limited" }, req);
2955
+ }
2956
+ if (!this.#actors.has(envelope.from)) {
2957
+ return this.#respondJson(403, { error: "not_an_actor" }, req);
2958
+ }
2959
+ const id = String(++this.#advertSeq);
2960
+ this.#currentAdvert = {
2961
+ dataJson: JSON.stringify(envelope),
2962
+ id,
2963
+ expiresAtMs: this.#now() + this.#advertTtlMs
2964
+ };
2965
+ for (const sub of this.#broadcastSubscribers) {
2966
+ this.#safeWrite(sub.stream, SSE_EVENT.ADVERT, this.#currentAdvert.dataJson, id);
2967
+ }
2968
+ return this.#respondJson(202, { ok: true }, req);
2969
+ }
2970
+ #openBroadcastSubscription(stream) {
2971
+ const sub = { stream };
2972
+ this.#broadcastSubscribers.add(sub);
2973
+ stream.onClose(() => {
2974
+ this.#closeBroadcastSubscriber(sub);
2975
+ this.#broadcastSubscribers.delete(sub);
2976
+ });
2977
+ if (this.#currentAdvert && this.#currentAdvert.expiresAtMs > this.#now()) {
2978
+ this.#safeWrite(stream, SSE_EVENT.ADVERT, this.#currentAdvert.dataJson, this.#currentAdvert.id);
2979
+ }
2980
+ if (this.#heartbeatMs > 0) {
2981
+ sub.heartbeatTimer = setInterval(() => {
2982
+ try {
2983
+ stream.writeComment("hb");
2984
+ } catch {
2985
+ }
2986
+ }, this.#heartbeatMs);
2987
+ }
2988
+ }
2989
+ #openInboxSubscription(req, stream, did, path) {
2990
+ const auth = req.headers.authorization;
2991
+ if (!auth) {
2992
+ this.#logger.debug(`Inbox subscribe: missing authorization header for ${did}`);
2993
+ stream.close();
2994
+ return;
2995
+ }
2996
+ const senderPk = this.#resolveSenderPk(did);
2997
+ if (!senderPk) {
2998
+ stream.close();
2999
+ return;
3000
+ }
3001
+ let parsedTs = 0;
3002
+ let parsedNonce = "";
3003
+ try {
3004
+ const parsed = verifyRequestAuth(auth, path, senderPk, {
3005
+ clockSkewSec: this.#clockSkewSec,
3006
+ now: () => this.#now()
3007
+ });
3008
+ if (parsed.did !== did) {
3009
+ stream.close();
3010
+ return;
3011
+ }
3012
+ parsedTs = parsed.ts;
3013
+ parsedNonce = parsed.nonce;
3014
+ } catch (err) {
3015
+ this.#logger.debug(`Inbox subscribe: auth verification failed for ${did}:`, err);
3016
+ stream.close();
3017
+ return;
3018
+ }
3019
+ if (!this.#nonceCache.store(did, parsedNonce, parsedTs)) {
3020
+ stream.close();
3021
+ return;
3022
+ }
3023
+ if (!this.#rateLimiter.consume(did, this.#now())) {
3024
+ stream.close();
3025
+ return;
3026
+ }
3027
+ const inbox = this.#getOrCreateInbox(did);
3028
+ const sub = { stream };
3029
+ inbox.subscribers.add(sub);
3030
+ stream.onClose(() => {
3031
+ this.#closeInboxSubscriber(sub);
3032
+ inbox.subscribers.delete(sub);
3033
+ });
3034
+ const lastEventId = req.headers["last-event-id"];
3035
+ for (const stored of inbox.buffer.since(lastEventId)) {
3036
+ this.#safeWrite(stream, stored.event, stored.data, stored.id);
3037
+ }
3038
+ if (this.#heartbeatMs > 0) {
3039
+ sub.heartbeatTimer = setInterval(() => {
3040
+ try {
3041
+ stream.writeComment("hb");
3042
+ } catch {
3043
+ }
3044
+ }, this.#heartbeatMs);
3045
+ }
3046
+ }
3047
+ // ----------------------------------------------------------------
3048
+ // Internal helpers
3049
+ // ----------------------------------------------------------------
3050
+ #getOrCreateInbox(did) {
3051
+ let inbox = this.#inboxes.get(did);
3052
+ if (!inbox) {
3053
+ inbox = { buffer: new InboxBuffer(this.#inboxBufferSize), subscribers: /* @__PURE__ */ new Set() };
3054
+ this.#inboxes.set(did, inbox);
3055
+ }
3056
+ return inbox;
3057
+ }
3058
+ #resolveSenderPk(did) {
3059
+ const peerBytes = this.#peers.get(did);
3060
+ if (peerBytes) {
3061
+ try {
3062
+ return new import_keypair3.CompressedSecp256k1PublicKey(peerBytes);
3063
+ } catch {
3064
+ }
3065
+ }
3066
+ try {
3067
+ const components = Identifier.decode(did);
3068
+ if (components.idType === "KEY") {
3069
+ return new import_keypair3.CompressedSecp256k1PublicKey(components.genesisBytes);
3070
+ }
3071
+ } catch {
3072
+ }
3073
+ return void 0;
3074
+ }
3075
+ #safeWrite(stream, event, data, id) {
3076
+ try {
3077
+ stream.writeEvent(event, data, id);
3078
+ } catch (err) {
3079
+ this.#logger.debug("SSE writeEvent failed:", err);
3080
+ }
3081
+ }
3082
+ #closeBroadcastSubscriber(sub) {
3083
+ if (sub.heartbeatTimer) clearInterval(sub.heartbeatTimer);
3084
+ try {
3085
+ sub.stream.close();
3086
+ } catch {
3087
+ }
3088
+ }
3089
+ #closeInboxSubscriber(sub) {
3090
+ if (sub.heartbeatTimer) clearInterval(sub.heartbeatTimer);
3091
+ try {
3092
+ sub.stream.close();
3093
+ } catch {
3094
+ }
3095
+ }
3096
+ #respondJson(status, body, req) {
3097
+ return {
3098
+ status,
3099
+ headers: { "content-type": "application/json", ...this.#corsHeaders(req) },
3100
+ body: JSON.stringify(body)
3101
+ };
3102
+ }
3103
+ #respond(status, body, req) {
3104
+ return { status, headers: this.#corsHeaders(req), body };
3105
+ }
3106
+ #corsHeaders(req) {
3107
+ const origin = req.headers.origin;
3108
+ if (!origin) return {};
3109
+ const common = {
3110
+ "access-control-allow-methods": "GET, POST, OPTIONS",
3111
+ "access-control-allow-headers": "authorization, content-type, last-event-id",
3112
+ "access-control-max-age": "86400"
3113
+ };
3114
+ switch (this.#cors.mode) {
3115
+ case "permissive":
3116
+ return { "access-control-allow-origin": "*", ...common };
3117
+ case "allowlist":
3118
+ if (this.#cors.origins.includes(origin)) {
3119
+ return { "access-control-allow-origin": origin, vary: "origin", ...common };
3120
+ }
3121
+ return {};
3122
+ case "same-origin":
3123
+ return {};
3124
+ }
3125
+ }
3126
+ #wellKnownMetadata() {
3127
+ return {
3128
+ envelopeVersion: HTTP_ENVELOPE_VERSION,
3129
+ heartbeatIntervalMs: this.#heartbeatMs,
3130
+ inboxBufferSize: this.#inboxBufferSize,
3131
+ advertTtlMs: this.#advertTtlMs
3132
+ };
3133
+ }
3134
+ };
3135
+ function extractPath(reqUrl) {
3136
+ if (reqUrl.startsWith("http://") || reqUrl.startsWith("https://")) {
3137
+ return new URL(reqUrl).pathname;
3138
+ }
3139
+ const q = reqUrl.indexOf("?");
3140
+ return q === -1 ? reqUrl : reqUrl.slice(0, q);
3141
+ }
3142
+ function matchInboxPath(path) {
3143
+ if (!path.startsWith(INBOX_PATH_PREFIX) || !path.endsWith(INBOX_PATH_SUFFIX)) return void 0;
3144
+ const encodedDid = path.slice(INBOX_PATH_PREFIX.length, path.length - INBOX_PATH_SUFFIX.length);
3145
+ if (!encodedDid) return void 0;
3146
+ try {
3147
+ return { did: decodeURIComponent(encodedDid) };
3148
+ } catch {
3149
+ return void 0;
3150
+ }
3151
+ }
3152
+ function parseJsonBody(body) {
3153
+ if (body === void 0 || body === "") return void 0;
3154
+ try {
3155
+ return JSON.parse(body);
3156
+ } catch {
3157
+ return void 0;
3158
+ }
3159
+ }
3160
+ function flattenMessage2(msg) {
3161
+ if (msg.body && typeof msg.body === "object") {
3162
+ return { ...msg, ...msg.body };
3163
+ }
3164
+ return msg;
3165
+ }
3166
+
3167
+ // src/core/aggregation/transport/nostr.ts
3168
+ var import_keypair4 = require("@did-btcr2/keypair");
3169
+ var import_utils6 = require("@noble/hashes/utils");
3170
+ var import_nostr_tools = require("nostr-tools");
3171
+ var import_pool = require("nostr-tools/pool");
3172
+ var DEFAULT_NOSTR_RELAYS = [
3173
+ "wss://relay.damus.io",
3174
+ "wss://nos.lol",
3175
+ "wss://relay.snort.social",
3176
+ "wss://nostr-pub.wellorder.net"
3177
+ ];
3178
+ var DEFAULT_BROADCAST_LOOKBACK_MS = 5 * 60 * 1e3;
3179
+ var NostrTransport = class _NostrTransport {
3180
+ name = "nostr";
3181
+ pool;
3182
+ #relays;
3183
+ #actors = /* @__PURE__ */ new Map();
3184
+ #peerRegistry = /* @__PURE__ */ new Map();
3185
+ #started = false;
3186
+ #logger;
3187
+ #broadcastLookbackMs;
3188
+ constructor(config) {
3189
+ this.#relays = config?.relays ?? DEFAULT_NOSTR_RELAYS;
3190
+ this.#logger = config?.logger ?? CONSOLE_LOGGER;
3191
+ this.#broadcastLookbackMs = config?.broadcastLookbackMs ?? DEFAULT_BROADCAST_LOOKBACK_MS;
3192
+ }
3193
+ /**
3194
+ * Registers an actor (DID + keys) to send/receive messages with.
3195
+ * Must be called before start() to ensure subscriptions are created for the actor.
3196
+ * @param {string} did - The DID of the actor.
3197
+ * @param {SchnorrKeyPair} keys - The Schnorr key pair for the actor.
3198
+ * @throws {TransportAdapterError} If the actor is already registered or if the transport has already started.
3199
+ * @example
3200
+ * const transport = new NostrTransport();
3201
+ * const keys = SchnorrKeyPair.generate();
3202
+ * const did = DidBtcr2.create(keys.publicKey.compressed, { idType: 'KEY', network: 'mutinynet' });
3203
+ * transport.registerActor(did, keys);
3204
+ * transport.start();
3205
+ */
3206
+ registerActor(did, keys) {
3207
+ const entry = { keys, handlers: /* @__PURE__ */ new Map(), subscriptions: [] };
3208
+ this.#actors.set(did, entry);
3209
+ if (this.#started && this.pool) {
3210
+ this.#subscribeDirected(did, entry);
3211
+ }
3212
+ }
3213
+ /**
3214
+ * Detach an actor: drop its handlers, close its relay subscriptions, and remove its keys + peer
3215
+ * mapping. Idempotent.
3216
+ * @param {string} did - The DID of the actor to unregister.
3217
+ * @returns {void}
3218
+ * @throws {TransportAdapterError} If the transport is not started or if the pool is unavailable.
3219
+ * @example
3220
+ * transport.unregisterActor(did);
3221
+ */
3222
+ unregisterActor(did) {
3223
+ const entry = this.#actors.get(did);
3224
+ if (!entry) return;
3225
+ for (const sub of entry.subscriptions) {
3226
+ try {
3227
+ sub.close();
3228
+ } catch (err) {
3229
+ this.#logger.debug(`Error closing subscription for ${did}:`, err);
3230
+ }
3231
+ }
3232
+ entry.subscriptions.length = 0;
3233
+ entry.handlers.clear();
3234
+ this.#actors.delete(did);
3235
+ this.#peerRegistry.delete(did);
3236
+ }
3237
+ /**
3238
+ * Remove a single (actor, messageType) handler. Idempotent.
3239
+ * @param {string} actorDid - The DID of the actor.
3240
+ * @param {string} messageType - The type of message to unregister the handler for.
3241
+ * @returns {void}
3242
+ * @example
3243
+ * transport.unregisterMessageHandler(actorDid, messageType);
3244
+ */
3245
+ unregisterMessageHandler(actorDid, messageType) {
3246
+ const actor = this.#actors.get(actorDid);
3247
+ if (!actor) return;
3248
+ actor.handlers.delete(messageType);
3249
+ }
3250
+ /**
3251
+ * Gets the public key for a registered actor by their DID.
3252
+ * @param {string} did - The DID of the registered actor to get the public key for.
3253
+ * @returns {Uint8Array | undefined} The compressed public key bytes for the actor's DID, or
3254
+ * undefined if the DID is not registered.
3255
+ */
3256
+ getActorPk(did) {
3257
+ return this.#actors.get(did)?.keys.publicKey.compressed;
3258
+ }
3259
+ /**
3260
+ * Registers a peer's communication public key for encrypted messages.
3261
+ * @param {string} did - The DID of the peer to register.
3262
+ * @param {Uint8Array} communicationPk - The compressed secp256k1 public key bytes for the peer.
3263
+ */
3264
+ registerPeer(did, communicationPk) {
3265
+ try {
3266
+ new import_keypair4.CompressedSecp256k1PublicKey(communicationPk);
3267
+ } catch {
3268
+ throw new TransportAdapterError(
3269
+ `Invalid communication public key for peer ${did}: expected a 33-byte compressed secp256k1 key.`,
3270
+ "INVALID_PEER_KEY",
3271
+ { adapter: this.name, did, keyLength: communicationPk.length }
3272
+ );
3273
+ }
3274
+ this.#peerRegistry.set(did, communicationPk);
3275
+ }
3276
+ /**
3277
+ * Gets the registered communication public key for a peer by their DID.
3278
+ * @param {string} did - The DID of the peer to get the communication public key for.
3279
+ * @returns {Uint8Array | undefined} The compressed secp256k1 public key bytes for the peer, or
3280
+ * undefined if the peer is not registered.
3281
+ */
3282
+ getPeerPk(did) {
3283
+ return this.#peerRegistry.get(did);
3284
+ }
3285
+ /**
3286
+ * Registers a message handler function for a specific actor and message type. The handler will be called
3287
+ * when a message of the specified type is received for the actor's DID. The transport must have been
3288
+ * started for handlers to be invoked. If the transport is already started, the handler will be registered
3289
+ * immediately; otherwise, it will be registered when the transport starts and the actor's subscription is created.
3290
+ * @param {string} actorDid - The DID of the actor to register the message handler for.
3291
+ * @param {string} messageType - The type of message to handle.
3292
+ * @param {MessageHandler} handler - The function to handle incoming messages of the specified type.
3293
+ * @throws {TransportAdapterError} If the actor DID is not registered or if the handler is invalid.
3294
+ */
3295
+ registerMessageHandler(actorDid, messageType, handler) {
3296
+ const actor = this.#actors.get(actorDid);
3297
+ if (!actor) {
3298
+ throw new TransportAdapterError(
3299
+ `Cannot register handler: actor ${actorDid} not registered. Call registerActor() first.`,
3300
+ "UNKNOWN_ACTOR_ERROR",
3301
+ { adapter: this.name, did: actorDid }
3302
+ );
3303
+ }
3304
+ actor.handlers.set(messageType, handler);
3305
+ }
3306
+ /**
3307
+ * Starts the transport by connecting to the configured Nostr relays and setting up subscriptions
3308
+ * for all registered actors. This method must be called after registering actors via registerActor()
3309
+ * and before sending or receiving messages. The transport will subscribe to broadcast events (kind 1)
3310
+ * for cohort adverts and directed events (kinds 1 and 1059) for each registered actor based on their
3311
+ * public keys. Incoming events are processed and dispatched to the appropriate handlers based on
3312
+ * message type. If the transport is already started, this method has no effect.
3313
+ * @returns {NostrTransport}
3314
+ */
3315
+ start() {
3316
+ if (this.#started) return this;
3317
+ this.#started = true;
3318
+ this.pool = new import_pool.SimplePool();
3319
+ const broadcastFilter = {
3320
+ kinds: [1],
3321
+ "#t": [COHORT_ADVERT]
3322
+ };
3323
+ if (this.#broadcastLookbackMs > 0) {
3324
+ broadcastFilter.since = Math.floor((Date.now() - this.#broadcastLookbackMs) / 1e3);
3325
+ }
3326
+ this.pool.subscribeMany(this.#relays, broadcastFilter, {
3327
+ onclose: (reasons) => this.#logger.debug("Nostr broadcast subscription closed", reasons),
3328
+ onevent: this.#handleBroadcastEvent.bind(this)
3329
+ });
3330
+ for (const [did, entry] of this.#actors) {
3331
+ this.#subscribeDirected(did, entry);
3332
+ }
3333
+ this.#logger.info(`NostrTransport started, listening on ${this.#relays.length} relay(s)`);
3334
+ return this;
3335
+ }
3336
+ /**
3337
+ * Sends a message by publishing a Nostr event to the configured relays. The message is serialized
3338
+ * as JSON and included in the event content.
3339
+ * @param {BaseMessage} message - The aggregation message to send. Must include a valid `type` property.
3340
+ * @param {Did} sender - The DID of the registered actor sending the message. Must have been
3341
+ * registered via registerActor().
3342
+ * @param {Did} [to] - Optional recipient DID for directed messages. Required for encrypted message
3343
+ * types. If provided, must have been registered via registerPeer().
3344
+ * @returns {Promise<void>} Resolves when the message has been published to the relays. Note that
3345
+ * publication is best-effort: the method will resolve as long as at least one relay accepts the
3346
+ * event, even if others reject it.
3347
+ */
3348
+ async sendMessage(message, sender, to) {
3349
+ const type = message.type;
3350
+ if (!type) {
3351
+ throw new TransportAdapterError(
3352
+ "Message type is undefined",
3353
+ "SEND_MESSAGE_ERROR",
3354
+ { adapter: this.name, type }
3355
+ );
3356
+ }
3357
+ const actor = this.#actors.get(sender);
3358
+ if (!actor) {
3359
+ throw new TransportAdapterError(
3360
+ `Unknown sender: ${sender}. Call registerActor() before sending messages.`,
1539
3361
  "UNKNOWN_ACTOR_ERROR",
1540
3362
  { adapter: this.name, did: sender }
1541
3363
  );
1542
3364
  }
1543
3365
  const senderKeys = actor.keys;
1544
3366
  const tags = [
1545
- ["p", (0, import_utils4.bytesToHex)(senderKeys.publicKey.x)],
3367
+ ["p", (0, import_utils6.bytesToHex)(senderKeys.publicKey.x)],
1546
3368
  ["t", type]
1547
3369
  ];
1548
3370
  if (to) {
1549
3371
  const recipientPkBytes = this.#peerRegistry.get(to);
1550
3372
  if (recipientPkBytes) {
1551
- const recipientPk = new import_keypair.CompressedSecp256k1PublicKey(recipientPkBytes);
1552
- tags.push(["p", (0, import_utils4.bytesToHex)(recipientPk.x)]);
3373
+ const recipientPk = new import_keypair4.CompressedSecp256k1PublicKey(recipientPkBytes);
3374
+ tags.push(["p", (0, import_utils6.bytesToHex)(recipientPk.x)]);
1553
3375
  }
1554
3376
  }
1555
3377
  if (isKeygenMessageType(type)) {
@@ -1559,7 +3381,7 @@ var NostrTransport = class _NostrTransport {
1559
3381
  tags,
1560
3382
  content: JSON.stringify(message, _NostrTransport.#jsonReplacer)
1561
3383
  }, senderKeys.secretKey.bytes);
1562
- console.debug(`Publishing kind 1 [${type}]`);
3384
+ this.#logger.debug(`Publishing kind 1 [${type}]`);
1563
3385
  await this.#publishToRelays(event);
1564
3386
  return;
1565
3387
  }
@@ -1579,10 +3401,10 @@ var NostrTransport = class _NostrTransport {
1579
3401
  { adapter: this.name, did: to }
1580
3402
  );
1581
3403
  }
1582
- const recipientPk = new import_keypair.CompressedSecp256k1PublicKey(recipientPkBytes);
3404
+ const recipientPk = new import_keypair4.CompressedSecp256k1PublicKey(recipientPkBytes);
1583
3405
  const conversationKey = import_nostr_tools.nip44.v2.utils.getConversationKey(
1584
3406
  senderKeys.secretKey.bytes,
1585
- (0, import_utils4.bytesToHex)(recipientPk.x)
3407
+ (0, import_utils6.bytesToHex)(recipientPk.x)
1586
3408
  );
1587
3409
  const content = import_nostr_tools.nip44.v2.encrypt(JSON.stringify(message, _NostrTransport.#jsonReplacer), conversationKey);
1588
3410
  const event = (0, import_nostr_tools.finalizeEvent)({
@@ -1591,25 +3413,70 @@ var NostrTransport = class _NostrTransport {
1591
3413
  tags,
1592
3414
  content
1593
3415
  }, senderKeys.secretKey.bytes);
1594
- console.debug(`Publishing kind 1059 [${type}]`);
3416
+ this.#logger.debug(`Publishing kind 1059 [${type}]`);
1595
3417
  await this.#publishToRelays(event);
1596
3418
  return;
1597
3419
  }
1598
- console.warn(`Unsupported message type: ${type}`);
3420
+ this.#logger.warn(`Unsupported message type: ${type}`);
3421
+ }
3422
+ /**
3423
+ * Publish the message once immediately and then re-publish on an interval
3424
+ * until the returned stop function is invoked.
3425
+ *
3426
+ * Useful for broadcast messages (COHORT_ADVERT) on relays that don't
3427
+ * backfill historical events to late subscribers: republishing gives late
3428
+ * joiners a window to discover the message without requiring protocol
3429
+ * changes. Relay rate-limit / publish failures inside the interval are
3430
+ * caught and logged rather than propagated — the caller should stop the
3431
+ * repeater once the protocol condition is satisfied.
3432
+ */
3433
+ publishRepeating(message, sender, intervalMs, recipient) {
3434
+ let stopped = false;
3435
+ void this.sendMessage(message, sender, recipient).catch((err) => {
3436
+ this.#logger.debug("publishRepeating first send failed:", err);
3437
+ });
3438
+ const timer = setInterval(() => {
3439
+ if (stopped) return;
3440
+ void this.sendMessage(message, sender, recipient).catch((err) => {
3441
+ this.#logger.debug("publishRepeating retry failed:", err);
3442
+ });
3443
+ }, intervalMs);
3444
+ return () => {
3445
+ if (stopped) return;
3446
+ stopped = true;
3447
+ clearInterval(timer);
3448
+ };
1599
3449
  }
3450
+ /**
3451
+ * Creates a directed subscription for the given actor, filtering for messages that match the
3452
+ * actor's public key. Messages received on this subscription are dispatched to the actor's
3453
+ * registered handlers based on message type.
3454
+ * @param {string} did - The DID of the actor to create the subscription for.
3455
+ * @param {ActorEntry} entry - The actor's registration entry containing keys and handlers.
3456
+ * @returns {void}
3457
+ * @throws {TransportAdapterError} If the transport is not started or if the pool is unavailable.
3458
+ */
1600
3459
  #subscribeDirected(did, entry) {
1601
3460
  if (!this.pool) return;
1602
- 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),
3461
+ const pkHex = (0, import_utils6.bytesToHex)(entry.keys.publicKey.x);
3462
+ const sub = this.pool.subscribeMany(this.#relays, { kinds: [1, 1059], "#p": [pkHex] }, {
3463
+ onclose: (reasons) => this.#logger.debug(`Nostr directed subscription closed for ${did}`, reasons),
1606
3464
  onevent: this.#makeActorEventHandler(did)
1607
3465
  });
3466
+ entry.subscriptions.push(sub);
1608
3467
  }
3468
+ /**
3469
+ * Creates an event handler function for a specific actor that processes incoming events, decrypts
3470
+ * if necessary, and dispatches messages to the actor's registered handlers based on message type.
3471
+ * @param {string} actorDid - The DID of the actor to create the event handler for.
3472
+ * @returns {(event: Event) => Promise<void>} An asynchronous event handler function that
3473
+ * processes incoming events for the specified actor.
3474
+ */
1609
3475
  #makeActorEventHandler(actorDid) {
1610
3476
  return async (event) => {
1611
3477
  const actor = this.#actors.get(actorDid);
1612
3478
  if (!actor) return;
3479
+ if (event.pubkey === (0, import_utils6.bytesToHex)(actor.keys.publicKey.x)) return;
1613
3480
  let message;
1614
3481
  try {
1615
3482
  if (event.kind === 1) {
@@ -1625,19 +3492,29 @@ var NostrTransport = class _NostrTransport {
1625
3492
  return;
1626
3493
  }
1627
3494
  } catch (err) {
1628
- console.debug(`Failed to parse event ${event.id} for ${actorDid}:`, err);
3495
+ this.#logger.debug(`Failed to parse event ${event.id} for ${actorDid}:`, err);
1629
3496
  return;
1630
3497
  }
1631
3498
  this.#dispatchMessage(message, actor);
1632
3499
  };
1633
3500
  }
3501
+ /**
3502
+ * Handles incoming broadcast events (kind 1) by parsing the event content, validating it as an
3503
+ * aggregation message, and dispatching it to all registered actors that have handlers for the
3504
+ * message type. This is used for COHORT_ADVERT messages that need to be received by all actors
3505
+ * regardless of DID.
3506
+ * @param {Event} event - The Nostr event to handle, expected to be a kind 1 broadcast containing
3507
+ * a COHORT_ADVERT message. The event content is parsed and dispatched to all registered actors
3508
+ * that have handlers for the
3509
+ * @returns
3510
+ */
1634
3511
  async #handleBroadcastEvent(event) {
1635
3512
  if (event.kind !== 1) return;
1636
3513
  let message;
1637
3514
  try {
1638
3515
  message = JSON.parse(event.content, _NostrTransport.#jsonReviver);
1639
3516
  } catch (err) {
1640
- console.debug(`Failed to parse broadcast event ${event.id}:`, err);
3517
+ this.#logger.debug(`Failed to parse broadcast event ${event.id}:`, err);
1641
3518
  return;
1642
3519
  }
1643
3520
  if (message.body && typeof message.body === "object") {
@@ -1650,6 +3527,18 @@ var NostrTransport = class _NostrTransport {
1650
3527
  if (handler) await handler(message);
1651
3528
  }
1652
3529
  }
3530
+ /**
3531
+ * Dispatches a parsed message to the appropriate handler of a registered actor based on message type.
3532
+ * The message is expected to have already been parsed from the Nostr event content and validated as
3533
+ * an aggregation message. If the message has a body, its properties are merged into the top-level
3534
+ * message object for easier handler access. The message is then dispatched to the handler registered
3535
+ * for its type, if one exists.
3536
+ * @param {Record<string, unknown>} message - The message object parsed from a Nostr event, expected to
3537
+ * @param {ActorEntry} actor - The registered actor entry containing keys and handlers to dispatch the message to.
3538
+ * @returns {void}
3539
+ * @throws {TransportAdapterError} If the message type is unsupported or if no handler is registered
3540
+ * for the message type.
3541
+ */
1653
3542
  #dispatchMessage(message, actor) {
1654
3543
  if (message.body && typeof message.body === "object") {
1655
3544
  message = { ...message, ...message.body };
@@ -1659,6 +3548,16 @@ var NostrTransport = class _NostrTransport {
1659
3548
  const handler = actor.handlers.get(messageType);
1660
3549
  if (handler) handler(message);
1661
3550
  }
3551
+ /**
3552
+ * Publishes a Nostr event to the configured relays and handles the results. The method waits for all
3553
+ * relay promises to settle and checks how many accepted or rejected the event. If all relays reject the event,
3554
+ * an error is thrown. Otherwise, the method completes successfully even if some relays rejected the event,
3555
+ * as long as at least one relay accepted it. Relay rejections are logged for debugging purposes.
3556
+ * @param {Event} event - The Nostr event to publish to the configured relays. The event should already
3557
+ * @returns {Promise<void>} A promise that resolves if the event was accepted by at least one relay, or rejects
3558
+ * with a TransportAdapterError if all relays rejected the event.
3559
+ * @throws {TransportAdapterError} If the pool is not initialized or if all relays reject the event.
3560
+ */
1662
3561
  async #publishToRelays(event) {
1663
3562
  const relayPromises = this.pool?.publish(this.#relays, event);
1664
3563
  if (!relayPromises?.length) return;
@@ -1666,7 +3565,7 @@ var NostrTransport = class _NostrTransport {
1666
3565
  const accepted = results.filter((r) => r.status === "fulfilled").length;
1667
3566
  const rejected = results.filter((r) => r.status === "rejected");
1668
3567
  for (const r of rejected) {
1669
- console.debug(`Relay rejected event ${event.id}: ${r.reason}`);
3568
+ this.#logger.debug(`Relay rejected event ${event.id}: ${r.reason}`);
1670
3569
  }
1671
3570
  if (accepted === 0) {
1672
3571
  throw new TransportAdapterError(
@@ -1676,15 +3575,39 @@ var NostrTransport = class _NostrTransport {
1676
3575
  );
1677
3576
  }
1678
3577
  }
3578
+ /**
3579
+ * Custom JSON replacer to handle serialization of Uint8Array values as hex strings in message
3580
+ * content. This allows messages containing binary data (e.g. public keys, signatures) to be correctly
3581
+ * serialized to JSON for Nostr event content. The replacer checks if a value is a Uint8Array and, if so,
3582
+ * converts it to a hex string wrapped in an object with a __bytes property. The corresponding reviver
3583
+ * can then convert this back to a Uint8Array when parsing the message content from the event.
3584
+ * @param {string} _key - The key of the property being processed.
3585
+ * @param {unknown} value - The value to check if the message type is valid.
3586
+ * @returns {unknown} The transformed value for JSON serialization. If the value is a Uint8Array,
3587
+ * it returns an object with a __bytes property containing the hex string. Otherwise, it returns
3588
+ * the value unchanged.
3589
+ */
1679
3590
  static #jsonReplacer(_key, value) {
1680
3591
  if (value instanceof Uint8Array) {
1681
- return { __bytes: (0, import_utils4.bytesToHex)(value) };
3592
+ return { __bytes: (0, import_utils6.bytesToHex)(value) };
1682
3593
  }
1683
3594
  return value;
1684
3595
  }
3596
+ /**
3597
+ * Custom JSON reviver to handle deserialization of hex strings back into Uint8Array values in message
3598
+ * content. This complements the custom replacer used during serialization, allowing messages that contain
3599
+ * binary data (e.g. public keys, signatures) to be correctly reconstructed when parsing JSON from
3600
+ * Nostr event content. The reviver checks if a value is an object with a __bytes property and,
3601
+ * if so, converts the hex string back into a Uint8Array. Otherwise, it returns the value unchanged.
3602
+ * @param {string} _key - The key of the property being processed.
3603
+ * @param {unknown} value - The value to check if it is an object containing a __bytes property for
3604
+ * hex string conversion.
3605
+ * @returns {unknown} The transformed value for JSON deserialization. If the value is an object
3606
+ * with a __bytes property, it returns a Uint8Array. Otherwise, it returns the value unchanged.
3607
+ */
1685
3608
  static #jsonReviver(_key, value) {
1686
3609
  if (value && typeof value === "object" && "__bytes" in value) {
1687
- return (0, import_utils4.hexToBytes)(value.__bytes);
3610
+ return (0, import_utils6.hexToBytes)(value.__bytes);
1688
3611
  }
1689
3612
  return value;
1690
3613
  }
@@ -1695,9 +3618,19 @@ var TransportFactory = class {
1695
3618
  static establish(config) {
1696
3619
  switch (config.type) {
1697
3620
  case "nostr":
1698
- return new NostrTransport({ relays: config.relays });
3621
+ return new NostrTransport({
3622
+ relays: config.relays,
3623
+ logger: config.logger,
3624
+ broadcastLookbackMs: config.broadcastLookbackMs
3625
+ });
1699
3626
  case "didcomm":
1700
- throw new import_common5.NotImplementedError("DIDComm transport not implemented yet.");
3627
+ throw new import_common10.NotImplementedError("DIDComm transport not implemented yet.");
3628
+ case "http":
3629
+ if (config.role === "client") return new HttpClientTransport(config);
3630
+ if (config.role === "server") return new HttpServerTransport(config);
3631
+ throw new import_common10.NotImplementedError(
3632
+ `HTTP transport role not implemented: ${config.role}`
3633
+ );
1701
3634
  default:
1702
3635
  throw new TransportError(
1703
3636
  `Invalid transport type: ${config.type}`,
@@ -1709,32 +3642,58 @@ var TransportFactory = class {
1709
3642
  };
1710
3643
 
1711
3644
  // src/core/aggregation/transport/didcomm.ts
1712
- var import_common6 = require("@did-btcr2/common");
3645
+ var import_common11 = require("@did-btcr2/common");
1713
3646
  var DidCommTransport = class {
1714
3647
  name = "didcomm";
1715
3648
  start() {
1716
- throw new import_common6.NotImplementedError("DidCommTransport not implemented. Use NostrTransport instead.");
3649
+ throw new import_common11.NotImplementedError("DidCommTransport not implemented. Use NostrTransport instead.");
1717
3650
  }
1718
3651
  registerActor(_did, _keys) {
1719
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
3652
+ throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
1720
3653
  }
1721
3654
  getActorPk(_did) {
1722
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
3655
+ throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
1723
3656
  }
1724
3657
  registerPeer(_did, _communicationPk) {
1725
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
3658
+ throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
1726
3659
  }
1727
3660
  getPeerPk(_did) {
1728
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
3661
+ throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
1729
3662
  }
1730
3663
  registerMessageHandler(_actorDid, _messageType, _handler) {
1731
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
3664
+ throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
3665
+ }
3666
+ unregisterMessageHandler(_actorDid, _messageType) {
3667
+ throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
3668
+ }
3669
+ unregisterActor(_did) {
3670
+ throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
1732
3671
  }
1733
3672
  async sendMessage(_message, _sender, _recipient) {
1734
- throw new import_common6.NotImplementedError("DidCommTransport not implemented.");
3673
+ throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
3674
+ }
3675
+ publishRepeating(_message, _sender, _intervalMs, _recipient) {
3676
+ throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
1735
3677
  }
1736
3678
  };
1737
3679
 
3680
+ // src/core/aggregation/transport/http/sse-writer.ts
3681
+ function formatSseEvent(event, data, id) {
3682
+ const lines = [];
3683
+ if (id !== void 0) lines.push(`id: ${id}`);
3684
+ lines.push(`event: ${event}`);
3685
+ for (const part of data.split("\n")) lines.push(`data: ${part}`);
3686
+ lines.push("");
3687
+ lines.push("");
3688
+ return lines.join("\n");
3689
+ }
3690
+ function formatSseComment(comment) {
3691
+ const safe = comment.replace(/\n/g, " ");
3692
+ return `: ${safe}
3693
+
3694
+ `;
3695
+ }
3696
+
1738
3697
  // src/core/aggregation/runner/typed-emitter.ts
1739
3698
  var TypedEventEmitter = class {
1740
3699
  #listeners = /* @__PURE__ */ new Map();
@@ -1790,7 +3749,8 @@ var TypedEventEmitter = class {
1790
3749
  };
1791
3750
 
1792
3751
  // src/core/aggregation/runner/service-runner.ts
1793
- var AggregationServiceRunner = class extends TypedEventEmitter {
3752
+ var DEFAULT_ADVERT_REPEAT_INTERVAL_MS = 6e4;
3753
+ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEventEmitter {
1794
3754
  /** Direct access to the underlying state machine for advanced use. */
1795
3755
  session;
1796
3756
  #transport;
@@ -1799,11 +3759,26 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1799
3759
  #onOptInReceived;
1800
3760
  #onReadyToFinalize;
1801
3761
  #onProvideTxData;
3762
+ #cohortTtlMs;
3763
+ #phaseTimeoutMs;
3764
+ #advertRepeatIntervalMs;
1802
3765
  #cohortId;
1803
3766
  #handlersRegistered = false;
1804
3767
  #stopped = false;
3768
+ /**
3769
+ * Guard against the async race where two concurrent #handleOptIn invocations
3770
+ * both pass the `participants.length >= minParticipants` check before either
3771
+ * mutates the cohort phase. Set synchronously before any `await` so subsequent
3772
+ * handlers observe it on their next resumption.
3773
+ */
3774
+ #finalizing = false;
1805
3775
  #resolveRun;
1806
3776
  #rejectRun;
3777
+ #cohortTtlTimer;
3778
+ #phaseTimer;
3779
+ #lastObservedPhase;
3780
+ /** Stop handle for the repeating COHORT_ADVERT publish loop. */
3781
+ #stopAdvertRepeat;
1807
3782
  constructor(options) {
1808
3783
  super();
1809
3784
  this.#transport = options.transport;
@@ -1814,7 +3789,25 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1814
3789
  finalize: acceptedCount >= minRequired
1815
3790
  }));
1816
3791
  this.#onProvideTxData = options.onProvideTxData;
1817
- this.session = new AggregationService({ did: options.did, keys: options.keys });
3792
+ this.#cohortTtlMs = options.cohortTtlMs;
3793
+ this.#phaseTimeoutMs = options.phaseTimeoutMs;
3794
+ this.#advertRepeatIntervalMs = options.advertRepeatIntervalMs ?? DEFAULT_ADVERT_REPEAT_INTERVAL_MS;
3795
+ this.session = new AggregationService({
3796
+ did: options.did,
3797
+ keys: options.keys,
3798
+ maxUpdateSizeBytes: options.maxUpdateSizeBytes
3799
+ });
3800
+ }
3801
+ /**
3802
+ * Drain any silent rejections the state machine recorded during the most
3803
+ * recent receive() and surface them as `message-rejected` events. Safe to
3804
+ * call even before a cohortId is assigned.
3805
+ */
3806
+ #drainRejections() {
3807
+ if (!this.#cohortId) return;
3808
+ for (const r of this.session.drainRejections(this.#cohortId)) {
3809
+ this.emit("message-rejected", { cohortId: this.#cohortId, ...r });
3810
+ }
1818
3811
  }
1819
3812
  /**
1820
3813
  * Run the protocol to completion. Resolves with the final aggregation result
@@ -1829,22 +3822,103 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1829
3822
  try {
1830
3823
  this.#registerHandlers();
1831
3824
  this.#cohortId = this.session.createCohort(this.#config);
3825
+ this.#startTimers();
1832
3826
  const advertMsgs = this.session.advertise(this.#cohortId);
3827
+ this.#onPhaseMaybeChanged();
1833
3828
  this.emit("cohort-advertised", { cohortId: this.#cohortId });
1834
- this.#sendAll(advertMsgs).catch((err) => this.#fail(err));
3829
+ if (this.#advertRepeatIntervalMs > 0) {
3830
+ this.#startAdvertRepeat(advertMsgs);
3831
+ } else {
3832
+ this.#sendAll(advertMsgs).catch((err) => this.#fail(err));
3833
+ }
1835
3834
  } catch (err) {
1836
3835
  this.#fail(err);
1837
3836
  }
1838
3837
  });
1839
3838
  }
1840
3839
  /**
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).
3840
+ * Begin publishing the cohort advert immediately and on a repeating interval
3841
+ * until {@link #stopAdvertRepeating} is called. Each advert is broadcast
3842
+ * (no recipient) via the transport's `publishRepeating` primitive.
3843
+ */
3844
+ #startAdvertRepeat(advertMsgs) {
3845
+ const stops = [];
3846
+ for (const msg of advertMsgs) {
3847
+ stops.push(this.#transport.publishRepeating(msg, this.#did, this.#advertRepeatIntervalMs));
3848
+ }
3849
+ this.#stopAdvertRepeat = () => {
3850
+ for (const stop of stops) {
3851
+ try {
3852
+ stop();
3853
+ } catch {
3854
+ }
3855
+ }
3856
+ };
3857
+ }
3858
+ /** Stop the advert republish loop. Idempotent. */
3859
+ #stopAdvertRepeating() {
3860
+ if (!this.#stopAdvertRepeat) return;
3861
+ const stop = this.#stopAdvertRepeat;
3862
+ this.#stopAdvertRepeat = void 0;
3863
+ stop();
3864
+ }
3865
+ /** Schedule cohort TTL + phase timeout at the start of a run. */
3866
+ #startTimers() {
3867
+ if (this.#cohortTtlMs !== void 0) {
3868
+ this.#cohortTtlTimer = setTimeout(() => {
3869
+ const reason = `Cohort ${this.#cohortId ?? ""} exceeded TTL of ${this.#cohortTtlMs}ms`;
3870
+ this.emit("cohort-failed", { cohortId: this.#cohortId ?? "", reason });
3871
+ this.#fail(new Error(reason));
3872
+ }, this.#cohortTtlMs);
3873
+ }
3874
+ this.#resetPhaseTimer();
3875
+ }
3876
+ /** Reset the per-phase stall timer. Called when a phase transition is observed. */
3877
+ #resetPhaseTimer() {
3878
+ if (this.#phaseTimer) clearTimeout(this.#phaseTimer);
3879
+ this.#phaseTimer = void 0;
3880
+ if (this.#phaseTimeoutMs === void 0) return;
3881
+ this.#phaseTimer = setTimeout(() => {
3882
+ const reason = `Cohort ${this.#cohortId ?? ""} stalled in phase ${this.#lastObservedPhase ?? "?"} for ${this.#phaseTimeoutMs}ms`;
3883
+ this.emit("cohort-failed", { cohortId: this.#cohortId ?? "", reason });
3884
+ this.#fail(new Error(reason));
3885
+ }, this.#phaseTimeoutMs);
3886
+ }
3887
+ /** Detect a phase change since the last observation and reset the phase timer. */
3888
+ #onPhaseMaybeChanged() {
3889
+ if (!this.#cohortId) return;
3890
+ const phase = this.session.getCohortPhase(this.#cohortId);
3891
+ if (phase !== this.#lastObservedPhase) {
3892
+ this.#lastObservedPhase = phase;
3893
+ this.#resetPhaseTimer();
3894
+ }
3895
+ }
3896
+ /** Clear both timers. Called on successful completion, stop(), and #fail. */
3897
+ #clearTimers() {
3898
+ if (this.#cohortTtlTimer) clearTimeout(this.#cohortTtlTimer);
3899
+ if (this.#phaseTimer) clearTimeout(this.#phaseTimer);
3900
+ this.#cohortTtlTimer = void 0;
3901
+ this.#phaseTimer = void 0;
3902
+ }
3903
+ /**
3904
+ * Stop the runner early. Marks the runner stopped and detaches transport
3905
+ * handlers so a restart or a new runner doesn't inherit stale dispatch.
1844
3906
  */
1845
3907
  stop() {
1846
3908
  this.#stopped = true;
1847
- }
3909
+ this.#stopAdvertRepeating();
3910
+ this.#clearTimers();
3911
+ this.#unregisterHandlers();
3912
+ if (this.#cohortId) this.session.removeCohort(this.#cohortId);
3913
+ }
3914
+ /** Message types this runner listens for on the transport. */
3915
+ static #HANDLED_MESSAGE_TYPES = [
3916
+ COHORT_OPT_IN,
3917
+ SUBMIT_UPDATE,
3918
+ VALIDATION_ACK,
3919
+ NONCE_CONTRIBUTION,
3920
+ SIGNATURE_AUTHORIZATION
3921
+ ];
1848
3922
  /**
1849
3923
  * Internal: handler registration with the transport. Idempotent.
1850
3924
  */
@@ -1857,6 +3931,14 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1857
3931
  this.#transport.registerMessageHandler(this.#did, NONCE_CONTRIBUTION, this.#handleNonceContribution.bind(this));
1858
3932
  this.#transport.registerMessageHandler(this.#did, SIGNATURE_AUTHORIZATION, this.#handleSignatureAuthorization.bind(this));
1859
3933
  }
3934
+ /** Internal: detach from the transport. Safe to call repeatedly. */
3935
+ #unregisterHandlers() {
3936
+ if (!this.#handlersRegistered) return;
3937
+ this.#handlersRegistered = false;
3938
+ for (const type of _AggregationServiceRunner.#HANDLED_MESSAGE_TYPES) {
3939
+ this.#transport.unregisterMessageHandler(this.#did, type);
3940
+ }
3941
+ }
1860
3942
  /**
1861
3943
  * Internal: message handlers for each protocol step. Each handler:
1862
3944
  * 1) feeds the message into the state machine via session.receive()
@@ -1873,6 +3955,8 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1873
3955
  if (this.#stopped) return;
1874
3956
  try {
1875
3957
  this.session.receive(msg);
3958
+ this.#drainRejections();
3959
+ this.#onPhaseMaybeChanged();
1876
3960
  const optIn = this.session.pendingOptIns(this.#cohortId).get(msg.from);
1877
3961
  if (!optIn) return;
1878
3962
  this.emit("opt-in-received", optIn);
@@ -1884,19 +3968,23 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1884
3968
  await this.#sendAll(this.session.acceptParticipant(this.#cohortId, msg.from));
1885
3969
  this.emit("participant-accepted", { participantDid: msg.from });
1886
3970
  const cohort = this.session.getCohort(this.#cohortId);
1887
- if (cohort.participants.length >= this.#config.minParticipants) {
3971
+ if (cohort.participants.length >= this.#config.minParticipants && !this.#finalizing) {
3972
+ this.#finalizing = true;
1888
3973
  const finalizeDecision = await this.#onReadyToFinalize({
1889
3974
  acceptedCount: cohort.participants.length,
1890
3975
  minRequired: this.#config.minParticipants
1891
3976
  });
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);
3977
+ if (!finalizeDecision.finalize) {
3978
+ this.#finalizing = false;
3979
+ return;
1899
3980
  }
3981
+ const readyMsgs = this.session.finalizeKeygen(this.#cohortId);
3982
+ this.#stopAdvertRepeating();
3983
+ this.emit("keygen-complete", {
3984
+ cohortId: this.#cohortId,
3985
+ beaconAddress: cohort.beaconAddress
3986
+ });
3987
+ await this.#sendAll(readyMsgs);
1900
3988
  }
1901
3989
  } catch (err) {
1902
3990
  this.#fail(err);
@@ -1914,6 +4002,8 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1914
4002
  if (this.#stopped) return;
1915
4003
  try {
1916
4004
  this.session.receive(msg);
4005
+ this.#drainRejections();
4006
+ this.#onPhaseMaybeChanged();
1917
4007
  this.emit("update-received", { participantDid: msg.from });
1918
4008
  if (this.session.getCohortPhase(this.#cohortId) === "UpdatesCollected" /* UpdatesCollected */) {
1919
4009
  const distributeMsgs = this.session.buildAndDistribute(this.#cohortId);
@@ -1936,9 +4026,18 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1936
4026
  if (this.#stopped) return;
1937
4027
  try {
1938
4028
  this.session.receive(msg);
4029
+ this.#drainRejections();
4030
+ this.#onPhaseMaybeChanged();
1939
4031
  const approved = !!msg.body?.approved;
1940
4032
  this.emit("validation-received", { participantDid: msg.from, approved });
1941
- if (this.session.getCohortPhase(this.#cohortId) === "Validated" /* Validated */) {
4033
+ const phase = this.session.getCohortPhase(this.#cohortId);
4034
+ if (phase === "Failed" /* Failed */) {
4035
+ const reason = `Validation rejected by participant ${msg.from}`;
4036
+ this.emit("cohort-failed", { cohortId: this.#cohortId, reason });
4037
+ this.#fail(new Error(reason));
4038
+ return;
4039
+ }
4040
+ if (phase === "Validated" /* Validated */) {
1942
4041
  const cohort = this.session.getCohort(this.#cohortId);
1943
4042
  const txData = await this.#onProvideTxData({
1944
4043
  cohortId: this.#cohortId,
@@ -1966,6 +4065,8 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1966
4065
  if (this.#stopped) return;
1967
4066
  try {
1968
4067
  this.session.receive(msg);
4068
+ this.#drainRejections();
4069
+ this.#onPhaseMaybeChanged();
1969
4070
  this.emit("nonce-received", { participantDid: msg.from });
1970
4071
  if (this.session.getCohortPhase(this.#cohortId) === "NoncesCollected" /* NoncesCollected */) {
1971
4072
  await this.#sendAll(this.session.sendAggregatedNonce(this.#cohortId));
@@ -1986,8 +4087,12 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
1986
4087
  if (this.#stopped) return;
1987
4088
  try {
1988
4089
  this.session.receive(msg);
4090
+ this.#drainRejections();
4091
+ this.#onPhaseMaybeChanged();
1989
4092
  const result = this.session.getResult(this.#cohortId);
1990
4093
  if (result) {
4094
+ this.#clearTimers();
4095
+ this.#unregisterHandlers();
1991
4096
  this.emit("signing-complete", result);
1992
4097
  this.#resolveRun?.(result);
1993
4098
  }
@@ -2012,6 +4117,10 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
2012
4117
  * @param {Error} err - The error to handle.
2013
4118
  */
2014
4119
  #fail(err) {
4120
+ this.#stopAdvertRepeating();
4121
+ this.#clearTimers();
4122
+ this.#unregisterHandlers();
4123
+ if (this.#cohortId) this.session.removeCohort(this.#cohortId);
2015
4124
  this.emit("error", err);
2016
4125
  this.#rejectRun?.(err);
2017
4126
  }
@@ -2046,9 +4155,27 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
2046
4155
  async start() {
2047
4156
  this.#registerHandlers();
2048
4157
  }
2049
- /** Stop the runner. Does not unregister transport handlers. */
4158
+ /** Stop the runner and detach transport handlers. Safe to call repeatedly. */
2050
4159
  stop() {
2051
4160
  this.#stopped = true;
4161
+ this.#unregisterHandlers();
4162
+ }
4163
+ /** Message types this runner listens for on the transport. */
4164
+ static #HANDLED_MESSAGE_TYPES = [
4165
+ COHORT_ADVERT,
4166
+ COHORT_OPT_IN_ACCEPT,
4167
+ COHORT_READY,
4168
+ DISTRIBUTE_AGGREGATED_DATA,
4169
+ AUTHORIZATION_REQUEST,
4170
+ AGGREGATED_NONCE
4171
+ ];
4172
+ /** Internal: detach from the transport. Safe to call repeatedly. */
4173
+ #unregisterHandlers() {
4174
+ if (!this.#handlersRegistered) return;
4175
+ this.#handlersRegistered = false;
4176
+ for (const type of _AggregationParticipantRunner.#HANDLED_MESSAGE_TYPES) {
4177
+ this.#transport.unregisterMessageHandler(this.#did, type);
4178
+ }
2052
4179
  }
2053
4180
  /**
2054
4181
  * Single-shot helper: start, join the first cohort that passes `shouldJoin`,
@@ -2213,7 +4340,14 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
2213
4340
  if (this.session.getCohortPhase(cohortId) === "Complete" /* Complete */) {
2214
4341
  const info = this.session.joinedCohorts.get(cohortId);
2215
4342
  if (info) {
2216
- this.emit("cohort-complete", { cohortId, beaconAddress: info.beaconAddress });
4343
+ const validation = this.session.pendingValidations.get(cohortId);
4344
+ this.emit("cohort-complete", {
4345
+ cohortId,
4346
+ beaconAddress: info.beaconAddress,
4347
+ beaconType: validation?.beaconType ?? "",
4348
+ casAnnouncement: validation?.casAnnouncement,
4349
+ smtProof: validation?.smtProof
4350
+ });
2217
4351
  }
2218
4352
  }
2219
4353
  } catch (err) {
@@ -2235,33 +4369,32 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
2235
4369
  };
2236
4370
 
2237
4371
  // src/core/beacon/beacon.ts
2238
- var import_keypair2 = require("@did-btcr2/keypair");
2239
- var import_utils5 = require("@noble/hashes/utils");
2240
- var import_bitcoinjs_lib4 = require("bitcoinjs-lib");
4372
+ var import_utils7 = require("@noble/hashes/utils.js");
4373
+ var import_btc_signer4 = require("@scure/btc-signer");
2241
4374
 
2242
4375
  // src/core/beacon/error.ts
2243
- var import_common7 = require("@did-btcr2/common");
2244
- var BeaconError = class extends import_common7.MethodError {
4376
+ var import_common12 = require("@did-btcr2/common");
4377
+ var BeaconError = class extends import_common12.MethodError {
2245
4378
  constructor(message, type = "BeaconError", data) {
2246
4379
  super(message, type, data);
2247
4380
  }
2248
4381
  };
2249
- var SingletonBeaconError = class extends import_common7.MethodError {
4382
+ var SingletonBeaconError = class extends import_common12.MethodError {
2250
4383
  constructor(message, type = "SingletonBeaconError", data) {
2251
4384
  super(message, type, data);
2252
4385
  }
2253
4386
  };
2254
- var AggregateBeaconError = class extends import_common7.MethodError {
4387
+ var AggregateBeaconError = class extends import_common12.MethodError {
2255
4388
  constructor(message, type = "AggregateBeaconError", data) {
2256
4389
  super(message, type, data);
2257
4390
  }
2258
4391
  };
2259
- var CASBeaconError = class extends import_common7.MethodError {
4392
+ var CASBeaconError = class extends import_common12.MethodError {
2260
4393
  constructor(message, type = "CASBeaconError", data) {
2261
4394
  super(message, type, data);
2262
4395
  }
2263
4396
  };
2264
- var SMTBeaconError = class extends import_common7.MethodError {
4397
+ var SMTBeaconError = class extends import_common12.MethodError {
2265
4398
  constructor(message, type = "SMTBeaconError", data) {
2266
4399
  super(message, type, data);
2267
4400
  }
@@ -2289,6 +4422,121 @@ var StaticFeeEstimator = class {
2289
4422
 
2290
4423
  // src/core/beacon/beacon.ts
2291
4424
  var DEFAULT_FEE_ESTIMATOR = new StaticFeeEstimator(5);
4425
+ var P2TR_BEACON_TX_VSIZE = 160;
4426
+ var P2WPKH_BEACON_TX_VSIZE = 155;
4427
+ var P2PKH_BEACON_TX_VSIZE = 240;
4428
+ var SINGLETON_BEACON_TX_VSIZE = {
4429
+ p2pkh: P2PKH_BEACON_TX_VSIZE,
4430
+ p2wpkh: P2WPKH_BEACON_TX_VSIZE,
4431
+ p2tr: P2TR_BEACON_TX_VSIZE
4432
+ };
4433
+ function detectSingletonScriptKind(bitcoinAddress, network) {
4434
+ const decoded = (0, import_btc_signer4.Address)(network).decode(bitcoinAddress);
4435
+ if (decoded.type === "pkh") return "p2pkh";
4436
+ if (decoded.type === "wpkh") return "p2wpkh";
4437
+ if (decoded.type === "tr") return "p2tr";
4438
+ throw new BeaconError(
4439
+ `Unsupported singleton beacon address type "${decoded.type}". Expected P2PKH, P2WPKH, or P2TR (taproot key-path).`,
4440
+ "UNSUPPORTED_BEACON_ADDRESS_TYPE",
4441
+ { address: bitcoinAddress, kind: decoded.type }
4442
+ );
4443
+ }
4444
+ function deriveSingletonAddress(kind, pubkey, network) {
4445
+ if (kind === "p2pkh") return (0, import_btc_signer4.p2pkh)(pubkey, network).address;
4446
+ if (kind === "p2wpkh") return (0, import_btc_signer4.p2wpkh)(pubkey, network).address;
4447
+ return (0, import_btc_signer4.p2tr)(pubkey.slice(1, 33), void 0, network).address;
4448
+ }
4449
+ function opReturnScript(signalBytes) {
4450
+ return import_btc_signer4.Script.encode(["RETURN", signalBytes]);
4451
+ }
4452
+ async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
4453
+ const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
4454
+ if (!utxos.length) {
4455
+ throw new BeaconError(
4456
+ "No UTXOs found, please fund address!",
4457
+ "UNFUNDED_BEACON_ADDRESS",
4458
+ { address: bitcoinAddress }
4459
+ );
4460
+ }
4461
+ const utxo = utxos.sort((a, b) => b.status.block_height - a.status.block_height).shift();
4462
+ if (!utxo) {
4463
+ throw new BeaconError(
4464
+ "Beacon bitcoin address unfunded or utxos unconfirmed.",
4465
+ "UNFUNDED_BEACON_ADDRESS",
4466
+ { address: bitcoinAddress }
4467
+ );
4468
+ }
4469
+ const prevTxHex = await bitcoin.rest.transaction.getHex(utxo.txid);
4470
+ return { utxo, prevTxBytes: (0, import_utils7.hexToBytes)(prevTxHex) };
4471
+ }
4472
+ async function buildAggregationBeaconTx(opts) {
4473
+ const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
4474
+ const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
4475
+ const tapOut = (0, import_btc_signer4.p2tr)(opts.internalPubkey, void 0, opts.network);
4476
+ const witnessScript = tapOut.script;
4477
+ const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
4478
+ if (BigInt(utxo.value) <= feeSats) {
4479
+ throw new BeaconError(
4480
+ `UTXO value (${utxo.value}) insufficient to cover fee (${feeSats}).`,
4481
+ "INSUFFICIENT_FUNDS",
4482
+ { address: opts.beaconAddress, valueSats: utxo.value, feeSats }
4483
+ );
4484
+ }
4485
+ const tx = new import_btc_signer4.Transaction({ allowUnknownOutputs: true });
4486
+ tx.addInput({
4487
+ txid: utxo.txid,
4488
+ index: utxo.vout,
4489
+ nonWitnessUtxo: prevTxBytes,
4490
+ witnessUtxo: { amount: BigInt(utxo.value), script: witnessScript },
4491
+ tapInternalKey: opts.internalPubkey
4492
+ });
4493
+ tx.addOutputAddress(opts.beaconAddress, BigInt(utxo.value) - feeSats, opts.network);
4494
+ tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
4495
+ return {
4496
+ tx,
4497
+ prevOutScripts: [witnessScript],
4498
+ prevOutValues: [BigInt(utxo.value)],
4499
+ beaconAddress: opts.beaconAddress,
4500
+ utxo,
4501
+ feeSats,
4502
+ scriptKind: "p2tr"
4503
+ };
4504
+ }
4505
+ async function signSingletonInput(tx, inputIdx, kind, signer, prevOutScript, amount) {
4506
+ const pubkey = signer.publicKey;
4507
+ if (kind === "p2pkh") {
4508
+ const sighashType = import_btc_signer4.SigHash.ALL;
4509
+ const sighash2 = tx.preimageLegacy(inputIdx, prevOutScript, sighashType);
4510
+ const sig2 = signer.sign(sighash2, "ecdsa");
4511
+ const sigWithType = (0, import_utils7.concatBytes)(sig2, new Uint8Array([sighashType]));
4512
+ tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
4513
+ tx.finalize();
4514
+ return tx.hex;
4515
+ }
4516
+ if (kind === "p2wpkh") {
4517
+ const decoded = import_btc_signer4.OutScript.decode(prevOutScript);
4518
+ if (decoded.type !== "wpkh") {
4519
+ throw new BeaconError(
4520
+ `Expected P2WPKH prev-output script, got "${decoded.type}".`,
4521
+ "PREVOUT_SCRIPT_MISMATCH",
4522
+ { kind, observedScriptType: decoded.type }
4523
+ );
4524
+ }
4525
+ const sighashScript = import_btc_signer4.OutScript.encode({ type: "pkh", hash: decoded.hash });
4526
+ const sighashType = import_btc_signer4.SigHash.ALL;
4527
+ const sighash2 = tx.preimageWitnessV0(inputIdx, sighashScript, sighashType, amount);
4528
+ const sig2 = signer.sign(sighash2, "ecdsa");
4529
+ const sigWithType = (0, import_utils7.concatBytes)(sig2, new Uint8Array([sighashType]));
4530
+ tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
4531
+ tx.finalize();
4532
+ return tx.hex;
4533
+ }
4534
+ const sighash = tx.preimageWitnessV1(inputIdx, [prevOutScript], import_btc_signer4.SigHash.DEFAULT, [amount]);
4535
+ const sig = signer.sign(sighash, "bip341");
4536
+ tx.updateInput(inputIdx, { tapKeySig: sig });
4537
+ tx.finalize();
4538
+ return tx.hex;
4539
+ }
2292
4540
  var Beacon = class {
2293
4541
  /**
2294
4542
  * The Beacon service configuration parsed from the DID Document.
@@ -2298,79 +4546,134 @@ var Beacon = class {
2298
4546
  this.service = service;
2299
4547
  }
2300
4548
  /**
2301
- * Shared PSBT construction + signing + broadcast helper used by all beacon types.
2302
- *
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.
4549
+ * Build + sign + broadcast a singleton beacon signal transaction. The beacon
4550
+ * address's script kind (P2PKH / P2WPKH / P2TR) is detected automatically
4551
+ * and the input is constructed and signed accordingly.
2312
4552
  *
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.
4553
+ * Composed from the three extracted phases ({@link buildSinglePartyTx},
4554
+ * {@link signSinglePartyTx}, {@link broadcastRawTx}) so each piece can be exercised
4555
+ * in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead
4556
+ * the multi-party path can't share the signing phase, but the tx-construction
4557
+ * plumbing (UTXO fetch + OP_RETURN output + change output) is shared.
2317
4558
  *
2318
4559
  * @param signalBytes 32-byte payload to embed in OP_RETURN.
2319
- * @param secretKey Secret key used to sign the spending input.
4560
+ * @param signer Signer used to sign the spending input.
2320
4561
  * @param bitcoin Bitcoin network connection.
2321
4562
  * @param options Broadcast options (fee estimator, etc.).
2322
4563
  * @returns The txid of the broadcast transaction.
2323
- * @throws {BeaconError} if the address is unfunded or no UTXO is available.
4564
+ * @throws {BeaconError} if the address is unfunded, no UTXO is available, or fee exceeds value.
2324
4565
  */
2325
- async buildSignAndBroadcast(signalBytes, secretKey, bitcoin, options) {
4566
+ async buildSignAndBroadcast(signalBytes, signer, bitcoin, options) {
2326
4567
  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) {
4568
+ const beaconAddress = this.service.serviceEndpoint.replace("bitcoin:", "");
4569
+ const { utxo, prevTxBytes } = await fetchSpendableUtxo(beaconAddress, bitcoin);
4570
+ const plan = await this.buildSinglePartyTx({
4571
+ signalBytes,
4572
+ beaconAddress,
4573
+ utxo,
4574
+ prevTxBytes,
4575
+ signer,
4576
+ bitcoin,
4577
+ feeEstimator
4578
+ });
4579
+ const signedHex = await this.signSinglePartyTx(plan, signer);
4580
+ return this.broadcastRawTx(bitcoin, signedHex);
4581
+ }
4582
+ /**
4583
+ * Build an unsigned singleton beacon tx ready for {@link signSinglePartyTx}.
4584
+ *
4585
+ * Detects the beacon address script kind (P2PKH / P2WPKH / P2TR) and configures
4586
+ * the input accordingly. Validates that the signer's pubkey produces the beacon
4587
+ * address under that script kind — without this check, a misconfigured caller
4588
+ * would burn a real UTXO on a tx that fails at broadcast. Fees are computed from
4589
+ * the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant, avoiding any probe-sign
4590
+ * round-trip.
4591
+ */
4592
+ async buildSinglePartyTx(opts) {
4593
+ const network = opts.bitcoin.data;
4594
+ const pubkey = opts.signer.publicKey;
4595
+ const kind = detectSingletonScriptKind(opts.beaconAddress, network);
4596
+ const derivedAddress = deriveSingletonAddress(kind, pubkey, network);
4597
+ if (derivedAddress !== opts.beaconAddress) {
2340
4598
  throw new BeaconError(
2341
- "Beacon bitcoin address unfunded or utxos unconfirmed.",
2342
- "UNFUNDED_BEACON_ADDRESS",
2343
- { bitcoinAddress }
4599
+ `Signer pubkey produces ${kind.toUpperCase()} address "${derivedAddress}", but beacon address is "${opts.beaconAddress}".`,
4600
+ "SIGNER_KEY_MISMATCH",
4601
+ { kind, address: opts.beaconAddress, derivedAddress }
2344
4602
  );
2345
4603
  }
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" })
2351
- };
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) {
4604
+ const feeSats = await opts.feeEstimator.estimateFee(SINGLETON_BEACON_TX_VSIZE[kind]);
4605
+ const amount = BigInt(opts.utxo.value);
4606
+ if (amount <= feeSats) {
2361
4607
  throw new BeaconError(
2362
- `UTXO value (${utxo.value}) insufficient to cover fee (${fee}).`,
4608
+ `UTXO value (${opts.utxo.value}) insufficient to cover fee (${feeSats}).`,
2363
4609
  "INSUFFICIENT_FUNDS",
2364
- { bitcoinAddress, utxoValue: utxo.value, fee: fee.toString() }
4610
+ { address: opts.beaconAddress, valueSats: opts.utxo.value, feeSats }
2365
4611
  );
2366
4612
  }
2367
- const signedTxHex = build(fee).signInput(0, signer).finalizeAllInputs().extractTransaction().toHex();
2368
- return bitcoin.rest.transaction.send(signedTxHex);
4613
+ const tx = new import_btc_signer4.Transaction({ allowUnknownOutputs: true });
4614
+ let prevOutScript;
4615
+ if (kind === "p2pkh") {
4616
+ prevOutScript = (0, import_btc_signer4.p2pkh)(pubkey, network).script;
4617
+ tx.addInput({
4618
+ txid: opts.utxo.txid,
4619
+ index: opts.utxo.vout,
4620
+ nonWitnessUtxo: opts.prevTxBytes
4621
+ });
4622
+ } else if (kind === "p2wpkh") {
4623
+ prevOutScript = (0, import_btc_signer4.p2wpkh)(pubkey, network).script;
4624
+ tx.addInput({
4625
+ txid: opts.utxo.txid,
4626
+ index: opts.utxo.vout,
4627
+ nonWitnessUtxo: opts.prevTxBytes,
4628
+ witnessUtxo: { amount, script: prevOutScript }
4629
+ });
4630
+ } else {
4631
+ const internalKey = pubkey.slice(1, 33);
4632
+ prevOutScript = (0, import_btc_signer4.p2tr)(internalKey, void 0, network).script;
4633
+ tx.addInput({
4634
+ txid: opts.utxo.txid,
4635
+ index: opts.utxo.vout,
4636
+ nonWitnessUtxo: opts.prevTxBytes,
4637
+ witnessUtxo: { amount, script: prevOutScript },
4638
+ tapInternalKey: internalKey
4639
+ });
4640
+ }
4641
+ tx.addOutputAddress(opts.beaconAddress, amount - feeSats, network);
4642
+ tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
4643
+ return {
4644
+ tx,
4645
+ prevOutScripts: [prevOutScript],
4646
+ prevOutValues: [amount],
4647
+ beaconAddress: opts.beaconAddress,
4648
+ utxo: opts.utxo,
4649
+ feeSats,
4650
+ scriptKind: kind
4651
+ };
4652
+ }
4653
+ /**
4654
+ * Sign + finalize the unsigned single-party tx and return its raw hex.
4655
+ * Dispatches to the correct signing primitive based on `plan.scriptKind`.
4656
+ */
4657
+ async signSinglePartyTx(plan, signer) {
4658
+ return signSingletonInput(
4659
+ plan.tx,
4660
+ 0,
4661
+ plan.scriptKind,
4662
+ signer,
4663
+ plan.prevOutScripts[0],
4664
+ plan.prevOutValues[0]
4665
+ );
4666
+ }
4667
+ /**
4668
+ * Broadcast raw transaction hex via the Bitcoin REST endpoint. Returns the txid.
4669
+ */
4670
+ async broadcastRawTx(bitcoin, rawHex) {
4671
+ return bitcoin.rest.transaction.send(rawHex);
2369
4672
  }
2370
4673
  };
2371
4674
 
2372
4675
  // src/core/beacon/cas-beacon.ts
2373
- var import_common8 = require("@did-btcr2/common");
4676
+ var import_common13 = require("@did-btcr2/common");
2374
4677
  var CASBeacon = class extends Beacon {
2375
4678
  /**
2376
4679
  * Creates an instance of CASBeacon.
@@ -2411,7 +4714,7 @@ var CASBeacon = class extends Beacon {
2411
4714
  if (!updateHashEncoded) {
2412
4715
  continue;
2413
4716
  }
2414
- const updateHash = (0, import_common8.encode)((0, import_common8.decode)(updateHashEncoded, "base64urlnopad"), "hex");
4717
+ const updateHash = (0, import_common13.encode)((0, import_common13.decode)(updateHashEncoded, "base64urlnopad"), "hex");
2415
4718
  const signedUpdate = sidecar.updateMap.get(updateHash);
2416
4719
  if (!signedUpdate) {
2417
4720
  needs.push({
@@ -2434,19 +4737,19 @@ var CASBeacon = class extends Beacon {
2434
4737
  * and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
2435
4738
  *
2436
4739
  * @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
2437
- * @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
4740
+ * @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
2438
4741
  * @param {BitcoinConnection} bitcoin The Bitcoin network connection.
2439
4742
  * @param {CASBroadcastOptions} [options] Optional broadcast configuration, including a
2440
4743
  * `casPublish` callback to publish the announcement off-chain and a `feeEstimator`.
2441
4744
  * @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
2442
4745
  * @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
2443
4746
  */
2444
- async broadcastSignal(signedUpdate, secretKey, bitcoin, options) {
4747
+ async broadcastSignal(signedUpdate, signer, bitcoin, options) {
2445
4748
  const did = this.service.id.split("#")[0];
2446
- const updateHash = (0, import_common8.canonicalHash)(signedUpdate);
4749
+ const updateHash = (0, import_common13.canonicalHash)(signedUpdate);
2447
4750
  const casAnnouncement = { [did]: updateHash };
2448
- const announcementHash = (0, import_common8.hash)((0, import_common8.canonicalize)(casAnnouncement));
2449
- await this.buildSignAndBroadcast(announcementHash, secretKey, bitcoin, options);
4751
+ const announcementHash = (0, import_common13.hash)((0, import_common13.canonicalize)(casAnnouncement));
4752
+ await this.buildSignAndBroadcast(announcementHash, signer, bitcoin, options);
2450
4753
  if (options?.casPublish) {
2451
4754
  await options.casPublish(casAnnouncement);
2452
4755
  }
@@ -2455,10 +4758,10 @@ var CASBeacon = class extends Beacon {
2455
4758
  };
2456
4759
 
2457
4760
  // src/core/beacon/factory.ts
2458
- var import_common11 = require("@did-btcr2/common");
4761
+ var import_common16 = require("@did-btcr2/common");
2459
4762
 
2460
4763
  // src/core/beacon/singleton-beacon.ts
2461
- var import_common9 = require("@did-btcr2/common");
4764
+ var import_common14 = require("@did-btcr2/common");
2462
4765
  var SingletonBeacon = class extends Beacon {
2463
4766
  /**
2464
4767
  * Creates an instance of SingletonBeacon.
@@ -2499,23 +4802,23 @@ var SingletonBeacon = class extends Beacon {
2499
4802
  * {@link Beacon.buildSignAndBroadcast}.
2500
4803
  *
2501
4804
  * @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
2502
- * @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
4805
+ * @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
2503
4806
  * @param {BitcoinConnection} bitcoin The Bitcoin network connection.
2504
4807
  * @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
2505
4808
  * @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
2506
4809
  * @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
2507
4810
  */
2508
- async broadcastSignal(signedUpdate, secretKey, bitcoin, options) {
2509
- const signalBytes = (0, import_common9.hash)((0, import_common9.canonicalize)(signedUpdate));
2510
- await this.buildSignAndBroadcast(signalBytes, secretKey, bitcoin, options);
4811
+ async broadcastSignal(signedUpdate, signer, bitcoin, options) {
4812
+ const signalBytes = (0, import_common14.hash)((0, import_common14.canonicalize)(signedUpdate));
4813
+ await this.buildSignAndBroadcast(signalBytes, signer, bitcoin, options);
2511
4814
  return signedUpdate;
2512
4815
  }
2513
4816
  };
2514
4817
 
2515
4818
  // src/core/beacon/smt-beacon.ts
2516
- var import_common10 = require("@did-btcr2/common");
4819
+ var import_common15 = require("@did-btcr2/common");
2517
4820
  var import_smt3 = require("@did-btcr2/smt");
2518
- var import_utils6 = require("@noble/hashes/utils");
4821
+ var import_utils8 = require("@noble/hashes/utils");
2519
4822
  var SMTBeacon = class extends Beacon {
2520
4823
  /**
2521
4824
  * Creates an instance of SMTBeacon.
@@ -2593,20 +4896,20 @@ var SMTBeacon = class extends Beacon {
2593
4896
  * signing, and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
2594
4897
  *
2595
4898
  * @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
2596
- * @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
4899
+ * @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
2597
4900
  * @param {BitcoinConnection} bitcoin The Bitcoin network connection.
2598
4901
  * @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
2599
4902
  * @return {Promise<SignedBTCR2Update>} The signed update that was broadcast.
2600
4903
  * @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
2601
4904
  */
2602
- async broadcastSignal(signedUpdate, secretKey, bitcoin, options) {
4905
+ async broadcastSignal(signedUpdate, signer, bitcoin, options) {
2603
4906
  const did = this.service.id.split("#")[0];
2604
- const canonicalBytes = new TextEncoder().encode((0, import_common10.canonicalize)(signedUpdate));
2605
- const nonce = (0, import_utils6.randomBytes)(32);
4907
+ const canonicalBytes = new TextEncoder().encode((0, import_common15.canonicalize)(signedUpdate));
4908
+ const nonce = (0, import_utils8.randomBytes)(32);
2606
4909
  const tree = new import_smt3.BTCR2MerkleTree();
2607
4910
  tree.addEntries([{ did, nonce, signedUpdate: canonicalBytes }]);
2608
4911
  tree.finalize();
2609
- await this.buildSignAndBroadcast(tree.rootHash, secretKey, bitcoin, options);
4912
+ await this.buildSignAndBroadcast(tree.rootHash, signer, bitcoin, options);
2610
4913
  return signedUpdate;
2611
4914
  }
2612
4915
  };
@@ -2627,19 +4930,19 @@ var BeaconFactory = class {
2627
4930
  case "SMTBeacon":
2628
4931
  return new SMTBeacon(service);
2629
4932
  default:
2630
- throw new import_common11.MethodError("Invalid Beacon Type", "INVALID_BEACON_ERROR", service);
4933
+ throw new import_common16.MethodError("Invalid Beacon Type", "INVALID_BEACON_ERROR", service);
2631
4934
  }
2632
4935
  }
2633
4936
  };
2634
4937
 
2635
4938
  // src/core/beacon/signal-discovery.ts
2636
4939
  var import_bitcoin2 = require("@did-btcr2/bitcoin");
2637
- var import_common14 = require("@did-btcr2/common");
4940
+ var import_common18 = require("@did-btcr2/common");
2638
4941
 
2639
4942
  // src/core/beacon/utils.ts
2640
4943
  var import_bitcoin = require("@did-btcr2/bitcoin");
2641
- var import_common13 = require("@did-btcr2/common");
2642
- var import_bitcoinjs_lib5 = require("bitcoinjs-lib");
4944
+ var import_common17 = require("@did-btcr2/common");
4945
+ var import_btc_signer5 = require("@scure/btc-signer");
2643
4946
 
2644
4947
  // src/utils/appendix.ts
2645
4948
  var import_dids = require("@web5/dids");
@@ -3679,198 +5982,6 @@ var Appendix = class _Appendix {
3679
5982
  }
3680
5983
  };
3681
5984
 
3682
- // 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");
3686
- var Identifier = class _Identifier {
3687
- /**
3688
- * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-encoding | 3.2 did:btcr2 Identifier Encoding}.
3689
- *
3690
- * A did:btcr2 DID consists of a did:btcr2 prefix, followed by an id-bech32 value, which is a Bech32m encoding of:
3691
- * - the specification version;
3692
- * - the Bitcoin network identifier; and
3693
- * - either:
3694
- * - a key-value representing a secp256k1 public key; or
3695
- * - a hash-value representing the hash of an initiating external DID document.
3696
- *
3697
- * @param {KeyBytes | DocumentBytes} genesisBytes The genesis bytes (public key or document bytes).
3698
- * @param {DidCreateOptions} options The DID creation options.
3699
- * @returns {string} The new did:btcr2 identifier.
3700
- */
3701
- static encode(genesisBytes, options) {
3702
- 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 });
3705
- }
3706
- if (isNaN(version) || version > 1) {
3707
- throw new import_common12.IdentifierError('Expected "version" to be 1', import_common12.INVALID_DID, { version });
3708
- }
3709
- if (typeof network === "string" && !(network in import_common12.BitcoinNetworkNames)) {
3710
- throw new import_common12.IdentifierError('Invalid "network" name', import_common12.INVALID_DID, { network });
3711
- }
3712
- if (typeof network === "number" && (network < 0 || network > 8)) {
3713
- throw new import_common12.IdentifierError('Invalid "network" number', import_common12.INVALID_DID, { network });
3714
- }
3715
- if (idType === "KEY") {
3716
- try {
3717
- new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
3718
- } catch {
3719
- throw new import_common12.IdentifierError(
3720
- 'Expected "genesisBytes" to be a valid compressed secp256k1 public key',
3721
- import_common12.INVALID_DID,
3722
- { genesisBytes }
3723
- );
3724
- }
3725
- }
3726
- const hrp = idType === "KEY" ? "k" : "x";
3727
- const nibbles = [];
3728
- const fCount = Math.floor((version - 1) / 15);
3729
- for (let i = 0; i < fCount; i++) {
3730
- nibbles.push(15);
3731
- }
3732
- nibbles.push((version - 1) % 15);
3733
- if (typeof network === "string") {
3734
- nibbles.push(import_common12.BitcoinNetworkNames[network]);
3735
- } else if (typeof network === "number") {
3736
- nibbles.push(network + 11);
3737
- }
3738
- if (nibbles.length % 2 !== 0) {
3739
- nibbles.push(0);
3740
- }
3741
- if (fCount !== 0) {
3742
- for (const index in Array.from({ length: nibbles.length / 2 - 1 })) {
3743
- throw new import_common12.IdentifierError("Not implemented", "NOT_IMPLEMENTED", { index });
3744
- }
3745
- }
3746
- const dataBytes = new Uint8Array([nibbles[2 * 0] << 4 | nibbles[2 * 0 + 1], ...genesisBytes]);
3747
- return `did:btcr2:${import_base5.bech32m.encodeFromBytes(hrp, dataBytes)}`;
3748
- }
3749
- /**
3750
- * Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}.
3751
- * @param {string} identifier The BTCR2 DID to be parsed
3752
- * @returns {DidComponents} The parsed identifier components. See {@link DidComponents} for details.
3753
- * @throws {DidError} if an error occurs while parsing the identifier
3754
- * @throws {DidErrorCode.InvalidDid} if identifier is invalid
3755
- * @throws {DidErrorCode.MethodNotSupported} if the method is not supported
3756
- */
3757
- static decode(identifier) {
3758
- const components = identifier.split(":");
3759
- if (components.length !== 3) {
3760
- throw new import_common12.IdentifierError(`Invalid did: ${identifier}`, import_common12.INVALID_DID, { identifier });
3761
- }
3762
- const [scheme, method, encoded] = components;
3763
- if (scheme !== "did") {
3764
- throw new import_common12.IdentifierError(`Invalid did: ${identifier}`, import_common12.INVALID_DID, { identifier });
3765
- }
3766
- if (method !== "btcr2") {
3767
- throw new import_common12.IdentifierError(`Invalid did method: ${method}`, import_common12.METHOD_NOT_SUPPORTED, { identifier });
3768
- }
3769
- if (!encoded) {
3770
- throw new import_common12.IdentifierError(`Invalid method-specific id: ${identifier}`, import_common12.INVALID_DID, { identifier });
3771
- }
3772
- const { prefix: hrp, bytes: dataBytes } = import_base5.bech32m.decodeToBytes(encoded);
3773
- if (!["x", "k"].includes(hrp)) {
3774
- throw new import_common12.IdentifierError(`Invalid hrp: ${hrp}`, import_common12.INVALID_DID, { identifier });
3775
- }
3776
- if (!dataBytes) {
3777
- throw new import_common12.IdentifierError(`Failed to decode id: ${encoded}`, import_common12.INVALID_DID, { identifier });
3778
- }
3779
- const idType = hrp === "k" ? "KEY" : "EXTERNAL";
3780
- let version = 1;
3781
- let byteIndex = 0;
3782
- let nibblesConsumed = 0;
3783
- let currentByte = dataBytes[byteIndex];
3784
- let versionNibble = currentByte >>> 4;
3785
- while (versionNibble === 15) {
3786
- version += 15;
3787
- if (nibblesConsumed % 2 === 0) {
3788
- versionNibble = currentByte & 15;
3789
- } else {
3790
- currentByte = dataBytes[++byteIndex];
3791
- versionNibble = currentByte >>> 4;
3792
- }
3793
- nibblesConsumed += 1;
3794
- if (version > 1) {
3795
- throw new import_common12.IdentifierError(`Invalid version: ${version}`, import_common12.INVALID_DID, { identifier });
3796
- }
3797
- }
3798
- version += versionNibble;
3799
- nibblesConsumed += 1;
3800
- let networkValue = nibblesConsumed % 2 === 0 ? dataBytes[++byteIndex] >>> 4 : currentByte & 15;
3801
- nibblesConsumed += 1;
3802
- let network = import_common12.BitcoinNetworkNames[networkValue];
3803
- if (!network) {
3804
- if (networkValue >= 8 && networkValue <= 15) {
3805
- network = networkValue - 11;
3806
- } else {
3807
- throw new import_common12.IdentifierError(`Invalid did: ${identifier}`, import_common12.INVALID_DID, { identifier });
3808
- }
3809
- }
3810
- if (nibblesConsumed % 2 === 1) {
3811
- const fillerNibble = currentByte & 15;
3812
- if (fillerNibble !== 0) {
3813
- throw new import_common12.IdentifierError(`Invalid did: ${identifier}`, import_common12.INVALID_DID, { identifier });
3814
- }
3815
- }
3816
- const genesisBytes = dataBytes.slice(byteIndex + 1);
3817
- if (idType === "KEY") {
3818
- try {
3819
- new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
3820
- } catch {
3821
- throw new import_common12.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common12.INVALID_DID, { identifier });
3822
- }
3823
- }
3824
- return { idType, hrp, version, network, genesisBytes };
3825
- }
3826
- /**
3827
- * Generates a new did:btcr2 identifier based on a newly generated key pair.
3828
- * @returns {string} The new did:btcr2 identifier.
3829
- */
3830
- static generate() {
3831
- const keyPair = import_keypair3.SchnorrKeyPair.generate();
3832
- const did = this.encode(
3833
- keyPair.publicKey.compressed,
3834
- {
3835
- idType: "KEY",
3836
- version: 1,
3837
- network: "regtest"
3838
- }
3839
- );
3840
- return { keyPair: keyPair.exportJSON(), did };
3841
- }
3842
- /**
3843
- * Extracts the compressed secp256k1 public key from a KEY-type did:btcr2 identifier.
3844
- * @param {string} did The did:btcr2 identifier to extract the public key from.
3845
- * @returns {CompressedSecp256k1PublicKey} The compressed public key.
3846
- * @throws {IdentifierError} If the DID is EXTERNAL type (genesis bytes are a hash, not a pubkey).
3847
- */
3848
- static getPublicKey(did) {
3849
- const { idType, genesisBytes } = _Identifier.decode(did);
3850
- if (idType !== "KEY") {
3851
- throw new import_common12.IdentifierError(
3852
- `Cannot extract public key from EXTERNAL DID: ${did}. EXTERNAL DIDs encode a document hash, not a public key.`,
3853
- import_common12.INVALID_DID,
3854
- { did, idType }
3855
- );
3856
- }
3857
- return new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
3858
- }
3859
- /**
3860
- * Validates a did:btcr2 identifier.
3861
- * @param {string} identifier The did:btcr2 identifier to validate.
3862
- * @returns {boolean} True if the identifier is valid, false otherwise.
3863
- */
3864
- static isValid(identifier) {
3865
- try {
3866
- this.decode(identifier);
3867
- return true;
3868
- } catch {
3869
- return false;
3870
- }
3871
- }
3872
- };
3873
-
3874
5985
  // src/core/beacon/utils.ts
3875
5986
  var BeaconUtils = class {
3876
5987
  /**
@@ -3881,7 +5992,7 @@ var BeaconUtils = class {
3881
5992
  */
3882
5993
  static parseBitcoinAddress(uri) {
3883
5994
  if (!uri.startsWith("bitcoin:")) {
3884
- throw new import_common13.MethodError("Invalid Bitcoin URI format", "BEACON_SERVICE_ERROR", { uri });
5995
+ throw new import_common17.MethodError("Invalid Bitcoin URI format", "BEACON_SERVICE_ERROR", { uri });
3885
5996
  }
3886
5997
  return uri.replace("bitcoin:", "").split("?")[0];
3887
5998
  }
@@ -3939,7 +6050,8 @@ var BeaconUtils = class {
3939
6050
  const network = (0, import_bitcoin.getNetwork)(components.network);
3940
6051
  const pubkey = components.genesisBytes;
3941
6052
  const id = `${did}#initial${addressType.toUpperCase()}`;
3942
- const serviceEndpoint = `bitcoin:${import_bitcoinjs_lib5.payments[addressType]({ pubkey, network }).address}`;
6053
+ 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;
6054
+ const serviceEndpoint = `bitcoin:${address}`;
3943
6055
  return { id, type: beaconType, serviceEndpoint };
3944
6056
  } catch (error) {
3945
6057
  throw new BeaconError(
@@ -3957,27 +6069,27 @@ var BeaconUtils = class {
3957
6069
  */
3958
6070
  static generateBeaconServices({ id, publicKey, network, beaconType }) {
3959
6071
  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");
6072
+ const p2pkhAddr = (0, import_btc_signer5.p2pkh)(publicKey, network).address;
6073
+ const p2wpkhAddr = (0, import_btc_signer5.p2wpkh)(publicKey, network).address;
6074
+ const p2trAddr = (0, import_btc_signer5.p2tr)(publicKey.slice(1, 33), void 0, network).address;
6075
+ if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
6076
+ throw new import_common17.DidMethodError("Failed to generate bitcoin addresses");
3965
6077
  }
3966
6078
  return [
3967
6079
  {
3968
6080
  id: `${id}#initialP2PKH`,
3969
6081
  type: beaconType,
3970
- serviceEndpoint: `bitcoin:${p2pkh}`
6082
+ serviceEndpoint: `bitcoin:${p2pkhAddr}`
3971
6083
  },
3972
6084
  {
3973
6085
  id: `${id}#initialP2WPKH`,
3974
6086
  type: beaconType,
3975
- serviceEndpoint: `bitcoin:${p2wpkh}`
6087
+ serviceEndpoint: `bitcoin:${p2wpkhAddr}`
3976
6088
  },
3977
6089
  {
3978
6090
  id: `${id}#initialP2TR`,
3979
6091
  type: beaconType,
3980
- serviceEndpoint: `bitcoin:${p2tr}`
6092
+ serviceEndpoint: `bitcoin:${p2trAddr}`
3981
6093
  }
3982
6094
  ];
3983
6095
  } catch (error) {
@@ -4077,7 +6189,7 @@ var BeaconSignalDiscovery = class {
4077
6189
  }
4078
6190
  const rpc = bitcoin.rpc;
4079
6191
  if (!rpc) {
4080
- throw new import_common14.ResolveError("RPC connection is not available", "RPC_CONNECTION_ERROR", bitcoin);
6192
+ throw new import_common18.ResolveError("RPC connection is not available", "RPC_CONNECTION_ERROR", bitcoin);
4081
6193
  }
4082
6194
  const targetHeight = await rpc.getBlockCount();
4083
6195
  const beaconServicesMap = BeaconUtils.getBeaconServicesMap(beaconServices);
@@ -4148,26 +6260,24 @@ var BeaconSignalDiscovery = class {
4148
6260
 
4149
6261
  // src/core/resolver.ts
4150
6262
  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");
6263
+ var import_common22 = require("@did-btcr2/common");
6264
+ var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
6265
+ var import_keypair6 = require("@did-btcr2/keypair");
4154
6266
 
4155
6267
  // src/did-btcr2.ts
4156
- var import_common17 = require("@did-btcr2/common");
6268
+ var import_common21 = require("@did-btcr2/common");
4157
6269
  var import_dids2 = require("@web5/dids");
4158
- var ecc = __toESM(require("@bitcoinerlab/secp256k1"), 1);
4159
- var import_bitcoinjs_lib7 = require("bitcoinjs-lib");
4160
6270
 
4161
6271
  // src/core/updater.ts
4162
- var import_common16 = require("@did-btcr2/common");
4163
- var import_cryptosuite = require("@did-btcr2/cryptosuite");
6272
+ var import_common20 = require("@did-btcr2/common");
6273
+ var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
4164
6274
 
4165
6275
  // src/utils/did-document.ts
4166
6276
  var import_bitcoin3 = require("@did-btcr2/bitcoin");
4167
- var import_common15 = require("@did-btcr2/common");
4168
- var import_keypair4 = require("@did-btcr2/keypair");
4169
- var import_utils8 = require("@web5/dids/utils");
4170
- var import_bitcoinjs_lib6 = require("bitcoinjs-lib");
6277
+ var import_common19 = require("@did-btcr2/common");
6278
+ var import_keypair5 = require("@did-btcr2/keypair");
6279
+ var import_utils10 = require("@web5/dids/utils");
6280
+ var import_btc_signer6 = require("@scure/btc-signer");
4171
6281
  var BTCR2_DID_DOCUMENT_CONTEXT = [
4172
6282
  "https://www.w3.org/ns/did/v1.1",
4173
6283
  "https://btcr2.dev/context/v1"
@@ -4208,20 +6318,20 @@ var DidDocument = class _DidDocument {
4208
6318
  deactivated;
4209
6319
  constructor(document) {
4210
6320
  if (!document.id) {
4211
- throw new import_common15.DidDocumentError("DID Document must have an id", import_common15.INVALID_DID_DOCUMENT, document);
6321
+ throw new import_common19.DidDocumentError("DID Document must have an id", import_common19.INVALID_DID_DOCUMENT, document);
4212
6322
  }
4213
- const idType = document.id.includes("k1") ? import_common15.IdentifierTypes.KEY : import_common15.IdentifierTypes.EXTERNAL;
6323
+ const idType = document.id.includes("k1") ? import_common19.IdentifierTypes.KEY : import_common19.IdentifierTypes.EXTERNAL;
4214
6324
  const isGenesis = document.id === ID_PLACEHOLDER_VALUE;
4215
6325
  const { id, verificationMethod: vm, service } = document;
4216
6326
  if (!isGenesis) {
4217
6327
  if (!_DidDocument.isValidId(id)) {
4218
- throw new import_common15.DidDocumentError(`Invalid id: ${id}`, import_common15.INVALID_DID_DOCUMENT, document);
6328
+ throw new import_common19.DidDocumentError(`Invalid id: ${id}`, import_common19.INVALID_DID_DOCUMENT, document);
4219
6329
  }
4220
6330
  if (!_DidDocument.isValidVerificationMethods(vm)) {
4221
- throw new import_common15.DidDocumentError("Invalid verificationMethod: " + vm, import_common15.INVALID_DID_DOCUMENT, document);
6331
+ throw new import_common19.DidDocumentError("Invalid verificationMethod: " + vm, import_common19.INVALID_DID_DOCUMENT, document);
4222
6332
  }
4223
6333
  if (!_DidDocument.isValidServices(service)) {
4224
- throw new import_common15.DidDocumentError("Invalid service: " + service, import_common15.INVALID_DID_DOCUMENT, document);
6334
+ throw new import_common19.DidDocumentError("Invalid service: " + service, import_common19.INVALID_DID_DOCUMENT, document);
4225
6335
  }
4226
6336
  }
4227
6337
  this.id = document.id;
@@ -4231,7 +6341,7 @@ var DidDocument = class _DidDocument {
4231
6341
  "https://www.w3.org/ns/did/v1.1",
4232
6342
  "https://btcr2.dev/context/v1"
4233
6343
  ];
4234
- if (idType === import_common15.IdentifierTypes.KEY) {
6344
+ if (idType === import_common19.IdentifierTypes.KEY) {
4235
6345
  const keyRef = `${this.id}#initialKey`;
4236
6346
  this.authentication = document.authentication || [keyRef];
4237
6347
  this.assertionMethod = document.assertionMethod || [keyRef];
@@ -4317,19 +6427,19 @@ var DidDocument = class _DidDocument {
4317
6427
  */
4318
6428
  static isValid(didDocument) {
4319
6429
  if (!this.isValidContext(didDocument?.["@context"])) {
4320
- throw new import_common15.DidDocumentError('Invalid "@context"', import_common15.INVALID_DID_DOCUMENT, didDocument);
6430
+ throw new import_common19.DidDocumentError('Invalid "@context"', import_common19.INVALID_DID_DOCUMENT, didDocument);
4321
6431
  }
4322
6432
  if (!this.isValidId(didDocument?.id)) {
4323
- throw new import_common15.DidDocumentError('Invalid "id"', import_common15.INVALID_DID_DOCUMENT, didDocument);
6433
+ throw new import_common19.DidDocumentError('Invalid "id"', import_common19.INVALID_DID_DOCUMENT, didDocument);
4324
6434
  }
4325
6435
  if (!this.isValidVerificationMethods(didDocument?.verificationMethod)) {
4326
- throw new import_common15.DidDocumentError('Invalid "verificationMethod"', import_common15.INVALID_DID_DOCUMENT, didDocument);
6436
+ throw new import_common19.DidDocumentError('Invalid "verificationMethod"', import_common19.INVALID_DID_DOCUMENT, didDocument);
4327
6437
  }
4328
6438
  if (!this.isValidServices(didDocument?.service)) {
4329
- throw new import_common15.DidDocumentError('Invalid "service"', import_common15.INVALID_DID_DOCUMENT, didDocument);
6439
+ throw new import_common19.DidDocumentError('Invalid "service"', import_common19.INVALID_DID_DOCUMENT, didDocument);
4330
6440
  }
4331
6441
  if (!this.isValidVerificationRelationships(didDocument)) {
4332
- throw new import_common15.DidDocumentError("Invalid verification relationships", import_common15.INVALID_DID_DOCUMENT, didDocument);
6442
+ throw new import_common19.DidDocumentError("Invalid verification relationships", import_common19.INVALID_DID_DOCUMENT, didDocument);
4333
6443
  }
4334
6444
  return true;
4335
6445
  }
@@ -4376,7 +6486,7 @@ var DidDocument = class _DidDocument {
4376
6486
  * @returns {boolean} True if the services are valid.
4377
6487
  */
4378
6488
  static isValidServices(service) {
4379
- return Array.isArray(service) && service.every(import_utils8.isDidService);
6489
+ return Array.isArray(service) && service.every(import_utils10.isDidService);
4380
6490
  }
4381
6491
  /**
4382
6492
  * Validates verification relationships (authentication, assertionMethod, capabilityInvocation, capabilityDelegation).
@@ -4419,16 +6529,16 @@ var DidDocument = class _DidDocument {
4419
6529
  */
4420
6530
  validateGenesis() {
4421
6531
  if (this.id !== ID_PLACEHOLDER_VALUE) {
4422
- throw new import_common15.DidDocumentError("Invalid GenesisDocument ID", import_common15.INVALID_DID_DOCUMENT, this);
6532
+ throw new import_common19.DidDocumentError("Invalid GenesisDocument ID", import_common19.INVALID_DID_DOCUMENT, this);
4423
6533
  }
4424
6534
  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);
6535
+ throw new import_common19.DidDocumentError("Invalid GenesisDocument verificationMethod", import_common19.INVALID_DID_DOCUMENT, this);
4426
6536
  }
4427
6537
  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);
6538
+ throw new import_common19.DidDocumentError("Invalid GenesisDocument service", import_common19.INVALID_DID_DOCUMENT, this);
4429
6539
  }
4430
6540
  if (!_DidDocument.isValidVerificationRelationships(this)) {
4431
- throw new import_common15.DidDocumentError("Invalid GenesisDocument assertionMethod", import_common15.INVALID_DID_DOCUMENT, this);
6541
+ throw new import_common19.DidDocumentError("Invalid GenesisDocument assertionMethod", import_common19.INVALID_DID_DOCUMENT, this);
4432
6542
  }
4433
6543
  return true;
4434
6544
  }
@@ -4438,7 +6548,7 @@ var DidDocument = class _DidDocument {
4438
6548
  */
4439
6549
  toIntermediate() {
4440
6550
  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);
6551
+ throw new import_common19.DidDocumentError("Cannot convert a key identifier to an intermediate document", import_common19.INVALID_DID_DOCUMENT, this);
4442
6552
  }
4443
6553
  return new GenesisDocument(this);
4444
6554
  }
@@ -4468,7 +6578,7 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
4468
6578
  * @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
4469
6579
  */
4470
6580
  static fromDidDocument(didDocument) {
4471
- const intermediateDocument = import_common15.JSONUtils.cloneReplace(didDocument, DID_REGEX, ID_PLACEHOLDER_VALUE);
6581
+ const intermediateDocument = import_common19.JSONUtils.cloneReplace(didDocument, DID_REGEX, ID_PLACEHOLDER_VALUE);
4472
6582
  return new _GenesisDocument(intermediateDocument);
4473
6583
  }
4474
6584
  /**
@@ -4487,9 +6597,9 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
4487
6597
  * @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
4488
6598
  */
4489
6599
  static fromPublicKey(publicKey, network) {
4490
- const pk = new import_keypair4.CompressedSecp256k1PublicKey(publicKey);
6600
+ const pk = new import_keypair5.CompressedSecp256k1PublicKey(publicKey);
4491
6601
  const id = ID_PLACEHOLDER_VALUE;
4492
- const address = import_bitcoinjs_lib6.payments.p2pkh({ pubkey: pk.compressed, network: (0, import_bitcoin3.getNetwork)(network) })?.address;
6602
+ const address = (0, import_btc_signer6.p2pkh)(pk.compressed, (0, import_bitcoin3.getNetwork)(network)).address;
4493
6603
  const services = [{
4494
6604
  id: `${id}#service-0`,
4495
6605
  serviceEndpoint: `bitcoin:${address}`,
@@ -4525,20 +6635,18 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
4525
6635
  * @returns {Bytes} The genesis bytes.
4526
6636
  */
4527
6637
  static toGenesisBytes(genesisDocument) {
4528
- return (0, import_common15.hash)((0, import_common15.canonicalize)(genesisDocument));
6638
+ return (0, import_common19.hash)((0, import_common19.canonicalize)(genesisDocument));
4529
6639
  }
4530
6640
  };
4531
6641
 
4532
6642
  // src/core/updater.ts
4533
6643
  var Updater = class _Updater {
4534
- #phase = "Construct" /* Construct */;
6644
+ #state = { phase: "Construct" };
4535
6645
  #sourceDocument;
4536
6646
  #patches;
4537
6647
  #sourceVersionId;
4538
6648
  #verificationMethod;
4539
6649
  #beaconService;
4540
- #unsignedUpdate = null;
4541
- #signedUpdate = null;
4542
6650
  /**
4543
6651
  * @internal — Use {@link DidBtcr2.update} to create instances.
4544
6652
  */
@@ -4572,19 +6680,26 @@ var Updater = class _Updater {
4572
6680
  patch: patches,
4573
6681
  targetHash: "",
4574
6682
  targetVersionId: sourceVersionId + 1,
4575
- sourceHash: (0, import_common16.canonicalHash)(sourceDocument)
6683
+ sourceHash: (0, import_common20.canonicalHash)(sourceDocument)
4576
6684
  };
4577
- const targetDocument = import_common16.JSONPatch.apply(sourceDocument, patches);
6685
+ const targetDocument = import_common20.JSONPatch.apply(sourceDocument, patches);
6686
+ if (targetDocument.id !== sourceDocument.id) {
6687
+ throw new import_common20.UpdateError(
6688
+ `Patches must not change the DID document id (source "${sourceDocument.id}" \u2192 target "${targetDocument.id}").`,
6689
+ import_common20.INVALID_DID_UPDATE,
6690
+ { sourceId: sourceDocument.id, targetId: targetDocument.id }
6691
+ );
6692
+ }
4578
6693
  try {
4579
6694
  DidDocument.isValid(targetDocument);
4580
6695
  } catch (error) {
4581
- throw new import_common16.UpdateError(
6696
+ throw new import_common20.UpdateError(
4582
6697
  "Error validating targetDocument: " + (error instanceof Error ? error.message : String(error)),
4583
- import_common16.INVALID_DID_UPDATE,
6698
+ import_common20.INVALID_DID_UPDATE,
4584
6699
  targetDocument
4585
6700
  );
4586
6701
  }
4587
- unsignedUpdate.targetHash = (0, import_common16.canonicalHash)(targetDocument);
6702
+ unsignedUpdate.targetHash = (0, import_common20.canonicalHash)(targetDocument);
4588
6703
  return unsignedUpdate;
4589
6704
  }
4590
6705
  /**
@@ -4593,13 +6708,28 @@ var Updater = class _Updater {
4593
6708
  * @param {string} did The did-btcr2 identifier to derive the root capability from.
4594
6709
  * @param {UnsignedBTCR2Update} unsignedUpdate The unsigned update to sign.
4595
6710
  * @param {DidVerificationMethod} verificationMethod The verification method for signing.
4596
- * @param {KeyBytes} secretKey The secret key bytes.
6711
+ * @param {Signer} signer Signer that produces the BIP-340 Schnorr signature.
4597
6712
  * @returns {SignedBTCR2Update} The signed update with a Data Integrity proof.
4598
6713
  */
4599
- static sign(did, unsignedUpdate, verificationMethod, secretKey) {
6714
+ static sign(did, unsignedUpdate, verificationMethod, signer) {
6715
+ if (!did.startsWith("did:btcr2:")) {
6716
+ throw new import_common20.UpdateError(
6717
+ `Expected a did:btcr2 identifier for the root capability; got "${did}".`,
6718
+ import_common20.INVALID_DID_UPDATE,
6719
+ { did }
6720
+ );
6721
+ }
4600
6722
  const controller = verificationMethod.controller;
4601
- const id = verificationMethod.id.slice(verificationMethod.id.indexOf("#"));
4602
- const multikey = import_cryptosuite.SchnorrMultikey.fromSecretKey(id, controller, secretKey);
6723
+ const hashIdx = verificationMethod.id.indexOf("#");
6724
+ if (hashIdx < 0) {
6725
+ throw new import_common20.UpdateError(
6726
+ `Verification method id must contain a fragment (e.g. "${verificationMethod.id}#initialKey"); got "${verificationMethod.id}".`,
6727
+ import_common20.INVALID_DID_UPDATE,
6728
+ { verificationMethodId: verificationMethod.id }
6729
+ );
6730
+ }
6731
+ const id = verificationMethod.id.slice(hashIdx);
6732
+ const multikey = import_cryptosuite2.SchnorrMultikey.fromSigner(id, controller, signer);
4603
6733
  const config = {
4604
6734
  "@context": [
4605
6735
  "https://w3id.org/security/v2",
@@ -4623,24 +6753,20 @@ var Updater = class _Updater {
4623
6753
  *
4624
6754
  * @param {BeaconService} beaconService The beacon service to broadcast through.
4625
6755
  * @param {SignedBTCR2Update} update The signed update to announce.
4626
- * @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
6756
+ * @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
4627
6757
  * @param {BitcoinConnection} bitcoin The Bitcoin network connection.
4628
6758
  * @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
4629
6759
  */
4630
- static async announce(beaconService, update, secretKey, bitcoin) {
6760
+ static async announce(beaconService, update, signer, bitcoin) {
4631
6761
  const beacon = BeaconFactory.establish(beaconService);
4632
- return beacon.broadcastSignal(update, secretKey, bitcoin);
6762
+ return beacon.broadcastSignal(update, signer, bitcoin);
4633
6763
  }
4634
- // ─── Private instance wrappers ─────────────────────────────────────────────
6764
+ // Private instance wrappers
4635
6765
  // Delegate to the public statics with bound instance fields for cleaner
4636
6766
  // advance/provide code.
4637
6767
  #construct() {
4638
6768
  return _Updater.construct(this.#sourceDocument, this.#patches, this.#sourceVersionId);
4639
6769
  }
4640
- #sign(secretKey) {
4641
- return _Updater.sign(this.#sourceDocument.id, this.#unsignedUpdate, this.#verificationMethod, secretKey);
4642
- }
4643
- // ─── State machine ─────────────────────────────────────────────────────────
4644
6770
  /**
4645
6771
  * Advance the state machine. Returns either:
4646
6772
  * - `{ status: 'action-required', needs }` — caller must provide data via {@link provide}
@@ -4648,30 +6774,30 @@ var Updater = class _Updater {
4648
6774
  */
4649
6775
  advance() {
4650
6776
  while (true) {
4651
- switch (this.#phase) {
6777
+ switch (this.#state.phase) {
4652
6778
  // Phase: Construct
4653
6779
  // Build the unsigned update from source doc + patches. Pure, synchronous.
4654
- case "Construct" /* Construct */: {
4655
- this.#unsignedUpdate = this.#construct();
4656
- this.#phase = "Sign" /* Sign */;
6780
+ case "Construct": {
6781
+ const unsignedUpdate = this.#construct();
6782
+ this.#state = { phase: "Sign", unsignedUpdate };
4657
6783
  continue;
4658
6784
  }
4659
6785
  // Phase: Sign
4660
6786
  // Emit NeedSigningKey — the caller supplies the secret key (or a KMS signature).
4661
- case "Sign" /* Sign */: {
6787
+ case "Sign": {
4662
6788
  return {
4663
6789
  status: "action-required",
4664
6790
  needs: [{
4665
6791
  kind: "NeedSigningKey",
4666
6792
  verificationMethodId: this.#verificationMethod.id,
4667
- unsignedUpdate: this.#unsignedUpdate
6793
+ unsignedUpdate: this.#state.unsignedUpdate
4668
6794
  }]
4669
6795
  };
4670
6796
  }
4671
6797
  // Phase: Fund
4672
6798
  // Emit NeedFunding with the beacon address. The caller checks UTXOs,
4673
6799
  // funds the address if needed, and provides to continue.
4674
- case "Fund" /* Fund */: {
6800
+ case "Fund": {
4675
6801
  const beaconAddress = this.#beaconService.serviceEndpoint.replace("bitcoin:", "");
4676
6802
  return {
4677
6803
  status: "action-required",
@@ -4685,21 +6811,21 @@ var Updater = class _Updater {
4685
6811
  // Phase: Broadcast
4686
6812
  // Emit NeedBroadcast with the signed update + beacon service. The caller performs
4687
6813
  // the actual on-chain announcement (or hands off to the aggregation protocol).
4688
- case "Broadcast" /* Broadcast */: {
6814
+ case "Broadcast": {
4689
6815
  return {
4690
6816
  status: "action-required",
4691
6817
  needs: [{
4692
6818
  kind: "NeedBroadcast",
4693
6819
  beaconService: this.#beaconService,
4694
- signedUpdate: this.#signedUpdate
6820
+ signedUpdate: this.#state.signedUpdate
4695
6821
  }]
4696
6822
  };
4697
6823
  }
4698
6824
  // Phase: Complete
4699
- case "Complete" /* Complete */: {
6825
+ case "Complete": {
4700
6826
  return {
4701
6827
  status: "complete",
4702
- result: { signedUpdate: this.#signedUpdate }
6828
+ result: { signedUpdate: this.#state.signedUpdate }
4703
6829
  };
4704
6830
  }
4705
6831
  }
@@ -4708,49 +6834,63 @@ var Updater = class _Updater {
4708
6834
  provide(need, data) {
4709
6835
  switch (need.kind) {
4710
6836
  case "NeedSigningKey": {
4711
- if (this.#phase !== "Sign" /* Sign */) {
4712
- throw new import_common16.UpdateError(
4713
- `Cannot provide NeedSigningKey: updater phase is ${this.#phase}, expected Sign.`,
4714
- import_common16.INVALID_DID_UPDATE,
4715
- { phase: this.#phase }
6837
+ if (this.#state.phase !== "Sign") {
6838
+ throw new import_common20.UpdateError(
6839
+ `Cannot provide NeedSigningKey: updater phase is ${this.#state.phase}, expected Sign.`,
6840
+ import_common20.INVALID_DID_UPDATE,
6841
+ { phase: this.#state.phase }
4716
6842
  );
4717
6843
  }
4718
6844
  if (!data) {
4719
- throw new import_common16.UpdateError(
4720
- "NeedSigningKey requires secret key bytes.",
4721
- import_common16.INVALID_DID_UPDATE
6845
+ throw new import_common20.UpdateError(
6846
+ "NeedSigningKey requires a Signer.",
6847
+ import_common20.INVALID_DID_UPDATE
4722
6848
  );
4723
6849
  }
4724
- if (!this.#unsignedUpdate) {
4725
- throw new import_common16.UpdateError(
4726
- "Internal error: unsigned update missing in Sign phase.",
4727
- import_common16.INVALID_DID_UPDATE
4728
- );
4729
- }
4730
- this.#signedUpdate = this.#sign(data);
4731
- this.#phase = "Fund" /* Fund */;
6850
+ const unsignedUpdate = this.#state.unsignedUpdate;
6851
+ const signedUpdate = _Updater.sign(
6852
+ this.#sourceDocument.id,
6853
+ unsignedUpdate,
6854
+ this.#verificationMethod,
6855
+ data
6856
+ );
6857
+ this.#state = { phase: "Fund", unsignedUpdate, signedUpdate };
4732
6858
  break;
4733
6859
  }
4734
6860
  case "NeedFunding": {
4735
- if (this.#phase !== "Fund" /* Fund */) {
4736
- throw new import_common16.UpdateError(
4737
- `Cannot provide NeedFunding: updater phase is ${this.#phase}, expected Fund.`,
4738
- import_common16.INVALID_DID_UPDATE,
4739
- { phase: this.#phase }
6861
+ if (this.#state.phase !== "Fund") {
6862
+ throw new import_common20.UpdateError(
6863
+ `Cannot provide NeedFunding: updater phase is ${this.#state.phase}, expected Fund.`,
6864
+ import_common20.INVALID_DID_UPDATE,
6865
+ { phase: this.#state.phase }
4740
6866
  );
4741
6867
  }
4742
- this.#phase = "Broadcast" /* Broadcast */;
6868
+ if (data !== void 0) {
6869
+ const proof = data;
6870
+ if (typeof proof.utxoCount !== "number" || !Number.isFinite(proof.utxoCount) || proof.utxoCount < 1) {
6871
+ throw new import_common20.UpdateError(
6872
+ `NeedFunding proof must have utxoCount >= 1; got ${String(proof.utxoCount)}.`,
6873
+ import_common20.INVALID_DID_UPDATE,
6874
+ { utxoCount: proof.utxoCount }
6875
+ );
6876
+ }
6877
+ }
6878
+ this.#state = {
6879
+ phase: "Broadcast",
6880
+ unsignedUpdate: this.#state.unsignedUpdate,
6881
+ signedUpdate: this.#state.signedUpdate
6882
+ };
4743
6883
  break;
4744
6884
  }
4745
6885
  case "NeedBroadcast": {
4746
- if (this.#phase !== "Broadcast" /* Broadcast */) {
4747
- throw new import_common16.UpdateError(
4748
- `Cannot provide NeedBroadcast: updater phase is ${this.#phase}, expected Broadcast.`,
4749
- import_common16.INVALID_DID_UPDATE,
4750
- { phase: this.#phase }
6886
+ if (this.#state.phase !== "Broadcast") {
6887
+ throw new import_common20.UpdateError(
6888
+ `Cannot provide NeedBroadcast: updater phase is ${this.#state.phase}, expected Broadcast.`,
6889
+ import_common20.INVALID_DID_UPDATE,
6890
+ { phase: this.#state.phase }
4751
6891
  );
4752
6892
  }
4753
- this.#phase = "Complete" /* Complete */;
6893
+ this.#state = { phase: "Complete", signedUpdate: this.#state.signedUpdate };
4754
6894
  break;
4755
6895
  }
4756
6896
  }
@@ -4758,7 +6898,6 @@ var Updater = class _Updater {
4758
6898
  };
4759
6899
 
4760
6900
  // src/did-btcr2.ts
4761
- (0, import_bitcoinjs_lib7.initEccLib)(ecc);
4762
6901
  var DidBtcr2 = class {
4763
6902
  /**
4764
6903
  * Name of the DID method, as defined in the DID BTCR2 specification
@@ -4783,9 +6922,9 @@ var DidBtcr2 = class {
4783
6922
  static create(genesisBytes, options) {
4784
6923
  const { idType, version = 1, network = "bitcoin" } = options || {};
4785
6924
  if (!idType) {
4786
- throw new import_common17.MethodError(
6925
+ throw new import_common21.MethodError(
4787
6926
  "idType is required for creating a did:btcr2 identifier",
4788
- import_common17.INVALID_DID_DOCUMENT,
6927
+ import_common21.INVALID_DID_DOCUMENT,
4789
6928
  options
4790
6929
  );
4791
6930
  }
@@ -4815,7 +6954,7 @@ var DidBtcr2 = class {
4815
6954
  static resolve(did, resolutionOptions = {}) {
4816
6955
  const didComponents = Identifier.decode(did);
4817
6956
  const sidecarData = Resolver.sidecarData(resolutionOptions.sidecar);
4818
- const currentDocument = didComponents.hrp === import_common17.IdentifierHrp.k ? Resolver.deterministic(didComponents) : null;
6957
+ const currentDocument = didComponents.hrp === import_common21.IdentifierHrp.k ? Resolver.deterministic(didComponents) : null;
4819
6958
  return new Resolver(didComponents, sidecarData, currentDocument, {
4820
6959
  versionId: resolutionOptions.versionId,
4821
6960
  versionTime: resolutionOptions.versionTime,
@@ -4853,39 +6992,39 @@ var DidBtcr2 = class {
4853
6992
  beaconId
4854
6993
  }) {
4855
6994
  if (!sourceDocument.capabilityInvocation?.some((vr) => vr === verificationMethodId)) {
4856
- throw new import_common17.UpdateError(
6995
+ throw new import_common21.UpdateError(
4857
6996
  "Invalid verificationMethodId: not authorized for capabilityInvocation",
4858
- import_common17.INVALID_DID_DOCUMENT,
6997
+ import_common21.INVALID_DID_DOCUMENT,
4859
6998
  sourceDocument
4860
6999
  );
4861
7000
  }
4862
7001
  const verificationMethod = this.getSigningMethod(sourceDocument, verificationMethodId);
4863
7002
  if (!verificationMethod) {
4864
- throw new import_common17.UpdateError(
7003
+ throw new import_common21.UpdateError(
4865
7004
  "Invalid verificationMethod: not found in source document",
4866
- import_common17.INVALID_DID_DOCUMENT,
7005
+ import_common21.INVALID_DID_DOCUMENT,
4867
7006
  { sourceDocument, verificationMethodId }
4868
7007
  );
4869
7008
  }
4870
7009
  if (verificationMethod.type !== "Multikey") {
4871
- throw new import_common17.UpdateError(
7010
+ throw new import_common21.UpdateError(
4872
7011
  'Invalid verificationMethod: verificationMethod.type must be "Multikey"',
4873
- import_common17.INVALID_DID_DOCUMENT,
7012
+ import_common21.INVALID_DID_DOCUMENT,
4874
7013
  verificationMethod
4875
7014
  );
4876
7015
  }
4877
7016
  if (verificationMethod.publicKeyMultibase?.slice(0, 4) !== "zQ3s") {
4878
- throw new import_common17.UpdateError(
7017
+ throw new import_common21.UpdateError(
4879
7018
  'Invalid verificationMethodId: publicKeyMultibase prefix must start with "zQ3s"',
4880
- import_common17.INVALID_DID_DOCUMENT,
7019
+ import_common21.INVALID_DID_DOCUMENT,
4881
7020
  verificationMethod
4882
7021
  );
4883
7022
  }
4884
7023
  const beaconService = sourceDocument.service.filter((service) => service.id === beaconId).filter((service) => !!service).shift();
4885
7024
  if (!beaconService) {
4886
- throw new import_common17.UpdateError(
7025
+ throw new import_common21.UpdateError(
4887
7026
  "No beacon service found for provided beaconId",
4888
- import_common17.INVALID_DID_UPDATE,
7027
+ import_common21.INVALID_DID_UPDATE,
4889
7028
  { sourceDocument, beaconId }
4890
7029
  );
4891
7030
  }
@@ -4911,7 +7050,7 @@ var DidBtcr2 = class {
4911
7050
  methodId ??= "#initialKey";
4912
7051
  const parsedDid = import_dids2.Did.parse(didDocument.id);
4913
7052
  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 });
7053
+ throw new import_common21.MethodError(`Method not supported: ${parsedDid.method}`, import_common21.METHOD_NOT_SUPPORTED, { identifier: didDocument.id });
4915
7054
  }
4916
7055
  const verificationMethod = didDocument.verificationMethod?.find(
4917
7056
  (vm) => Appendix.extractDidFragment(vm.id) === (Appendix.extractDidFragment(methodId) ?? Appendix.extractDidFragment(didDocument.assertionMethod?.[0]))
@@ -4927,7 +7066,7 @@ var DidBtcr2 = class {
4927
7066
  };
4928
7067
 
4929
7068
  // src/core/resolver.ts
4930
- var import_utils10 = require("@noble/curves/utils.js");
7069
+ var import_utils12 = require("@noble/curves/utils.js");
4931
7070
  var Resolver = class _Resolver {
4932
7071
  // --- Immutable inputs ---
4933
7072
  #didComponents;
@@ -4967,7 +7106,7 @@ var Resolver = class _Resolver {
4967
7106
  static deterministic(didComponents) {
4968
7107
  const genesisBytes = didComponents.genesisBytes;
4969
7108
  const did = Identifier.encode(genesisBytes, didComponents);
4970
- const { multibase } = new import_keypair5.CompressedSecp256k1PublicKey(genesisBytes);
7109
+ const { multibase } = new import_keypair6.CompressedSecp256k1PublicKey(genesisBytes);
4971
7110
  const service = BeaconUtils.generateBeaconServices({
4972
7111
  id: did,
4973
7112
  publicKey: genesisBytes,
@@ -4993,14 +7132,14 @@ var Resolver = class _Resolver {
4993
7132
  * @throws {ResolveError} InvalidDidDocument if not conformant to DID Core v1.1
4994
7133
  */
4995
7134
  static external(didComponents, genesisDocument) {
4996
- const genesisDocumentHash = (0, import_common18.canonicalHashBytes)(genesisDocument);
4997
- if (!(0, import_utils10.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
4998
- throw new import_common18.ResolveError(
7135
+ const genesisDocumentHash = (0, import_common22.canonicalHashBytes)(genesisDocument);
7136
+ if (!(0, import_utils12.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
7137
+ throw new import_common22.ResolveError(
4999
7138
  `Initial document mismatch: genesisBytes !== genesisDocumentHash`,
5000
- import_common18.INVALID_DID_DOCUMENT,
7139
+ import_common22.INVALID_DID_DOCUMENT,
5001
7140
  {
5002
- genesisBytes: (0, import_common18.encode)(didComponents.genesisBytes, "hex"),
5003
- genesisDocumentHash: (0, import_common18.encode)(genesisDocumentHash, "hex")
7141
+ genesisBytes: (0, import_common22.encode)(didComponents.genesisBytes, "hex"),
7142
+ genesisDocumentHash: (0, import_common22.encode)(genesisDocumentHash, "hex")
5004
7143
  }
5005
7144
  );
5006
7145
  }
@@ -5019,12 +7158,12 @@ var Resolver = class _Resolver {
5019
7158
  const updateMap = /* @__PURE__ */ new Map();
5020
7159
  if (sidecar.updates?.length)
5021
7160
  for (const update of sidecar.updates) {
5022
- updateMap.set((0, import_common18.canonicalHash)(update, { encoding: "hex" }), update);
7161
+ updateMap.set((0, import_common22.canonicalHash)(update, { encoding: "hex" }), update);
5023
7162
  }
5024
7163
  const casMap = /* @__PURE__ */ new Map();
5025
7164
  if (sidecar.casUpdates?.length)
5026
7165
  for (const update of sidecar.casUpdates) {
5027
- casMap.set((0, import_common18.canonicalHash)(update, { encoding: "hex" }), update);
7166
+ casMap.set((0, import_common22.canonicalHash)(update, { encoding: "hex" }), update);
5028
7167
  }
5029
7168
  const smtMap = /* @__PURE__ */ new Map();
5030
7169
  if (sidecar.smtProofs?.length)
@@ -5057,12 +7196,12 @@ var Resolver = class _Resolver {
5057
7196
  }
5058
7197
  };
5059
7198
  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);
7199
+ const currentDocumentHash = (0, import_common22.canonicalHashBytes)(response.didDocument);
7200
+ const blocktime = import_common22.DateUtils.blocktimeToTimestamp(block.time);
7201
+ response.metadata.updated = import_common22.DateUtils.toISOStringNonFractional(blocktime);
5063
7202
  response.metadata.confirmations = block.confirmations;
5064
7203
  if (versionTime) {
5065
- if (blocktime > import_common18.DateUtils.dateStringToTimestamp(versionTime)) {
7204
+ if (blocktime > import_common22.DateUtils.dateStringToTimestamp(versionTime)) {
5066
7205
  return response;
5067
7206
  }
5068
7207
  }
@@ -5070,22 +7209,22 @@ var Resolver = class _Resolver {
5070
7209
  updateHashHistory.push(currentDocumentHash);
5071
7210
  this.confirmDuplicate(update, updateHashHistory);
5072
7211
  } else if (update.targetVersionId === currentVersionId + 1) {
5073
- const sourceHashBytes = (0, import_common18.decode)(update.sourceHash, "base64urlnopad");
5074
- if (!(0, import_utils10.equalBytes)(sourceHashBytes, currentDocumentHash)) {
5075
- throw new import_common18.ResolveError(
7212
+ const sourceHashBytes = (0, import_common22.decode)(update.sourceHash, "base64urlnopad");
7213
+ if (!(0, import_utils12.equalBytes)(sourceHashBytes, currentDocumentHash)) {
7214
+ throw new import_common22.ResolveError(
5076
7215
  `Hash mismatch: update.sourceHash !== currentDocumentHash`,
5077
- import_common18.INVALID_DID_UPDATE,
7216
+ import_common22.INVALID_DID_UPDATE,
5078
7217
  {
5079
7218
  sourceHash: update.sourceHash,
5080
- currentDocumentHash: (0, import_common18.encode)(currentDocumentHash, "hex")
7219
+ currentDocumentHash: (0, import_common22.encode)(currentDocumentHash, "hex")
5081
7220
  }
5082
7221
  );
5083
7222
  }
5084
7223
  response.didDocument = this.applyUpdate(response.didDocument, update);
5085
- const unsignedUpdate = import_common18.JSONUtils.deleteKeys(update, ["proof"]);
5086
- updateHashHistory.push((0, import_common18.canonicalHashBytes)(unsignedUpdate));
7224
+ const unsignedUpdate = import_common22.JSONUtils.deleteKeys(update, ["proof"]);
7225
+ updateHashHistory.push((0, import_common22.canonicalHashBytes)(unsignedUpdate));
5087
7226
  } else if (update.targetVersionId > currentVersionId + 1) {
5088
- throw new import_common18.ResolveError(
7227
+ throw new import_common22.ResolveError(
5089
7228
  `Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
5090
7229
  "LATE_PUBLISHING_ERROR",
5091
7230
  {
@@ -5116,15 +7255,15 @@ var Resolver = class _Resolver {
5116
7255
  */
5117
7256
  static confirmDuplicate(update, updateHashHistory) {
5118
7257
  const { proof: _, ...unsignedUpdate } = update;
5119
- const unsignedUpdateHash = (0, import_common18.canonicalHashBytes)(unsignedUpdate);
7258
+ const unsignedUpdateHash = (0, import_common22.canonicalHashBytes)(unsignedUpdate);
5120
7259
  const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
5121
- if (!(0, import_utils10.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
5122
- throw new import_common18.ResolveError(
7260
+ if (!(0, import_utils12.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
7261
+ throw new import_common22.ResolveError(
5123
7262
  `Invalid duplicate: unsigned update hash does not match historical hash`,
5124
- import_common18.LATE_PUBLISHING_ERROR,
7263
+ import_common22.LATE_PUBLISHING_ERROR,
5125
7264
  {
5126
- unsignedUpdateHash: (0, import_common18.encode)(unsignedUpdateHash, "hex"),
5127
- historicalHash: (0, import_common18.encode)(historicalUpdateHash, "hex")
7265
+ unsignedUpdateHash: (0, import_common22.encode)(unsignedUpdateHash, "hex"),
7266
+ historicalHash: (0, import_common22.encode)(historicalUpdateHash, "hex")
5128
7267
  }
5129
7268
  );
5130
7269
  }
@@ -5139,42 +7278,42 @@ var Resolver = class _Resolver {
5139
7278
  static applyUpdate(currentDocument, update) {
5140
7279
  const capabilityId = update.proof?.capability;
5141
7280
  if (!capabilityId) {
5142
- throw new import_common18.ResolveError("No root capability found in update", import_common18.INVALID_DID_UPDATE, update);
7281
+ throw new import_common22.ResolveError("No root capability found in update", import_common22.INVALID_DID_UPDATE, update);
5143
7282
  }
5144
7283
  const rootCapability = Appendix.dereferenceZcapId(capabilityId);
5145
7284
  const { invocationTarget, controller: rootController } = rootCapability;
5146
7285
  if (![invocationTarget, rootController].every((id) => id === currentDocument.id)) {
5147
- throw new import_common18.ResolveError(
7286
+ throw new import_common22.ResolveError(
5148
7287
  "Invalid root capability",
5149
- import_common18.INVALID_DID_UPDATE,
7288
+ import_common22.INVALID_DID_UPDATE,
5150
7289
  { rootCapability, currentDocument }
5151
7290
  );
5152
7291
  }
5153
7292
  const verificationMethodId = update.proof?.verificationMethod;
5154
7293
  if (!verificationMethodId) {
5155
- throw new import_common18.ResolveError("No verificationMethod found in update", import_common18.INVALID_DID_UPDATE, update);
7294
+ throw new import_common22.ResolveError("No verificationMethod found in update", import_common22.INVALID_DID_UPDATE, update);
5156
7295
  }
5157
7296
  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);
7297
+ const multikey = import_cryptosuite3.SchnorrMultikey.fromVerificationMethod(vm);
7298
+ const cryptosuite = new import_cryptosuite3.BIP340Cryptosuite(multikey);
7299
+ const canonicalUpdate = (0, import_common22.canonicalize)(update);
7300
+ const diProof = new import_cryptosuite3.BIP340DataIntegrityProof(cryptosuite);
5162
7301
  const verificationResult = diProof.verifyProof(canonicalUpdate, "capabilityInvocation");
5163
7302
  if (!verificationResult.verified) {
5164
- throw new import_common18.ResolveError(
7303
+ throw new import_common22.ResolveError(
5165
7304
  "Invalid update: proof not verified",
5166
- import_common18.INVALID_DID_UPDATE,
7305
+ import_common22.INVALID_DID_UPDATE,
5167
7306
  verificationResult
5168
7307
  );
5169
7308
  }
5170
- const updatedDocument = import_common18.JSONPatch.apply(currentDocument, update.patch);
7309
+ const updatedDocument = import_common22.JSONPatch.apply(currentDocument, update.patch);
5171
7310
  DidDocument.validate(updatedDocument);
5172
- const currentDocumentHash = (0, import_common18.canonicalHashBytes)(updatedDocument);
5173
- const updateTargetHash = (0, import_common18.decode)(update.targetHash);
5174
- if (!(0, import_utils10.equalBytes)(updateTargetHash, currentDocumentHash)) {
5175
- throw new import_common18.ResolveError(
7311
+ const currentDocumentHash = (0, import_common22.canonicalHashBytes)(updatedDocument);
7312
+ const updateTargetHash = (0, import_common22.decode)(update.targetHash);
7313
+ if (!(0, import_utils12.equalBytes)(updateTargetHash, currentDocumentHash)) {
7314
+ throw new import_common22.ResolveError(
5176
7315
  `Invalid update: update.targetHash !== currentDocumentHash`,
5177
- import_common18.INVALID_DID_UPDATE,
7316
+ import_common22.INVALID_DID_UPDATE,
5178
7317
  { updateTargetHash, currentDocumentHash }
5179
7318
  );
5180
7319
  }
@@ -5202,7 +7341,7 @@ var Resolver = class _Resolver {
5202
7341
  this.#phase = "BeaconDiscovery" /* BeaconDiscovery */;
5203
7342
  continue;
5204
7343
  }
5205
- const genesisHash = (0, import_common18.encode)(this.#didComponents.genesisBytes, "hex");
7344
+ const genesisHash = (0, import_common22.encode)(this.#didComponents.genesisBytes, "hex");
5206
7345
  return {
5207
7346
  status: "action-required",
5208
7347
  needs: [{ kind: "NeedGenesisDocument", genesisHash }]
@@ -5306,21 +7445,21 @@ var Resolver = class _Resolver {
5306
7445
  }
5307
7446
  case "NeedCASAnnouncement": {
5308
7447
  const announcement = data;
5309
- this.#sidecarData.casMap.set((0, import_common18.canonicalHash)(announcement, { encoding: "hex" }), announcement);
7448
+ this.#sidecarData.casMap.set((0, import_common22.canonicalHash)(announcement, { encoding: "hex" }), announcement);
5310
7449
  break;
5311
7450
  }
5312
7451
  case "NeedSignedUpdate": {
5313
7452
  const update = data;
5314
- this.#sidecarData.updateMap.set((0, import_common18.canonicalHash)(update, { encoding: "hex" }), update);
7453
+ this.#sidecarData.updateMap.set((0, import_common22.canonicalHash)(update, { encoding: "hex" }), update);
5315
7454
  break;
5316
7455
  }
5317
7456
  case "NeedSMTProof": {
5318
7457
  const smtNeed = need;
5319
7458
  const proof = data;
5320
7459
  if (proof.id !== smtNeed.smtRootHash) {
5321
- throw new import_common18.ResolveError(
7460
+ throw new import_common22.ResolveError(
5322
7461
  `SMT proof root hash mismatch: expected ${smtNeed.smtRootHash}, got ${proof.id}`,
5323
- import_common18.INVALID_DID_UPDATE,
7462
+ import_common22.INVALID_DID_UPDATE,
5324
7463
  { expected: smtNeed.smtRootHash, actual: proof.id }
5325
7464
  );
5326
7465
  }
@@ -5332,12 +7471,12 @@ var Resolver = class _Resolver {
5332
7471
  };
5333
7472
 
5334
7473
  // src/utils/did-document-builder.ts
5335
- var import_common19 = require("@did-btcr2/common");
7474
+ var import_common23 = require("@did-btcr2/common");
5336
7475
  var DidDocumentBuilder = class {
5337
7476
  document = {};
5338
7477
  constructor(initialDocument) {
5339
7478
  if (!initialDocument.id) {
5340
- throw new import_common19.DidDocumentError('Missing required "id" property', import_common19.INVALID_DID_DOCUMENT, initialDocument);
7479
+ throw new import_common23.DidDocumentError('Missing required "id" property', import_common23.INVALID_DID_DOCUMENT, initialDocument);
5341
7480
  }
5342
7481
  this.document.id = initialDocument.id;
5343
7482
  this.document.verificationMethod = initialDocument.verificationMethod ?? [];
@@ -5389,6 +7528,7 @@ var DidDocumentBuilder = class {
5389
7528
  0 && (module.exports = {
5390
7529
  AGGREGATED_NONCE,
5391
7530
  AGGREGATION_MESSAGE_PREFIX,
7531
+ AGGREGATION_WIRE_VERSION,
5392
7532
  AUTHORIZATION_REQUEST,
5393
7533
  AggregateBeaconError,
5394
7534
  AggregationCohort,
@@ -5415,6 +7555,12 @@ var DidDocumentBuilder = class {
5415
7555
  COHORT_OPT_IN,
5416
7556
  COHORT_OPT_IN_ACCEPT,
5417
7557
  COHORT_READY,
7558
+ CONSOLE_LOGGER,
7559
+ DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
7560
+ DEFAULT_BROADCAST_LOOKBACK_MS,
7561
+ DEFAULT_CLOCK_SKEW_SEC,
7562
+ DEFAULT_MAX_UPDATE_SIZE_BYTES,
7563
+ DEFAULT_NONCE_LEN_BYTES,
5418
7564
  DEFAULT_NOSTR_RELAYS,
5419
7565
  DID_REGEX,
5420
7566
  DISTRIBUTE_AGGREGATED_DATA,
@@ -5425,15 +7571,31 @@ var DidDocumentBuilder = class {
5425
7571
  DidVerificationMethod,
5426
7572
  Document,
5427
7573
  GenesisDocument,
7574
+ HTTP_ENVELOPE_VERSION,
7575
+ HTTP_ROUTE,
7576
+ HttpClientTransport,
7577
+ HttpServerTransport,
7578
+ HttpTransportError,
5428
7579
  ID_PLACEHOLDER_VALUE,
5429
7580
  Identifier,
7581
+ InMemoryRateLimitStore,
7582
+ InboxBuffer,
5430
7583
  NONCE_CONTRIBUTION,
7584
+ NonceCache,
5431
7585
  NostrTransport,
7586
+ P2PKH_BEACON_TX_VSIZE,
7587
+ P2TR_BEACON_TX_VSIZE,
7588
+ P2WPKH_BEACON_TX_VSIZE,
5432
7589
  ParticipantCohortPhase,
7590
+ REQUEST_AUTH_SCHEME,
7591
+ RateLimiter,
5433
7592
  Resolver,
5434
7593
  SIGNATURE_AUTHORIZATION,
7594
+ SILENT_LOGGER,
7595
+ SINGLETON_BEACON_TX_VSIZE,
5435
7596
  SMTBeacon,
5436
7597
  SMTBeaconError,
7598
+ SSE_EVENT,
5437
7599
  SUBMIT_UPDATE,
5438
7600
  ServiceCohortPhase,
5439
7601
  SigningSessionError,
@@ -5447,6 +7609,8 @@ var DidDocumentBuilder = class {
5447
7609
  TypedEventEmitter,
5448
7610
  Updater,
5449
7611
  VALIDATION_ACK,
7612
+ buildAggregationBeaconTx,
7613
+ buildRequestAuth,
5450
7614
  createAggregatedNonceMessage,
5451
7615
  createAuthorizationRequestMessage,
5452
7616
  createCohortAdvertMessage,
@@ -5458,8 +7622,34 @@ var DidDocumentBuilder = class {
5458
7622
  createSignatureAuthorizationMessage,
5459
7623
  createSubmitUpdateMessage,
5460
7624
  createValidationAckMessage,
7625
+ defaultReconnectBackoff,
7626
+ deriveSingletonAddress,
7627
+ detectSingletonScriptKind,
7628
+ formatSseComment,
7629
+ formatSseEvent,
7630
+ getBeaconStrategy,
7631
+ isAggregatedNonceMessage,
5461
7632
  isAggregationMessageType,
7633
+ isAuthorizationRequestMessage,
7634
+ isCohortAdvertMessage,
7635
+ isCohortOptInAcceptMessage,
7636
+ isCohortOptInMessage,
7637
+ isCohortReadyMessage,
7638
+ isDistributeAggregatedDataMessage,
5462
7639
  isKeygenMessageType,
7640
+ isNonceContributionMessage,
5463
7641
  isSignMessageType,
5464
- isUpdateMessageType
7642
+ isSignatureAuthorizationMessage,
7643
+ isSubmitUpdateMessage,
7644
+ isUpdateMessageType,
7645
+ isValidationAckMessage,
7646
+ normalizeForWire,
7647
+ opReturnScript,
7648
+ parseRequestAuth,
7649
+ parseSseStream,
7650
+ registerBeaconStrategy,
7651
+ reviveFromWire,
7652
+ signEnvelope,
7653
+ verifyEnvelope,
7654
+ verifyRequestAuth
5465
7655
  });