@did-btcr2/method 0.36.1 → 0.37.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 (164) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/browser.js +1060 -96
  3. package/dist/browser.mjs +1060 -96
  4. package/dist/cjs/index.js +1153 -179
  5. package/dist/esm/core/aggregation/beacon-strategy.js +19 -6
  6. package/dist/esm/core/aggregation/beacon-strategy.js.map +1 -1
  7. package/dist/esm/core/aggregation/cohort.js +142 -33
  8. package/dist/esm/core/aggregation/cohort.js.map +1 -1
  9. package/dist/esm/core/aggregation/conditions.js +27 -0
  10. package/dist/esm/core/aggregation/conditions.js.map +1 -1
  11. package/dist/esm/core/aggregation/fallback-spend.js +132 -0
  12. package/dist/esm/core/aggregation/fallback-spend.js.map +1 -0
  13. package/dist/esm/core/aggregation/messages/base.js.map +1 -1
  14. package/dist/esm/core/aggregation/messages/bodies.js +46 -1
  15. package/dist/esm/core/aggregation/messages/bodies.js.map +1 -1
  16. package/dist/esm/core/aggregation/messages/constants.js +11 -0
  17. package/dist/esm/core/aggregation/messages/constants.js.map +1 -1
  18. package/dist/esm/core/aggregation/messages/factories.js +27 -1
  19. package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
  20. package/dist/esm/core/aggregation/messages/guards.js +4 -1
  21. package/dist/esm/core/aggregation/messages/guards.js.map +1 -1
  22. package/dist/esm/core/aggregation/participant.js +210 -21
  23. package/dist/esm/core/aggregation/participant.js.map +1 -1
  24. package/dist/esm/core/aggregation/phases.js +6 -0
  25. package/dist/esm/core/aggregation/phases.js.map +1 -1
  26. package/dist/esm/core/aggregation/recovery-policy.js +156 -0
  27. package/dist/esm/core/aggregation/recovery-policy.js.map +1 -0
  28. package/dist/esm/core/aggregation/recovery-spend.js +106 -0
  29. package/dist/esm/core/aggregation/recovery-spend.js.map +1 -0
  30. package/dist/esm/core/aggregation/runner/aggregation-runner.js +11 -3
  31. package/dist/esm/core/aggregation/runner/aggregation-runner.js.map +1 -1
  32. package/dist/esm/core/aggregation/runner/participant-runner.js +74 -22
  33. package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
  34. package/dist/esm/core/aggregation/runner/service-runner.js +97 -9
  35. package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
  36. package/dist/esm/core/aggregation/service.js +221 -11
  37. package/dist/esm/core/aggregation/service.js.map +1 -1
  38. package/dist/esm/core/aggregation/signing-session.js +3 -3
  39. package/dist/esm/core/aggregation/signing-session.js.map +1 -1
  40. package/dist/esm/core/aggregation/transport/http/envelope.js +3 -3
  41. package/dist/esm/core/aggregation/transport/http/inbox-buffer.js +1 -1
  42. package/dist/esm/core/aggregation/transport/http/nonce-cache.js +1 -1
  43. package/dist/esm/core/aggregation/transport/http/rate-limiter.js +1 -1
  44. package/dist/esm/core/aggregation/transport/http/request-auth.js +1 -1
  45. package/dist/esm/core/aggregation/transport/http/server.js +1 -1
  46. package/dist/esm/core/aggregation/transport/http/sse-stream.js +1 -1
  47. package/dist/esm/core/aggregation/transport/http/sse-writer.js +1 -1
  48. package/dist/esm/core/aggregation/transport/in-memory.js +1 -1
  49. package/dist/esm/core/aggregation/transport/nostr.js +6 -6
  50. package/dist/esm/core/beacon/beacon.js +15 -10
  51. package/dist/esm/core/beacon/beacon.js.map +1 -1
  52. package/dist/esm/core/beacon/cas-beacon.js +6 -6
  53. package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
  54. package/dist/esm/core/beacon/singleton-beacon.js +2 -2
  55. package/dist/esm/core/beacon/singleton-beacon.js.map +1 -1
  56. package/dist/esm/core/beacon/smt-beacon.js +3 -3
  57. package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
  58. package/dist/esm/core/resolver.js +8 -8
  59. package/dist/esm/core/resolver.js.map +1 -1
  60. package/dist/esm/core/updater.js +6 -6
  61. package/dist/esm/core/updater.js.map +1 -1
  62. package/dist/esm/did-btcr2.js +2 -2
  63. package/dist/esm/index.js +3 -0
  64. package/dist/esm/index.js.map +1 -1
  65. package/dist/types/core/aggregation/beacon-strategy.d.ts +8 -4
  66. package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -1
  67. package/dist/types/core/aggregation/cohort.d.ts +88 -17
  68. package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
  69. package/dist/types/core/aggregation/conditions.d.ts +28 -0
  70. package/dist/types/core/aggregation/conditions.d.ts.map +1 -1
  71. package/dist/types/core/aggregation/fallback-spend.d.ts +94 -0
  72. package/dist/types/core/aggregation/fallback-spend.d.ts.map +1 -0
  73. package/dist/types/core/aggregation/logger.d.ts +1 -1
  74. package/dist/types/core/aggregation/messages/base.d.ts +6 -0
  75. package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
  76. package/dist/types/core/aggregation/messages/bodies.d.ts +50 -2
  77. package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -1
  78. package/dist/types/core/aggregation/messages/constants.d.ts +8 -0
  79. package/dist/types/core/aggregation/messages/constants.d.ts.map +1 -1
  80. package/dist/types/core/aggregation/messages/factories.d.ts +40 -0
  81. package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
  82. package/dist/types/core/aggregation/messages/guards.d.ts.map +1 -1
  83. package/dist/types/core/aggregation/participant.d.ts +41 -7
  84. package/dist/types/core/aggregation/participant.d.ts.map +1 -1
  85. package/dist/types/core/aggregation/phases.d.ts +8 -2
  86. package/dist/types/core/aggregation/phases.d.ts.map +1 -1
  87. package/dist/types/core/aggregation/recovery-policy.d.ts +132 -0
  88. package/dist/types/core/aggregation/recovery-policy.d.ts.map +1 -0
  89. package/dist/types/core/aggregation/recovery-spend.d.ts +94 -0
  90. package/dist/types/core/aggregation/recovery-spend.d.ts.map +1 -0
  91. package/dist/types/core/aggregation/runner/aggregation-runner.d.ts +13 -3
  92. package/dist/types/core/aggregation/runner/aggregation-runner.d.ts.map +1 -1
  93. package/dist/types/core/aggregation/runner/events.d.ts +28 -7
  94. package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
  95. package/dist/types/core/aggregation/runner/participant-runner.d.ts +3 -3
  96. package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
  97. package/dist/types/core/aggregation/runner/service-runner.d.ts +28 -3
  98. package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
  99. package/dist/types/core/aggregation/service.d.ts +26 -2
  100. package/dist/types/core/aggregation/service.d.ts.map +1 -1
  101. package/dist/types/core/aggregation/signing-session.d.ts +2 -2
  102. package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
  103. package/dist/types/core/aggregation/transport/http/envelope.d.ts +3 -3
  104. package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts +1 -1
  105. package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts +1 -1
  106. package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts +1 -1
  107. package/dist/types/core/aggregation/transport/http/request-auth.d.ts +1 -1
  108. package/dist/types/core/aggregation/transport/http/server.d.ts +1 -1
  109. package/dist/types/core/aggregation/transport/http/sse-stream.d.ts +2 -2
  110. package/dist/types/core/aggregation/transport/http/sse-writer.d.ts +1 -1
  111. package/dist/types/core/aggregation/transport/in-memory.d.ts +1 -1
  112. package/dist/types/core/aggregation/transport/nostr.d.ts +4 -4
  113. package/dist/types/core/aggregation/transport/transport.d.ts +4 -4
  114. package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
  115. package/dist/types/core/beacon/beacon.d.ts +7 -7
  116. package/dist/types/core/beacon/beacon.d.ts.map +1 -1
  117. package/dist/types/core/beacon/cas-beacon.d.ts +2 -2
  118. package/dist/types/core/beacon/interfaces.d.ts +1 -1
  119. package/dist/types/core/resolver.d.ts +3 -3
  120. package/dist/types/core/updater.d.ts +4 -4
  121. package/dist/types/did-btcr2.d.ts +2 -2
  122. package/dist/types/index.d.ts +3 -0
  123. package/dist/types/index.d.ts.map +1 -1
  124. package/package.json +4 -4
  125. package/src/core/aggregation/beacon-strategy.ts +27 -9
  126. package/src/core/aggregation/cohort.ts +171 -35
  127. package/src/core/aggregation/conditions.ts +56 -0
  128. package/src/core/aggregation/fallback-spend.ts +223 -0
  129. package/src/core/aggregation/logger.ts +1 -1
  130. package/src/core/aggregation/messages/base.ts +6 -0
  131. package/src/core/aggregation/messages/bodies.ts +94 -1
  132. package/src/core/aggregation/messages/constants.ts +12 -0
  133. package/src/core/aggregation/messages/factories.ts +57 -0
  134. package/src/core/aggregation/messages/guards.ts +6 -0
  135. package/src/core/aggregation/participant.ts +255 -23
  136. package/src/core/aggregation/phases.ts +9 -0
  137. package/src/core/aggregation/recovery-policy.ts +237 -0
  138. package/src/core/aggregation/recovery-spend.ts +192 -0
  139. package/src/core/aggregation/runner/aggregation-runner.ts +27 -5
  140. package/src/core/aggregation/runner/events.ts +26 -7
  141. package/src/core/aggregation/runner/participant-runner.ts +75 -23
  142. package/src/core/aggregation/runner/service-runner.ts +120 -10
  143. package/src/core/aggregation/service.ts +260 -14
  144. package/src/core/aggregation/signing-session.ts +3 -3
  145. package/src/core/aggregation/transport/http/envelope.ts +3 -3
  146. package/src/core/aggregation/transport/http/inbox-buffer.ts +1 -1
  147. package/src/core/aggregation/transport/http/nonce-cache.ts +1 -1
  148. package/src/core/aggregation/transport/http/rate-limiter.ts +1 -1
  149. package/src/core/aggregation/transport/http/request-auth.ts +1 -1
  150. package/src/core/aggregation/transport/http/server.ts +1 -1
  151. package/src/core/aggregation/transport/http/sse-stream.ts +2 -2
  152. package/src/core/aggregation/transport/http/sse-writer.ts +1 -1
  153. package/src/core/aggregation/transport/in-memory.ts +1 -1
  154. package/src/core/aggregation/transport/nostr.ts +6 -6
  155. package/src/core/aggregation/transport/transport.ts +4 -4
  156. package/src/core/beacon/beacon.ts +16 -11
  157. package/src/core/beacon/cas-beacon.ts +7 -7
  158. package/src/core/beacon/interfaces.ts +1 -1
  159. package/src/core/beacon/singleton-beacon.ts +2 -2
  160. package/src/core/beacon/smt-beacon.ts +3 -3
  161. package/src/core/resolver.ts +8 -8
  162. package/src/core/updater.ts +6 -6
  163. package/src/did-btcr2.ts +2 -2
  164. package/src/index.ts +3 -0
package/dist/cjs/index.js CHANGED
@@ -63,9 +63,11 @@ __export(index_exports, {
63
63
  DEFAULT_ADVERT_REPEAT_INTERVAL_MS: () => DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
64
64
  DEFAULT_BROADCAST_LOOKBACK_MS: () => DEFAULT_BROADCAST_LOOKBACK_MS,
65
65
  DEFAULT_CLOCK_SKEW_SEC: () => DEFAULT_CLOCK_SKEW_SEC,
66
+ DEFAULT_FUNDING_MODEL: () => DEFAULT_FUNDING_MODEL,
66
67
  DEFAULT_MAX_UPDATE_SIZE_BYTES: () => DEFAULT_MAX_UPDATE_SIZE_BYTES,
67
68
  DEFAULT_NONCE_LEN_BYTES: () => DEFAULT_NONCE_LEN_BYTES,
68
69
  DEFAULT_NOSTR_RELAYS: () => DEFAULT_NOSTR_RELAYS,
70
+ DEFAULT_RECOVERY_SEQUENCE: () => DEFAULT_RECOVERY_SEQUENCE,
69
71
  DID_REGEX: () => DID_REGEX,
70
72
  DISTRIBUTE_AGGREGATED_DATA: () => DISTRIBUTE_AGGREGATED_DATA,
71
73
  DidBtcr2: () => DidBtcr2,
@@ -74,6 +76,8 @@ __export(index_exports, {
74
76
  DidDocumentBuilder: () => DidDocumentBuilder,
75
77
  DidVerificationMethod: () => DidVerificationMethod,
76
78
  Document: () => Document,
79
+ FALLBACK_AUTHORIZATION_REQUEST: () => FALLBACK_AUTHORIZATION_REQUEST,
80
+ FALLBACK_SIGNATURE: () => FALLBACK_SIGNATURE,
77
81
  GenesisDocument: () => GenesisDocument,
78
82
  HTTP_ENVELOPE_VERSION: () => HTTP_ENVELOPE_VERSION,
79
83
  HTTP_ROUTE: () => HTTP_ROUTE,
@@ -87,7 +91,9 @@ __export(index_exports, {
87
91
  InMemoryTransport: () => InMemoryTransport,
88
92
  InboxBuffer: () => InboxBuffer,
89
93
  KNOWN_BEACON_TYPES: () => KNOWN_BEACON_TYPES,
94
+ KNOWN_FUNDING_MODELS: () => KNOWN_FUNDING_MODELS,
90
95
  KeyPairAggregationSigner: () => KeyPairAggregationSigner,
96
+ MAX_RECOVERY_SEQUENCE: () => MAX_RECOVERY_SEQUENCE,
91
97
  NONCE_CONTRIBUTION: () => NONCE_CONTRIBUTION,
92
98
  NonceCache: () => NonceCache,
93
99
  NostrTransport: () => NostrTransport,
@@ -104,6 +110,7 @@ __export(index_exports, {
104
110
  SMTBeacon: () => SMTBeacon,
105
111
  SMTBeaconError: () => SMTBeaconError,
106
112
  SSE_EVENT: () => SSE_EVENT,
113
+ SUBMIT_NONINCLUDED: () => SUBMIT_NONINCLUDED,
107
114
  SUBMIT_UPDATE: () => SUBMIT_UPDATE,
108
115
  ServiceCohortPhase: () => ServiceCohortPhase,
109
116
  SigningSessionError: () => SigningSessionError,
@@ -112,6 +119,7 @@ __export(index_exports, {
112
119
  SingletonBeacon: () => SingletonBeacon,
113
120
  SingletonBeaconError: () => SingletonBeaconError,
114
121
  StaticFeeEstimator: () => StaticFeeEstimator,
122
+ TAPROOT_LEAF_VERSION: () => TAPROOT_LEAF_VERSION,
115
123
  TransportAdapterError: () => TransportAdapterError,
116
124
  TransportError: () => TransportError,
117
125
  TransportFactory: () => TransportFactory,
@@ -119,6 +127,11 @@ __export(index_exports, {
119
127
  Updater: () => Updater,
120
128
  VALIDATION_ACK: () => VALIDATION_ACK,
121
129
  buildAggregationBeaconTx: () => buildAggregationBeaconTx,
130
+ buildFallbackLeaf: () => buildFallbackLeaf,
131
+ buildFallbackSpend: () => buildFallbackSpend,
132
+ buildRecoveryLeaves: () => buildRecoveryLeaves,
133
+ buildRecoveryScript: () => buildRecoveryScript,
134
+ buildRecoverySpend: () => buildRecoverySpend,
122
135
  buildRequestAuth: () => buildRequestAuth,
123
136
  createAggregatedNonceMessage: () => createAggregatedNonceMessage,
124
137
  createAuthorizationRequestMessage: () => createAuthorizationRequestMessage,
@@ -127,13 +140,17 @@ __export(index_exports, {
127
140
  createCohortOptInMessage: () => createCohortOptInMessage,
128
141
  createCohortReadyMessage: () => createCohortReadyMessage,
129
142
  createDistributeAggregatedDataMessage: () => createDistributeAggregatedDataMessage,
143
+ createFallbackAuthorizationRequestMessage: () => createFallbackAuthorizationRequestMessage,
144
+ createFallbackSignatureMessage: () => createFallbackSignatureMessage,
130
145
  createNonceContributionMessage: () => createNonceContributionMessage,
131
146
  createSignatureAuthorizationMessage: () => createSignatureAuthorizationMessage,
147
+ createSubmitNonIncludedMessage: () => createSubmitNonIncludedMessage,
132
148
  createSubmitUpdateMessage: () => createSubmitUpdateMessage,
133
149
  createValidationAckMessage: () => createValidationAckMessage,
134
150
  defaultReconnectBackoff: () => defaultReconnectBackoff,
135
151
  deriveSingletonAddress: () => deriveSingletonAddress,
136
152
  detectSingletonScriptKind: () => detectSingletonScriptKind,
153
+ fallbackSighash: () => fallbackSighash,
137
154
  formatSseComment: () => formatSseComment,
138
155
  formatSseEvent: () => formatSseEvent,
139
156
  getBeaconStrategy: () => getBeaconStrategy,
@@ -145,10 +162,13 @@ __export(index_exports, {
145
162
  isCohortOptInMessage: () => isCohortOptInMessage,
146
163
  isCohortReadyMessage: () => isCohortReadyMessage,
147
164
  isDistributeAggregatedDataMessage: () => isDistributeAggregatedDataMessage,
165
+ isFallbackAuthorizationRequestMessage: () => isFallbackAuthorizationRequestMessage,
166
+ isFallbackSignatureMessage: () => isFallbackSignatureMessage,
148
167
  isKeygenMessageType: () => isKeygenMessageType,
149
168
  isNonceContributionMessage: () => isNonceContributionMessage,
150
169
  isSignMessageType: () => isSignMessageType,
151
170
  isSignatureAuthorizationMessage: () => isSignatureAuthorizationMessage,
171
+ isSubmitNonIncludedMessage: () => isSubmitNonIncludedMessage,
152
172
  isSubmitUpdateMessage: () => isSubmitUpdateMessage,
153
173
  isUpdateMessageType: () => isUpdateMessageType,
154
174
  isValidationAckMessage: () => isValidationAckMessage,
@@ -157,8 +177,10 @@ __export(index_exports, {
157
177
  parseRequestAuth: () => parseRequestAuth,
158
178
  parseSseStream: () => parseSseStream,
159
179
  registerBeaconStrategy: () => registerBeaconStrategy,
180
+ resolveFallbackThreshold: () => resolveFallbackThreshold,
160
181
  reviveFromWire: () => reviveFromWire,
161
182
  signEnvelope: () => signEnvelope,
183
+ tapLeafHash: () => tapLeafHash,
162
184
  validateCohortConditions: () => validateCohortConditions,
163
185
  verifyEnvelope: () => verifyEnvelope,
164
186
  verifyRequestAuth: () => verifyRequestAuth
@@ -168,7 +190,8 @@ module.exports = __toCommonJS(index_exports);
168
190
  // src/core/aggregation/service.ts
169
191
  var import_common4 = require("@did-btcr2/common");
170
192
  var import_cryptosuite = require("@did-btcr2/cryptosuite");
171
- var import_utils2 = require("@noble/hashes/utils");
193
+ var import_secp256k14 = require("@noble/curves/secp256k1.js");
194
+ var import_utils3 = require("@noble/hashes/utils");
172
195
 
173
196
  // src/core/aggregation/beacon-strategy.ts
174
197
  var import_common = require("@did-btcr2/common");
@@ -181,9 +204,12 @@ var CAS_STRATEGY = {
181
204
  getDistributePayload(cohort) {
182
205
  return { casAnnouncement: cohort.casAnnouncement };
183
206
  },
184
- validateParticipantView({ participantDid, expectedHash, body }) {
207
+ validateParticipantView({ participantDid, included, expectedHash, body }) {
185
208
  const casAnnouncement = body.casAnnouncement;
186
209
  if (!casAnnouncement) return { matches: false };
210
+ if (!included) {
211
+ return { matches: !(participantDid in casAnnouncement), casAnnouncement };
212
+ }
187
213
  return {
188
214
  matches: casAnnouncement[participantDid] === expectedHash,
189
215
  casAnnouncement
@@ -199,15 +225,20 @@ var SMT_STRATEGY = {
199
225
  const proof = cohort.smtProofs?.get(participantDid);
200
226
  return { smtProof: proof };
201
227
  },
202
- validateParticipantView({ participantDid, submittedUpdate, body }) {
228
+ validateParticipantView({ participantDid, included, submittedUpdate, body }) {
203
229
  const smtProof = body.smtProof;
204
- if (!smtProof?.updateId || !smtProof?.nonce) return { matches: false };
230
+ const index = (0, import_smt.didToIndex)(participantDid);
231
+ if (!included) {
232
+ if (!smtProof?.nonce || smtProof?.updateId) return { matches: false, smtProof };
233
+ const candidateHash2 = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.base64UrlToHash)(smtProof.nonce)));
234
+ return { matches: (0, import_smt.verifySerializedProof)(smtProof, index, candidateHash2), smtProof };
235
+ }
236
+ if (!smtProof?.updateId || !smtProof?.nonce || !submittedUpdate) return { matches: false, smtProof };
205
237
  const canonicalBytes = new TextEncoder().encode((0, import_common.canonicalize)(submittedUpdate));
206
238
  const expectedUpdateId = (0, import_smt.hashToBase64Url)((0, import_smt.blockHash)(canonicalBytes));
207
239
  if (smtProof.updateId !== expectedUpdateId) {
208
240
  return { matches: false, smtProof };
209
241
  }
210
- const index = (0, import_smt.didToIndex)(participantDid);
211
242
  const candidateHash = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.base64UrlToHash)(smtProof.nonce)), (0, import_smt.base64UrlToHash)(smtProof.updateId));
212
243
  return {
213
244
  matches: (0, import_smt.verifySerializedProof)(smtProof, index, candidateHash),
@@ -230,10 +261,10 @@ function getBeaconStrategy(type) {
230
261
  var import_bitcoin = require("@did-btcr2/bitcoin");
231
262
  var import_common3 = require("@did-btcr2/common");
232
263
  var import_smt2 = require("@did-btcr2/smt");
233
- var import_secp256k1 = require("@noble/curves/secp256k1.js");
234
- var import_utils = require("@noble/hashes/utils");
235
- var import_btc_signer = require("@scure/btc-signer");
236
- var import_musig2 = require("@scure/btc-signer/musig2");
264
+ var import_secp256k12 = require("@noble/curves/secp256k1.js");
265
+ var import_utils2 = require("@noble/hashes/utils");
266
+ var import_btc_signer2 = require("@scure/btc-signer");
267
+ var import_musig22 = require("@scure/btc-signer/musig2");
237
268
 
238
269
  // src/core/aggregation/errors.ts
239
270
  var import_common2 = require("@did-btcr2/common");
@@ -258,6 +289,105 @@ var SigningSessionError = class extends import_common2.MethodError {
258
289
  }
259
290
  };
260
291
 
292
+ // src/core/aggregation/recovery-policy.ts
293
+ var import_secp256k1 = require("@noble/curves/secp256k1.js");
294
+ var import_utils = require("@noble/hashes/utils");
295
+ var import_btc_signer = require("@scure/btc-signer");
296
+ var import_musig2 = require("@scure/btc-signer/musig2");
297
+ var DEFAULT_FUNDING_MODEL = "operator-funded";
298
+ var DEFAULT_RECOVERY_SEQUENCE = 144;
299
+ var MAX_RECOVERY_SEQUENCE = 65535;
300
+ var TAPROOT_LEAF_VERSION = 192;
301
+ function compactSize(n) {
302
+ if (n < 253) return new Uint8Array([n]);
303
+ if (n <= 65535) return new Uint8Array([253, n & 255, n >> 8 & 255]);
304
+ return new Uint8Array([254, n & 255, n >> 8 & 255, n >> 16 & 255, n >> 24 & 255]);
305
+ }
306
+ function tapLeafHash(script, leafVersion = TAPROOT_LEAF_VERSION) {
307
+ return import_secp256k1.schnorr.utils.taggedHash(
308
+ "TapLeaf",
309
+ (0, import_utils.concatBytes)(new Uint8Array([leafVersion]), compactSize(script.length), script)
310
+ );
311
+ }
312
+ function resolveFallbackThreshold(advertised, n) {
313
+ return advertised ?? Math.max(1, n - 1);
314
+ }
315
+ function assertFallbackParams({ cohortKeys, fallbackThreshold }) {
316
+ const n = cohortKeys.length;
317
+ if (n === 0) {
318
+ throw new AggregationCohortError(
319
+ "Cannot build fallback leaf: no cohort keys.",
320
+ "NO_COHORT_KEYS"
321
+ );
322
+ }
323
+ for (const key of cohortKeys) {
324
+ if (key.length !== 33) {
325
+ throw new AggregationCohortError(
326
+ `Cohort key must be a 33-byte compressed public key, got ${key.length} bytes.`,
327
+ "INVALID_COHORT_KEY",
328
+ { length: key.length }
329
+ );
330
+ }
331
+ }
332
+ if (!Number.isInteger(fallbackThreshold) || fallbackThreshold < 1 || fallbackThreshold > n) {
333
+ throw new AggregationCohortError(
334
+ `Fallback threshold must be an integer in [1, ${n}] (k-of-n), got ${fallbackThreshold}.`,
335
+ "INVALID_FALLBACK_THRESHOLD",
336
+ { fallbackThreshold, n }
337
+ );
338
+ }
339
+ }
340
+ function buildFallbackLeaf(params) {
341
+ assertFallbackParams(params);
342
+ const xOnlyKeys = (0, import_musig2.sortKeys)(params.cohortKeys).map((k) => k.slice(1));
343
+ return (0, import_btc_signer.p2tr_ms)(params.fallbackThreshold, xOnlyKeys).script;
344
+ }
345
+ function assertRecoveryParams({ recoveryKey, recoverySequence }) {
346
+ if (recoveryKey.length !== 32) {
347
+ throw new AggregationCohortError(
348
+ `Recovery key must be a 32-byte x-only public key, got ${recoveryKey.length} bytes.`,
349
+ "INVALID_RECOVERY_KEY"
350
+ );
351
+ }
352
+ if (!Number.isInteger(recoverySequence) || recoverySequence < 1 || recoverySequence > MAX_RECOVERY_SEQUENCE) {
353
+ throw new AggregationCohortError(
354
+ `Recovery sequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}], got ${recoverySequence}.`,
355
+ "INVALID_RECOVERY_SEQUENCE"
356
+ );
357
+ }
358
+ }
359
+ function buildRecoveryScript(params) {
360
+ assertRecoveryParams(params);
361
+ return import_btc_signer.Script.encode([
362
+ params.recoverySequence,
363
+ "CHECKSEQUENCEVERIFY",
364
+ "DROP",
365
+ params.recoveryKey,
366
+ "CHECKSIG"
367
+ ]);
368
+ }
369
+ function buildRecoveryLeaves(fundingModel, params) {
370
+ switch (fundingModel) {
371
+ case "operator-funded":
372
+ return [
373
+ { script: buildFallbackLeaf(params) },
374
+ { script: buildRecoveryScript(params) }
375
+ ];
376
+ case "participant-funded":
377
+ throw new AggregationCohortError(
378
+ "Funding model 'participant-funded' is reserved and not yet implemented.",
379
+ "UNSUPPORTED_FUNDING_MODEL",
380
+ { fundingModel }
381
+ );
382
+ default:
383
+ throw new AggregationCohortError(
384
+ `Unknown funding model: ${fundingModel}.`,
385
+ "UNKNOWN_FUNDING_MODEL",
386
+ { fundingModel }
387
+ );
388
+ }
389
+ }
390
+
261
391
  // src/core/aggregation/cohort.ts
262
392
  var AggregationCohort = class {
263
393
  /** Unique identifier for the cohort. */
@@ -273,8 +403,8 @@ var AggregationCohort = class {
273
403
  /** List of participant DIDs that have been accepted into the cohort. */
274
404
  participants = [];
275
405
  /**
276
- * Mapping from participant DID their compressed secp256k1 public key.
277
- * Distinct from {@link cohortKeys} (which is sorted per BIP-327) this lets
406
+ * Mapping from participant DID to their compressed secp256k1 public key.
407
+ * Distinct from {@link cohortKeys} (which is sorted per BIP-327): this lets
278
408
  * callers look up a participant's key without knowing their position in the
279
409
  * sorted array. Populated by the service at `acceptParticipant` time.
280
410
  */
@@ -282,16 +412,41 @@ var AggregationCohort = class {
282
412
  /** Sorted list of cohort participants' compressed public keys. */
283
413
  #cohortKeys = [];
284
414
  /**
285
- * BIP-341 TapTweak `taggedHash("TapTweak", internalPubkey)` for a key-path-only
286
- * Taproot output. Despite prior naming, this is NOT a Merkle root: key-path-only
287
- * spends have no script tree.
415
+ * BIP-341 TapTweak scalar: `taggedHash("TapTweak", internalPubkey || tapMerkleRoot)`.
416
+ * The beacon output is an internal key (the MuSig2 aggregate) plus a script
417
+ * tree (the recovery leaves), so the tweak commits to the tree's Merkle root.
418
+ * The MuSig2 signing session applies this as an x-only tweak; a value that does
419
+ * not match the root the funded address was derived from silently yields an
420
+ * invalid key-path signature.
288
421
  */
289
422
  tapTweak = new Uint8Array();
290
- /** The n-of-n MuSig2 Taproot beacon address. */
423
+ /** The n-of-n MuSig2 aggregate internal key, x-only (32 bytes), set by computeBeaconAddress(). */
424
+ internalKey = new Uint8Array();
425
+ /** BIP-341 Taproot Merkle root of the recovery script tree, set by computeBeaconAddress(). */
426
+ tapMerkleRoot = new Uint8Array();
427
+ /** Operator recovery key, x-only (32 bytes). Used to build the CSV recovery leaf. */
428
+ recoveryKey;
429
+ /** Relative-timelock (BIP-68 nSequence) before the recovery leaf is spendable. */
430
+ recoverySequence;
431
+ /** Funding model governing the recovery leaves (default 'operator-funded'). */
432
+ fundingModel;
433
+ /**
434
+ * Advertised k of the k-of-n fallback leaf, or undefined to default to n-1 at
435
+ * address computation. Read the resolved value via {@link effectiveFallbackThreshold}.
436
+ */
437
+ fallbackThreshold;
438
+ /** The Taproot beacon address: key path is the MuSig2 aggregate, script path is fallback + recovery. */
291
439
  beaconAddress = "";
292
440
  /** Pending DID updates submitted by participants, keyed by DID. */
293
441
  pendingUpdates = /* @__PURE__ */ new Map();
294
- /** CAS Beacon Announcement Map (DID → updateHash), set by buildCASAnnouncement(). */
442
+ /**
443
+ * Participant DIDs that explicitly declined to submit an update this round
444
+ * (cooperative non-inclusion). A decliner is absent from the CAS Announcement
445
+ * Map and carries a non-inclusion leaf in the SMT, yet still signs. Kept
446
+ * disjoint from {@link pendingUpdates} so CAS correctness holds by construction.
447
+ */
448
+ nonIncluded = /* @__PURE__ */ new Set();
449
+ /** CAS Beacon Announcement Map (DID to updateHash), set by buildCASAnnouncement(). */
295
450
  casAnnouncement;
296
451
  /** Per-participant SMT proofs, set by buildSMTTree(). */
297
452
  smtProofs;
@@ -301,23 +456,45 @@ var AggregationCohort = class {
301
456
  validationAcks = /* @__PURE__ */ new Set();
302
457
  /** Set of participant DIDs that have rejected the aggregated data. */
303
458
  validationRejections = /* @__PURE__ */ new Set();
304
- constructor({ id, minParticipants, serviceDid, network, beaconType }) {
459
+ constructor({ id, minParticipants, serviceDid, network, beaconType, recoveryKey, recoverySequence, fundingModel, fallbackThreshold }) {
305
460
  this.id = id || crypto.randomUUID();
306
461
  this.minParticipants = minParticipants ?? 2;
307
462
  this.serviceDid = serviceDid || "";
308
463
  this.network = network;
309
464
  this.beaconType = beaconType || "CASBeacon";
465
+ this.recoveryKey = recoveryKey;
466
+ this.recoverySequence = recoverySequence;
467
+ this.fundingModel = fundingModel ?? DEFAULT_FUNDING_MODEL;
468
+ this.fallbackThreshold = fallbackThreshold;
310
469
  }
311
470
  /** Sorted cohort keys (sorted on assignment per BIP-327). */
312
471
  get cohortKeys() {
313
472
  return this.#cohortKeys;
314
473
  }
315
474
  set cohortKeys(keys) {
316
- this.#cohortKeys = (0, import_musig2.sortKeys)(keys);
475
+ this.#cohortKeys = (0, import_musig22.sortKeys)(keys);
476
+ }
477
+ /**
478
+ * The resolved k of the k-of-n fallback leaf: the advertised
479
+ * {@link fallbackThreshold}, or n-1 (floored at 1) when unadvertised, where n is
480
+ * the current cohort size. This is the value the beacon address commits to and
481
+ * the spend builders must reproduce. Returns 0 before any cohort keys are set.
482
+ */
483
+ get effectiveFallbackThreshold() {
484
+ if (this.#cohortKeys.length === 0) return 0;
485
+ return resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length);
317
486
  }
318
487
  /**
319
- * Computes the n-of-n MuSig2 Taproot beacon address from cohort keys.
320
- * Sets `tapTweak` to the BIP-341 key-path-only tweak.
488
+ * Computes the Taproot beacon address from the cohort keys and recovery params.
489
+ *
490
+ * The output's key path is the n-of-n MuSig2 aggregate of the cohort keys; its
491
+ * script path is the recovery tree (a CSV recovery leaf so a missing signer
492
+ * cannot permanently lock the UTXO). Sets `internalKey`, `tapMerkleRoot`, and
493
+ * the `tapTweak` the MuSig2 session must apply for the key-path spend.
494
+ *
495
+ * The tweak is derived from the Merkle root the address was built with (read
496
+ * back from the payment), never recomputed by hand, so the MuSig2 key-path
497
+ * signature is guaranteed to validate against the funded address.
321
498
  */
322
499
  computeBeaconAddress() {
323
500
  if (this.#cohortKeys.length === 0) {
@@ -327,10 +504,25 @@ var AggregationCohort = class {
327
504
  { cohortId: this.id }
328
505
  );
329
506
  }
330
- const keyAggContext = (0, import_musig2.keyAggregate)(this.#cohortKeys);
331
- const aggPubkey = (0, import_musig2.keyAggExport)(keyAggContext);
332
- const payment = (0, import_btc_signer.p2tr)(aggPubkey, void 0, (0, import_bitcoin.getNetwork)(this.network));
333
- this.tapTweak = import_secp256k1.schnorr.utils.taggedHash("TapTweak", aggPubkey);
507
+ if (!this.recoveryKey || this.recoveryKey.length === 0 || this.recoverySequence === void 0) {
508
+ throw new AggregationCohortError(
509
+ "Cannot compute beacon address: missing recovery key or sequence.",
510
+ "NO_RECOVERY_PARAMS",
511
+ { cohortId: this.id }
512
+ );
513
+ }
514
+ const keyAggContext = (0, import_musig22.keyAggregate)(this.#cohortKeys);
515
+ const aggPubkey = (0, import_musig22.keyAggExport)(keyAggContext);
516
+ const leaves = buildRecoveryLeaves(this.fundingModel, {
517
+ recoveryKey: this.recoveryKey,
518
+ recoverySequence: this.recoverySequence,
519
+ cohortKeys: this.#cohortKeys,
520
+ fallbackThreshold: resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length)
521
+ });
522
+ const payment = (0, import_btc_signer2.p2tr)(aggPubkey, leaves, (0, import_bitcoin.getNetwork)(this.network), true);
523
+ this.internalKey = aggPubkey;
524
+ this.tapMerkleRoot = payment.tapMerkleRoot;
525
+ this.tapTweak = import_secp256k12.schnorr.utils.taggedHash("TapTweak", (0, import_utils2.concatBytes)(aggPubkey, payment.tapMerkleRoot));
334
526
  if (!payment.address) {
335
527
  throw new AggregationCohortError(
336
528
  "Failed to compute Taproot address",
@@ -354,7 +546,7 @@ var AggregationCohort = class {
354
546
  { cohortId: this.id, participantPkHex }
355
547
  );
356
548
  }
357
- this.cohortKeys = cohortKeysHex.map((k) => (0, import_utils.hexToBytes)(k));
549
+ this.cohortKeys = cohortKeysHex.map((k) => (0, import_utils2.hexToBytes)(k));
358
550
  const computed = this.computeBeaconAddress();
359
551
  if (computed !== expectedBeaconAddress) {
360
552
  throw new AggregationCohortError(
@@ -386,20 +578,54 @@ var AggregationCohort = class {
386
578
  }
387
579
  this.pendingUpdates.set(participantDid, signedUpdate);
388
580
  }
581
+ /**
582
+ * Record that a participant declined to submit an update this round
583
+ * (cooperative non-inclusion). The member stays in the cohort and still signs.
584
+ */
585
+ addNonInclusion(participantDid) {
586
+ if (!this.participants.includes(participantDid)) {
587
+ throw new AggregationCohortError(
588
+ `Participant ${participantDid} is not in cohort ${this.id}.`,
589
+ "UNKNOWN_PARTICIPANT",
590
+ { cohortId: this.id, participantDid }
591
+ );
592
+ }
593
+ if (this.pendingUpdates.has(participantDid)) {
594
+ throw new AggregationCohortError(
595
+ `Participant ${participantDid} already submitted an update; cannot also decline.`,
596
+ "CONFLICTING_RESPONSE",
597
+ { cohortId: this.id, participantDid }
598
+ );
599
+ }
600
+ this.nonIncluded.add(participantDid);
601
+ }
389
602
  hasAllUpdates() {
390
603
  return this.pendingUpdates.size === this.participants.length;
391
604
  }
605
+ /**
606
+ * True when every participant has responded for this round, either with an
607
+ * update or with an explicit non-inclusion. This is the aggregation gate when
608
+ * non-inclusion is in play; it generalizes {@link hasAllUpdates} the same way
609
+ * {@link hasAllValidationResponses} generalizes a unanimous ack.
610
+ */
611
+ hasAllResponses() {
612
+ return this.pendingUpdates.size + this.nonIncluded.size === this.participants.length;
613
+ }
392
614
  /**
393
615
  * Builds a CAS Announcement Map from collected updates.
394
- * Maps each participant DID base64url canonical hash of their signed update.
616
+ * Maps each participant DID to base64url canonical hash of their signed update.
395
617
  * Computes signal bytes as SHA-256 of canonicalized announcement.
618
+ *
619
+ * Members who declined (cooperative non-inclusion) are naturally absent from
620
+ * the map: the body iterates {@link pendingUpdates}, which never holds a
621
+ * decliner. Absence from the map is exactly the CAS non-inclusion signal.
396
622
  */
397
623
  buildCASAnnouncement() {
398
- if (!this.hasAllUpdates()) {
624
+ if (!this.hasAllResponses()) {
399
625
  throw new AggregationCohortError(
400
- "Cannot build CAS Announcement: not all updates collected.",
401
- "INCOMPLETE_UPDATES",
402
- { cohortId: this.id, collected: this.pendingUpdates.size, total: this.participants.length }
626
+ "Cannot build CAS Announcement: not all participants have responded.",
627
+ "INCOMPLETE_RESPONSES",
628
+ { cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
403
629
  );
404
630
  }
405
631
  const announcement = {};
@@ -411,31 +637,41 @@ var AggregationCohort = class {
411
637
  return announcement;
412
638
  }
413
639
  /**
414
- * Builds an SMT tree from collected updates.
415
- * Each entry uses a random 32-byte nonce + canonicalized signed update bytes.
416
- * Stores per-participant proofs and the SMT root as signalBytes.
640
+ * Builds an SMT tree with one leaf per participant.
641
+ *
642
+ * A member who submitted an update gets an inclusion leaf
643
+ * (SHA-256(SHA-256(nonce) || SHA-256(update))); a member who declined gets a
644
+ * non-inclusion leaf (SHA-256(SHA-256(nonce)), the `signedUpdate` entry field
645
+ * omitted). The cohort mints each member's nonce and returns it inside that
646
+ * member's serialized proof, so a decliner can self-validate its own
647
+ * non-inclusion slot and the resolver can recompute the leaf. Stores
648
+ * per-participant proofs and the SMT root as signalBytes.
417
649
  */
418
650
  buildSMTTree() {
419
- if (!this.hasAllUpdates()) {
651
+ if (!this.hasAllResponses()) {
420
652
  throw new AggregationCohortError(
421
- "Cannot build SMT tree: not all updates collected.",
422
- "INCOMPLETE_UPDATES",
423
- { cohortId: this.id }
653
+ "Cannot build SMT tree: not all participants have responded.",
654
+ "INCOMPLETE_RESPONSES",
655
+ { cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
424
656
  );
425
657
  }
426
658
  const tree = new import_smt2.BTCR2MerkleTree();
427
659
  const entries = [];
428
660
  const encoder = new TextEncoder();
429
- for (const [did, signedUpdate] of this.pendingUpdates) {
430
- const canonicalBytes = encoder.encode((0, import_common3.canonicalize)(signedUpdate));
431
- const nonce = (0, import_utils.randomBytes)(32);
432
- entries.push({ did, nonce, signedUpdate: canonicalBytes });
661
+ for (const did of this.participants) {
662
+ const nonce = (0, import_utils2.randomBytes)(32);
663
+ const signedUpdate = this.pendingUpdates.get(did);
664
+ if (signedUpdate) {
665
+ entries.push({ did, nonce, signedUpdate: encoder.encode((0, import_common3.canonicalize)(signedUpdate)) });
666
+ } else {
667
+ entries.push({ did, nonce });
668
+ }
433
669
  }
434
670
  tree.addEntries(entries);
435
671
  tree.finalize();
436
672
  this.signalBytes = tree.rootHash;
437
673
  this.smtProofs = /* @__PURE__ */ new Map();
438
- for (const [did] of this.pendingUpdates) {
674
+ for (const did of this.participants) {
439
675
  this.smtProofs.set(did, tree.proof(did));
440
676
  }
441
677
  return this.smtProofs;
@@ -461,7 +697,7 @@ var AggregationCohort = class {
461
697
  return this.validationAcks.size + this.validationRejections.size === this.participants.length;
462
698
  }
463
699
  /**
464
- * True when all participants approved. Note: differs from {@link hasAllValidationResponses}
700
+ * True when all participants approved. Note: differs from {@link hasAllValidationResponses},
465
701
  * this returns false if any participant rejected, even if all responses are in.
466
702
  */
467
703
  isFullyValidated() {
@@ -471,6 +707,7 @@ var AggregationCohort = class {
471
707
 
472
708
  // src/core/aggregation/conditions.ts
473
709
  var KNOWN_BEACON_TYPES = ["CASBeacon", "SMTBeacon"];
710
+ var KNOWN_FUNDING_MODELS = ["operator-funded", "participant-funded"];
474
711
  function checkPair(problems, label, min, max) {
475
712
  if (min !== void 0 && (!Number.isInteger(min) || min < 0)) {
476
713
  problems.push(`min${label} must be an integer >= 0`);
@@ -514,11 +751,130 @@ function validateCohortConditions(c) {
514
751
  if (c.pendingUpdateTrigger !== void 0 && (!Number.isInteger(c.pendingUpdateTrigger) || c.pendingUpdateTrigger < 1)) {
515
752
  problems.push("pendingUpdateTrigger must be an integer >= 1");
516
753
  }
754
+ if (c.fallbackThreshold !== void 0) {
755
+ if (!Number.isInteger(c.fallbackThreshold) || c.fallbackThreshold < 1) {
756
+ problems.push("fallbackThreshold must be an integer >= 1");
757
+ } else if (c.maxParticipants !== void 0 && Number.isInteger(c.maxParticipants) && c.fallbackThreshold > c.maxParticipants) {
758
+ problems.push("fallbackThreshold must be <= maxParticipants");
759
+ }
760
+ }
517
761
  checkCost(problems, "costOfEnrollment", c.costOfEnrollment);
518
762
  checkCost(problems, "costPerAnnouncement", c.costPerAnnouncement);
763
+ if (typeof c.recoveryKey !== "string" || !/^[0-9a-fA-F]{64}$/.test(c.recoveryKey)) {
764
+ problems.push("recoveryKey must be a 64-character hex string (x-only public key)");
765
+ }
766
+ if (!Number.isInteger(c.recoverySequence) || c.recoverySequence < 1 || c.recoverySequence > MAX_RECOVERY_SEQUENCE) {
767
+ problems.push(`recoverySequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}]`);
768
+ }
769
+ if (c.fundingModel !== void 0 && !KNOWN_FUNDING_MODELS.includes(c.fundingModel)) {
770
+ problems.push(`fundingModel must be one of ${KNOWN_FUNDING_MODELS.join(", ")}`);
771
+ }
519
772
  return problems;
520
773
  }
521
774
 
775
+ // src/core/aggregation/fallback-spend.ts
776
+ var import_bitcoin2 = require("@did-btcr2/bitcoin");
777
+ var import_secp256k13 = require("@noble/curves/secp256k1.js");
778
+ var import_btc_signer3 = require("@scure/btc-signer");
779
+ var import_musig23 = require("@scure/btc-signer/musig2");
780
+ function bytesEqual(a, b) {
781
+ return a.length === b.length && a.every((x, i) => x === b[i]);
782
+ }
783
+ function fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeafScript) {
784
+ return tx.preimageWitnessV1(
785
+ inputIndex,
786
+ [prevOutScript],
787
+ import_btc_signer3.SigHash.DEFAULT,
788
+ [prevOutValue],
789
+ void 0,
790
+ fallbackLeafScript,
791
+ TAPROOT_LEAF_VERSION
792
+ );
793
+ }
794
+ function buildFallbackSpend(params) {
795
+ const {
796
+ pendingTx,
797
+ cohortKeys,
798
+ recoveryKey,
799
+ recoverySequence,
800
+ fundingModel,
801
+ network,
802
+ prevOutScript,
803
+ prevOutValue,
804
+ signatures
805
+ } = params;
806
+ const inputIndex = params.inputIndex ?? 0;
807
+ if (cohortKeys.length === 0) {
808
+ throw new AggregationCohortError(
809
+ "Cannot build fallback spend: no cohort keys.",
810
+ "NO_COHORT_KEYS"
811
+ );
812
+ }
813
+ const fallbackThreshold = resolveFallbackThreshold(params.fallbackThreshold, cohortKeys.length);
814
+ const internalKey = (0, import_musig23.keyAggExport)((0, import_musig23.keyAggregate)((0, import_musig23.sortKeys)(cohortKeys)));
815
+ const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
816
+ recoveryKey,
817
+ recoverySequence,
818
+ cohortKeys,
819
+ fallbackThreshold
820
+ });
821
+ const net = (0, import_bitcoin2.getNetwork)(network);
822
+ const payment = (0, import_btc_signer3.p2tr)(internalKey, leaves, net, true);
823
+ if (!bytesEqual(payment.script, prevOutScript)) {
824
+ throw new AggregationCohortError(
825
+ "Reconstructed beacon output script does not match the spent prevout script.",
826
+ "PREVOUT_SCRIPT_MISMATCH"
827
+ );
828
+ }
829
+ const fallbackLeaf = buildFallbackLeaf({ cohortKeys, fallbackThreshold });
830
+ const leafEntry = payment.tapLeafScript?.find(([, scriptVer]) => {
831
+ const script = scriptVer.slice(0, scriptVer.length - 1);
832
+ return bytesEqual(script, fallbackLeaf);
833
+ });
834
+ if (!leafEntry) {
835
+ throw new AggregationCohortError(
836
+ "Could not locate the fallback leaf in the reconstructed beacon output.",
837
+ "FALLBACK_LEAF_NOT_FOUND"
838
+ );
839
+ }
840
+ const cohortXOnly = (0, import_musig23.sortKeys)(cohortKeys).map((k) => k.slice(1));
841
+ const tx = pendingTx;
842
+ const sighash = fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeaf);
843
+ const leafHash = tapLeafHash(fallbackLeaf);
844
+ const accepted = /* @__PURE__ */ new Map();
845
+ for (const { pubKey, signature } of signatures) {
846
+ if (pubKey.length !== 32 || signature.length !== 64) continue;
847
+ const isCohortKey = cohortXOnly.some((k) => bytesEqual(k, pubKey));
848
+ if (!isCohortKey) continue;
849
+ const hex = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
850
+ if (accepted.has(hex)) continue;
851
+ let ok = false;
852
+ try {
853
+ ok = import_secp256k13.schnorr.verify(signature, sighash, pubKey);
854
+ } catch {
855
+ ok = false;
856
+ }
857
+ if (ok) accepted.set(hex, signature);
858
+ }
859
+ if (accepted.size < fallbackThreshold) {
860
+ throw new AggregationCohortError(
861
+ `Not enough valid fallback signatures: have ${accepted.size}, need ${fallbackThreshold}.`,
862
+ "NOT_ENOUGH_FALLBACK_SIGNATURES",
863
+ { have: accepted.size, need: fallbackThreshold }
864
+ );
865
+ }
866
+ const chosen = cohortXOnly.filter((k) => accepted.has(Array.from(k, (b) => b.toString(16).padStart(2, "0")).join(""))).slice(0, fallbackThreshold);
867
+ tx.updateInput(inputIndex, {
868
+ tapLeafScript: [leafEntry],
869
+ tapScriptSig: chosen.map((pubKey) => {
870
+ const hex = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
871
+ return [{ pubKey, leafHash }, accepted.get(hex)];
872
+ })
873
+ });
874
+ tx.finalize();
875
+ return tx;
876
+ }
877
+
522
878
  // src/core/aggregation/messages/base.ts
523
879
  var AGGREGATION_WIRE_VERSION = 1;
524
880
  var BaseMessage = class {
@@ -556,12 +912,15 @@ var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
556
912
  var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
557
913
  var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
558
914
  var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
915
+ var SUBMIT_NONINCLUDED = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_nonincluded`;
559
916
  var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
560
917
  var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
561
918
  var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
562
919
  var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
563
920
  var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
564
921
  var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
922
+ var FALLBACK_AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_authorization_request`;
923
+ var FALLBACK_SIGNATURE = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_signature`;
565
924
 
566
925
  // src/core/aggregation/messages/factories.ts
567
926
  function createCohortAdvertMessage(fields) {
@@ -584,6 +943,10 @@ function createSubmitUpdateMessage(fields) {
584
943
  const { from: from2, to, ...body } = fields;
585
944
  return new BaseMessage({ type: SUBMIT_UPDATE, from: from2, to, body });
586
945
  }
946
+ function createSubmitNonIncludedMessage(fields) {
947
+ const { from: from2, to, ...body } = fields;
948
+ return new BaseMessage({ type: SUBMIT_NONINCLUDED, from: from2, to, body });
949
+ }
587
950
  function createDistributeAggregatedDataMessage(fields) {
588
951
  const { from: from2, to, ...body } = fields;
589
952
  return new BaseMessage({ type: DISTRIBUTE_AGGREGATED_DATA, from: from2, to, body });
@@ -608,6 +971,14 @@ function createSignatureAuthorizationMessage(fields) {
608
971
  const { from: from2, to, ...body } = fields;
609
972
  return new BaseMessage({ type: SIGNATURE_AUTHORIZATION, from: from2, to, body });
610
973
  }
974
+ function createFallbackAuthorizationRequestMessage(fields) {
975
+ const { from: from2, to, ...body } = fields;
976
+ return new BaseMessage({ type: FALLBACK_AUTHORIZATION_REQUEST, from: from2, to, body });
977
+ }
978
+ function createFallbackSignatureMessage(fields) {
979
+ const { from: from2, to, ...body } = fields;
980
+ return new BaseMessage({ type: FALLBACK_SIGNATURE, from: from2, to, body });
981
+ }
611
982
 
612
983
  // src/core/aggregation/phases.ts
613
984
  var ServiceCohortPhase = /* @__PURE__ */ ((ServiceCohortPhase2) => {
@@ -621,6 +992,7 @@ var ServiceCohortPhase = /* @__PURE__ */ ((ServiceCohortPhase2) => {
621
992
  ServiceCohortPhase2["SigningStarted"] = "SigningStarted";
622
993
  ServiceCohortPhase2["NoncesCollected"] = "NoncesCollected";
623
994
  ServiceCohortPhase2["AwaitingPartialSigs"] = "AwaitingPartialSigs";
995
+ ServiceCohortPhase2["FallbackRequested"] = "FallbackRequested";
624
996
  ServiceCohortPhase2["Complete"] = "Complete";
625
997
  ServiceCohortPhase2["Failed"] = "Failed";
626
998
  return ServiceCohortPhase2;
@@ -630,11 +1002,13 @@ var ParticipantCohortPhase = /* @__PURE__ */ ((ParticipantCohortPhase2) => {
630
1002
  ParticipantCohortPhase2["OptedIn"] = "OptedIn";
631
1003
  ParticipantCohortPhase2["CohortReady"] = "CohortReady";
632
1004
  ParticipantCohortPhase2["UpdateSubmitted"] = "UpdateSubmitted";
1005
+ ParticipantCohortPhase2["NonIncluded"] = "NonIncluded";
633
1006
  ParticipantCohortPhase2["AwaitingValidation"] = "AwaitingValidation";
634
1007
  ParticipantCohortPhase2["ValidationSent"] = "ValidationSent";
635
1008
  ParticipantCohortPhase2["AwaitingSigning"] = "AwaitingSigning";
636
1009
  ParticipantCohortPhase2["NonceSent"] = "NonceSent";
637
1010
  ParticipantCohortPhase2["AwaitingPartialSig"] = "AwaitingPartialSig";
1011
+ ParticipantCohortPhase2["AwaitingFallbackSig"] = "AwaitingFallbackSig";
638
1012
  ParticipantCohortPhase2["Complete"] = "Complete";
639
1013
  ParticipantCohortPhase2["Failed"] = "Failed";
640
1014
  return ParticipantCohortPhase2;
@@ -651,7 +1025,7 @@ var SigningSessionPhase = /* @__PURE__ */ ((SigningSessionPhase2) => {
651
1025
 
652
1026
  // src/core/aggregation/signing-session.ts
653
1027
  var import_keypair = require("@did-btcr2/keypair");
654
- var import_btc_signer2 = require("@scure/btc-signer");
1028
+ var import_btc_signer4 = require("@scure/btc-signer");
655
1029
  var musig2 = __toESM(require("@scure/btc-signer/musig2"), 1);
656
1030
  var BeaconSigningSession = class {
657
1031
  /** Unique identifier for this signing session. */
@@ -664,11 +1038,11 @@ var BeaconSigningSession = class {
664
1038
  prevOutScripts;
665
1039
  /** Previous output values for Taproot sighash computation. */
666
1040
  prevOutValues;
667
- /** Map of participant publicKey-hex public nonce contribution. */
1041
+ /** Map of participant publicKey-hex to public nonce contribution. */
668
1042
  nonceContributions = /* @__PURE__ */ new Map();
669
1043
  /** Aggregated MuSig2 nonce (66 bytes). */
670
1044
  aggregatedNonce;
671
- /** Map of participant DID partial signature. */
1045
+ /** Map of participant DID to partial signature. */
672
1046
  partialSignatures = /* @__PURE__ */ new Map();
673
1047
  /** Final 64-byte Schnorr signature. */
674
1048
  signature;
@@ -703,7 +1077,7 @@ var BeaconSigningSession = class {
703
1077
  return this.pendingTx.preimageWitnessV1(
704
1078
  0,
705
1079
  this.prevOutScripts,
706
- import_btc_signer2.SigHash.DEFAULT,
1080
+ import_btc_signer4.SigHash.DEFAULT,
707
1081
  this.prevOutValues
708
1082
  );
709
1083
  }
@@ -851,7 +1225,7 @@ var BeaconSigningSession = class {
851
1225
  throw new SigningSessionError("Aggregated nonce not available.", "MISSING_AGGREGATED_NONCE");
852
1226
  }
853
1227
  if (!this.#secretNonce) {
854
- throw new SigningSessionError("Secret nonce not available \u2014 generateNonceContribution() must be called first.", "MISSING_SECRET_NONCE");
1228
+ throw new SigningSessionError("Secret nonce not available - generateNonceContribution() must be called first.", "MISSING_SECRET_NONCE");
855
1229
  }
856
1230
  const session = new musig2.Session(
857
1231
  this.aggregatedNonce,
@@ -921,6 +1295,9 @@ var AggregationService = class {
921
1295
  case SUBMIT_UPDATE:
922
1296
  this.#handleSubmitUpdate(message);
923
1297
  break;
1298
+ case SUBMIT_NONINCLUDED:
1299
+ this.#handleSubmitNonInclusion(message);
1300
+ break;
924
1301
  case VALIDATION_ACK:
925
1302
  this.#handleValidationAck(message);
926
1303
  break;
@@ -930,6 +1307,9 @@ var AggregationService = class {
930
1307
  case SIGNATURE_AUTHORIZATION:
931
1308
  this.#handleSignatureAuthorization(message);
932
1309
  break;
1310
+ case FALLBACK_SIGNATURE:
1311
+ this.#handleFallbackSignature(message);
1312
+ break;
933
1313
  default:
934
1314
  break;
935
1315
  }
@@ -948,7 +1328,7 @@ var AggregationService = class {
948
1328
  }
949
1329
  /**
950
1330
  * Create a new cohort with the given config. Returns the cohort ID.
951
- * Cohort starts in `Created` phase call `advertise()` to broadcast.
1331
+ * Cohort starts in `Created` phase - call `advertise()` to broadcast.
952
1332
  */
953
1333
  createCohort(config) {
954
1334
  const problems = validateCohortConditions(config);
@@ -963,7 +1343,11 @@ var AggregationService = class {
963
1343
  serviceDid: this.did,
964
1344
  minParticipants: config.minParticipants,
965
1345
  network: config.network,
966
- beaconType: config.beaconType
1346
+ beaconType: config.beaconType,
1347
+ recoveryKey: (0, import_utils3.hexToBytes)(config.recoveryKey),
1348
+ recoverySequence: config.recoverySequence,
1349
+ fundingModel: config.fundingModel,
1350
+ fallbackThreshold: config.fallbackThreshold
967
1351
  });
968
1352
  this.#cohortStates.set(cohort.id, {
969
1353
  phase: "Created" /* Created */,
@@ -1164,11 +1548,64 @@ var AggregationService = class {
1164
1548
  });
1165
1549
  return;
1166
1550
  }
1551
+ if (state.cohort.nonIncluded.has(message.from)) {
1552
+ state.rejections.push({
1553
+ from: message.from,
1554
+ code: "UPDATE_MALFORMED",
1555
+ reason: "Participant already declined this round; cannot also submit an update"
1556
+ });
1557
+ return;
1558
+ }
1559
+ if (state.cohort.pendingUpdates.has(message.from)) {
1560
+ state.rejections.push({
1561
+ from: message.from,
1562
+ code: "UPDATE_MALFORMED",
1563
+ reason: "Participant already submitted an update this round; cannot resubmit"
1564
+ });
1565
+ return;
1566
+ }
1167
1567
  state.cohort.addUpdate(message.from, signedUpdate);
1168
1568
  if (state.phase === "CohortSet" /* CohortSet */) {
1169
1569
  state.phase = "CollectingUpdates" /* CollectingUpdates */;
1170
1570
  }
1171
- if (state.cohort.hasAllUpdates()) {
1571
+ if (state.cohort.hasAllResponses()) {
1572
+ state.phase = "UpdatesCollected" /* UpdatesCollected */;
1573
+ }
1574
+ }
1575
+ /**
1576
+ * Handle an incoming SUBMIT_NONINCLUDED message: a member declares it has no
1577
+ * update this round (cooperative non-inclusion). Membership is proven by the
1578
+ * signed transport envelope, so the body carries only the cohortId. The member
1579
+ * stays in the cohort and still signs; it is absent from the CAS map and
1580
+ * carries a non-inclusion leaf in the SMT.
1581
+ */
1582
+ #handleSubmitNonInclusion(message) {
1583
+ const cohortId = message.body?.cohortId;
1584
+ if (!cohortId) return;
1585
+ const state = this.#cohortStates.get(cohortId);
1586
+ if (!state) return;
1587
+ if (state.phase !== "CohortSet" /* CohortSet */ && state.phase !== "CollectingUpdates" /* CollectingUpdates */) return;
1588
+ if (!state.cohort.participants.includes(message.from)) {
1589
+ state.rejections.push({
1590
+ from: message.from,
1591
+ code: "UPDATE_MALFORMED",
1592
+ reason: "Sender is not a member of this cohort"
1593
+ });
1594
+ return;
1595
+ }
1596
+ if (state.cohort.pendingUpdates.has(message.from) || state.cohort.nonIncluded.has(message.from)) {
1597
+ state.rejections.push({
1598
+ from: message.from,
1599
+ code: "UPDATE_MALFORMED",
1600
+ reason: "Participant already responded this round"
1601
+ });
1602
+ return;
1603
+ }
1604
+ state.cohort.addNonInclusion(message.from);
1605
+ if (state.phase === "CohortSet" /* CohortSet */) {
1606
+ state.phase = "CollectingUpdates" /* CollectingUpdates */;
1607
+ }
1608
+ if (state.cohort.hasAllResponses()) {
1172
1609
  state.phase = "UpdatesCollected" /* UpdatesCollected */;
1173
1610
  }
1174
1611
  }
@@ -1227,7 +1664,7 @@ var AggregationService = class {
1227
1664
  );
1228
1665
  }
1229
1666
  strategy.buildAggregatedData(state.cohort);
1230
- const signalBytesHex = (0, import_utils2.bytesToHex)(state.cohort.signalBytes);
1667
+ const signalBytesHex = (0, import_utils3.bytesToHex)(state.cohort.signalBytes);
1231
1668
  state.phase = "DataDistributed" /* DataDistributed */;
1232
1669
  const messages = [];
1233
1670
  for (const participantDid of state.cohort.participants) {
@@ -1278,7 +1715,7 @@ var AggregationService = class {
1278
1715
  }
1279
1716
  /**
1280
1717
  * Start a signing session by creating auth requests for all participants.
1281
- * The caller provides the transaction data typically built via
1718
+ * The caller provides the transaction data - typically built via
1282
1719
  * `buildBeaconTransaction()` against a Bitcoin connection.
1283
1720
  */
1284
1721
  startSigning(cohortId, txData) {
@@ -1317,7 +1754,7 @@ var AggregationService = class {
1317
1754
  cohortId,
1318
1755
  sessionId: session.id,
1319
1756
  pendingTx: txData.tx.hex,
1320
- prevOutScriptHex: (0, import_utils2.bytesToHex)(prevOutScript),
1757
+ prevOutScriptHex: (0, import_utils3.bytesToHex)(prevOutScript),
1321
1758
  prevOutValue: txData.prevOutValues[0]?.toString() ?? "0"
1322
1759
  }));
1323
1760
  }
@@ -1385,11 +1822,144 @@ var AggregationService = class {
1385
1822
  state.result = {
1386
1823
  cohortId,
1387
1824
  signature,
1388
- signedTx: state.signingSession.pendingTx
1825
+ signedTx: state.signingSession.pendingTx,
1826
+ path: "key-path"
1389
1827
  };
1390
1828
  state.phase = "Complete" /* Complete */;
1391
1829
  }
1392
1830
  }
1831
+ /**
1832
+ * Abandon the optimistic n-of-n key path and ask members to authorize the
1833
+ * k-of-n fallback (script-path) spend of the SAME beacon transaction (graceful
1834
+ * liveness, ADR 042). Reuses the in-flight signing session's transaction and
1835
+ * spent output, so the announcement and its outputs are unchanged: only the
1836
+ * witness path differs. Returns one FALLBACK_AUTHORIZATION_REQUEST per
1837
+ * participant.
1838
+ *
1839
+ * Callable once optimistic signing has started (the session and its tx exist)
1840
+ * and before it completes. A cohort can take exactly one of the two paths: the
1841
+ * caller (runner) must commit to fallback and stop driving the optimistic path.
1842
+ */
1843
+ startFallbackSigning(cohortId) {
1844
+ const state = this.#cohortStates.get(cohortId);
1845
+ if (!state) {
1846
+ throw new AggregationServiceError(`Cohort ${cohortId} not found.`, "COHORT_NOT_FOUND", { cohortId });
1847
+ }
1848
+ if (!state.signingSession) {
1849
+ throw new AggregationServiceError(
1850
+ `Cannot start fallback for cohort ${cohortId}: no signing session.`,
1851
+ "NO_SIGNING_SESSION",
1852
+ { cohortId }
1853
+ );
1854
+ }
1855
+ const signingPhases = [
1856
+ "SigningStarted" /* SigningStarted */,
1857
+ "NoncesCollected" /* NoncesCollected */,
1858
+ "AwaitingPartialSigs" /* AwaitingPartialSigs */
1859
+ ];
1860
+ if (!signingPhases.includes(state.phase)) {
1861
+ throw new AggregationServiceError(
1862
+ `Cannot start fallback for cohort ${cohortId}: phase is ${state.phase}.`,
1863
+ "INVALID_PHASE",
1864
+ { cohortId, phase: state.phase }
1865
+ );
1866
+ }
1867
+ const session = state.signingSession;
1868
+ const prevOutScript = session.prevOutScripts[0];
1869
+ const prevOutValue = session.prevOutValues[0];
1870
+ if (!prevOutScript || prevOutValue === void 0) {
1871
+ throw new AggregationServiceError(
1872
+ `Cannot start fallback for cohort ${cohortId}: signing session missing prevout data.`,
1873
+ "MISSING_PREV_OUT",
1874
+ { cohortId }
1875
+ );
1876
+ }
1877
+ const fallbackLeaf = buildFallbackLeaf({
1878
+ cohortKeys: state.cohort.cohortKeys,
1879
+ fallbackThreshold: state.cohort.effectiveFallbackThreshold
1880
+ });
1881
+ state.fallbackSignatures = /* @__PURE__ */ new Map();
1882
+ state.phase = "FallbackRequested" /* FallbackRequested */;
1883
+ const messages = [];
1884
+ for (const participantDid of state.cohort.participants) {
1885
+ messages.push(createFallbackAuthorizationRequestMessage({
1886
+ from: this.did,
1887
+ to: participantDid,
1888
+ cohortId,
1889
+ sessionId: session.id,
1890
+ pendingTx: session.pendingTx.hex,
1891
+ prevOutScriptHex: (0, import_utils3.bytesToHex)(prevOutScript),
1892
+ prevOutValue: prevOutValue.toString(),
1893
+ fallbackLeafScriptHex: (0, import_utils3.bytesToHex)(fallbackLeaf)
1894
+ }));
1895
+ }
1896
+ return messages;
1897
+ }
1898
+ /**
1899
+ * Handle an incoming FALLBACK_SIGNATURE: a member's standalone BIP-340
1900
+ * signature over the fallback script-path sighash. The signature is
1901
+ * authenticated to the sender (its `signerPk` must be the sender's own cohort
1902
+ * key and the signature must verify against the sighash) and collected. Once k
1903
+ * valid signatures are in, the k-of-n fallback spend is assembled and the
1904
+ * cohort completes via the script path.
1905
+ */
1906
+ #handleFallbackSignature(message) {
1907
+ const cohortId = message.body?.cohortId;
1908
+ if (!cohortId) return;
1909
+ const state = this.#cohortStates.get(cohortId);
1910
+ if (!state || !state.signingSession || !state.fallbackSignatures) return;
1911
+ if (state.phase !== "FallbackRequested" /* FallbackRequested */) return;
1912
+ const sessionId = message.body?.sessionId;
1913
+ if (sessionId !== state.signingSession.id) return;
1914
+ const signerPk = message.body?.signerPk;
1915
+ const fallbackSignature = message.body?.fallbackSignature;
1916
+ if (!signerPk || !fallbackSignature) return;
1917
+ const prevOutScript = state.signingSession.prevOutScripts[0];
1918
+ const prevOutValue = state.signingSession.prevOutValues[0];
1919
+ if (!prevOutScript || prevOutValue === void 0) return;
1920
+ const memberKey = state.cohort.participantKeys.get(message.from);
1921
+ if (!memberKey) {
1922
+ state.rejections.push({ from: message.from, code: "UPDATE_MALFORMED", reason: "Fallback signature from a non-member" });
1923
+ return;
1924
+ }
1925
+ const memberXOnly = memberKey.slice(1);
1926
+ if (signerPk.length !== 32 || !memberXOnly.every((b, i) => b === signerPk[i])) {
1927
+ state.rejections.push({ from: message.from, code: "UPDATE_MALFORMED", reason: "Fallback signerPk does not match the sender cohort key" });
1928
+ return;
1929
+ }
1930
+ const fallbackLeaf = buildFallbackLeaf({
1931
+ cohortKeys: state.cohort.cohortKeys,
1932
+ fallbackThreshold: state.cohort.effectiveFallbackThreshold
1933
+ });
1934
+ const sighash = fallbackSighash(state.signingSession.pendingTx, 0, prevOutScript, prevOutValue, fallbackLeaf);
1935
+ let valid = false;
1936
+ try {
1937
+ valid = fallbackSignature.length === 64 && import_secp256k14.schnorr.verify(fallbackSignature, sighash, signerPk);
1938
+ } catch {
1939
+ valid = false;
1940
+ }
1941
+ if (!valid) {
1942
+ state.rejections.push({ from: message.from, code: "UPDATE_VERIFICATION_FAILED", reason: "Fallback signature failed verification" });
1943
+ return;
1944
+ }
1945
+ state.fallbackSignatures.set(message.from, { pubKey: signerPk, signature: fallbackSignature });
1946
+ if (state.fallbackSignatures.size >= state.cohort.effectiveFallbackThreshold) {
1947
+ const signedTx = buildFallbackSpend({
1948
+ pendingTx: state.signingSession.pendingTx,
1949
+ cohortKeys: state.cohort.cohortKeys,
1950
+ fallbackThreshold: state.cohort.effectiveFallbackThreshold,
1951
+ recoveryKey: state.cohort.recoveryKey,
1952
+ recoverySequence: state.cohort.recoverySequence,
1953
+ fundingModel: state.cohort.fundingModel,
1954
+ network: state.cohort.network,
1955
+ prevOutScript,
1956
+ prevOutValue,
1957
+ signatures: [...state.fallbackSignatures.values()]
1958
+ });
1959
+ state.result = { cohortId, signature: new Uint8Array(), signedTx, path: "script-path" };
1960
+ state.phase = "Complete" /* Complete */;
1961
+ }
1962
+ }
1393
1963
  getResult(cohortId) {
1394
1964
  return this.#cohortStates.get(cohortId)?.result;
1395
1965
  }
@@ -1421,8 +1991,9 @@ var AggregationService = class {
1421
1991
 
1422
1992
  // src/core/aggregation/participant.ts
1423
1993
  var import_common5 = require("@did-btcr2/common");
1424
- var import_utils3 = require("@noble/hashes/utils");
1425
- var import_btc_signer3 = require("@scure/btc-signer");
1994
+ var import_secp256k15 = require("@noble/curves/secp256k1.js");
1995
+ var import_utils4 = require("@noble/hashes/utils");
1996
+ var import_btc_signer5 = require("@scure/btc-signer");
1426
1997
 
1427
1998
  // src/core/aggregation/messages/bodies.ts
1428
1999
  var hasStr = (b, k) => !!b && typeof b[k] === "string";
@@ -1434,6 +2005,14 @@ var optIntMin = (b, k, min) => {
1434
2005
  const v = b ? b[k] : void 0;
1435
2006
  return v === void 0 || typeof v === "number" && Number.isInteger(v) && v >= min;
1436
2007
  };
2008
+ var intInRange = (b, k, min, max) => {
2009
+ const v = b ? b[k] : void 0;
2010
+ return typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
2011
+ };
2012
+ var optStrOneOf = (b, k, allowed) => {
2013
+ const v = b ? b[k] : void 0;
2014
+ return v === void 0 || typeof v === "string" && allowed.includes(v);
2015
+ };
1437
2016
  var hasBool = (b, k) => !!b && typeof b[k] === "boolean";
1438
2017
  var hasBytes = (b, k) => !!b && b[k] instanceof Uint8Array;
1439
2018
  var hasBytesArray = (b, k) => {
@@ -1441,7 +2020,7 @@ var hasBytesArray = (b, k) => {
1441
2020
  return Array.isArray(v) && v.every((x) => x instanceof Uint8Array);
1442
2021
  };
1443
2022
  function isCohortAdvertMessage(m) {
1444
- return m.type === COHORT_ADVERT && hasStr(m.body, "cohortId") && hasIntMin(m.body, "minParticipants", 1) && optIntMin(m.body, "maxParticipants", 1) && hasStr(m.body, "beaconType") && hasStr(m.body, "network") && hasBytes(m.body, "communicationPk");
2023
+ return m.type === COHORT_ADVERT && hasStr(m.body, "cohortId") && hasIntMin(m.body, "minParticipants", 1) && optIntMin(m.body, "maxParticipants", 1) && hasStr(m.body, "beaconType") && hasStr(m.body, "network") && hasStr(m.body, "recoveryKey") && intInRange(m.body, "recoverySequence", 1, MAX_RECOVERY_SEQUENCE) && optStrOneOf(m.body, "fundingModel", KNOWN_FUNDING_MODELS) && optIntMin(m.body, "fallbackThreshold", 1) && hasBytes(m.body, "communicationPk");
1445
2024
  }
1446
2025
  function isCohortOptInMessage(m) {
1447
2026
  return m.type === COHORT_OPT_IN && hasStr(m.body, "cohortId") && hasBytes(m.body, "participantPk") && hasBytes(m.body, "communicationPk");
@@ -1455,6 +2034,9 @@ function isCohortReadyMessage(m) {
1455
2034
  function isSubmitUpdateMessage(m) {
1456
2035
  return m.type === SUBMIT_UPDATE && hasStr(m.body, "cohortId") && !!m.body && typeof m.body.signedUpdate === "object";
1457
2036
  }
2037
+ function isSubmitNonIncludedMessage(m) {
2038
+ return m.type === SUBMIT_NONINCLUDED && hasStr(m.body, "cohortId");
2039
+ }
1458
2040
  function isDistributeAggregatedDataMessage(m) {
1459
2041
  return m.type === DISTRIBUTE_AGGREGATED_DATA && hasStr(m.body, "cohortId") && hasStr(m.body, "beaconType") && hasStr(m.body, "signalBytesHex");
1460
2042
  }
@@ -1473,8 +2055,38 @@ function isAggregatedNonceMessage(m) {
1473
2055
  function isSignatureAuthorizationMessage(m) {
1474
2056
  return m.type === SIGNATURE_AUTHORIZATION && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "partialSignature");
1475
2057
  }
2058
+ function isFallbackAuthorizationRequestMessage(m) {
2059
+ return m.type === FALLBACK_AUTHORIZATION_REQUEST && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasStr(m.body, "pendingTx") && hasStr(m.body, "prevOutScriptHex") && hasStr(m.body, "prevOutValue") && hasStr(m.body, "fallbackLeafScriptHex");
2060
+ }
2061
+ function isFallbackSignatureMessage(m) {
2062
+ return m.type === FALLBACK_SIGNATURE && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "signerPk") && hasBytes(m.body, "fallbackSignature");
2063
+ }
1476
2064
 
1477
2065
  // src/core/aggregation/participant.ts
2066
+ function txEmbedsSignal(tx, signalHex) {
2067
+ let expected;
2068
+ try {
2069
+ expected = (0, import_utils4.hexToBytes)(signalHex);
2070
+ } catch {
2071
+ return false;
2072
+ }
2073
+ if (expected.length === 0) return false;
2074
+ for (let i = 0; i < tx.outputsLength; i++) {
2075
+ const script = tx.getOutput(i)?.script;
2076
+ if (!script) continue;
2077
+ let decoded;
2078
+ try {
2079
+ decoded = import_btc_signer5.Script.decode(script);
2080
+ } catch {
2081
+ continue;
2082
+ }
2083
+ if (decoded.length === 2 && decoded[0] === "RETURN" && decoded[1] instanceof Uint8Array) {
2084
+ const payload = decoded[1];
2085
+ if (payload.length === expected.length && payload.every((b, j) => b === expected[j])) return true;
2086
+ }
2087
+ }
2088
+ return false;
2089
+ }
1478
2090
  var AggregationParticipant = class {
1479
2091
  did;
1480
2092
  /** MuSig2 signing capability. The raw secret never lives as a field here. */
@@ -1491,7 +2103,7 @@ var AggregationParticipant = class {
1491
2103
  }
1492
2104
  /**
1493
2105
  * Process an incoming message. Updates internal state but never produces
1494
- * outgoing messages those come exclusively from action methods.
2106
+ * outgoing messages: those come exclusively from action methods.
1495
2107
  */
1496
2108
  receive(message) {
1497
2109
  if (message.version === void 0 || message.version !== AGGREGATION_WIRE_VERSION) {
@@ -1517,6 +2129,9 @@ var AggregationParticipant = class {
1517
2129
  case AGGREGATED_NONCE:
1518
2130
  this.#handleAggregatedNonce(message);
1519
2131
  break;
2132
+ case FALLBACK_AUTHORIZATION_REQUEST:
2133
+ this.#handleFallbackAuthorizationRequest(message);
2134
+ break;
1520
2135
  default:
1521
2136
  break;
1522
2137
  }
@@ -1562,12 +2177,17 @@ var AggregationParticipant = class {
1562
2177
  { cohortId, phase: state?.phase }
1563
2178
  );
1564
2179
  }
2180
+ const advert = state.advert;
1565
2181
  const cohort = new AggregationCohort({
1566
2182
  id: cohortId,
1567
2183
  serviceDid: state.serviceDid,
1568
- minParticipants: state.advert.minParticipants,
1569
- network: state.advert.network,
1570
- beaconType: state.advert.beaconType
2184
+ minParticipants: advert.minParticipants,
2185
+ network: advert.network,
2186
+ beaconType: advert.beaconType,
2187
+ recoveryKey: advert.recoveryKey ? (0, import_utils4.hexToBytes)(advert.recoveryKey) : void 0,
2188
+ recoverySequence: advert.recoverySequence,
2189
+ fundingModel: advert.fundingModel,
2190
+ fallbackThreshold: advert.fallbackThreshold
1571
2191
  });
1572
2192
  state.cohort = cohort;
1573
2193
  state.phase = "OptedIn" /* OptedIn */;
@@ -1583,7 +2203,7 @@ var AggregationParticipant = class {
1583
2203
  #handleOptInAccept(message) {
1584
2204
  void message;
1585
2205
  }
1586
- /** Cohorts that have been finalized beacon address available. */
2206
+ /** Cohorts that have been finalized: beacon address available. */
1587
2207
  get joinedCohorts() {
1588
2208
  const map = /* @__PURE__ */ new Map();
1589
2209
  for (const [id, state] of this.#cohortStates) {
@@ -1607,8 +2227,8 @@ var AggregationParticipant = class {
1607
2227
  const beaconAddress = message.body?.beaconAddress;
1608
2228
  const cohortKeys = message.body?.cohortKeys;
1609
2229
  if (!beaconAddress || !cohortKeys) return;
1610
- const participantPkHex = (0, import_utils3.bytesToHex)(this.publicKey);
1611
- const cohortKeysHex = cohortKeys.map((k) => (0, import_utils3.bytesToHex)(new Uint8Array(k)));
2230
+ const participantPkHex = (0, import_utils4.bytesToHex)(this.publicKey);
2231
+ const cohortKeysHex = cohortKeys.map((k) => (0, import_utils4.bytesToHex)(new Uint8Array(k)));
1612
2232
  state.cohort.validateMembership(participantPkHex, cohortKeysHex, beaconAddress);
1613
2233
  state.phase = "CohortReady" /* CohortReady */;
1614
2234
  }
@@ -1626,6 +2246,7 @@ var AggregationParticipant = class {
1626
2246
  );
1627
2247
  }
1628
2248
  state.submittedUpdate = signedUpdate;
2249
+ state.included = true;
1629
2250
  state.phase = "UpdateSubmitted" /* UpdateSubmitted */;
1630
2251
  const message = createSubmitUpdateMessage({
1631
2252
  from: this.did,
@@ -1635,6 +2256,30 @@ var AggregationParticipant = class {
1635
2256
  });
1636
2257
  return [message];
1637
2258
  }
2259
+ /**
2260
+ * User action: decline to submit an update this round (cooperative
2261
+ * non-inclusion). The member stays in the cohort and still signs; it will be
2262
+ * absent from the CAS Announcement Map, or carry a non-inclusion leaf in the
2263
+ * SMT. Returns the SUBMIT_NONINCLUDED message to send.
2264
+ */
2265
+ declineUpdate(cohortId) {
2266
+ const state = this.#cohortStates.get(cohortId);
2267
+ if (!state || state.phase !== "CohortReady" /* CohortReady */) {
2268
+ throw new AggregationParticipantError(
2269
+ `Cannot decline in cohort ${cohortId}: not in CohortReady phase.`,
2270
+ "INVALID_PHASE",
2271
+ { cohortId, phase: state?.phase }
2272
+ );
2273
+ }
2274
+ state.included = false;
2275
+ state.phase = "NonIncluded" /* NonIncluded */;
2276
+ const message = createSubmitNonIncludedMessage({
2277
+ from: this.did,
2278
+ to: state.serviceDid,
2279
+ cohortId
2280
+ });
2281
+ return [message];
2282
+ }
1638
2283
  /** Aggregated data awaiting user validation. */
1639
2284
  get pendingValidations() {
1640
2285
  const map = /* @__PURE__ */ new Map();
@@ -1648,9 +2293,9 @@ var AggregationParticipant = class {
1648
2293
  /**
1649
2294
  * The validated aggregated data retained for a cohort, regardless of phase.
1650
2295
  * Unlike {@link pendingValidations} (which lists only cohorts still awaiting
1651
- * the validate decision), this returns the stored validation including the
1652
- * participant's sidecar (the CAS Announcement map or its SMT inclusion proof)
1653
- * so it is still readable once the cohort reaches Complete. Returns
2296
+ * the validate decision), this returns the stored validation, including the
2297
+ * participant's sidecar (the CAS Announcement map or its SMT inclusion proof),
2298
+ * so it is still readable once the cohort reaches Complete. Returns
1654
2299
  * undefined before aggregated data has been received.
1655
2300
  */
1656
2301
  getValidation(cohortId) {
@@ -1660,18 +2305,20 @@ var AggregationParticipant = class {
1660
2305
  const cohortId = message.body?.cohortId;
1661
2306
  if (!cohortId) return;
1662
2307
  const state = this.#cohortStates.get(cohortId);
1663
- if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */) return;
1664
- if (!state.submittedUpdate) return;
2308
+ if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */ && state.phase !== "NonIncluded" /* NonIncluded */) return;
2309
+ const declined = state.included === false;
2310
+ if (!declined && !state.submittedUpdate) return;
1665
2311
  const beaconType = message.body?.beaconType;
1666
2312
  if (!beaconType) return;
1667
2313
  const strategy = getBeaconStrategy(beaconType);
1668
2314
  if (!strategy) return;
1669
2315
  const signalBytesHex = message.body?.signalBytesHex ?? "";
1670
- const expectedHash = (0, import_common5.canonicalHash)(state.submittedUpdate);
2316
+ const expectedHash = declined ? "" : (0, import_common5.canonicalHash)(state.submittedUpdate);
1671
2317
  const result = strategy.validateParticipantView({
1672
2318
  participantDid: this.did,
1673
- submittedUpdate: state.submittedUpdate,
1674
- expectedHash,
2319
+ included: !declined,
2320
+ submittedUpdate: declined ? void 0 : state.submittedUpdate,
2321
+ expectedHash: declined ? void 0 : expectedHash,
1675
2322
  body: message.body
1676
2323
  });
1677
2324
  state.validation = {
@@ -1681,7 +2328,8 @@ var AggregationParticipant = class {
1681
2328
  expectedHash,
1682
2329
  matches: result.matches,
1683
2330
  casAnnouncement: result.casAnnouncement,
1684
- smtProof: result.smtProof
2331
+ smtProof: result.smtProof,
2332
+ included: !declined
1685
2333
  };
1686
2334
  state.phase = "AwaitingValidation" /* AwaitingValidation */;
1687
2335
  }
@@ -1755,6 +2403,31 @@ var AggregationParticipant = class {
1755
2403
  };
1756
2404
  state.phase = "AwaitingSigning" /* AwaitingSigning */;
1757
2405
  }
2406
+ /**
2407
+ * Bind a signing approval to the announcement the member validated: a beacon
2408
+ * transaction MUST carry an OP_RETURN with the exact 32-byte signal stored when
2409
+ * the aggregated data was distributed. Both the optimistic nonce approval and
2410
+ * the fallback approval sign with SIGHASH_DEFAULT (committing to every output)
2411
+ * while the coordinator drives output selection, so without this check a
2412
+ * coordinator could anchor a different signal under the member's signature.
2413
+ */
2414
+ #assertTxAnchorsValidatedSignal(cohortId, state, tx) {
2415
+ const signalHex = state.validation?.signalBytesHex;
2416
+ if (!signalHex) {
2417
+ throw new AggregationParticipantError(
2418
+ `Cohort ${cohortId} has no validated signal to bind the signature to.`,
2419
+ "MISSING_STATE",
2420
+ { cohortId }
2421
+ );
2422
+ }
2423
+ if (!txEmbedsSignal(tx, signalHex)) {
2424
+ throw new AggregationParticipantError(
2425
+ `Transaction for cohort ${cohortId} does not anchor the validated signal.`,
2426
+ "SIGNAL_MISMATCH",
2427
+ { cohortId }
2428
+ );
2429
+ }
2430
+ }
1758
2431
  /**
1759
2432
  * User action: approve signing and generate nonce contribution.
1760
2433
  */
@@ -1774,8 +2447,9 @@ var AggregationParticipant = class {
1774
2447
  { cohortId }
1775
2448
  );
1776
2449
  }
1777
- const tx = import_btc_signer3.Transaction.fromRaw((0, import_utils3.hexToBytes)(state.signingRequest.pendingTxHex));
1778
- const prevOutScripts = [(0, import_utils3.hexToBytes)(state.signingRequest.prevOutScriptHex)];
2450
+ const tx = import_btc_signer5.Transaction.fromRaw((0, import_utils4.hexToBytes)(state.signingRequest.pendingTxHex), { allowUnknownOutputs: true });
2451
+ this.#assertTxAnchorsValidatedSignal(cohortId, state, tx);
2452
+ const prevOutScripts = [(0, import_utils4.hexToBytes)(state.signingRequest.prevOutScriptHex)];
1779
2453
  const prevOutValues = [BigInt(state.signingRequest.prevOutValue)];
1780
2454
  const session = new BeaconSigningSession({
1781
2455
  id: state.signingRequest.sessionId,
@@ -1810,7 +2484,7 @@ var AggregationParticipant = class {
1810
2484
  }
1811
2485
  /**
1812
2486
  * User action: generate and return the partial signature.
1813
- * In most UIs this is automatic after AwaitingPartialSig but exposing it
2487
+ * In most UIs this is automatic after AwaitingPartialSig, but exposing it
1814
2488
  * as an explicit action lets the client UI confirm before signing if desired.
1815
2489
  */
1816
2490
  generatePartialSignature(cohortId) {
@@ -1842,6 +2516,83 @@ var AggregationParticipant = class {
1842
2516
  partialSignature: partialSig
1843
2517
  })];
1844
2518
  }
2519
+ /** Fallback signing requests awaiting user approval (UI: review the fallback spend). */
2520
+ get pendingFallbackRequests() {
2521
+ const map = /* @__PURE__ */ new Map();
2522
+ for (const [id, state] of this.#cohortStates) {
2523
+ if (state.phase === "AwaitingFallbackSig" /* AwaitingFallbackSig */ && state.fallbackRequest) {
2524
+ map.set(id, state.fallbackRequest);
2525
+ }
2526
+ }
2527
+ return map;
2528
+ }
2529
+ #handleFallbackAuthorizationRequest(message) {
2530
+ const cohortId = message.body?.cohortId;
2531
+ if (!cohortId) return;
2532
+ const state = this.#cohortStates.get(cohortId);
2533
+ if (!state || !state.cohort) return;
2534
+ const acceptFrom = [
2535
+ "ValidationSent" /* ValidationSent */,
2536
+ "AwaitingSigning" /* AwaitingSigning */,
2537
+ "NonceSent" /* NonceSent */,
2538
+ "AwaitingPartialSig" /* AwaitingPartialSig */,
2539
+ "Complete" /* Complete */
2540
+ ];
2541
+ if (!acceptFrom.includes(state.phase)) return;
2542
+ const sessionId = message.body?.sessionId;
2543
+ const pendingTxHex = message.body?.pendingTx;
2544
+ const prevOutScriptHex = message.body?.prevOutScriptHex;
2545
+ const prevOutValue = message.body?.prevOutValue;
2546
+ const fallbackLeafScriptHex = message.body?.fallbackLeafScriptHex;
2547
+ if (!sessionId || !pendingTxHex || !prevOutScriptHex || !prevOutValue || !fallbackLeafScriptHex) return;
2548
+ state.fallbackRequest = { cohortId, sessionId, pendingTxHex, prevOutScriptHex, prevOutValue, fallbackLeafScriptHex };
2549
+ state.signingSession?.clearSecrets();
2550
+ state.phase = "AwaitingFallbackSig" /* AwaitingFallbackSig */;
2551
+ }
2552
+ /**
2553
+ * User action: authorize the fallback spend. Recomputes the k-of-n fallback
2554
+ * leaf from the member's OWN cohort state (not the service-provided script),
2555
+ * computes the BIP-341 script-path sighash over the requested transaction, and
2556
+ * returns a standalone BIP-340 signature (no nonce round). The member completes
2557
+ * once it has contributed; the service needs only k of these.
2558
+ */
2559
+ approveFallback(cohortId) {
2560
+ const state = this.#cohortStates.get(cohortId);
2561
+ if (!state || state.phase !== "AwaitingFallbackSig" /* AwaitingFallbackSig */) {
2562
+ throw new AggregationParticipantError(
2563
+ `Cannot approve fallback for cohort ${cohortId}: not in AwaitingFallbackSig phase.`,
2564
+ "INVALID_PHASE",
2565
+ { cohortId, phase: state?.phase }
2566
+ );
2567
+ }
2568
+ if (!state.fallbackRequest || !state.cohort) {
2569
+ throw new AggregationParticipantError(
2570
+ `Cohort ${cohortId} missing fallback request or cohort state.`,
2571
+ "MISSING_STATE",
2572
+ { cohortId }
2573
+ );
2574
+ }
2575
+ const req = state.fallbackRequest;
2576
+ const tx = import_btc_signer5.Transaction.fromRaw((0, import_utils4.hexToBytes)(req.pendingTxHex), { allowUnknownOutputs: true });
2577
+ const prevOutScript = (0, import_utils4.hexToBytes)(req.prevOutScriptHex);
2578
+ const prevOutValue = BigInt(req.prevOutValue);
2579
+ this.#assertTxAnchorsValidatedSignal(cohortId, state, tx);
2580
+ const fallbackLeaf = buildFallbackLeaf({
2581
+ cohortKeys: state.cohort.cohortKeys,
2582
+ fallbackThreshold: state.cohort.effectiveFallbackThreshold
2583
+ });
2584
+ const sighash = fallbackSighash(tx, 0, prevOutScript, prevOutValue, fallbackLeaf);
2585
+ const signature = this.#signer.withSecret((secretKey) => import_secp256k15.schnorr.sign(sighash, secretKey));
2586
+ state.phase = "Complete" /* Complete */;
2587
+ return [createFallbackSignatureMessage({
2588
+ from: this.did,
2589
+ to: state.serviceDid,
2590
+ cohortId,
2591
+ sessionId: req.sessionId,
2592
+ signerPk: this.publicKey.slice(1),
2593
+ fallbackSignature: signature
2594
+ })];
2595
+ }
1845
2596
  getCohortPhase(cohortId) {
1846
2597
  return this.#cohortStates.get(cohortId)?.phase;
1847
2598
  }
@@ -1878,6 +2629,87 @@ var KeyPairAggregationSigner = class {
1878
2629
  }
1879
2630
  };
1880
2631
 
2632
+ // src/core/aggregation/recovery-spend.ts
2633
+ var import_bitcoin3 = require("@did-btcr2/bitcoin");
2634
+ var import_secp256k16 = require("@noble/curves/secp256k1.js");
2635
+ var import_btc_signer6 = require("@scure/btc-signer");
2636
+ var import_musig24 = require("@scure/btc-signer/musig2");
2637
+ var DUST_LIMIT_SATS = 546n;
2638
+ function bytesEqual2(a, b) {
2639
+ return a.length === b.length && a.every((x, i) => x === b[i]);
2640
+ }
2641
+ function buildRecoverySpend(params) {
2642
+ const {
2643
+ cohortKeys,
2644
+ recoverySecretKey,
2645
+ recoveryKey,
2646
+ recoverySequence,
2647
+ fallbackThreshold,
2648
+ fundingModel,
2649
+ network,
2650
+ utxo,
2651
+ destinationAddress,
2652
+ fee,
2653
+ beaconAddress
2654
+ } = params;
2655
+ if (cohortKeys.length === 0) {
2656
+ throw new AggregationCohortError(
2657
+ "Cannot build recovery spend: no cohort keys.",
2658
+ "NO_COHORT_KEYS"
2659
+ );
2660
+ }
2661
+ const derivedPub = import_secp256k16.schnorr.getPublicKey(recoverySecretKey);
2662
+ if (!bytesEqual2(derivedPub, recoveryKey)) {
2663
+ throw new AggregationCohortError(
2664
+ "Recovery secret key does not correspond to the committed recovery key.",
2665
+ "RECOVERY_KEY_MISMATCH"
2666
+ );
2667
+ }
2668
+ const out = utxo.value - fee;
2669
+ if (out <= 0n) {
2670
+ throw new AggregationCohortError(
2671
+ `Recovery fee ${fee} exceeds UTXO value ${utxo.value}.`,
2672
+ "FEE_EXCEEDS_VALUE",
2673
+ { fee: fee.toString(), value: utxo.value.toString() }
2674
+ );
2675
+ }
2676
+ if (out < DUST_LIMIT_SATS) {
2677
+ throw new AggregationCohortError(
2678
+ `Recovered output ${out} is below the dust limit ${DUST_LIMIT_SATS}; the spend would not relay.`,
2679
+ "DUST_OUTPUT",
2680
+ { output: out.toString(), dustLimit: DUST_LIMIT_SATS.toString() }
2681
+ );
2682
+ }
2683
+ const internalKey = (0, import_musig24.keyAggExport)((0, import_musig24.keyAggregate)((0, import_musig24.sortKeys)(cohortKeys)));
2684
+ const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
2685
+ recoveryKey,
2686
+ recoverySequence,
2687
+ cohortKeys,
2688
+ fallbackThreshold: resolveFallbackThreshold(fallbackThreshold, cohortKeys.length)
2689
+ });
2690
+ const net = (0, import_bitcoin3.getNetwork)(network);
2691
+ const payment = (0, import_btc_signer6.p2tr)(internalKey, leaves, net, true);
2692
+ if (beaconAddress !== void 0 && payment.address !== beaconAddress) {
2693
+ throw new AggregationCohortError(
2694
+ `Reconstructed beacon address ${payment.address} does not match the cohort's funded address ${beaconAddress}.`,
2695
+ "BEACON_ADDRESS_MISMATCH",
2696
+ { reconstructed: payment.address, expected: beaconAddress }
2697
+ );
2698
+ }
2699
+ const tx = new import_btc_signer6.Transaction({ version: 2, allowUnknownInputs: true });
2700
+ tx.addInput({
2701
+ txid: utxo.txid,
2702
+ index: utxo.vout,
2703
+ witnessUtxo: { script: payment.script, amount: utxo.value },
2704
+ tapLeafScript: payment.tapLeafScript,
2705
+ sequence: recoverySequence
2706
+ });
2707
+ tx.addOutputAddress(destinationAddress, out, net);
2708
+ tx.signIdx(recoverySecretKey, 0);
2709
+ tx.finalize();
2710
+ return tx;
2711
+ }
2712
+
1881
2713
  // src/core/aggregation/logger.ts
1882
2714
  var CONSOLE_LOGGER = {
1883
2715
  debug: (msg, ...args) => console.debug(msg, ...args),
@@ -1905,6 +2737,7 @@ var KEYGEN_VALUES = /* @__PURE__ */ new Set([
1905
2737
  ]);
1906
2738
  var UPDATE_VALUES = /* @__PURE__ */ new Set([
1907
2739
  SUBMIT_UPDATE,
2740
+ SUBMIT_NONINCLUDED,
1908
2741
  DISTRIBUTE_AGGREGATED_DATA,
1909
2742
  VALIDATION_ACK
1910
2743
  ]);
@@ -1912,7 +2745,9 @@ var SIGN_VALUES = /* @__PURE__ */ new Set([
1912
2745
  AUTHORIZATION_REQUEST,
1913
2746
  NONCE_CONTRIBUTION,
1914
2747
  AGGREGATED_NONCE,
1915
- SIGNATURE_AUTHORIZATION
2748
+ SIGNATURE_AUTHORIZATION,
2749
+ FALLBACK_AUTHORIZATION_REQUEST,
2750
+ FALLBACK_SIGNATURE
1916
2751
  ]);
1917
2752
  function isAggregationMessageType(type) {
1918
2753
  return KEYGEN_VALUES.has(type) || UPDATE_VALUES.has(type) || SIGN_VALUES.has(type);
@@ -2140,7 +2975,7 @@ var Identifier = class _Identifier {
2140
2975
 
2141
2976
  // src/core/aggregation/transport/http/envelope.ts
2142
2977
  var import_common8 = require("@did-btcr2/common");
2143
- var import_utils4 = require("@noble/hashes/utils");
2978
+ var import_utils5 = require("@noble/hashes/utils");
2144
2979
 
2145
2980
  // src/core/aggregation/transport/http/errors.ts
2146
2981
  var HttpTransportError = class extends TransportAdapterError {
@@ -2168,7 +3003,7 @@ var DEFAULT_NONCE_LEN_BYTES = 16;
2168
3003
  // src/core/aggregation/transport/http/envelope.ts
2169
3004
  function signEnvelope(message, sender, opts = {}) {
2170
3005
  const timestamp = opts.timestamp ?? Math.floor(Date.now() / 1e3);
2171
- const nonce = opts.nonce ?? (0, import_utils4.bytesToHex)((0, import_utils4.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
3006
+ const nonce = opts.nonce ?? (0, import_utils5.bytesToHex)((0, import_utils5.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
2172
3007
  const messageJson = normalizeForWire(normalizeMessage(message));
2173
3008
  const unsigned = {
2174
3009
  v: HTTP_ENVELOPE_VERSION,
@@ -2180,7 +3015,7 @@ function signEnvelope(message, sender, opts = {}) {
2180
3015
  };
2181
3016
  const digest = (0, import_common8.canonicalHashBytes)(unsigned);
2182
3017
  const sig = sender.keys.secretKey.sign(digest, { scheme: "schnorr" });
2183
- return { ...unsigned, sig: (0, import_utils4.bytesToHex)(sig) };
3018
+ return { ...unsigned, sig: (0, import_utils5.bytesToHex)(sig) };
2184
3019
  }
2185
3020
  function verifyEnvelope(envelope, senderPk, opts = {}) {
2186
3021
  if (envelope.v !== HTTP_ENVELOPE_VERSION) {
@@ -2217,7 +3052,7 @@ function verifyEnvelope(envelope, senderPk, opts = {}) {
2217
3052
  }
2218
3053
  let sigBytes;
2219
3054
  try {
2220
- sigBytes = (0, import_utils4.hexToBytes)(envelope.sig);
3055
+ sigBytes = (0, import_utils5.hexToBytes)(envelope.sig);
2221
3056
  } catch {
2222
3057
  throw new HttpTransportError(
2223
3058
  "Envelope signature is not valid hex",
@@ -2250,7 +3085,7 @@ function normalizeMessage(message) {
2250
3085
  }
2251
3086
  function normalizeForWire(value) {
2252
3087
  if (value instanceof Uint8Array) {
2253
- return { __bytes: (0, import_utils4.bytesToHex)(value) };
3088
+ return { __bytes: (0, import_utils5.bytesToHex)(value) };
2254
3089
  }
2255
3090
  if (Array.isArray(value)) {
2256
3091
  return value.map((v) => normalizeForWire(v));
@@ -2269,7 +3104,7 @@ function reviveFromWire(value) {
2269
3104
  const rec = value;
2270
3105
  const keys = Object.keys(rec);
2271
3106
  if (keys.length === 1 && keys[0] === "__bytes" && typeof rec.__bytes === "string") {
2272
- return (0, import_utils4.hexToBytes)(rec.__bytes);
3107
+ return (0, import_utils5.hexToBytes)(rec.__bytes);
2273
3108
  }
2274
3109
  const out = {};
2275
3110
  for (const [k, v] of Object.entries(rec)) out[k] = reviveFromWire(v);
@@ -2283,11 +3118,11 @@ function reviveFromWire(value) {
2283
3118
 
2284
3119
  // src/core/aggregation/transport/http/request-auth.ts
2285
3120
  var import_common9 = require("@did-btcr2/common");
2286
- var import_utils5 = require("@noble/hashes/utils");
3121
+ var import_utils6 = require("@noble/hashes/utils");
2287
3122
  var REQUEST_AUTH_SCHEME = "BTCR2-Sig";
2288
3123
  function buildRequestAuth(did, keys, path, opts = {}) {
2289
3124
  const ts = opts.timestamp ?? Math.floor(Date.now() / 1e3);
2290
- const nonce = opts.nonce ?? (0, import_utils5.bytesToHex)((0, import_utils5.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
3125
+ const nonce = opts.nonce ?? (0, import_utils6.bytesToHex)((0, import_utils6.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
2291
3126
  const digest = (0, import_common9.canonicalHashBytes)({
2292
3127
  v: HTTP_ENVELOPE_VERSION,
2293
3128
  did,
@@ -2296,7 +3131,7 @@ function buildRequestAuth(did, keys, path, opts = {}) {
2296
3131
  path
2297
3132
  });
2298
3133
  const sig = keys.secretKey.sign(digest, { scheme: "schnorr" });
2299
- return `${REQUEST_AUTH_SCHEME} v=${HTTP_ENVELOPE_VERSION},did=${did},ts=${ts},nonce=${nonce},sig=${(0, import_utils5.bytesToHex)(sig)}`;
3134
+ return `${REQUEST_AUTH_SCHEME} v=${HTTP_ENVELOPE_VERSION},did=${did},ts=${ts},nonce=${nonce},sig=${(0, import_utils6.bytesToHex)(sig)}`;
2300
3135
  }
2301
3136
  function parseRequestAuth(headerValue) {
2302
3137
  const prefix = `${REQUEST_AUTH_SCHEME} `;
@@ -2347,7 +3182,7 @@ function verifyRequestAuth(headerValue, expectedPath, senderPk, opts = {}) {
2347
3182
  }
2348
3183
  let sigBytes;
2349
3184
  try {
2350
- sigBytes = (0, import_utils5.hexToBytes)(parsed.sig);
3185
+ sigBytes = (0, import_utils6.hexToBytes)(parsed.sig);
2351
3186
  } catch {
2352
3187
  throw new HttpTransportError("Auth signature is not valid hex", "REQUEST_AUTH_SIG_HEX");
2353
3188
  }
@@ -3326,7 +4161,7 @@ function flattenMessage2(msg) {
3326
4161
 
3327
4162
  // src/core/aggregation/transport/nostr.ts
3328
4163
  var import_keypair6 = require("@did-btcr2/keypair");
3329
- var import_utils6 = require("@noble/hashes/utils");
4164
+ var import_utils7 = require("@noble/hashes/utils");
3330
4165
  var import_nostr_tools = require("nostr-tools");
3331
4166
  var import_pool = require("nostr-tools/pool");
3332
4167
  var DEFAULT_NOSTR_RELAYS = [
@@ -3524,14 +4359,14 @@ var NostrTransport = class _NostrTransport {
3524
4359
  }
3525
4360
  const senderKeys = actor.keys;
3526
4361
  const tags = [
3527
- ["p", (0, import_utils6.bytesToHex)(senderKeys.publicKey.x)],
4362
+ ["p", (0, import_utils7.bytesToHex)(senderKeys.publicKey.x)],
3528
4363
  ["t", type]
3529
4364
  ];
3530
4365
  if (to) {
3531
4366
  const recipientPkBytes = this.#peerRegistry.get(to);
3532
4367
  if (recipientPkBytes) {
3533
4368
  const recipientPk = new import_keypair6.CompressedSecp256k1PublicKey(recipientPkBytes);
3534
- tags.push(["p", (0, import_utils6.bytesToHex)(recipientPk.x)]);
4369
+ tags.push(["p", (0, import_utils7.bytesToHex)(recipientPk.x)]);
3535
4370
  }
3536
4371
  }
3537
4372
  if (isKeygenMessageType(type)) {
@@ -3564,7 +4399,7 @@ var NostrTransport = class _NostrTransport {
3564
4399
  const recipientPk = new import_keypair6.CompressedSecp256k1PublicKey(recipientPkBytes);
3565
4400
  const conversationKey = import_nostr_tools.nip44.v2.utils.getConversationKey(
3566
4401
  senderKeys.secretKey.bytes,
3567
- (0, import_utils6.bytesToHex)(recipientPk.x)
4402
+ (0, import_utils7.bytesToHex)(recipientPk.x)
3568
4403
  );
3569
4404
  const content = import_nostr_tools.nip44.v2.encrypt(JSON.stringify(message, _NostrTransport.#jsonReplacer), conversationKey);
3570
4405
  const event = (0, import_nostr_tools.finalizeEvent)({
@@ -3587,7 +4422,7 @@ var NostrTransport = class _NostrTransport {
3587
4422
  * backfill historical events to late subscribers: republishing gives late
3588
4423
  * joiners a window to discover the message without requiring protocol
3589
4424
  * changes. Relay rate-limit / publish failures inside the interval are
3590
- * caught and logged rather than propagated the caller should stop the
4425
+ * caught and logged rather than propagated - the caller should stop the
3591
4426
  * repeater once the protocol condition is satisfied.
3592
4427
  */
3593
4428
  publishRepeating(message, sender, intervalMs, recipient) {
@@ -3618,7 +4453,7 @@ var NostrTransport = class _NostrTransport {
3618
4453
  */
3619
4454
  #subscribeDirected(did, entry) {
3620
4455
  if (!this.pool) return;
3621
- const pkHex = (0, import_utils6.bytesToHex)(entry.keys.publicKey.x);
4456
+ const pkHex = (0, import_utils7.bytesToHex)(entry.keys.publicKey.x);
3622
4457
  const sub = this.pool.subscribeMany(this.#relays, { kinds: [1, 1059], "#p": [pkHex] }, {
3623
4458
  onclose: (reasons) => this.#logger.debug(`Nostr directed subscription closed for ${did}`, reasons),
3624
4459
  onevent: this.#makeActorEventHandler(did)
@@ -3636,7 +4471,7 @@ var NostrTransport = class _NostrTransport {
3636
4471
  return async (event) => {
3637
4472
  const actor = this.#actors.get(actorDid);
3638
4473
  if (!actor) return;
3639
- if (event.pubkey === (0, import_utils6.bytesToHex)(actor.keys.publicKey.x)) return;
4474
+ if (event.pubkey === (0, import_utils7.bytesToHex)(actor.keys.publicKey.x)) return;
3640
4475
  let message;
3641
4476
  try {
3642
4477
  if (event.kind === 1) {
@@ -3749,7 +4584,7 @@ var NostrTransport = class _NostrTransport {
3749
4584
  */
3750
4585
  static #jsonReplacer(_key, value) {
3751
4586
  if (value instanceof Uint8Array) {
3752
- return { __bytes: (0, import_utils6.bytesToHex)(value) };
4587
+ return { __bytes: (0, import_utils7.bytesToHex)(value) };
3753
4588
  }
3754
4589
  return value;
3755
4590
  }
@@ -3767,7 +4602,7 @@ var NostrTransport = class _NostrTransport {
3767
4602
  */
3768
4603
  static #jsonReviver(_key, value) {
3769
4604
  if (value && typeof value === "object" && "__bytes" in value) {
3770
- return (0, import_utils6.hexToBytes)(value.__bytes);
4605
+ return (0, import_utils7.hexToBytes)(value.__bytes);
3771
4606
  }
3772
4607
  return value;
3773
4608
  }
@@ -3802,7 +4637,7 @@ var TransportFactory = class {
3802
4637
  };
3803
4638
 
3804
4639
  // src/core/aggregation/transport/in-memory.ts
3805
- var import_utils7 = require("@noble/hashes/utils");
4640
+ var import_utils8 = require("@noble/hashes/utils");
3806
4641
  var InMemoryBus = class {
3807
4642
  #transports = /* @__PURE__ */ new Set();
3808
4643
  /** Attach a transport to this bus. Called by the transport's constructor. */
@@ -3821,8 +4656,8 @@ var InMemoryBus = class {
3821
4656
  async deliver(message, _sender, recipient) {
3822
4657
  const type = message.type;
3823
4658
  if (!type) return;
3824
- const replacer = (_k, v) => v instanceof Uint8Array ? { __bytes: (0, import_utils7.bytesToHex)(v) } : v;
3825
- const reviver = (_k, v) => v && typeof v === "object" && "__bytes" in v ? (0, import_utils7.hexToBytes)(v.__bytes) : v;
4659
+ const replacer = (_k, v) => v instanceof Uint8Array ? { __bytes: (0, import_utils8.bytesToHex)(v) } : v;
4660
+ const reviver = (_k, v) => v && typeof v === "object" && "__bytes" in v ? (0, import_utils8.hexToBytes)(v.__bytes) : v;
3826
4661
  const raw = JSON.parse(JSON.stringify(message, replacer), reviver);
3827
4662
  const serialized = { ...raw, ...raw.body ?? {} };
3828
4663
  if (!recipient) {
@@ -4035,6 +4870,13 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4035
4870
  #cohortTtlMs;
4036
4871
  #phaseTimeoutMs;
4037
4872
  #advertRepeatIntervalMs;
4873
+ #autoFallbackOnStall;
4874
+ /** Phases during which a stall can be salvaged by the k-of-n fallback (ADR 042). */
4875
+ static #SIGNING_PHASES = [
4876
+ "SigningStarted" /* SigningStarted */,
4877
+ "NoncesCollected" /* NoncesCollected */,
4878
+ "AwaitingPartialSigs" /* AwaitingPartialSigs */
4879
+ ];
4038
4880
  /** Per-cohort run state, keyed by cohortId. */
4039
4881
  #contexts = /* @__PURE__ */ new Map();
4040
4882
  #handlersRegistered = false;
@@ -4052,6 +4894,7 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4052
4894
  this.#cohortTtlMs = options.cohortTtlMs;
4053
4895
  this.#phaseTimeoutMs = options.phaseTimeoutMs;
4054
4896
  this.#advertRepeatIntervalMs = options.advertRepeatIntervalMs ?? DEFAULT_ADVERT_REPEAT_INTERVAL_MS;
4897
+ this.#autoFallbackOnStall = options.autoFallbackOnStall ?? false;
4055
4898
  this.session = new AggregationService({
4056
4899
  // The coordinator never signs, so the state machine receives only the
4057
4900
  // public half of the operator's keypair (see ADR 038). The full keypair
@@ -4213,11 +5056,42 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4213
5056
  ctx.phaseTimer = void 0;
4214
5057
  if (this.#phaseTimeoutMs === void 0) return;
4215
5058
  ctx.phaseTimer = setTimeout(() => {
5059
+ const phase = this.session.getCohortPhase(ctx.cohortId);
5060
+ const inSigning = phase !== void 0 && _AggregationServiceRunner.#SIGNING_PHASES.includes(phase);
5061
+ if (this.#autoFallbackOnStall && inSigning && !ctx.committedPath && !ctx.settled) {
5062
+ this.triggerFallback(ctx.cohortId).catch((err) => this.#failCohort(ctx, err));
5063
+ return;
5064
+ }
4216
5065
  const reason = `Cohort ${ctx.cohortId} stalled in phase ${ctx.lastObservedPhase ?? "?"} for ${this.#phaseTimeoutMs}ms`;
4217
5066
  this.emit("cohort-failed", { cohortId: ctx.cohortId, reason });
4218
5067
  this.#failCohort(ctx, new Error(reason));
4219
5068
  }, this.#phaseTimeoutMs);
4220
5069
  }
5070
+ /**
5071
+ * Abandon the optimistic n-of-n key path for a cohort and collect k-of-n
5072
+ * fallback (script-path) signatures instead (ADR 042). Idempotent and safe
5073
+ * against the optimistic completion: it commits the cohort to the fallback
5074
+ * path synchronously (the `committedPath` latch) before sending anything, so a
5075
+ * late optimistic signature can no longer complete-and-broadcast a competing
5076
+ * spend of the same UTXO. No-op if the cohort is unknown, already settled, or
5077
+ * already committed to a path.
5078
+ *
5079
+ * Wired automatically to the phase-stall timer when `autoFallbackOnStall` is
5080
+ * set; otherwise call it from an operator decision (a UI "fall back now"
5081
+ * action). Throws only if the underlying state machine rejects the transition
5082
+ * (e.g. signing has not started).
5083
+ */
5084
+ async triggerFallback(cohortId) {
5085
+ const ctx = this.#contexts.get(cohortId);
5086
+ if (!ctx || ctx.settled || ctx.committedPath) return;
5087
+ const messages = this.session.startFallbackSigning(cohortId);
5088
+ ctx.committedPath = "fallback";
5089
+ this.#stopAdvertRepeating(ctx);
5090
+ this.#onPhaseMaybeChanged(ctx);
5091
+ const sessionId = this.session.getSigningSessionId(cohortId) ?? "";
5092
+ this.emit("fallback-started", { cohortId, sessionId });
5093
+ await this.#sendAll(messages);
5094
+ }
4221
5095
  /** Detect a phase change for a cohort since the last observation and reset its phase timer. */
4222
5096
  #onPhaseMaybeChanged(ctx) {
4223
5097
  const phase = this.session.getCohortPhase(ctx.cohortId);
@@ -4306,13 +5180,15 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4306
5180
  static #HANDLED_MESSAGE_TYPES = [
4307
5181
  COHORT_OPT_IN,
4308
5182
  SUBMIT_UPDATE,
5183
+ SUBMIT_NONINCLUDED,
4309
5184
  VALIDATION_ACK,
4310
5185
  NONCE_CONTRIBUTION,
4311
- SIGNATURE_AUTHORIZATION
5186
+ SIGNATURE_AUTHORIZATION,
5187
+ FALLBACK_SIGNATURE
4312
5188
  ];
4313
5189
  /**
4314
5190
  * Internal: handler registration with the transport. Idempotent. Handlers
4315
- * are DID-scoped and cohort-agnostic one registration serves every cohort
5191
+ * are DID-scoped and cohort-agnostic - one registration serves every cohort
4316
5192
  * this runner drives; demux to the right {@link RunContext} happens in each
4317
5193
  * handler via the inbound message's cohortId.
4318
5194
  */
@@ -4321,9 +5197,11 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4321
5197
  this.#handlersRegistered = true;
4322
5198
  this.#transport.registerMessageHandler(this.#did, COHORT_OPT_IN, this.#handleOptIn.bind(this));
4323
5199
  this.#transport.registerMessageHandler(this.#did, SUBMIT_UPDATE, this.#handleSubmitUpdate.bind(this));
5200
+ this.#transport.registerMessageHandler(this.#did, SUBMIT_NONINCLUDED, this.#handleSubmitUpdate.bind(this));
4324
5201
  this.#transport.registerMessageHandler(this.#did, VALIDATION_ACK, this.#handleValidationAck.bind(this));
4325
5202
  this.#transport.registerMessageHandler(this.#did, NONCE_CONTRIBUTION, this.#handleNonceContribution.bind(this));
4326
5203
  this.#transport.registerMessageHandler(this.#did, SIGNATURE_AUTHORIZATION, this.#handleSignatureAuthorization.bind(this));
5204
+ this.#transport.registerMessageHandler(this.#did, FALLBACK_SIGNATURE, this.#handleFallbackSignature.bind(this));
4327
5205
  }
4328
5206
  /** Internal: detach from the transport. Safe to call repeatedly. */
4329
5207
  #unregisterHandlers() {
@@ -4484,6 +5362,32 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
4484
5362
  * @returns {Promise<void>} Resolves when handling is complete.
4485
5363
  */
4486
5364
  async #handleSignatureAuthorization(msg) {
5365
+ if (this.#stopped) return;
5366
+ const ctx = this.#contextFor(msg);
5367
+ if (!ctx) return;
5368
+ try {
5369
+ this.session.receive(msg);
5370
+ this.#drainRejections(ctx);
5371
+ this.#onPhaseMaybeChanged(ctx);
5372
+ if (ctx.committedPath === "fallback") return;
5373
+ const result = this.session.getResult(ctx.cohortId);
5374
+ if (result) {
5375
+ ctx.committedPath = "optimistic";
5376
+ this.#completeCohort(ctx, result);
5377
+ }
5378
+ } catch (err) {
5379
+ this.#failCohort(ctx, err);
5380
+ }
5381
+ }
5382
+ /**
5383
+ * Handler for receiving fallback (k-of-n script-path) signatures. The state
5384
+ * machine assembles and finalizes the fallback spend once k valid signatures
5385
+ * are in; the result is then emitted and the cohort's completion resolves. The
5386
+ * cohort is already committed to the fallback path (via {@link triggerFallback}).
5387
+ * @param {BaseMessage} msg - The incoming FALLBACK_SIGNATURE message.
5388
+ * @returns {Promise<void>} Resolves when handling is complete.
5389
+ */
5390
+ async #handleFallbackSignature(msg) {
4487
5391
  if (this.#stopped) return;
4488
5392
  const ctx = this.#contextFor(msg);
4489
5393
  if (!ctx) return;
@@ -4556,7 +5460,8 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
4556
5460
  COHORT_READY,
4557
5461
  DISTRIBUTE_AGGREGATED_DATA,
4558
5462
  AUTHORIZATION_REQUEST,
4559
- AGGREGATED_NONCE
5463
+ AGGREGATED_NONCE,
5464
+ FALLBACK_AUTHORIZATION_REQUEST
4560
5465
  ];
4561
5466
  /** Internal: detach from the transport. Safe to call repeatedly. */
4562
5467
  #unregisterHandlers() {
@@ -4591,7 +5496,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
4591
5496
  *
4592
5497
  * For an open-ended, long-lived subscriber (no fixed count), construct an
4593
5498
  * {@link AggregationParticipantRunner} directly, set `shouldJoin`, call
4594
- * `start()`, and listen for `cohort-complete` the runner already drives
5499
+ * `start()`, and listen for `cohort-complete` - the runner already drives
4595
5500
  * any number of cohorts concurrently.
4596
5501
  *
4597
5502
  * @param options Participant runner options (set `shouldJoin` to select cohorts).
@@ -4626,6 +5531,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
4626
5531
  this.#transport.registerMessageHandler(this.#did, DISTRIBUTE_AGGREGATED_DATA, this.#handleDistributeData.bind(this));
4627
5532
  this.#transport.registerMessageHandler(this.#did, AUTHORIZATION_REQUEST, this.#handleAuthorizationRequest.bind(this));
4628
5533
  this.#transport.registerMessageHandler(this.#did, AGGREGATED_NONCE, this.#handleAggregatedNonce.bind(this));
5534
+ this.#transport.registerMessageHandler(this.#did, FALLBACK_AUTHORIZATION_REQUEST, this.#handleFallbackAuthorizationRequest.bind(this));
4629
5535
  }
4630
5536
  /**
4631
5537
  * Internal: handler for cohort adverts. Applies the `shouldJoin` filter and joins if approved.
@@ -4681,8 +5587,13 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
4681
5587
  cohortId,
4682
5588
  beaconAddress: info.beaconAddress
4683
5589
  });
4684
- await this.#sendAll(this.session.submitUpdate(cohortId, signedUpdate));
4685
- this.emit("update-submitted", { cohortId });
5590
+ if (signedUpdate === null) {
5591
+ await this.#sendAll(this.session.declineUpdate(cohortId));
5592
+ this.emit("update-declined", { cohortId });
5593
+ } else {
5594
+ await this.#sendAll(this.session.submitUpdate(cohortId, signedUpdate));
5595
+ this.emit("update-submitted", { cohortId });
5596
+ }
4686
5597
  } catch (err) {
4687
5598
  this.emit("error", err);
4688
5599
  }
@@ -4758,23 +5669,62 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
4758
5669
  const cohortId = msg.body?.cohortId;
4759
5670
  if (!cohortId) return;
4760
5671
  await this.#sendAll(this.session.generatePartialSignature(cohortId));
4761
- if (this.session.getCohortPhase(cohortId) === "Complete" /* Complete */) {
4762
- const info = this.session.joinedCohorts.get(cohortId);
4763
- if (info) {
4764
- const validation = this.session.getValidation(cohortId);
4765
- this.emit("cohort-complete", {
4766
- cohortId,
4767
- beaconAddress: info.beaconAddress,
4768
- beaconType: validation?.beaconType ?? "",
4769
- casAnnouncement: validation?.casAnnouncement,
4770
- smtProof: validation?.smtProof
4771
- });
4772
- }
5672
+ this.#emitCohortCompleteIfDone(cohortId);
5673
+ } catch (err) {
5674
+ this.emit("error", err);
5675
+ }
5676
+ }
5677
+ /**
5678
+ * Internal: handler for fallback authorization requests. The service abandoned
5679
+ * the optimistic key path; the member authorizes the k-of-n script-path spend
5680
+ * of the same beacon transaction (ADR 042). Reuses `onApproveSigning` to gate
5681
+ * the decision, signs the fallback, and completes once it has contributed (the
5682
+ * service needs only k members).
5683
+ * @param {BaseMessage} msg - The received fallback authorization request message.
5684
+ * @returns {Promise<void>} Resolves when processing is complete.
5685
+ */
5686
+ async #handleFallbackAuthorizationRequest(msg) {
5687
+ if (this.#stopped) return;
5688
+ try {
5689
+ this.session.receive(msg);
5690
+ const cohortId = msg.body?.cohortId;
5691
+ if (!cohortId) return;
5692
+ const req = this.session.pendingFallbackRequests.get(cohortId);
5693
+ if (!req) return;
5694
+ this.emit("fallback-requested", req);
5695
+ const decision = await this.#onApproveSigning(req);
5696
+ if (!decision.approved) {
5697
+ this.emit("cohort-failed", { cohortId, reason: "Fallback signing rejected by participant" });
5698
+ return;
4773
5699
  }
5700
+ await this.#sendAll(this.session.approveFallback(cohortId));
5701
+ this.#emitCohortCompleteIfDone(cohortId);
4774
5702
  } catch (err) {
4775
5703
  this.emit("error", err);
4776
5704
  }
4777
5705
  }
5706
+ /**
5707
+ * Internal: emit `cohort-complete` with the participant's retained sidecar once
5708
+ * the cohort has reached the Complete phase. Surfaces the CAS Announcement map
5709
+ * (CAS beacons) or the SMT proof (SMT beacons) the participant keeps for future
5710
+ * DID resolution. Read via getValidation (not pendingValidations, which lists
5711
+ * only the AwaitingValidation phase) so the sidecar is still available at
5712
+ * Complete. Shared by the optimistic and fallback completion paths.
5713
+ */
5714
+ #emitCohortCompleteIfDone(cohortId) {
5715
+ if (this.session.getCohortPhase(cohortId) !== "Complete" /* Complete */) return;
5716
+ const info = this.session.joinedCohorts.get(cohortId);
5717
+ if (!info) return;
5718
+ const validation = this.session.getValidation(cohortId);
5719
+ this.emit("cohort-complete", {
5720
+ cohortId,
5721
+ beaconAddress: info.beaconAddress,
5722
+ beaconType: validation?.beaconType ?? "",
5723
+ included: validation?.included ?? true,
5724
+ casAnnouncement: validation?.casAnnouncement,
5725
+ smtProof: validation?.smtProof
5726
+ });
5727
+ }
4778
5728
  /**
4779
5729
  * Internal: send helper to ensure messages are sent sequentially. This is important for protocol
4780
5730
  * correctness, as some transports may not guarantee message order if sent in parallel.
@@ -4790,6 +5740,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
4790
5740
  };
4791
5741
 
4792
5742
  // src/core/aggregation/runner/aggregation-runner.ts
5743
+ var import_utils9 = require("@noble/hashes/utils");
4793
5744
  var AggregationRunner = class {
4794
5745
  /**
4795
5746
  * Run a cohort of ONE participant entirely in-process and return the
@@ -4797,8 +5748,8 @@ var AggregationRunner = class {
4797
5748
  *
4798
5749
  * One party plays both the coordinating service and the lone participant,
4799
5750
  * connected over an {@link InMemoryTransport} (no relay or HTTP server). This
4800
- * makes the single-participant aggregate-beacon path the N=1 corner of the
4801
- * two-axis beacon matrix (see ADR 037) first-class, useful for generating
5751
+ * makes the single-participant aggregate-beacon path (the N=1 corner of the
5752
+ * two-axis beacon matrix, see ADR 037) first-class, useful for generating
4802
5753
  * and reproducing single-participant aggregate test vectors.
4803
5754
  *
4804
5755
  * The service advertises a cohort with `minParticipants: 1`; the participant
@@ -4816,11 +5767,13 @@ var AggregationRunner = class {
4816
5767
  transport.registerPeer(options.participant.did, options.participant.keys.publicKey.compressed);
4817
5768
  transport.registerPeer(options.service.did, options.service.keys.publicKey.compressed);
4818
5769
  transport.start();
5770
+ const recoveryKey = options.config.recoveryKey ?? (0, import_utils9.bytesToHex)(options.service.keys.publicKey.compressed.slice(1));
5771
+ const recoverySequence = options.config.recoverySequence ?? DEFAULT_RECOVERY_SEQUENCE;
4819
5772
  const service = new AggregationServiceRunner({
4820
5773
  transport,
4821
5774
  did: options.service.did,
4822
5775
  keys: options.service.keys,
4823
- config: { minParticipants: 1, network: options.config.network, beaconType: options.config.beaconType },
5776
+ config: { minParticipants: 1, network: options.config.network, beaconType: options.config.beaconType, recoveryKey, recoverySequence, fundingModel: DEFAULT_FUNDING_MODEL },
4824
5777
  onProvideTxData: options.onProvideTxData,
4825
5778
  cohortTtlMs: options.cohortTtlMs,
4826
5779
  phaseTimeoutMs: options.phaseTimeoutMs,
@@ -4846,8 +5799,8 @@ var AggregationRunner = class {
4846
5799
  };
4847
5800
 
4848
5801
  // src/core/beacon/beacon.ts
4849
- var import_utils8 = require("@noble/hashes/utils.js");
4850
- var import_btc_signer4 = require("@scure/btc-signer");
5802
+ var import_utils10 = require("@noble/hashes/utils.js");
5803
+ var import_btc_signer7 = require("@scure/btc-signer");
4851
5804
 
4852
5805
  // src/core/beacon/error.ts
4853
5806
  var import_common12 = require("@did-btcr2/common");
@@ -4908,7 +5861,7 @@ var SINGLETON_BEACON_TX_VSIZE = {
4908
5861
  p2tr: P2TR_BEACON_TX_VSIZE
4909
5862
  };
4910
5863
  function detectSingletonScriptKind(bitcoinAddress, network) {
4911
- const decoded = (0, import_btc_signer4.Address)(network).decode(bitcoinAddress);
5864
+ const decoded = (0, import_btc_signer7.Address)(network).decode(bitcoinAddress);
4912
5865
  if (decoded.type === "pkh") return "p2pkh";
4913
5866
  if (decoded.type === "wpkh") return "p2wpkh";
4914
5867
  if (decoded.type === "tr") return "p2tr";
@@ -4919,12 +5872,12 @@ function detectSingletonScriptKind(bitcoinAddress, network) {
4919
5872
  );
4920
5873
  }
4921
5874
  function deriveSingletonAddress(kind, pubkey, network) {
4922
- if (kind === "p2pkh") return (0, import_btc_signer4.p2pkh)(pubkey, network).address;
4923
- if (kind === "p2wpkh") return (0, import_btc_signer4.p2wpkh)(pubkey, network).address;
4924
- return (0, import_btc_signer4.p2tr)(pubkey.slice(1, 33), void 0, network).address;
5875
+ if (kind === "p2pkh") return (0, import_btc_signer7.p2pkh)(pubkey, network).address;
5876
+ if (kind === "p2wpkh") return (0, import_btc_signer7.p2wpkh)(pubkey, network).address;
5877
+ return (0, import_btc_signer7.p2tr)(pubkey.slice(1, 33), void 0, network).address;
4925
5878
  }
4926
5879
  function opReturnScript(signalBytes) {
4927
- return import_btc_signer4.Script.encode(["RETURN", signalBytes]);
5880
+ return import_btc_signer7.Script.encode(["RETURN", signalBytes]);
4928
5881
  }
4929
5882
  async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
4930
5883
  const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
@@ -4944,13 +5897,12 @@ async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
4944
5897
  );
4945
5898
  }
4946
5899
  const prevTxHex = await bitcoin.rest.transaction.getHex(utxo.txid);
4947
- return { utxo, prevTxBytes: (0, import_utils8.hexToBytes)(prevTxHex) };
5900
+ return { utxo, prevTxBytes: (0, import_utils10.hexToBytes)(prevTxHex) };
4948
5901
  }
4949
5902
  async function buildAggregationBeaconTx(opts) {
4950
5903
  const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
4951
5904
  const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
4952
- const tapOut = (0, import_btc_signer4.p2tr)(opts.internalPubkey, void 0, opts.network);
4953
- const witnessScript = tapOut.script;
5905
+ const witnessScript = import_btc_signer7.OutScript.encode((0, import_btc_signer7.Address)(opts.network).decode(opts.beaconAddress));
4954
5906
  const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
4955
5907
  if (BigInt(utxo.value) <= feeSats) {
4956
5908
  throw new BeaconError(
@@ -4959,7 +5911,7 @@ async function buildAggregationBeaconTx(opts) {
4959
5911
  { address: opts.beaconAddress, valueSats: utxo.value, feeSats }
4960
5912
  );
4961
5913
  }
4962
- const tx = new import_btc_signer4.Transaction({ allowUnknownOutputs: true });
5914
+ const tx = new import_btc_signer7.Transaction({ allowUnknownOutputs: true });
4963
5915
  tx.addInput({
4964
5916
  txid: utxo.txid,
4965
5917
  index: utxo.vout,
@@ -4982,16 +5934,16 @@ async function buildAggregationBeaconTx(opts) {
4982
5934
  async function signSingletonInput(tx, inputIdx, kind, signer, prevOutScript, amount) {
4983
5935
  const pubkey = signer.publicKey;
4984
5936
  if (kind === "p2pkh") {
4985
- const sighashType = import_btc_signer4.SigHash.ALL;
5937
+ const sighashType = import_btc_signer7.SigHash.ALL;
4986
5938
  const sighash2 = tx.preimageLegacy(inputIdx, prevOutScript, sighashType);
4987
5939
  const sig2 = signer.sign(sighash2, "ecdsa");
4988
- const sigWithType = (0, import_utils8.concatBytes)(sig2, new Uint8Array([sighashType]));
5940
+ const sigWithType = (0, import_utils10.concatBytes)(sig2, new Uint8Array([sighashType]));
4989
5941
  tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
4990
5942
  tx.finalize();
4991
5943
  return tx.hex;
4992
5944
  }
4993
5945
  if (kind === "p2wpkh") {
4994
- const decoded = import_btc_signer4.OutScript.decode(prevOutScript);
5946
+ const decoded = import_btc_signer7.OutScript.decode(prevOutScript);
4995
5947
  if (decoded.type !== "wpkh") {
4996
5948
  throw new BeaconError(
4997
5949
  `Expected P2WPKH prev-output script, got "${decoded.type}".`,
@@ -4999,16 +5951,16 @@ async function signSingletonInput(tx, inputIdx, kind, signer, prevOutScript, amo
4999
5951
  { kind, observedScriptType: decoded.type }
5000
5952
  );
5001
5953
  }
5002
- const sighashScript = import_btc_signer4.OutScript.encode({ type: "pkh", hash: decoded.hash });
5003
- const sighashType = import_btc_signer4.SigHash.ALL;
5954
+ const sighashScript = import_btc_signer7.OutScript.encode({ type: "pkh", hash: decoded.hash });
5955
+ const sighashType = import_btc_signer7.SigHash.ALL;
5004
5956
  const sighash2 = tx.preimageWitnessV0(inputIdx, sighashScript, sighashType, amount);
5005
5957
  const sig2 = signer.sign(sighash2, "ecdsa");
5006
- const sigWithType = (0, import_utils8.concatBytes)(sig2, new Uint8Array([sighashType]));
5958
+ const sigWithType = (0, import_utils10.concatBytes)(sig2, new Uint8Array([sighashType]));
5007
5959
  tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
5008
5960
  tx.finalize();
5009
5961
  return tx.hex;
5010
5962
  }
5011
- const sighash = tx.preimageWitnessV1(inputIdx, [prevOutScript], import_btc_signer4.SigHash.DEFAULT, [amount]);
5963
+ const sighash = tx.preimageWitnessV1(inputIdx, [prevOutScript], import_btc_signer7.SigHash.DEFAULT, [amount]);
5012
5964
  const sig = signer.sign(sighash, "bip341");
5013
5965
  tx.updateInput(inputIdx, { tapKeySig: sig });
5014
5966
  tx.finalize();
@@ -5029,7 +5981,7 @@ var SinglePartyBeacon = class {
5029
5981
  *
5030
5982
  * Composed from the three extracted phases ({@link buildSinglePartyTx},
5031
5983
  * {@link signSinglePartyTx}, {@link broadcastRawTx}) so each piece can be exercised
5032
- * in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead
5984
+ * in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead:
5033
5985
  * the multi-party path can't share the signing phase, but the tx-construction
5034
5986
  * plumbing (UTXO fetch + OP_RETURN output + change output) is shared.
5035
5987
  *
@@ -5061,7 +6013,7 @@ var SinglePartyBeacon = class {
5061
6013
  *
5062
6014
  * Detects the beacon address script kind (P2PKH / P2WPKH / P2TR) and configures
5063
6015
  * the input accordingly. Validates that the signer's pubkey produces the beacon
5064
- * address under that script kind without this check, a misconfigured caller
6016
+ * address under that script kind: without this check, a misconfigured caller
5065
6017
  * would burn a real UTXO on a tx that fails at broadcast. Fees are computed from
5066
6018
  * the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant, avoiding any probe-sign
5067
6019
  * round-trip.
@@ -5087,17 +6039,17 @@ var SinglePartyBeacon = class {
5087
6039
  { address: opts.beaconAddress, valueSats: opts.utxo.value, feeSats }
5088
6040
  );
5089
6041
  }
5090
- const tx = new import_btc_signer4.Transaction({ allowUnknownOutputs: true });
6042
+ const tx = new import_btc_signer7.Transaction({ allowUnknownOutputs: true });
5091
6043
  let prevOutScript;
5092
6044
  if (kind === "p2pkh") {
5093
- prevOutScript = (0, import_btc_signer4.p2pkh)(pubkey, network).script;
6045
+ prevOutScript = (0, import_btc_signer7.p2pkh)(pubkey, network).script;
5094
6046
  tx.addInput({
5095
6047
  txid: opts.utxo.txid,
5096
6048
  index: opts.utxo.vout,
5097
6049
  nonWitnessUtxo: opts.prevTxBytes
5098
6050
  });
5099
6051
  } else if (kind === "p2wpkh") {
5100
- prevOutScript = (0, import_btc_signer4.p2wpkh)(pubkey, network).script;
6052
+ prevOutScript = (0, import_btc_signer7.p2wpkh)(pubkey, network).script;
5101
6053
  tx.addInput({
5102
6054
  txid: opts.utxo.txid,
5103
6055
  index: opts.utxo.vout,
@@ -5106,7 +6058,7 @@ var SinglePartyBeacon = class {
5106
6058
  });
5107
6059
  } else {
5108
6060
  const internalKey = pubkey.slice(1, 33);
5109
- prevOutScript = (0, import_btc_signer4.p2tr)(internalKey, void 0, network).script;
6061
+ prevOutScript = (0, import_btc_signer7.p2tr)(internalKey, void 0, network).script;
5110
6062
  tx.addInput({
5111
6063
  txid: opts.utxo.txid,
5112
6064
  index: opts.utxo.vout,
@@ -5295,7 +6247,7 @@ var SingletonBeacon = class extends SinglePartyBeacon {
5295
6247
  // src/core/beacon/smt-beacon.ts
5296
6248
  var import_common15 = require("@did-btcr2/common");
5297
6249
  var import_smt3 = require("@did-btcr2/smt");
5298
- var import_utils9 = require("@noble/hashes/utils");
6250
+ var import_utils11 = require("@noble/hashes/utils");
5299
6251
  var SMTBeacon = class extends SinglePartyBeacon {
5300
6252
  /**
5301
6253
  * Creates an instance of SMTBeacon.
@@ -5384,7 +6336,7 @@ var SMTBeacon = class extends SinglePartyBeacon {
5384
6336
  async broadcastSignal(signedUpdate, signer, bitcoin, options) {
5385
6337
  const did = this.service.id.split("#")[0];
5386
6338
  const canonicalBytes = new TextEncoder().encode((0, import_common15.canonicalize)(signedUpdate));
5387
- const nonce = (0, import_utils9.randomBytes)(32);
6339
+ const nonce = (0, import_utils11.randomBytes)(32);
5388
6340
  const tree = new import_smt3.BTCR2MerkleTree();
5389
6341
  tree.addEntries([{ did, nonce, signedUpdate: canonicalBytes }]);
5390
6342
  tree.finalize();
@@ -5415,13 +6367,13 @@ var BeaconFactory = class {
5415
6367
  };
5416
6368
 
5417
6369
  // src/core/beacon/signal-discovery.ts
5418
- var import_bitcoin3 = require("@did-btcr2/bitcoin");
6370
+ var import_bitcoin5 = require("@did-btcr2/bitcoin");
5419
6371
  var import_common18 = require("@did-btcr2/common");
5420
6372
 
5421
6373
  // src/core/beacon/utils.ts
5422
- var import_bitcoin2 = require("@did-btcr2/bitcoin");
6374
+ var import_bitcoin4 = require("@did-btcr2/bitcoin");
5423
6375
  var import_common17 = require("@did-btcr2/common");
5424
- var import_btc_signer5 = require("@scure/btc-signer");
6376
+ var import_btc_signer8 = require("@scure/btc-signer");
5425
6377
 
5426
6378
  // src/utils/appendix.ts
5427
6379
  var import_dids = require("@web5/dids");
@@ -6526,10 +7478,10 @@ var BeaconUtils = class {
6526
7478
  static createBeaconService(did, addressType, beaconType) {
6527
7479
  try {
6528
7480
  const components = Identifier.decode(did);
6529
- const network = (0, import_bitcoin2.getNetwork)(components.network);
7481
+ const network = (0, import_bitcoin4.getNetwork)(components.network);
6530
7482
  const pubkey = components.genesisBytes;
6531
7483
  const id = `${did}#initial${addressType.toUpperCase()}`;
6532
- 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;
7484
+ const address = addressType === "p2tr" ? (0, import_btc_signer8.p2tr)(pubkey.slice(1, 33), void 0, network).address : addressType === "p2wpkh" ? (0, import_btc_signer8.p2wpkh)(pubkey, network).address : (0, import_btc_signer8.p2pkh)(pubkey, network).address;
6533
7485
  const serviceEndpoint = `bitcoin:${address}`;
6534
7486
  return { id, type: beaconType, serviceEndpoint };
6535
7487
  } catch (error) {
@@ -6548,9 +7500,9 @@ var BeaconUtils = class {
6548
7500
  */
6549
7501
  static generateBeaconServices({ id, publicKey, network, beaconType }) {
6550
7502
  try {
6551
- const p2pkhAddr = (0, import_btc_signer5.p2pkh)(publicKey, network).address;
6552
- const p2wpkhAddr = (0, import_btc_signer5.p2wpkh)(publicKey, network).address;
6553
- const p2trAddr = (0, import_btc_signer5.p2tr)(publicKey.slice(1, 33), void 0, network).address;
7503
+ const p2pkhAddr = (0, import_btc_signer8.p2pkh)(publicKey, network).address;
7504
+ const p2wpkhAddr = (0, import_btc_signer8.p2wpkh)(publicKey, network).address;
7505
+ const p2trAddr = (0, import_btc_signer8.p2tr)(publicKey.slice(1, 33), void 0, network).address;
6554
7506
  if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
6555
7507
  throw new import_common17.DidMethodError("Failed to generate bitcoin addresses");
6556
7508
  }
@@ -6677,14 +7629,14 @@ var BeaconSignalDiscovery = class {
6677
7629
  console.info(`Searching for beacon signals, please wait ...`);
6678
7630
  while (block.height <= targetHeight) {
6679
7631
  for (const tx of block.tx) {
6680
- if (tx.txid === import_bitcoin3.GENESIS_TX_ID) {
7632
+ if (tx.txid === import_bitcoin5.GENESIS_TX_ID) {
6681
7633
  continue;
6682
7634
  }
6683
7635
  for (const vin of tx.vin) {
6684
7636
  if (vin.coinbase) {
6685
7637
  continue;
6686
7638
  }
6687
- if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] === import_bitcoin3.TXIN_WITNESS_COINBASE) {
7639
+ if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] === import_bitcoin5.TXIN_WITNESS_COINBASE) {
6688
7640
  continue;
6689
7641
  }
6690
7642
  if (!vin.txid) {
@@ -6738,7 +7690,7 @@ var BeaconSignalDiscovery = class {
6738
7690
  };
6739
7691
 
6740
7692
  // src/core/resolver.ts
6741
- var import_bitcoin5 = require("@did-btcr2/bitcoin");
7693
+ var import_bitcoin7 = require("@did-btcr2/bitcoin");
6742
7694
  var import_common22 = require("@did-btcr2/common");
6743
7695
  var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
6744
7696
  var import_keypair8 = require("@did-btcr2/keypair");
@@ -6752,11 +7704,11 @@ var import_common20 = require("@did-btcr2/common");
6752
7704
  var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
6753
7705
 
6754
7706
  // src/utils/did-document.ts
6755
- var import_bitcoin4 = require("@did-btcr2/bitcoin");
7707
+ var import_bitcoin6 = require("@did-btcr2/bitcoin");
6756
7708
  var import_common19 = require("@did-btcr2/common");
6757
7709
  var import_keypair7 = require("@did-btcr2/keypair");
6758
- var import_utils11 = require("@web5/dids/utils");
6759
- var import_btc_signer6 = require("@scure/btc-signer");
7710
+ var import_utils13 = require("@web5/dids/utils");
7711
+ var import_btc_signer9 = require("@scure/btc-signer");
6760
7712
  var BTCR2_DID_DOCUMENT_CONTEXT = [
6761
7713
  "https://www.w3.org/ns/did/v1.1",
6762
7714
  "https://btcr2.dev/context/v1"
@@ -6965,7 +7917,7 @@ var DidDocument = class _DidDocument {
6965
7917
  * @returns {boolean} True if the services are valid.
6966
7918
  */
6967
7919
  static isValidServices(service) {
6968
- return Array.isArray(service) && service.every(import_utils11.isDidService);
7920
+ return Array.isArray(service) && service.every(import_utils13.isDidService);
6969
7921
  }
6970
7922
  /**
6971
7923
  * Validates verification relationships (authentication, assertionMethod, capabilityInvocation, capabilityDelegation).
@@ -7078,7 +8030,7 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
7078
8030
  static fromPublicKey(publicKey, network) {
7079
8031
  const pk = new import_keypair7.CompressedSecp256k1PublicKey(publicKey);
7080
8032
  const id = ID_PLACEHOLDER_VALUE;
7081
- const address = (0, import_btc_signer6.p2pkh)(pk.compressed, (0, import_bitcoin4.getNetwork)(network)).address;
8033
+ const address = (0, import_btc_signer9.p2pkh)(pk.compressed, (0, import_bitcoin6.getNetwork)(network)).address;
7082
8034
  const services = [{
7083
8035
  id: `${id}#service-0`,
7084
8036
  serviceEndpoint: `bitcoin:${address}`,
@@ -7127,7 +8079,7 @@ var Updater = class _Updater {
7127
8079
  #verificationMethod;
7128
8080
  #beaconService;
7129
8081
  /**
7130
- * @internal Use {@link DidBtcr2.update} to create instances.
8082
+ * @internal Use {@link DidBtcr2.update} to create instances.
7131
8083
  */
7132
8084
  constructor(params) {
7133
8085
  this.#sourceDocument = params.sourceDocument;
@@ -7164,7 +8116,7 @@ var Updater = class _Updater {
7164
8116
  const targetDocument = import_common20.JSONPatch.apply(sourceDocument, patches);
7165
8117
  if (targetDocument.id !== sourceDocument.id) {
7166
8118
  throw new import_common20.UpdateError(
7167
- `Patches must not change the DID document id (source "${sourceDocument.id}" \u2192 target "${targetDocument.id}").`,
8119
+ `Patches must not change the DID document id (source "${sourceDocument.id}" to target "${targetDocument.id}").`,
7168
8120
  import_common20.INVALID_DID_UPDATE,
7169
8121
  { sourceId: sourceDocument.id, targetId: targetDocument.id }
7170
8122
  );
@@ -7248,8 +8200,8 @@ var Updater = class _Updater {
7248
8200
  }
7249
8201
  /**
7250
8202
  * Advance the state machine. Returns either:
7251
- * - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
7252
- * - `{ status: 'complete', result }` update is signed and broadcast
8203
+ * - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
8204
+ * - `{ status: 'complete', result }` update is signed and broadcast
7253
8205
  */
7254
8206
  advance() {
7255
8207
  while (true) {
@@ -7262,7 +8214,7 @@ var Updater = class _Updater {
7262
8214
  continue;
7263
8215
  }
7264
8216
  // Phase: Sign
7265
- // Emit NeedSigningKey the caller supplies the secret key (or a KMS signature).
8217
+ // Emit NeedSigningKey: the caller supplies the secret key (or a KMS signature).
7266
8218
  case "Sign": {
7267
8219
  return {
7268
8220
  status: "action-required",
@@ -7445,8 +8397,8 @@ var DidBtcr2 = class {
7445
8397
  *
7446
8398
  * Factory method that validates the update parameters and returns a sans-I/O
7447
8399
  * {@link Updater} state machine. The caller drives the updater through its
7448
- * phases (Construct Sign Broadcast Complete) by calling `advance()` and
7449
- * `provide()`. The method package performs **zero I/O** signing key retrieval
8400
+ * phases (Construct -> Sign -> Broadcast -> Complete) by calling `advance()` and
8401
+ * `provide()`. The method package performs **zero I/O**: signing key retrieval
7450
8402
  * (or KMS delegation) and the on-chain broadcast are the caller's responsibility.
7451
8403
  *
7452
8404
  * For a fully-wired version with Bitcoin broadcast and key handling, see
@@ -7545,7 +8497,7 @@ var DidBtcr2 = class {
7545
8497
  };
7546
8498
 
7547
8499
  // src/core/resolver.ts
7548
- var import_utils13 = require("@noble/curves/utils.js");
8500
+ var import_utils15 = require("@noble/curves/utils.js");
7549
8501
  var Resolver = class _Resolver {
7550
8502
  // --- Immutable inputs ---
7551
8503
  #didComponents;
@@ -7564,7 +8516,7 @@ var Resolver = class _Resolver {
7564
8516
  #unsortedUpdates = [];
7565
8517
  #resolvedResponse = null;
7566
8518
  /**
7567
- * @internal Use {@link DidBtcr2.resolve} to create instances.
8519
+ * @internal Use {@link DidBtcr2.resolve} to create instances.
7568
8520
  */
7569
8521
  constructor(didComponents, sidecarData, currentDocument, options) {
7570
8522
  this.#didComponents = didComponents;
@@ -7589,7 +8541,7 @@ var Resolver = class _Resolver {
7589
8541
  const service = BeaconUtils.generateBeaconServices({
7590
8542
  id: did,
7591
8543
  publicKey: genesisBytes,
7592
- network: (0, import_bitcoin5.getNetwork)(didComponents.network),
8544
+ network: (0, import_bitcoin7.getNetwork)(didComponents.network),
7593
8545
  beaconType: "SingletonBeacon"
7594
8546
  });
7595
8547
  return new DidDocument({
@@ -7612,7 +8564,7 @@ var Resolver = class _Resolver {
7612
8564
  */
7613
8565
  static external(didComponents, genesisDocument) {
7614
8566
  const genesisDocumentHash = (0, import_common22.canonicalHashBytes)(genesisDocument);
7615
- if (!(0, import_utils13.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
8567
+ if (!(0, import_utils15.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
7616
8568
  throw new import_common22.ResolveError(
7617
8569
  `Initial document mismatch: genesisBytes !== genesisDocumentHash`,
7618
8570
  import_common22.INVALID_DID_DOCUMENT,
@@ -7689,7 +8641,7 @@ var Resolver = class _Resolver {
7689
8641
  this.confirmDuplicate(update, updateHashHistory);
7690
8642
  } else if (update.targetVersionId === currentVersionId + 1) {
7691
8643
  const sourceHashBytes = (0, import_common22.decode)(update.sourceHash, "base64urlnopad");
7692
- if (!(0, import_utils13.equalBytes)(sourceHashBytes, currentDocumentHash)) {
8644
+ if (!(0, import_utils15.equalBytes)(sourceHashBytes, currentDocumentHash)) {
7693
8645
  throw new import_common22.ResolveError(
7694
8646
  `Hash mismatch: update.sourceHash !== currentDocumentHash`,
7695
8647
  import_common22.INVALID_DID_UPDATE,
@@ -7736,7 +8688,7 @@ var Resolver = class _Resolver {
7736
8688
  const { proof: _, ...unsignedUpdate } = update;
7737
8689
  const unsignedUpdateHash = (0, import_common22.canonicalHashBytes)(unsignedUpdate);
7738
8690
  const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
7739
- if (!(0, import_utils13.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
8691
+ if (!(0, import_utils15.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
7740
8692
  throw new import_common22.ResolveError(
7741
8693
  `Invalid duplicate: unsigned update hash does not match historical hash`,
7742
8694
  import_common22.LATE_PUBLISHING_ERROR,
@@ -7789,7 +8741,7 @@ var Resolver = class _Resolver {
7789
8741
  DidDocument.validate(updatedDocument);
7790
8742
  const currentDocumentHash = (0, import_common22.canonicalHashBytes)(updatedDocument);
7791
8743
  const updateTargetHash = (0, import_common22.decode)(update.targetHash);
7792
- if (!(0, import_utils13.equalBytes)(updateTargetHash, currentDocumentHash)) {
8744
+ if (!(0, import_utils15.equalBytes)(updateTargetHash, currentDocumentHash)) {
7793
8745
  throw new import_common22.ResolveError(
7794
8746
  `Invalid update: update.targetHash !== currentDocumentHash`,
7795
8747
  import_common22.INVALID_DID_UPDATE,
@@ -7800,8 +8752,8 @@ var Resolver = class _Resolver {
7800
8752
  }
7801
8753
  /**
7802
8754
  * Advance the state machine. Returns either:
7803
- * - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
7804
- * - `{ status: 'resolved', result }` resolution complete
8755
+ * - `{ status: 'action-required', needs }` - caller must provide data via {@link provide}
8756
+ * - `{ status: 'resolved', result }` - resolution complete
7805
8757
  *
7806
8758
  * Analogous to Rust's `Resolver::resolve()`.
7807
8759
  */
@@ -8039,9 +8991,11 @@ var DidDocumentBuilder = class {
8039
8991
  DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
8040
8992
  DEFAULT_BROADCAST_LOOKBACK_MS,
8041
8993
  DEFAULT_CLOCK_SKEW_SEC,
8994
+ DEFAULT_FUNDING_MODEL,
8042
8995
  DEFAULT_MAX_UPDATE_SIZE_BYTES,
8043
8996
  DEFAULT_NONCE_LEN_BYTES,
8044
8997
  DEFAULT_NOSTR_RELAYS,
8998
+ DEFAULT_RECOVERY_SEQUENCE,
8045
8999
  DID_REGEX,
8046
9000
  DISTRIBUTE_AGGREGATED_DATA,
8047
9001
  DidBtcr2,
@@ -8050,6 +9004,8 @@ var DidDocumentBuilder = class {
8050
9004
  DidDocumentBuilder,
8051
9005
  DidVerificationMethod,
8052
9006
  Document,
9007
+ FALLBACK_AUTHORIZATION_REQUEST,
9008
+ FALLBACK_SIGNATURE,
8053
9009
  GenesisDocument,
8054
9010
  HTTP_ENVELOPE_VERSION,
8055
9011
  HTTP_ROUTE,
@@ -8063,7 +9019,9 @@ var DidDocumentBuilder = class {
8063
9019
  InMemoryTransport,
8064
9020
  InboxBuffer,
8065
9021
  KNOWN_BEACON_TYPES,
9022
+ KNOWN_FUNDING_MODELS,
8066
9023
  KeyPairAggregationSigner,
9024
+ MAX_RECOVERY_SEQUENCE,
8067
9025
  NONCE_CONTRIBUTION,
8068
9026
  NonceCache,
8069
9027
  NostrTransport,
@@ -8080,6 +9038,7 @@ var DidDocumentBuilder = class {
8080
9038
  SMTBeacon,
8081
9039
  SMTBeaconError,
8082
9040
  SSE_EVENT,
9041
+ SUBMIT_NONINCLUDED,
8083
9042
  SUBMIT_UPDATE,
8084
9043
  ServiceCohortPhase,
8085
9044
  SigningSessionError,
@@ -8088,6 +9047,7 @@ var DidDocumentBuilder = class {
8088
9047
  SingletonBeacon,
8089
9048
  SingletonBeaconError,
8090
9049
  StaticFeeEstimator,
9050
+ TAPROOT_LEAF_VERSION,
8091
9051
  TransportAdapterError,
8092
9052
  TransportError,
8093
9053
  TransportFactory,
@@ -8095,6 +9055,11 @@ var DidDocumentBuilder = class {
8095
9055
  Updater,
8096
9056
  VALIDATION_ACK,
8097
9057
  buildAggregationBeaconTx,
9058
+ buildFallbackLeaf,
9059
+ buildFallbackSpend,
9060
+ buildRecoveryLeaves,
9061
+ buildRecoveryScript,
9062
+ buildRecoverySpend,
8098
9063
  buildRequestAuth,
8099
9064
  createAggregatedNonceMessage,
8100
9065
  createAuthorizationRequestMessage,
@@ -8103,13 +9068,17 @@ var DidDocumentBuilder = class {
8103
9068
  createCohortOptInMessage,
8104
9069
  createCohortReadyMessage,
8105
9070
  createDistributeAggregatedDataMessage,
9071
+ createFallbackAuthorizationRequestMessage,
9072
+ createFallbackSignatureMessage,
8106
9073
  createNonceContributionMessage,
8107
9074
  createSignatureAuthorizationMessage,
9075
+ createSubmitNonIncludedMessage,
8108
9076
  createSubmitUpdateMessage,
8109
9077
  createValidationAckMessage,
8110
9078
  defaultReconnectBackoff,
8111
9079
  deriveSingletonAddress,
8112
9080
  detectSingletonScriptKind,
9081
+ fallbackSighash,
8113
9082
  formatSseComment,
8114
9083
  formatSseEvent,
8115
9084
  getBeaconStrategy,
@@ -8121,10 +9090,13 @@ var DidDocumentBuilder = class {
8121
9090
  isCohortOptInMessage,
8122
9091
  isCohortReadyMessage,
8123
9092
  isDistributeAggregatedDataMessage,
9093
+ isFallbackAuthorizationRequestMessage,
9094
+ isFallbackSignatureMessage,
8124
9095
  isKeygenMessageType,
8125
9096
  isNonceContributionMessage,
8126
9097
  isSignMessageType,
8127
9098
  isSignatureAuthorizationMessage,
9099
+ isSubmitNonIncludedMessage,
8128
9100
  isSubmitUpdateMessage,
8129
9101
  isUpdateMessageType,
8130
9102
  isValidationAckMessage,
@@ -8133,8 +9105,10 @@ var DidDocumentBuilder = class {
8133
9105
  parseRequestAuth,
8134
9106
  parseSseStream,
8135
9107
  registerBeaconStrategy,
9108
+ resolveFallbackThreshold,
8136
9109
  reviveFromWire,
8137
9110
  signEnvelope,
9111
+ tapLeafHash,
8138
9112
  validateCohortConditions,
8139
9113
  verifyEnvelope,
8140
9114
  verifyRequestAuth